Merge branch 'saas-dev' into saas

This commit is contained in:
Albert Santoni 2015-07-09 11:18:10 -04:00
commit c35a8cc952
29 changed files with 569 additions and 497 deletions

View file

@ -130,9 +130,11 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
protected function _initTasks() { protected function _initTasks() {
/* We need to wrap this here so that we aren't checking when we're running the unit test suite /* We need to wrap this here so that we aren't checking when we're running the unit test suite
*/ */
$taskManager = TaskManager::getInstance();
$taskManager->runTask(AirtimeTask::UPGRADE); // Run the upgrade on each request (if it needs to be run)
if (getenv("AIRTIME_UNIT_TEST") != 1) { if (getenv("AIRTIME_UNIT_TEST") != 1) {
//This will do the upgrade too if it's needed... //This will do the upgrade too if it's needed...
TaskManager::getInstance()->runTasks(); $taskManager->runTasks();
} }
} }

View file

@ -2,6 +2,27 @@
class FileDataHelper { class FileDataHelper {
public static function getAudioMimeTypeArray() {
return array(
"audio/ogg" => "ogg",
"application/ogg" => "ogg",
"audio/vorbis" => "ogg",
"audio/mp3" => "mp3",
"audio/mpeg" => "mp3",
"audio/mpeg3" => "mp3",
"audio/aac" => "aac",
"audio/aacp" => "aac",
"audio/mp4" => "mp4",
"audio/x-flac" => "flac",
"audio/wav" => "wav",
"audio/x-wav" => "wav",
"audio/mp2" => "mp2",
"audio/mp1" => "mp1",
"audio/x-ms-wma" => "wma",
"audio/basic" => "au",
);
}
/** /**
* We want to throw out invalid data and process the upload successfully * We want to throw out invalid data and process the upload successfully
* at all costs, so check the data and sanitize it if necessary * at all costs, so check the data and sanitize it if necessary
@ -24,4 +45,24 @@ class FileDataHelper {
$data["bpm"] = intval($data["bpm"]); $data["bpm"] = intval($data["bpm"]);
} }
} }
/**
* Return a suitable extension for the given file
*
* @param string $mime
*
* @return string file extension with(!) a dot (for convenience)
*
* @throws Exception
*/
public static function getFileExtensionFromMime($mime)
{
$mime = trim(strtolower($mime));
try {
return ('.' . static::getAudioMimeTypeArray()[$mime]);
} catch (Exception $e) {
throw new Exception("Unknown file type: $mime");
}
}
} }

View file

@ -45,8 +45,6 @@ class Application_Common_HTTPHelper
class ZendActionHttpException extends Exception { class ZendActionHttpException extends Exception {
private $_action;
/** /**
* @param Zend_Controller_Action $action * @param Zend_Controller_Action $action
* @param int $statusCode * @param int $statusCode
@ -58,8 +56,7 @@ class ZendActionHttpException extends Exception {
*/ */
public function __construct(Zend_Controller_Action $action, $statusCode, $message, public function __construct(Zend_Controller_Action $action, $statusCode, $message,
$code = 0, Exception $previous = null) { $code = 0, Exception $previous = null) {
$this->_action = $action; Logging::error("Error in action " . $action->getRequest()->getActionName()
Logging::info("Error in action " . $action->getRequest()->getActionName()
. " with status code $statusCode: $message"); . " with status code $statusCode: $message");
$action->getResponse() $action->getResponse()
->setHttpResponseCode($statusCode) ->setHttpResponseCode($statusCode)
@ -67,8 +64,4 @@ class ZendActionHttpException extends Exception {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
} }
public function getAction() {
return $this->_action;
}
} }

View file

@ -2,15 +2,19 @@
/** /**
* Class TaskManager * Class TaskManager
*
* When adding a new task, the new AirtimeTask class will need to be added to the internal task list,
* as an ENUM value to the AirtimeTask interface, and as a case in the TaskFactory.
*/ */
final class TaskManager { final class TaskManager {
/** /**
* @var array tasks to be run * @var array tasks to be run. Maps task names to a boolean value denoting
* whether the task has been checked/run
*/ */
protected $_taskList = [ protected $_taskList = [
AirtimeTask::UPGRADE, // Always run the upgrade first AirtimeTask::UPGRADE => false,
AirtimeTask::CELERY AirtimeTask::CELERY => false,
]; ];
/** /**
@ -19,8 +23,7 @@ final class TaskManager {
protected static $_instance; protected static $_instance;
/** /**
* @var int TASK_INTERVAL_SECONDS how often, in seconds, to run the TaskManager tasks, * @var int TASK_INTERVAL_SECONDS how often, in seconds, to run the TaskManager tasks
* if they need to be run
*/ */
const TASK_INTERVAL_SECONDS = 30; const TASK_INTERVAL_SECONDS = 30;
@ -47,6 +50,22 @@ final class TaskManager {
return self::$_instance; return self::$_instance;
} }
/**
* Run a single task.
*
* @param string $taskName the ENUM name of the task to be run
*/
public function runTask($taskName) {
$task = TaskFactory::getTask($taskName);
if ($task && $task->shouldBeRun()) {
$task->run();
}
$this->_taskList[$taskName] = true; // Mark that the task has been checked/run.
// This is important for prioritized tasks that
// we need to run on every request (such as the
// schema check/upgrade)
}
/** /**
* Run all tasks that need to be run. * Run all tasks that need to be run.
* *
@ -81,10 +100,9 @@ final class TaskManager {
// better to be silent here to avoid log bloat // better to be silent here to avoid log bloat
return; return;
} }
foreach ($this->_taskList as $task) { foreach ($this->_taskList as $task => $hasTaskRun) {
$task = TaskFactory::getTask($task); if (!$hasTaskRun) {
if ($task && $task->shouldBeRun()) { $this->runTask($task);
$task->run();
} }
} }
} }

View file

@ -57,83 +57,93 @@ class WidgetHelper
return $result; return $result;
} }
// Second version of this function. /**
// Removing "next" days and creating two weekly arrays * Returns a weeks worth of shows in UTC, and an info array of the current week's days.
public static function getWeekInfoV2($timezone) * Returns an array of two arrays:
*
* The first array is 7 consecutive week days, starting with the current day.
*
* The second array contains shows scheduled during the 7 week days in the first array.
* The shows returned in this array are not in any order and are in UTC.
*
* We don't do any timezone conversion in this function on purpose. All timezone conversion
* and show time ordering should be done on the frontend.
*
* @return array
*/
public static function getWeekInfoV2()
{ {
//weekStart is in station time.
//$weekStartDateTime = Application_Common_DateHelper::getWeekStartDateTime();
$weekStartDateTime = new DateTime("now", new DateTimeZone(Application_Model_Preference::GetTimezone())); $weekStartDateTime = new DateTime("now", new DateTimeZone(Application_Model_Preference::GetTimezone()));
$maxNumOFWeeks = 2;
$result = array(); $result = array();
// default to the station timezone
$timezone = Application_Model_Preference::GetDefaultTimezone();
$userDefinedTimezone = strtolower($timezone);
// if the timezone defined by the user exists, use that
if (array_key_exists($userDefinedTimezone, timezone_abbreviations_list())) {
$timezone = $userDefinedTimezone;
}
$utcTimezone = new DateTimeZone("UTC"); $utcTimezone = new DateTimeZone("UTC");
$weekStartDateTime->setTimezone($utcTimezone); $weekStartDateTime->setTimezone($utcTimezone);
// When querying for shows we need the start and end date range to have // Use this variable as the start date/time range when querying
// a time of "00:00". $utcDayStart is used below when querying for shows. // for shows. We set it to 1 day prior to the beginning of the
$utcDayStartDT = clone $weekStartDateTime; // schedule widget data to account for show date changes when
$utcDayStartDT->setTime(0, 0, 0); // converting their start day/time to the client's local timezone.
$utcDayStart = $utcDayStartDT->format(DEFAULT_TIMESTAMP_FORMAT); $showQueryDateRangeStart = clone $weekStartDateTime;
$weekCounter = 0; $showQueryDateRangeStart->sub(new DateInterval("P1D"));
while ($weekCounter < $maxNumOFWeeks) { $showQueryDateRangeStart->setTime(0, 0, 0);
for ($dayOfWeekCounter = 0; $dayOfWeekCounter < DAYS_PER_WEEK; $dayOfWeekCounter++) { for ($dayOfWeekCounter = 0; $dayOfWeekCounter < DAYS_PER_WEEK; $dayOfWeekCounter++) {
$dateParse = date_parse($weekStartDateTime->format(DEFAULT_TIMESTAMP_FORMAT)); $dateParse = date_parse($weekStartDateTime->format("Y-m-d H:i:s"));
$result[$weekCounter][$dayOfWeekCounter]["dayOfMonth"] = $dateParse["day"]; // Associate data to its date so that when we convert this array
$result[$weekCounter][$dayOfWeekCounter]["dayOfWeek"] = strtoupper(date("D", $weekStartDateTime->getTimestamp())); // to json the order remains the same - in chronological order.
// We also format the key to be for example: "2015-6-1" to match
// javascript date formats so it's easier to sort the shows by day.
$result["weekDays"][$weekStartDateTime->format("Y-n-j")] = array();
$result["weekDays"][$weekStartDateTime->format("Y-n-j")]["dayOfMonth"] = $dateParse["day"];
$result["weekDays"][$weekStartDateTime->format("Y-n-j")]["dayOfWeek"] = strtoupper(_(date("D", $weekStartDateTime->getTimestamp())));
// Shows scheduled for this day will get added to this array when
// we convert the show times to the client's local timezone in weekly-program.phtml
$result["weekDays"][$weekStartDateTime->format("Y-n-j")]["shows"] = array();
// $weekStartDateTime has to be in station timezone when adding 1 day for daylight savings.
// TODO: is this necessary since we set the time to "00:00" ?
$stationTimezone = Application_Model_Preference::GetDefaultTimezone();
$weekStartDateTime->setTimezone(new DateTimeZone($stationTimezone));
//have to be in station timezone when adding 1 day for daylight savings.
$weekStartDateTime->setTimezone(new DateTimeZone($timezone));
$weekStartDateTime->add(new DateInterval('P1D')); $weekStartDateTime->add(new DateInterval('P1D'));
//convert back to UTC to get the actual timestamp used for search. //convert back to UTC to get the actual timestamp used for search.
$weekStartDateTime->setTimezone($utcTimezone); $weekStartDateTime->setTimezone($utcTimezone);
}
// When querying for shows we need the start and end date range to have // Use this variable as the end date/time range when querying
// a time of "00:00". // for shows. We set it to 1 day after the end of the schedule
$utcDayEndDT = clone $weekStartDateTime; // widget data to account for show date changes when converting
$utcDayEndDT->setTime(0, 0, 0); // their start day/time to the client's local timezone.
$utcDayEnd = $utcDayEndDT->format(DEFAULT_TIMESTAMP_FORMAT); $showQueryDateRangeEnd = clone $weekStartDateTime;
$shows = Application_Model_Show::getNextShows($utcDayStart, "ALL", $utcDayEnd); $showQueryDateRangeEnd->setTime(23, 59, 0);
$utcDayStart = $utcDayEnd;
// convert to user-defined timezone, or default to station
Application_Common_DateHelper::convertTimestampsToTimezone(
$shows,
array("starts", "ends", "start_timestamp", "end_timestamp"),
$timezone
);
$shows = Application_Model_Show::getNextShows(
$showQueryDateRangeStart->format("Y-m-d H:i:s"),
"ALL",
$showQueryDateRangeEnd->format("Y-m-d H:i:s"));
// Convert each start and end time string to DateTime objects
// so we can get a real timestamp. The timestamps will be used
// to convert into javascript Date objects.
foreach($shows as &$show) { foreach($shows as &$show) {
$startParseDate = date_parse($show['starts']); $dtStarts = new DateTime($show["starts"], new DateTimeZone("UTC"));
$show["show_start_hour"] = str_pad($startParseDate["hour"], 2, "0", STR_PAD_LEFT).":".str_pad($startParseDate["minute"], 2, 0, STR_PAD_LEFT); $show["starts_timestamp"] = $dtStarts->getTimestamp();
$endParseDate = date_parse($show['ends']); $dtEnds = new DateTime($show["ends"], new DateTimeZone("UTC"));
$show["show_end_hour"] = str_pad($endParseDate["hour"], 2, 0, STR_PAD_LEFT).":".str_pad($endParseDate["minute"],2, 0, STR_PAD_LEFT); $show["ends_timestamp"] = $dtEnds->getTimestamp();
} }
$result[$weekCounter][$dayOfWeekCounter]["shows"] = $shows; $result["shows"] = $shows;
}
$weekCounter += 1;
}
// XSS exploit prevention // XSS exploit prevention
SecurityHelper::htmlescape_recursive($result); SecurityHelper::htmlescape_recursive($result);
// convert image paths to point to api endpoints // convert image paths to point to api endpoints
//TODO: do we need this here?
self::findAndConvertPaths($result); self::findAndConvertPaths($result);
return $result; return $result;

View file

@ -24,7 +24,8 @@ define('LICENSE_URL' , 'http://www.gnu.org/licenses/agpl-3.0-standalone.h
define('AIRTIME_COPYRIGHT_DATE' , '2010-2015'); define('AIRTIME_COPYRIGHT_DATE' , '2010-2015');
define('AIRTIME_REST_VERSION' , '1.1'); define('AIRTIME_REST_VERSION' , '1.1');
define('AIRTIME_API_VERSION' , '1.1'); define('AIRTIME_API_VERSION' , '1.1');
define('AIRTIME_CODE_VERSION' , '2.5.13'); // XXX: it's important that we upgrade this every time we add an upgrade!
define('AIRTIME_CODE_VERSION' , '2.5.14');
// Defaults // Defaults
define('DEFAULT_LOGO_PLACEHOLDER', 1); define('DEFAULT_LOGO_PLACEHOLDER', 1);

View file

@ -198,23 +198,10 @@ class AudiopreviewController extends Zend_Controller_Action
$elementMap['type'] = $track['type']; $elementMap['type'] = $track['type'];
if ($track['type'] == 0) { if ($track['type'] == 0) {
$mime = $track['mime']; $mime = trim(strtolower($track['mime']));
//type is file try {
if (strtolower($mime) === 'audio/mp3') { $elementMap['element_' . FileDataHelper::getAudioMimeTypeArray()[$mime]] = $track['item_id'];
$elementMap['element_mp3'] = $track['item_id']; } catch (Exception $e) {
} elseif (strtolower($mime) === 'audio/ogg') {
$elementMap['element_oga'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/vorbis') {
$elementMap['element_oga'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/mp4') {
$elementMap['element_m4a'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/wav') {
$elementMap['element_wav'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/x-wav') {
$elementMap['element_wav'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/x-flac') {
$elementMap['element_flac'] = $track['item_id'];
} else {
throw new Exception("Unknown file type: $mime"); throw new Exception("Unknown file type: $mime");
} }
@ -288,22 +275,10 @@ class AudiopreviewController extends Zend_Controller_Action
$elementMap['type'] = $track['type']; $elementMap['type'] = $track['type'];
if ($track['type'] == 0) { if ($track['type'] == 0) {
$mime = $track['mime']; $mime = trim(strtolower($track['mime']));
if (strtolower($mime) === 'audio/mp3') { try {
$elementMap['element_mp3'] = $track['item_id']; $elementMap['element_' . FileDataHelper::getAudioMimeTypeArray()[$mime]] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/ogg') { } catch (Exception $e) {
$elementMap['element_oga'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/vorbis') {
$elementMap['element_oga'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/mp4') {
$elementMap['element_m4a'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/wav') {
$elementMap['element_wav'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/x-wav') {
$elementMap['element_wav'] = $track['item_id'];
} elseif (strtolower($mime) === 'audio/x-flac') {
$elementMap['element_flac'] = $track['item_id'];
} else {
throw new Exception("Unknown file type: $mime"); throw new Exception("Unknown file type: $mime");
} }

View file

@ -84,7 +84,6 @@ class EmbedController extends Zend_Controller_Action
$request = $this->getRequest(); $request = $this->getRequest();
$widgetStyle = $request->getParam('style'); $widgetStyle = $request->getParam('style');
if ($widgetStyle == "premium") { if ($widgetStyle == "premium") {
$this->view->widgetStyle = "premium"; $this->view->widgetStyle = "premium";
@ -95,10 +94,9 @@ class EmbedController extends Zend_Controller_Action
} }
$this->view->jquery = Application_Common_HTTPHelper::getStationUrl() . "widgets/js/jquery-1.6.1.min.js?".$CC_CONFIG['airtime_version']; $this->view->jquery = Application_Common_HTTPHelper::getStationUrl() . "widgets/js/jquery-1.6.1.min.js?".$CC_CONFIG['airtime_version'];
$weeklyScheduleData = WidgetHelper::getWeekInfoV2($this->getRequest()->getParam("timezone")); $weeklyScheduleData = WidgetHelper::getWeekInfoV2();
// Return only the current week's schedule data. In the future we may use the next week's data. $this->view->schedule_data = json_encode($weeklyScheduleData);
$this->view->weeklyScheduleData = ($weeklyScheduleData[0]);
$currentDay = new DateTime("now", new DateTimeZone(Application_Model_Preference::GetTimezone())); $currentDay = new DateTime("now", new DateTimeZone(Application_Model_Preference::GetTimezone()));
//day of the month without leading zeros (1 to 31) //day of the month without leading zeros (1 to 31)

View file

@ -0,0 +1,4 @@
ALTER TABLE cc_pref ALTER COLUMN subjid SET NULL;
ALTER TABLE cc_pref ALTER COLUMN subjid SET DEFAULT NULL;
CREATE UNIQUE INDEX cc_pref_key_idx ON cc_pref (keystr) WHERE subjid IS NULL;
ANALYZE cc_pref;

View file

@ -8,14 +8,13 @@ class Application_Form_AddShowLiveStream extends Zend_Form_SubForm
{ {
$cb_airtime_auth = new Zend_Form_Element_Checkbox("cb_airtime_auth"); $cb_airtime_auth = new Zend_Form_Element_Checkbox("cb_airtime_auth");
$cb_airtime_auth->setLabel(sprintf(_("Use %s Authentication:"), PRODUCT_NAME)) $cb_airtime_auth->setLabel(sprintf(_("Use %s Authentication:"), PRODUCT_NAME))
->setRequired(false) ->setChecked(true)
->setDecorators(array('ViewHelper')); ->setRequired(false);
$this->addElement($cb_airtime_auth); $this->addElement($cb_airtime_auth);
$cb_custom_auth = new Zend_Form_Element_Checkbox("cb_custom_auth"); $cb_custom_auth = new Zend_Form_Element_Checkbox("cb_custom_auth");
$cb_custom_auth ->setLabel(_("Use Custom Authentication:")) $cb_custom_auth ->setLabel(_("Use Custom Authentication:"))
->setRequired(false) ->setRequired(false);
->setDecorators(array('ViewHelper'));
$this->addElement($cb_custom_auth); $this->addElement($cb_custom_auth);
//custom username //custom username
@ -26,8 +25,7 @@ class Application_Form_AddShowLiveStream extends Zend_Form_SubForm
->setLabel(_('Custom Username')) ->setLabel(_('Custom Username'))
->setFilters(array('StringTrim')) ->setFilters(array('StringTrim'))
->setValidators(array( ->setValidators(array(
new ConditionalNotEmpty(array("cb_custom_auth"=>"1")))) new ConditionalNotEmpty(array("cb_custom_auth"=>"1"))));
->setDecorators(array('ViewHelper'));
$this->addElement($custom_username); $this->addElement($custom_username);
//custom password //custom password
@ -39,17 +37,33 @@ class Application_Form_AddShowLiveStream extends Zend_Form_SubForm
->setLabel(_('Custom Password')) ->setLabel(_('Custom Password'))
->setFilters(array('StringTrim')) ->setFilters(array('StringTrim'))
->setValidators(array( ->setValidators(array(
new ConditionalNotEmpty(array("cb_custom_auth"=>"1")))) new ConditionalNotEmpty(array("cb_custom_auth"=>"1"))));
->setDecorators(array('ViewHelper'));
$this->addElement($custom_password); $this->addElement($custom_password);
$connection_url = Application_Model_Preference::GetLiveDJSourceConnectionURL(); $showSourceParams = parse_url(Application_Model_Preference::GetLiveDJSourceConnectionURL());
if (trim($connection_url) == "") {
$connection_url = "N/A";
}
$this->setDecorators(array( // Show source connection url parameters
array('ViewScript', array('viewScript' => 'form/add-show-live-stream.phtml', "connection_url"=>$connection_url)) $showSourceHost = new Zend_Form_Element_Text('show_source_host');
$showSourceHost->setAttrib('readonly', true)
->setLabel(_('Host:'))
->setValue(isset($showSourceParams["host"])?$showSourceParams["host"]:"");
$this->addElement($showSourceHost);
$showSourcePort = new Zend_Form_Element_Text('show_source_port');
$showSourcePort->setAttrib('readonly', true)
->setLabel(_('Port:'))
->setValue(isset($showSourceParams["port"])?$showSourceParams["port"]:"");
$this->addElement($showSourcePort);
$showSourceMount = new Zend_Form_Element_Text('show_source_mount');
$showSourceMount->setAttrib('readonly', true)
->setLabel(_('Mount:'))
->setValue(isset($showSourceParams["path"])?$showSourceParams["path"]:"");
$this->addElement($showSourceMount);
$this->setDecorators(
array(
array('ViewScript', array('viewScript' => 'form/add-show-live-stream.phtml'))
)); ));
} }

View file

@ -11,38 +11,38 @@ class Application_Form_LiveStreamingPreferences extends Zend_Form_SubForm
$defaultFade = Application_Model_Preference::GetDefaultTransitionFade(); $defaultFade = Application_Model_Preference::GetDefaultTransitionFade();
$this->setDecorators(array(
array('ViewScript', array('viewScript' => 'form/preferences_livestream.phtml')),
));
// automatic trasition on source disconnection // automatic trasition on source disconnection
$auto_transition = new Zend_Form_Element_Checkbox("auto_transition"); $auto_transition = new Zend_Form_Element_Checkbox("auto_transition");
$auto_transition->setLabel(_("Auto Switch Off")) $auto_transition->setLabel(_("Auto Switch Off:"))
->setValue(Application_Model_Preference::GetAutoTransition()) ->setValue(Application_Model_Preference::GetAutoTransition());
->setDecorators(array('ViewHelper'));
$this->addElement($auto_transition); $this->addElement($auto_transition);
// automatic switch on upon source connection // automatic switch on upon source connection
$auto_switch = new Zend_Form_Element_Checkbox("auto_switch"); $auto_switch = new Zend_Form_Element_Checkbox("auto_switch");
$auto_switch->setLabel(_("Auto Switch On")) $auto_switch->setLabel(_("Auto Switch On:"))
->setValue(Application_Model_Preference::GetAutoSwitch()) ->setValue(Application_Model_Preference::GetAutoSwitch());
->setDecorators(array('ViewHelper'));
$this->addElement($auto_switch); $this->addElement($auto_switch);
// Default transition fade // Default transition fade
$transition_fade = new Zend_Form_Element_Text("transition_fade"); $transition_fade = new Zend_Form_Element_Text("transition_fade");
$transition_fade->setLabel(_("Switch Transition Fade (s)")) $transition_fade->setLabel(_("Switch Transition Fade (s):"))
->setFilters(array('StringTrim')) ->setFilters(array('StringTrim'))
->addValidator('regex', false, array('/^\d*(\.\d+)?$/', ->addValidator('regex', false, array('/^\d*(\.\d+)?$/',
'messages' => _('Please enter a time in seconds (eg. 0.5)'))) 'messages' => _('Please enter a time in seconds (eg. 0.5)')))
->setValue($defaultFade) ->setValue($defaultFade);
->setDecorators(array('ViewHelper'));
$this->addElement($transition_fade); $this->addElement($transition_fade);
//Master username //Master username
$master_username = new Zend_Form_Element_Text('master_username'); $master_username = new Zend_Form_Element_Text('master_username');
$master_username->setAttrib('autocomplete', 'off') $master_username->setAttrib('autocomplete', 'off')
->setAllowEmpty(true) ->setAllowEmpty(true)
->setLabel(_('Master Username')) ->setLabel(_('Username:'))
->setFilters(array('StringTrim')) ->setFilters(array('StringTrim'))
->setValue(Application_Model_Preference::GetLiveStreamMasterUsername()) ->setValue(Application_Model_Preference::GetLiveStreamMasterUsername());
->setDecorators(array('ViewHelper'));
$this->addElement($master_username); $this->addElement($master_username);
//Master password //Master password
@ -56,26 +56,51 @@ class Application_Form_LiveStreamingPreferences extends Zend_Form_SubForm
->setAttrib('renderPassword','true') ->setAttrib('renderPassword','true')
->setAllowEmpty(true) ->setAllowEmpty(true)
->setValue(Application_Model_Preference::GetLiveStreamMasterPassword()) ->setValue(Application_Model_Preference::GetLiveStreamMasterPassword())
->setLabel(_('Master Password')) ->setLabel(_('Password:'))
->setFilters(array('StringTrim')) ->setFilters(array('StringTrim'));
->setDecorators(array('ViewHelper'));
$this->addElement($master_password); $this->addElement($master_password);
//Master source connection url $masterSourceParams = parse_url(Application_Model_Preference::GetMasterDJSourceConnectionURL());
$master_dj_connection_url = new Zend_Form_Element_Text('master_dj_connection_url');
$master_dj_connection_url->setAttrib('readonly', true)
->setLabel(_('Master Source Connection URL'))
->setValue(Application_Model_Preference::GetMasterDJSourceConnectionURL())
->setDecorators(array('ViewHelper'));
$this->addElement($master_dj_connection_url);
//Show source connection url // Master source connection url parameters
$live_dj_connection_url = new Zend_Form_Element_Text('live_dj_connection_url'); $masterSourceHost = new Zend_Form_Element_Text('master_source_host');
$live_dj_connection_url->setAttrib('readonly', true) $masterSourceHost->setAttrib('readonly', true)
->setLabel(_('Show Source Connection URL')) ->setLabel(_('Host:'))
->setValue(Application_Model_Preference::GetLiveDJSourceConnectionURL()) ->setValue(isset($masterSourceParams["host"])?$masterSourceParams["host"]:"");
->setDecorators(array('ViewHelper')); $this->addElement($masterSourceHost);
$this->addElement($live_dj_connection_url);
$masterSourcePort = new Zend_Form_Element_Text('master_source_port');
$masterSourcePort->setAttrib('readonly', true)
->setLabel(_('Port:'))
->setValue(isset($masterSourceParams["port"])?$masterSourceParams["port"]:"");
$this->addElement($masterSourcePort);
$masterSourceMount = new Zend_Form_Element_Text('master_source_mount');
$masterSourceMount->setAttrib('readonly', true)
->setLabel(_('Mount:'))
->setValue(isset($masterSourceParams["path"])?$masterSourceParams["path"]:"");
$this->addElement($masterSourceMount);
$showSourceParams = parse_url(Application_Model_Preference::GetLiveDJSourceConnectionURL());
// Show source connection url parameters
$showSourceHost = new Zend_Form_Element_Text('show_source_host');
$showSourceHost->setAttrib('readonly', true)
->setLabel(_('Host:'))
->setValue(isset($showSourceParams["host"])?$showSourceParams["host"]:"");
$this->addElement($showSourceHost);
$showSourcePort = new Zend_Form_Element_Text('show_source_port');
$showSourcePort->setAttrib('readonly', true)
->setLabel(_('Port:'))
->setValue(isset($showSourceParams["port"])?$showSourceParams["port"]:"");
$this->addElement($showSourcePort);
$showSourceMount = new Zend_Form_Element_Text('show_source_mount');
$showSourceMount->setAttrib('readonly', true)
->setLabel(_('Mount:'))
->setValue(isset($showSourceParams["path"])?$showSourceParams["path"]:"");
$this->addElement($showSourceMount);
// demo only code // demo only code
if (!$isStreamConfigable) { if (!$isStreamConfigable) {
@ -93,19 +118,30 @@ class Application_Form_LiveStreamingPreferences extends Zend_Form_SubForm
$CC_CONFIG = Config::getConfig(); $CC_CONFIG = Config::getConfig();
$isDemo = isset($CC_CONFIG['demo']) && $CC_CONFIG['demo'] == 1; $isDemo = isset($CC_CONFIG['demo']) && $CC_CONFIG['demo'] == 1;
$master_dj_connection_url = Application_Model_Preference::GetMasterDJSourceConnectionURL(); $masterSourceParams = parse_url(Application_Model_Preference::GetMasterDJSourceConnectionURL());
$live_dj_connection_url = Application_Model_Preference::GetLiveDJSourceConnectionURL(); $showSourceParams = parse_url(Application_Model_Preference::GetLiveDJSourceConnectionURL());
$this->setDecorators(array( $this->setDecorators(
array('ViewScript', array('viewScript' => 'form/preferences_livestream.phtml', 'master_dj_connection_url'=>$master_dj_connection_url, 'live_dj_connection_url'=>$live_dj_connection_url, 'isDemo' => $isDemo)) array (
)); array ('ViewScript',
array (
'viewScript' => 'form/preferences_livestream.phtml',
'master_source_host' => isset($masterSourceParams["host"])?$masterSourceParams["host"]:"",
'master_source_port' => isset($masterSourceParams["port"])?$masterSourceParams["port"]:"",
'master_source_mount' => isset($masterSourceParams["path"])?$masterSourceParams["path"]:"",
'show_source_host' => isset($showSourceParams["host"])?$showSourceParams["host"]:"",
'show_source_port' => isset($showSourceParams["port"])?$showSourceParams["port"]:"",
'show_source_mount' => isset($showSourceParams["path"])?$showSourceParams["path"]:"",
'isDemo' => $isDemo,
)
)
)
);
} }
public function isValid($data) public function isValid($data)
{ {
$isValid = parent::isValid($data); return parent::isValid($data);
return $isValid;
} }
} }

View file

@ -17,6 +17,7 @@ class Application_Model_StoredFile
{ {
/** /**
* @holds propel database object * @holds propel database object
* @var CcFiles
*/ */
private $_file; private $_file;
@ -467,48 +468,6 @@ SQL;
$this->_file->save(); $this->_file->save();
} }
public function getRealFileExtension() {
$path = $this->_file->getDbFilepath();
$path_elements = explode('.', $path);
if (count($path_elements) < 2) {
return "";
} else {
return $path_elements[count($path_elements) - 1];
}
}
/**
* Return suitable extension.
*
* @return string
* file extension without a dot
*/
public function getFileExtension()
{
$possible_ext = $this->getRealFileExtension();
if ($possible_ext !== "") {
return $possible_ext;
}
// We fallback to guessing the extension from the mimetype if we
// cannot extract it from the file name
$mime = $this->_file->getDbMime();
if ($mime == "audio/ogg" || $mime == "application/ogg" || $mime == "audio/vorbis") {
return "ogg";
} elseif ($mime == "audio/mp3" || $mime == "audio/mpeg") {
return "mp3";
} elseif ($mime == "audio/x-flac") {
return "flac";
} elseif ($mime == "audio/mp4") {
return "mp4";
} else {
throw new Exception("Unknown $mime");
}
}
/** /**
* Get the absolute filepath * Get the absolute filepath
* *
@ -568,7 +527,7 @@ SQL;
*/ */
public function getRelativeFileUrl($baseUrl) public function getRelativeFileUrl($baseUrl)
{ {
return $baseUrl."api/get-media/file/".$this->getId().".".$this->getFileExtension(); return $baseUrl."api/get-media/file/".$this->getId();
} }
public function getResourceId() public function getResourceId()

View file

@ -205,11 +205,6 @@ class CcFiles extends BaseCcFiles {
$cloudFile->save(); $cloudFile->save();
Application_Model_Preference::updateDiskUsage($fileSizeBytes); Application_Model_Preference::updateDiskUsage($fileSizeBytes);
$now = new DateTime("now", new DateTimeZone("UTC"));
$file->setDbMtime($now);
$file->save();
} else if ($file) { } else if ($file) {
// Since we check for this value when deleting files, set it first // Since we check for this value when deleting files, set it first
@ -238,14 +233,13 @@ class CcFiles extends BaseCcFiles {
$file->setDbFilepath($filePathRelativeToStor); $file->setDbFilepath($filePathRelativeToStor);
} }
} }
} else {
throw new FileNotFoundException();
}
$now = new DateTime("now", new DateTimeZone("UTC")); $now = new DateTime("now", new DateTimeZone("UTC"));
$file->setDbMtime($now); $file->setDbMtime($now);
$file->save(); $file->save();
} else {
throw new FileNotFoundException();
}
} }
catch (FileNotFoundException $e) catch (FileNotFoundException $e)
{ {
@ -356,17 +350,27 @@ class CcFiles extends BaseCcFiles {
/** /**
* *
* Strips out the private fields we do not want to send back in API responses * Strips out the private fields we do not want to send back in API responses
* @param $file string a CcFiles object *
* @param CcFiles $file a CcFiles object
*
* @return array
*/ */
//TODO: rename this function? //TODO: rename this function?
public static function sanitizeResponse($file) public static function sanitizeResponse($file) {
{
$response = $file->toArray(BasePeer::TYPE_FIELDNAME); $response = $file->toArray(BasePeer::TYPE_FIELDNAME);
foreach (self::$privateFields as $key) { foreach (self::$privateFields as $key) {
unset($response[$key]); unset($response[$key]);
} }
$mime = $file->getDbMime();
if (!empty($mime)) {
// Get an extension based on the file's mime type and change the path to use this extension
$path = pathinfo($file->getDbFilepath());
$ext = FileDataHelper::getFileExtensionFromMime($mime);
$response["filepath"] = ($path["dirname"] . '/' . $path["filename"] . $ext);
}
return $response; return $response;
} }

View file

@ -455,3 +455,21 @@ class AirtimeUpgrader2513 extends AirtimeUpgrader
return '2.5.13'; return '2.5.13';
} }
} }
/**
* Class AirtimeUpgrader2514
*
* SAAS-923 - Add a partial constraint to cc_pref so that keystrings must be unique
*/
class AirtimeUpgrader2514 extends AirtimeUpgrader
{
protected function getSupportedSchemaVersions() {
return array (
'2.5.13'
);
}
public function getNewVersion() {
return '2.5.14';
}
}

View file

@ -2,13 +2,20 @@
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.16/angular.js" type="text/javascript"></script>
<link rel="stylesheet" href="<?php echo $this->css?>" type="text/css"> <link rel="stylesheet" href="<?php echo $this->css?>" type="text/css">
<script src="<?php echo $this->jquery ?>" type="text/javascript"></script> <script src="<?php echo $this->jquery ?>" type="text/javascript"></script>
<link href='https://fonts.googleapis.com/css?family=Roboto:400,100,300,700' rel='stylesheet' type='text/css'> <link href='https://fonts.googleapis.com/css?family=Roboto:400,100,300,700' rel='stylesheet' type='text/css'>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
//initialize first day to active
$('.tabs').find("li").first().addClass("active");
$('.schedule_content').find('.schedule_item').first().addClass("active");
$('.tabs li').click(function(){ $('.tabs li').click(function(){
var tab_id = $(this).attr('data-tab'); //var tab_id = $(this).attr('data-tab');
var tab_id = "day-"+$(this).find('span').text();
$('.tabs li').removeClass('active'); $('.tabs li').removeClass('active');
$('.schedule_item').removeClass('active'); $('.schedule_item').removeClass('active');
@ -18,38 +25,71 @@
}); });
}); });
String.prototype.paddingLeft = function(paddingValue) {
return String(paddingValue + this).slice(-paddingValue.length);
};
var schedule_data = <?php echo $this->schedule_data; ?>;
var app = angular.module('scheduleWidget', []);
app.controller('scheduleController', ['$scope', '$window', function($scope, $window) {
// Loop through every show and assign it to the corresponding day of the week's
// show array.
angular.forEach($window.schedule_data["shows"], function(value, key) {
// First we have to create a Date object out of the show time in UTC.
// Then we can format the string in the client's local timezone.
// NOTE: we have to multiply the timestamp by 1000 because in PHP
// the timestamps are in seconds and are in milliseconds in javascript.
var start_date = new Date(value.starts_timestamp*1000);
var end_date = new Date(value.ends_timestamp*1000);
// This variable is used to identify which schedule_data object (which day of the week)
// we should assign the show to.
// NOTE: we have to add 1 to the month because javascript's Date.getMonth()
// function returns the month number starting with an index of 0. In PHP,
// the months are indexed starting at 1.
var format_start_date = start_date.getFullYear() + "-" + (start_date.getMonth()+1) + "-" + start_date.getDate();
if ($window.schedule_data["weekDays"][format_start_date] !== undefined) {
$window.schedule_data["weekDays"][format_start_date]["shows"].push(
{
"show_start_hour": start_date.toLocaleTimeString([], { hour: 'numeric', minute : 'numeric' }),
"show_end_hour": end_date.toLocaleTimeString([], { hour: 'numeric', minute : 'numeric' }),
"name": value.name
});
}
});
// Convert the object into an array to maintain the same order when we
// iterate over each weekday
$scope.weekDays = $.map($window.schedule_data["weekDays"], function(value, index) {
return [value];
});
$scope.isEmpty = function(obj) {
return obj.length == 0;
};
}]);
</script> </script>
</head> </head>
<body> <body ng-app="scheduleWidget" ng-controller="scheduleController">
<div class="schedule tab_content current"> <div class="schedule tab_content current">
<ul class="tabs"> <ul class="tabs">
<?php <li ng-repeat="x in weekDays track by $index">
foreach($this->weeklyScheduleData as $day => $data) { {{x.dayOfWeek}}<span>{{x.dayOfMonth}}</span>
$activeClass = $this->currentDayOfMonth == $data["dayOfMonth"] ? "active" : ""; </li>
echo "<li class='".$activeClass."' data-tab='day-".$data["dayOfMonth"]."'>" . $data["dayOfWeek"] . "<span>" . $data["dayOfMonth"] . "</span></li>";
}?>
</ul> </ul>
<div class="schedule_content"> <div class="schedule_content">
<?php <div ng-repeat="x in weekDays track by $index" ng-attr-id="{{'day-' + x.dayOfMonth}}" class="schedule_item">
foreach($this->weeklyScheduleData as $day => $data) { <div ng-if="isEmpty(x.shows)" class="row empty-schedule">Looks like there are no shows scheduled on this day.</div>
$activeClass = $this->currentDayOfMonth == $data["dayOfMonth"] ? "active" : ""; <div ng-repeat="show in x.shows" class="row">
<div class="time_grid">{{show.show_start_hour}} - {{show.show_end_hour}}</div>
echo "<div id='day-".$data["dayOfMonth"]."' class='schedule_item ".$activeClass."'>"; <div class="name_grid">{{show.name}}</div>
if (count($data["shows"]) == 0) { </div>
echo "<div class='row empty-schedule'>Looks like there are no shows scheduled on this day.</div>"; </div>
} else {
foreach ($data["shows"] as $show => $showData) {
echo "<div class='row'>";
echo "<div class='time_grid'>" . $showData["show_start_hour"] . ' - ' . $showData["show_end_hour"] . "</div>";
echo "<div class='name_grid'>" . $showData["name"] . "</div>";
echo "</div>";
}
}
echo "</div>";
}?>
</div> </div>
<div class="weekly-schedule-widget-footer" <?php if ($this->widgetStyle == "premium") echo "style='display:none'"; ?>> <div class="weekly-schedule-widget-footer" <?php if ($this->widgetStyle == "premium") echo "style='display:none'"; ?>>

View file

@ -1,60 +1,20 @@
<fieldset> <fieldset>
<dl> <dl>
<dt id="cb_airtime_auth_override-label"> <?php echo $this->element->getElement('cb_airtime_auth')->render(); ?>
<label class="optional" for="cb_airtime_auth"> <?php echo $this->element->getElement('cb_custom_auth')->render(); ?>
<?php echo $this->element->getElement('cb_airtime_auth')->getLabel() ?>
<span class='airtime_auth_help_icon'></span>
</label>
</dt>
<dd id="cb_airtime_auth_override-element">
<?php echo $this->element->getElement('cb_airtime_auth') ?>
</dd>
<dt id="cb_custom_auth_override-label">
<label class="optional" for="cb_custom_auth">
<?php echo $this->element->getElement('cb_custom_auth')->getLabel() ?>
<span class='custom_auth_help_icon'></span>
</label>
</dt>
<dd id="cb_custom_auth_override-element">
<?php echo $this->element->getElement('cb_custom_auth') ?>
</dd>
<div id="custom_auth_div"> <div id="custom_auth_div">
<dt id="custom_username-label" class="block-display"> <?php echo $this->element->getElement('custom_username')->render(); ?>
<label class="optional" for="custom_username"><?php echo $this->element->getElement('custom_username')->getLabel() ?> : <?php echo $this->element->getElement('custom_password')->render(); ?>
<span class='stream_username_help_icon'></span>
</label>
</dt>
<dd id="custom_username-element" class="block-display">
<?php echo $this->element->getElement('custom_username') ?>
<?php if($this->element->getElement('custom_username')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('custom_username')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="custom_password-label" class="block-display">
<label class="optional" for="custom_password"><?php echo $this->element->getElement('custom_password')->getLabel() ?> :
</label>
</dt>
<dd id="custom_password-element" class="block-display">
<?php echo $this->element->getElement('custom_password') ?>
<?php if($this->element->getElement('custom_password')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('custom_password')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
</div> </div>
<dt id="Connection_URL-label"> <fieldset>
<label for="outputStreamURL"><?php echo _("Connection URL: "); ?></label> <legend><?php echo _("Show Source") ?></legend>
</dt> <p class="input-settings-inline-p">
<dd id="Connection_URL-element"> <?php echo _("DJs can use these settings to connect with compatible software and broadcast live during this show. Assign a DJ below.") ?>
<span id="stream_url" class="static_text"><?php echo $this->connection_url; ?></span> </p>
</dd> <?php echo $this->element->getElement("show_source_host")->render() ?>
<?php echo $this->element->getElement("show_source_port")->render() ?>
<?php echo $this->element->getElement("show_source_mount")->render() ?>
</fieldset>
</dl> </dl>
</fieldset> </fieldset>

View file

@ -1,102 +1,34 @@
<fieldset class="padded stream-setting-global" style="margin-top: 15px"> <fieldset class="padded stream-setting-global" style="margin-top: 15px">
<legend><?php echo _("Input Stream Settings") ?></legend> <legend><?php echo _("Input Stream Settings") ?></legend>
<dl class="zend_form"> <dl class="zend_form">
<dt id="auto_transition-label"> <?php echo $this->element->getElement('auto_transition')->render() ?>
<label class="optional" for="auto_transition"><?php echo $this->element->getElement('auto_transition')->getLabel() ?> : <span class="icecast_metadata_help_icon" id="auto_transition_help"></span>
<span class="icecast_metadata_help_icon" id="auto_transition_help"> <?php echo $this->element->getElement('auto_switch')->render() ?>
</span> <span class="icecast_metadata_help_icon" id="auto_switch_help"></span>
</label> <?php echo $this->element->getElement('transition_fade')->render() ?>
</dt>
<dd id="auto_transition-element"> <fieldset class="padded stream-setting-global" style="margin-top: 15px">
<?php echo $this->element->getElement('auto_transition') ?> <legend><?php echo _("Master Source") ?></legend>
<?php if($this->element->getElement('auto_transition')->hasErrors()) : ?> <p class="input-settings-inline-p">
<ul class='errors'> <?php echo _("Use these settings in your broadcasting software to stream live at any time.") ?>
<?php foreach($this->element->getElement('auto_transition')->getMessages() as $error): ?> </p>
<li><?php echo $error; ?></li> <?php echo $this->element->getElement('master_username')->render() ?>
<?php endforeach; ?> <span class="master_username_help_icon"></span>
</ul> <?php echo $this->element->getElement('master_password')->render() ?>
<?php endif; ?>
</dd> <?php echo $this->element->getElement("master_source_host")->render() ?>
<dt id="auto_switch-label"> <?php echo $this->element->getElement("master_source_port")->render() ?>
<label class="optional" for="auto_transition"><?php echo $this->element->getElement('auto_switch')->getLabel() ?> : <?php echo $this->element->getElement("master_source_mount")->render() ?>
<span class="icecast_metadata_help_icon" id="auto_switch_help"> </fieldset>
</span>
</label> <fieldset class="padded stream-setting-global" style="margin-top: 15px">
</dt> <legend><?php echo _("Show Source") ?></legend>
<dd id="auto_switch-element"> <p class="input-settings-inline-p">
<?php echo $this->element->getElement('auto_switch') ?> <?php echo _("DJs can use these settings in their broadcasting software to broadcast live only during shows assigned to them.") ?>
<?php if($this->element->getElement('auto_switch')->hasErrors()) : ?> </p>
<ul class='errors'> <?php echo $this->element->getElement("show_source_host")->render() ?>
<?php foreach($this->element->getElement('auto_switch')->getMessages() as $error): ?> <?php echo $this->element->getElement("show_source_port")->render() ?>
<li><?php echo $error; ?></li> <?php echo $this->element->getElement("show_source_mount")->render() ?>
<?php endforeach; ?> </fieldset>
</ul>
<?php endif; ?>
</dd>
<dt id="transition_fade-label">
<label class="optional" for="transition_fade"><?php echo $this->element->getElement('transition_fade')->getLabel() ?> :
</label>
</dt>
<dd id="transition_fade-element">
<?php echo $this->element->getElement('transition_fade') ?>
<?php if($this->element->getElement('transition_fade')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('transition_fade')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="master_username-label">
<label class="optional" for="master_username"><?php echo $this->element->getElement('master_username')->getLabel() ?> :
<span class='master_username_help_icon'></span>
</label>
</dt>
<dd id="master_username-element">
<?php echo $this->element->getElement('master_username') ?>
<?php if($this->element->getElement('master_username')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('master_username')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="master_password-label">
<label class="optional" for="master_password"><?php echo $this->element->getElement('master_password')->getLabel() ?> :
</label>
</dt>
<dd id="master_password-element">
<?php echo $this->element->getElement('master_password') ?>
<?php if($this->element->getElement('master_password')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('master_password')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="master_dj_connection_url-label">
<label class="optional" for="master_dj_connection_url" style="white-space: nowrap">
<?php echo _("Master Source Connection URL:")?>
</label>
</dt>
<dd id="master_dj_connection_url-element">
<span id="stream_url"><?php echo $this->element->getElement('master_dj_connection_url')->setValue($this->master_dj_connection_url) ?></span>
<div id="master_dj_connection_url_actions" style="display:none">
<a href=# id="ok" style="font-size: 12px;"><?php echo _("OK") ?></a> <a href=# id="reset" style="font-size: 12px;"><?php echo _("RESET"); ?></a>
</div>
</dd>
<dt id="live_dj_connection_url-label">
<label class="optional" for="live_dj_connection_url" style="white-space: nowrap">
<?php echo _("Show Source Connection URL:")?>
</label>
</dt>
<dd id="live_dj_connection_url-element">
<span id="stream_url"><?php echo $this->element->getElement('live_dj_connection_url')->setValue($this->live_dj_connection_url) ?></span>
<div id="live_dj_connection_url_actions" style="display:none">
<a href=# id="ok" style="font-size: 12px;"><?php echo _("OK") ?></a> <a href=# id="reset" style="font-size: 12px;"><?php echo _("RESET"); ?></a>
</div>
</dd>
</dl> </dl>
</fieldset> </fieldset>

View file

@ -376,3 +376,11 @@ INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_descripti
INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_genre', '', 'string'); INSERT INTO cc_stream_setting ("keyname", "value", "type") VALUES ('s4_genre', '', 'string');
INSERT INTO cc_stream_setting (keyname, value, type) VALUES ('s4_channels', 'stereo', 'string'); INSERT INTO cc_stream_setting (keyname, value, type) VALUES ('s4_channels', 'stereo', 'string');
-- added in 2.5.14 - this can't be set up in Propel's XML schema, so we need to do it here -- Duncan
ALTER TABLE cc_pref ALTER COLUMN subjid SET NULL;
ALTER TABLE cc_pref ALTER COLUMN subjid SET DEFAULT NULL;
CREATE UNIQUE INDEX cc_pref_key_idx ON cc_pref (keystr) WHERE subjid IS NULL;
ANALYZE cc_pref; -- this validates the new partial index
--end added in 2.5.14

View file

@ -123,7 +123,7 @@ background: rgba(53, 53, 53, 1.0);
.schedule_item div.time_grid { .schedule_item div.time_grid {
/*padding-right: 10px;*/ /*padding-right: 10px;*/
width: 20%; width: 30%;
font-weight: 300; font-weight: 300;
color: #AAAAAA; color: #AAAAAA;
display: inline-block; display: inline-block;
@ -133,7 +133,7 @@ background: rgba(53, 53, 53, 1.0);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
padding-left: 10px; padding-left: 10px;
width: 77%; width: 67%;
display:inline-block; display:inline-block;
vertical-align: middle; vertical-align: middle;
} }
@ -182,11 +182,11 @@ background: rgba(53, 53, 53, 1.0);
@media (max-width: 630px) { @media (max-width: 630px) {
.schedule_item div.time_grid { .schedule_item div.time_grid {
width: 25%; width: 37%;
} }
.schedule_item div.name_grid { .schedule_item div.name_grid {
width: 72%; width: 58%;
} }
} }
@ -197,31 +197,31 @@ background: rgba(53, 53, 53, 1.0);
} }
.schedule_item div.time_grid { .schedule_item div.time_grid {
width: 30%; width: 35%;
} }
.schedule_item div.name_grid { .schedule_item div.name_grid {
width: 67%; width: 60%;
} }
} }
@media (max-width: 500px) { @media (max-width: 500px) {
.schedule_item div.time_grid { .schedule_item div.time_grid {
width: 35%; width: 40%;
} }
.schedule_item div.name_grid { .schedule_item div.name_grid {
width: 62%; width: 55%;
} }
} }
@media (max-width: 400px) { @media (max-width: 400px) {
.schedule_item div.time_grid { .schedule_item div.time_grid {
width: 40%; width: 90%;
} }
.schedule_item div.name_grid { .schedule_item div.name_grid {
width: 50%; width: 90%;
} }
} }

View file

@ -97,7 +97,7 @@ body {
.schedule_item div.time_grid { .schedule_item div.time_grid {
/*padding-right: 10px;*/ /*padding-right: 10px;*/
width: 20%; width: 30%;
font-weight: 300; font-weight: 300;
color: #AAAAAA; color: #AAAAAA;
display: inline-block; display: inline-block;
@ -107,7 +107,7 @@ body {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
padding-left: 10px; padding-left: 10px;
width: 77%; width: 67%;
display:inline-block; display:inline-block;
vertical-align: middle; vertical-align: middle;
} }
@ -132,11 +132,11 @@ body {
@media (max-width: 630px) { @media (max-width: 630px) {
.schedule_item div.time_grid { .schedule_item div.time_grid {
width: 25%; width: 37%;
} }
.schedule_item div.name_grid { .schedule_item div.name_grid {
width: 72%; width: 58%;
} }
} }
@ -147,31 +147,31 @@ body {
} }
.schedule_item div.time_grid { .schedule_item div.time_grid {
width: 30%; width: 35%;
} }
.schedule_item div.name_grid { .schedule_item div.name_grid {
width: 67%; width: 60%;
} }
} }
@media (max-width: 500px) { @media (max-width: 500px) {
.schedule_item div.time_grid { .schedule_item div.time_grid {
width: 35%; width: 40%;
} }
.schedule_item div.name_grid { .schedule_item div.name_grid {
width: 62%; width: 55%;
} }
} }
@media (max-width: 400px) { @media (max-width: 400px) {
.schedule_item div.time_grid { .schedule_item div.time_grid {
width: 40%; width: 90%;
} }
.schedule_item div.name_grid { .schedule_item div.name_grid {
width: 50%; width: 90%;
} }
} }

View file

@ -47,6 +47,11 @@ select {
border:1px solid #9d9d9d; border:1px solid #9d9d9d;
} }
/* Fix for Firefox */
fieldset {
clear: both;
}
.logo { .logo {
background: transparent url(images/airtime_logo.png) no-repeat 0 0; background: transparent url(images/airtime_logo.png) no-repeat 0 0;
height: 35px; height: 35px;
@ -111,19 +116,38 @@ select {
margin-left: 7px !important; margin-left: 7px !important;
} }
.override_help_icon, .icecast_metadata_help_icon { .override_help_icon, .icecast_metadata_help_icon, .master_username_help_icon {
cursor: help; cursor: help;
position: relative; position: relative;
display:inline-block; zoom:1; display:inline; display: inline-block;
zoom:1;
width:14px; height:14px; width:14px; height:14px;
background:url(images/icon_info.png) 0 0 no-repeat; background:url(images/icon_info.png) 0 0 no-repeat;
float:right; position:relative; top:2px; right:7px; float:right;
top:2px;
right:7px;
line-height:16px !important; line-height:16px !important;
} }
#auto_switch_help, #auto_transition_help {
right: 200px;
}
.master_username_help_icon {
margin-top: 6px;
}
.input-settings-inline-p {
color: #4F4F4F;
font-size: 12px;
line-height: 140%;
margin-top: 0;
}
.airtime_auth_help_icon, .custom_auth_help_icon, .stream_username_help_icon, .airtime_auth_help_icon, .custom_auth_help_icon, .stream_username_help_icon,
.playlist_type_help_icon, .master_username_help_icon, .repeat_tracks_help_icon, .playlist_type_help_icon, .repeat_tracks_help_icon,
.admin_username_help_icon, .stream_type_help_icon, .show_linking_help_icon, .admin_username_help_icon, .stream_type_help_icon, .show_linking_help_icon,
.show_timezone_help_icon{ .show_timezone_help_icon{
cursor: help; cursor: help;
@ -1510,7 +1534,7 @@ h2#scheduled_playlist_name span {
border-color:#343434; border-color:#343434;
border-width: 1px 0 0 1px; border-width: 1px 0 0 1px;
} }
/*---//////////////////// Advenced Search ////////////////////---*/ /*---//////////////////// Advanced Search ////////////////////---*/
.search_control { .search_control {
padding:8px; padding:8px;

View file

@ -63,6 +63,12 @@ if (file_exists($filename)) {
} }
// Otherwise, we'll need to run our configuration setup // Otherwise, we'll need to run our configuration setup
else { else {
// Sometimes we can get into a weird NFS state where a station's airtime.conf has
// been neg-cached - redirect to a 404 instead until the NFS cache is updated
if (strpos($_SERVER['SERVER_NAME'], "airtime.pro") !== false) {
header($_SERVER['SERVER_PROTOCOL'] . ' 404 Page Not Found', true, 404);
exit;
}
$airtimeSetup = true; $airtimeSetup = true;
require_once(SETUP_PATH . 'setup-config.php'); require_once(SETUP_PATH . 'setup-config.php');
} }

View file

@ -1,5 +1,6 @@
var _playlist_jplayer; var _playlist_jplayer;
var _idToPostionLookUp; var _idToPostionLookUp;
var URL_BAR_HEIGHT = 32;
/** /**
*When the page loads the ready function will get all the data it can from the hidden span elements *When the page loads the ready function will get all the data it can from the hidden span elements
@ -187,18 +188,11 @@ function buildplaylist(p_url, p_playIndex) {
_playlist_jplayer.option("autoPlay", true); _playlist_jplayer.option("autoPlay", true);
play(p_playIndex); play(p_playIndex);
var height = Math.min(143 + (26 * total), 400); window.scrollbars = false;
var width = 505;
if (height === 400) { var container = $("#jp_container_1");
window.scrollbars = true; // Add 2px to account for borders
} window.resizeTo(container.width() + 2, container.height() + URL_BAR_HEIGHT + 2);
else {
//there's no scrollbars so we don't need the window to be as wide.
width = 490;
}
window.resizeTo(width, height);
}); });
} }
@ -247,4 +241,8 @@ function playOne(uri, mime) {
_playlist_jplayer.setPlaylist(playlist); _playlist_jplayer.setPlaylist(playlist);
_playlist_jplayer.play(0); _playlist_jplayer.play(0);
} }
var container = $("#jp_container_1");
// Add 2px to account for borders
window.resizeTo(container.width() + 2, container.height() + URL_BAR_HEIGHT + 2);
} }

View file

@ -1,3 +1,6 @@
var previewWidth = 482,
previewHeight = 110;
$(document).ready(function() { $(document).ready(function() {
/* Removed as this is now (hopefully) unnecessary */ /* Removed as this is now (hopefully) unnecessary */
@ -95,7 +98,7 @@ function open_audio_preview(type, id) {
// The reason that we need to encode artist and title string is that // The reason that we need to encode artist and title string is that
// sometime they contain '/' or '\' and apache reject %2f or %5f // sometime they contain '/' or '\' and apache reject %2f or %5f
// so the work around is to encode it twice. // so the work around is to encode it twice.
openPreviewWindow(baseUrl+'audiopreview/audio-preview/audioFileID/'+id+'/type/'+type); openPreviewWindow(baseUrl+'audiopreview/audio-preview/audioFileID/'+id+'/type/'+type, previewWidth, previewHeight);
_preview_window.focus(); _preview_window.focus();
} }
@ -113,7 +116,7 @@ function open_playlist_preview(p_playlistID, p_playlistIndex) {
if (_preview_window != null && !_preview_window.closed) if (_preview_window != null && !_preview_window.closed)
_preview_window.playAllPlaylist(p_playlistID, p_playlistIndex); _preview_window.playAllPlaylist(p_playlistID, p_playlistIndex);
else else
openPreviewWindow(baseUrl+'audiopreview/playlist-preview/playlistIndex/'+p_playlistIndex+'/playlistID/'+p_playlistID); openPreviewWindow(baseUrl+'audiopreview/playlist-preview/playlistIndex/'+p_playlistIndex+'/playlistID/'+p_playlistID, previewWidth, previewHeight);
_preview_window.focus(); _preview_window.focus();
} }
@ -124,7 +127,7 @@ function open_block_preview(p_blockId, p_blockIndex) {
if (_preview_window != null && !_preview_window.closed) if (_preview_window != null && !_preview_window.closed)
_preview_window.playBlock(p_blockId, p_blockIndex); _preview_window.playBlock(p_blockId, p_blockIndex);
else else
openPreviewWindow(baseUrl+'audiopreview/block-preview/blockIndex/'+p_blockIndex+'/blockId/'+p_blockId); openPreviewWindow(baseUrl+'audiopreview/block-preview/blockIndex/'+p_blockIndex+'/blockId/'+p_blockId, previewWidth, previewHeight);
_preview_window.focus(); _preview_window.focus();
} }
@ -138,13 +141,14 @@ function open_show_preview(p_showID, p_showIndex) {
if (_preview_window != null && !_preview_window.closed) if (_preview_window != null && !_preview_window.closed)
_preview_window.playAllShow(p_showID, p_showIndex); _preview_window.playAllShow(p_showID, p_showIndex);
else else
openPreviewWindow(baseUrl+'audiopreview/show-preview/showID/'+p_showID+'/showIndex/'+p_showIndex); openPreviewWindow(baseUrl+'audiopreview/show-preview/showID/'+p_showID+'/showIndex/'+p_showIndex, previewWidth, previewHeight);
_preview_window.focus(); _preview_window.focus();
} }
function openPreviewWindow(url) { function openPreviewWindow(url, w, h) {
var dim = (w && h) ? 'width=' + w + ',height=' + h + ',' : '';
// Hardcoding this here is kinda gross, but the alternatives aren't much better... // Hardcoding this here is kinda gross, but the alternatives aren't much better...
_preview_window = window.open(url, $.i18n._('Audio Player'), 'width=482,height=110,scrollbars=yes'); _preview_window = window.open(url, $.i18n._('Audio Player'), dim + 'scrollbars=yes');
return false; return false;
} }

View file

@ -786,6 +786,11 @@ function setAddShowEvents(form) {
scheduleRefetchEvents(json); scheduleRefetchEvents(json);
$addShowForm.hide(); $addShowForm.hide();
} }
/* CC-6062: Resize the window to avoid stretching the last column */
windowResize();
makeAddShowButton();
} }
}); });
}); });

33
install
View file

@ -312,6 +312,17 @@ else
checkCommandExists "psql" checkCommandExists "psql"
fi fi
# Check if composer exists and install if it doesn't
eval hash "composer" 2>/dev/null
commandFound=$?
if [[ ! ${commandFound} -eq 0 ]]; then
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
fi
# Run composer (install PHP dependencies) and create a VERSION file
loudCmd "./build.sh"
if [ -f /etc/airtime/airtime.conf ]; then if [ -f /etc/airtime/airtime.conf ]; then
OLD_CONF=$(grep "[media-monitor]" /etc/airtime/airtime.conf) OLD_CONF=$(grep "[media-monitor]" /etc/airtime/airtime.conf)
@ -472,19 +483,27 @@ verbose "\n * Installing airtime_analyzer..."
loudCmd "python ${AIRTIMEROOT}/python_apps/airtime_analyzer/setup.py install --install-scripts=/usr/bin" loudCmd "python ${AIRTIMEROOT}/python_apps/airtime_analyzer/setup.py install --install-scripts=/usr/bin"
verbose "...Done" verbose "...Done"
#for i in /etc/init/airtime*.template; do for i in /etc/init/airtime*.template; do
# chmod 644 $i chmod 644 $i
# sed -i "s/WEB_USER/${web_user}/g" $i sed -i "s/WEB_USER/${web_user}/g" $i
# mv $i ${i%.template} mv $i ${i%.template}
#done done
set +e set +e
loudCmd "initctl reload-configuration" loudCmd "initctl reload-configuration"
loudCmd "systemctl daemon-reload" #systemd hipsters
loudCmd "update-rc.d airtime-playout defaults" # Start at bootup, on Debian # airtime-celery only has an init.d startup script
loudCmd "update-rc.d airtime-celery defaults" # Start at bootup, on Debian loudCmd "update-rc.d airtime-celery defaults" # Start at bootup, on Debian
# On Ubuntu, we already have the upstart configs, so this is redundant
# and causes multiple processes to spawn on startup
if [ "$dist" != "ubuntu" ]; then
loudCmd "systemctl daemon-reload" #systemd hipsters
loudCmd "update-rc.d airtime-playout defaults" # Start at bootup, on Debian
loudCmd "update-rc.d airtime-liquidsoap defaults" # Start at bootup, on Debian loudCmd "update-rc.d airtime-liquidsoap defaults" # Start at bootup, on Debian
loudCmd "update-rc.d airtime_analyzer defaults" # Start at bootup, on Debian loudCmd "update-rc.d airtime_analyzer defaults" # Start at bootup, on Debian
fi
set -e set -e
if [ ! -d /var/log/airtime ]; then if [ ! -d /var/log/airtime ]; then

View file

@ -9,6 +9,9 @@ import pickle
import threading import threading
from urlparse import urlparse from urlparse import urlparse
# Disable urllib3 warnings because these can cause a rare deadlock due to Python 2's crappy internal non-reentrant locking
# around POSIX stuff. See SAAS-714. The hasattr() is for compatibility with older versions of requests.
if hasattr(requests, 'packages'):
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
class PicklableHttpRequest: class PicklableHttpRequest:

View file

@ -22,7 +22,7 @@ else:
mm2_files.append(os.path.join(root, filename)) mm2_files.append(os.path.join(root, filename))
data_files = [ data_files = [
# ('/etc/init', ['install/upstart/airtime-media-monitor.conf.template']), ('/etc/init', ['install/upstart/airtime-media-monitor.conf.template']),
('/etc/init.d', ['install/sysvinit/airtime-media-monitor']), ('/etc/init.d', ['install/sysvinit/airtime-media-monitor']),
('/etc/airtime', ['install/media_monitor_logging.cfg']), ('/etc/airtime', ['install/media_monitor_logging.cfg']),
('/var/log/airtime/media-monitor', []), ('/var/log/airtime/media-monitor', []),