🔥 remove remaining legacy saas code

This commit is contained in:
Lucas Bickel 2019-08-18 15:57:24 +02:00
parent e232469551
commit 0f5cb8b1f8
123 changed files with 10 additions and 10171 deletions

View file

@ -1,439 +0,0 @@
<?php
define("AIRTIME_PRO_FREE_TRIAL_PLAN_ID", 34);
define("WHMCS_AIRTIME_GROUP_ID", 15);
class Billing
{
// TODO: remove this once all existing customers have bandwidth limits set
public static $PLAN_TYPE_DEFAULTS = array(
"trial" => array(
"bandwidth_limit" => 3298534883328
),
"hobbyist" => array(
"bandwidth_limit" => 1099511627776
),
"starter" => array(
"bandwidth_limit" => 3298534883328
),
"starter2" => array(
"bandwidth_limit" => 3298534883328
),
"plus" => array(
"bandwidth_limit" => 10995116277760
),
"plus2" => array(
"bandwidth_limit" => 10995116277760
),
"premium" => array(
"bandwidth_limit" => 43980465111040
),
"premium2" => array(
"bandwidth_limit" => 43980465111040
),
"enterprise" => array(
"bandwidth_limit" => 164926744166400
),
"complimentary" => array(
"bandwidth_limit" => 32985348833280
),
"sida" => array(
"bandwidth_limit" => 32985348833280
),
"custom" => array(
"bandwidth_limit" => 10995116277760
),
"awesome-hobbyist-2015" => array(
"bandwidth_limit" => 1099511627776
),
"awesome-starter-2015" => array(
"bandwidth_limit" => 3298534883328
),
"awesome-plus-2015" => array(
"bandwidth_limit" => 10995116277760
),
"awesome-premium-2015" => array(
"bandwidth_limit" => 43980465111040
),
);
public static function getAPICredentials()
{
return array(
"username" => $_SERVER["WHMCS_USERNAME"],
"password" => $_SERVER["WHMCS_PASSWORD"],
"url" => "https://account.sourcefabric.com/includes/api.php?accesskey=".$_SERVER["WHMCS_ACCESS_KEY"],
);
}
/** Get the Airtime instance ID of the instance the customer is currently viewing. */
public static function getClientInstanceId()
{
//$currentProduct = Billing::getClientCurrentAirtimeProduct();
//return $currentProduct["id"];
//XXX: Major hack attack. Since this function gets called often, rather than querying WHMCS
// we're just going to extract it from airtime.conf since it's the same as the rabbitmq username.
$CC_CONFIG = Config::getConfig();
$instanceId = $CC_CONFIG['rabbitmq']['user'];
if (!is_numeric($instanceId)) {
throw new Exception("Invalid instance id in " . __FUNCTION__ . ": " . $instanceId);
}
return $instanceId;
}
public static function getProducts()
{
//Making this static to cache the products during a single HTTP request.
//This saves us roundtrips to WHMCS if getProducts() is called multiple times.
static $products = array();
if (!empty($products))
{
return $products;
}
$credentials = self::getAPICredentials();
$postfields = array();
$postfields["username"] = $credentials["username"];
$postfields["password"] = md5($credentials["password"]);
$postfields["action"] = "getproducts";
$postfields["responsetype"] = "json";
//gid is the Airtime product group id on whmcs
$postfields["gid"] = WHMCS_AIRTIME_GROUP_ID;
$query_string = "";
foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&";
$result = self::makeRequest($credentials["url"], $query_string);
//Logging::info($result["products"]["product"]);
$products = $result["products"]["product"];
//Blacklist all free plans
//Hide the promo plans - we will tell the user if they are eligible for a promo plan
foreach ($products as $k => $p) {
if ($p["paytype"] === "free" || strpos($p["name"], "Awesome August 2015") !== false)
{
unset($products[$k]);
}
}
return $products;
}
public static function getProductPricesAndTypes()
{
$products = Billing::getProducts();
$productPrices = array();
$productTypes = array();
foreach ($products as $k => $p) {
$productPrices[$p["name"]] = array(
"monthly" => $p["pricing"]["USD"]["monthly"],
"annually" => $p["pricing"]["USD"]["annually"]
);
$productTypes[$p["pid"]] = $p["name"] . " ($" . $productPrices[$p['name']]['monthly'] . "/mo)";
}
return array($productPrices, $productTypes);
}
/** Get the plan (or product in WHMCS lingo) that the customer is currently on.
* @return An associative array containing the fields for the product
* */
public static function getClientCurrentAirtimeProduct()
{
static $airtimeProduct = null;
//Ghetto caching to avoid multiple round trips to WHMCS
if ($airtimeProduct) {
return $airtimeProduct;
}
$credentials = self::getAPICredentials();
$postfields = array();
$postfields["username"] = $credentials["username"];
$postfields["password"] = md5($credentials["password"]);
$postfields["action"] = "getclientsproducts";
$postfields["responsetype"] = "json";
$postfields["clientid"] = Application_Model_Preference::GetClientId();
$query_string = "";
foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&";
$result = self::makeRequest($credentials["url"], $query_string);
//XXX: Debugging / local testing
if ($_SERVER['SERVER_NAME'] == "localhost") {
$_SERVER['SERVER_NAME'] = "bananas.airtime.pro";
}
//This code must run on airtime.pro for it to work... it's trying to match
//the server's hostname with the client subdomain. Once it finds a match
//between the product and the server's hostname/subdomain, then it
//returns the ID of that product (aka. the service ID of an Airtime instance)
foreach ($result["products"]["product"] as $product)
{
if (strpos($product["groupname"], "Airtime") === FALSE)
{
//Ignore non-Airtime products
continue;
}
else
{
if ($product["status"] === "Active" ||
$product["status"] === "Suspended") {
$airtimeProduct = $product;
$subdomain = '';
foreach ($airtimeProduct['customfields']['customfield'] as $customField) {
if ($customField['name'] === SUBDOMAIN_WHMCS_CUSTOM_FIELD_NAME) {
$subdomain = $customField['value'];
if (($subdomain . ".airtime.pro") === $_SERVER['SERVER_NAME']) {
return $airtimeProduct;
}
}
}
}
}
}
throw new Exception("Unable to match subdomain to a service ID");
}
public static function getClientDetails()
{
try {
$credentials = self::getAPICredentials();
$postfields = array();
$postfields["username"] = $credentials["username"];
$postfields["password"] = md5($credentials["password"]);
$postfields["action"] = "getclientsdetails";
$postfields["stats"] = true;
$postfields["clientid"] = Application_Model_Preference::GetClientId();
$postfields["responsetype"] = "json";
$query_string = "";
foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&";
$arr = self::makeRequest($credentials["url"], $query_string);
return $arr["client"];
} catch (Exception $e) {
Logging::info($e->getMessage());
}
return array();
}
public static function makeRequest($url, $query_string) {
try {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); // WHMCS IP whitelist doesn't support IPv6
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 5); //Aggressive 5 second timeout
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $query_string);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$jsondata = curl_exec($ch);
if (curl_error($ch)) {
//die("Connection Error: ".curl_errno($ch).' - '.curl_error($ch));
throw new Exception("WHMCS server down or invalid request.");
}
curl_close($ch);
return json_decode($jsondata, true);
} catch (Exception $e) {
Logging::info($e->getMessage());
}
return array();
}
public static function ensureClientIdIsValid()
{
if (Application_Model_Preference::GetClientId() == null)
{
throw new Exception("Invalid client ID: " . Application_Model_Preference::GetClientId());
}
}
/**
* @return True if VAT should be applied to the order, false otherwise.
*/
public static function checkIfVatShouldBeApplied($vatNumber, $countryCode)
{
if ($countryCode === 'UK') {
$countryCode = 'GB'; //VIES database has it as GB
}
//We don't charge you VAT if you're not in the EU
if (!Billing::isCountryInEU($countryCode))
{
return false;
}
//So by here, we know you're in the EU.
//No VAT number? Then we charge you VAT.
if (empty($vatNumber)) {
return true;
}
//Check if VAT number is valid
return Billing::validateVATNumber($vatNumber, $countryCode);
}
public static function isCountryInEU($countryCode)
{
$euCountryCodes = array('BE', 'BG', 'CZ', 'DK', 'DE', 'EE', 'IE', 'EL', 'ES', 'FR',
'HR', 'IT', 'CY', 'LV', 'LT', 'LU', 'HU', 'MT', 'NL', 'AT',
'PL', 'PT', 'RO', 'SI', 'SK', 'FI', 'SE', 'UK', 'GB');
if (!in_array($countryCode, $euCountryCodes)) {
return false;
}
return true;
}
/**
* Check if an EU VAT number is valid, using the EU VIES validation web API.
*
* @param string $vatNumber - A VAT identifier (number), with or without the two letter country code at the
* start (either one works) .
* @param string $countryCode - A two letter country code
* @return boolean true if the VAT number is valid, false otherwise.
*/
public static function validateVATNumber($vatNumber, $countryCode)
{
$vatNumber = str_replace(array(' ', '.', '-', ',', ', '), '', trim($vatNumber));
//If the first two letters are a country code, use that as the country code and remove those letters.
$firstTwoCharacters = substr($vatNumber, 0, 2);
if (preg_match("/[a-zA-Z][a-zA-Z]/", $firstTwoCharacters) === 1) {
$countryCode = strtoupper($firstTwoCharacters); //The country code from the VAT number overrides your country.
$vatNumber = substr($vatNumber, 2);
}
$client = new SoapClient("http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl");
if($client){
$params = array('countryCode' => $countryCode, 'vatNumber' => $vatNumber);
try{
$r = $client->checkVat($params);
if($r->valid == true){
// VAT-ID is valid
return true;
} else {
// VAT-ID is NOT valid
return false;
}
} catch(SoapFault $e) {
Logging::error('VIES EU VAT validation error: '.$e->faultstring);
if ($e->faultstring == "INVALID_INPUT") {
return false;
}
//If there was another error with the VAT validation service, we allow
//the VAT number to pass. (eg. SERVER_BUSY, MS_UNAVAILABLE, TIMEOUT, SERVICE_UNAVAILABLE)
return true;
}
} else {
// Connection to host not possible, europe.eu down?
Logging::error('VIES EU VAT validation error: Host unreachable');
//If there was an error with the VAT validation service, we allow
//the VAT number to pass.
return true;
}
return false;
}
public static function addVatToInvoice($invoice_id)
{
$credentials = self::getAPICredentials();
//First we need to get the invoice details: sub total, and total
//so we can calcuate the amount of VAT to add
$invoicefields = array();
$invoicefields["username"] = $credentials["username"];
$invoicefields["password"] = md5($credentials["password"]);
$invoicefields["action"] = "getinvoice";
$invoicefields["invoiceid"] = $invoice_id;
$invoicefields["responsetype"] = "json";
$invoice_query_string = "";
foreach ($invoicefields as $k=>$v) $invoice_query_string .= "$k=".urlencode($v)."&";
//TODO: error checking
$result = Billing::makeRequest($credentials["url"], $invoice_query_string);
$vat_amount = $result["subtotal"] * (VAT_RATE/100);
$invoice_total = $result["total"] + $vat_amount;
//Second, update the invoice with the VAT amount and updated total
$postfields = array();
$postfields["username"] = $credentials["username"];
$postfields["password"] = md5($credentials["password"]);
$postfields["action"] = "updateinvoice";
$postfields["invoiceid"] = $invoice_id;
$postfields["tax"] = "$vat_amount";
$postfields["taxrate"] = strval(VAT_RATE);
$postfields["total"] = "$invoice_total";
$postfields["responsetype"] = "json";
$query_string = "";
foreach ($postfields as $k=>$v) $query_string .= "$k=".urlencode($v)."&";
//TODO: error checking
$result = Billing::makeRequest($credentials["url"], $query_string);
}
public static function getInvoices()
{
Billing::ensureClientIdIsValid();
$credentials = Billing::getAPICredentials();
$postfields = array();
$postfields["username"] = $credentials["username"];
$postfields["password"] = md5($credentials["password"]);
$postfields["action"] = "getinvoices";
$postfields["responsetype"] = "json";
$postfields["userid"] = Application_Model_Preference::GetClientId();
$query_string = "";
foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&";
$result = Billing::makeRequest($credentials["url"], $query_string);
$invoices = array();
if ($result["invoices"]) {
$invoices = $result["invoices"]["invoice"];
}
return $invoices;
}
/**
* Checks if the customer has any unpaid invoices and if so, returns
* the ID of one of them. Returns 0 otherwise.
*/
public static function checkForUnpaidInvoice() {
$invoices = self::getInvoices();
$unpaidInvoice = 0;
$unpaidInvoices = 0;
foreach ($invoices as $invoice)
{
if ($invoice['status'] == 'Unpaid') {
$unpaidInvoices += 1;
$unpaidInvoice = $invoice;
}
}
if ($unpaidInvoices > 0) {
return $unpaidInvoice;
} else {
return 0;
}
}
public static function isStationPodcastAllowed() {
$planLevel = Application_Model_Preference::GetPlanLevel();
if ($planLevel == "hobbyist") {
return false;
} else {
return true;
}
}
}

View file

@ -1,104 +0,0 @@
<?php
class Application_Common_GoogleAnalytics
{
/** Returns a string containing the JavaScript code to pass some billing account info
* into Google Tag Manager / Google Analytics, so we can track things like the plan type.
*/
public static function generateGoogleTagManagerDataLayerJavaScript()
{
$code = "";
if (LIBRETIME_ENABLE_GOOGLE_ANALYTICS !== true) {
return $code;
}
try {
$clientId = Application_Model_Preference::GetClientId();
$plan = Application_Model_Preference::GetPlanLevel();
$isTrial = ($plan == "trial");
//Figure out how long the customer has been around using a mega hack.
//(I'm avoiding another round trip to WHMCS for now...)
//We calculate it based on the trial end date...
$trialEndDateStr = Application_Model_Preference::GetTrialEndingDate();
if ($trialEndDateStr == '') {
$accountDuration = 0;
} else {
$today = new DateTime();
$trialEndDate = new DateTime($trialEndDateStr);
$trialDuration = new DateInterval("P30D"); //30 day trial duration
$accountCreationDate = $trialEndDate->sub($trialDuration);
$interval = $today->diff($accountCreationDate);
$accountDuration = $interval->days;
}
$code = "$( document ).ready(function() {
dataLayer.push({
'UserID': '" . $clientId . "',
'Customer': 'Customer',
'PlanType': '" . $plan . "',
'Trial': '" . $isTrial . "',
'AccountDuration': '" . strval($accountDuration) . "'
});
});";
//No longer sending these variables because we used to make a query to WHMCS
//to fetch them, which was slow.
// 'ZipCode': '" . $postcode . "',
// 'Country': '" . $country . "',
} catch (Exception $e) {
Logging::error($e);
return "";
}
return $code;
}
/** Generate the JavaScript snippet that logs a trial to paid conversion */
public static function generateConversionTrackingJavaScript()
{
$code = "";
if (LIBRETIME_ENABLE_GOOGLE_ANALYTICS !== true) {
return $code;
}
$newPlan = Application_Model_Preference::GetPlanLevel();
$oldPlan = Application_Model_Preference::GetOldPlanLevel();
$code = "dataLayer.push({'event': 'Conversion',
'Conversion': 'Trial to Paid',
'Old Plan' : '$oldPlan',
'New Plan' : '$newPlan'});";
return $code;
}
/** Return true if the user used to be on a trial plan and was just converted to a paid plan. */
public static function didPaidConversionOccur($request)
{
if (LIBRETIME_ENABLE_GOOGLE_ANALYTICS !== true) {
return false;
}
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
if ($userInfo) {
$user = new Application_Model_User($userInfo->id);
} else {
return;
}
$oldPlan = Application_Model_Preference::GetOldPlanLevel();
if ($user->isSuperAdmin() &&
!$user->isSourcefabricAdmin() &&
$request->getControllerKey() !== "thank-you")
{
//Only tracking trial->paid conversions for now.
if ($oldPlan == "trial")
{
return true;
}
}
return false;
}
}

View file

@ -2,10 +2,6 @@
// Global functions for translating domain-specific strings
function _pro($str) {
return dgettext("pro", $str);
}
class Application_Common_LocaleHelper {
/**

View file

@ -1,242 +0,0 @@
<?php
/** This class provides the business logic for station provisioning. */
class ProvisioningHelper
{
/* @var $dbh PDO */
static $dbh;
// Parameter values
private $dbuser, $dbpass, $dbname, $dbhost, $dbowner, $apikey;
private $instanceId;
private $stationName, $description;
private $defaultIcecastPassword;
private $bandwidthLimit;
public function __construct($apikey)
{
$this->apikey = $apikey;
}
/**
* Endpoint for setting up and installing the Airtime database. This all has to be done without Zend
* which is why the code looks so old school (eg. http_response_code). (We can't currently bootstrap our
* Zend app without the database unfortunately.)
*/
public function createAction()
{
$apikey = "";
if (isset($_SERVER['PHP_AUTH_USER']))
{
$apikey = $_SERVER['PHP_AUTH_USER'];
}
if ($apikey != $this->apikey) {
Logging::info("Invalid API Key: $apikey");
http_response_code(403);
echo "ERROR: Incorrect API key";
return;
}
try {
$this->parsePostParams();
//For security, the Airtime Pro provisioning system creates the database for the user.
if ($this->dbhost && !empty($this->dbhost)) {
$this->setNewDatabaseConnection();
if (!$this->checkDatabaseExists()) {
throw new DatabaseDoesNotExistException("ERROR: $this->dbname database does not exist.");
}
//We really want to do this check because all the Propel-generated SQL starts with "DROP TABLE IF EXISTS".
//If we don't check, then a second call to this API endpoint would wipe all the tables!
if ($this->checkTablesExist()) {
throw new DatabaseAlreadyExistsException();
}
$this->createDatabaseTables();
$this->initializeMusicDirsTable($this->instanceId);
}
//$this->createDatabase();
//All we need to do is create the database tables.
$this->initializePrefs();
} catch (DatabaseDoesNotExistException $e) {
http_response_code(400);
Logging::error($e->getMessage());
echo $e->getMessage() . PHP_EOL;
return;
} catch (DatabaseAlreadyExistsException $e) {
// When we recreate a terminated instance, the process will fail
// if we return a 40x response here. In order to circumvent this,
// just return a 200; we still avoid dropping the existing tables
http_response_code(200);
Logging::info($e->getMessage());
echo $e->getMessage() . PHP_EOL;
return;
}
http_response_code(201);
}
/**
* Check if the database settings and credentials given are valid
* @return boolean true if the database given exists and the user is valid and can access it
*/
private function checkDatabaseExists()
{
$statement = self::$dbh->prepare("SELECT datname FROM pg_database WHERE datname = :dbname");
$statement->execute(array(":dbname" => $this->dbname));
$result = $statement->fetch();
return isset($result[0]);
}
private function checkTablesExist()
{
try {
$result = self::$dbh->query("SELECT 1 FROM cc_files LIMIT 1");
} catch (Exception $e) {
// We got an exception == table not found
echo($e . PHP_EOL);
return FALSE;
}
// Result is either boolean FALSE (no table found) or PDOStatement Object (table found)
return $result !== FALSE;
}
private function parsePostParams()
{
$this->dbuser = $_POST['dbuser'];
$this->dbpass = $_POST['dbpass'];
$this->dbname = $_POST['dbname'];
$this->dbhost = $_POST['dbhost'];
$this->dbowner = $_POST['dbowner'];
$this->instanceId = $_POST['instanceid'];
if (isset($_POST['station_name'])) {
$this->stationName = $_POST['station_name'];
}
if (isset($_POST['description'])) {
$this->description = $_POST['description'];
}
if (isset($_POST['icecast_pass'])) {
$this->defaultIcecastPassword = $_POST['icecast_pass'];
}
if (isset($_POST['bandwidth_limit'])) {
$this->bandwidthLimit = $_POST['bandwidth_limit'];
}
}
/**
* Set up a new database connection based on the parameters in the request
* @throws PDOException upon failure to connect
*/
private function setNewDatabaseConnection()
{
self::$dbh = new PDO("pgsql:host=" . $this->dbhost
. ";dbname=" . $this->dbname
. ";port=5432" . ";user=" . $this->dbuser
. ";password=" . $this->dbpass);
//Turn on PDO exceptions because they're off by default.
//self::$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$err = self::$dbh->errorInfo();
if ($err[1] != null) {
throw new PDOException("ERROR: Could not connect to database");
}
}
/**
* Creates the Airtime database using the given credentials
* @throws Exception
*/
private function createDatabase()
{
Logging::info("Creating database...");
$statement = self::$dbh->prepare("CREATE DATABASE " . pg_escape_string($this->dbname)
. " WITH ENCODING 'UTF8' TEMPLATE template0"
. " OWNER " . pg_escape_string($this->dbowner));
if (!$statement->execute()) {
throw new Exception("ERROR: Failed to create Airtime database");
}
}
/**
* Install the Airtime database
* @throws Exception
*/
private function createDatabaseTables()
{
Logging::info("Creating database tables...");
$sqlDir = dirname(APPLICATION_PATH) . "/build/sql/";
$files = array("schema.sql", "sequences.sql", "views.sql", "triggers.sql", "defaultdata.sql");
foreach ($files as $f) {
/*
* Unfortunately, we need to use exec here due to PDO's lack of support for importing
* multi-line .sql files. PDO->exec() almost works, but any SQL errors stop the import,
* so the necessary DROPs on non-existent tables make it unusable. Prepared statements
* have multiple issues; they similarly die on any SQL errors, fail to read in multi-line
* commands, and fail on any unescaped ? or $ characters.
*/
exec("PGPASSWORD=$this->dbpass psql -U $this->dbuser --dbname $this->dbname -h $this->dbhost -f $sqlDir$f", $out, $status);
if ($status != 0) {
throw new Exception("ERROR: Failed to create database tables");
}
}
}
private function initializeMusicDirsTable($instanceId)
{
if (!is_string($instanceId) || empty($instanceId) || !is_numeric($instanceId))
{
throw new Exception("Invalid instance id: " . $instanceId);
}
$instanceIdPrefix = $instanceId[0];
//Reinitialize Propel, just in case...
Propel::init(__DIR__."/../configs/airtime-conf-production.php");
//Create the cc_music_dir entry
$musicDir = new CcMusicDirs();
$musicDir->setType("stor");
$musicDir->setExists(true);
$musicDir->setWatched(true);
$musicDir->setDirectory("/mnt/airtimepro/instances/$instanceIdPrefix/$instanceId/srv/airtime/stor/");
$musicDir->save();
}
/**
* Initialize preference values passed from the dashboard (if any exist)
*/
private function initializePrefs() {
if ($this->stationName) {
Application_Model_Preference::SetStationName($this->stationName);
}
if ($this->description) {
Application_Model_Preference::SetStationDescription($this->description);
}
if (isset($this->defaultIcecastPassword)) {
Application_Model_Preference::setDefaultIcecastPassword($this->defaultIcecastPassword);
}
if (isset($this->bandwidthLimit)) {
Application_Model_Preference::setBandwidthLimit($this->bandwidthLimit);
}
}
}
class DatabaseAlreadyExistsException extends Exception {
private static $_defaultMessage = "ERROR: airtime tables already exists";
public function __construct($message = null, $code = 0, Exception $previous = null) {
$message = _((is_null($message) ? self::$_defaultMessage : $message));
parent::__construct($message, $code, $previous);
}
}
class DatabaseDoesNotExistException extends Exception {}

View file

@ -103,13 +103,6 @@ class Application_Common_UsabilityHints
"<a href=\"/schedule\">",
"</a>");
}
} else if (LIBRETIME_ENABLE_BILLING === true && $userIsOnShowbuilderPage && $userIsSuperAdmin) {
$unpaidInvoice = Billing::checkForUnpaidInvoice();
if ($unpaidInvoice != null) {
$invoiceUrl = "/billing/invoice?invoiceid=" . $unpaidInvoice['id'];
$amount = $unpaidInvoice['currencyprefix'] . $unpaidInvoice['total'];
return _pro(sprintf("You have an unpaid invoice for %s due soon. <a href='%s'>Please pay it to keep your station on the air.</a>", $amount, $invoiceUrl));;
}
}
return "";
}
@ -225,4 +218,4 @@ class Application_Common_UsabilityHints
return false;
}
}
}
}