Merge remote-tracking branch 'upstream/master'

This commit is contained in:
jerry 2018-01-16 17:29:16 -08:00
commit ed9c645476
24 changed files with 1184 additions and 475 deletions

View File

@ -163,6 +163,8 @@ final class LocaleController extends Zend_Controller_Action
"is greater than" => _("is greater than"),
"is less than" => _("is less than"),
"is in the range" => _("is in the range"),
"Preview" => _("Preview"),
"Generate" => _("Generate"),
//preferences/musicdirs.js
"Choose Storage Folder" => _("Choose Storage Folder"),
"Choose Folder to Watch" => _("Choose Folder to Watch"),

View File

@ -57,18 +57,21 @@ class PlaylistController extends Zend_Controller_Action
return $obj;
}
private function createUpdateResponse($obj)
private function createUpdateResponse($obj, $formIsValid = false)
{
$formatter = new LengthFormatter($obj->getLength());
$this->view->length = $formatter->format();
$this->view->obj = $obj;
$this->view->contents = $obj->getContents();
if ($formIsValid) {
$this->view->poolCount = $obj->getListofFilesMeetCriteria()['count'];
}
$this->view->showPoolCount = true;
$this->view->html = $this->view->render('playlist/update.phtml');
$this->view->name = $obj->getName();
$this->view->description = $obj->getDescription();
$this->view->modified = $obj->getLastModified("U");
unset($this->view->obj);
}
@ -99,7 +102,6 @@ class PlaylistController extends Zend_Controller_Action
$form = new Application_Form_SmartBlockCriteria();
$form->removeDecorator('DtDdWrapper');
$form->startForm($obj->getId(), $formIsValid);
$this->view->form = $form;
$this->view->obj = $obj;
//$this->view->type = "sb";
@ -555,8 +557,7 @@ class PlaylistController extends Zend_Controller_Action
if ($form->isValid($params)) {
$this->setPlaylistNameDescAction();
$bl->saveSmartBlockCriteria($params['data']);
$this->createUpdateResponse($bl);
$this->createUpdateResponse($bl, true);
$this->view->result = 0;
/*
$result['html'] = $this->createFullResponse($bl, true, true);
@ -599,7 +600,7 @@ class PlaylistController extends Zend_Controller_Action
if ($form->isValid($params)) {
$result = $bl->generateSmartBlock($params['data']);
$this->view->result = $result['result'];
$this->createUpdateResponse($bl);
$this->createUpdateResponse($bl, true);
#$this->_helper->json->sendJson(array("result"=>0, "html"=>$this->createFullResponse($bl, true, true)));
} else {
$this->view->obj = $bl;
@ -624,7 +625,7 @@ class PlaylistController extends Zend_Controller_Action
$result = $bl->shuffleSmartBlock();
$this->view->result = $result["result"];
$this->createUpdateResponse($bl);
$this->createUpdateResponse($bl,true);
/*
if ($result['result'] == 0) {
@ -652,7 +653,7 @@ class PlaylistController extends Zend_Controller_Action
$result = $pl->shuffle();
$this->view->result = $result["result"];
$this->createUpdateResponse($pl);
$this->createUpdateResponse($pl,true);
/*
if ($result['result'] == 0) {
$this->_helper->json->sendJson(array(

View File

@ -4,6 +4,8 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
private $criteriaOptions;
private $stringCriteriaOptions;
private $numericCriteriaOptions;
private $dateTimeCriteriaOptions;
private $timePeriodCriteriaOptions;
private $sortOptions;
private $limitOptions;
@ -24,9 +26,9 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
"description" => "s",
"artist_name" => "s",
"encoded_by" => "s",
"utime" => "n",
"mtime" => "n",
"lptime" => "n",
"utime" => "d",
"mtime" => "d",
"lptime" => "d",
"genre" => "s",
"isrc_number" => "s",
"label" => "s",
@ -114,6 +116,43 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
return $this->numericCriteriaOptions;
}
private function getDateTimeCriteriaOptions()
{
if (!isset($this->dateTimeCriteriaOptions)) {
$this->dateTimeCriteriaOptions = array(
"0" => _("Select modifier"),
"before" => _("before"),
"after" => _("after"),
"between" => _("between"),
"is" => _("is"),
"is not" => _("is not"),
"is greater than" => _("is greater than"),
"is less than" => _("is less than"),
"is in the range" => _("is in the range")
);
}
return $this->dateTimeCriteriaOptions;
}
private function getTimePeriodCriteriaOptions()
{
if (!isset($this->timePeriodCriteriaOptions)) {
$this->timePeriodCriteriaOptions = array(
"0" => _("Select unit of time"),
"minute" => _("minute(s)"),
"hour" => _("hour(s)"),
"day" => _("day(s)"),
"week" => _("week(s)"),
"month" => _("month(s)"),
"year" => _("year(s)")
);
}
return $this->timePeriodCriteriaOptions;
}
private function getLimitOptions()
{
if (!isset($this->limitOptions)) {
@ -154,19 +193,32 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
if (isset($criteria[$column])) {
foreach ($criteria[$column] as &$constraint) {
$constraint['value'] =
Application_Common_DateHelper::UTCStringToUserTimezoneString($constraint['value']);
if (isset($constraint['extra'])) {
$constraint['extra'] =
Application_Common_DateHelper::UTCStringToUserTimezoneString($constraint['extra']);
}
// convert to appropriate timezone timestamps only if the modifier is not a relative time
if (!in_array($constraint['modifier'], array('before','after','between'))) {
$constraint['value'] =
Application_Common_DateHelper::UTCStringToUserTimezoneString($constraint['value']);
if (isset($constraint['extra'])) {
$constraint['extra'] =
Application_Common_DateHelper::UTCStringToUserTimezoneString($constraint['extra']);
}
}
}
}
}
}
/*
* This function takes a blockID as param and creates the data structure for the form displayed with the view
* smart-block-criteria.phtml
*
* A description of the dataflow. First it loads the block and determines if it is a static or dynamic smartblock.
* Next it adds a radio selector for static or dynamic type.
* Then it loads the criteria via the getCriteria() function, which returns an array for each criteria.
*
*
*/
public function startForm($p_blockId, $p_isValid = false)
{
// load type
@ -204,20 +256,25 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$openSmartBlockOption = true;
}
// this returns a number indexed array for each criteria found in the database
$criteriaKeys = array();
if (isset($storedCrit["crit"])) {
$criteriaKeys = array_keys($storedCrit["crit"]);
}
$numElements = count($this->getCriteriaOptions());
// loop through once for each potential criteria option ie album, composer, track
for ($i = 0; $i < $numElements; $i++) {
$criteriaType = "";
// if there is a criteria found then count the number of rows for this specific criteria ie > 1 track title
if (isset($criteriaKeys[$i])) {
$critCount = count($storedCrit["crit"][$criteriaKeys[$i]]);
} else {
$critCount = 1;
}
// store the number of items with the same key in the ModRowMap
$modRowMap[$i] = $critCount;
/* Loop through all criteria with the same field
@ -225,6 +282,7 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
*/
for ($j = 0; $j < $critCount; $j++) {
/****************** CRITERIA ***********/
// hide the criteria drop down select on any rows after the first
if ($j > 0) {
$invisible = ' sp-invisible';
} else {
@ -236,17 +294,23 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
->setValue('Select criteria')
->setDecorators(array('viewHelper'))
->setMultiOptions($this->getCriteriaOptions());
// if this isn't the first criteria and there isn't an entry for it already disable it
if ($i != 0 && !isset($criteriaKeys[$i])) {
$criteria->setAttrib('disabled', 'disabled');
}
// add the numbering to the form ie the i loop for each specific criteria and
// the j loop starts at 0 and grows for each item matching the same criteria
// look up the criteria type using the criteriaTypes function from above based upon the criteria value
if (isset($criteriaKeys[$i])) {
$criteriaType = $this->criteriaTypes[$storedCrit["crit"][$criteriaKeys[$i]][$j]["criteria"]];
$criteria->setValue($storedCrit["crit"][$criteriaKeys[$i]][$j]["criteria"]);
}
$this->addElement($criteria);
/****************** MODIFIER ***********/
// every element has an optional modifier dropdown select
$criteriaModifers = new Zend_Form_Element_Select("sp_criteria_modifier_".$i."_".$j);
$criteriaModifers->setValue('Select modifier')
->setAttrib('class', 'input_select sp_input_select')
@ -254,10 +318,15 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
if ($i != 0 && !isset($criteriaKeys[$i])) {
$criteriaModifers->setAttrib('disabled', 'disabled');
}
// determine the modifier based upon criteria type which is looked up based upon an array
if (isset($criteriaKeys[$i])) {
if ($criteriaType == "s") {
$criteriaModifers->setMultiOptions($this->getStringCriteriaOptions());
} else {
}
elseif ($criteriaType == "d") {
$criteriaModifers->setMultiOptions($this->getDateTimeCriteriaOptions());
}
else {
$criteriaModifers->setMultiOptions($this->getNumericCriteriaOptions());
}
$criteriaModifers->setValue($storedCrit["crit"][$criteriaKeys[$i]][$j]["modifier"]);
@ -267,6 +336,7 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$this->addElement($criteriaModifers);
/****************** VALUE ***********/
/* The challenge here is that datetime */
$criteriaValue = new Zend_Form_Element_Text("sp_criteria_value_".$i."_".$j);
$criteriaValue->setAttrib('class', 'input_text sp_input_text')
->setDecorators(array('viewHelper'));
@ -274,22 +344,96 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$criteriaValue->setAttrib('disabled', 'disabled');
}
if (isset($criteriaKeys[$i])) {
$criteriaValue->setValue($storedCrit["crit"][$criteriaKeys[$i]][$j]["value"]);
/*
* Need to parse relative dates in a special way to populate select box down below
*/
// this is used below to test whether the datetime select should be shown or hidden
$relativeDateTime = false;
$modifierTest = (string)$storedCrit["crit"][$criteriaKeys[$i]][$j]["modifier"];
if(isset($criteriaType) && $criteriaType == "d" &&
preg_match('/before|after|between/', $modifierTest) == 1) {
// set relativeDatetime boolean to true so that the datetime select is displayed below
$relativeDateTime = true;
// the criteria value will be a number followed by time unit and ago so set input to number part
$criteriaValue->setValue(filter_var($storedCrit["crit"][$criteriaKeys[$i]][$j]["value"], FILTER_SANITIZE_NUMBER_INT));
} else {
$criteriaValue->setValue($storedCrit["crit"][$criteriaKeys[$i]][$j]["value"]);
}
}
$this->addElement($criteriaValue);
/****************** DATETIME SELECT *************/
$criteriaDatetimeSelect = new Zend_Form_Element_Select("sp_criteria_datetime_select_".$i."_".$j);
$criteriaDatetimeSelect->setAttrib('class','input_select sp_input_select')
->setDecorators(array('viewHelper'));
if (isset($criteriaKeys[$i]) && $relativeDateTime) {
$criteriaDatetimeSelect->setAttrib('enabled', 'enabled');
}
else {
$criteriaDatetimeSelect->setAttrib('disabled', 'disabled');
}
// check if the value is stored and it is a relative datetime field
if (isset($criteriaKeys[$i]) && isset($storedCrit["crit"][$criteriaKeys[$i]][$j]["value"])
&& isset($criteriaType) && $criteriaType == "d" &&
preg_match('/before|after|between/', $modifierTest) == 1) {
// need to remove any leading numbers stored in the database
$dateTimeSelectValue = preg_replace('/[0-9]+/', '', $storedCrit["crit"][$criteriaKeys[$i]][$j]["value"]);
// need to strip white from front and ago from the end to match with the value of the time unit select dropdown
$dateTimeSelectValue = trim(preg_replace('/\W\w+\s*(\W*)$/', '$1', $dateTimeSelectValue));
$criteriaDatetimeSelect->setMultiOptions($this->getTimePeriodCriteriaOptions());
$criteriaDatetimeSelect->setValue($dateTimeSelectValue);
$criteriaDatetimeSelect->setAttrib('enabled', 'enabled');
}
else {
$criteriaDatetimeSelect->setMultiOptions(array('0' => _('Select unit of time')));
$criteriaDatetimeSelect->setMultiOptions($this->getTimePeriodCriteriaOptions());
}
$this->addElement($criteriaDatetimeSelect);
/****************** EXTRA ***********/
$criteriaExtra = new Zend_Form_Element_Text("sp_criteria_extra_".$i."_".$j);
$criteriaExtra->setAttrib('class', 'input_text sp_extra_input_text')
->setDecorators(array('viewHelper'));
if (isset($criteriaKeys[$i]) && isset($storedCrit["crit"][$criteriaKeys[$i]][$j]["extra"])) {
$criteriaExtra->setValue($storedCrit["crit"][$criteriaKeys[$i]][$j]["extra"]);
// need to check if this is a relative date time value
if(isset($criteriaType) && $criteriaType == "d" && $modifierTest == 'between') {
// the criteria value will be a number followed by time unit and ago so set input to number part
$criteriaExtra->setValue(filter_var($storedCrit["crit"][$criteriaKeys[$i]][$j]["extra"], FILTER_SANITIZE_NUMBER_INT));
}
else {
$criteriaExtra->setValue($storedCrit["crit"][$criteriaKeys[$i]][$j]["extra"]);
}
$criteriaValue->setAttrib('class', 'input_text sp_extra_input_text');
} else {
$criteriaExtra->setAttrib('disabled', 'disabled');
}
$this->addElement($criteriaExtra);
/****************** DATETIME SELECT EXTRA **********/
$criteriaExtraDatetimeSelect = new Zend_Form_Element_Select("sp_criteria_extra_datetime_select_".$i."_".$j);
$criteriaExtraDatetimeSelect->setAttrib('class','input_select sp_input_select')
->setDecorators(array('viewHelper'));
if (isset($criteriaKeys[$i]) && isset($storedCrit["crit"][$criteriaKeys[$i]][$j]["extra"])
&& $modifierTest == 'between') {
// need to remove the leading numbers stored in the database
$extraDateTimeSelectValue = preg_replace('/[0-9]+/', '', $storedCrit["crit"][$criteriaKeys[$i]][$j]["extra"]);
// need to strip white from front and ago from the end to match with the value of the time unit select dropdown
$extraDateTimeSelectValue = trim(preg_replace('/\W\w+\s*(\W*)$/', '$1', $extraDateTimeSelectValue));
$criteriaExtraDatetimeSelect->setMultiOptions($this->getTimePeriodCriteriaOptions());
// Logging::info('THIS IS-'.$extraDateTimeSelectValue.'-IT');
$criteriaExtraDatetimeSelect->setValue($extraDateTimeSelectValue);
$criteriaExtraDatetimeSelect->setAttrib('enabled', 'enabled');
} else {
$criteriaExtraDatetimeSelect->setMultiOptions(array('0' => _('Select unit of time')));
$criteriaExtraDatetimeSelect->setMultiOptions($this->getTimePeriodCriteriaOptions());
$criteriaExtraDatetimeSelect->setAttrib('disabled', 'disabled');
}
$this->addElement($criteriaExtraDatetimeSelect);
}//for
}//for
@ -347,7 +491,12 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$generate->setAttrib('class', 'sp-button btn');
$generate->setAttrib('title', _('Generate playlist content and save criteria'));
$generate->setIgnore(true);
$generate->setLabel(_('Generate'));
if ($blockType == 0) {
$generate->setLabel(_('Generate'));
}
else {
$generate->setLabel(_('Preview'));
}
$generate->setDecorators(array('viewHelper'));
$this->addElement($generate);
@ -361,10 +510,15 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$this->setDecorators(array(
array('ViewScript', array('viewScript' => 'form/smart-block-criteria.phtml', "openOption"=> $openSmartBlockOption,
'criteriasLength' => count($this->getCriteriaOptions()), 'poolCount' => $files['count'], 'modRowMap' => $modRowMap,
'showPoolCount' => $showPoolCount))
'criteriasLength' => count($this->getCriteriaOptions()), 'modRowMap' => $modRowMap))
));
}
/*
* This is a simple function that determines if a modValue should enable a datetime
*/
public function enableDateTimeUnit($modValue) {
return (preg_match('/before|after|between/', $modValue) == 1);
}
public function preValidation($params)
{
@ -390,12 +544,23 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$eleMod->setMultiOptions($this->getStringCriteriaOptions());
} elseif ($criteriaType == "n") {
$eleMod->setMultiOptions($this->getNumericCriteriaOptions());
} elseif ($criteriaType == "d") {
$eleMod->setMultiOptions($this->getDateTimeCriteriaOptions());
} else {
$eleMod->setMultiOptions(array('0' => _('Select modifier')));
}
$eleMod->setValue($modInfo['sp_criteria_modifier']);
$eleMod->setAttrib("disabled", null);
$eleDatetime = $this->getElement("sp_criteria_datetime_select_".$critKey."_".$modKey);
if ($this->enableDateTimeUnit($eleMod->getValue())) {
$eleDatetime->setAttrib("enabled", "enabled");
$eleDatetime->setValue($modInfo['sp_criteria_datetime_select']);
$eleDatetime->setAttrib("disabled", null);
}
else {
$eleDatetime->setAttrib("disabled","disabled");
}
$eleValue = $this->getElement("sp_criteria_value_".$critKey."_".$modKey);
$eleValue->setValue($modInfo['sp_criteria_value']);
$eleValue->setAttrib("disabled", null);
@ -406,6 +571,15 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$eleValue->setAttrib('class', 'input_text sp_extra_input_text');
$eleExtra->setAttrib("disabled", null);
}
$eleExtraDatetime = $this->getElement("sp_criteria_extra_datetime_select_".$critKey."_".$modKey);
if ($eleMod->getValue() == 'between') {
$eleExtraDatetime->setAttrib("enabled", "enabled");
$eleExtraDatetime->setValue($modInfo['sp_criteria_extra_datetime_select']);
$eleExtraDatetime->setAttrib("disabled", null);
}
else {
$eleExtraDatetime->setAttrib("disabled","disabled");
}
} else {
$criteria = new Zend_Form_Element_Select("sp_criteria_field_".$critKey."_".$modKey);
@ -428,6 +602,9 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$criteriaModifers->setMultiOptions($this->getStringCriteriaOptions());
} elseif ($criteriaType == "n") {
$criteriaModifers->setMultiOptions($this->getNumericCriteriaOptions());
}
elseif ($criteriaType == "d") {
$criteriaModifers->setMultiOptions($this->getDateTimeCriteriaOptions());
} else {
$criteriaModifers->setMultiOptions(array('0' => _('Select modifier')));
}
@ -440,7 +617,20 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
->setDecorators(array('viewHelper'));
$criteriaValue->setValue($modInfo['sp_criteria_value']);
$this->addElement($criteriaValue);
/****************** DATETIME UNIT SELECT ***********/
$criteriaDatetimeSelect = new Zend_Form_Element_Select("sp_criteria_datetime_select_".$critKey."_".$modKey);
$criteriaDatetimeSelect->setAttrib('class','input_select sp_input_select')
->setDecorators(array('viewHelper'));
if ($this->enableDateTimeUnit($criteriaValue->getValue())) {
$criteriaDatetimeSelect->setAttrib('enabled', 'enabled');
$criteriaDatetimeSelect->setAttrib('disabled', null);
$criteriaDatetimeSelect->setValue($modInfo['sp_criteria_datetime_select']);
$this->addElement($criteriaDatetimeSelect);
}
else {
$criteriaDatetimeSelect->setAttrib('disabled', 'disabled');
}
/****************** EXTRA ***********/
$criteriaExtra = new Zend_Form_Element_Text("sp_criteria_extra_".$critKey."_".$modKey);
$criteriaExtra->setAttrib('class', 'input_text sp_extra_input_text')
@ -452,6 +642,21 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$criteriaExtra->setAttrib('disabled', 'disabled');
}
$this->addElement($criteriaExtra);
/****************** EXTRA DATETIME UNIT SELECT ***********/
$criteriaExtraDatetimeSelect = new Zend_Form_Element_Select("sp_criteria_extra_datetime_select_".$critKey."_".$modKey);
$criteriaExtraDatetimeSelect->setAttrib('class','input_select sp_input_select')
->setDecorators(array('viewHelper'));
if ($criteriaValue->getValue() == 'between') {
$criteriaExtraDatetimeSelect->setAttrib('enabled', 'enabled');
$criteriaExtraDatetimeSelect->setAttrib('disabled', null);
$criteriaExtraDatetimeSelect->setValue($modInfo['sp_criteria_extra_datetime_select']);
$this->addElement($criteriaExtraDatetimeSelect);
}
else {
$criteriaExtraDatetimeSelect->setAttrib('disabled', 'disabled');
}
$count++;
}
}
@ -473,6 +678,7 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$this->populate($formData);
// Logging::info($formData);
return $data;
}
@ -565,25 +771,26 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
$element->addError(_("'Length' should be in '00:00:00' format"));
$isValid = false;
}
// this looks up the column type for the criteria the modified time, upload time etc.
} elseif ($column->getType() == PropelColumnTypes::TIMESTAMP) {
if (!preg_match("/(\d{4})-(\d{2})-(\d{2})/", $d['sp_criteria_value'])) {
$element->addError(_("The value should be in timestamp format (e.g. 0000-00-00 or 0000-00-00 00:00:00)"));
$isValid = false;
} else {
$result = Application_Common_DateHelper::checkDateTimeRangeForSQL($d['sp_criteria_value']);
if (!$result["success"]) {
// check for if it is in valid range( 1753-01-01 ~ 12/31/9999 )
$element->addError($result["errMsg"]);
// need to check for relative modifiers first - bypassing currently
if (in_array($d['sp_criteria_modifier'], array('before','after','between'))) {
if (!preg_match("/^[1-9][0-9]*$|0/",$d['sp_criteria_value'])) {
$element->addError(_("Only non-negative integer numbers are allowed (e.g 1 or 5) for the text value"));
$isValid = false;
// TODO validate this on numeric input with whatever parsing also do for extra
//if the modifier is before ago or between we skip validation until we confirm format
}
elseif (isSet($d['sp_criteria_datetime_select']) && $d['sp_criteria_datetime_select'] == "0") {
$element->addError(_("You must select a time unit for a relative datetime."));
$isValid = false;
}
}
if (isset($d['sp_criteria_extra'])) {
if (!preg_match("/(\d{4})-(\d{2})-(\d{2})/", $d['sp_criteria_extra'])) {
} else {
if (!preg_match("/(\d{4})-(\d{2})-(\d{2})/", $d['sp_criteria_value'])) {
$element->addError(_("The value should be in timestamp format (e.g. 0000-00-00 or 0000-00-00 00:00:00)"));
$isValid = false;
} else {
$result = Application_Common_DateHelper::checkDateTimeRangeForSQL($d['sp_criteria_extra']);
$result = Application_Common_DateHelper::checkDateTimeRangeForSQL($d['sp_criteria_value']);
if (!$result["success"]) {
// check for if it is in valid range( 1753-01-01 ~ 12/31/9999 )
$element->addError($result["errMsg"]);
@ -591,6 +798,33 @@ class Application_Form_SmartBlockCriteria extends Zend_Form_SubForm
}
}
}
if (isset($d['sp_criteria_extra'])) {
if ($d['sp_criteria_modifier'] == 'between') {
// validate that the input value only contains a number if using relative date times
if (!preg_match("/^[1-9][0-9]*$|0/",$d['sp_criteria_extra'])) {
$element->addError(_("Only non-negative integer numbers are allowed for a relative date time"));
$isValid = false;
}
// also need to check to make sure they chose a time unit from the dropdown
elseif ($d['sp_criteria_extra_datetime_select'] == "0") {
$element->addError(_("You must select a time unit for a relative datetime."));
$isValid = false;
}
}
else {
if (!preg_match("/(\d{4})-(\d{2})-(\d{2})/", $d['sp_criteria_extra'])) {
$element->addError(_("The value should be in timestamp format (e.g. 0000-00-00 or 0000-00-00 00:00:00)"));
$isValid = false;
} else {
$result = Application_Common_DateHelper::checkDateTimeRangeForSQL($d['sp_criteria_extra']);
if (!$result["success"]) {
// check for if it is in valid range( 1753-01-01 ~ 12/31/9999 )
$element->addError($result["errMsg"]);
$isValid = false;
}
}
}
}
} elseif ($column->getType() == PropelColumnTypes::INTEGER &&
$d['sp_criteria_field'] != 'owner_id') {
if (!is_numeric($d['sp_criteria_value'])) {

View File

@ -51,7 +51,10 @@ class Application_Model_Block implements Application_Model_LibraryEditable
"ends with" => Criteria::ILIKE,
"is greater than" => Criteria::GREATER_THAN,
"is less than" => Criteria::LESS_THAN,
"is in the range" => Criteria::CUSTOM);
"is in the range" => Criteria::CUSTOM,
"before" => Criteria::CUSTOM,
"after" => Criteria::CUSTOM,
"between" => Criteria::CUSTOM);
private static $criteria2PeerMap = array(
0 => "Select criteria",
@ -179,7 +182,7 @@ class Application_Model_Block implements Application_Model_LibraryEditable
}
/**
* Get the entire block as a two dimentional array, sorted in order of play.
* Get the entire block as a two dimensional array, sorted in order of play.
* @param boolean $filterFiles if this is true, it will only return files that has
* file_exists flag set to true
* @return array
@ -273,7 +276,7 @@ SQL;
/**
* The database stores fades in 00:00:00 Time format with optional millisecond resolution .000000
* but this isn't practical since fades shouldn't be very long usuall 1 second or less. This function
* but this isn't practical since fades shouldn't be very long usually 1 second or less. This function
* will normalize the fade so that it looks like 00.000000 to the user.
**/
public function normalizeFade($fade)
@ -1188,18 +1191,30 @@ SQL;
{
// delete criteria under $p_blockId
CcBlockcriteriaQuery::create()->findByDbBlockId($this->id)->delete();
//Logging::info($p_criteriaData);
// Logging::info($p_criteriaData);
//insert modifier rows
if (isset($p_criteriaData['criteria'])) {
$critKeys = array_keys($p_criteriaData['criteria']);
for ($i = 0; $i < count($critKeys); $i++) {
foreach ($p_criteriaData['criteria'][$critKeys[$i]] as $d) {
// Logging::info($d);
$field = $d['sp_criteria_field'];
$value = $d['sp_criteria_value'];
$modifier = $d['sp_criteria_modifier'];
if (isset($d['sp_criteria_extra'])) { $extra = $d['sp_criteria_extra']; }
if (isset($d['sp_criteria_datetime_select'])) { $datetimeunit = $d['sp_criteria_datetime_select']; }
if (isset($d['sp_criteria_extra_datetime_select'])) {$extradatetimeunit = $d['sp_criteria_extra_datetime_select'];}
if ($field == 'utime' || $field == 'mtime' || $field == 'lptime') {
$value = Application_Common_DateHelper::UserTimezoneStringToUTCString($value);
// if the date isn't relative we want to convert the value to a specific UTC date
if (!(in_array($modifier,array('before','after','between')))) {
$value = Application_Common_DateHelper::UserTimezoneStringToUTCString($value);
}
else {
$value = $value . ' ' . $datetimeunit . ' ago';
// Logging::info($value);
}
}
$qry = new CcBlockcriteria();
@ -1211,10 +1226,17 @@ SQL;
if (isset($d['sp_criteria_extra'])) {
if ($field == 'utime' || $field == 'mtime' || $field == 'lptime') {
$d['sp_criteria_extra'] = Application_Common_DateHelper::UserTimezoneStringToUTCString($d['sp_criteria_extra']);
// if the date isn't relative we want to convert the value to a specific UTC date
if (!(in_array($modifier,array('before','after','between')))) {
$extra = Application_Common_DateHelper::UserTimezoneStringToUTCString($extra);
}
else {
$extra = $extra . ' ' . $extradatetimeunit. ' ago';
}
}
$qry->setDbExtra($d['sp_criteria_extra']);
$qry->setDbExtra($extra);
}
$qry->save();
}
@ -1250,7 +1272,7 @@ SQL;
}
/**
* generate list of tracks. This function saves creiteria and generate
* generate list of tracks. This function saves criteria and generate
* tracks.
* @param array $p_criteria
*/
@ -1259,7 +1281,7 @@ SQL;
$this->saveSmartBlockCriteria($p_criteria);
$insertList = $this->getListOfFilesUnderLimit();
$this->deleteAllFilesFromBlock();
// constrcut id array
// construct id array
$ids = array();
foreach ($insertList as $ele) {
$ids[] = $ele['id'];
@ -1350,6 +1372,13 @@ SQL;
return $insertList;
}
/**
* Parses each row in the database for the criteria associated with this block and renders human readable labels.
* Returns it as an array with each criteria_name and modifier_name added based upon options array lookup.
*
*/
public function getCriteria()
{
$criteriaOptions = array(
@ -1393,6 +1422,9 @@ SQL;
"is not" => _("is not"),
"starts with" => _("starts with"),
"ends with" => _("ends with"),
"before" => _("before"),
"after" => _("after"),
"between" => _("between"),
"is" => _("is"),
"is not" => _("is not"),
"is greater than" => _("is greater than"),
@ -1453,9 +1485,9 @@ SQL;
//data should already be in UTC, do we have to do anything special here anymore?
if ($column->getType() == PropelColumnTypes::TIMESTAMP) {
$spCriteriaValue = $criteria['value'];
if (isset($criteria['extra'])) {
$spCriteriaExtra = $criteria['extra'];
}
@ -1501,6 +1533,28 @@ SQL;
} elseif ($spCriteriaModifier == "is in the range") {
$spCriteriaValue = "$spCriteria >= '$spCriteriaValue' AND $spCriteria <= '$spCriteriaExtra'";
}
elseif ($spCriteriaModifier == "before") {
// need to pull in the current time and subtract the value or figure out how to make it relative
$relativedate = new DateTime($spCriteriaValue);
$dt = $relativedate->format(DateTime::ISO8601);
$spCriteriaValue = "$spCriteria <= '$dt'";
// Logging::info($spCriteriaValue);
}
elseif ($spCriteriaModifier == "after") {
$relativedate = new DateTime($spCriteriaValue);
$dt = $relativedate->format(DateTime::ISO8601);
$spCriteriaValue = "$spCriteria >= '$dt'";
// Logging::info($spCriteriaValue);
} elseif ($spCriteriaModifier == "between") {
$fromrelativedate = new DateTime($spCriteriaValue);
$fdt = $fromrelativedate->format(DateTime::ISO8601);
// Logging::info($fdt);
$torelativedate = new DateTime($spCriteriaExtra);
$tdt = $torelativedate->format(DateTime::ISO8601);
// Logging::info($tdt);
$spCriteriaValue = "$spCriteria >= '$fdt' AND $spCriteria <= '$tdt'";
}
$spCriteriaModifier = self::$modifier2CriteriaMap[$spCriteriaModifier];
@ -1574,7 +1628,7 @@ SQL;
}
public static function organizeSmartPlaylistCriteria($p_criteria)
{
$fieldNames = array('sp_criteria_field', 'sp_criteria_modifier', 'sp_criteria_value', 'sp_criteria_extra');
$fieldNames = array('sp_criteria_field', 'sp_criteria_modifier', 'sp_criteria_value', 'sp_criteria_extra', 'sp_criteria_datetime_select', 'sp_criteria_extra_datetime_select');
$output = array();
foreach ($p_criteria as $ele) {

View File

@ -1023,7 +1023,7 @@ SQL;
$content_count = Application_Model_ShowInstance::getContentCount(
$p_start, $p_end);
$isFull = Application_Model_ShowInstance::getIsFull($p_start, $p_end);
$hasAutoPlaylist = Application_Model_ShowInstance::getShowHasAutoplaylist($p_start, $p_end);
$displayTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone());
$utcTimezone = new DateTimeZone("UTC");
$now = new DateTime("now", $utcTimezone);
@ -1064,6 +1064,11 @@ SQL;
} else {
$options["show_partial_filled"] = true;
}
if (array_key_exists($show['instance_id'], $hasAutoPlaylist)) {
$options["show_has_auto_playlist"] = true;
} else {
$options["show_has_auto_playlist"] = false;
}
$event = array();

View File

@ -237,8 +237,6 @@ SQL;
$ts = intval($this->_showInstance->getDbLastScheduled("U")) ? : 0;
$id = $this->_showInstance->getDbId();
$lastid = $this->getLastAudioItemId();
// Logging::info("The last id is $lastid");
$scheduler = new Application_Model_Scheduler($checkUserPerm);
$scheduler->scheduleAfter(
array(array("id" => $lastid, "instance" => $id, "timestamp" => $ts)),
@ -290,6 +288,7 @@ SQL;
}
private function checkToDeleteShow($showId)
{
//UTC DateTime object
$showsPopUntil = Application_Model_Preference::GetShowsPopulatedUntil();
@ -556,6 +555,32 @@ SQL;
return $isFilled;
}
public static function getShowHasAutoplaylist($p_start, $p_end)
{
$con = Propel::getConnection(CcShowInstancesPeer::DATABASE_NAME);
$con->beginTransaction();
try {
// query the show instances to find whether a show instance has an autoplaylist
$showInstances = CcShowInstancesQuery::create()
->filterByDbEnds($p_end->format(DEFAULT_TIMESTAMP_FORMAT), Criteria::LESS_THAN)
->filterByDbStarts($p_start->format(DEFAULT_TIMESTAMP_FORMAT), Criteria::GREATER_THAN)
->leftJoinCcShow()
->where('CcShow.has_autoplaylist = ?', 'true')
->find($con);
$hasAutoplaylist = array();
foreach ($showInstances->toArray() as $ap) {
$hasAutoplaylist[$ap['DbId']] = true;
}
return $hasAutoplaylist;
}
catch (Exception $e) {
$con->rollback();
Logging::info("Couldn't query show instances for calendar to find which had autoplaylists");
Logging::info($e->getMessage());
}
}
public function showEmpty()
{
$sql = <<<SQL

View File

@ -67,7 +67,7 @@
<dd id='sp_criteria-element' class='criteria-element'>
<a class='btn btn-small btn-new' id='criteria_add'><i class='icon-white icon-plus'></i><?php echo(_("New Criteria")); ?></a>
<?php for ($i = 0; $i < $this->criteriasLength; $i++) {
<?php for ($i = 0; $i < $this->criteriasLength; $i++) {
// modRowMap holds the number of modifier rows for each criteria element
// i.e. if we have 'Album contains 1' and 'Album contains 2' the modRowMap
// for Album is 2
@ -93,13 +93,18 @@
echo 'style=display:none';
} ?> >
<?php echo $this->element->getElement("sp_criteria_field_".$i."_".$j) ?>
<?php echo $this->element->getElement("sp_criteria_modifier_".$i."_".$j) ?>
<?php echo $this->element->getElement("sp_criteria_modifier_".$i."_".$j) /* @todo finish this */?>
<?php echo $this->element->getElement("sp_criteria_value_".$i."_".$j) ?>
<span class='sp_text_font' id="datetime_select" <?php echo $this->element->getElement("sp_criteria_datetime_select_".$i."_".$j)->getAttrib("disabled") == "disabled"?'style="display:none;"':""?>><?php echo $this->element->getElement('sp_criteria_datetime_select_'.$i."_".$j) ?><?php echo _(" ago "); ?></span>
<a class='btn btn-small btn-new' id='modifier_add_<?php echo $i ?>'>
<i class='icon-white icon-plus'></i><?php echo(_("New Modifier")); ?>
</a>
<span class='sp_text_font' id="extra_criteria" <?php echo $this->element->getElement("sp_criteria_extra_".$i."_".$j)->getAttrib("disabled") == "disabled"?'style="display:none;"':""?>><?php echo _(" to "); ?><?php echo $this->element->getElement('sp_criteria_extra_'.$i."_".$j) ?></span>
<span class='sp_text_font' id="extra_datetime_select" <?php echo $this->element->getElement("sp_criteria_extra_datetime_select_".$i."_".$j)->getAttrib("disabled") == "disabled"?'style="display:none;"':""?>><?php echo $this->element->getElement('sp_criteria_extra_datetime_select_'.$i."_".$j) ?><?php echo _(" ago "); ?></span>
<a style='margin-right:3px' class='btn btn-small btn-danger' id='criteria_remove_<?php echo $i ?>'>
<i class='icon-white icon-remove spl-no-r-margin'></i>
</a>
@ -119,35 +124,6 @@
<?php } ?>
</dd>
<?php if ($this->showPoolCount) { ?>
<div class='sp_text_font sp_text_font_bold'>
<span id='sp_pool_count' class='sp_text_font sp_text_font_bold'>
<?php
if ($this->poolCount > 1) {
echo $this->poolCount;
?>
<?php echo _("files meet the criteria")?>
</span>
<span class='checked-icon sp-checked-icon' id='sp_pool_count_icon'></span>
<?php
} else if ($this->poolCount == 1) {
echo $this->poolCount;
?>
<?php echo _("file meets the criteria")?>
</span>
<span class='checked-icon sp-checked-icon' id='sp_pool_count_icon'></span>
<?php
} else {
?>
0 <?php echo " "._("files meet the criteria")?>
</span>
<span class='sp-warning-icon' id='sp_pool_count_icon'></span>
<?php
}
?>
</div>
<?php } ?>
</dl>
</form>

View File

@ -1,9 +1,12 @@
<?php
$items = $this->contents;
$isStaticSmartBlock = ($this->obj instanceof Application_Model_Block && $this->obj->isStatic());
$isSmartBlock = ($this->obj instanceof Application_Model_Block);
$isPlaylist = ($this->obj instanceof Application_Model_Playlist);
if (count($items) && ($isStaticSmartBlock || $isPlaylist)) : ?>
if (count($items) && ($isSmartBlock || $isPlaylist)) : ?>
<?php $i = 0; ?>
<?php if (!($this->obj->isStatic())) {
echo _("</br>This is only a preview of possible content generated by the smart block based upon the above criteria.");}
?>
<?php foreach($items as $item) :
$staticBlock = null;
$nextFileUrl = null;
@ -120,6 +123,7 @@ if (($i < count($items) -1) && ($items[$i+1]['type'] == 0)) {
}
echo $this->partial('playlist/set-fade.phtml', $vars);
?>
</div>
<?php endif; ?>

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

View File

@ -734,11 +734,12 @@ input.input_text.sp_extra_input_text{
.sp_text_font{
font-size: 13px;
font-family: Helvetica, Arial, sans-serif;
color: #5B5B5B;
color: #FFFFFF;
}
.sp_text_font_bold{
font-weight: bold;
color: white;
}
.sp-ui-button-icon-only {
@ -782,6 +783,8 @@ input.input_text.sp_extra_input_text{
.sp-closed{
border-width: 0 0 0 !important;
}
/***** SMART BLOCK SPECIFIC STYLES END *****/
label {
@ -2275,6 +2278,10 @@ span.errors.sp-errors{
background:url(images/icon_link.png) no-repeat 0 0;
margin-top: 0px !important;
}
.small-icon.autoplaylist {
background:url(images/icon_alert_cal_autoplaylist.png) no-repeat 0 0;
}
.small-icon.recording {
background:url(images/icon_record.png) no-repeat 0 0;
margin-top: 0px !important;

View File

@ -19,6 +19,8 @@ function setSmartBlockEvents() {
appendAddButton();
appendModAddButton();
removeButtonCheck();
disableAndHideDateTimeDropdown(newRowVal);
} else {
@ -34,6 +36,8 @@ function setSmartBlockEvents() {
appendAddButton();
appendModAddButton();
removeButtonCheck();
disableAndHideDateTimeDropdown(newRowVal);
}
});
@ -66,6 +70,8 @@ function setSmartBlockEvents() {
newRowVal.val('');
newRowExtra.val('');
disableAndHideExtraField(newRowVal);
disableAndHideDateTimeDropdown(newRowVal);
disableAndHideExtraDateTimeDropdown(newRowVal);
sizeTextBoxes(newRowVal, 'sp_extra_input_text', 'sp_input_text');
//remove the 'criteria add' button from new modifier row
@ -89,18 +95,28 @@ function setSmartBlockEvents() {
var item_to_hide;
var prev;
var index;
//remove error message from current row, if any
var error_element = curr.find('span[class="errors sp-errors"]');
if (error_element.is(':visible')) {
error_element.remove();
}
/* In the case that there is only one element we need to remove the
* date_select drop down.
*/
if (count == 0) {
disableAndHideDateTimeDropdown(curr.find(':first-child'), index);
disableAndHideExtraDateTimeDropdown(curr.find(':first-child'),index);
disableAndHideExtraField(curr.find(':first-child'),index);
}
/* assign next row to current row for all rows below and including
* the row getting removed
*/
for (var i=0; i<count; i++) {
index = getRowIndex(curr);
var criteria = next.find('[name^="sp_criteria_field"]').val();
@ -109,10 +125,12 @@ function setSmartBlockEvents() {
var modifier = next.find('[name^="sp_criteria_modifier"]').val();
populateModifierSelect(curr.find('[name^="sp_criteria_field"]'), false);
curr.find('[name^="sp_criteria_modifier"]').val(modifier);
var criteria_value = next.find('[name^="sp_criteria_value"]').val();
curr.find('[name^="sp_criteria_value"]').val(criteria_value);
/* if current and next row have the extra criteria value
* (for 'is in the range' modifier), then assign the next
* extra value to current and remove that element from
@ -124,7 +142,7 @@ function setSmartBlockEvents() {
var criteria_extra = next.find('[name^="sp_criteria_extra"]').val();
curr.find('[name^="sp_criteria_extra"]').val(criteria_extra);
disableAndHideExtraField(next.find(':first-child'), getRowIndex(next));
/* if only the current row has the extra criteria value,
* then just remove the current row's extra criteria element
*/
@ -142,6 +160,54 @@ function setSmartBlockEvents() {
curr.find('[name^="sp_criteria_extra"]').val(criteria_extra);
}
/* if current and next row have the date_time_select_criteria visible
* then show the current and it from the next row
*/
if (curr.find('[name^="sp_criteria_datetime_select"]').attr("disabled") != "disabled"
&& next.find('#datetime_select').is(':visible')) {
var criteria_datetime = next.find('[name^="sp_criteria_datetime_select"]').val();
curr.find('[name^="sp_criteria_datetime_select"]').val(criteria_datetime);
disableAndHideDateTimeDropdown(next.find('first-child'), getRowIndex(next));
/* if only the current row has the extra criteria value,
* then just remove the current row's extra criteria element
*/
} else if (curr.find('[name^="sp_criteria_datetime_select"]').attr("disabled") != "disabled"
&& next.find('#datetime_select').not(':visible')) {
disableAndHideDateTimeDropdown(curr.find(':first-child'), index);
/* if only the next row has date_time_select then just enable it on the current row
*/
} else if (next.find('#datetime_select').is(':visible')) {
criteria_datetime = next.find('[name^="sp_criteria_datetime_select"]').val();
enableAndShowDateTimeDropdown(curr.find(':first-child'), index);
curr.find('[name^="sp_criteria_datetime_select"]').val(criteria_datetime);
}
/* if current and next row have the extra_date_time_select_criteria visible
* then show the current and it from the next row
*/
if (curr.find('[name^="sp_criteria_extra_datetime_select"]').attr("disabled") != "disabled"
&& next.find('#extra_datetime_select').is(':visible')) {
var extra_criteria_datetime = next.find('[name^="sp_criteria_extra_datetime_select"]').val();
curr.find('[name^="sp_criteria_extra_datetime_select"]').val(extra_criteria_datetime);
disableAndHideExtraDateTimeDropdown(next.find('first-child'), getRowIndex(next));
/* if only the current row has the extra criteria value,
* then just remove the current row's extra criteria element
*/
} else if (curr.find('[name^="sp_criteria_extra_datetime_select"]').attr("disabled") != "disabled"
&& next.find('#extra_datetime_select').not(':visible')) {
disableAndHideExtraDateTimeDropdown(curr.find(':first-child'), index);
/* if only the next row has date_time_select then just enable it on the current row
*/
} else if (next.find('#datetime_select').is(':visible')) {
criteria_datetime = next.find('[name^="sp_criteria_extra_datetime_select"]').val();
enableAndShowExtraDateTimeDropdown(curr.find(':first-child'), index);
curr.find('[name^="sp_criteria_extra_datetime_select"]').val(criteria_datetime);
}
/* determine if current row is a modifier row
* if it is, make the criteria select invisible
*/
@ -167,13 +233,18 @@ function setSmartBlockEvents() {
*/
item_to_hide = list.find('div:visible:last');
item_to_hide.children().attr('disabled', 'disabled');
item_to_hide.find('[name^="sp_criteria_datetime_select"]').attr('disabled', 'disabled');
item_to_hide.find('[name^="sp_criteria_extra"]').attr('disabled', 'disabled');
item_to_hide.find('[name^="sp_criteria_extra_datetime_select"]').attr('disabled', 'disabled');
if (item_to_hide.find('select[name^="sp_criteria_field"]').hasClass('sp-invisible')) {
item_to_hide.find('select[name^="sp_criteria_field"]').removeClass('sp-invisible');
}
item_to_hide.find('[name^="sp_criteria_field"]').val(0).end()
.find('[name^="sp_criteria_modifier"]').val(0).end()
.find('[name^="sp_criteria_datetime_select"]').end()
.find('[name^="sp_criteria_value"]').val('').end()
.find('[name^="sp_criteria_extra"]').val('');
.find('[name^="sp_criteria_extra"]').val('')
.find('[name^="sp_criteria_extra_datetime_select"]').end();
sizeTextBoxes(item_to_hide.find('[name^="sp_criteria_value"]'), 'sp_extra_input_text', 'sp_input_text');
item_to_hide.hide();
@ -241,6 +312,8 @@ function setSmartBlockEvents() {
// disable extra field and hide the span
disableAndHideExtraField($(this), index);
disableAndHideDateTimeDropdown($(this), index);
disableAndHideExtraDateTimeDropdown($(this),index);
populateModifierSelect(this, true);
});
@ -248,12 +321,30 @@ function setSmartBlockEvents() {
form.find('select[id^="sp_criteria_modifier"]').live("change", function(){
var criteria_value = $(this).next(),
index_num = getRowIndex($(this).parent());
if ($(this).val() == 'is in the range') {
if ($(this).val().match('before|after')) {
enableAndShowDateTimeDropdown(criteria_value, index_num);
console.log($(this).val());
}
else {
disableAndHideDateTimeDropdown(criteria_value, index_num);
disableAndHideExtraDateTimeDropdown(criteria_value,index_num);
}
if ($(this).val().match('is in the range')) {
enableAndShowExtraField(criteria_value, index_num);
} else {
disableAndHideExtraField(criteria_value, index_num);
}
if ($(this).val().match('between')) {
enableAndShowExtraField(criteria_value, index_num);
enableAndShowDateTimeDropdown(criteria_value,index_num);
enableAndShowExtraDateTimeDropdown(criteria_value,index_num);
}
else {
disableAndHideExtraDateTimeDropdown(criteria_value,index_num);
}
});
setupUI();
@ -367,8 +458,8 @@ function setupUI() {
*/
var sortable = activeTab.find('.spl_sortable'),
plContents = sortable.children(),
shuffleButton = activeTab.find('button[name="shuffle_button"], #pl-bl-clear-content'),
generateButton = activeTab.find('button[name="generate_button"], #pl-bl-clear-content'),
shuffleButton = activeTab.find('button[name="shuffle_button"]'),
generateButton = activeTab.find('button[name="generate_button"]'),
fadesButton = activeTab.find('#spl_crossfade, #pl-bl-clear-content');
if (!plContents.hasClass('spl_empty')) {
@ -385,11 +476,12 @@ function setupUI() {
if (playlist_type == "0") {
shuffleButton.removeAttr("disabled");
generateButton.removeAttr("disabled");
generateButton.html($.i18n._("Generate"));
fadesButton.removeAttr("disabled");
//sortable.children().show();
} else {
shuffleButton.attr("disabled", "disabled");
generateButton.attr("disabled", "disabled");
generateButton.html($.i18n._("Preview"));
fadesButton.attr("disabled", "disabled");
//sortable.children().hide();
}
@ -439,8 +531,68 @@ function setupUI() {
});
}
/* Utilizing jQuery this function finds the #datetime_select element on the given row
* and shows the criteria drop-down
*/
function enableAndShowDateTimeDropdown(valEle, index) {
console.log('datetime show');
var spanDatetime = valEle.nextAll("#datetime_select");
spanDatetime.children('#sp_criteria_datetime_select_'+index).removeAttr("disabled");
spanDatetime.children('#sp_criteria_extra_datetime_select_'+index).removeAttr("disabled");
spanDatetime.show();
//make value input smaller since we have extra element now
var criteria_val = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_val, 'sp_input_text', 'sp_extra_input_text');
}
/* Utilizing jQuery this function finds the #datetime_select element on the given row
* and hides the datetime criteria drop-down
*/
function disableAndHideDateTimeDropdown(valEle, index) {
console.log('datetime hide');
var spanDatetime = valEle.nextAll("#datetime_select");
spanDatetime.children('#sp_criteria_datetime_select_'+index).val("").attr("disabled", "disabled");
spanDatetime.hide();
//make value input larger since we don't have extra field now
var criteria_value = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_value, 'sp_extra_input_text', 'sp_input_text');
}
/* Utilizing jQuery this function finds the #datetime_select element on the given row
* and shows the criteria drop-down
*/
function enableAndShowExtraDateTimeDropdown(valEle, index) {
console.log('datetime show');
var spanDatetime = valEle.nextAll("#extra_datetime_select");
spanDatetime.children('#sp_criteria_extra_datetime_select_'+index).removeAttr("disabled");
spanDatetime.show();
//make value input smaller since we have extra element now
var criteria_val = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_val, 'sp_input_text', 'sp_extra_input_text');
}
/* Utilizing jQuery this function finds the #datetime_select element on the given row
* and hides the datetime criteria drop-down
*/
function disableAndHideExtraDateTimeDropdown(valEle, index) {
console.log('datetime hide');
var spanDatetime = valEle.nextAll("#extra_datetime_select");
spanDatetime.children('#sp_criteria_extra_datetime_select_'+index).val("").attr("disabled", "disabled");
spanDatetime.hide();
//make value input larger since we don't have extra field now
var criteria_value = $('#sp_criteria_value_'+index);
sizeTextBoxes(criteria_value, 'sp_extra_input_text', 'sp_input_text');
}
function enableAndShowExtraField(valEle, index) {
var spanExtra = valEle.nextAll("#extra_criteria");
console.log('shown');
spanExtra.children('#sp_criteria_extra_'+index).removeAttr("disabled");
spanExtra.show();
@ -453,6 +605,7 @@ function disableAndHideExtraField(valEle, index) {
var spanExtra = valEle.nextAll("#extra_criteria");
spanExtra.children('#sp_criteria_extra_'+index).val("").attr("disabled", "disabled");
spanExtra.hide();
console.log('hidden');
//make value input larger since we don't have extra field now
var criteria_value = $('#sp_criteria_value_'+index);
@ -484,7 +637,15 @@ function populateModifierSelect(e, popAllMods) {
.attr('value', key)
.text(value));
});
} else {
}
else if(criteria_type == 'd') {
$.each(dateTimeCriteriaOptions, function(key, value){
$(div).append($('<option></option>')
.attr('value', key)
.text(value));
});
}
else {
$.each(numericCriteriaOptions, function(key, value){
$(div).append($('<option></option>')
.attr('value', key)
@ -610,9 +771,9 @@ var criteriaTypes = {
"description" : "s",
"artist_name" : "s",
"encoded_by" : "s",
"utime" : "n",
"mtime" : "n",
"lptime" : "n",
"utime" : "d",
"mtime" : "d",
"lptime" : "d",
"genre" : "s",
"isrc_number" : "s",
"label" : "s",
@ -647,3 +808,15 @@ var numericCriteriaOptions = {
"is less than" : $.i18n._("is less than"),
"is in the range" : $.i18n._("is in the range")
};
var dateTimeCriteriaOptions = {
"0" : $.i18n._("Select modifier"),
"before" : $.i18n._("before"),
"after" : $.i18n._("after"),
"between" : $.i18n._("between"),
"is" : $.i18n._("is"),
"is not" : $.i18n._("is not"),
"is greater than" : $.i18n._("is greater than"),
"is less than" : $.i18n._("is less than"),
"is in the range" : $.i18n._("is in the range")
};

View File

@ -214,7 +214,13 @@ function eventRender(event, element, view) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon linked"></span><span class="small-icon show-empty"></span>');
} else {
// in theory a linked show shouldn't have an automatic playlist so adding this here
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon autoplaylist"></span><span class="small-icon show-empty"></span>');
}
else {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon show-empty"></span>');
@ -224,7 +230,11 @@ function eventRender(event, element, view) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon linked"></span><span class="small-icon show-partial-filled"></span>');
} else {
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon autoplaylist"></span><span class="small-icon show-partial-filled"></span>');
} else {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon show-partial-filled"></span>');
@ -234,26 +244,38 @@ function eventRender(event, element, view) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon linked"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-time")
.before('<span class="small-icon autoplaylist"></span>');
}
}
} else if (view.name === 'month') {
if (event.show_empty === 1) {
if (event.linked) {
$(element)
$(element)
.find(".fc-event-title")
.after('<span class="small-icon linked"></span><span title="'+$.i18n._("Show is empty")+'" class="small-icon show-empty"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-title")
.after('<span title="'+$.i18n._("Show has an automatic playlist")+'"class="small-icon autoplaylist"></span><span title="'+$.i18n._("Show is empty")+'" class="small-icon show-empty"></span>');
} else {
$(element)
$(element)
.find(".fc-event-title")
.after('<span title="'+$.i18n._("Show is empty")+'" class="small-icon show-empty"></span>');
}
} else if (event.show_partial_filled === true) {
if (event.linked) {
$(element)
.find(".fc-event-title")
.after('<span class="small-icon linked"></span><span title="'+$.i18n._("Show is partially filled")+'" class="small-icon show-partial-filled"></span>');
.find(".fc-event-title")
.after('<span class="small-icon linked"></span><span title="' + $.i18n._("Show is partially filled") + '" class="small-icon show-partial-filled"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-title")
.after('<span title="'+$.i18n._("Show has an automatic playlist")+'"class="small-icon autoplaylist"></span><span title="'+$.i18n._("Show is partially filled")+'" class="small-icon show-partial-filled"></span>');
} else {
$(element)
$(element)
.find(".fc-event-title")
.after('<span title="'+$.i18n._("Show is partially filled")+'" class="small-icon show-partial-filled"></span>');
}
@ -262,6 +284,10 @@ function eventRender(event, element, view) {
$(element)
.find(".fc-event-title")
.after('<span class="small-icon linked"></span>');
} else if (event.show_has_auto_playlist === true) {
$(element)
.find(".fc-event-title")
.after('<span class="small-icon autoplaylist"></span>');
}
}
}

View File

@ -1,8 +1,8 @@
The Listener Stats page on the System menu shows graphs of listener connections to the configured streaming servers for the selected date and time range.  On the right side, a green **Status** indicator shows **OK** if the connection to the streaming server is active.
The Listener Stats page on the Analytics menu shows graphs of listener connections to the configured streaming servers for the selected date and time range.  On the right side, a green **Status** indicator shows **OK** if the connection to the streaming server is active.
![](static/Screenshot483-Listener_Stats.png)
If the status indicator is red, check that the **Admin User** and **Admin Password** settings are correct under **Additional Options** for the named mount point, such as *airtime\_128*, on the **Streams** page of the **System** menu.
If the status indicator is red, check that the **Admin User** and **Admin Password** settings are correct under **Additional Options** for the named mount point, such as *airtime\_128*, on the **Streams** page of the **Settings** menu.
By default, statistics for the last 24 hours of streaming are shown. To change this date and time range, click the calendar and clock icons in the lower left corner of the page, then click the magnifying glass icon.

View File

@ -1,26 +1,27 @@
Here's how you can use Airtime to manage your broadcasts. Chapter names in this book are shown in *italics*, to help you find the details of each step (if you need to read more).
Here's how you can use Libretime to manage your broadcasts.
Chapter names in this book are shown in *italics*, to help you find the details of each step (if you need to read more).
1. Log in to your Airtime server with your **Username** and **Password** (*Getting started*).
1. Log in to your Libretime server with your **Username** and **Password** (*Getting started*).
<img src="static/Screenshot559-Log_in.png" alt="Log in" width="740" />
2. Add your files to the Airtime library by clicking **Add media** on the main menu, then click the **Add files** button. You can drag and drop your files into this window too. Then click the **Start upload** button (*Add media*).
2. Add your files to the Libretime library by clicking **Upload** on the left menu, then click on the the **Drop files here or click to browse your computer.** area. You can drag and drop your files into this window too. The upload will start as soon as you drop a file (*Add media*).
<img src="static/Screenshot557-Select_files_a7GflUi.png" alt="Select files" width="740" />
3. Create a show by clicking **Calendar** on the main menu, and then clicking the **+ Show** button (*Calendar*). Only admins and program managers can add shows (<span style="font-style: italic;">Users</span>).
3. Create a show by clicking **Calendar** on the main menu, and then clicking the **New Show** button (*Calendar*). Only admins and program managers can add shows (<span style="font-style: italic;">Users</span>).
<img src="static/Screenshot558-Add_Show.png" alt="Add Show" width="740" />
4. Set a name for your show in the **What** section of the box, and a date and time in the **When** section, then click the **+ Add this show** button (*Calendar*).
4. Set a name for your show in the **What** section of the box, and a date and time in the **When** section, then click the **+ Add this show** button at the end of the page (*Calendar*).
<img src="static/Screenshot560-Show_when.png" alt="Show when" width="740" />
5. Add media to the new show by clicking your show in the **Calendar** and selecting **Add / Remove Content** (*Calendar*). Or if the show has already started, use the **Add / Remove Content** button on the Now Playing page (*Now Playing*).
5. Add media to the new show by clicking the name of your show in the **Calendar** and selecting **Schedule Tracks** (*Calendar*).
<img src="static/Screenshot561-Add_show_content.png" alt="Add show content" width="490" />
<img src="static/Screenshot561-Add_show_content.png" alt="Add show content" width="740" />
6. Click media in the search results on the left side of the pop-up window which will appear, and drag it into your show on the right side (*Now Playing*).
6. Search for the media in the left side of the pop-up window which will appear, and drag it into your show on the right side (*Now Playing*).
<img src="static/Screenshot562-Drag_show_content.png" alt="Drag show content" width="740" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,34 +1,17 @@
The following instructions assume that you have root access (**sudo** on most
distributions) to a GNU/Linux server, and are familiar with basic command line
tasks. Experienced system administrators may prefer to skip to
[Expert install](../expert-install/) in the appendix of manual after having
prepared a server with the needed dependencies show below.
The following instructions assume that you have root access (**sudo** on most distributions) to a GNU/Linux server, and are familiar with basic command line tasks. Experienced system administrators may prefer to skip to [Expert install](../expert-install/) in the appendix of manual after having prepared a server with the needed dependencies show below.
The recommended Libretime server platform is Debian 9 'stretch'.
Ubuntu 16.04 LTS 'Xenial Xerus' and CentOS 7 are also supported. Users of
other GNU/Linux distributions may be able to adapt these instructions to suit
their needs.
The recommended Libretime server platform is Ubuntu 16.04 LTS (Xenial Xerus), or Ubuntu 14.04.5 LTS (Trusty Tahr). Debian 9 'stretch' and CentOS 7 are also supported. Users of other GNU/Linux distributions may be able to adapt these instructions to suit their needs.
The server should have at least a 1GHz processor and 1GB of RAM, preferably
2GB RAM or more. If you are using a desktop environment and web browser directly
on the server you should install at least 2GB RAM, to avoid swapping to disk.
The server should have at least a 1GHz processor and 1GB of RAM, preferably 2GB RAM or more. If you are using a desktop environment and web browser directly on the server you should install at least 2GB RAM, to avoid swapping to disk.
The LibreTime installation does not use much disk space, but you should allow
plenty of storage capacity for the Airtime library. A hot-swap RAID array is
recommended for media storage, in case of disk failure. You should also consider
a UPS or other battery-powered system to offer some protection against
short-term power failures.
The LibreTime installation does not use much disk space, but you should allow plenty of storage capacity for the Airtime library. A hot-swap RAID array is recommended for media storage, in case of disk failure. You should also consider a UPS or other battery-powered system to offer some protection against short-term power failures.
LibreTime depends on infrastructure and services that need to be configured
properly for it to run smoothly. This chapter will go through the individual
parts of a LibreTime install and help you assess how you need to manage them.
LibreTime depends on infrastructure and services that need to be configured properly for it to run smoothly. This chapter will go through the individual parts of a LibreTime install and help you assess how you need to manage them.
Firewall
--------
LibreTime should only be run on a Server behind a firewall. This can either be
a dedicated firewall in the network (like on some cloud providers) or a local
firewall running iptables (as you would use on a root server or a local machine).
LibreTime should only be run on a Server behind a firewall. This can either be a dedicated firewall in the network (like on some cloud providers) or a local firewall running iptables (as you would use on a root server or a local machine).
Setting up a local firewall is done differently on all the supported distros.
@ -36,82 +19,55 @@ Setting up a local firewall is done differently on all the supported distros.
* [Ubuntu](https://help.ubuntu.com/lts/serverguide/firewall.html)
* [FirewallD](http://www.firewalld.org/) (CentOS)
There is a vast amount of ways to configure your network, firewall included.
Choose the way that best fits your deploy and dont internal expose parts of your
LibreTime install on the network.
There is a vast amount of ways to configure your network, firewall included. Choose the way that best fits your deploy and dont internal expose parts of your LibreTime install on the network.
The following ports are relevant to the deploy and need to be opened to varying
degrees.
The following ports are relevant to the deploy and need to be opened to varying degrees.
| Port | Description |
| ---- | ----------- |
| 80 | Default unsecure web port. Needs to be open for the webserver to serve the LibreTime webinterface or if you enable TLS a redirect to the secure web port.|
| 80 | Default unsecure web port. Needs to be open for the webserver to serve the LibreTime webinterface or if you enable TLS a redirect to the secure web port.|
| 443 | Default secure web port. This is where your LibreTime webinterface lives if you choose to configure TLS.|
| 8000 | Main Icecast instance. This is where your listeners connect if you plan on using your LibreTime server to directly serve such connections. You can also configure external Icecast or ShoutCast instances for this later.|
| 8001 and 8002 | Master and Show source input ports. Only open these ports if you plan on letting anyone use these features. You might want to consider usinga fixed IP if you use the master source for studio connections on port 8001 and only allow DJ to connect over a VPN link or similar depending your security needs.|
The remaining parts of LibreTime might open additional ports that should not
be accessible from any untrusted networks. You should consider how to configure
their firewall access individually once you configure them.
The remaining parts of LibreTime might open additional ports that should not be accessible from any untrusted networks. You should consider how to configure their firewall access individually once you configure them.
PostgreSQL
----------
You should set up PostgreSQL properly according to the instructions for your
distro. Distro provided packages are fine for LibreTime as it does not have
any specific version dependencies.
You should set up PostgreSQL properly according to the instructions for your distro. Distro provided packages are fine for LibreTime as it does not have any specific version dependencies.
* [Debian](https://wiki.debian.org/PostgreSql)
* [Ubuntu](https://help.ubuntu.com/lts/serverguide/postgresql.html)
* [CentOS](https://www.postgresql.org/download/linux/redhat/)
You should restrict access to the database and create a user specific to
LibreTime. The default installer script create a user with the password
`airtime` which is far to open and should be replaced with a self provided
user and a secure password. See the PostgreSQL docs on [roles](https://www.postgresql.org/docs/7.0/static/newuser.htm)
and [databases](https://www.postgresql.org/docs/10/static/managing-databases.html).
You should restrict access to the database and create a user specific to LibreTime. The default installer script create a user with the password`airtime` which is far to open and should be replaced with a self provided user and a secure password. See the PostgreSQL docs on [roles](https://www.postgresql.org/docs/7.0/static/newuser.htm) and [databases](https://www.postgresql.org/docs/10/static/managing-databases.html).
Please take care to ensure that your database is properly backed up at regular
intervals. LibreTime uses the database to store your schedule, settings, playout
history and more. See [backing up the server](../backing-up-the-server) for
more information on this.
Please take care to ensure that your database is properly backed up at regular intervals. LibreTime uses the database to store your schedule, settings, playout history and more. See [backing up the server](../backing-up-the-server) for more information on this.
RabbitMQ
--------
LibreTime uses RabbitMQ interfacing various components like the main interface
and parts of the system like the audio analyzer and podcast downloader as well
as the playout system through a common message queue.
Again, the version provided by your distro will probably work fine for all
LibreTime is concerned.
LibreTime uses RabbitMQ interfacing various components like the main interface and parts of the system like the audio analyzer and podcast downloader as well as the playout system through a common message queue.
Again, the version provided by your distro will probably work fine for all LibreTime is concerned.
* [Debian/Ubuntu](https://www.rabbitmq.com/install-debian.html)
* [CentOS](https://www.rabbitmq.com/install-rpm.html)
The install script sets `airtime` as the password on the default user. It is
recommended to provide your own user using a secure password.
The install script sets `airtime` as the password on the default user. It is recommended to provide your own user using a secure password.
You can use [`rabbitmqctl`](https://www.rabbitmq.com/man/rabbitmqctl.1.man.html)
or the [Management Plugin](https://www.rabbitmq.com/management.html) to manage
your RabbitMQ users.
You can use [`rabbitmqctl`](https://www.rabbitmq.com/man/rabbitmqctl.1.man.html) or the [Management Plugin](https://www.rabbitmq.com/management.html) to manage your RabbitMQ users.
There is no state in the RabbitMQ system that you need to backup but you want
to ensure that your RabbitMQ configuration and user permissions are safe.
There is no state in the RabbitMQ system that you need to backup but you want to ensure that your RabbitMQ configuration and user permissions are safe.
### RabbitMQ hostname
RabbitMQ requires a fixed and resolvable hostname
(see [the docs](http://www.rabbitmq.com/ec2.html#issues-hostname)), which is
normal for a server. For a desktop or laptop machine where the hostname changes
frequently or is not resolvable, this issue may prevent RabbitMQ from starting.
When using a desktop or laptop computer with a dynamic IP address, such as an
address obtained from a wireless network, the `rabbitmq-server` daemon must
not start up before the `NetworkManager` service or `network.target`.
RabbitMQ requires a fixed and resolvable hostname (see [the docs](http://www.rabbitmq.com/ec2.html#issues-hostname)), which is
normal for a server. For a desktop or laptop machine where the hostname changes frequently or is not resolvable, this issue may prevent RabbitMQ from starting.
When using a desktop or laptop computer with a dynamic IP address, such as an address obtained from a wireless network, the `rabbitmq-server` daemon must not start up before the `NetworkManager` service or `network.target`.
You may also choose to configure RabbitMQ to only listen on the loopback
interface with a localhost node name. You can use [environment
variables](https://www.rabbitmq.com/configure.html#define-environment-variables)
or a configuration file to do this.
You may also choose to configure RabbitMQ to only listen on the loopback interface with a localhost node name. You can use [environment
variables](https://www.rabbitmq.com/configure.html#define-environment-variables) or a configuration file to do this.
See these links for more information:

View File

@ -15,60 +15,60 @@ extra_css:
pages:
- 'Home': index.md
- 'What is LibreTime': manual/index.md
- 'What is LibreTime?': manual/index.md
- 'Features': features.md
- 'F.A.Q.': faq.md
- 'Rights and Royalties': manual/rights-and-royalties/index.md
- 'Using LibreTime':
- 'On air in 60 seconds!': 'manual/on-air-in-60-seconds/index.md'
- 'Getting started': manual/getting-started/index.md
- 'The System Menu':
- 'On Air in 60 seconds!': 'manual/on-air-in-60-seconds/index.md'
- 'Getting Started': manual/getting-started/index.md
- 'The Settings Menu':
- 'General': manual/preferences/index.md
- 'Users': manual/users/index.md
- 'Media Folders': manual/media-folders/index.md
- 'Streams': manual/stream-settings/index.md
- 'Support Feedback': manual/support-feedback/index.md
- 'Status': manual/status/index.md
- 'Listener stats': manual/listener-stats/index.md
- 'The main menus':
- 'Now playing': manual/now-playing/index.md
- 'Add media': manual/add-media/index.md
- 'The Main Menus':
- 'Now Playing': manual/now-playing/index.md
- 'Add Media': manual/add-media/index.md
- 'Library': manual/library/index.md
- 'Calendar': manual/calendar/index.md
- 'History': manual/history/index.md
- 'Listen': manual/listen/index.md
- 'Analytics': manual/listener-stats/index.md
- 'Help': manual/help/index.md
- 'LibreTime in the studio':
- 'Preparing media for ingest': manual/preparing-media-for-ingest/index.md
- 'Live shows with Mixxx': manual/live-shows-with-mixxx/index.md
- 'Smartphone journalism': manual/smartphone-journalism/index.md
- 'LibreTime in the Studio':
- 'Preparing Media for Ingest': manual/preparing-media-for-ingest/index.md
- 'Live Shows with Mixxx': manual/live-shows-with-mixxx/index.md
- 'Smartphone Journalism': manual/smartphone-journalism/index.md
- 'Icecast and SHOUTcast': manual/icecast-and-shoutcast/index.md
- 'Recording shows': manual/recording-shows/index.md
- 'Recording Shows': manual/recording-shows/index.md
- 'Installation':
- 'Install': install.md
- 'Preparing the server': manual/preparing-the-server/index.md
- 'Easy setup': manual/easy-setup/index.md
- 'Automated installation': manual/automated-installation/index.md
- 'Manual installation': manual/manual-installation/index.md
- 'Setting the server time': manual/setting-the-server-time/index.md
- 'Preparing the Server': manual/preparing-the-server/index.md
- 'Easy Setup': manual/easy-setup/index.md
- 'Automated Installation': manual/automated-installation/index.md
- 'Manual Installation': manual/manual-installation/index.md
- 'Setting the Server Time': manual/setting-the-server-time/index.md
- 'Administration':
- 'The airtime-log command': manual/the-airtime-log-command/index.md
- 'Backing up the server': manual/backing-up-the-server/index.md
- 'The airtime-log Command': manual/the-airtime-log-command/index.md
- 'Backing Up the Server': manual/backing-up-the-server/index.md
- 'Upgrading': manual/upgrading/index.md
- 'Troubleshooting': manual/troubleshooting/index.md
- 'Using Monit': manual/using-monit/index.md
- 'Advanced configuration':
- 'Automated file import': manual/automated-file-import/index.md
- 'Icecast handover': manual/icecast-handover/index.md
- 'Promoting your station': manual/promoting-your-station/index.md
- 'Stream player for your website': manual/stream-player-for-your-website/index.md
- 'Exporting the schedule': manual/exporting-the-schedule/index.md
- 'Interface customization': manual/interface-customization/index.md
- 'Interface localization': manual/interface-localization/index.md
- 'Host configuration': manual/host-configuration/index.md
- 'LibreTime API authentication': manual/airtime-api-authentication/index.md
- 'Secure login with SSL or TLS': manual/secure-login-with-ssl/index.md
- 'Icecast statistics with Piwik': manual/icecast-statistics-with-piwik/index.md
- 'Advanced Configuration':
- 'Automated File Import': manual/automated-file-import/index.md
- 'Icecast Handover': manual/icecast-handover/index.md
- 'Promoting Your Station': manual/promoting-your-station/index.md
- 'Stream Player for Your Website': manual/stream-player-for-your-website/index.md
- 'Exporting the Schedule': manual/exporting-the-schedule/index.md
- 'Interface Customization': manual/interface-customization/index.md
- 'Interface Localization': manual/interface-localization/index.md
- 'Host Configuration': manual/host-configuration/index.md
- 'LibreTime API Authentication': manual/airtime-api-authentication/index.md
- 'Secure Login with SSL or TLS': manual/secure-login-with-ssl/index.md
- 'Icecast Statistics with Piwik': manual/icecast-statistics-with-piwik/index.md
- 'FreeIPA Authentication': freeipa.md
- 'Development':
- 'Testing': testing.md
@ -77,6 +77,6 @@ pages:
- 'Translating': translating.md
- 'Documentation': documentation.md
- 'Appendix':
- 'Expert install': manual/expert-install/index.md
- 'Expert Install': manual/expert-install/index.md
- 'HD Audio Models': manual/hd-audio-models/index.md
- 'About this manual': manual/about-this-manual/index.md
- 'About This Manual': manual/about-this-manual/index.md