diff --git a/airtime_mvc/application/configs/ACL.php b/airtime_mvc/application/configs/ACL.php
index 842778065..ff65a9192 100644
--- a/airtime_mvc/application/configs/ACL.php
+++ b/airtime_mvc/application/configs/ACL.php
@@ -7,13 +7,15 @@ $ccAcl = new Zend_Acl();
$ccAcl->addRole(new Zend_Acl_Role('G'))
->addRole(new Zend_Acl_Role('H'), 'G')
->addRole(new Zend_Acl_Role('P'), 'H')
- ->addRole(new Zend_Acl_Role('A'), 'P');
+ ->addRole(new Zend_Acl_Role('A'), 'P')
+ ->addRole(new Zend_Acl_Role('S'), 'A');
$ccAcl->add(new Zend_Acl_Resource('library'))
->add(new Zend_Acl_Resource('index'))
->add(new Zend_Acl_Resource('user'))
->add(new Zend_Acl_Resource('error'))
->add(new Zend_Acl_Resource('login'))
+ ->add(new Zend_Acl_Resource('whmcs-login'))
->add(new Zend_Acl_Resource('playlist'))
->add(new Zend_Acl_Resource('plupload'))
->add(new Zend_Acl_Resource('schedule'))
@@ -29,11 +31,13 @@ $ccAcl->add(new Zend_Acl_Resource('library'))
->add(new Zend_Acl_Resource('audiopreview'))
->add(new Zend_Acl_Resource('webstream'))
->add(new Zend_Acl_Resource('locale'))
- ->add(new Zend_Acl_Resource('upgrade'));
+ ->add(new Zend_Acl_Resource('upgrade'))
+ ->add(new Zend_Acl_Resource('billing'));
/** Creating permissions */
$ccAcl->allow('G', 'index')
->allow('G', 'login')
+ ->allow('G', 'whmcs-login')
->allow('G', 'error')
->allow('G', 'user', 'edit-user')
->allow('G', 'showbuilder')
@@ -54,7 +58,8 @@ $ccAcl->allow('G', 'index')
->allow('A', 'listenerstat')
->allow('A', 'user')
->allow('A', 'systemstatus')
- ->allow('A', 'preference');
+ ->allow('A', 'preference')
+ ->allow('S', 'billing');
$aclPlugin = new Zend_Controller_Plugin_Acl($ccAcl);
diff --git a/airtime_mvc/application/configs/constants.php b/airtime_mvc/application/configs/constants.php
index 34f8cab41..69ce78d19 100644
--- a/airtime_mvc/application/configs/constants.php
+++ b/airtime_mvc/application/configs/constants.php
@@ -41,6 +41,7 @@ define('UI_MDATA_VALUE_FORMAT_STREAM' , 'live stream');
//User types
define('UTYPE_HOST' , 'H');
define('UTYPE_ADMIN' , 'A');
+define('UTYPE_SUPERADMIN' , 'S');
define('UTYPE_GUEST' , 'G');
define('UTYPE_PROGRAM_MANAGER' , 'P');
@@ -63,3 +64,9 @@ define('UI_BLOCK_SESSNAME', 'BLOCK');*/
define('SOUNDCLOUD_NOT_UPLOADED_YET' , -1);
define('SOUNDCLOUD_PROGRESS' , -2);
define('SOUNDCLOUD_ERROR' , -3);
+
+
+//WHMCS integration
+define("WHMCS_API_URL", "https://account.sourcefabric.com/includes/api.php");
+define("SUBDOMAIN_WHMCS_CUSTOM_FIELD_NAME", "Choose your domain");
+
diff --git a/airtime_mvc/application/configs/navigation.php b/airtime_mvc/application/configs/navigation.php
index f3149f146..5b7e4e049 100644
--- a/airtime_mvc/application/configs/navigation.php
+++ b/airtime_mvc/application/configs/navigation.php
@@ -134,6 +134,34 @@ $pages = array(
'resource' => 'dashboard'
)
)
+ ),
+ array(
+ 'label' => _('Billing'),
+ 'uri' => '#',
+ 'resource' => 'billing',
+ 'pages' => array(
+ array(
+ 'label' => _('Account Details'),
+ 'module' => 'default',
+ 'controller' => 'billing',
+ 'action' => 'client',
+ 'resource' => 'billing'
+ ),
+ array(
+ 'label' => _('Account Plans'),
+ 'module' => 'default',
+ 'controller' => 'billing',
+ 'action' => 'upgrade',
+ 'resource' => 'billing'
+ ),
+ array(
+ 'label' => _('View Invoices'),
+ 'module' => 'default',
+ 'controller' => 'billing',
+ 'action' => 'invoices',
+ 'resource' => 'billing'
+ )
+ )
)
);
diff --git a/airtime_mvc/application/controllers/BillingController.php b/airtime_mvc/application/controllers/BillingController.php
new file mode 100644
index 000000000..696c69319
--- /dev/null
+++ b/airtime_mvc/application/controllers/BillingController.php
@@ -0,0 +1,558 @@
+_helper->getHelper('AjaxContext');
+ $ajaxContext->addActionContext('vat-validator', 'json')
+ ->addActionContext('is-country-in-eu', 'json')
+ ->initContext();
+ }
+
+ public function indexAction()
+ {
+
+ }
+
+ public function upgradeAction()
+ {
+ $CC_CONFIG = Config::getConfig();
+ $baseUrl = Application_Common_OsPath::getBaseDir();
+ $this->view->headLink()->appendStylesheet($baseUrl.'css/billing.css?'.$CC_CONFIG['airtime_version']);
+ BillingController::ensureClientIdIsValid();
+
+ $request = $this->getRequest();
+ $form = new Application_Form_BillingUpgradeDowngrade();
+ if ($request->isPost()) {
+
+ /*
+ * TODO: determine if VAT shoould be charged on the invoice or not.
+ * We'll need to check if a VAT number was supplied in the form and if so,
+ * validate it somehow. We'll also need to make sure the country given is
+ * in the EU
+ */
+
+ $formData = $request->getPost();
+ if ($form->isValid($formData)) {
+ $credentials = self::getAPICredentials();
+
+ if (in_array("7", $formData["customfields"])) {
+ $apply_vat = BillingController::checkIfVatShouldBeApplied($formData["customfields"]["7"], $formData["country"]);
+ } else {
+ $apply_vat = false;
+ }
+
+ $postfields = array();
+ $postfields["username"] = $credentials["username"];
+ $postfields["password"] = md5($credentials["password"]);
+ $postfields["action"] = "upgradeproduct";
+ $postfields["clientid"] = Application_Model_Preference::GetClientId();
+
+ $postfields["serviceid"] = self::getClientInstanceId();
+ $postfields["type"] = "product";
+ $postfields["newproductid"] = $formData["newproductid"];
+ $postfields["newproductbillingcycle"] = $formData["newproductbillingcycle"];
+ $postfields["paymentmethod"] = $formData["paymentmethod"];
+ $postfields["responsetype"] = "json";
+
+ $upgrade_query_string = "";
+ foreach ($postfields AS $k=>$v) $upgrade_query_string .= "$k=".urlencode($v)."&";
+
+ //update client info
+
+ $clientfields = array();
+ $clientfields["username"] = $credentials["username"];
+ $clientfields["password"] = md5($credentials["password"]);
+ $clientfields["action"] = "updateclient";
+ $clientfields["clientid"] = Application_Model_Preference::GetClientId();
+ $clientfields["customfields"] = base64_encode(serialize($formData["customfields"]));
+ unset($formData["customfields"]);
+ $clientfields["responsetype"] = "json";
+ unset($formData["newproductid"]);
+ unset($formData["newproductbillingcycle"]);
+ unset($formData["paymentmethod"]);
+ unset($formData["action"]);
+ $clientfields = array_merge($clientfields, $formData);
+ unset($clientfields["password2verify"]);
+ unset($clientfields["submit"]);
+ $client_query_string = "";
+ foreach ($clientfields AS $k=>$v) $client_query_string .= "$k=".urlencode($v)."&";
+
+ $result = $this->makeRequest($credentials["url"], $client_query_string);
+ Logging::info($result);
+ if ($result["result"] == "error") {
+ $this->setErrorMessage();
+ $this->view->form = $form;
+ } else {
+ $result = $this->makeRequest($credentials["url"], $upgrade_query_string);
+ if ($result["result"] == "error") {
+ Logging::info($_SERVER['HTTP_HOST']." - Account upgrade failed. - ".$result["message"]);
+ $this->setErrorMessage();
+ $this->view->form = $form;
+ } else {
+ if ($apply_vat) {
+ $this->addVatToInvoice($result["invoiceid"]);
+ }
+ self::viewInvoice($result["invoiceid"]);
+ }
+ }
+ } else {
+ $this->view->form = $form;
+ }
+ } else {
+ $this->view->form = $form;
+ }
+ }
+
+
+ public function isCountryInEuAction()
+ {
+ // Disable the view and the layout
+ $this->view->layout()->disableLayout();
+ $this->_helper->viewRenderer->setNoRender(true);
+
+ $request = $this->getRequest();
+ if (!$request->isPost()) {
+ throw new Exception("Must POST data to isCountryInEuAction.");
+ }
+ $formData = $request->getPost();
+
+ //Set the return JSON value
+ $this->_helper->json(array("result"=>BillingController::isCountryInEU($formData["country"])));
+ }
+
+ public function vatValidatorAction()
+ {
+ // Disable the view and the layout
+ $this->view->layout()->disableLayout();
+ $this->_helper->viewRenderer->setNoRender(true);
+
+ $request = $this->getRequest();
+ if (!$request->isPost()) {
+ throw new Exception("Must POST data to vatValidatorAction.");
+ }
+ $formData = $request->getPost();
+
+ $vatNumber = trim($formData["vatnumber"]);
+ if (empty($vatNumber)) {
+ $this->_helper->json(array("result"=>false));
+ }
+
+ //Set the return JSON value
+ $this->_helper->json(array("result"=>BillingController::checkIfVatShouldBeApplied($vatNumber, $formData["country"])));
+ }
+
+ /**
+ * @return True if VAT should be applied to the order, false otherwise.
+ */
+ private 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 (!BillingController::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 BillingController::validateVATNumber($vatNumber, $countryCode);
+ }
+
+ private 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.
+ */
+ private 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;
+ }
+
+
+ private 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 = $this->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 = $this->makeRequest($credentials["url"], $query_string);
+ }
+
+ private function setErrorMessage($msg=null)
+ {
+ if (!is_null($msg)) {
+ $this->view->errorMessage = $msg;
+ } else {
+ $this->view->errorMessage = "An error occurred and we could not update your account. Please contact support for help.";
+ }
+ }
+
+ private function setSuccessMessage($msg=null)
+ {
+ if (!is_null($msg)) {
+ $this->view->successMessage = $msg;
+ } else {
+ $this->view->successMessage = "Your account has been updated.";
+ }
+ }
+
+ private 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"],
+ );
+ }
+
+ private static function viewInvoice($invoice_id)
+ {
+ $whmcsurl = "https://account.sourcefabric.com/dologin.php";
+ $autoauthkey = $_SERVER["WHMCS_AUTOAUTH_KEY"];
+ $timestamp = time(); //whmcs timezone?
+ $client = self::getClientDetails();
+ $email = $client["email"];
+ $hash = sha1($email.$timestamp.$autoauthkey);
+ $goto = "viewinvoice.php?id=".$invoice_id;
+ header("Location: ".$whmcsurl."?email=$email×tamp=$timestamp&hash=$hash&goto=$goto");
+ }
+
+ public function clientAction()
+ {
+ $CC_CONFIG = Config::getConfig();
+ $baseUrl = Application_Common_OsPath::getBaseDir();
+ $this->view->headLink()->appendStylesheet($baseUrl.'css/billing.css?'.$CC_CONFIG['airtime_version']);
+
+ $request = $this->getRequest();
+ $form = new Application_Form_BillingClient();
+ BillingController::ensureClientIdIsValid();
+ if ($request->isPost()) {
+ $formData = $request->getPost();
+ if ($form->isValid($formData)) {
+
+ $credentials = self::getAPICredentials();
+
+ $postfields = array();
+ $postfields["username"] = $credentials["username"];
+ $postfields["password"] = md5($credentials["password"]);
+ $postfields["action"] = "updateclient";
+
+ $postfields["customfields"] = base64_encode(serialize($formData["customfields"]));
+ unset($formData["customfields"]);
+
+ $postfields["clientid"] = Application_Model_Preference::GetClientId();
+ $postfields["responsetype"] = "json";
+ $postfields = array_merge($postfields, $formData);
+ unset($postfields["password2verify"]);
+ unset($postfields["submit"]);
+
+ $query_string = "";
+ foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&";
+
+ $result = $this->makeRequest($credentials["url"], $query_string);
+
+ if ($result["result"] == "error") {
+ $this->setErrorMessage();
+ } else {
+ $form = new Application_Form_BillingClient();
+ $this->setSuccessMessage();
+ }
+
+ $this->view->form = $form;
+ } else {
+ $this->view->form = $form;
+ }
+ } else {
+ $this->view->form = $form;
+ }
+ }
+
+ public function invoicesAction()
+ {
+ $CC_CONFIG = Config::getConfig();
+ $baseUrl = Application_Common_OsPath::getBaseDir();
+ $this->view->headLink()->appendStylesheet($baseUrl.'css/billing.css?'.$CC_CONFIG['airtime_version']);
+
+ BillingController::ensureClientIdIsValid();
+ $credentials = self::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 = self::makeRequest($credentials["url"], $query_string);
+
+ $this->view->invoices = $result["invoices"]["invoice"];
+ }
+
+ public function invoiceAction()
+ {
+ BillingController::ensureClientIdIsValid();
+ $request = $this->getRequest();
+ $invoice_id = $request->getParam('invoiceid');
+ self::viewInvoice($invoice_id);
+ }
+
+ /** Get the Airtime instance ID of the instance the customer is currently viewing. */
+ private static function getClientInstanceId()
+ {
+ $currentProduct = BillingController::getClientCurrentAirtimeProduct();
+ return $currentProduct["id"];
+ }
+
+ 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 $result = array();
+ if (!empty($result))
+ {
+ return $result["products"]["product"];
+ }
+
+ $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"] = "15";
+
+ $query_string = "";
+ foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&";
+
+ $result = self::makeRequest($credentials["url"], $query_string);
+ return $result["products"]["product"];
+ }
+
+ public static function getProductPricesAndTypes()
+ {
+ $products = BillingController::getProducts();
+
+ 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'] == "airtime.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"] as $product)
+ {
+ if (strpos($product[0]["groupname"], "Airtime") === FALSE)
+ {
+ //Ignore non-Airtime products
+ continue;
+ }
+ else
+ {
+ if ($product[0]["status"] === "Active") {
+ $airtimeProduct = $product[0];
+ $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());
+ }
+ }
+
+ private static function makeRequest($url, $query_string) {
+ try {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $url);
+ 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());
+ }
+ }
+
+ private static function ensureClientIdIsValid()
+ {
+ if (Application_Model_Preference::GetClientId() == null)
+ {
+ throw new Exception("Invalid client ID: " . Application_Model_Preference::GetClientId());
+ }
+ }
+}
\ No newline at end of file
diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php
index 41ab24eaa..42699e9bc 100644
--- a/airtime_mvc/application/controllers/LibraryController.php
+++ b/airtime_mvc/application/controllers/LibraryController.php
@@ -83,7 +83,7 @@ class LibraryController extends Zend_Controller_Action
$obj = new $objInfo['className']($obj_sess->id);
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new Application_Model_User($userInfo->id);
- $isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
+ $isAdminOrPM = $user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
if ($isAdminOrPM || $obj->getCreatorId() == $userInfo->id) {
$this->view->obj = $obj;
@@ -186,7 +186,7 @@ class LibraryController extends Zend_Controller_Action
//Open a jPlayer window and play the audio clip.
$menu["play"] = array("name"=> _("Preview"), "icon" => "play", "disabled" => false);
- $isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
+ $isAdminOrPM = $user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
$obj_sess = new Zend_Session_Namespace(UI_PLAYLISTCONTROLLER_OBJ_SESSNAME);
@@ -302,7 +302,7 @@ class LibraryController extends Zend_Controller_Action
$mediaItems = $this->_getParam('media', null);
$user = Application_Model_User::getCurrentUser();
- //$isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
+ //$isAdminOrPM = $user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
$files = array();
$playlists = array();
@@ -418,7 +418,7 @@ class LibraryController extends Zend_Controller_Action
public function editFileMdAction()
{
$user = Application_Model_User::getCurrentUser();
- $isAdminOrPM = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
+ $isAdminOrPM = $user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
$request = $this->getRequest();
diff --git a/airtime_mvc/application/controllers/LoginController.php b/airtime_mvc/application/controllers/LoginController.php
index 84af7f954..9899139d5 100644
--- a/airtime_mvc/application/controllers/LoginController.php
+++ b/airtime_mvc/application/controllers/LoginController.php
@@ -1,5 +1,7 @@
getRequest();
+ //Allow AJAX requests from www.airtime.pro. We use this to automatically login users
+ //after they sign up from the microsite.
+ //Chrome sends the Origin header for all requests, so we whitelist the webserver's hostname as well.
+ $response = $this->getResponse()->setHeader('Access-Control-Allow-Origin', '*');
+ $origin = $request->getHeader('Origin');
+ if (($origin != "") &&
+ (!in_array($origin,
+ array("http://www.airtime.pro",
+ "https://www.airtime.pro",
+ "http://" . $_SERVER['SERVER_NAME'],
+ "https://" . $_SERVER['SERVER_NAME']
+ ))
+ ))
+ {
+ //Don't allow CORS from other domains to prevent XSS.
+ throw new Zend_Controller_Action_Exception('Forbidden', 403);
+ }
+
Application_Model_Locale::configureLocalization($request->getcookie('airtime_locale', 'en_CA'));
$auth = Zend_Auth::getInstance();
@@ -71,12 +91,23 @@ class LoginController extends Zend_Controller_Action
$this->_redirect('Showbuilder');
} else {
-
- $message = _("Wrong username or password provided. Please try again.");
- Application_Model_Subjects::increaseLoginAttempts($username);
- Application_Model_LoginAttempts::increaseAttempts($_SERVER['REMOTE_ADDR']);
- $form = new Application_Form_Login();
- $error = true;
+ $email = $form->getValue('username');
+ $authAdapter = new WHMCS_Auth_Adapter("admin", $email, $password);
+ $auth = Zend_Auth::getInstance();
+ $result = $auth->authenticate($authAdapter);
+ if ($result->isValid()) {
+ //set the user locale in case user changed it in when logging in
+ Application_Model_Preference::SetUserLocale($locale);
+
+ $this->_redirect('Showbuilder');
+ }
+ else {
+ $message = _("Wrong username or password provided. Please try again.");
+ Application_Model_Subjects::increaseLoginAttempts($username);
+ Application_Model_LoginAttempts::increaseAttempts($_SERVER['REMOTE_ADDR']);
+ $form = new Application_Form_Login();
+ $error = true;
+ }
}
}
}
diff --git a/airtime_mvc/application/controllers/ScheduleController.php b/airtime_mvc/application/controllers/ScheduleController.php
index f3d61d867..d41cd37a6 100644
--- a/airtime_mvc/application/controllers/ScheduleController.php
+++ b/airtime_mvc/application/controllers/ScheduleController.php
@@ -104,7 +104,7 @@ class ScheduleController extends Zend_Controller_Action
$this->createShowFormAction(true);
$user = Application_Model_User::getCurrentUser();
- if ($user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
+ if ($user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
$this->view->preloadShowForm = true;
}
@@ -133,7 +133,7 @@ class ScheduleController extends Zend_Controller_Action
{
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new Application_Model_User($userInfo->id);
- $editable = $user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
+ $editable = $user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER));
$calendar_interval = Application_Model_Preference::GetCalendarTimeScale();
Logging::info($calendar_interval);
@@ -209,7 +209,7 @@ class ScheduleController extends Zend_Controller_Action
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
$user = new Application_Model_User($userInfo->id);
- if ($user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
+ if ($user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
try {
$show = new Application_Model_Show($showId);
} catch (Exception $e) {
@@ -654,7 +654,7 @@ class ScheduleController extends Zend_Controller_Action
$user = Application_Model_User::getCurrentUser();
- if ($user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
+ if ($user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
$id = $this->_getParam('id');
try {
diff --git a/airtime_mvc/application/controllers/UpgradeController.php b/airtime_mvc/application/controllers/UpgradeController.php
index 2df3636ff..30067ece7 100644
--- a/airtime_mvc/application/controllers/UpgradeController.php
+++ b/airtime_mvc/application/controllers/UpgradeController.php
@@ -1,71 +1,51 @@
view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
if (!$this->verifyAuth()) {
return;
}
+
+ $upgraders = array();
+ array_push($upgraders, new AirtimeUpgrader253());
+ array_push($upgraders, new AirtimeUpgrader254());
- if (!$this->verifyAirtimeVersion()) {
- return;
- }
-
- $con = Propel::getConnection();
- $con->beginTransaction();
- try {
- //Disable Airtime UI
- //create a temporary maintenance notification file
- //when this file is on the server, zend framework redirects all
- //requests to the maintenance page and sets a 503 response code
-
- $maintenanceFile = isset($_SERVER['AIRTIME_BASE']) ? $_SERVER['AIRTIME_BASE']."maintenance.txt" : "/tmp/maintenance.txt";
- $file = fopen($maintenanceFile, 'w');
- fclose($file);
-
- //Begin upgrade
+ $didWePerformAnUpgrade = false;
+ try
+ {
+ for ($i = 0; $i < count($upgraders); $i++)
+ {
+ $upgrader = $upgraders[$i];
+ if ($upgrader->checkIfUpgradeSupported())
+ {
+ $upgrader->upgrade(); //This will throw an exception if the upgrade fails.
+ $didWePerformAnUpgrade = true;
+ $this->getResponse()
+ ->setHttpResponseCode(200)
+ ->appendBody("Upgrade to Airtime " . $upgrader->getNewVersion() . " OK
");
+ $i = 0; //Start over, in case the upgrade handlers are not in ascending order.
+ }
+ }
- //Update disk_usage value in cc_pref
- $storDir = isset($_SERVER['AIRTIME_BASE']) ? $_SERVER['AIRTIME_BASE']."srv/airtime/stor" : "/srv/airtime/stor";
- $diskUsage = shell_exec("du -sb $storDir | awk '{print $1}'");
-
- Application_Model_Preference::setDiskUsage($diskUsage);
-
- //TODO: clear out the cache
-
- $con->commit();
-
- //update system_version in cc_pref and change some columns in cc_files
- $airtimeConf = isset($_SERVER['AIRTIME_CONF']) ? $_SERVER['AIRTIME_CONF'] : "/etc/airtime/airtime.conf";
- $values = parse_ini_file($airtimeConf, true);
-
- $username = $values['database']['dbuser'];
- $password = $values['database']['dbpass'];
- $host = $values['database']['host'];
- $database = $values['database']['dbname'];
- $dir = __DIR__;
-
- passthru("export PGPASSWORD=$password && psql -h $host -U $username -q -f $dir/upgrade_sql/airtime_$airtime_upgrade_version/upgrade.sql $database 2>&1 | grep -v \"will create implicit index\"");
-
- //delete maintenance.txt to give users access back to Airtime
- unlink($maintenanceFile);
-
- $this->getResponse()
+ if (!$didWePerformAnUpgrade)
+ {
+ $this->getResponse()
->setHttpResponseCode(200)
- ->appendBody("Upgrade to Airtime 2.5.3 OK");
-
- } catch(Exception $e) {
- $con->rollback();
- unlink($maintenanceFile);
+ ->appendBody("No upgrade was performed. The current Airtime version is " . AirtimeUpgrader::getCurrentVersion() . ".
");
+ }
+ }
+ catch (Exception $e)
+ {
$this->getResponse()
- ->setHttpResponseCode(400)
- ->appendBody($e->getMessage());
+ ->setHttpResponseCode(400)
+ ->appendBody($e->getMessage());
}
}
@@ -86,25 +66,10 @@ class UpgradeController extends Zend_Controller_Action
{
$this->getResponse()
->setHttpResponseCode(401)
- ->appendBody("Error: Incorrect API key.");
+ ->appendBody("Error: Incorrect API key.
");
return false;
}
return true;
}
- private function verifyAirtimeVersion()
- {
- $pref = CcPrefQuery::create()
- ->filterByKeystr('system_version')
- ->findOne();
- $airtime_version = $pref->getValStr();
-
- if (!in_array($airtime_version, array('2.5.1', '2.5.2'))) {
- $this->getResponse()
- ->setHttpResponseCode(400)
- ->appendBody("Upgrade to Airtime 2.5.3 FAILED. You must be using Airtime 2.5.1 or 2.5.2 to upgrade.");
- return false;
- }
- return true;
- }
}
diff --git a/airtime_mvc/application/controllers/UserController.php b/airtime_mvc/application/controllers/UserController.php
index fad0277db..4ce81eb90 100644
--- a/airtime_mvc/application/controllers/UserController.php
+++ b/airtime_mvc/application/controllers/UserController.php
@@ -62,7 +62,11 @@ class UserController extends Zend_Controller_Action
if ($formData['password'] != "xxxxxx") {
$user->setPassword($formData['password']);
}
- $user->setType($formData['type']);
+ if (array_key_exists('type', $formData)) {
+ if ($formData['type'] != UTYPE_SUPERADMIN) { //Don't allow any other user to be promoted to Super Admin
+ $user->setType($formData['type']);
+ }
+ }
$user->setEmail($formData['email']);
$user->setCellPhone($formData['cell_phone']);
$user->setSkype($formData['skype']);
@@ -121,6 +125,10 @@ class UserController extends Zend_Controller_Action
public function editUserAction()
{
+ if (Application_Model_User::getCurrentUser()->isSuperAdmin()) {
+ $this->_redirect('billing/client');
+ }
+
$request = $this->getRequest();
$form = new Application_Form_EditUser();
if ($request->isPost()) {
@@ -187,6 +195,12 @@ class UserController extends Zend_Controller_Action
}
$user = new Application_Model_User($delId);
+
+ // Don't allow super admins to be deleted.
+ if ($user->isSuperAdmin())
+ {
+ return;
+ }
# Take care of the user's files by either assigning them to somebody
# or deleting them all
diff --git a/airtime_mvc/application/controllers/WebstreamController.php b/airtime_mvc/application/controllers/WebstreamController.php
index 8eb9a2ac5..1d94923c3 100644
--- a/airtime_mvc/application/controllers/WebstreamController.php
+++ b/airtime_mvc/application/controllers/WebstreamController.php
@@ -88,7 +88,7 @@ class WebstreamController extends Zend_Controller_Action
public function isAuthorized($webstream_id)
{
$user = Application_Model_User::getCurrentUser();
- if ($user->isUserType(array(UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
+ if ($user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) {
return true;
}
diff --git a/airtime_mvc/application/controllers/WhmcsLoginController.php b/airtime_mvc/application/controllers/WhmcsLoginController.php
new file mode 100644
index 000000000..2f78ce5ed
--- /dev/null
+++ b/airtime_mvc/application/controllers/WhmcsLoginController.php
@@ -0,0 +1,258 @@
+getRequest();
+ $this->view->layout()->disableLayout();
+ $this->_helper->viewRenderer->setNoRender(true);
+
+ $username = "admin"; //This is just for appearance in your session. It shows up in the corner of the Airtime UI.
+ $email = $_POST["email"];
+ $password = $_POST["password"];
+
+ Application_Model_Locale::configureLocalization($request->getcookie('airtime_locale', 'en_CA'));
+ if (Zend_Auth::getInstance()->hasIdentity())
+ {
+ $this->_redirect('Showbuilder');
+ }
+
+ $authAdapter = new WHMCS_Auth_Adapter($username, $email, $password);
+
+ $auth = Zend_Auth::getInstance();
+ $result = $auth->authenticate($authAdapter);
+ if ($result->isValid()) {
+ //all info about this user from the login table omit only the password
+ //$userInfo = $authAdapter->getResultRowObject(null, 'password');
+
+ //the default storage is a session with namespace Zend_Auth
+ /*
+ [id] => 1
+ [login] => admin
+ [pass] => hashed password
+ [type] => A
+ [first_name] =>
+ [last_name] =>
+ [lastlogin] =>
+ [lastfail] =>
+ [skype_contact] =>
+ [jabber_contact] =>
+ [email] => asdfasdf@asdasdf.com
+ [cell_phone] =>
+ [login_attempts] => 0
+ */
+
+ //Zend_Auth already does this for us, it's not needed:
+ //$authStorage = $auth->getStorage();
+ //$authStorage->write($result->getIdentity()); //$userInfo);
+
+ //set the user locale in case user changed it in when logging in
+ //$locale = $form->getValue('locale');
+ //Application_Model_Preference::SetUserLocale($locale);
+
+ $this->_redirect('Showbuilder');
+ }
+ else {
+ echo("Sorry, that username or password was incorrect.");
+ }
+
+ return;
+ }
+}
+
+class WHMCS_Auth_Adapter implements Zend_Auth_Adapter_Interface {
+ private $username;
+ private $password;
+ private $email;
+
+ function __construct($username, $email, $password) {
+ $this->username = $username;
+ $this->password = $password;
+ $this->email = $email;
+ $this->identity = null;
+ }
+
+ function authenticate() {
+ list($credentialsValid, $clientId) = $this->validateCredentialsWithWHMCS($this->email, $this->password);
+ if (!$credentialsValid)
+ {
+ return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, null);
+ }
+ if (!$this->verifyClientSubdomainOwnership($clientId))
+ {
+ return new Zend_Auth_Result(Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, null);
+ }
+
+ $identity = array();
+
+ //TODO: Get identity of the first admin user!
+
+ /*
+ $identity["id"] = 1;
+ $identity["type"] = "S";
+ $identity["login"] = $this->username; //admin";
+ $identity["email"] = $this->email;*/
+ $identity = $this->getSuperAdminIdentity();
+ if (is_null($identity)) {
+ Logging::error("No super admin user found");
+ return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null);
+ }
+ $identity = (object)$identity; //Convert the array into an stdClass object
+
+ try {
+ return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $identity);
+ } catch (Exception $e) {
+ // exception occured
+ return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null);
+ }
+ }
+
+ private function getSuperAdminIdentity()
+ {
+ $firstSuperAdminUser = CcSubjsQuery::create()
+ ->filterByDbType('S')
+ ->orderByDbId()
+ ->findOne();
+ if (!$firstSuperAdminUser) {
+ //If there's no super admin users, get the first regular admin user!
+ $firstSuperAdminUser = CcSubjsQuery::create()
+ ->filterByDbType('A')
+ ->orderByDbId()
+ ->findOne();
+ if (!$firstSuperAdminUser) {
+ return null;
+ }
+ }
+ $identity["id"] = $firstSuperAdminUser->getDbId();
+ $identity["type"] = "S"; //Super Admin
+ $identity["login"] = $firstSuperAdminUser->getDbLogin();
+ $identity["email"] = $this->email;
+ return $identity;
+ }
+
+ //Returns an array! Read the code carefully:
+ private function validateCredentialsWithWHMCS($email, $password)
+ {
+ $client_postfields = array();
+ $client_postfields["username"] = $_SERVER['WHMCS_USERNAME']; //WHMCS API username
+ $client_postfields["password"] = md5($_SERVER['WHMCS_PASSWORD']); //WHMCS API password
+ $client_postfields["action"] ="validatelogin";
+ $client_postfields["responsetype"] = "json";
+
+ $client_postfields["email"] = $email;
+ $client_postfields["password2"] = $password;
+
+ $query_string = "";
+ foreach ($client_postfields as $k => $v) $query_string .= "$k=".urlencode($v)."&";
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, WHMCS_API_URL);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+ curl_setopt($ch, CURLOPT_FAILONERROR, 1);
+ 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(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
+ //die("Connection Error: ".curl_errno($ch).' - '.curl_error($ch));
+ }
+ curl_close($ch);
+
+ $arr = json_decode($jsondata, true); # Decode JSON String
+
+ if ($arr["result"] != "success") {
+ return array(false, -1);
+ }
+ $clientId = $arr["userid"];
+
+ return array(true, $clientId);
+ }
+
+ function verifyClientSubdomainOwnership($clientId)
+ {
+ //Do a quick safety check to ensure the client ID we're authenticating
+ //matches up to the owner of this instance.
+ if ($clientId != Application_Model_Preference::GetClientId())
+ {
+ return false;
+ }
+ $client_postfields = array();
+ $client_postfields["username"] = $_SERVER['WHMCS_USERNAME'];
+ $client_postfields["password"] = md5($_SERVER['WHMCS_PASSWORD']);
+ $client_postfields["action"] ="getclientsproducts";
+ $client_postfields["responsetype"] = "json";
+
+ $client_postfields["clientid"] = $clientId;
+ //$client_postfields["stats"] = "true";
+
+ $query_string = "";
+ foreach ($client_postfields as $k => $v) $query_string .= "$k=".urlencode($v)."&";
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, WHMCS_API_URL);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+ curl_setopt($ch, CURLOPT_FAILONERROR, 1);
+ 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(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
+ //die("Connection Error: ".curl_errno($ch).' - '.curl_error($ch));
+ }
+ curl_close($ch);
+
+ $arr = json_decode($jsondata, true); # Decode JSON String
+ //$client_id = $arr["clientid"];
+ //print_r($arr);
+ if ($arr["result"] != "success") {
+ die("Sorry, that email address or password was incorrect.");
+ }
+
+ $doesAirtimeProductExist = false;
+ $isAirtimeAccountSuspended = true;
+ $airtimeProduct = null;
+
+ foreach ($arr["products"] as $product)
+ {
+ if (strpos($product[0]["groupname"], "Airtime") === FALSE)
+ {
+ //Ignore non-Airtime products
+ continue;
+ }
+ else
+ {
+ if ($product[0]["status"] === "Active") {
+ $airtimeProduct = $product[0];
+ $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 true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/airtime_mvc/application/controllers/plugins/Acl_plugin.php b/airtime_mvc/application/controllers/plugins/Acl_plugin.php
index 0915f37f6..533429e47 100644
--- a/airtime_mvc/application/controllers/plugins/Acl_plugin.php
+++ b/airtime_mvc/application/controllers/plugins/Acl_plugin.php
@@ -118,8 +118,7 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
return;
}
- if (in_array($controller, array("api", "auth", "locale", "upgrade"))) {
-
+ if (in_array($controller, array("api", "auth", "locale", "upgrade", 'whmcs-login'))) {
$this->setRoleName("G");
} elseif (!Zend_Auth::getInstance()->hasIdentity()) {
diff --git a/airtime_mvc/application/forms/AddUser.php b/airtime_mvc/application/forms/AddUser.php
index 1d3835ae7..09d5216c1 100644
--- a/airtime_mvc/application/forms/AddUser.php
+++ b/airtime_mvc/application/forms/AddUser.php
@@ -95,9 +95,9 @@ class Application_Form_AddUser extends Zend_Form
"G" => _("Guest"),
"H" => _("DJ"),
"P" => _("Program Manager"),
- "A" => _("Admin")
+ "A" => _("Admin"),
));
- $select->setRequired(true);
+ $select->setRequired(false);
$this->addElement($select);
$saveBtn = new Zend_Form_Element_Button('save_user');
diff --git a/airtime_mvc/application/forms/BillingClient.php b/airtime_mvc/application/forms/BillingClient.php
new file mode 100644
index 000000000..5e6a2c252
--- /dev/null
+++ b/airtime_mvc/application/forms/BillingClient.php
@@ -0,0 +1,196 @@
+setDecorators(array(
+ array('ViewScript', array('viewScript' => 'form/billing-purchase.phtml'))));*/
+ $client = BillingController::getClientDetails();
+ $this->setAttrib("id", "clientdetails_form");
+
+ $notEmptyValidator = Application_Form_Helper_ValidationTypes::overrideNotEmptyValidator();
+ $emailValidator = Application_Form_Helper_ValidationTypes::overrideEmailAddressValidator();
+
+ $firstname = new Zend_Form_Element_Text('firstname');
+ $firstname->setLabel(_('First Name:'))
+ ->setValue($client["firstname"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($firstname);
+
+ $lastname = new Zend_Form_Element_Text('lastname');
+ $lastname->setLabel(_('Last Name:'))
+ ->setValue($client["lastname"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($lastname);
+
+ $companyname = new Zend_Form_Element_Text('companyname');
+ $companyname->setLabel(_('Company Name:'))
+ ->setValue($client["companyname"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(false)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($companyname);
+
+ $email = new Zend_Form_Element_Text('email');
+ $email->setLabel(_('Email Address:'))
+ ->setValue($client["email"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->setAttrib('readonly', 'readonly')
+ ->addValidator($emailValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($email);
+
+ $address1 = new Zend_Form_Element_Text('address1');
+ $address1->setLabel(_('Address 1:'))
+ ->setValue($client["address1"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($address1);
+
+ $address2 = new Zend_Form_Element_Text('address2');
+ $address2->setLabel(_('Address 2:'))
+ ->setValue($client["address2"])
+ ->setAttrib('class', 'input_text')
+ ->addFilter('StringTrim');
+ $this->addElement($address2);
+
+ $city = new Zend_Form_Element_Text('city');
+ $city->setLabel(_('City:'))
+ ->setValue($client["city"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($city);
+
+ //TODO: get list from whmcs?
+ $state = new Zend_Form_Element_Text('state');
+ $state->setLabel(_('State/Region:'))
+ ->setValue($client["state"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($state);
+
+ $postcode = new Zend_Form_Element_Text('postcode');
+ $postcode->setLabel(_('Zip Code / Postal Code:'))
+ ->setValue($client["postcode"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($postcode);
+
+ $locale = new Zend_Locale('en_US');
+ $countries = $locale->getTranslationList('Territory', 'en', 2);
+ asort($countries, SORT_LOCALE_STRING);
+
+ $country = new Zend_Form_Element_Select('country');
+ $country->setLabel(_('Country:'))
+ ->setValue($client["country"])
+ ->setAttrib('class', 'input_text')
+ ->setMultiOptions($countries)
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($country);
+
+ $phonenumber = new Zend_Form_Element_Text('phonenumber');
+ $phonenumber->setLabel(_('Phone Number:'))
+ ->setValue($client["phonenumber"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($phonenumber);
+
+ $securityqid = new Zend_Form_Element_Select('securityqid');
+ $securityqid->setLabel(_('Please choose a security question:'))
+ ->setValue($client["securityqid"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->setMultiOptions(array(
+ "1" => _("What is the name of your favorite childhood friend?"),
+ "3" => _("What school did you attend for sixth grade?"),
+ "4" => _("In what city did you meet your spouse/significant other?"),
+ "5" => _("What street did you live on in third grade?"),
+ "6" => _("What is the first name of the boy or girl that you first kissed?"),
+ "7" => _("In what city or town was your first job?")));
+ $this->addElement($securityqid);
+
+ $securityqans = new Zend_Form_Element_Text('securityqans');
+ $securityqans->setLabel(_('Please enter an answer:'))
+ ->setValue($client["securityqans"])
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($securityqans);
+
+ foreach ($client["customfields"] as $field) {
+ if ($field["id"] == "7") {
+ $vatvalue = $field["value"];
+ } elseif ($field["id"] == "71") {
+ $subscribevalue = $field["value"];
+ }
+ }
+
+ $vat = new Zend_Form_Element_Text("7");
+ $vat->setLabel(_('VAT/Tax ID (EU only)'))
+ ->setBelongsTo('customfields')
+ ->setValue($vatvalue)
+ ->setAttrib('class', 'input_text')
+ //->setRequired(true)
+ //->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($vat);
+
+ $subscribe = new Zend_Form_Element_Checkbox('71');
+ $subscribe->setLabel(_('Subscribe to Sourcefabric newsletter'))
+ ->setValue($subscribevalue)
+ ->setBelongsTo('customfields')
+ ->setAttrib('class', 'input_text')
+ ->setRequired(true)
+ ->addValidator($notEmptyValidator)
+ ->addFilter('StringTrim');
+ $this->addElement($subscribe);
+
+ $password = new Zend_Form_Element_Password('password2');
+ $password->setLabel(_('Password:'));
+ $password->setAttrib('class', 'input_text');
+ $password->setValue("xxxxxx");
+ $password->setRequired(true);
+ $password->addFilter('StringTrim');
+ $password->addValidator($notEmptyValidator);
+ $this->addElement($password);
+
+ $passwordVerify = new Zend_Form_Element_Password('password2verify');
+ $passwordVerify->setLabel(_('Verify Password:'));
+ $passwordVerify->setAttrib('class', 'input_text');
+ $passwordVerify->setValue("xxxxxx");
+ $passwordVerify->setRequired(true);
+ $passwordVerify->addFilter('StringTrim');
+ //$passwordVerify->addValidator($notEmptyValidator);
+ $passwordVerify->addValidator('Identical', false, array('token' => 'password2'));
+ $passwordVerify->addValidator($notEmptyValidator);
+ $this->addElement($passwordVerify);
+
+ $submit = new Zend_Form_Element_Submit("submit");
+ $submit->setIgnore(true)
+ ->setLabel(_("Save"));
+ $this->addElement($submit);
+ }
+}
\ No newline at end of file
diff --git a/airtime_mvc/application/forms/BillingUpgradeDowngrade.php b/airtime_mvc/application/forms/BillingUpgradeDowngrade.php
new file mode 100644
index 000000000..15492f3d2
--- /dev/null
+++ b/airtime_mvc/application/forms/BillingUpgradeDowngrade.php
@@ -0,0 +1,54 @@
+setLabel(_('Plan type:'))
+ ->setMultiOptions($productTypes)
+ ->setRequired(true)
+ ->setValue($currentPlanProductId);
+ $this->addElement($pid);
+
+ //Logging::info(BillingController::getClientCurrentAirtimeProduct());
+ $billingcycle = new Zend_Form_Element_Radio('newproductbillingcycle');
+ $billingcycle->setLabel(_('Billing cycle:'))
+ ->setMultiOptions(array('monthly' => 'Monthly', 'annually' => 'Annually'))
+ ->setRequired(true)
+ ->setValue($currentPlanProductBillingCycle);
+
+ $this->addElement($billingcycle);
+
+ $paymentmethod = new Zend_Form_Element_Radio('paymentmethod');
+ $paymentmethod->setLabel(_('Payment method:'))
+ ->setRequired(true)
+ ->setMultiOptions(array(
+ 'paypal' => _('PayPal'),
+ 'tco' => _('Credit Card via 2Checkout')))
+ ->setValue('paypal');
+ $this->addElement($paymentmethod);
+
+ /*$submit = new Zend_Form_Element_Submit("submit");
+ $submit->setIgnore(true)
+ ->setLabel(_("Save"));
+ $this->addElement($submit);*/
+
+ $client = new Application_Form_BillingClient();
+ $client->removeElement("password2");
+ $client->removeElement("password2verify");
+ $this->addSubForm($client, 'billing_client_info');
+ }
+}
diff --git a/airtime_mvc/application/layouts/scripts/layout.phtml b/airtime_mvc/application/layouts/scripts/layout.phtml
index a85ede7e6..df5d0281b 100644
--- a/airtime_mvc/application/layouts/scripts/layout.phtml
+++ b/airtime_mvc/application/layouts/scripts/layout.phtml
@@ -33,7 +33,9 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=