From fa340c3e30f49fe0706d2fdfb57af077460b9920 Mon Sep 17 00:00:00 2001 From: drigato Date: Mon, 31 Aug 2015 13:37:20 -0400 Subject: [PATCH 01/35] SAAS-976: September 1st: Undo the billing page changes for the August promotion --- airtime_mvc/application/common/Billing.php | 146 ------------------ .../controllers/BillingController.php | 36 ----- .../views/scripts/billing/upgrade.phtml | 78 ++-------- airtime_mvc/public/css/billing.css | 17 +- .../images/august_aweomse_promo_banner.png | Bin 49859 -> 0 bytes 5 files changed, 16 insertions(+), 261 deletions(-) delete mode 100644 airtime_mvc/public/css/images/august_aweomse_promo_banner.png diff --git a/airtime_mvc/application/common/Billing.php b/airtime_mvc/application/common/Billing.php index 0baa56104..5fc2cb94f 100644 --- a/airtime_mvc/application/common/Billing.php +++ b/airtime_mvc/application/common/Billing.php @@ -329,150 +329,4 @@ class Billing $result = Billing::makeRequest($credentials["url"], $query_string); } - /** - * Returns an array of the current Airtime Pro plan IDs. - * This excludes any old, free, promotional, or custom plans. - */ - public static function getCurrentPaidProductIds() - { - $products = self::getProducts(); - $productIds = array(); - foreach ($products as $k => $p) { - array_push($productIds, $p["pid"]); - } - - return $productIds; - } - - /** - * Returns an array of the Awesome August 2015 Promotional plans - */ - public static function getAwesomeAugustPromoProducts() - { - $credentials = self::getAPICredentials(); - - $postfields = array(); - $postfields["username"] = $credentials["username"]; - $postfields["password"] = md5($credentials["password"]); - $postfields["action"] = "getproducts"; - $postfields["responsetype"] = "json"; - //gid is the Airtime product group id on whmcs - $postfields["gid"] = WHMCS_AIRTIME_GROUP_ID; - - $query_string = ""; - foreach ($postfields AS $k=>$v) $query_string .= "$k=".urlencode($v)."&"; - - $result = self::makeRequest($credentials["url"], $query_string); - $promoProducts = $result["products"]["product"]; - - foreach ($promoProducts as $k => $p) { - if (strpos($p["name"], "Awesome August 2015") === false) { - unset($promoProducts[$k]); - } - } - - return $promoProducts; - } - - /** - * Returns the eligible promo plan ID that corresponds to the regular paid plan. - * - * i.e. if the client wants to upgrade to the Plus plan this function returns the - * plan id of the Awesome August 2015 Plus plan. - */ - public static function getEligibleAwesomeAugustPromoPlanId($productName) - { - $promoPlans = self::getAwesomeAugustPromoProducts(); - $promoPlanId = ""; - - foreach($promoPlans as $k => $p) { - if (strpos($p["name"], $productName) !== false) { - $promoPlanId = $p["pid"]; - break; - } - } - - return $promoPlanId; - } - - public static function getProductName($productId) - { - $products = self::getProducts(); - $productName = ""; - - foreach($products as $k => $p) { - if ($p["pid"] == $productId) { - $productName = $p["name"]; - break; - } - } - - return $productName; - } - - public static function isClientEligibleForPromo($newProductId, $newProductBillingCycle) - { - // use this to check if client is upgrading from an old plan - $currentPaidPlanProductIds = self::getCurrentPaidProductIds(); - - $currentPlanProduct = self::getClientCurrentAirtimeProduct(); - $currentPlanProductId = $currentPlanProduct["pid"]; - $currentPlanBillingCycle = strtolower($currentPlanProduct["billingcycle"]); - - if (self::isClientOnAwesomeAugustPromoPlan($currentPlanProductId)) { - - $newEligiblePromoId = self::getEligibleAwesomeAugustPromoPlanId( - self::getProductName($newProductId) - ); - - if ($newProductBillingCycle == "annually" || $newEligiblePromoId > $currentPlanProductId) { - return true; - } else { - return false; - } - } - - // if client is on trial plan, YES - if ($currentPlanProductId == AIRTIME_PRO_FREE_TRIAL_PLAN_ID) { - return true; - } - - // if client is currently on monthly or annually or old/free plan AND (upgrading OR upgrading/downgrading to annual plan), YES - if ($currentPlanBillingCycle == "monthly" || $currentPlanBillingCycle == "free account" - || $currentPlanBillingCycle == "annually") { - // is the client changing billing cycle to annual? - if ($newProductBillingCycle == "annually") { - return true; - } - - // Is the client staying on monthly and upgrading? - // This won't hold true if the client is on an old/free plan because the - // old/free plan ids are higher than the current paid plan ids. - if ($newProductBillingCycle == "monthly" && $newProductId > $currentPlanProductId) { - return true; - } - - // Is the client staying on monthly and upgrading from an old plan? - if ($newProductBillingCycle == "monthly" && !in_array($currentPlanProductId, $currentPaidPlanProductIds) - && in_array($newProductId, $currentPaidPlanProductIds)) { - return true; - } - } - - return false; - } - - public static function isClientOnAwesomeAugustPromoPlan($currentPlanId) - { - $promoPlans = self::getAwesomeAugustPromoProducts(); - - foreach ($promoPlans as $k => $p) { - if ($p["pid"] == $currentPlanId) { - return true; - } - } - - return false; - } - } diff --git a/airtime_mvc/application/controllers/BillingController.php b/airtime_mvc/application/controllers/BillingController.php index 92f499199..2be8bc7e4 100644 --- a/airtime_mvc/application/controllers/BillingController.php +++ b/airtime_mvc/application/controllers/BillingController.php @@ -10,7 +10,6 @@ class BillingController extends Zend_Controller_Action { //Two of the actions in this controller return JSON because they're used for AJAX: $ajaxContext = $this->_helper->getHelper('AjaxContext'); $ajaxContext->addActionContext('vat-validator', 'json') - ->addActionContext('promo-eligibility-check', 'json') ->addActionContext('is-country-in-eu', 'json') ->initContext(); } @@ -20,33 +19,6 @@ class BillingController extends Zend_Controller_Action { $this->_redirect('billing/upgrade'); } - public function promoEligibilityCheckAction() - { - $this->view->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - $request = $this->getRequest(); - if (!$request->isPost()) { - throw new Exception("Must POST data to promoEligibilityCheckAction."); - } - $data = $request->getPost(); - - $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) { - $eligible = Billing::isClientEligibleForPromo( - $data["newproductid"], $data["newproductbillingcycle"]); - - //Set the return JSON value - $this->_helper->json(array("result"=>$eligible)); - } else { - $this->getResponse()->setHttpResponseCode(403); - $this->_helper->json(array("result"=>false, "error"=>"CSRF token did not match.")); - } - } - public function upgradeAction() { $CC_CONFIG = Config::getConfig(); @@ -63,14 +35,6 @@ class BillingController extends Zend_Controller_Action { if ($form->isValid($formData)) { - // Check if client is eligible for promo and update the new product id if so - $eligibleForPromo = Billing::isClientEligibleForPromo( - $formData["newproductid"], $formData["newproductbillingcycle"]); - if ($eligibleForPromo) { - $newProductName = Billing::getProductName($formData["newproductid"]); - $formData["newproductid"] = Billing::getEligibleAwesomeAugustPromoPlanId($newProductName); - } - $credentials = Billing::getAPICredentials(); //Check if VAT should be applied or not to this invoice. diff --git a/airtime_mvc/application/views/scripts/billing/upgrade.phtml b/airtime_mvc/application/views/scripts/billing/upgrade.phtml index 5d583f245..1843fcbbe 100644 --- a/airtime_mvc/application/views/scripts/billing/upgrade.phtml +++ b/airtime_mvc/application/views/scripts/billing/upgrade.phtml @@ -120,30 +120,6 @@ function configureByCountry(countryCode) }); } -function promoEligibilityCheck() -{ - var newproductid = $("input[type='radio'][name='newproductid']:checked").val(); - - // newproductid can be undefined if the client is currently on an old plan - // and they just change the billing cycle value without selecting a new plan type. - // In this case, let's not check if they are eligible for the promo because - // they won't be able to upgrade without selecting a new plan first. - if (newproductid === undefined) { - return; - } - var newproductbillingcycle = $("input[type='radio'][name='newproductbillingcycle']:checked").val(); - - $.post("/billing/promo-eligibility-check", {"newproductid": newproductid, - "newproductbillingcycle": newproductbillingcycle, "csrf_token": $("#csrf").attr('value')}) - .success(function(data) { - if (data.result == true) { - $("#promo-plan-eligible").show(); - } else if ($("#promo-plan-eligible").is(":visible")) { - $("#promo-plan-eligible").hide(); - } - }); -} - $(document).ready(function() { configureByCountry($("#country").val()); @@ -152,11 +128,9 @@ $(document).ready(function() { $("input[name='newproductid']").change(function() { validatePlan(); recalculateTotals(); - promoEligibilityCheck(); }); $("input[name='newproductbillingcycle']").change(function() { recalculateTotals(); - promoEligibilityCheck(); }); $("#country").change(function() { @@ -196,17 +170,13 @@ $(document).ready(function() {

-
- - -
- - - - + + + + - - + - - - - - + - - - @@ -322,10 +276,6 @@ echo($currentProduct["name"]);
Save 15% on annual plans (Hobbyist plan excluded).
-
-
Subtotal:
diff --git a/airtime_mvc/public/css/billing.css b/airtime_mvc/public/css/billing.css index 33cbe006c..8e3cad43c 100644 --- a/airtime_mvc/public/css/billing.css +++ b/airtime_mvc/public/css/billing.css @@ -56,8 +56,8 @@ border-spacing: 0px; border-collapse: separate; border: 1px solid #777; - width: 680px; - margin-left: -140px; + width: 600px; + margin-left: -100px; /*background-color: #555;*/ table-layout: fixed; margin-top: 20px; @@ -65,19 +65,6 @@ box-shadow: 0px 5px 5px rgba(0,0,0,0.5); } -.pricing-grid .august-promo div { - display: inline-block; - background-color: #ff611f; - padding: 3px 5px; - border-radius: 5px; - margin-left: -5px; - color: #ffffff; -} - -.pricing-grid .august-promo span { - text-decoration: line-through; -} - .pricing-grid td, .pricing-grid th { border-bottom: 1px solid #999; diff --git a/airtime_mvc/public/css/images/august_aweomse_promo_banner.png b/airtime_mvc/public/css/images/august_aweomse_promo_banner.png deleted file mode 100644 index 22d10be9e370a59da035a683822a7902ba27eb8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49859 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

|QV~PSi#JzfHkP46ji9#bUH7b_r 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 From 46e126145ed97dbcea5329125b220d574b230174 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Tue, 1 Sep 2015 13:53:59 -0400 Subject: [PATCH 02/35] CC-6122 - change library behaviour for guest users --- .../controllers/LibraryController.php | 89 ------- .../controllers/LocaleController.php | 10 +- .../controllers/plugins/Acl_plugin.php | 6 +- .../application/services/CalendarService.php | 2 +- .../public/ajax/library_placeholders.json | 28 +++ airtime_mvc/public/css/dashboard.css | 1 + .../library/events/library_showbuilder.js | 233 ++++++++---------- .../public/js/airtime/library/library.js | 20 +- 8 files changed, 165 insertions(+), 224 deletions(-) create mode 100644 airtime_mvc/public/ajax/library_placeholders.json diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php index 2a04b66e8..1c85621e7 100644 --- a/airtime_mvc/application/controllers/LibraryController.php +++ b/airtime_mvc/application/controllers/LibraryController.php @@ -24,95 +24,6 @@ class LibraryController extends Zend_Controller_Action public function indexAction() { $this->_redirect("showbuilder"); -// $CC_CONFIG = Config::getConfig(); -// -// $request = $this->getRequest(); -// $baseUrl = Application_Common_OsPath::getBaseDir(); -// -// $this->view->headScript()->appendFile($baseUrl.'js/blockui/jquery.blockUI.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $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'); -// $this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.pluginAPI.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.fnSetFilteringDelay.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.ColVis.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.colReorder.min.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.FixedColumns.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.columnFilter.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// -// $this->view->headScript()->appendFile($baseUrl.'js/airtime/buttons/buttons.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/airtime/utilities/utilities.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/airtime/library/library.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/airtime/library/events/library_playlistbuilder.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// -// $this->view->headLink()->appendStylesheet($baseUrl.'css/media_library.css?'.$CC_CONFIG['airtime_version']); -// $this->view->headLink()->appendStylesheet($baseUrl.'css/jquery.contextMenu.css?'.$CC_CONFIG['airtime_version']); -// $this->view->headLink()->appendStylesheet($baseUrl.'css/datatables/css/ColVis.css?'.$CC_CONFIG['airtime_version']); -// $this->view->headLink()->appendStylesheet($baseUrl.'css/datatables/css/dataTables.colReorder.min.css?'.$CC_CONFIG['airtime_version']); -// $this->view->headLink()->appendStylesheet($baseUrl.'css/waveform.css?'.$CC_CONFIG['airtime_version']); -// -// $this->view->headScript()->appendFile($baseUrl.'js/airtime/library/spl.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/airtime/playlist/smart_blockbuilder.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/observer/observer.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/config.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/curves.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/fades.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/local_storage.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/controls.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/playout.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/track_render.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/track.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/time_scale.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// $this->view->headScript()->appendFile($baseUrl.'js/waveformplaylist/playlist.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); -// -// //arbitrary attributes need to be allowed to set an id for the templates. -// $this->view->headScript()->setAllowArbitraryAttributes(true); -// //$this->view->headScript()->appendScript(file_get_contents(APPLICATION_PATH.'/../public/js/waveformplaylist/templates/bottombar.tpl'), -// // 'text/template', array('id' => 'tpl_playlist_cues', 'noescape' => true)); -// -// $this->view->headLink()->appendStylesheet($baseUrl.'css/playlist_builder.css?'.$CC_CONFIG['airtime_version']); -// -// try { -// -// $obj_sess = new Zend_Session_Namespace(UI_PLAYLISTCONTROLLER_OBJ_SESSNAME); -// if (isset($obj_sess->id)) { -// $objInfo = Application_Model_Library::getObjInfo($obj_sess->type); -// $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_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER)); -// -// if ($isAdminOrPM || $obj->getCreatorId() == $userInfo->id) { -// $this->view->obj = $obj; -// if ($obj_sess->type == "block") { -// $form = new Application_Form_SmartBlockCriteria(); -// $form->startForm($obj_sess->id); -// $this->view->form = $form; -// } -// } -// -// $formatter = new LengthFormatter($obj->getLength()); -// $this->view->length = $formatter->format(); -// $this->view->type = $obj_sess->type; -// } -// -// //get user settings and determine if we need to hide -// // or show the playlist editor -// $showPlaylist = false; -// $data = Application_Model_Preference::getLibraryScreenSettings(); -// if (!is_null($data)) { -// if ($data["playlist"] == "true") { -// $showPlaylist = true; -// } -// } -// $this->view->showPlaylist = $showPlaylist; -// } catch (PlaylistNotFoundException $e) { -// $this->playlistNotFound($obj_sess->type); -// } catch (Exception $e) { -// $this->playlistNotFound($obj_sess->type); -// Logging::info($e->getMessage()); -// //$this->playlistUnknownError($e); -// } } protected function playlistNotFound($p_type) diff --git a/airtime_mvc/application/controllers/LocaleController.php b/airtime_mvc/application/controllers/LocaleController.php index 0d7ce2281..330bae1d0 100644 --- a/airtime_mvc/application/controllers/LocaleController.php +++ b/airtime_mvc/application/controllers/LocaleController.php @@ -51,8 +51,14 @@ class LocaleController extends Zend_Controller_Action //library/events/library_showbuilder.js //already in library/events/library_playlistbuilder.js "Please select a cursor position on timeline." => _("Please select a cursor position on timeline."), - "You haven't added any " => _("You haven't added any "), - "Learn about " => _("Learn about "), + "You haven't added any tracks" => _("You haven't added any tracks"), + "You haven't added any playlists" => _("You haven't added any playlists"), + "You haven't added any smart blocks" => _("You haven't added any smart blocks"), + "You haven't added any webstreams" => _("You haven't added any webstreams"), + "Learn about tracks" => _("Learn about tracks"), + "Learn about playlists" => _("Learn about playlists"), + "Learn about smart blocks" => _("Learn about smart blocks"), + "Learn about webstreams" => _("Learn about webstreams"), "Click 'New' to create one." => _("Click 'New' to create one."), //"Adding 1 Item" => _("Adding 1 Item"), //"Adding %s Items" => _("Adding %s Items"), diff --git a/airtime_mvc/application/controllers/plugins/Acl_plugin.php b/airtime_mvc/application/controllers/plugins/Acl_plugin.php index c08533ab9..fb87574e0 100644 --- a/airtime_mvc/application/controllers/plugins/Acl_plugin.php +++ b/airtime_mvc/application/controllers/plugins/Acl_plugin.php @@ -204,7 +204,11 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract $resourceName, $request->getActionName())) { /** Redirect to access denied page */ - $this->denyAccess(); + $this->getResponse() + ->setHttpResponseCode(403) + ->appendBody("You don't have permission to access this resource.") + ->sendResponse(); + // $this->denyAccess(); /* This results in a 404! */ } } } diff --git a/airtime_mvc/application/services/CalendarService.php b/airtime_mvc/application/services/CalendarService.php index f6d87c116..853052644 100644 --- a/airtime_mvc/application/services/CalendarService.php +++ b/airtime_mvc/application/services/CalendarService.php @@ -78,7 +78,7 @@ class Application_Service_CalendarService $menu["schedule"] = array( // "name"=> _("Add / Remove Content"), - "name" => _("Schedule Show"), + "name" => _("Schedule Tracks"), "icon" => "add-remove-content", "url" => $baseUrl."showbuilder/builder-dialog/"); } diff --git a/airtime_mvc/public/ajax/library_placeholders.json b/airtime_mvc/public/ajax/library_placeholders.json new file mode 100644 index 000000000..d1fa5f8af --- /dev/null +++ b/airtime_mvc/public/ajax/library_placeholders.json @@ -0,0 +1,28 @@ +{ + "1": { + "media": "tracks", + "icon": "icon-music", + "subtext": "Click 'Upload' to add some now.", + "href": "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/add-media/" + }, + "2": { + "media": "playlists", + "icon": "icon-list", + "subtext": "Click 'New' to create one now.", + "href": "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/library/" + }, + "3": { + "media": "smart blocks", + "icon": "icon-time", + "subtext": "Click 'New' to create one now.", + "href": "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/library/" + + }, + "4": { + "media": "webstreams", + "icon": "icon-random", + "subtext": "Click 'New' to create one now.", + "href": "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/library/" + }, + "unauthorized": "You don't have permission to view the library." +} \ No newline at end of file diff --git a/airtime_mvc/public/css/dashboard.css b/airtime_mvc/public/css/dashboard.css index 7ddd44b3d..d92d1041b 100644 --- a/airtime_mvc/public/css/dashboard.css +++ b/airtime_mvc/public/css/dashboard.css @@ -610,6 +610,7 @@ div.ColVis_collectionBackground { .ColVis.TableTools > button { padding: 3px 9px; + margin: -1px -1px 5px 0; } .ColVis_title { diff --git a/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js b/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js index 0d6cb047f..6cdf9d394 100644 --- a/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js +++ b/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js @@ -79,47 +79,23 @@ var AIRTIME = (function(AIRTIME) { if (emptyRow.length > 0) { emptyRow.hide(); - var opts = {}, - mediaType = parseInt($('.media_type_selector.selected').attr('data-selection-id')), + var mediaType = parseInt($('.media_type_selector.selected').attr('data-selection-id')), img = $('#library_empty_image'); - // TODO: once the new manual pages are added, change links! - if (mediaType > 1) { - opts.subtext = $.i18n._("Click 'New' to create one now."); - opts.href = "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/library/"; - } else { - opts.subtext = $.i18n._("Click 'Upload' to add some now."); - opts.href = "http://sourcefabric.booktype.pro/airtime-pro-for-broadcasters/add-media/"; - } - - switch(mediaType) { - case 1: - opts.media = $.i18n._('tracks'); - opts.icon = 'icon-music'; - break; - case 2: - opts.media = $.i18n._('playlists'); - opts.icon = 'icon-list'; - break; - case 3: - opts.media = $.i18n._('smart blocks'); - opts.icon = 'icon-time'; - break; - case 4: - opts.media = $.i18n._('webstreams'); - opts.icon = 'icon-random'; - break; - } - // Remove all classes for when we change between empty media types img.removeClass(function() { return $( this ).attr( "class" ); }); - img.addClass("icon-white " + opts.icon); - $('#library_empty_text').html( - $.i18n._("You haven't added any ") + opts.media + "." - + "
" + opts.subtext - + "
" + $.i18n._("Learn about ") + opts.media + "" - ); + // TODO: once the new manual pages are added, change links! + $.getJSON( "ajax/library_placeholders.json", function( data ) { + var opts = data[mediaType]; + img.addClass("icon-white " + opts.icon); + $('#library_empty_text').html( + $.i18n._("You haven't added any " + opts.media + ".") + + "
" + $.i18n._(opts.subtext) + + "
" + $.i18n._("Learn about " + opts.media) + "" + ); + }) ; + $('#library_empty').show(); } else { $('#library_empty').hide(); @@ -281,118 +257,119 @@ var AIRTIME = (function(AIRTIME) { mod.createToolbarButtons(); mod.moveSearchBarToHeader(); - - $toolbar.append($menu); - // add to timeline button - $toolbar - .find('#library-plus') - .click( - function() { - if (AIRTIME.button.isDisabled('btn-group #library-plus') === true) { - return; - } + if (localStorage.getItem('user-type') != 'G') { + $toolbar.append($menu); + // add to timeline button + $toolbar + .find('#library-plus') + .click( + function () { - var selected = AIRTIME.library.getSelectedData(), data, i, length, temp, aMediaIds = [], aSchedIds = [], aData = []; + if (AIRTIME.button.isDisabled('btn-group #library-plus') === true) { + return; + } - if ($("#show_builder_table").is(":visible")) { - for (i = 0, length = selected.length; i < length; i++) { - data = selected[i]; - aMediaIds.push( { - "id" : data.id, - "type" : data.ftype - }); - } + var selected = AIRTIME.library.getSelectedData(), data, i, length, temp, aMediaIds = [], aSchedIds = [], aData = []; - // process selected files/playlists. - $("#show_builder_table tr.sb-selected").each(function(i, el) { - aData.push($(el).data("aData")); + if ($("#show_builder_table").is(":visible")) { + for (i = 0, length = selected.length; i < length; i++) { + data = selected[i]; + aMediaIds.push({ + "id": data.id, + "type": data.ftype }); + } - // process selected schedule rows to add media - // after. - for (i = 0, length = aData.length; i < length; i++) { - temp = aData[i]; - aSchedIds.push( { - "id" : temp.id, - "instance" : temp.instance, - "timestamp" : temp.timestamp - }); - } + // process selected files/playlists. + $("#show_builder_table tr.sb-selected").each(function (i, el) { + aData.push($(el).data("aData")); + }); - if (aSchedIds.length == 0) { - if (!addToCurrentOrNext(aSchedIds)) { - return; - } - } + // process selected schedule rows to add media + // after. + for (i = 0, length = aData.length; i < length; i++) { + temp = aData[i]; + aSchedIds.push({ + "id": temp.id, + "instance": temp.instance, + "timestamp": temp.timestamp + }); + } - AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds); - } else { - for (i = 0, length = selected.length; i < length; i++) { - data = selected[i]; - aMediaIds.push([data.id, data.ftype]); - } - - // check if a playlist/block is open before adding items - if ($('.active-tab .obj_type').val() == 'playlist' - || $('.active-tab .obj_type').val() == 'block') { - AIRTIME.playlist.fnAddItems(aMediaIds, undefined, 'after'); + if (aSchedIds.length == 0) { + if (!addToCurrentOrNext(aSchedIds)) { + return; } } - }); - // delete from library. - $toolbar.find('.icon-trash').parent().click(function() { + AIRTIME.showbuilder.fnAdd(aMediaIds, aSchedIds); + } else { + for (i = 0, length = selected.length; i < length; i++) { + data = selected[i]; + aMediaIds.push([data.id, data.ftype]); + } - if (AIRTIME.button.isDisabled('icon-trash') === true) { - return; - } + // check if a playlist/block is open before adding items + if ($('.active-tab .obj_type').val() == 'playlist' + || $('.active-tab .obj_type').val() == 'block') { + AIRTIME.playlist.fnAddItems(aMediaIds, undefined, 'after'); + } + } + }); - AIRTIME.library.fnDeleteSelectedItems(); - }); + // delete from library. + $toolbar.find('.icon-trash').parent().click(function () { + if (AIRTIME.button.isDisabled('icon-trash') === true) { + return; + } - $toolbar.find('#sb-new').click(function() { - if (AIRTIME.button.isDisabled('btn-group #sb-new') === true) { - return; - } + AIRTIME.library.fnDeleteSelectedItems(); + }); - var selection = $(".media_type_selector.selected").attr("data-selection-id"); + $toolbar.find('#sb-new').click(function () { + if (AIRTIME.button.isDisabled('btn-group #sb-new') === true) { + return; + } - if (selection == 2) { - AIRTIME.playlist.fnNew(); - } else if (selection == 3) { - AIRTIME.playlist.fnNewBlock(); - } else if (selection == 4) { - AIRTIME.playlist.fnWsNew(); - } - }); + var selection = $(".media_type_selector.selected").attr("data-selection-id"); - - $toolbar.find('#sb-edit').click(function() { - if (AIRTIME.button.isDisabled('btn-group #sb-edit') === true) { - return; - } - - var selected = $(".lib-selected"); - - selected.each(function(i, el) { - var data = $(el).data("aData"); - - if (data.ftype === "audioclip") { - $.get(baseUrl + "library/edit-file-md/id/" + data.id, {format: "json"}, function(json){ - AIRTIME.playlist.fileMdEdit(json); - //buildEditMetadataDialog(json); - }); - } else if (data.ftype === "playlist" || data.ftype === "block") { - AIRTIME.playlist.fnEdit(data.id, data.ftype, baseUrl+'playlist/edit'); - AIRTIME.playlist.validatePlaylistElements(); - } else if (data.ftype === "stream") { - AIRTIME.playlist.fnEdit(data.id, data.ftype, baseUrl + 'webstream/edit'); + if (selection == 2) { + AIRTIME.playlist.fnNew(); + } else if (selection == 3) { + AIRTIME.playlist.fnNewBlock(); + } else if (selection == 4) { + AIRTIME.playlist.fnWsNew(); } }); - }); - mod.createToolbarDropDown(); + + $toolbar.find('#sb-edit').click(function () { + if (AIRTIME.button.isDisabled('btn-group #sb-edit') === true) { + return; + } + + var selected = $(".lib-selected"); + + selected.each(function (i, el) { + var data = $(el).data("aData"); + + if (data.ftype === "audioclip") { + $.get(baseUrl + "library/edit-file-md/id/" + data.id, {format: "json"}, function (json) { + AIRTIME.playlist.fileMdEdit(json); + //buildEditMetadataDialog(json); + }); + } else if (data.ftype === "playlist" || data.ftype === "block") { + AIRTIME.playlist.fnEdit(data.id, data.ftype, baseUrl + 'playlist/edit'); + AIRTIME.playlist.validatePlaylistElements(); + } else if (data.ftype === "stream") { + AIRTIME.playlist.fnEdit(data.id, data.ftype, baseUrl + 'webstream/edit'); + } + }); + }); + + mod.createToolbarDropDown(); + } }; return AIRTIME; diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 5214b80b2..75c34e6a9 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -558,6 +558,19 @@ var AIRTIME = (function(AIRTIME) { } + function handleAjaxError(r) { + // If the request was denied due to permissioning + if (r.status === 403) { + // Hide the processing div + $("#library_display_wrapper").find(".dt-process-rel").hide(); + $.getJSON( "ajax/library_placeholders.json", function( data ) { + $('#library_empty_text').text($.i18n._(data.unauthorized)); + }) ; + + $('#library_empty').show(); + } + } + oTable = $libTable.dataTable( { // put hidden columns at the top to insure they can never be visible @@ -689,13 +702,14 @@ var AIRTIME = (function(AIRTIME) { getUsabilityHint(); - $.ajax( { + $.ajax({ "dataType": 'json', "type": "POST", "url": sSource, "data": aoData, - "success": fnCallback - } ); + "success": fnCallback, + "error": handleAjaxError, + }); }, "fnRowCallback": AIRTIME.library.fnRowCallback, "fnCreatedRow": function( nRow, aData, iDataIndex ) { From f283d694816cb100538729744297b5d352ca0aa9 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Tue, 1 Sep 2015 14:27:54 -0400 Subject: [PATCH 03/35] Add vertical scrollbar to recent uploads table --- .../application/views/scripts/plupload/index.phtml | 2 -- airtime_mvc/public/css/addmedia.css | 10 +++++----- airtime_mvc/public/js/airtime/library/library.js | 2 +- airtime_mvc/public/js/airtime/library/plupload.js | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/airtime_mvc/application/views/scripts/plupload/index.phtml b/airtime_mvc/application/views/scripts/plupload/index.phtml index a935e8151..ff3b68ade 100644 --- a/airtime_mvc/application/views/scripts/plupload/index.phtml +++ b/airtime_mvc/application/views/scripts/plupload/index.phtml @@ -68,10 +68,8 @@

-
Awesome HobbyistAwesome StarterAwesome PlusAwesome PremiumHobbyistStarterPlusPremium
1 Stream @@ -228,40 +198,24 @@ $(document).ready(function() { 64kbps, 128kbps, and 196kbps Stream Quality
- 5 Listeners
-
10 Listeners
+
5 Listeners - 40 Listeners per stream
-
80 Listeners per stream
+
40 Listeners per stream - 100 Listeners per stream
-
200 Listeners per stream
+
100 Listeners per stream - 500 Listeners per stream
-
1000 Listeners per stream
+
500 Listeners per stream
- 2GB Storage
-
4GB Storage
+
2GB Storage - 5GB Storage
-
10GB Storage
+
5GB Storage - 30GB Storage
-
60GB Storage
+
30GB Storage - 150GB Storage
-
300GB Storage
+
150GB Storage
-
\ No newline at end of file diff --git a/airtime_mvc/public/css/addmedia.css b/airtime_mvc/public/css/addmedia.css index 1a225a175..1186bb762 100644 --- a/airtime_mvc/public/css/addmedia.css +++ b/airtime_mvc/public/css/addmedia.css @@ -1,9 +1,8 @@ @CHARSET "UTF-8"; -#recent_uploads > .dataTables_scrolling +#recent_uploads .dataTables_scrolling { - top: 41px; - bottom: 8px; + top: 0; } #recent_uploads_wrapper @@ -25,9 +24,10 @@ position: absolute; left: 8px; right: 8px; - bottom: 0; - top: 0; + bottom: 8px; + top: 41px; border: 1px solid #5b5b5b; + overflow-x: hidden; } #recent_uploads_table_wrapper > .fg-toolbar diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js index 75c34e6a9..54109ec58 100644 --- a/airtime_mvc/public/js/airtime/library/library.js +++ b/airtime_mvc/public/js/airtime/library/library.js @@ -708,7 +708,7 @@ var AIRTIME = (function(AIRTIME) { "url": sSource, "data": aoData, "success": fnCallback, - "error": handleAjaxError, + "error": handleAjaxError }); }, "fnRowCallback": AIRTIME.library.fnRowCallback, diff --git a/airtime_mvc/public/js/airtime/library/plupload.js b/airtime_mvc/public/js/airtime/library/plupload.js index 2a5e35158..5d0fabcc5 100644 --- a/airtime_mvc/public/js/airtime/library/plupload.js +++ b/airtime_mvc/public/js/airtime/library/plupload.js @@ -185,7 +185,7 @@ $(document).ready(function () { "bFilter": false, "bSort": false, //"sDom": '<"H">frtip<"F"l>', - "sDom": 'frt<"F"lip>', + "sDom": '<"dataTables_scrolling"frt><"F"lip>', "bPaginate": true, "sPaginationType": "full_numbers", "oLanguage": getDatatablesStrings({ From 92f722452abab136d057fb54fa90a86141940275 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Tue, 1 Sep 2015 16:05:59 -0400 Subject: [PATCH 04/35] Adjust user settings form widths to be consistent --- airtime_mvc/public/css/styles.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/airtime_mvc/public/css/styles.css b/airtime_mvc/public/css/styles.css index 1cf57d2c8..c58102ad5 100644 --- a/airtime_mvc/public/css/styles.css +++ b/airtime_mvc/public/css/styles.css @@ -3322,10 +3322,14 @@ dd .stream-status { padding-bottom: 5px; } -.edit-user-global input, .edit-user-global select { +.edit-user-global input { width: 200px; } +.edit-user-global select { + width: 212px; +} + .edit-user-errors { margin-left: 33% !important; width: 208px; From a99bc2671571581ea5b03376b2519bb74fe97357 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Tue, 1 Sep 2015 16:07:16 -0400 Subject: [PATCH 05/35] Set autofocus on login username field --- airtime_mvc/application/forms/Login.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/airtime_mvc/application/forms/Login.php b/airtime_mvc/application/forms/Login.php index 623fa14fa..0fd00d47b 100644 --- a/airtime_mvc/application/forms/Login.php +++ b/airtime_mvc/application/forms/Login.php @@ -37,19 +37,17 @@ class Application_Form_Login extends Zend_Form )); // Add username element - $this->addElement('text', 'username', array( - 'label' => _('Username:'), - 'class' => 'input_text', - 'required' => true, - 'value' => (isset($CC_CONFIG['demo']) && $CC_CONFIG['demo'] == 1)?'admin':'', - 'filters' => array('StringTrim'), - 'validators' => array( - 'NotEmpty', - ), - 'decorators' => array( - 'ViewHelper' - ) - )); + $username = new Zend_Form_Element_Text("username"); + $username->setLabel(_('Username:')) + ->setAttribs(array( + 'autofocus' => 'true', + 'class' => 'input_text', + 'required' => 'true')) + ->setValue((isset($CC_CONFIG['demo']) && $CC_CONFIG['demo'] == 1)?'admin':'') + ->addFilter('StringTrim') + ->setDecorators(array('ViewHelper')) + ->setValidators(array('NotEmpty')); + $this->addElement($username); // Add password element $this->addElement('password', 'password', array( From 37df86723d2a8ea7f9f3e19df1d30631caa52991 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Tue, 1 Sep 2015 16:10:33 -0400 Subject: [PATCH 06/35] CC-6127 - Add 'Use station default' option to user settings timezone, don't set user timezone by default when creating the admin user --- airtime_mvc/application/common/Timezone.php | 2 +- airtime_mvc/application/forms/EditUser.php | 7 +++++-- airtime_mvc/application/models/Preference.php | 10 +++------- airtime_mvc/build/sql/defaultdata.sql | 4 +++- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/airtime_mvc/application/common/Timezone.php b/airtime_mvc/application/common/Timezone.php index 7da82f6f9..5e12338e2 100644 --- a/airtime_mvc/application/common/Timezone.php +++ b/airtime_mvc/application/common/Timezone.php @@ -18,7 +18,7 @@ class Application_Common_Timezone 'UTC' => DateTimeZone::UTC ); - $tzlist = array(); + $tzlist = array(NULL => "Use station default"); foreach ($regions as $name => $mask) { $ids = DateTimeZone::listIdentifiers($mask); diff --git a/airtime_mvc/application/forms/EditUser.php b/airtime_mvc/application/forms/EditUser.php index d9486113f..cbb2ec22f 100644 --- a/airtime_mvc/application/forms/EditUser.php +++ b/airtime_mvc/application/forms/EditUser.php @@ -121,11 +121,14 @@ class Application_Form_EditUser extends Zend_Form $locale->setValue(Application_Model_Preference::GetUserLocale($currentUserId)); $locale->setDecorators(array('ViewHelper')); $this->addElement($locale); - + + $stationTz = Application_Model_Preference::GetTimezone($currentUserId); + $userTz = Application_Model_Preference::GetUserTimezone($currentUserId); + $timezone = new Zend_Form_Element_Select("cu_timezone"); $timezone->setLabel(_("Interface Timezone:")); $timezone->setMultiOptions(Application_Common_Timezone::getTimezones()); - $timezone->setValue(Application_Model_Preference::GetUserTimezone($currentUserId)); + $timezone->setValue($userTz == $stationTz ? null : $userTz); $timezone->setDecorators(array('ViewHelper')); $this->addElement($timezone); diff --git a/airtime_mvc/application/models/Preference.php b/airtime_mvc/application/models/Preference.php index c6e6fff49..30d9a9e9e 100644 --- a/airtime_mvc/application/models/Preference.php +++ b/airtime_mvc/application/models/Preference.php @@ -477,10 +477,6 @@ class Application_Model_Preference public static function SetUserTimezone($timezone = null) { - // When a new user is created they will get the default timezone - // setting which the admin sets on preferences page - if (is_null($timezone)) - $timezone = self::GetDefaultTimezone(); self::setValue("user_timezone", $timezone, true); } @@ -520,10 +516,10 @@ class Application_Model_Preference public static function GetUserLocale() { $locale = self::getValue("user_locale", true); - if (!$locale) { + // empty() checks for null and empty strings - more robust than !val + if (empty($locale)) { return self::GetDefaultLocale(); - } - else { + } else { return $locale; } } diff --git a/airtime_mvc/build/sql/defaultdata.sql b/airtime_mvc/build/sql/defaultdata.sql index bed25b869..4c2ca0af7 100644 --- a/airtime_mvc/build/sql/defaultdata.sql +++ b/airtime_mvc/build/sql/defaultdata.sql @@ -353,7 +353,9 @@ INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('zh_CN', '简体中文' -- added in 2.5.2 INSERT INTO cc_pref (keystr, valstr) VALUES ('timezone', 'UTC'); -INSERT INTO cc_pref (subjid, keystr, valstr) VALUES (1, 'user_timezone', 'UTC'); +-- We don't want to set the user timezone by default - it should instead use the station timezone +-- until the user changes it manually. +-- INSERT INTO cc_pref (subjid, keystr, valstr) VALUES (1, 'user_timezone', 'UTC'); INSERT INTO cc_pref (keystr, valstr) VALUES ('import_timestamp', '0'); From 6959c459cd40baee5cf643e015f2cd2ae4966713 Mon Sep 17 00:00:00 2001 From: Albert Santoni Date: Wed, 2 Sep 2015 10:40:38 -0400 Subject: [PATCH 07/35] Scheduler transaction improvement and preferences transaction removal * Added a transaction to createAndFillShowInstancesPastPopulatedUntilDate to ensure two requests don't trigger duplicate repeated show instances in the calendar. * Removed the transaction from setValue() in Preference and opt for a row-level lock instead. --- airtime_mvc/application/models/Preference.php | 13 ++++++---- airtime_mvc/application/models/Show.php | 24 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/airtime_mvc/application/models/Preference.php b/airtime_mvc/application/models/Preference.php index c6e6fff49..1dd9124a0 100644 --- a/airtime_mvc/application/models/Preference.php +++ b/airtime_mvc/application/models/Preference.php @@ -28,7 +28,9 @@ class Application_Model_Preference { $cache = new Cache(); $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME); - $con->beginTransaction(); + + //We are using row-level locking in Postgres via "FOR UPDATE" instead of a transaction here + //because sometimes this function needs to be called while a transaction is already started. try { /* Comment this out while we reevaluate it in favor of a unique constraint @@ -40,7 +42,7 @@ class Application_Model_Preference } //Check if key already exists - $sql = "SELECT COUNT(*) FROM cc_pref" + $sql = "SELECT valstr FROM cc_pref" ." WHERE keystr = :key"; $paramMap = array(); @@ -50,13 +52,16 @@ class Application_Model_Preference if ($isUserValue) { $sql .= " AND subjid = :id"; $paramMap[':id'] = $userId; - } + } + + $sql .= " FOR UPDATE"; $result = Application_Common_Database::prepareAndExecute($sql, $paramMap, Application_Common_Database::COLUMN, PDO::FETCH_ASSOC, $con); + $result = count($result); $paramMap = array(); if ($result > 1) { @@ -103,9 +108,7 @@ class Application_Model_Preference PDO::FETCH_ASSOC, $con); - $con->commit(); } catch (Exception $e) { - $con->rollback(); header('HTTP/1.0 503 Service Unavailable'); Logging::info("Database error: ".$e->getMessage()); exit; diff --git a/airtime_mvc/application/models/Show.php b/airtime_mvc/application/models/Show.php index 8fcb64cda..832186d80 100644 --- a/airtime_mvc/application/models/Show.php +++ b/airtime_mvc/application/models/Show.php @@ -847,14 +847,24 @@ SQL; */ public static function createAndFillShowInstancesPastPopulatedUntilDate($needScheduleUntil) { - //UTC DateTime object - $showsPopUntil = Application_Model_Preference::GetShowsPopulatedUntil(); - //if application is requesting shows past our previous populated until date, generate shows up until this point. - if (is_null($showsPopUntil) || $showsPopUntil->getTimestamp() < $needScheduleUntil->getTimestamp()) { - $service_show = new Application_Service_ShowService(); - $ccShow = $service_show->delegateInstanceCreation(null, $needScheduleUntil, true); - Application_Model_Preference::SetShowsPopulatedUntil($needScheduleUntil); + $con = Propel::getConnection(CcPrefPeer::DATABASE_NAME); + try { + $con->beginTransaction(); + + //UTC DateTime object + $showsPopUntil = Application_Model_Preference::GetShowsPopulatedUntil(); + //if application is requesting shows past our previous populated until date, generate shows up until this point. + if (is_null($showsPopUntil) || $showsPopUntil->getTimestamp() < $needScheduleUntil->getTimestamp()) { + $service_show = new Application_Service_ShowService(); + $ccShow = $service_show->delegateInstanceCreation(null, $needScheduleUntil, true); + Application_Model_Preference::SetShowsPopulatedUntil($needScheduleUntil); + } + $con->commit(); + } catch (Exception $e) { + $con->rollBack(); + throw $e; } + } /** From 63f563f06debaae4d9fba67c1ca886c4ac5bfbb7 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 11:22:24 -0400 Subject: [PATCH 08/35] Fix calendar context menu word wrapping --- airtime_mvc/application/services/CalendarService.php | 12 ++++++++---- airtime_mvc/public/css/styles.css | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/airtime_mvc/application/services/CalendarService.php b/airtime_mvc/application/services/CalendarService.php index 853052644..e85814afb 100644 --- a/airtime_mvc/application/services/CalendarService.php +++ b/airtime_mvc/application/services/CalendarService.php @@ -142,7 +142,8 @@ class Application_Service_CalendarService if ($isRepeating) { if ($populateInstance) { $menu["edit"] = array( - "name" => _("Edit This Instance"), + // "name" => _("Edit This Instance"), + "name" => _("Edit Instance"), "icon" => "edit", "url" => $baseUrl . "Schedule/populate-repeating-show-instance-form" ); @@ -160,7 +161,8 @@ class Application_Service_CalendarService ); $menu["edit"]["items"]["instance"] = array( - "name" => _("Edit This Instance"), + // "name" => _("Edit This Instance"), + "name" => _("Edit Instance"), "icon" => "edit", "url" => $baseUrl . "Schedule/populate-repeating-show-instance-form" ); @@ -188,12 +190,14 @@ class Application_Service_CalendarService "items" => array()); $menu["del"]["items"]["single"] = array( - "name"=> _("Delete This Instance"), + // "name"=> _("Delete This Instance"), + "name"=> _("Delete Instance"), "icon" => "delete", "url" => $baseUrl."schedule/delete-show-instance"); $menu["del"]["items"]["following"] = array( - "name"=> _("Delete This Instance and All Following"), + // "name"=> _("Delete This Instance and All Following"), + "name"=> _("Delete Instance and All Following"), "icon" => "delete", "url" => $baseUrl."schedule/delete-show"); } elseif ($populateInstance) { diff --git a/airtime_mvc/public/css/styles.css b/airtime_mvc/public/css/styles.css index c58102ad5..dfff09eab 100644 --- a/airtime_mvc/public/css/styles.css +++ b/airtime_mvc/public/css/styles.css @@ -3462,6 +3462,10 @@ dd .stream-status { min-width: 200px !important; } +.calendar-context-menu .context-menu-list { + min-width: 260px !important; +} + /* Add Media Page */ #upload_form, #recent_uploads_wrapper { From 3e29ecad798740f0df0b7eac991e3aa83b3abbf2 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 11:41:51 -0400 Subject: [PATCH 09/35] CC-6128 - open placeholder 'Learn more' links in new tab --- .../public/js/airtime/library/events/library_showbuilder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js b/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js index 6cdf9d394..2bb35ee04 100644 --- a/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js +++ b/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js @@ -92,7 +92,7 @@ var AIRTIME = (function(AIRTIME) { $('#library_empty_text').html( $.i18n._("You haven't added any " + opts.media + ".") + "
" + $.i18n._(opts.subtext) - + "
" + $.i18n._("Learn about " + opts.media) + "" + + "
" + $.i18n._("Learn about " + opts.media) + "" ); }) ; From c45f1ddd1ce4053cce10c6cc200da2fe80bb0a28 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 11:52:43 -0400 Subject: [PATCH 10/35] SAAS-1039 - add processing overlay to listener stats screen --- airtime_mvc/public/css/styles.css | 6 ++++++ .../js/airtime/listenerstat/listenerstat.js | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/airtime_mvc/public/css/styles.css b/airtime_mvc/public/css/styles.css index dfff09eab..e636103a6 100644 --- a/airtime_mvc/public/css/styles.css +++ b/airtime_mvc/public/css/styles.css @@ -3738,6 +3738,12 @@ button.btn-icon-text > i.icon-white { font-size: 12px; } +#flot_placeholder.processing { + width: 100%; + height: 100%; + background: url("img/loading.gif") no-repeat 50% 50% rgba(0, 0, 0, .25); +} + .dashboard-btn { width: 50%; } diff --git a/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js b/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js index 8507c876e..0c239455f 100644 --- a/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js +++ b/airtime_mvc/public/js/airtime/listenerstat/listenerstat.js @@ -10,7 +10,7 @@ $(document).ready(function() { width = width * .91; $("#listenerstat_content").find("#flot_placeholder").width(width); $("#listenerstat_content").find("#legend").width(width); - + getDataAndPlot(); listenerstat_content.find("#his_submit").click(function(){ @@ -21,7 +21,17 @@ $(document).ready(function() { }); }); -function getDataAndPlot(startTimestamp, endTimestamp){ +/** + * Toggle a spinner overlay so the user knows the page is processing + */ +function toggleOverlay() { + $('#flot_placeholder').toggleClass('processing'); +} + +function getDataAndPlot(startTimestamp, endTimestamp) { + // Turn on the processing overlay + toggleOverlay(); + // get data $.get(baseUrl+'Listenerstat/get-data', {start: startTimestamp, end: endTimestamp}, function(data){ out = new Object(); @@ -37,6 +47,8 @@ function getDataAndPlot(startTimestamp, endTimestamp){ out[mpName] = plotData; }); plot(out); + // Turn off the processing overlay + toggleOverlay(); }) } From 9d373791c04f27cd4655d6b868bedb0311b361b0 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 12:23:23 -0400 Subject: [PATCH 11/35] SAAS-1038 - fix bug where 403's weren't being redirected properly --- .../application/controllers/plugins/Acl_plugin.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/airtime_mvc/application/controllers/plugins/Acl_plugin.php b/airtime_mvc/application/controllers/plugins/Acl_plugin.php index fb87574e0..18f1c69a5 100644 --- a/airtime_mvc/application/controllers/plugins/Acl_plugin.php +++ b/airtime_mvc/application/controllers/plugins/Acl_plugin.php @@ -82,7 +82,7 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract * @param string $module * @return void **/ - public function setErrorPage($action, $controller = 'error', $module = null) + public function setErrorPage($action, $controller = 'error', $module = 'default') { $this->_errorPage = array('module' => $module, 'controller' => $controller, @@ -204,11 +204,8 @@ class Zend_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract $resourceName, $request->getActionName())) { /** Redirect to access denied page */ - $this->getResponse() - ->setHttpResponseCode(403) - ->appendBody("You don't have permission to access this resource.") - ->sendResponse(); - // $this->denyAccess(); /* This results in a 404! */ + $this->setErrorPage('error403'); + $this->denyAccess(); /* This results in a 404! */ } } } From 0722225211c5ba00aead897d36f046654d954fd8 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 12:45:37 -0400 Subject: [PATCH 12/35] Front end tweaks and fixes --- airtime_mvc/public/css/dashboard.css | 3 +++ .../js/airtime/library/events/library_showbuilder.js | 7 ++++--- airtime_mvc/public/js/airtime/library/spl.js | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/airtime_mvc/public/css/dashboard.css b/airtime_mvc/public/css/dashboard.css index d92d1041b..d8220ea6a 100644 --- a/airtime_mvc/public/css/dashboard.css +++ b/airtime_mvc/public/css/dashboard.css @@ -150,6 +150,9 @@ div.btn > span { #library_empty_image { opacity: .3; + width: 16px; + height: 20px; + top: -20px; margin-top: -2px; padding-right: 2px; /* For the webstream icon */ diff --git a/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js b/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js index 2bb35ee04..3459c862d 100644 --- a/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js +++ b/airtime_mvc/public/js/airtime/library/events/library_showbuilder.js @@ -77,6 +77,7 @@ var AIRTIME = (function(AIRTIME) { cb.append(""); } + var libEmpty = $('#library_empty'); if (emptyRow.length > 0) { emptyRow.hide(); var mediaType = parseInt($('.media_type_selector.selected').attr('data-selection-id')), @@ -94,11 +95,11 @@ var AIRTIME = (function(AIRTIME) { + "
" + $.i18n._(opts.subtext) + "
" + $.i18n._("Learn about " + opts.media) + "" ); - }) ; + }); - $('#library_empty').show(); + libEmpty.show(); } else { - $('#library_empty').hide(); + libEmpty.hide(); } if ($("#show_builder_table").is(":visible")) { diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 27c0aa743..6ec41c6fd 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -569,6 +569,10 @@ var AIRTIME = (function(AIRTIME){ if (pane.get(0) == curr.get(0)) { // Closing the current tab, otherwise we don't need to switch tabs AIRTIME.showbuilder.switchTab(toPane, toTab); } + + // If we close a tab that was causing tabs to wrap to the next row, we need to resize to change the + // margin for the tab nav + AIRTIME.playlist.onResize(); } mod.closeTab = function(id) { From 69293703f21fd53b2232d8faa9aa10bae2a26202 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 12:53:24 -0400 Subject: [PATCH 13/35] SAAS-1041 - add confirmation window when deleting a user --- airtime_mvc/public/js/airtime/user/user.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/airtime_mvc/public/js/airtime/user/user.js b/airtime_mvc/public/js/airtime/user/user.js index e277d4266..921369546 100644 --- a/airtime_mvc/public/js/airtime/user/user.js +++ b/airtime_mvc/public/js/airtime/user/user.js @@ -43,10 +43,16 @@ function rowClickCallback(row_id){ }}); } -function removeUserCallback(row_id, nRow){ - $.ajax({ url: baseUrl+'User/remove-user/id/'+ row_id +'/format/json', dataType:"text", success:function(data){ - var o = $('#users_datatable').dataTable().fnDeleteRow(nRow); - }}); +function removeUserCallback(row_id, nRow) { + if (confirm($.i18n._("Are you sure you want to delete this user?"))) { + $.ajax({ + url: baseUrl + 'User/remove-user/id/' + row_id + '/format/json', + dataType: "text", + success: function (data) { + var o = $('#users_datatable').dataTable().fnDeleteRow(nRow); + } + }); + } } function rowCallback( nRow, aData, iDisplayIndex ){ From c020e3449da1a905056582de84e7240afbd407fa Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 12:55:36 -0400 Subject: [PATCH 14/35] SAAS-1044 - fix links in usability hints --- airtime_mvc/application/common/UsabilityHints.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/airtime_mvc/application/common/UsabilityHints.php b/airtime_mvc/application/common/UsabilityHints.php index 934d89c2c..0b9ed51b0 100644 --- a/airtime_mvc/application/common/UsabilityHints.php +++ b/airtime_mvc/application/common/UsabilityHints.php @@ -55,7 +55,7 @@ class Application_Common_UsabilityHints return _("Upload some tracks below to add them to your library!"); } else { return sprintf(_("It looks like you haven't uploaded any audio files yet. %sUpload a file now%s."), - "", + "", ""); } } else if (!self::isFutureOrCurrentShowScheduled()) { @@ -63,7 +63,7 @@ class Application_Common_UsabilityHints return _("Click the 'New Show' button and fill out the required fields."); } else { return sprintf(_("It looks like you don't have any shows scheduled. %sCreate a show now%s."), - "", + "", ""); } } else if (self::isCurrentShowEmpty()) { @@ -73,14 +73,14 @@ class Application_Common_UsabilityHints return _("To start broadcasting, cancel the current linked show by clicking on it and selecting 'Cancel Show'."); } else { return sprintf(_("Linked shows need to be filled with tracks before it starts. To start broadcasting cancel the current linked show and schedule an unlinked show. - %sCreate an unlinked show now%s."), "", ""); + %sCreate an unlinked show now%s."), "", ""); } } else { if ($userIsOnCalendarPage) { return _("To start broadcasting, click on the current show and select 'Schedule Show'"); } else { return sprintf(_("It looks like the current show needs more tracks. %sAdd tracks to your show now%s."), - "", + "", ""); } } @@ -89,7 +89,7 @@ class Application_Common_UsabilityHints return _("Click on the show starting next and select 'Schedule Show'"); } else { return sprintf(_("It looks like the next show is empty. %sAdd tracks to your show now%s."), - "", + "", ""); } } else { From 80d78e9fc58c48f9faf9bed4cbbaaa6cacdb59ae Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 13:08:16 -0400 Subject: [PATCH 15/35] SAAS-1043 - fix flexbox settings for editors to work in firefox --- airtime_mvc/public/css/dashboard.css | 8 +++++++- airtime_mvc/public/css/styles.css | 20 ++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/airtime_mvc/public/css/dashboard.css b/airtime_mvc/public/css/dashboard.css index d8220ea6a..c30d2e848 100644 --- a/airtime_mvc/public/css/dashboard.css +++ b/airtime_mvc/public/css/dashboard.css @@ -459,8 +459,10 @@ li.ui-state-default { max-height: 60%; overflow-x: hidden; width: 100%; - flex: 1 0 100%; + flex: 1 0 auto; padding: 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; } @@ -503,6 +505,8 @@ li.ui-state-default { padding: 5px; border: 1px solid #444; border-radius: 3px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; background-color: #111; display: flex; @@ -562,6 +566,8 @@ li.ui-state-default { } .side_playlist .zend_form input { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; height: 26px; } diff --git a/airtime_mvc/public/css/styles.css b/airtime_mvc/public/css/styles.css index e636103a6..f2a21787d 100644 --- a/airtime_mvc/public/css/styles.css +++ b/airtime_mvc/public/css/styles.css @@ -1357,6 +1357,8 @@ input[type="checkbox"] { } #schedule_calendar { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; width: 100%; @@ -1819,6 +1821,8 @@ button, input { } .user-management .dataTables_filter input { width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; margin-bottom:8px; } @@ -1945,7 +1949,9 @@ div.success{ float: right; height: 30px; line-height: 24px; - box-sizing: border-box;; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } div.errors, span.errors{ @@ -1957,7 +1963,9 @@ div.errors, span.errors{ border:1px solid #c83f3f; height: 30px; line-height: 24px; - box-sizing: border-box;; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } span.errors.sp-errors{ @@ -2625,6 +2633,8 @@ label span { fieldset > legend { width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; background: #333; @@ -3469,6 +3479,8 @@ dd .stream-status { /* Add Media Page */ #upload_form, #recent_uploads_wrapper { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; } @@ -3597,6 +3609,8 @@ button.btn-icon-text > i.icon-white { .dashboard_sub_nav { padding: 0px 0px 0px 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; } @@ -3726,6 +3740,8 @@ button.btn-icon-text > i.icon-white { #listenerstat_content { width: 100%; display: block; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; box-sizing: border-box; } From 8d0cfdc20244dc65ffd9ee0255fb1056dae4aac6 Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 13:58:38 -0400 Subject: [PATCH 16/35] Fix empty-name playlist bug --- .../controllers/PlaylistController.php | 13 ++++++++++++- airtime_mvc/public/js/airtime/library/spl.js | 19 +++++++++++-------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/airtime_mvc/application/controllers/PlaylistController.php b/airtime_mvc/application/controllers/PlaylistController.php index d94c34ec2..b3c8ef317 100644 --- a/airtime_mvc/application/controllers/PlaylistController.php +++ b/airtime_mvc/application/controllers/PlaylistController.php @@ -30,6 +30,7 @@ class PlaylistController extends Zend_Controller_Action ->addActionContext('get-block-info', 'json') ->addActionContext('shuffle', 'json') ->addActionContext('empty-content', 'json') + ->addActionContext('change-playlist', 'json') ->initContext(); //This controller writes to the session all over the place, so we're going to reopen it for writing here. @@ -201,6 +202,16 @@ class PlaylistController extends Zend_Controller_Action $this->createFullResponse($obj); } + public function changePlaylistAction() { + $this->view->layout()->disableLayout(); // Don't inject the standard Now Playing header. + $this->_helper->viewRenderer->setNoRender(true); // Don't use (phtml) templates + + $id = $this->_getParam('id', null); + $type = $this->_getParam('type'); + + Application_Model_Library::changePlaylist($id, $type); + } + public function editAction() { $id = $this->_getParam('id', null); @@ -241,7 +252,7 @@ class PlaylistController extends Zend_Controller_Action Logging::info("Currently active {$type} {$obj_sess->id}"); if (in_array($obj_sess->id, $ids)) { Logging::info("Deleting currently active {$type}"); - Application_Model_Library::changePlaylist(null, $type); + // Application_Model_Library::changePlaylist(null, $type); } else { Logging::info("Not deleting currently active {$type}"); $obj = new $objInfo['className']($obj_sess->id); diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 6ec41c6fd..00342cd8a 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -421,17 +421,19 @@ var AIRTIME = (function(AIRTIME){ $('.zend_form + .spl-no-margin > div:has(*:visible):last').css('margin-left', 0); } - function getId() { - return parseInt($pl.find(".obj_id").val(), 10); + function getId(pl) { + pl = (pl === undefined) ? $pl : pl; + return parseInt(pl.find(".obj_id").val(), 10); } - mod.getModified = function() { - return parseInt($pl.find(".obj_lastMod").val(), 10); - } + mod.getModified = function(pl) { + pl = (pl === undefined) ? $pl : pl; + return parseInt(pl.find(".obj_lastMod").val(), 10); + }; mod.setModified = function(modified) { $pl.find(".obj_lastMod").val(modified); - } + }; function setTitleLabel(title) { $pl.find(".title_obj_name").text(title); @@ -1212,8 +1214,8 @@ var AIRTIME = (function(AIRTIME){ var url, id, lastMod, type, pl = (tabId === undefined) ? $pl : $('#pl-tab-content-' + tabId); stopAudioPreview(); - id = (plid === undefined) ? getId() : plid; - lastMod = mod.getModified(); + id = (plid === undefined) ? getId(pl) : plid; + lastMod = mod.getModified(pl); type = pl.find('.obj_type').val(); url = baseUrl+'playlist/delete'; @@ -1562,6 +1564,7 @@ var AIRTIME = (function(AIRTIME){ mod.setAsActive = function() { $pl = $(".active-tab"); + $.post(baseUrl + "playlist/change-playlist", {"id": getId(), "type": $pl.find('.obj_type').val()}); }; mod.init = function() { From 6046f8843b0c060b85cd32f6a14b1a7a0a107a75 Mon Sep 17 00:00:00 2001 From: Albert Santoni Date: Wed, 2 Sep 2015 15:07:17 -0400 Subject: [PATCH 17/35] CC-6129: Static smart block errors don't appear --- airtime_mvc/application/controllers/PlaylistController.php | 5 +---- airtime_mvc/public/js/airtime/library/spl.js | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/airtime_mvc/application/controllers/PlaylistController.php b/airtime_mvc/application/controllers/PlaylistController.php index d94c34ec2..f9b21cddd 100644 --- a/airtime_mvc/application/controllers/PlaylistController.php +++ b/airtime_mvc/application/controllers/PlaylistController.php @@ -595,10 +595,7 @@ class PlaylistController extends Zend_Controller_Action $this->view->obj = $bl; $this->view->id = $bl->getId(); $this->view->form = $form; - $viewPath = 'playlist/smart-block.phtml'; - $result['html'] = $this->view->render($viewPath); - $result['result'] = 1; - $this->_helper->json->sendJson($result); + $this->createFullResponse($bl, false, true); } } catch (BlockNotFoundException $e) { $this->playlistNotFound('block', true); diff --git a/airtime_mvc/public/js/airtime/library/spl.js b/airtime_mvc/public/js/airtime/library/spl.js index 27c0aa743..ca9ca135a 100644 --- a/airtime_mvc/public/js/airtime/library/spl.js +++ b/airtime_mvc/public/js/airtime/library/spl.js @@ -1001,7 +1001,7 @@ var AIRTIME = (function(AIRTIME){ }); }); - $pl.on("click", "#save_button", function(event) { + $pl.unbind().on("click", "#save_button", function(event) { /* Smart blocks: get name, description, and criteria * Playlists: get name, description */ From 63ce022a8d90072fca6f386cf66b5a8768c6e64f Mon Sep 17 00:00:00 2001 From: Duncan Sommerville Date: Wed, 2 Sep 2015 15:28:36 -0400 Subject: [PATCH 18/35] SAAS-1040 - station logo functionality improvements --- .../application/forms/GeneralPreferences.php | 2 +- airtime_mvc/application/models/Preference.php | 9 ++-- .../public/js/airtime/common/common.js | 35 ++++++++++++++++ .../js/airtime/preferences/preferences.js | 41 ++++++++++++++++--- .../public/js/airtime/schedule/add-show.js | 35 ---------------- 5 files changed, 76 insertions(+), 46 deletions(-) diff --git a/airtime_mvc/application/forms/GeneralPreferences.php b/airtime_mvc/application/forms/GeneralPreferences.php index be12ea565..508561bac 100644 --- a/airtime_mvc/application/forms/GeneralPreferences.php +++ b/airtime_mvc/application/forms/GeneralPreferences.php @@ -1,4 +1,4 @@ - 1) { @@ -103,8 +102,8 @@ class Application_Model_Preference $paramMap[':value'] = $value; Application_Common_Database::prepareAndExecute($sql, - $paramMap, - 'execute', + $paramMap, + Application_Common_Database::EXECUTE, PDO::FETCH_ASSOC, $con); diff --git a/airtime_mvc/public/js/airtime/common/common.js b/airtime_mvc/public/js/airtime/common/common.js index d8e43d979..55708d174 100644 --- a/airtime_mvc/public/js/airtime/common/common.js +++ b/airtime_mvc/public/js/airtime/common/common.js @@ -212,6 +212,41 @@ function validateTimeRange() { }; } +// validate uploaded images +function validateImage(img, el) { + // remove any existing error messages + if ($("#img-err")) { $("#img-err").remove(); } + + if (img.size > 2048000) { // 2MB - pull this from somewhere instead? + // hack way of inserting an error message + var err = $.i18n._("Selected file is too large"); + el.parent().after( + "
    " + + "
  • " + err + "
  • " + + "
"); + return false; + } else if (validateMimeType(img.type) < 0) { + var err = $.i18n._("File format is not supported"); + el.parent().after( + "
    " + + "
  • " + err + "
  • " + + "
"); + return false; + } + return true; +} + +// validate image mime type +function validateMimeType(mime) { + var extensions = [ + 'image/jpeg', + 'image/png', + 'image/gif' + // BMP? + ]; + return $.inArray(mime, extensions); +} + function pad(number, length) { return sprintf("%'0"+length+"d", number); } diff --git a/airtime_mvc/public/js/airtime/preferences/preferences.js b/airtime_mvc/public/js/airtime/preferences/preferences.js index 91a9bef3a..40e267ecd 100644 --- a/airtime_mvc/public/js/airtime/preferences/preferences.js +++ b/airtime_mvc/public/js/airtime/preferences/preferences.js @@ -33,10 +33,10 @@ function setSystemFromEmailReadonly() { var enableSystemEmails = $("#enableSystemEmail"); var systemFromEmail = $("#systemEmail"); if ($(enableSystemEmails).is(':checked')) { - systemFromEmail.removeAttr("readonly"); + systemFromEmail.removeAttr("readonly"); } else { systemFromEmail.attr("readonly", "readonly"); - } + } } function setMailServerInputReadonly() { @@ -114,14 +114,15 @@ function setMsAuthenticationFieldsReadonly(ele) { } function removeLogo() { - $.post(baseUrl+'Preference/remove-logo', function(json){}); - location.reload(); + $.post(baseUrl+'preference/remove-logo', function(json){}); + // Reload without resubmitting the form + location.href = location.href.replace(location.hash,""); } function deleteAllFiles() { var resp = confirm($.i18n._("Are you sure you want to delete all the tracks in your library?")) if (resp) { - $.post(baseUrl+'Preference/delete-all-files', function(json){}); + $.post(baseUrl+'preference/delete-all-files', function(json){}); location.reload(); } } @@ -153,6 +154,36 @@ $(document).ready(function() { }); });*/ + // when an image is uploaded, preview it to the user + var logo = $("#stationLogo"), + preview = $("#logo-img"); + logo.change(function(e) { + if (this.files && this.files[0]) { + preview.show(); + var reader = new FileReader(); // browser compatibility? + reader.onload = function (e) { + console.log("Reader loaded"); + preview.attr('src', e.target.result); + }; + + // check image size so we don't crash the page trying to render + if (validateImage(this.files[0], logo)) { + // read the image data as though it were a data URI + reader.readAsDataURL(this.files[0]); + } else { + // remove the file element data + $(this).val('').replaceWith($(this).clone(true)); + preview.hide(); + } + } else { + preview.hide(); + } + }); + + if (preview.attr('src').indexOf('images/') > -1) { + $("#logo-remove-btn").hide(); + } + showErrorSections(); setMailServerInputReadonly(); diff --git a/airtime_mvc/public/js/airtime/schedule/add-show.js b/airtime_mvc/public/js/airtime/schedule/add-show.js index 326bf9a14..dc459cd10 100644 --- a/airtime_mvc/public/js/airtime/schedule/add-show.js +++ b/airtime_mvc/public/js/airtime/schedule/add-show.js @@ -678,41 +678,6 @@ function setAddShowEvents(form) { } }); - // validate on upload - function validateImage(img, el) { - // remove any existing error messages - if ($("#img-err")) { $("#img-err").remove(); } - - if (img.size > 2048000) { // 2MB - pull this from somewhere instead? - // hack way of inserting an error message - var err = $.i18n._("Selected file is too large"); - el.parent().after( - "
    " + - "
  • " + err + "
  • " + - "
"); - return false; - } else if (validateMimeType(img.type) < 0) { - var err = $.i18n._("File format is not supported"); - el.parent().after( - "
    " + - "
  • " + err + "
  • " + - "
"); - return false; - } - return true; - } - - // Duplicate of the function in ShowImageController - function validateMimeType(mime) { - var extensions = [ - 'image/jpeg', - 'image/png', - 'image/gif' - // BMP? - ]; - return $.inArray(mime, extensions); - } - form.find("#add_show_logo_current_remove").click(function() { if (confirm($.i18n._('Are you sure you want to delete the current logo?'))) { var showId = $("#add_show_id").attr("value"); From 01b3ae1bf0f29886222b338b2ea57d3f7e22d0d8 Mon Sep 17 00:00:00 2001 From: Albert Santoni Date: Wed, 2 Sep 2015 16:25:30 -0400 Subject: [PATCH 19/35] Allow smart blocks with no criteria --- .../application/forms/SmartBlockCriteria.php | 4 ++ airtime_mvc/application/models/Block.php | 40 +++++++++---------- .../scripts/form/smart-block-criteria.phtml | 6 +-- .../js/airtime/playlist/smart_blockbuilder.js | 40 +++++++++++++------ 4 files changed, 55 insertions(+), 35 deletions(-) diff --git a/airtime_mvc/application/forms/SmartBlockCriteria.php b/airtime_mvc/application/forms/SmartBlockCriteria.php index 4c307cdb9..bc22b731d 100644 --- a/airtime_mvc/application/forms/SmartBlockCriteria.php +++ b/airtime_mvc/application/forms/SmartBlockCriteria.php @@ -370,6 +370,10 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm // add elelments that needs to be added // set multioption for modifier according to criteria_field $modRowMap = array(); + if (!isset($data['criteria'])) { + return $data; + } + foreach ($data['criteria'] as $critKey=>$d) { $count = 1; foreach ($d as $modKey=>$modInfo) { diff --git a/airtime_mvc/application/models/Block.php b/airtime_mvc/application/models/Block.php index a61c68658..767a982e7 100644 --- a/airtime_mvc/application/models/Block.php +++ b/airtime_mvc/application/models/Block.php @@ -1517,27 +1517,27 @@ SQL; $i++; } } - - // check if file exists - $qry->add("file_exists", "true", Criteria::EQUAL); - $qry->add("hidden", "false", Criteria::EQUAL); - $sortTracks = 'random'; - if (isset($storedCrit['sort'])) { - $sortTracks = $storedCrit['sort']['value']; - } - if ($sortTracks == 'newest') { - $qry->addDescendingOrderByColumn('utime'); - } - else if ($sortTracks == 'oldest') { - $qry->addAscendingOrderByColumn('utime'); - } - else if ($sortTracks == 'random') { - $qry->addAscendingOrderByColumn('random()'); - } else { - Logging::warning("Unimplemented sortTracks type in ".__FILE__); - } - } + + // check if file exists + $qry->add("file_exists", "true", Criteria::EQUAL); + $qry->add("hidden", "false", Criteria::EQUAL); + $sortTracks = 'random'; + if (isset($storedCrit['sort'])) { + $sortTracks = $storedCrit['sort']['value']; + } + if ($sortTracks == 'newest') { + $qry->addDescendingOrderByColumn('utime'); + } + else if ($sortTracks == 'oldest') { + $qry->addAscendingOrderByColumn('utime'); + } + else if ($sortTracks == 'random') { + $qry->addAscendingOrderByColumn('random()'); + } else { + Logging::warning("Unimplemented sortTracks type in ".__FILE__); + } + // construct limit restriction $limits = array(); diff --git a/airtime_mvc/application/views/scripts/form/smart-block-criteria.phtml b/airtime_mvc/application/views/scripts/form/smart-block-criteria.phtml index 7f99abf97..032ae669f 100644 --- a/airtime_mvc/application/views/scripts/form/smart-block-criteria.phtml +++ b/airtime_mvc/application/views/scripts/form/smart-block-criteria.phtml @@ -89,9 +89,9 @@ } $nextDisabled = $this->element->getElement("sp_criteria_field_".$nextIndex)->getAttrib('disabled') == 'disabled'?true:false; ?> -
0) && $disabled) { - echo 'style=display:none'; - } ?>> +
0) && */ $disabled) { + echo 'style=display:none'; + } ?> > element->getElement("sp_criteria_field_".$i."_".$j) ?> element->getElement("sp_criteria_modifier_".$i."_".$j) ?> element->getElement("sp_criteria_value_".$i."_".$j) ?> diff --git a/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js b/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js index d44ce8b30..57c3666a0 100644 --- a/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js +++ b/airtime_mvc/public/js/airtime/playlist/smart_blockbuilder.js @@ -11,18 +11,30 @@ function setSmartBlockEvents() { var div = $('dd[id="sp_criteria-element"]').children('div:visible:last'); - div.find('.db-logic-label').text('and').show(); - div = div.next().show(); + if (div.length == 0) { + div = $('dd[id="sp_criteria-element"]').children('div:first'); + div.children().removeAttr('disabled'); + div.show(); - div.children().removeAttr('disabled'); - div = div.next(); - if (div.length === 0) { - $(this).hide(); + appendAddButton(); + appendModAddButton(); + removeButtonCheck(); + + } else { + + div.find('.db-logic-label').text('and').show(); + div = div.next().show(); + + div.children().removeAttr('disabled'); + div = div.next(); + if (div.length === 0) { + $(this).hide(); + } + + appendAddButton(); + appendModAddButton(); + removeButtonCheck(); } - - appendAddButton(); - appendModAddButton(); - removeButtonCheck(); }); /********** ADD MODIFIER ROW **********/ @@ -512,7 +524,9 @@ function callback(json, type) { form.find('.success').show(); } - form.find('.smart-block-form').removeClass("closed"); + removeButtonCheck(); + + form.find('.smart-block-form').removeClass("closed"); } else { if (json.result == "0") { $('.active-tab #sp-success-saved').text($.i18n._('Smart block saved')).show(); @@ -524,6 +538,7 @@ function callback(json, type) { } else { AIRTIME.playlist.playlistResponse(json); + removeButtonCheck(); } form.find('.smart-block-form').removeClass("closed"); } @@ -551,6 +566,7 @@ function appendAddButton() { } function removeButtonCheck() { + /* var rows = $('.active-tab dd[id="sp_criteria-element"]').children('div'), enabled = rows.find('select[name^="sp_criteria_field"]:enabled'), rmv_button = enabled.siblings('a[id^="criteria_remove"]'); @@ -560,7 +576,7 @@ function removeButtonCheck() { } else { rmv_button.removeAttr('disabled'); rmv_button.show(); - } + }*/ } function enableLoadingIcon() { From 65fd40bd6e42467aaf76f778e229972096cfc947 Mon Sep 17 00:00:00 2001 From: Albert Santoni Date: Wed, 2 Sep 2015 17:39:00 -0400 Subject: [PATCH 20/35] CC-6115: Redesign the navbar upload button --- airtime_mvc/application/layouts/scripts/layout.phtml | 4 +++- airtime_mvc/public/css/styles.css | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/airtime_mvc/application/layouts/scripts/layout.phtml b/airtime_mvc/application/layouts/scripts/layout.phtml index 98ecccb3d..e2e486415 100644 --- a/airtime_mvc/application/layouts/scripts/layout.phtml +++ b/airtime_mvc/application/layouts/scripts/layout.phtml @@ -67,7 +67,9 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= - +