diff --git a/airtime_mvc/application/Bootstrap.php b/airtime_mvc/application/Bootstrap.php index 57ff42b0d..91736f51a 100644 --- a/airtime_mvc/application/Bootstrap.php +++ b/airtime_mvc/application/Bootstrap.php @@ -24,12 +24,14 @@ require_once "FileIO.php"; require_once "OsPath.php"; require_once "Database.php"; require_once "ProvisioningHelper.php"; +require_once "GoogleAnalytics.php"; require_once "Timezone.php"; require_once "Auth.php"; require_once __DIR__.'/forms/helpers/ValidationTypes.php'; require_once __DIR__.'/forms/helpers/CustomDecorators.php'; require_once __DIR__.'/controllers/plugins/RabbitMqPlugin.php'; require_once __DIR__.'/controllers/plugins/Maintenance.php'; +require_once __DIR__.'/controllers/plugins/ConversionTracking.php'; require_once __DIR__.'/modules/rest/controllers/ShowImageController.php'; require_once __DIR__.'/modules/rest/controllers/MediaController.php'; @@ -52,6 +54,7 @@ Application_Model_Auth::pinSessionToClient(Zend_Auth::getInstance()); $front = Zend_Controller_Front::getInstance(); $front->registerPlugin(new RabbitMqPlugin()); +$front->registerPlugin(new Zend_Controller_Plugin_ConversionTracking()); $front->throwExceptions(false); //localization configuration diff --git a/airtime_mvc/application/common/FileIO.php b/airtime_mvc/application/common/FileIO.php index 2c7724757..4d1ffd68c 100644 --- a/airtime_mvc/application/common/FileIO.php +++ b/airtime_mvc/application/common/FileIO.php @@ -65,7 +65,9 @@ class Application_Common_FileIO //We can have multiple levels of output buffering. Need to //keep looping until all have been disabled!!! //http://www.php.net/manual/en/function.ob-end-flush.php - while (@ob_end_flush()); + while (ob_get_level() > 0) { + ob_end_flush(); + } // NOTE: We can't use fseek here because it does not work with streams // (a.k.a. Files stored in the cloud) diff --git a/airtime_mvc/application/common/GoogleAnalytics.php b/airtime_mvc/application/common/GoogleAnalytics.php new file mode 100644 index 000000000..00a95a51e --- /dev/null +++ b/airtime_mvc/application/common/GoogleAnalytics.php @@ -0,0 +1,92 @@ +sub($trialDuration); + $interval = $today->diff($accountCreationDate); + $accountDuration = $interval->days; + } + + $code = "$( document ).ready(function() { + dataLayer.push({ + 'UserID': '" . $clientId . "', + 'Customer': 'Customer', + 'PlanType': '" . $plan . "', + 'Trial': '" . $isTrial . "', + 'AccountDuration': '" . strval($accountDuration) . "' + }); + });"; + //No longer sending these variables because we used to make a query to WHMCS + //to fetch them, which was slow. + // 'ZipCode': '" . $postcode . "', + // 'Country': '" . $country . "', + + } catch (Exception $e) { + Logging::error($e); + return ""; + } + return $code; + } + + /** Generate the JavaScript snippet that logs a trial to paid conversion */ + public static function generateConversionTrackingJavaScript() + { + $newPlan = Application_Model_Preference::GetPlanLevel(); + $oldPlan = Application_Model_Preference::GetOldPlanLevel(); + + $code = "dataLayer.push({'event': 'Conversion', + 'Conversion': 'Trial to Paid', + 'Old Plan' : '$oldPlan', + 'New Plan' : '$newPlan'});"; + return $code; + } + + /** Return true if the user used to be on a trial plan and was just converted to a paid plan. */ + public static function didPaidConversionOccur($request) + { + $userInfo = Zend_Auth::getInstance()->getStorage()->read(); + if ($userInfo) { + $user = new Application_Model_User($userInfo->id); + } else { + return; + } + + $oldPlan = Application_Model_Preference::GetOldPlanLevel(); + + if ($user->isSuperAdmin() && + !$user->isSourcefabricAdmin() && + $request->getControllerKey() !== "thank-you") + { + //Only tracking trial->paid conversions for now. + if ($oldPlan == "trial") + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/airtime_mvc/application/common/HTTPHelper.php b/airtime_mvc/application/common/HTTPHelper.php index 177892e1f..d6f1d6820 100644 --- a/airtime_mvc/application/common/HTTPHelper.php +++ b/airtime_mvc/application/common/HTTPHelper.php @@ -27,7 +27,7 @@ class Application_Common_HTTPHelper if (empty($baseDir)) { $baseDir = "/"; } - if ($baseDir[0] != "") { + if ($baseDir[0] != "/") { $baseDir = "/" . $baseDir; } diff --git a/airtime_mvc/application/configs/ACL.php b/airtime_mvc/application/configs/ACL.php index 5105b812f..fe6e4d3d6 100644 --- a/airtime_mvc/application/configs/ACL.php +++ b/airtime_mvc/application/configs/ACL.php @@ -36,6 +36,7 @@ $ccAcl->add(new Zend_Acl_Resource('library')) ->add(new Zend_Acl_Resource('rest:media')) ->add(new Zend_Acl_Resource('rest:show-image')) ->add(new Zend_Acl_Resource('billing')) + ->add(new Zend_Acl_Resource('thank-you')) ->add(new Zend_Acl_Resource('provisioning')) ->add(new Zend_Acl_Resource('embeddableplayer')); @@ -70,7 +71,11 @@ $ccAcl->allow('G', 'index') ->allow('A', 'user') ->allow('A', 'systemstatus') ->allow('A', 'preference') +<<<<<<< HEAD ->allow('A', 'embeddableplayer') +======= + ->allow('S', 'thank-you') +>>>>>>> saas ->allow('S', 'billing'); diff --git a/airtime_mvc/application/configs/conf.php b/airtime_mvc/application/configs/conf.php index eae99d778..f89a5472e 100644 --- a/airtime_mvc/application/configs/conf.php +++ b/airtime_mvc/application/configs/conf.php @@ -44,6 +44,13 @@ class Config { $CC_CONFIG['dev_env'] = 'production'; } + //Backported static_base_dir default value into saas for now. + if (array_key_exists('static_base_dir', $values['general'])) { + $CC_CONFIG['staticBaseDir'] = $values['general']['static_base_dir']; + } else { + $CC_CONFIG['staticBaseDir'] = '/'; + } + // Parse separate conf file for cloud storage values $cloudStorageConfig = "/etc/airtime-saas/".$CC_CONFIG['dev_env']."/cloud_storage.conf"; if (!file_exists($cloudStorageConfig)) { diff --git a/airtime_mvc/application/controllers/ShowbuilderController.php b/airtime_mvc/application/controllers/ShowbuilderController.php index 5638cf719..3bb30abf4 100644 --- a/airtime_mvc/application/controllers/ShowbuilderController.php +++ b/airtime_mvc/application/controllers/ShowbuilderController.php @@ -35,7 +35,7 @@ class ShowbuilderController extends Zend_Controller_Action $user = Application_Model_User::GetCurrentUser(); $userType = $user->getType(); $this->view->headScript()->appendScript("localStorage.setItem( 'user-type', '$userType' );"); - $this->view->headScript()->appendScript($this->generateGoogleTagManagerDataLayerJavaScript()); + $this->view->headScript()->appendScript(Application_Common_GoogleAnalytics::generateGoogleTagManagerDataLayerJavaScript()); $this->view->headScript()->appendFile($baseUrl.'js/contextmenu/jquery.contextMenu.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'js/datatables/js/jquery.dataTables.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); @@ -367,104 +367,5 @@ class ShowbuilderController extends Zend_Controller_Action throw new Exception("this controller is/was a no-op please fix your code"); } - - /** Returns a string containing the JavaScript code to pass some billing account info - * into Google Tag Manager / Google Analytics, so we can track things like the plan type. - */ - private static function generateGoogleTagManagerDataLayerJavaScript() - { - $code = ""; - - try - { - $accessKey = $_SERVER["WHMCS_ACCESS_KEY"]; - $username = $_SERVER["WHMCS_USERNAME"]; - $password = $_SERVER["WHMCS_PASSWORD"]; - $url = "https://account.sourcefabric.com/includes/api.php?accesskey=" . $accessKey; # URL to WHMCS API file goes here - - $postfields = array(); - $postfields["username"] = $username; - $postfields["password"] = md5($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)."&"; - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); // WHMCS IP whitelist doesn't support IPv6 - 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); - - $arr = json_decode($jsondata); # Decode JSON String - - if ($arr->result !== "success") { - Logging::warn("WHMCS API call failed in " . __FUNCTION__); - return; - } - - $client = $arr->client; - $stats = $arr->stats; - $currencyCode = $client->currency_code; - //$incomeCents = NumberFormatter::parseCurrency($stats->income, $currencyCode); - - $isTrial = true; - if (strpos($stats->income, "0.00") === FALSE) { - $isTrial = false; - } - /* - if ($incomeCents > 0) { - $isTrial = false; - }*/ - $plan = Application_Model_Preference::GetPlanLevel(); - $country = $client->country; - $postcode = $client->postcode; - - //Figure out how long the customer has been around using a mega hack. - //(I'm avoiding another round trip to WHMCS for now...) - //We calculate it based on the trial end date... - $trialEndDateStr = Application_Model_Preference::GetTrialEndingDate(); - if ($trialEndDateStr == '') { - $accountDuration = 0; - } else { - $today = new DateTime(); - $trialEndDate = new DateTime($trialEndDateStr); - $trialDuration = new DateInterval("P30D"); //30 day trial duration - $accountCreationDate = $trialEndDate->sub($trialDuration); - $interval = $today->diff($accountCreationDate); - $accountDuration = $interval->days; - } - - $code = "$( document ).ready(function() { - dataLayer.push({ - 'ZipCode': '" . $postcode . "', - 'UserID': '" . $client->id . "', - 'Customer': 'Customer', - 'PlanType': '" . $plan . "', - 'Trial': '" . $isTrial . "', - 'Country': '" . $country . "', - 'AccountDuration': '" . strval($accountDuration) . "' - }); - });"; - - } - catch (Exception $e) - { - return ""; - } - return $code; - } + } diff --git a/airtime_mvc/application/controllers/ThankYouController.php b/airtime_mvc/application/controllers/ThankYouController.php new file mode 100644 index 000000000..86a57a54c --- /dev/null +++ b/airtime_mvc/application/controllers/ThankYouController.php @@ -0,0 +1,48 @@ +view->stationUrl = Application_Common_HTTPHelper::getStationUrl(); + $this->view->conversionUrl = Application_Common_HTTPHelper::getStationUrl() . 'thank-you/confirm-conversion'; + $this->view->gaEventTrackingJsCode = ""; //Google Analytics event tracking code that logs an event. + + // Embed the Google Analytics conversion tracking code if the + // user is a super admin and old plan level is set to trial. + if (Application_Common_GoogleAnalytics::didPaidConversionOccur($this->getRequest())) { + $this->view->gaEventTrackingJsCode = Application_Common_GoogleAnalytics::generateConversionTrackingJavaScript(); + } + + $csrf_namespace = new Zend_Session_Namespace('csrf_namespace'); + $csrf_element = new Zend_Form_Element_Hidden('csrf'); + $csrf_element->setValue($csrf_namespace->authtoken)->setRequired('true')->removeDecorator('HtmlTag')->removeDecorator('Label'); + $csrf_form = new Zend_Form(); + $csrf_form->addElement($csrf_element); + $this->view->form = $csrf_form; + } + + /** Confirm that a conversion was tracked. */ + public function confirmConversionAction() + { + $this->view->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + $current_namespace = new Zend_Session_Namespace('csrf_namespace'); + $observed_csrf_token = $this->_getParam('csrf_token'); + $expected_csrf_token = $current_namespace->authtoken; + + if($observed_csrf_token != $expected_csrf_token) { + Logging::info("Invalid CSRF token"); + return; + } + + if ($this->getRequest()->isPost()) { + Logging::info("Goal conversion from trial to paid."); + // Clear old plan level so we prevent duplicate events. + // This should only be called from AJAX. See thank-you/index.phtml + Application_Model_Preference::ClearOldPlanLevel(); + } + } +} \ No newline at end of file diff --git a/airtime_mvc/application/controllers/UpgradeController.php b/airtime_mvc/application/controllers/UpgradeController.php index 9af3dc1e9..518e173ac 100644 --- a/airtime_mvc/application/controllers/UpgradeController.php +++ b/airtime_mvc/application/controllers/UpgradeController.php @@ -20,7 +20,8 @@ class UpgradeController extends Zend_Controller_Action array_push($upgraders, new AirtimeUpgrader259()); array_push($upgraders, new AirtimeUpgrader2510()); array_push($upgraders, new AirtimeUpgrader2511()); - + array_push($upgraders, new AirtimeUpgrader2512()); + $didWePerformAnUpgrade = false; try { diff --git a/airtime_mvc/application/controllers/plugins/ConversionTracking.php b/airtime_mvc/application/controllers/plugins/ConversionTracking.php new file mode 100644 index 000000000..09904828d --- /dev/null +++ b/airtime_mvc/application/controllers/plugins/ConversionTracking.php @@ -0,0 +1,21 @@ +getControllerName() != 'thank-you') + { + $request->setModuleName('default') + ->setControllerName('thank-you') + ->setActionName('index') + ->setDispatched(true); + } + } + } + +} \ No newline at end of file diff --git a/airtime_mvc/application/controllers/upgrade_sql/airtime_2.5.12/upgrade.sql b/airtime_mvc/application/controllers/upgrade_sql/airtime_2.5.12/upgrade.sql new file mode 100644 index 000000000..92ca30ade --- /dev/null +++ b/airtime_mvc/application/controllers/upgrade_sql/airtime_2.5.12/upgrade.sql @@ -0,0 +1,2 @@ +ALTER TABLE cc_show ALTER COLUMN description TYPE varchar(8192); +ALTER TABLE cc_show_instances ALTER COLUMN description TYPE varchar(8192); diff --git a/airtime_mvc/application/layouts/scripts/layout.phtml b/airtime_mvc/application/layouts/scripts/layout.phtml index df5d0281b..bf97086c5 100644 --- a/airtime_mvc/application/layouts/scripts/layout.phtml +++ b/airtime_mvc/application/layouts/scripts/layout.phtml @@ -21,7 +21,6 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= partial('partialviews/trialBox.phtml', array("is_trial"=>$this->isTrial(), "trial_remain"=> $this->trialRemaining())) ?>