diff --git a/airtime_mvc/application/configs/ACL.php b/airtime_mvc/application/configs/ACL.php index df4f38d07..be904e86e 100644 --- a/airtime_mvc/application/configs/ACL.php +++ b/airtime_mvc/application/configs/ACL.php @@ -26,6 +26,7 @@ $ccAcl->add(new Zend_Acl_Resource('library')) ->add(new Zend_Acl_Resource('playouthistory')) ->add(new Zend_Acl_Resource('playouthistorytemplate')) ->add(new Zend_Acl_Resource('listenerstat')) + ->add(new Zend_Acl_Resource('showlistenerstat')) ->add(new Zend_Acl_Resource('usersettings')) ->add(new Zend_Acl_Resource('audiopreview')) ->add(new Zend_Acl_Resource('webstream')) @@ -84,6 +85,7 @@ $ccAcl->allow('G', 'index') ->allow('H', 'playlist') ->allow('H', 'playouthistory') ->allow('H', 'listenerstat') + ->allow('H', 'showlistenerstat') ->allow('A', 'playouthistorytemplate') ->allow('A', 'user') ->allow('A', 'systemstatus') diff --git a/airtime_mvc/application/configs/navigation.php b/airtime_mvc/application/configs/navigation.php index b211b323f..fb666613c 100644 --- a/airtime_mvc/application/configs/navigation.php +++ b/airtime_mvc/application/configs/navigation.php @@ -138,6 +138,14 @@ $pages[] = array( 'action' => 'index', 'resource' => 'listenerstat' ), + array( + 'label' => _('Show Listener Stats'), + 'module' => 'default', + 'controller' => 'listenerstat', + 'action' => 'show', + 'resource' => 'showlistenerstat' + ), + ) ); if (LIBRETIME_ENABLE_BILLING === true) { @@ -221,4 +229,4 @@ $container = new Zend_Navigation($pages); $container->id = "nav"; //store it in the registry: -Zend_Registry::set('Zend_Navigation', $container); \ No newline at end of file +Zend_Registry::set('Zend_Navigation', $container); diff --git a/airtime_mvc/application/controllers/ListenerstatController.php b/airtime_mvc/application/controllers/ListenerstatController.php index 2247a6b03..525faace3 100644 --- a/airtime_mvc/application/controllers/ListenerstatController.php +++ b/airtime_mvc/application/controllers/ListenerstatController.php @@ -26,7 +26,6 @@ class ListenerstatController extends Zend_Controller_Action $this->view->headScript()->appendFile($baseUrl.'js/timepicker/jquery.ui.timepicker.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->headLink()->appendStylesheet($baseUrl.'css/jquery.ui.timepicker.css?'.$CC_CONFIG['airtime_version']); list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($request); @@ -56,12 +55,76 @@ class ListenerstatController extends Zend_Controller_Action $this->view->errorStatus = $out; $this->view->date_form = $form; } + public function showAction() { + $CC_CONFIG = Config::getConfig(); + + $request = $this->getRequest(); + $baseUrl = Application_Common_OsPath::getBaseDir(); + $headScript = $this->view->headScript(); + AirtimeTableView::injectTableJavaScriptDependencies($headScript, $baseUrl, $CC_CONFIG['airtime_version']); + Zend_Layout::getMvcInstance()->assign('parent_page', 'Analytics'); + $this->view->headScript()->appendFile($baseUrl.'js/timepicker/jquery.ui.timepicker.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/listenerstat/showlistenerstat.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); + + + + $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.'js/datatables/plugin/TableTools-2.1.5/css/TableTools.css?'.$CC_CONFIG['airtime_version']); + $this->view->headLink()->appendStylesheet($baseUrl.'css/jquery.ui.timepicker.css?'.$CC_CONFIG['airtime_version']); + $this->view->headLink()->appendStylesheet($baseUrl.'css/show_analytics.css'.$CC_CONFIG['airtime_version']); + + $user = Application_Model_User::getCurrentUser(); + if ($user->isUserType(array(UTYPE_SUPERADMIN, UTYPE_ADMIN, UTYPE_PROGRAM_MANAGER))) { + $this->view->showAllShows = true; + } + $data = []; + $this->view->showData = $data; + + $form = new Application_Form_ShowListenerStat(); + + list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($request); + $userTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone()); + $startsDT->setTimezone($userTimezone); + $endsDT->setTimezone($userTimezone); + $form->populate(array( + 'his_date_start' => $startsDT->format("Y-m-d"), + 'his_time_start' => $startsDT->format("H:i"), + 'his_date_end' => $endsDT->format("Y-m-d"), + 'his_time_end' => $endsDT->format("H:i") + )); + + $this->view->date_form = $form; + } public function getDataAction(){ list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($this->getRequest()); - $data = Application_Model_ListenerStat::getDataPointsWithinRange($startsDT->format(DEFAULT_TIMESTAMP_FORMAT), $endsDT->format(DEFAULT_TIMESTAMP_FORMAT)); $this->_helper->json->sendJson($data); } + + public function getShowDataAction(){ + list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($this->getRequest()); + $show_id = $this->getRequest()->getParam("show_id", null); + $data = Application_Model_ListenerStat::getShowDataPointsWithinRange($startsDT->format(DEFAULT_TIMESTAMP_FORMAT), + $endsDT->format(DEFAULT_TIMESTAMP_FORMAT),$show_id); + $this->_helper->json->sendJson($data); + } + public function getAllShowData(){ + list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($this->getRequest()); + $data = Application_Model_ListenerStat::getAllShowDataPointsWithinRange($startsDT->format(DEFAULT_TIMESTAMP_FORMAT), + $endsDT->format(DEFAULT_TIMESTAMP_FORMAT)); + return $data; + } + + public function getAllShowDataAction(){ + list($startsDT, $endsDT) = Application_Common_HTTPHelper::getStartEndFromRequest($this->getRequest()); + $show_id = $this->getRequest()->getParam("show_id", null); + $data = Application_Model_ListenerStat::getAllShowDataPointsWithinRange($startsDT->format(DEFAULT_TIMESTAMP_FORMAT), + $endsDT->format(DEFAULT_TIMESTAMP_FORMAT)); + $this->_helper->json->sendJson($data); + } } diff --git a/airtime_mvc/application/forms/ShowListenerStat.php b/airtime_mvc/application/forms/ShowListenerStat.php new file mode 100644 index 000000000..60b4eb875 --- /dev/null +++ b/airtime_mvc/application/forms/ShowListenerStat.php @@ -0,0 +1,68 @@ +setDecorators(array( + array('ViewScript', array('viewScript' => 'form/daterange.phtml')) + )); + + // Add start date element + $startDate = new Zend_Form_Element_Text('his_date_start'); + $startDate->class = 'input_text'; + $startDate->setRequired(true) + ->setLabel(_('Date Start:')) + ->setValue(date("Y-m-d")) + ->setFilters(array('StringTrim')) + ->setValidators(array( + 'NotEmpty', + array('date', false, array('YYYY-MM-DD')))) + ->setDecorators(array('ViewHelper')); + $startDate->setAttrib('alt', 'date'); + $this->addElement($startDate); + + // Add start time element + $startTime = new Zend_Form_Element_Text('his_time_start'); + $startTime->class = 'input_text'; + $startTime->setRequired(true) + ->setValue('00:00') + ->setFilters(array('StringTrim')) + ->setValidators(array( + 'NotEmpty', + array('date', false, array('HH:mm')), + array('regex', false, array('/^[0-2]?[0-9]:[0-5][0-9]$/', 'messages' => _('Invalid character entered'))))) + ->setDecorators(array('ViewHelper')); + $startTime->setAttrib('alt', 'time'); + $this->addElement($startTime); + + // Add end date element + $endDate = new Zend_Form_Element_Text('his_date_end'); + $endDate->class = 'input_text'; + $endDate->setRequired(true) + ->setLabel(_('Date End:')) + ->setValue(date("Y-m-d")) + ->setFilters(array('StringTrim')) + ->setValidators(array( + 'NotEmpty', + array('date', false, array('YYYY-MM-DD')))) + ->setDecorators(array('ViewHelper')); + $endDate->setAttrib('alt', 'date'); + $this->addElement($endDate); + + // Add end time element + $endTime = new Zend_Form_Element_Text('his_time_end'); + $endTime->class = 'input_text'; + $endTime->setRequired(true) + ->setValue('01:00') + ->setFilters(array('StringTrim')) + ->setValidators(array( + 'NotEmpty', + array('date', false, array('HH:mm')), + array('regex', false, array('/^[0-2]?[0-9]:[0-5][0-9]$/', 'messages' => _('Invalid character entered'))))) + ->setDecorators(array('ViewHelper')); + $endTime->setAttrib('alt', 'time'); + $this->addElement($endTime); + } +} diff --git a/airtime_mvc/application/models/ListenerStat.php b/airtime_mvc/application/models/ListenerStat.php index c43cf0fb7..9580cadb0 100644 --- a/airtime_mvc/application/models/ListenerStat.php +++ b/airtime_mvc/application/models/ListenerStat.php @@ -16,8 +16,8 @@ group by mount_name SQL; $data = Application_Common_Database::prepareAndExecute($sql, array('p1'=>$p_start, 'p2'=>$p_end)); - $out = array(); + foreach ($data as $d) { $jump = intval($d['count']/1000); $jump = max(1, $jump); @@ -36,7 +36,7 @@ WHERE (temp.rownum%:p4) = :p5; SQL; $result = Application_Common_Database::prepareAndExecute($sql, array('p1'=>$p_start, 'p2'=>$p_end, 'p3'=>$d['mount_name'], 'p4'=>$jump, 'p5'=>$remainder)); - + $utcTimezone = new DateTimeZone("UTC"); $displayTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone()); @@ -51,6 +51,7 @@ SQL; } } + return $out; $enabledStreamIds = Application_Model_StreamSetting::getEnabledStreamIds(); $enabledOut = array(); @@ -77,6 +78,68 @@ SQL; return $enabledOut; } +// this will currently log the average number of listeners to a specific show during a certain range + public static function getShowDataPointsWithinRange($p_start, $p_end, $show_id) { + $showData = []; + $ccShow = CcShowQuery::create()->findPk($show_id); + $showName = $ccShow->getDbName(); + + + // this query selects all show instances that aired in this date range that match the show_id + $sql = <<=:p2 AND ends <=:p3 +SQL; + $data = Application_Common_Database::prepareAndExecute($sql, + array('p1'=>$show_id,'p2'=>$p_start, 'p3'=>$p_end)); + foreach ($data as $d) { + $sql = <<=:p1 AND ts.timestamp <=:p2) +GROUP BY timestamp +SQL; + $data = Application_Common_Database::prepareAndExecute($sql, + array('p1'=>$d['starts'], 'p2'=>$d['ends'])); + $utcTimezone = new DateTimeZone("UTC"); + $displayTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone()); + if (sizeof($data) > 0) { + $t = new DateTime($data[0]['timestamp'], $utcTimezone); + $t->setTimezone($displayTimezone); + // tricking javascript so it thinks the server timezone is in UTC + $average_listeners = array_sum(array_column($data, 'listeners')) / sizeof($data); + $max_num_listeners = max(array_column($data, 'listeners')); + $entry = array("show" => $showName, "time" => $t->format( 'Y-m-d H:i:s') + , "average_number_of_listeners" => $average_listeners, + "maximum_number_of_listeners" => $max_num_listeners); + array_push($showData, $entry); + } + } + return($showData); + } + public static function getAllShowDataPointsWithinRange($p_start, $p_end) { + // this query selects the id of all show instances that aired in this date range + $all_show_data = []; + $sql = <<=:p1 AND ends <=:p2 +GROUP BY show_id +SQL; + $data = Application_Common_Database::prepareAndExecute($sql, + array('p1'=>$p_start, 'p2'=>$p_end)); + + foreach($data as $show_id) { + $all_show_data = array_merge(self::getShowDataPointsWithinRange($p_start,$p_end,$show_id['show_id']), $all_show_data); + } + /* option to sort by number of listeners currently commented out + usort($all_show_data, function($a, $b) { + return $a['average_number_of_listeners'] - $b['average_number_of_listeners']; + }); + */ + return $all_show_data; + } public static function insertDataPoints($p_dataPoints) { diff --git a/airtime_mvc/application/views/scripts/listenerstat/show.phtml b/airtime_mvc/application/views/scripts/listenerstat/show.phtml new file mode 100644 index 000000000..064e8f3cf --- /dev/null +++ b/airtime_mvc/application/views/scripts/listenerstat/show.phtml @@ -0,0 +1,23 @@ +
+

+
+
+

Date Range

+ date_form; ?> +
+ +
+ + + + + + + + + + + +
+
+
diff --git a/airtime_mvc/public/css/show_analytics.css b/airtime_mvc/public/css/show_analytics.css new file mode 100644 index 000000000..41f08c874 --- /dev/null +++ b/airtime_mvc/public/css/show_analytics.css @@ -0,0 +1,10 @@ +div.date_form input.error { + background-color: rgba(255,0,0,0.2); +} +div.error_window.error { + background-color: rgba(255,0,0,0.2); + color:white; +} +div.error_window { + background-color: white; +} \ No newline at end of file diff --git a/airtime_mvc/public/js/airtime/listenerstat/showlistenerstat.js b/airtime_mvc/public/js/airtime/listenerstat/showlistenerstat.js new file mode 100644 index 000000000..7d6a7ec9d --- /dev/null +++ b/airtime_mvc/public/js/airtime/listenerstat/showlistenerstat.js @@ -0,0 +1,127 @@ +$(document).ready(function() { + showlistenerstat_content = $("#showlistenerstat_content") + dateStartId = "#his_date_start", + timeStartId = "#his_time_start", + dateEndId = "#his_date_end", + timeEndId = "#his_time_end", + show_id = "#his_show_filter"; + + // set width dynamically + var width = $("#showlistenerstat_content").width(); + width = width * .91; + addDatePicker(); + + showlistenerstat_content.find("#his_submit").click(function(){ +// var show_id = $("#sb_show_filter").val(); + var oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId); + var start = oRange.start; + var end = oRange.end; + showListenerDataTable(); + }); +}); + +function getShowData(startTimestamp, endTimestamp, show_id) { + // get data + $.get(baseUrl+'Listenerstat/get-all-show-data', {start: startTimestamp, end: endTimestamp }, function(data) { + return data; + }); +} + +function addDatePicker() { + + oBaseDatePickerSettings = { + dateFormat: 'yy-mm-dd', + //i18n_months, i18n_days_short are in common.js + monthNames: i18n_months, + dayNamesMin: i18n_days_short, + onSelect: function(sDate, oDatePicker) { + $(this).datepicker( "setDate", sDate ); + }, + onClose: validateTimeRange + }; + oBaseTimePickerSettings = { + showPeriodLabels: false, + showCloseButton: true, + closeButtonText: $.i18n._("Done"), + showLeadingZero: false, + defaultTime: '0:00', + hourText: $.i18n._("Hour"), + minuteText: $.i18n._("Minute"), + onClose: validateTimeRange + }; + + showlistenerstat_content.find(dateStartId).datepicker(oBaseDatePickerSettings).blur(validateTimeRange()); + showlistenerstat_content.find(timeStartId).timepicker(oBaseTimePickerSettings).blur(validateTimeRange()); + showlistenerstat_content.find(dateEndId).datepicker(oBaseDatePickerSettings).blur(validateTimeRange()); + showlistenerstat_content.find(timeEndId).timepicker(oBaseTimePickerSettings).blur(validateTimeRange()); +} + +function getStartEnd() { + + return AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId); +} + +function validateTimeRange() { + var oRange, + inputs = $('.date_form > input'), + error_window = $('.error_window'), + start, end; + + oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId); + + start = oRange.start; + end = oRange.end; + + if (end >= start) { + error_window.removeClass('error'); + $('.error_window').html(''); + } + else { + error_window.addClass('error'); + console.log('bad') + $('.error_window').html('Your start date time is after your end date time'); + } + + return { + start: start, + end: end, + isValid: end >= start + }; +} + +function showListenerDataTable() { + var oRange = AIRTIME.utilities.fnGetScheduleRange(dateStartId, timeStartId, dateEndId, timeEndId); + var start = oRange.start; + var lengthMenu = [[10, 25, 50, 100, 500, -1], [10, 25, 50, 100, 500, $.i18n._("All")]]; + var end = oRange.end; + var sDom = 'l<"dt-process-rel"r><"H"T><"dataTables_scrolling"t><"F"ip>'; + var show_id = $("#sb_show_filter").val(); + var dt = $('#show_stats_datatable'); + info = getStartEnd(); + dt.dataTable({ + "aoColumns": [ + /* first name */ {"sName": "show", "mDataProp": "show"}, + /* air date */ {"sName": "time", "mDataProp": "time"}, + /* last name */ {"sName": "average_number_of_listeners", "mDataProp": "average_number_of_listeners"}, + /* last name */ {"sName": "maximum_number_of_listeners", "mDataProp": "maximum_number_of_listeners"}], + "sAjaxSource": baseUrl+'Listenerstat/get-all-show-data', + "sAjaxDataProp": "", + "bDestroy": true, + "aLengthMenu": lengthMenu, + "iDisplayLength": 25, + "sPaginationType": "full_numbers", + "bJQueryUI": true, + "bAutoWidth": true, + "sDom": sDom, + "fnServerData": function ( sSource, aoData, fnCallback ) { + aoData.push({"start": start, "end": end}); + $.ajax( { + "dataType": 'json', + "type": "POST", + "url": sSource, + "data": {"start": start, "end": end}, + "success": fnCallback + } ); + }, + }); +} \ No newline at end of file