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=
diff --git a/airtime_mvc/application/models/Block.php b/airtime_mvc/application/models/Block.php index babef06a5..8ea3593b1 100644 --- a/airtime_mvc/application/models/Block.php +++ b/airtime_mvc/application/models/Block.php @@ -1038,7 +1038,7 @@ SQL; { $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)); // get only the files from the blocks // we are about to delete diff --git a/airtime_mvc/application/models/Cache.php b/airtime_mvc/application/models/Cache.php index 033b55bf1..dbbcb9d58 100644 --- a/airtime_mvc/application/models/Cache.php +++ b/airtime_mvc/application/models/Cache.php @@ -35,4 +35,11 @@ class Cache //return apc_fetch($cacheKey); return false; } + + public static function clear() + { + // Disabled on SaaS + // apc_clear_cache('user'); + // apc_clear_cache(); + } } diff --git a/airtime_mvc/application/models/Playlist.php b/airtime_mvc/application/models/Playlist.php index 7a7573d2a..90baaf621 100644 --- a/airtime_mvc/application/models/Playlist.php +++ b/airtime_mvc/application/models/Playlist.php @@ -987,7 +987,7 @@ SQL; { $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)); // get only the files from the playlists // we are about to delete diff --git a/airtime_mvc/application/models/Preference.php b/airtime_mvc/application/models/Preference.php index 2f2d160f8..e5d459652 100644 --- a/airtime_mvc/application/models/Preference.php +++ b/airtime_mvc/application/models/Preference.php @@ -6,7 +6,7 @@ class Application_Model_Preference { private static function getUserId() - { + { //pass in true so the check is made with the autoloader //we need this check because saas calls this function from outside Zend if (!class_exists("Zend_Auth", true) || !Zend_Auth::getInstance()->hasIdentity()) { diff --git a/airtime_mvc/application/models/StoredFile.php b/airtime_mvc/application/models/StoredFile.php index 79c80a238..b54cedfa5 100644 --- a/airtime_mvc/application/models/StoredFile.php +++ b/airtime_mvc/application/models/StoredFile.php @@ -379,7 +379,7 @@ SQL; $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 && $this->getFileOwnerId() != $user->getId()) { throw new FileNoPermissionException(); } diff --git a/airtime_mvc/application/models/User.php b/airtime_mvc/application/models/User.php index 2fc763e06..489d0e933 100644 --- a/airtime_mvc/application/models/User.php +++ b/airtime_mvc/application/models/User.php @@ -49,14 +49,20 @@ class Application_Model_User { return $this->isUserType(UTYPE_ADMIN); } - + + public function isSuperAdmin() + { + return $this->isUserType(UTYPE_SUPERADMIN); + } + public function canSchedule($p_showId) { $type = $this->getType(); $result = false; - if ($type === UTYPE_ADMIN || - $type === UTYPE_PROGRAM_MANAGER || + if ($this->isAdmin() || + $this->isSuperAdmin() || + $this->isPM() || self::isHostOfShow($p_showId)) { $result = true; } diff --git a/airtime_mvc/application/models/airtime/CcSubjs.php b/airtime_mvc/application/models/airtime/CcSubjs.php index 78fd56d9e..7fe6289f1 100644 --- a/airtime_mvc/application/models/airtime/CcSubjs.php +++ b/airtime_mvc/application/models/airtime/CcSubjs.php @@ -15,7 +15,7 @@ class CcSubjs extends BaseCcSubjs { public function isAdminOrPM() { - return $this->type === UTYPE_ADMIN || $this->type === UTYPE_PROGRAM_MANAGER; + return $this->type === UTYPE_SUPERADMIN || $this->type === UTYPE_ADMIN || $this->type === UTYPE_PROGRAM_MANAGER; } public function isHostOfShow($showId) diff --git a/airtime_mvc/application/upgrade/Upgrades.php b/airtime_mvc/application/upgrade/Upgrades.php new file mode 100644 index 000000000..b427a6297 --- /dev/null +++ b/airtime_mvc/application/upgrade/Upgrades.php @@ -0,0 +1,197 @@ +filterByKeystr('system_version') + ->findOne(); + $airtime_version = $pref->getValStr(); + return $airtime_version; + } + + /** + * This function checks to see if this class can perform an upgrade of your version of Airtime + * @return boolean True if we can upgrade your version of Airtime. + */ + public function checkIfUpgradeSupported() + { + if (!in_array(AirtimeUpgrader::getCurrentVersion(), $this->getSupportedVersions())) { + return false; + } + return true; + } + + protected function toggleMaintenanceScreen($toggle) + { + if ($toggle) + { + //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 + $this->maintenanceFile = isset($_SERVER['AIRTIME_BASE']) ? $_SERVER['AIRTIME_BASE']."maintenance.txt" : "/tmp/maintenance.txt"; + $file = fopen($this->maintenanceFile, 'w'); + fclose($file); + } else { + //delete maintenance.txt to give users access back to Airtime + if ($this->maintenanceFile) { + unlink($this->maintenanceFile); + } + } + } + + /** Implement this for each new version of Airtime */ + abstract public function upgrade(); +} + +class AirtimeUpgrader253 extends AirtimeUpgrader +{ + protected function getSupportedVersions() + { + return array('2.5.1', '2.5.2'); + } + public function getNewVersion() + { + return '2.5.3'; + } + + public function upgrade() + { + Cache::clear(); + assert($this->checkIfUpgradeSupported()); + + $con = Propel::getConnection(); + $con->beginTransaction(); + try { + + $this->toggleMaintenanceScreen(true); + Cache::clear(); + + //Begin upgrade + + //Update disk_usage value in cc_pref + $musicDir = CcMusicDirsQuery::create() + ->filterByType('stor') + ->filterByExists(true) + ->findOne(); + $storPath = $musicDir->getDirectory(); + + //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); + + //clear out the cache + Cache::clear(); + + $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\""); + + Application_Model_Preference::SetAirtimeVersion($this->getNewVersion()); + //clear out the cache + Cache::clear(); + + $this->toggleMaintenanceScreen(false); + + } catch (Exception $e) { + $con->rollback(); + $this->toggleMaintenanceScreen(false); + } + } +} + +class AirtimeUpgrader254 extends AirtimeUpgrader +{ + protected function getSupportedVersions() + { + return array('2.5.3'); + } + public function getNewVersion() + { + return '2.5.4'; + } + + public function upgrade() + { + Cache::clear(); + + assert($this->checkIfUpgradeSupported()); + + $newVersion = $this->getNewVersion(); + + $con = Propel::getConnection(); + //$con->beginTransaction(); + try { + $this->toggleMaintenanceScreen(true); + Cache::clear(); + + //Begin upgrade + + //First, ensure there are no superadmins already. + $numberOfSuperAdmins = CcSubjsQuery::create() + ->filterByDbType(UTYPE_SUPERADMIN) + ->count(); + + //Only create a super admin if there isn't one already. + if ($numberOfSuperAdmins == 0) + { + //Find the "admin" user and promote them to superadmin. + $adminUser = CcSubjsQuery::create() + ->filterByDbLogin('admin') + ->findOne(); + if (!$adminUser) + { + //TODO: Otherwise get the user with the lowest ID that is of type administrator: + // + $adminUser = CcSubjsQuery::create() + ->filterByDbType(UTYPE_ADMIN) + ->orderByDbId(Criteria::ASC) + ->findOne(); + + if (!$adminUser) { + throw new Exception("Failed to find any users of type 'admin' ('A')."); + } + } + + $adminUser = new Application_Model_User($adminUser->getDbId()); + $adminUser->setType(UTYPE_SUPERADMIN); + $adminUser->save(); + Logging::info($_SERVER['HTTP_HOST'] . ': ' . $newVersion . " Upgrade: Promoted user " . $adminUser->getLogin() . " to be a Super Admin."); + } + + //$con->commit(); + Application_Model_Preference::SetAirtimeVersion($newVersion); + Cache::clear(); + + $this->toggleMaintenanceScreen(false); + + return true; + + } catch(Exception $e) { + //$con->rollback(); + $this->toggleMaintenanceScreen(false); + throw $e; + } + } +} \ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/billing/client.phtml b/airtime_mvc/application/views/scripts/billing/client.phtml new file mode 100644 index 000000000..44a3c8d72 --- /dev/null +++ b/airtime_mvc/application/views/scripts/billing/client.phtml @@ -0,0 +1,15 @@ +form->getElement("submit")->setAttrib("class", "right-align"); +$this->form->getElement("country")->setAttrib("class", "right-align"); +$this->form->getElement("securityqid")->setAttrib("class", "right-align"); +?> +
+

Billing Account Details

+errorMessage)) {?> +
errorMessage ?>
+successMessage)) {?> +
successMessage ?>
+ + +form ?> +
\ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/billing/index.phtml b/airtime_mvc/application/views/scripts/billing/index.phtml new file mode 100644 index 000000000..30d74d258 --- /dev/null +++ b/airtime_mvc/application/views/scripts/billing/index.phtml @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/billing/invoice.phtml b/airtime_mvc/application/views/scripts/billing/invoice.phtml new file mode 100644 index 000000000..e69de29bb diff --git a/airtime_mvc/application/views/scripts/billing/invoices.phtml b/airtime_mvc/application/views/scripts/billing/invoices.phtml new file mode 100644 index 000000000..d143441cc --- /dev/null +++ b/airtime_mvc/application/views/scripts/billing/invoices.phtml @@ -0,0 +1,22 @@ +
+

Invoices

+

and look for the \"Checkout\" button.")?>

+ + + + + + + +invoices as $invoice) {?> + + + + + + + + +
Date IssuedDue DateLinkStatus
">View Invoice">
+
\ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/billing/upgrade.phtml b/airtime_mvc/application/views/scripts/billing/upgrade.phtml new file mode 100644 index 000000000..260eb45c4 --- /dev/null +++ b/airtime_mvc/application/views/scripts/billing/upgrade.phtml @@ -0,0 +1,365 @@ +form; + $form->setAttrib('id', 'upgrade-downgrade'); + +?> + + +
+

+

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
HobbyistStarterPlusPremium
1 Stream + 2 Streams + 2 Streams + 3 Streams +
64kbps Stream Quality + 64kbps and 128kbps Stream Quality + 64kbps and 196kbps Stream Quality + 64kbps, 128kbps, and 196kbps Stream Quality +
5 Listeners + 40 Listeners per stream + 100 Listeners per stream + 500 Listeners per stream +
2GB Storage + 5GB Storage + 30GB Storage + 150GB Storage +
Ticket, Email, Forum Support + Live Chat, Ticket, Email, Forum Support + Live Chat, Ticket, Email, Forum Support + Live Chat, Ticket, Email, Forum Support +
+ Save 15% if paid annually + Save 15% if paid annually + Save 15% if paid annually +
+ + + +
+
+ +

Current Plan: + +

+ +

Choose a plan:

+
+ +
+ newproductid ?> +
+
+ newproductbillingcycle ?> +
+
+ Save 15% on annual plans (Hobbyist plan excluded). +
+
+
+ Subtotal:
+
+
+
+ +
+ VAT will be added below if you are an EU resident without a valid VAT number. +
+ +

Enter your payment details:

+ errorMessage)) {?> +
errorMessage ?>
+ + + + getSubform("billing_client_info") ?> +
+ firstname?> +
+
+ lastname?> +
+
+
+ companyname?> +
+
+ email?> +
+
+
+ address1?> +
+
+ address2?> +
+
+
+ city?> +
+
+ state?> +
+
+
+ postcode?> +
+
+ country?> +
+
+ phonenumber?> +
+
+ securityqid?> +
+
+ securityqans?> +
+

VAT will be added to your invoice if you are an EU resident without a valid company VAT number.

+
+
+ getElement("7"); ?> +
+
+
+ +
+
+ getElement("71")->renderViewHelper(); ?> +
+ getElement("71")->renderLabel(); ?> +
+
+ +

After submitting your order, you will be redirected to an invoice with payment buttons.

+
+
+ paymentmethod ?> +
+ +
+
+ Subtotal:
+
+ Total: +
+ + +
+
+
\ No newline at end of file diff --git a/airtime_mvc/application/views/scripts/form/edit-user.phtml b/airtime_mvc/application/views/scripts/form/edit-user.phtml index 1cf55dba4..b26d87499 100644 --- a/airtime_mvc/application/views/scripts/form/edit-user.phtml +++ b/airtime_mvc/application/views/scripts/form/edit-user.phtml @@ -157,7 +157,7 @@ - + diff --git a/airtime_mvc/application/views/scripts/partialviews/trialBox.phtml b/airtime_mvc/application/views/scripts/partialviews/trialBox.phtml index 301beaf75..dca68dca4 100644 --- a/airtime_mvc/application/views/scripts/partialviews/trialBox.phtml +++ b/airtime_mvc/application/views/scripts/partialviews/trialBox.phtml @@ -6,7 +6,7 @@
- + " href="/billing/upgrade" target="_blank">
diff --git a/airtime_mvc/application/views/scripts/user/add-user.phtml b/airtime_mvc/application/views/scripts/user/add-user.phtml index a85744a46..b58607a89 100644 --- a/airtime_mvc/application/views/scripts/user/add-user.phtml +++ b/airtime_mvc/application/views/scripts/user/add-user.phtml @@ -26,6 +26,9 @@ +
successMessage ?>
diff --git a/airtime_mvc/public/css/billing.css b/airtime_mvc/public/css/billing.css new file mode 100644 index 000000000..06edbec0b --- /dev/null +++ b/airtime_mvc/public/css/billing.css @@ -0,0 +1,226 @@ +@CHARSET "UTF-8"; + +.billing-panel +{ + width: 400px; + margin: 0 auto; + margin-bottom: 30px; +} + +.billing-panel h3 +{ + color: #222; +} + +.billing-panel h4 +{ + font-size: 1.25em; + margin: 0px; + color: #333; + font-weight: normal; +} + +#upgrade-downgrade +{ + border: 0px solid #000; + margin: 0 auto; + color: #000; +} + +#upgrade-downgrade label +{ + color: rgb(28,28,28); +} +#upgrade-downgrade label.disabled +{ + color: rgb(108,108,108); +} + +#upgrade-downgrade dl +{ + width: 300px; +} + +#upgrade-downgrade dt, #upgrade-downgrade dd +{ + margin: 0px; +} + +#upgrade-downgrade dd +{ + margin-bottom: 10px; +} + +.pricing-grid table +{ + border-spacing: 0px; + border-collapse: separate; + border: 1px solid #777; + width: 600px; + margin-left: -100px; + /*background-color: #555;*/ + table-layout: fixed; + margin-top: 20px; + margin-bottom: 20px; + box-shadow: 0px 5px 5px rgba(0,0,0,0.5); +} + +.pricing-grid td, .pricing-grid th +{ + border-bottom: 1px solid #999; + border-right: 1px solid #bbb; + background-color: #ccc; + padding: 10px; +} + +.pricing-grid th +{ + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border: 0px; +} + + +.pricing-grid tr.price td +{ + text-align: right; + background-color: #ddd; + font-weight: bold; +} + +#current_plan +{ + text-align: center; +} + +#plantype +{ + float: left; +} + +#billingcycle +{ + float: left; + margin-left: 30px; +} + +#billingcycle_disclaimer +{ + float: left; + margin-left: 30px; + width: 200px; +} + +#vat_disclaimer +{ + text-align: right; + font-size: 0.9em; + margin-bottom: 30px; +} + +#vat_disclaimer2 +{ + float:right; + width: 200px; +} + +#subtotal_box, #total_box +{ + position: relative; + text-align: right; + margin-top: 30px; + margin-bottom: 10px; + border: 1px solid #777; + background: #ccc; + padding: 5px; +} +#total_box +{ + margin-top: 10px; +} + +#total +{ + border-bottom: 3px double; +} + +#savings +{ + /*line-height: 30px;*/ + position: absolute; + bottom: 5px; + left: 5px; +} + +#paymentmethod +{ +} + +.billing_col1, .billing_col2 +{ + float: left; + margin-right: 10px; +} + +.billing_checkbox +{ + float: left; + margin-right: 5px; + margin-bottom: 10px; +} + +#upgrade-downgrade input[type=submit] +{ + float: right; +} + +#invoices_table +{ + margin: 0 auto; + border-spacing: 0px; + border-collapse: separate; + border: 1px solid #777; + /*background-color: #555;*/ + table-layout: fixed; + margin-top: 20px; + margin-bottom: 35px; + background: #ccc; +} + +#invoices_table tbody tr th +{ + border: 0px; +} + +#invoices_table .header +{ + box-shadow: 0px 2px 2px rgba(0,0,0,0.5); +} + +#invoices_table tr +{ + border: 1px solid #555; +} + +#invoices_table .unpaid +{ + color: #ff0000; +} + +/** This form is the separate one on the Billing Account Details page (BillingClient.php) */ +#clientdetails_form dt { + float: left; + clear: both; + width: 50%; + margin-bottom: 10px; +} +#clientdetails_form dd { + float: left; + margin-left: 0px; + margin-bottom: 10px; +} +#clientdetails_form .right-align +{ + /*text-align: right;*/ + width: 100%; +} \ No newline at end of file diff --git a/airtime_mvc/public/index.php b/airtime_mvc/public/index.php index 3d53b9e22..38f607f33 100644 --- a/airtime_mvc/public/index.php +++ b/airtime_mvc/public/index.php @@ -43,6 +43,10 @@ if (file_exists('/usr/share/php/libzend-framework-php')) { set_include_path('/usr/share/php/libzend-framework-php' . PATH_SEPARATOR . get_include_path()); } +//Upgrade directory +set_include_path(APPLICATION_PATH . '/upgrade/' . PATH_SEPARATOR . get_include_path()); + + /** Zend_Application */ require_once 'Zend/Application.php'; $application = new Zend_Application( diff --git a/airtime_mvc/public/js/airtime/schedule/full-calendar-functions.js b/airtime_mvc/public/js/airtime/schedule/full-calendar-functions.js index a178159ca..01b1096d5 100644 --- a/airtime_mvc/public/js/airtime/schedule/full-calendar-functions.js +++ b/airtime_mvc/public/js/airtime/schedule/full-calendar-functions.js @@ -35,7 +35,7 @@ function makeTimeStamp(date){ function dayClick(date, allDay, jsEvent, view){ // The show from will be preloaded if the user is admin or program manager. // Hence, if the user if DJ then it won't open anything. - if(userType == "A" || userType == "P"){ + if(userType == "S" || userType == "A" || userType == "P"){ var now, today, selected, chosenDate, chosenTime; now = adjustDateToServerDate(new Date(), serverTimezoneOffset); @@ -163,7 +163,7 @@ function viewDisplay( view ) { if(($("#add-show-form").length == 1) && ($("#add-show-form").css('display')=='none') && ($('.fc-header-left > span').length == 5)) { //userType is defined in bootstrap.php, and is derived from the currently logged in user. - if(userType == "A" || userType == "P"){ + if(userType == "S" || userType == "A" || userType == "P"){ makeAddShowButton(); } } diff --git a/airtime_mvc/public/js/airtime/user/user.js b/airtime_mvc/public/js/airtime/user/user.js index 71f9eed6e..f3a414b66 100644 --- a/airtime_mvc/public/js/airtime/user/user.js +++ b/airtime_mvc/public/js/airtime/user/user.js @@ -4,6 +4,17 @@ function populateForm(entries){ $('.errors').remove(); $('.success').remove(); + if (entries.type === 'S') + { + $("#user_details").hide(); + $("#user_details_superadmin_message").show(); + $('#type').attr('disabled', '1'); + } else { + $("#user_details").show(); + $("#user_details_superadmin_message").hide(); + $('#type').removeAttr('disabled'); + } + $('#user_id').val(entries.id); $('#login').val(entries.login); $('#first_name').val(entries.first_name); @@ -57,6 +68,10 @@ function rowCallback( nRow, aData, iDisplayIndex ){ } else if ( aData['type'] == "P" ) { $('td:eq(3)', nRow).html( $.i18n._('Program Manager') ); + } else if ( aData['type'] == "S" ) + { + $('td:eq(3)', nRow).html( $.i18n._('Super Admin') ); + $('td:eq(4)', nRow).html(""); //Disable deleting the super admin } return nRow; @@ -183,7 +198,7 @@ $(document).ready(function() { var newUser = {login:"", first_name:"", last_name:"", type:"G", id:""}; - $('#add_user_button').live('click', function(){populateForm(newUser)}); + $('#add_user_button').live('click', function(){populateForm(newUser);}); $('#save_user').live('click', function(){ var data = $('#user_form').serialize();