Rename airtime_mvc/ to legacy/

This commit is contained in:
jo 2021-10-11 13:43:25 +02:00
parent f0879322c2
commit 3e18d42c8b
1316 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,109 @@
<?php
class AutoPlaylistManager {
/**
* @var int how often, in seconds, to check for and ingest new podcast episodes
*/
private static $_AUTOPLAYLIST_POLL_INTERVAL_SECONDS = 60; // 10 minutes
/**
* Check whether $_AUTOPLAYLIST_POLL_INTERVAL_SECONDS have passed since the last call to
* buildAutoPlaylist
*
* @return bool true if $_AUTOPLAYLIST_POLL_INTERVAL_SECONDS has passed since the last check
*/
public static function hasAutoPlaylistPollIntervalPassed() {
$lastPolled = Application_Model_Preference::getAutoPlaylistPollLock();
return empty($lastPolled) || (microtime(true) > $lastPolled + self::$_AUTOPLAYLIST_POLL_INTERVAL_SECONDS);
}
/**
* Find all shows with autoplaylists who have yet to have their playlists built and added to the schedule
*
*/
public static function buildAutoPlaylist() {
$autoPlaylists = static::_upcomingAutoPlaylistShows();
foreach ($autoPlaylists as $autoplaylist) {
// creates a ShowInstance object to build the playlist in from the ShowInstancesQuery Object
$si = new Application_Model_ShowInstance($autoplaylist->getDbId());
$playlistid = $si->GetAutoPlaylistId();
// call the addPlaylist to show function and don't check for user permission to avoid call to non-existant user object
$sid = $si->getShowId();
$playlistrepeat = new Application_Model_Show($sid);
$introplaylistid = Application_Model_Preference::GetIntroPlaylist();
$outroplaylistid = Application_Model_Preference::GetOutroPlaylist();
// we want to check and see if we need to repeat this process until the show is 100% scheduled
// so we create a while loop and break it immediately if repeat until full isn't enabled
// otherwise we continue to go through adding playlists, including the intro and outro if enabled
$full = false;
$repeatuntilfull = $playlistrepeat->getAutoPlaylistRepeat();
$tempPercentScheduled = 0;
$si = new Application_Model_ShowInstance($autoplaylist->getDbId());
// the intro playlist should be added exactly once
if ($introplaylistid != null) {
//Logging::info('adding intro');
$si->addPlaylistToShowStart($introplaylistid, false);
}
while(!$full) {
// we do not want to try to schedule an empty playlist
if ($playlistid != null) {
$si->addPlaylistToShow($playlistid, false);
}
$ps = $si->getPercentScheduled();
if ($ps > 100) {
$full = true;
}
elseif (!$repeatuntilfull) {
break;
}
// we want to avoid an infinite loop if all of the playlists are null
if ($playlistid == null) {
break;
}
// another possible issue would be if the show isn't increasing in length each loop
// ie if all of the playlists being added are zero lengths this could cause an infinite loop
if ($tempPercentScheduled == $ps) {
break;
}
//now reset it to the current percent scheduled
$tempPercentScheduled = $ps;
}
// the outroplaylist is added at the end, it will always overbook
// shows that have repeat until full enabled because they will
// never have time remaining for the outroplaylist to be added
// this is done outside the content loop to avoid a scenario
// where a time remaining smartblock in a outro playlist
// prevents the repeat until full from functioning by filling up the show
if ($outroplaylistid != null) {
$si->addPlaylistToShow($outroplaylistid, false);
}
$si->setAutoPlaylistBuilt(true);
}
Application_Model_Preference::setAutoPlaylistPollLock(microtime(true));
}
/**
* Find all show instances starting in the next hour with autoplaylists not yet added to the schedule
*
* @return PropelObjectCollection collection of ShowInstance objects
* that have unbuilt autoplaylists
*/
protected static function _upcomingAutoPlaylistShows() {
//setting now so that past shows aren't referenced
$now = new DateTime("now", new DateTimeZone("UTC"));
// only build playlists for shows that start up to an hour from now
$future = clone $now;
$future->add(new DateInterval('PT1H'));
return CcShowInstancesQuery::create()
->filterByDbModifiedInstance(false)
->filterByDbStarts($now,Criteria::GREATER_THAN)
->filterByDbStarts($future,Criteria::LESS_THAN)
->useCcShowQuery('a', 'left join')
->filterByDbHasAutoPlaylist(true)
->endUse()
->filterByDbAutoPlaylistBuilt(false)
->find();
}
}

View file

@ -0,0 +1,59 @@
<?php
class CORSHelper
{
public static function enableCrossOriginRequests(&$request, &$response)
{
//Chrome sends the Origin header for all requests, so we whitelist the webserver's hostname as well.
$origin = $request->getHeader('Origin');
$allowedOrigins = self::getAllowedOrigins($request);
if ((!(preg_match("/https?:\/\/localhost/", $origin) === 1)) && ($origin != "") &&
(!in_array($origin, $allowedOrigins))
) {
//Don't allow CORS from other domains to prevent XSS.
Logging::error("request origin '{$origin}' is not in allowed '" . implode(', ', $allowedOrigins) . "'!");
throw new Zend_Controller_Action_Exception('Forbidden', 403);
}
//Allow AJAX requests from configured websites. We use this to allow other pages to use LibreTimes API.
if ($origin) {
$response = $response->setHeader('Access-Control-Allow-Origin', $origin);
}
}
/**
* Get all allowed origins
*
* @param Request $request request object
*/
public static function getAllowedOrigins($request)
{
$allowedCorsUrls = array_map(
function($v) { return trim($v); },
explode(PHP_EOL, Application_Model_Preference::GetAllowedCorsUrls())
);
// always allow the configured server in (as reported by the server and not what is i baseUrl)
$scheme = $request->getServer('REQUEST_SCHEME');
$host = $request->getServer('SERVER_NAME');
$port = $request->getServer('SERVER_PORT');
$portString = '';
if (
$scheme == 'https' && $port != 443 ||
$scheme == 'http' && $port != 80
) {
$portString = sprintf(':%s', $port);
}
$requestedUrl = sprintf(
'%s://%s%s',
$scheme,
$host,
$portString
);
return array_merge($allowedCorsUrls, array(
$requestedUrl
));
}
}

View file

@ -0,0 +1,211 @@
<?php
class CeleryManager {
/**
* @var int milliseconds (for compatibility with celery) until we consider a message to have timed out
*/
private static $_CELERY_MESSAGE_TIMEOUT = 900000; // 15 minutes
/**
* We have to use celeryresults (the default results exchange) because php-celery
* doesn't support named results exchanges.
*
* @var string exchange for celery task results
*/
private static $_CELERY_RESULTS_EXCHANGE = 'celeryresults';
/**
* @var PropelCollection cache of any pending CeleryTasks results for a service or task
*/
private static $_pendingTasks;
/**
* Connect to the Celery daemon via amqp
*
* @param $config array the airtime configuration array
* @param $exchange string the amqp exchange name
* @param $queue string the amqp queue name
*
* @return Celery the Celery connection object
*
* @throws Exception when a connection error occurs
*/
private static function _setupCeleryExchange($config, $exchange, $queue) {
return new Celery($config["rabbitmq"]["host"],
$config["rabbitmq"]["user"],
$config["rabbitmq"]["password"],
$config["rabbitmq"]["vhost"],
$exchange, // Exchange name
$queue, // Binding/queue
$config["rabbitmq"]["port"],
false,
true, // Persistent messages
self::$_CELERY_MESSAGE_TIMEOUT); // Result expiration
}
/**
* Send an amqp message to Celery the airtime-celery daemon to perform a task
*
* @param $task string the Celery task name
* @param $exchange string the amqp exchange name
* @param $data array an associative array containing arguments for the Celery task
*
* @return string the task identifier for the started Celery task so we can fetch the
* results asynchronously later
*/
public static function sendCeleryMessage($task, $exchange, $data) {
$config = Config::getConfig();
$queue = $routingKey = $exchange;
$c = self::_setupCeleryExchange($config, $exchange, $queue); // Use the exchange name for the queue
$result = $c->PostTask($task, $data, true, $routingKey); // and routing key
return $result->getId();
}
/**
* Given a task name and identifier, check the Celery results queue for any
* corresponding messages
*
* @param $task CeleryTasks the Celery task object
*
* @return array the message response array
*
* @throws CeleryException when no message is found
* @throws CeleryTimeoutException when no message is found and more than
* $_CELERY_MESSAGE_TIMEOUT milliseconds have passed
*/
private static function getAsyncResultMessage($task) {
$config = Config::getConfig();
$queue = self::$_CELERY_RESULTS_EXCHANGE . "." . $task;
$c = self::_setupCeleryExchange($config, self::$_CELERY_RESULTS_EXCHANGE, $queue);
$message = $c->getAsyncResultMessage($task->getDbName(), $task->getDbTaskId());
// If the message isn't ready yet (Celery hasn't finished the task), throw an exception.
if ($message == FALSE) {
if (static::_checkMessageTimeout($task)) {
// If the task times out, mark it as failed. We don't want to remove the
// track reference here in case it was a deletion that failed, for example.
$task->setDbStatus(CELERY_FAILED_STATUS)->save();
throw new CeleryTimeoutException("Celery task " . $task->getDbName()
. " with ID " . $task->getDbTaskId() . " timed out");
} else {
// The message hasn't timed out, but it's still false, which means it hasn't been
// sent back from Celery yet.
throw new CeleryException("Waiting on Celery task " . $task->getDbName()
. " with ID " . $task->getDbTaskId());
}
}
return $message;
}
/**
* Check to see if there are any pending tasks for this service
*
* @param string $taskName the name of the task to poll for
* @param string $serviceName the name of the service to poll for
*
* @return bool true if there are any pending tasks, otherwise false
*/
public static function isBrokerTaskQueueEmpty($taskName = "", $serviceName = "") {
self::$_pendingTasks = static::_getPendingTasks($taskName, $serviceName);
return empty(self::$_pendingTasks);
}
/**
* Poll the message queue for this service to see if any tasks with the given name have completed
*
* If we find any completed tasks, adjust the ThirdPartyTrackReferences table accordingly
*
* If no task name is passed, we poll all tasks for this service
*
* @param string $taskName the name of the task to poll for
* @param string $serviceName the name of the service to poll for
*/
public static function pollBrokerTaskQueue($taskName = "", $serviceName = "") {
$pendingTasks = empty(self::$_pendingTasks) ? static::_getPendingTasks($taskName, $serviceName)
: self::$_pendingTasks;
foreach ($pendingTasks as $task) {
try {
$message = static::_getTaskMessage($task);
static::_processTaskMessage($task, $message);
} catch (CeleryTimeoutException $e) {
Logging::warn($e->getMessage());
} catch (CeleryException $e) {
// Don't log these - they end up clogging up the logs
} catch (Exception $e) {
// Because $message->result can be either an object or a string, sometimes
// we get a json_decode error and end up here
Logging::info($e->getMessage());
}
}
}
/**
* Return a collection of all pending CeleryTasks for this service or task
*
* @param string $taskName the name of the task to find
* @param string $serviceName the name of the service to find
*
* @return PropelCollection any pending CeleryTasks results for this service
* or task if taskName is provided
*/
protected static function _getPendingTasks($taskName, $serviceName) {
$query = CeleryTasksQuery::create()
->filterByDbStatus(CELERY_PENDING_STATUS)
->filterByDbTaskId('', Criteria::NOT_EQUAL);
if (!empty($taskName)) {
$query->filterByDbName($taskName);
}
if (!empty($serviceName)) {
$query->useThirdPartyTrackReferencesQuery()
->filterByDbService($serviceName)->endUse();
}
return $query->joinThirdPartyTrackReferences()
->with('ThirdPartyTrackReferences')->find();
}
/**
* Get a Celery task message from the results queue
*
* @param $task CeleryTasks the Celery task object
*
* @return object the task message object
*
* @throws CeleryException when the result message for this task is still pending
* @throws CeleryTimeoutException when the result message for this task no longer exists
*/
protected static function _getTaskMessage($task) {
$message = self::getAsyncResultMessage($task);
return json_decode($message['body']);
}
/**
* Process a message from the results queue
*
* @param $task CeleryTasks Celery task object
* @param $message mixed async message object from php-celery
*/
protected static function _processTaskMessage($task, $message) {
$ref = $task->getThirdPartyTrackReferences(); // ThirdPartyTrackReferences join
$service = CeleryServiceFactory::getService($ref->getDbService());
$service->updateTrackReference($task, $ref->getDbId(), json_decode($message->result), $message->status);
}
/**
* Check if a task message has been unreachable for more our timeout time
*
* @param $task CeleryTasks the Celery task object
*
* @return bool true if the dispatch time is empty or it's been more than our timeout time
* since the message was dispatched, otherwise false
*/
protected static function _checkMessageTimeout($task) {
$utc = new DateTimeZone("UTC");
$dispatchTime = new DateTime($task->getDbDispatchTime(), $utc);
$now = new DateTime("now", $utc);
$timeoutSeconds = self::$_CELERY_MESSAGE_TIMEOUT / 1000; // Convert from milliseconds
$timeoutInterval = new DateInterval("PT" . $timeoutSeconds . "S");
return (empty($dispatchTime) || $dispatchTime->add($timeoutInterval) <= $now);
}
}

View file

@ -0,0 +1,70 @@
<?php
class Application_Common_Database
{
const SINGLE = 'single';
const COLUMN = 'column';
const ALL = 'all';
const EXECUTE = 'execute';
const ROW_COUNT = 'row_count';
public static function prepareAndExecute($sql,
array $paramValueMap = array(),
$type=self::ALL,
$fetchType=PDO::FETCH_ASSOC,
$con=null)
{
if (is_null($con)) {
$con = Propel::getConnection();
}
$stmt = $con->prepare($sql);
foreach ($paramValueMap as $param => $v) {
$stmt->bindValue($param, $v);
}
$rows = array();
if ($stmt->execute()) {
if ($type == self::SINGLE) {
$rows = $stmt->fetch($fetchType);
} else if ($type == self::COLUMN){
$rows = $stmt->fetchColumn();
} else if ($type == self::ALL) {
$rows = $stmt->fetchAll($fetchType);
} else if ($type == self::EXECUTE) {
$rows = null;
} else if ($type == self::ROW_COUNT) {
$rows = $stmt->rowCount();
} else {
$msg = "bad type passed: type($type)";
throw new Exception("Error: $msg");
}
} else {
$msg = implode(',', $stmt->errorInfo());
throw new Exception("Error: $msg");
}
return $rows;
}
/*
Wrapper around prepareAndExecute that allows you to use multipe :xx's
in one query. Transforms $sql to :xx1, :xx2, ....
*/
public static function smartPrepareAndExecute($sql, array $params,
$type='all', $fetchType=PDO::FETCH_ASSOC)
{
$new_params = array();
$new_sql = $sql;
foreach ($params as $k => $v) {
$matches_count = substr_count($sql, $k);
if ($matches_count == 0) {
throw new Exception("Argument $k is not inside $sql");
} elseif ($matches_count == 1) {
$new_params[$k] = $new_params[$v];
} else {
foreach ( range(1,$matches_count) as $i ) {
preg_replace( "/$k(\D)/", "$k$i${1}", $sql, 1);
$new_params[ $k.$i ] = $v;
}
}
}
return Application_Common_Database::prepareAndExecute( $new_sql,
$new_params, $type, $fetchType);
}
}

View file

@ -0,0 +1,501 @@
<?php
class Application_Common_DateHelper
{
private $_dateTime;
function __construct()
{
$this->_dateTime = date("U");
}
/**
* Get time of object construction in the format
* YYYY-MM-DD HH:mm:ss
*/
function getTimestamp()
{
return date(DEFAULT_TIMESTAMP_FORMAT, $this->_dateTime);
}
/**
* Get time of object construction in the format
* YYYY-MM-DD HH:mm:ss
*/
function getUtcTimestamp()
{
return gmdate(DEFAULT_TIMESTAMP_FORMAT, $this->_dateTime);
}
/**
* Get date of object construction in the format
* YYYY-MM-DD
*/
function getDate()
{
return gmdate("Y-m-d", $this->_dateTime);
}
/**
* Get time of object construction in the format
* HH:mm:ss
*/
function getTime()
{
return gmdate("H:i:s", $this->_dateTime);
}
/** Get the abbreviated timezone for the currently logged in user.
* @return A string containing the short form of the timezone set in the preferences for the current user (eg. EST, CEST, etc.)
*/
public static function getUserTimezoneAbbreviation()
{
return self::getTimezoneAbbreviation(Application_Model_Preference::GetUserTimezone());
}
/** Get the abbreviated timezone string of the timezone the station is set to.
* @return A string containing the short form of the station's timezone (eg. EST, CEST, etc.)
*/
public static function getStationTimezoneAbbreviation()
{
return self::getTimezoneAbbreviation(Application_Model_Preference::GetDefaultTimezone());
}
private static function getTimezoneAbbreviation($fullTimeZoneName)
{
$timeZone = new DateTimeZone($fullTimeZoneName);
$now = new DateTime("now", $timeZone);
return $now->format("T");
}
public static function getUserTimezoneOffset()
{
$userTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone());
$now = new DateTime("now", $userTimezone);
return $now->format("Z");
}
public static function getStationTimezoneOffset()
{
$stationTimezone = new DateTimeZone(Application_Model_Preference::GetDefaultTimezone());
$now = new DateTime("now", $stationTimezone);
return $now->format("Z");
}
/**
*
* @return DateTime - YYYY-MM-DD 00:00 in station timezone of today
*/
public static function getTodayStationStartDateTime()
{
$stationTimezone = new DateTimeZone(Application_Model_Preference::GetDefaultTimezone());
$now = new DateTime("now", $stationTimezone);
$now->setTime(0, 0, 0);
return $now;
}
/**
*
* @return DateTime - YYYY-MM-DD 00:00 in station timezone of tomorrow
*/
public static function getTodayStationEndDateTime()
{
$stationTimezone = new DateTimeZone(Application_Model_Preference::GetDefaultTimezone());
$now = new DateTime("now", $stationTimezone);
$now->add(new DateInterval("P1D"));
$now->setTime(0, 0, 0);
return $now;
}
/**
*
* @return DateTime - YYYY-MM-DD 00:00 in station timezone
*/
public static function getWeekStartDateTime()
{
$now = self::getTodayStationStartDateTime();
// our week starts on monday, but php week starts on sunday.
$day = $now->format('w');
if ($day == 0) {
$day = 7;
}
$dayDiff = $day - 1;
if ($dayDiff > 0) {
$now->sub(new DateInterval("P{$dayDiff}D"));
}
return $now;
}
/**
* This function formats a time by removing seconds
*
* When we receive a time from the database we get the
* format "hh:mm:ss". But when dealing with show times, we
* do not care about the seconds.
*
* @param int $p_dateTime
* The value which to format.
* @return int
* The timestamp with the new format "hh:mm", or
* the original input parameter, if it does not have
* the correct format.
*/
public static function removeSecondsFromTime($p_dateTime)
{
//Format is in hh:mm:ss. We want hh:mm
$timeExplode = explode(":", $p_dateTime);
if (count($timeExplode) == 3)
return $timeExplode[0].":".$timeExplode[1];
else
return $p_dateTime;
}
/* Given a track length in the format HH:MM:SS.mm, we want to
* convert this to seconds. This is useful for Liquidsoap which
* likes input parameters give in seconds.
* For example, 00:06:31.444, should be converted to 391.444 seconds
* @param int $p_time
* The time interval in format HH:MM:SS.mm we wish to
* convert to seconds.
* @return float
* The input parameter converted to seconds.
*/
public static function calculateLengthInSeconds($p_time){
if (2 !== substr_count($p_time, ":")){
return false;
}
if (1 === substr_count($p_time, ".")){
list($hhmmss, $ms) = explode(".", $p_time);
} else {
$hhmmss = $p_time;
$ms = 0;
}
list($hours, $minutes, $seconds) = explode(":", $hhmmss);
$totalSeconds = ($hours*3600 + $minutes*60 + $seconds).".$ms";
return round($totalSeconds, 3);
}
/**
* returns true or false depending on input is wether in
* valid range of SQL date/time
* @param string $p_datetime
* should be in format of '0000-00-00 00:00:00'
*/
public static function checkDateTimeRangeForSQL($p_datetime){
$info = explode(' ', $p_datetime);
$dateInfo = explode('-', $info[0]);
if (isset($info[1])) {
$timeInfo = explode(':', $info[1]);
}
$retVal = array();
$retVal["success"] = true;
$year = $dateInfo[0];
$month = $dateInfo[1];
$day = $dateInfo[2];
// if year is < 1753 or > 9999 it's out of range
if ($year < 1753) {
$retVal['success'] = false;
$retVal['errMsg'] = sprintf(_("The year %s must be within the range of 1753 - 9999"), $year);
} else if (!checkdate($month, $day, $year)) {
$retVal['success'] = false;
$retVal['errMsg'] = sprintf(_("%s-%s-%s is not a valid date"), $year, $month, $day);
} else {
// check time
if (isset($timeInfo)) {
if (isset($timeInfo[0]) && $timeInfo[0] != "") {
$hour = intval($timeInfo[0]);
} else {
$hour = -1;
}
if (isset($timeInfo[1]) && $timeInfo[1] != "") {
$min = intval($timeInfo[1]);
} else {
$min = -1;
}
if (isset($timeInfo[2]) && $timeInfo[2] != "") {
$sec = intval($timeInfo[2]);
} else {
$sec = -1;
}
if ( ($hour < 0 || $hour > 23) || ($min < 0 || $min > 59) || ($sec < 0 || $sec > 59) ) {
$retVal['success'] = false;
$retVal['errMsg'] = sprintf(_("%s:%s:%s is not a valid time"), $timeInfo[0], $timeInfo[1] ,$timeInfo[2]);
}
}
}
return $retVal;
}
/*
* @param $datetime string Y-m-d H:i:s in UTC timezone
*
* @return string in $format default Y-m-d H:i:s in station timezone
*/
public static function UTCStringToStationTimezoneString($datetime, $format=DEFAULT_TIMESTAMP_FORMAT) {
$stationTimezone = new DateTimeZone(Application_Model_Preference::GetDefaultTimezone());
$utcTimezone = new DateTimeZone("UTC");
$d = new DateTime($datetime, $utcTimezone);
$d->setTimezone($stationTimezone);
return $d->format($format);
}
/*
* @param $datetime string Y-m-d H:i:s in UTC timezone
*
* @return string Y-m-d H:i:s in user's timezone
*/
public static function UTCStringToUserTimezoneString($datetime, $format=DEFAULT_TIMESTAMP_FORMAT) {
$userTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone());
$utcTimezone = new DateTimeZone("UTC");
$d = new DateTime($datetime, $utcTimezone);
$d->setTimezone($userTimezone);
return $d->format($format);
}
/*
* @param $datetime string Y-m-d H:i:s in user timezone
*
* @return string Y-m-d H:i:s in UTC timezone
*/
public static function UserTimezoneStringToUTCString($datetime, $format=DEFAULT_TIMESTAMP_FORMAT) {
$userTimezone = new DateTimeZone(Application_Model_Preference::GetUserTimezone());
$utcTimezone = new DateTimeZone("UTC");
$d = new DateTime($datetime, $userTimezone);
$d->setTimezone($utcTimezone);
return $d->format($format);
}
/**
* Convert the columns given in the array $columnsToConvert in the
* database result $rows to local timezone.
*
* @param array $rows arrays of arrays containing database query result
* @param array $columnsToConvert array of column names to convert
* @param string (station|user) convert to either station or user timezone.
*/
public static function convertTimestamps(&$rows, $columnsToConvert, $domain="station")
{
if (!is_array($rows)) {
return;
}
$converter = "UTCStringTo".ucfirst($domain)."TimezoneString";
foreach ($rows as &$row) {
foreach ($columnsToConvert as $column) {
$row[$column] = self::$converter($row[$column]);
}
}
}
/**
* Convert the columns given in the array $columnsToConvert in the
* database result $rows to local timezone.
*
* @param array $rows arrays of arrays containing database query result
* @param array $columnsToConvert array of column names to convert
* @param string $timezone convert to the given timezone.
* @param string $format time format to convert to
*/
public static function convertTimestampsToTimezone(&$rows, $columnsToConvert, $timezone, $format=DEFAULT_TIMESTAMP_FORMAT)
{
$timezone = strtolower($timezone);
// Check that the timezone is valid and rows is an array
if (!is_array($rows)) {
return;
}
foreach ($rows as &$row) {
if (is_array($row)) {
foreach ($columnsToConvert as $column) {
if (array_key_exists($column, $row)) {
$newTimezone = new DateTimeZone($timezone);
$utcTimezone = new DateTimeZone("UTC");
$d = new DateTime($row[$column], $utcTimezone);
$d->setTimezone($newTimezone);
$row[$column] = $d->format($format);
}
}
self::convertTimestampsToTimezone($row, $columnsToConvert, $timezone, $format);
}
}
}
/**
* Return the end date time in the given timezone
*
* @return DateTime
*/
public static function getEndDateTime($timezoneString, $days)
{
$timezone = new DateTimeZone($timezoneString);
$now = new DateTime("now", $timezone);
$now->add(new DateInterval("P".$days."D"));
$now->setTime(0, 0, 0);
return $now;
}
/**
* Return a formatted string representing the
* given datetime in the given timezone
*
* @param unknown $datetime the time to convert
* @param unknown $timezone the timezone to convert to
* @param string $format the formatted string
*/
public static function UTCStringToTimezoneString($datetime, $timezone, $format=DEFAULT_TIMESTAMP_FORMAT) {
$d = new DateTime($datetime, new DateTimeZone("UTC"));
$timezone = strtolower($timezone);
$newTimezone = new DateTimeZone($timezone);
$d->setTimezone($newTimezone);
return $d->format($format);
}
/**
* Return the timezone offset in seconds for the given timezone
*
* @param unknown $userDefinedTimezone the timezone used to determine the offset
*/
public static function getTimezoneOffset($userDefinedTimezone) {
$now = new DateTimeZone($userDefinedTimezone);
$d = new DateTime("now", $now);
return $d->format("Z");
}
/**
* This function is used for calculations! Don't modify for display purposes!
*
* Convert playlist time value to float seconds
*
* @param string $plt
* playlist interval value (HH:mm:ss.dddddd)
* @return int
* seconds
*/
public static function playlistTimeToSeconds($plt)
{
$arr = preg_split('/:/', $plt);
if (isset($arr[2])) {
return (intval($arr[0])*60 + intval($arr[1]))*60 + floatval($arr[2]);
}
if (isset($arr[1])) {
return intval($arr[0])*60 + floatval($arr[1]);
}
return floatval($arr[0]);
}
/**
* This function is used for calculations! Don't modify for display purposes!
*
* Convert float seconds value to playlist time format
*
* @param float $seconds
* @return string
* interval in playlist time format (HH:mm:ss.d)
*/
public static function secondsToPlaylistTime($p_seconds)
{
$info = explode('.', $p_seconds);
$seconds = $info[0];
if (!isset($info[1])) {
$milliStr = 0;
} else {
$milliStr = $info[1];
}
$hours = floor($seconds / 3600);
$seconds -= $hours * 3600;
$minutes = floor($seconds / 60);
$seconds -= $minutes * 60;
$res = sprintf("%02d:%02d:%02d.%s", $hours, $minutes, $seconds, $milliStr);
return $res;
}
/**
* Returns date fields from give start and end teimstamp strings
* if no start or end parameter is passed start will be set to 1
* in the past and end to now
*
* @param string startTimestamp Y-m-d H:i:s
* @param string endTImestamp Y-m-d H:i:s
* @param string timezone (ex UTC) of the start and end parameters
* @return array (start DateTime, end DateTime) in UTC timezone
*/
public static function getStartEnd($startTimestamp, $endTimestamp, $timezone)
{
$prefTimezone = Application_Model_Preference::GetTimezone();
$utcTimezone = new DateTimeZone("UTC");
$utcNow = new DateTime("now", $utcTimezone);
if (empty($timezone)) {
$userTimezone = new DateTimeZone($prefTimezone);
} else {
$userTimezone = new DateTimeZone($timezone);
}
// default to 1 day
if (empty($startTimestamp) || empty($endTimestamp)) {
$startsDT = clone $utcNow;
$startsDT->sub(new DateInterval("P1D"));
$endsDT = clone $utcNow;
} else {
try {
$startsDT = new DateTime($startTimestamp, $userTimezone);
$startsDT->setTimezone($utcTimezone);
$endsDT = new DateTime($endTimestamp, $userTimezone);
$endsDT->setTimezone($utcTimezone);
if ($startsDT > $endsDT) {
throw new Exception("start greater than end");
}
}
catch (Exception $e) {
Logging::info($e);
Logging::info($e->getMessage());
$startsDT = clone $utcNow;
$startsDT->sub(new DateInterval("P1D"));
$endsDT = clone $utcNow;
}
}
return array($startsDT, $endsDT);
}
}

View file

@ -0,0 +1,452 @@
<?php
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/x-aac" => "aac",
"audio/aac" => "aac",
"audio/aacp" => "aac",
"audio/mp4" => "m4a",
"video/mp4" => "mp4",
"audio/x-flac" => "flac",
"audio/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
* at all costs, so check the data and sanitize it if necessary
* @param array $data array containing new file metadata
*/
public static function sanitizeData(&$data)
{
if (array_key_exists("track_number", $data)) {
// If the track number isn't numeric, this will return 0
$data["track_number"] = intval($data["track_number"]);
}
if (array_key_exists("year", $data)) {
// If the track number isn't numeric, this will return 0
$data["year"] = intval($data["year"]);
}
if (array_key_exists("bpm", $data)) {
//Some BPM tags are silly and include the word "BPM". Let's strip that...
$data["bpm"] = str_ireplace("BPM", "", $data["bpm"]);
// This will convert floats to ints too.
$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");
}
}
/**
* Gets data URI from artwork file
*
* @param string $file
* @param int $size
* @param string $filepath
*
* @return string Data URI for artwork
*/
public static function getArtworkData($file, $size, $filepath = false)
{
$baseUrl = Application_Common_HTTPHelper::getStationUrl();
$default = $baseUrl . "css/images/no-cover.jpg";
if ($filepath != false) {
$path = $filepath . $file . "-" . $size;
if (!file_exists($path)) {
$get_file_content = $default;
} else {
$get_file_content = file_get_contents($path);
}
} else {
$storDir = Application_Model_MusicDir::getStorDir();
$path = $storDir->getDirectory() . $file . "-" . $size;
if (!file_exists($path)) {
$get_file_content = $default;
} else {
$get_file_content = file_get_contents($path);
}
}
return $get_file_content;
}
/**
* Add artwork file
*
* @param string $analyzeFile
* @param string $filename
* @param string $importDir
* @param string $DbPath
*
* @return string Path to artwork
*/
public static function saveArtworkData($analyzeFile, $filename, $importDir = null, $DbPath = null)
{
if (class_exists('getID3')) {
$getID3 = new \getID3();
$getFileInfo = $getID3->analyze($analyzeFile);
} else {
$getFileInfo = [];
Logging::error("Failed to load getid3 library. Please upgrade Libretime.");
}
if(isset($getFileInfo['comments']['picture'][0])) {
$get_img = "";
$timestamp = time();
$mime = $getFileInfo['comments']['picture'][0]['image_mime'];
$Image = 'data:'.$mime.';charset=utf-8;base64,'.base64_encode($getFileInfo['comments']['picture'][0]['data']);
$base64 = @$Image;
if (!file_exists($importDir . "/" . "artwork/")) {
if (!mkdir($importDir . "/" . "artwork/", 0777, true)) {
Logging::error("Failed to create artwork directory.");
throw new Exception("Failed to create artwork directory.");
}
}
$path_parts = pathinfo($filename);
$file = $importDir . "artwork/" . $path_parts['filename'];
//Save Data URI
if (file_put_contents($file, $base64)) {
$get_img = $DbPath . "artwork/". $path_parts['filename'];
} else {
Logging::error("Could not save Data URI");
}
if ($mime == "image/png") {
$ext = 'png';
} elseif ($mime == "image/gif") {
$ext = 'gif';
} elseif ($mime == "image/bmp") {
$ext = 'bmp';
} else {
$ext = 'jpg';
}
self::resizeGroup($file, $ext);
} else {
$get_img = '';
}
return $get_img;
}
/**
* Reset artwork
*
* @param string $trackid
*
* @return string $get_img Path to artwork
*/
public static function resetArtwork($trackid)
{
$file = Application_Model_StoredFile::RecallById($trackid);
$md = $file->getMetadata();
$storDir = Application_Model_MusicDir::getStorDir();
$fp = $storDir->getDirectory();
$dbAudioPath = $md["MDATA_KEY_FILEPATH"];
$fullpath = $fp . $dbAudioPath;
if (class_exists('getID3')) {
$getID3 = new \getID3();
$getFileInfo = $getID3->analyze($fullpath);
} else {
$getFileInfo = [];
Logging::error("Failed to load getid3 library. Please upgrade Libretime.");
}
if(isset($getFileInfo['comments']['picture'][0])) {
$get_img = "";
$mime = $getFileInfo['comments']['picture'][0]['image_mime'];
$Image = 'data:'.$getFileInfo['comments']['picture'][0]['image_mime'].';charset=utf-8;base64,'.base64_encode($getFileInfo['comments']['picture'][0]['data']);
$base64 = @$Image;
$audioPath = dirname($fullpath);
$dbPath = dirname($dbAudioPath);
$path_parts = pathinfo($fullpath);
$file = $path_parts['filename'];
//Save Data URI
if (file_put_contents($audioPath . "/" . $file, $base64)) {
$get_img = $dbPath . "/" . $file;
} else {
Logging::error("Could not save Data URI");
}
$rfile = $audioPath . "/" . $file;
if ($mime == "image/png") {
$ext = 'png';
} elseif ($mime == "image/gif") {
$ext = 'gif';
} elseif ($mime == "image/bmp") {
$ext = 'bmp';
} else {
$ext = 'jpg';
}
self::resizeGroup($rfile, $ext);
} else {
$get_img = "";
}
return $get_img;
}
/**
* Upload artwork
*
* @param string $trackid
* @param string $data
*
* @return string Path to artwork
*/
public static function setArtwork($trackid, $data)
{
$file = Application_Model_StoredFile::RecallById($trackid);
$md = $file->getMetadata();
$storDir = Application_Model_MusicDir::getStorDir();
$fp = $storDir->getDirectory();
$dbAudioPath = $md["MDATA_KEY_FILEPATH"];
$fullpath = $fp . $dbAudioPath;
if ($data == "0") {
$get_img = "";
self::removeArtwork($trackid, $data);
} else {
$base64 = @$data;
$mime = explode(';', $base64)[0];
$audioPath = dirname($fullpath);
$dbPath = dirname($dbAudioPath);
$path_parts = pathinfo($fullpath);
$file = $path_parts['filename'];
//Save Data URI
if (file_put_contents($audioPath . "/" . $file, $base64)) {
$get_img = $dbPath . "/" . $file;
} else {
Logging::error("Could not save Data URI");
}
$rfile = $audioPath . "/" . $file;
if ($mime == "data:image/png") {
$ext = 'png';
} elseif ($mime == "data:image/gif") {
$ext = 'gif';
} elseif ($mime == "data:image/bmp") {
$ext = 'bmp';
} else {
$ext = 'jpg';
}
self::resizeGroup($rfile, $ext);
}
return $get_img;
}
/**
*
* Deletes just the artwork
*/
public static function removeArtwork($trackid)
{
$file = Application_Model_StoredFile::RecallById($trackid);
$md = $file->getMetadata();
$storDir = Application_Model_MusicDir::getStorDir();
$fp = $storDir->getDirectory();
$dbAudioPath = $md["MDATA_KEY_ARTWORK"];
$fullpath = $fp . $dbAudioPath;
if (file_exists($fullpath)) {
foreach (glob("$fullpath*", GLOB_NOSORT) as $filename) {
unlink($filename);
}
} else {
throw new Exception("Could not locate file ".$filepath);
}
return "";
}
/**
* Resize artwork group
*
* @param string $file
* @param string $ext
*/
public static function resizeGroup($file, $ext)
{
if (file_exists($file)) {
self::resizeImage($file, $file . '-32.jpg', $ext, 32, 100);
self::resizeImage($file, $file . '-64.jpg', $ext, 64, 100);
self::resizeImage($file, $file . '-128.jpg', $ext, 128, 100);
self::resizeImage($file, $file . '-256.jpg', $ext, 256, 100);
self::resizeImage($file, $file . '-512.jpg', $ext, 512, 100);
self::imgToDataURI($file . '-32.jpg', $file . '-32');
self::imgToDataURI($file . '-64.jpg', $file . '-64');
self::imgToDataURI($file . '-128.jpg', $file . '-128');
self::imgToDataURI($file . '-256.jpg', $file . '-256');
} else {
Logging::error("The file $file does not exist");
}
}
/**
* Render image
* Used in API to render JPEG
*
* @param string $file
*/
public static function renderImage($file)
{
$im = @imagecreatefromjpeg($file);
header('Content-Type: image/jpeg');
$img = $im;
imagejpeg($img);
imagedestroy($img);
}
/**
* Render Data URI
* Used in API to render Data URI
*
* @param string $dataFile
*/
public static function renderDataURI($dataFile)
{
if($filecontent = file_get_contents($dataFile) !== false){
$image = @file_get_contents($dataFile);
$image = base64_encode($image);
if (!$image || $image === '') {
return;
}
$blob = base64_decode($image);
$f = finfo_open();
$mime_type = finfo_buffer($f, $blob, FILEINFO_MIME_TYPE);
finfo_close($f);
header("Content-Type: " . $mime_type);
echo $blob;
} else {
return;
}
}
/**
* Resize Image
*
* @param string $orig_filename
* @param string $converted_filename
* @param string $ext
* @param string $size Default: 500
* @param string $quality Default: 75
*
*/
public static function resizeImage($orig_filename, $converted_filename, $ext, $size=500, $quality=75)
{
$get_cont = file_get_contents($orig_filename);
if ($ext == "png") {
$im = @imagecreatefrompng($get_cont);
} elseif ($ext == "gif") {
$im = @imagecreatefromgif($get_cont);
} else {
$im = @imagecreatefromjpeg($get_cont);
}
// if one of those bombs, create an error image instead
if (!$im) {
$im = imagecreatetruecolor(150, 30);
$bgc = imagecolorallocate($im, 255, 255, 255);
$tc = imagecolorallocate($im, 0, 0, 0);
imagefilledrectangle($im, 0, 0, 150, 30, $bgc);
imagestring($im, 1, 5, 5, 'Error loading ' . $converted_filename, $tc);
}
// scale if appropriate
if ($size){
$im = imagescale($im , $size);
}
$img = $im;
imagejpeg($img, $converted_filename, $quality);
imagedestroy($img);
}
/**
* Convert image to Data URI
*
* @param string $orig_filename
* @param string $conv_filename
*/
public static function imgToDataURI($orig_filename, $conv_filename)
{
$file = file_get_contents($orig_filename);
$Image = 'data:image/jpeg;charset=utf-8;base64,'.base64_encode($file);
$base64 = @$Image;
//Save Data URI
if (file_put_contents($conv_filename, $base64)) {
} else {
Logging::error("Could not save Data URI");
}
}
/**
* Track Type
*
* @return string Track type key value
*/
public static function saveTrackType()
{
if (isset($_COOKIE['tt_upload'])) {
$tt = $_COOKIE['tt_upload'];
} else {
// Use default track type
$tt = Application_Model_Preference::GetTrackTypeDefault();
}
return $tt;
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* Class Application_Common_FileIO contains helper functions for reading and writing files, and sending them over HTTP.
*/
class Application_Common_FileIO
{
/**
* Reads the requested portion of a file and sends its contents to the client with the appropriate headers.
*
* This HTTP_RANGE compatible read file function is necessary for allowing streaming media to be skipped around in.
*
* @param string $filePath - the full filepath or URL pointing to the location of the file
* @param string $mimeType - the file's mime type. Defaults to 'audio/mp3'
* @param integer $size - the file size, in bytes
* @return void
*
* @link https://groups.google.com/d/msg/jplayer/nSM2UmnSKKA/Hu76jDZS4xcJ
* @link http://php.net/manual/en/function.readfile.php#86244
*/
public static function smartReadFile($filePath, $size, $mimeType)
{
$fm = @fopen($filePath, 'rb');
if (!$fm) {
throw new LibreTimeFileNotFoundException($filePath);
}
//Note that $size is allowed to be zero. If that's the case, it means we don't
//know the filesize, and we need to figure one out so modern browsers don't get
//confused. This should only affect files imported by legacy upstream since
//media monitor did not always set the proper size in the database but analyzer
//seems to always have a value for this.
if ($size === 0) {
$fstats = fstat($fm);
$size = $fstats['size'];
}
if ($size <= 0) {
throw new Exception("Invalid file size returned for file at $filePath");
}
$begin = 0;
$end = $size - 1;
ob_start(); //Must start a buffer here for these header() functions
if (isset($_SERVER['HTTP_RANGE'])) {
if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
$begin = intval($matches[1]);
if (!empty($matches[2])) {
$end = intval($matches[2]);
}
}
}
if (isset($_SERVER['HTTP_RANGE'])) {
header('HTTP/1.1 206 Partial Content');
} else {
header('HTTP/1.1 200 OK');
}
header("Content-Type: $mimeType");
header("Content-Transfer-Encoding: binary");
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Accept-Ranges: bytes');
header('Content-Length:' . (($end - $begin) + 1));
if (isset($_SERVER['HTTP_RANGE'])) {
header("Content-Range: bytes $begin-$end/$size");
}
//We can have multiple levels of output buffering. Need to
//keep looping until all have been disabled!!!
//http://www.php.net/manual/en/function.ob-end-flush.php
while (ob_get_level() > 0) {
ob_end_flush();
}
//These two lines were removed from Airtime 2.5.x at some point after Libretime forked from Airtime.
//These lines allow seek to work for files.
//Issue #349
$cur = $begin;
fseek($fm,$begin,0);
while(!feof($fm) && (connection_status() == 0) && ($cur <= $end)) {
echo fread($fm, 1024 * 8);
}
fclose($fm);
}
}

View file

@ -0,0 +1,117 @@
<?php
class Application_Common_HTTPHelper
{
/**
* Returns start and end DateTime vars from given
* HTTP Request object
*
* @param Request
* @return array(start DateTime, end DateTime)
*/
public static function getStartEndFromRequest($request)
{
return Application_Common_DateHelper::getStartEnd(
$request->getParam("start", null),
$request->getParam("end", null),
$request->getParam("timezone", null)
);
}
/**
* Construct the base station URL
*
* @param boolean $secured whether or not to use HTTPS
*
* @return string the station URL
*/
public static function getStationUrl($secured = true)
{
$CC_CONFIG = Config::getConfig();
$baseUrl = $CC_CONFIG['baseUrl'];
$baseDir = $CC_CONFIG['baseDir'];
$basePort = $CC_CONFIG['basePort'];
$forceSSL = $CC_CONFIG['forceSSL'];
$configProtocol = $CC_CONFIG['protocol'];
if (empty($baseDir)) {
$baseDir = "/";
}
if ($baseDir[0] != "/") {
$baseDir = "/" . $baseDir;
}
if (substr($baseDir, -1) != "/") {
$baseDir = $baseDir . "/";
}
# Set in reverse order of preference. ForceSSL configuration takes absolute preference, then
# the protocol set in config. If neither are set, the port is used to determine the scheme
$scheme = "http";
if ($secured && $basePort == "443") {
$scheme = "https";
}
if (!empty($configProtocol)) {
$scheme = $configProtocol;
}
if ($forceSSL) {
$scheme = "https";
}
$portStr = "";
if (($scheme == "http" && $basePort !== "80")
|| ($scheme == "https" && $basePort !== "443")) {
$portStr = ":${basePort}";
}
$stationUrl = "$scheme://${baseUrl}${portStr}${baseDir}";
return $stationUrl;
}
/**
* Execute a cURL POST
*
* @param string $url the URL to POST to
* @param string[] $userPwd array of user args of the form ['user', 'pwd']
* @param array $formData array of form data kwargs
*
* @return mixed the cURL result
*/
public static function doPost($url, $userPwd, $formData) {
$params = "";
foreach($formData as $key=>$value) {
$params .= $key.'='.$value.'&';
}
rtrim($params, '&');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
curl_setopt($ch, CURLOPT_USERPWD, implode(':', $userPwd));
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}
class ZendActionHttpException extends Exception {
/**
* @param Zend_Controller_Action $action
* @param int $statusCode
* @param string $message
* @param int $code
* @param Exception $previous
*
* @throws Zend_Controller_Response_Exception
*/
public function __construct(Zend_Controller_Action $action, $statusCode, $message,
$code = 0, Exception $previous = null) {
Logging::error("Error in action " . $action->getRequest()->getActionName()
. " with status code $statusCode: $message");
$action->getResponse()
->setHttpResponseCode($statusCode)
->appendBody($message);
parent::__construct($message, $code, $previous);
}
}

View file

@ -0,0 +1,160 @@
<?php
// Global functions for translating domain-specific strings
class Application_Common_LocaleHelper {
/**
* Return an array of all ISO 639-1 language codes and their corresponding translated language names
*
* @return array the array of language codes to names
*/
public static function getISO6391LanguageCodes() {
/**
* From: http://www.binarytides.com/php-array-of-iso-639-1-language-codes-and-names/
*
* ISO 639-1 Language Codes
* References :
* 1. http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
* 2. http://blog.xoundboy.com/?p=235
*/
return array(
'en' => _('English'),
'aa' => _('Afar'),
'ab' => _('Abkhazian'),
'af' => _('Afrikaans'),
'am' => _('Amharic'),
'ar' => _('Arabic'),
'as' => _('Assamese'),
'ay' => _('Aymara'),
'az' => _('Azerbaijani'),
'ba' => _('Bashkir'),
'be' => _('Belarusian'),
'bg' => _('Bulgarian'),
'bh' => _('Bihari'),
'bi' => _('Bislama'),
'bn' => _('Bengali/Bangla'),
'bo' => _('Tibetan'),
'br' => _('Breton'),
'ca' => _('Catalan'),
'co' => _('Corsican'),
'cs' => _('Czech'),
'cy' => _('Welsh'),
'da' => _('Danish'),
'de' => _('German'),
'dz' => _('Bhutani'),
'el' => _('Greek'),
'eo' => _('Esperanto'),
'es' => _('Spanish'),
'et' => _('Estonian'),
'eu' => _('Basque'),
'fa' => _('Persian'),
'fi' => _('Finnish'),
'fj' => _('Fiji'),
'fo' => _('Faeroese'),
'fr' => _('French'),
'fy' => _('Frisian'),
'ga' => _('Irish'),
'gd' => _('Scots/Gaelic'),
'gl' => _('Galician'),
'gn' => _('Guarani'),
'gu' => _('Gujarati'),
'ha' => _('Hausa'),
'hi' => _('Hindi'),
'hr' => _('Croatian'),
'hu' => _('Hungarian'),
'hy' => _('Armenian'),
'ia' => _('Interlingua'),
'ie' => _('Interlingue'),
'ik' => _('Inupiak'),
'in' => _('Indonesian'),
'is' => _('Icelandic'),
'it' => _('Italian'),
'iw' => _('Hebrew'),
'ja' => _('Japanese'),
'ji' => _('Yiddish'),
'jw' => _('Javanese'),
'ka' => _('Georgian'),
'kk' => _('Kazakh'),
'kl' => _('Greenlandic'),
'km' => _('Cambodian'),
'kn' => _('Kannada'),
'ko' => _('Korean'),
'ks' => _('Kashmiri'),
'ku' => _('Kurdish'),
'ky' => _('Kirghiz'),
'la' => _('Latin'),
'ln' => _('Lingala'),
'lo' => _('Laothian'),
'lt' => _('Lithuanian'),
'lv' => _('Latvian/Lettish'),
'mg' => _('Malagasy'),
'mi' => _('Maori'),
'mk' => _('Macedonian'),
'ml' => _('Malayalam'),
'mn' => _('Mongolian'),
'mo' => _('Moldavian'),
'mr' => _('Marathi'),
'ms' => _('Malay'),
'mt' => _('Maltese'),
'my' => _('Burmese'),
'na' => _('Nauru'),
'ne' => _('Nepali'),
'nl' => _('Dutch'),
'no' => _('Norwegian'),
'oc' => _('Occitan'),
'om' => _('(Afan)/Oromoor/Oriya'),
'pa' => _('Punjabi'),
'pl' => _('Polish'),
'ps' => _('Pashto/Pushto'),
'pt' => _('Portuguese'),
'qu' => _('Quechua'),
'rm' => _('Rhaeto-Romance'),
'rn' => _('Kirundi'),
'ro' => _('Romanian'),
'ru' => _('Russian'),
'rw' => _('Kinyarwanda'),
'sa' => _('Sanskrit'),
'sd' => _('Sindhi'),
'sg' => _('Sangro'),
'sh' => _('Serbo-Croatian'),
'si' => _('Singhalese'),
'sk' => _('Slovak'),
'sl' => _('Slovenian'),
'sm' => _('Samoan'),
'sn' => _('Shona'),
'so' => _('Somali'),
'sq' => _('Albanian'),
'sr' => _('Serbian'),
'ss' => _('Siswati'),
'st' => _('Sesotho'),
'su' => _('Sundanese'),
'sv' => _('Swedish'),
'sw' => _('Swahili'),
'ta' => _('Tamil'),
'te' => _('Tegulu'),
'tg' => _('Tajik'),
'th' => _('Thai'),
'ti' => _('Tigrinya'),
'tk' => _('Turkmen'),
'tl' => _('Tagalog'),
'tn' => _('Setswana'),
'to' => _('Tonga'),
'tr' => _('Turkish'),
'ts' => _('Tsonga'),
'tt' => _('Tatar'),
'tw' => _('Twi'),
'uk' => _('Ukrainian'),
'ur' => _('Urdu'),
'uz' => _('Uzbek'),
'vi' => _('Vietnamese'),
'vo' => _('Volapuk'),
'wo' => _('Wolof'),
'xh' => _('Xhosa'),
'yo' => _('Yoruba'),
'zh' => _('Chinese'),
'zu' => _('Zulu'),
);
}
}

View file

@ -0,0 +1,96 @@
<?php
class Application_Common_OsPath{
// this function is from http://stackoverflow.com/questions/2670299/is-there-a-php-equivalent-function-to-the-python-os-path-normpath
public static function normpath($path)
{
if (empty($path))
return '.';
if (strpos($path, '/') === 0)
$initial_slashes = true;
else
$initial_slashes = false;
if (
($initial_slashes) &&
(strpos($path, '//') === 0) &&
(strpos($path, '///') === false)
)
$initial_slashes = 2;
$initial_slashes = (int) $initial_slashes;
$comps = explode('/', $path);
$new_comps = array();
foreach ($comps as $comp)
{
if (in_array($comp, array('', '.')))
continue;
if (
($comp != '..') ||
(!$initial_slashes && !$new_comps) ||
($new_comps && (end($new_comps) == '..'))
)
array_push($new_comps, $comp);
elseif ($new_comps)
array_pop($new_comps);
}
$comps = $new_comps;
$path = implode('/', $comps);
if ($initial_slashes)
$path = str_repeat('/', $initial_slashes) . $path;
if ($path)
return $path;
else
return '.';
}
/* Similar to the os.path.join python method
* http://stackoverflow.com/a/1782990/276949 */
public static function join() {
$args = func_get_args();
$paths = array();
foreach($args as $arg) {
$paths = array_merge($paths, (array)$arg);
}
foreach($paths as &$path) {
$path = trim($path, DIRECTORY_SEPARATOR);
}
if (substr($args[0], 0, 1) == DIRECTORY_SEPARATOR) {
$paths[0] = DIRECTORY_SEPARATOR . $paths[0];
}
return join(DIRECTORY_SEPARATOR, $paths);
}
public static function getBaseDir() {
$CC_CONFIG = Config::getConfig();
$baseUrl = $CC_CONFIG['baseDir'];
if ($baseUrl[0] != "/") {
$baseUrl = "/".$baseUrl;
}
if ($baseUrl[strlen($baseUrl) -1] != "/") {
$baseUrl = $baseUrl."/";
}
return $baseUrl;
}
public static function formatDirectoryWithDirectorySeparators($dir)
{
if ($dir[0] != "/") {
$dir = "/".$dir;
}
if ($dir[strlen($dir) -1] != "/") {
$dir = $dir."/";
}
return $dir;
}
}

View file

@ -0,0 +1,104 @@
<?php
class PodcastManager {
/**
* @var int how often, in seconds, to check for and ingest new podcast episodes
*/
private static $_PODCAST_POLL_INTERVAL_SECONDS = 3600; // 1 hour
/**
* Check whether $_PODCAST_POLL_INTERVAL_SECONDS have passed since the last call to
* downloadNewestEpisodes
*
* @return bool true if $_PODCAST_POLL_INTERVAL_SECONDS has passed since the last check
*/
public static function hasPodcastPollIntervalPassed() {
$lastPolled = Application_Model_Preference::getPodcastPollLock();
return empty($lastPolled) || (microtime(true) > $lastPolled + self::$_PODCAST_POLL_INTERVAL_SECONDS);
}
/**
* Find all podcasts flagged for automatic ingest whose most recent episode has
* yet to be downloaded and download it with Celery
*
* @throws InvalidPodcastException
* @throws PodcastNotFoundException
*/
public static function downloadNewestEpisodes() {
$autoIngestPodcasts = static::_getAutoIngestPodcasts();
$service = new Application_Service_PodcastEpisodeService();
foreach ($autoIngestPodcasts as $podcast) {
$episodes = static::_findUningestedEpisodes($podcast, $service);
// Since episodes don't have to be uploaded with a time (H:i:s) component,
// store the timestamp of the most recent (first pushed to the array) episode
// that we're ingesting.
// Note that this folds to the failure case (Celery task timeout/download failure)
// but will at least continue to ingest new episodes.
if (!empty($episodes)) {
$podcast->setDbAutoIngestTimestamp(gmdate('r', strtotime($episodes[0]->getDbPublicationDate())))->save();
$service->downloadEpisodes($episodes);
}
}
Application_Model_Preference::setPodcastPollLock(microtime(true));
}
/**
* Given an ImportedPodcast, find all uningested episodes since the last automatic ingest,
* and add them to a given episodes array
*
* @param ImportedPodcast $podcast the podcast to search
* @param Application_Service_PodcastEpisodeService $service podcast episode service object
*
* @return array array of episodes to append be downloaded
*/
protected static function _findUningestedEpisodes($podcast, $service) {
$episodeList = $service->getPodcastEpisodes($podcast->getDbPodcastId());
$episodes = array();
usort($episodeList, array(__CLASS__, "_sortByEpisodePubDate"));
for ($i = 0; $i < sizeof($episodeList); $i++) {
$episodeData = $episodeList[$i];
$ts = $podcast->getDbAutoIngestTimestamp();
// If the timestamp for this podcast is empty (no previous episodes have been ingested) and there are no
// episodes in the list of episodes to ingest, don't skip this episode - we should try to ingest the
// most recent episode when the user first sets the podcast to automatic ingest.
// If the publication date of this episode is before the ingest timestamp, we don't need to ingest it
if ((empty($ts) && ($i > 0)) || strtotime($episodeData["pub_date"]) < strtotime($ts)) {
continue;
}
$episode = PodcastEpisodesQuery::create()->findOneByDbEpisodeGuid($episodeData["guid"]);
// Make sure there's no existing episode placeholder or import, and that the data is non-empty
if (empty($episode) && !empty($episodeData)) {
$placeholder = $service->addPlaceholder($podcast->getDbPodcastId(), $episodeData);
array_push($episodes, $placeholder);
}
}
return $episodes;
}
/**
* Find all podcasts flagged for automatic ingest
*
* @return PropelObjectCollection collection of ImportedPodcast objects
* flagged for automatic ingest
*/
protected static function _getAutoIngestPodcasts() {
return ImportedPodcastQuery::create()
->filterByDbAutoIngest(true)
->find();
}
/**
* Custom sort function for podcast episodes
*
* @param array $a first episode array to compare
* @param array $b second episode array to compare
* @return bool boolean for ordering
*/
protected static function _sortByEpisodePubDate($a, $b) {
if ($a["pub_date"] == $b["pub_date"]) return 0;
return (strtotime($a["pub_date"]) < strtotime($b["pub_date"])) ? 1 : -1; // Descending order
}
}

View file

@ -0,0 +1,23 @@
<?php
class SecurityHelper {
public static function htmlescape_recursive(&$arr) {
foreach ($arr as $key => $val) {
if (is_array($val)) {
self::htmlescape_recursive($arr[$key]);
} else if (is_string($val)) {
$arr[$key] = htmlspecialchars($val, ENT_QUOTES);
}
}
return $arr;
}
public static function verifyCSRFToken($observedToken) {
$current_namespace = new Zend_Session_Namespace('csrf_namespace');
$observed_csrf_token = $observedToken;
$expected_csrf_token = $current_namespace->authtoken;
return ($observed_csrf_token == $expected_csrf_token);
}
}

View file

@ -0,0 +1,13 @@
<?php
class SessionHelper
{
public static function reopenSessionForWriting() {
//PHP will send double Set-Cookie headers if we reopen the
//session for writing, and this breaks IE8 and some other browsers.
//This hacky workaround prevents double headers. Background here:
// https://bugs.php.net/bug.php?id=38104
ini_set('session.cache_limiter', null);
session_start(); // Reopen the session for writing (without resending the Set-Cookie header)
}
}

View file

@ -0,0 +1,372 @@
<?php
/**
* Class TaskManager
*
* Background class for 'asynchronous' task management for Airtime stations
*/
final class TaskManager {
/**
* @var array tasks to be run. Maps task names to a boolean value denoting
* whether the task has been checked/run
*/
protected $_taskList;
/**
* @var TaskManager singleton instance object
*/
protected static $_instance;
/**
* @var int TASK_INTERVAL_SECONDS how often, in seconds, to run the TaskManager tasks
*/
const TASK_INTERVAL_SECONDS = 30;
/**
*
* @var $con PDO Propel connection object
*/
private $_con;
/**
* Private constructor so class is uninstantiable
*/
private function __construct() {
foreach (TaskFactory::getTasks() as $k => $task) {
$this->_taskList[$task] = false;
}
}
/**
* Get the singleton instance of this class
*
* @return TaskManager the TaskManager instance
*/
public static function getInstance() {
if (!self::$_instance) {
self::$_instance = new TaskManager();
}
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.
*
* To prevent blocking and making too many requests to the database,
* we implement a row-level, non-blocking, read-protected lock on a
* timestamp that we check each time the application is bootstrapped,
* which, assuming enough time has passed, is updated before running
* the tasks.
*/
public function runTasks() {
// If there is data in auth storage, this could be a user request
// so we should just return to avoid blocking
if ($this->_isUserSessionRequest()) {
return;
}
$this->_con = Propel::getConnection(CcPrefPeer::DATABASE_NAME);
$this->_con->beginTransaction();
try {
$lock = $this->_getLock();
if ($lock && (microtime(true) < ($lock['valstr'] + self::TASK_INTERVAL_SECONDS))) {
// Propel caches the database connection and uses it persistently, so if we don't
// use commit() here, we end up blocking other queries made within this request
$this->_con->commit();
return;
}
$this->_updateLock($lock);
$this->_con->commit();
} catch (Exception $e) {
// We get here if there are simultaneous requests trying to fetch the lock row
$this->_con->rollBack();
Logging::warn($e->getMessage());
return;
}
foreach ($this->_taskList as $task => $hasTaskRun) {
if (!$hasTaskRun) {
$this->runTask($task);
}
}
}
/**
* Check if the current session is a user request
*
* @return bool true if there is a Zend_Auth object in the current session,
* otherwise false
*/
private function _isUserSessionRequest() {
if (!Zend_Session::isStarted()) {
return false;
}
$auth = Zend_Auth::getInstance();
$data = $auth->getStorage()->read();
return !empty($data);
}
/**
* Get the task_manager_lock from cc_pref with a row-level lock for atomicity
*
* The lock is exclusive (prevent reads) and will only last for the duration
* of the transaction. We add NOWAIT so reads on the row during the transaction
* won't block
*
* @return array|bool an array containing the row values, or false on failure
*/
private function _getLock() {
$sql = "SELECT * FROM cc_pref WHERE keystr='task_manager_lock' LIMIT 1 FOR UPDATE NOWAIT";
$st = $this->_con->prepare($sql);
$st->execute();
return $st->fetch();
}
/**
* Update and commit the new lock value, or insert it if it doesn't exist
*
* @param $lock array cc_pref lock row values
*/
private function _updateLock($lock) {
$sql = empty($lock) ? "INSERT INTO cc_pref (keystr, valstr) VALUES ('task_manager_lock', :value)"
: "UPDATE cc_pref SET valstr=:value WHERE keystr='task_manager_lock'";
$st = $this->_con->prepare($sql);
$st->execute(array(":value" => microtime(true)));
}
}
/**
* Interface AirtimeTask Interface for task operations
*/
interface AirtimeTask {
/**
* Check whether the task should be run
*
* @return bool true if the task needs to be run, otherwise false
*/
public function shouldBeRun();
/**
* Run the task
*
* @return void
*/
public function run();
}
/**
* Class UpgradeTask
*
* Checks the current Airtime version and runs any outstanding upgrades
*/
class UpgradeTask implements AirtimeTask {
/**
* Check the current Airtime schema version to see if an upgrade should be run
*
* @return bool true if an upgrade is needed
*/
public function shouldBeRun() {
return UpgradeManager::checkIfUpgradeIsNeeded();
}
/**
* Run all upgrades above the current schema version
*/
public function run() {
UpgradeManager::doUpgrade();
}
}
/**
* Class CeleryTask
*
* Checks the Celery broker task queue and runs callbacks for completed tasks
*/
class CeleryTask implements AirtimeTask {
/**
* Check the ThirdPartyTrackReferences table to see if there are any pending tasks
*
* @return bool true if there are pending tasks in ThirdPartyTrackReferences
*/
public function shouldBeRun() {
return !CeleryManager::isBrokerTaskQueueEmpty();
}
/**
* Poll the task queue for any completed Celery tasks
*/
public function run() {
CeleryManager::pollBrokerTaskQueue();
}
}
/**
* Class AutoPlaylistTask
*
* Checks for shows with an autoplaylist that needs to be filled in
*
*/
class AutoPlaylistTask implements AirtimeTask
{
/**
* Checks whether or not the autoplaylist polling interval has passed
*
* @return bool true if the autoplaylist polling interval has passed
*/
public function shouldBeRun()
{
return AutoPlaylistManager::hasAutoPlaylistPollIntervalPassed();
}
/**
* Schedule the autoplaylist for the shows
*/
public function run()
{
AutoPlaylistManager::buildAutoPlaylist();
}
}
/**
* Class PodcastTask
*
* Checks podcasts marked for automatic ingest and downloads any new episodes
* since the task was last run
*/
class PodcastTask implements AirtimeTask {
/**
* Check whether or not the podcast polling interval has passed
*
* @return bool true if the podcast polling interval has passed
*/
public function shouldBeRun() {
$overQuota = Application_Model_Systemstatus::isDiskOverQuota();
return !$overQuota && PodcastManager::hasPodcastPollIntervalPassed();
}
/**
* Download the latest episode for all podcasts flagged for automatic ingest
*/
public function run() {
PodcastManager::downloadNewestEpisodes();
}
}
/**
* Class ImportTask
*/
class ImportCleanupTask implements AirtimeTask {
/**
* Check if there are any files that have been stuck
* in Pending status for over an hour
*
* @return bool true if there are any files stuck pending,
* otherwise false
*/
public function shouldBeRun() {
return Application_Service_MediaService::areFilesStuckInPending();
}
/**
* Clean up stuck imports by changing their import status to Failed
*/
public function run() {
Application_Service_MediaService::clearStuckPendingImports();
}
}
/**
* Class StationPodcastTask
*
* Checks the Station podcast rollover timer and resets allotted
* downloads if enough time has passed (default: 1 month)
*/
class StationPodcastTask implements AirtimeTask {
const STATION_PODCAST_RESET_TIMER_SECONDS = 2.628e+6; // 1 month
/**
* Check whether or not the download counter for the station podcast should be reset
*
* @return bool true if enough time has passed
*/
public function shouldBeRun() {
$lastReset = Application_Model_Preference::getStationPodcastDownloadResetTimer();
return empty($lastReset) || (microtime(true) > ($lastReset + self::STATION_PODCAST_RESET_TIMER_SECONDS));
}
/**
* Reset the station podcast download counter
*/
public function run() {
Application_Model_Preference::resetStationPodcastDownloadCounter();
Application_Model_Preference::setStationPodcastDownloadResetTimer(microtime(true));
}
}
/**
* Class TaskFactory Factory class to abstract task instantiation
*/
class TaskFactory {
/**
* Check if the class with the given name implements AirtimeTask
*
* @param $c string class name
*
* @return bool true if the class $c implements AirtimeTask
*/
private static function _isTask($c) {
return array_key_exists('AirtimeTask', class_implements($c));
}
/**
* Filter all declared classes to get all classes implementing the AirtimeTask interface
*
* @return array all classes implementing the AirtimeTask interface
*/
public static function getTasks() {
return array_filter(get_declared_classes(), array(__CLASS__, "_isTask"));
}
/**
* Get an AirtimeTask based on class name
*
* @param $task string name of the class implementing AirtimeTask to construct
*
* @return AirtimeTask|null return a task of the given type or null if no corresponding task exists
*/
public static function getTask($task) {
// Try to get a valid class name from the given string
if (!class_exists($task)) $task = str_replace(' ', '', ucwords($task)) . "Task";
return class_exists($task) ? new $task() : null;
}
}

View file

@ -0,0 +1,32 @@
<?php
class Application_Common_Timezone
{
public static function getTimezones()
{
$regions = array(
'Africa' => DateTimeZone::AFRICA,
'America' => DateTimeZone::AMERICA,
'Antarctica' => DateTimeZone::ANTARCTICA,
'Arctic' => DateTimeZone::ARCTIC,
'Asia' => DateTimeZone::ASIA,
'Atlantic' => DateTimeZone::ATLANTIC,
'Australia' => DateTimeZone::AUSTRALIA,
'Europe' => DateTimeZone::EUROPE,
'Indian' => DateTimeZone::INDIAN,
'Pacific' => DateTimeZone::PACIFIC,
'UTC' => DateTimeZone::UTC
);
$tzlist = array(NULL => _("Use station default"));
foreach ($regions as $name => $mask) {
$ids = DateTimeZone::listIdentifiers($mask);
foreach ($ids as $id) {
$tzlist[$id] = str_replace("_", " ", $id);
}
}
return $tzlist;
}
}

View file

@ -0,0 +1,44 @@
<?php
class Application_Common_TuneIn
{
/**
* @param $title url encoded string
* @param $artist url encoded string
*/
public static function sendMetadataToTunein($title, $artist)
{
$credQryStr = self::getCredentialsQueryString();
$metadataQryStr = "&title=".$title."&artist=".$artist."&commercial=false";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, TUNEIN_API_URL . $credQryStr . $metadataQryStr);
curl_setopt($ch, CURLOPT_FAILONERROR, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$xmlResponse = curl_exec($ch);
if (curl_error($ch)) {
Logging::error("Failed to reach TuneIn: ". curl_errno($ch)." - ". curl_error($ch) . " - " . curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
}
curl_close($ch);
$xmlObj = new SimpleXMLElement($xmlResponse);
if (!$xmlObj || $xmlObj->head->status != "200") {
Logging::info("Error occurred pushing metadata to TuneIn:");
Logging::info($xmlResponse);
} else if ($xmlObj->head->status == "200") {
Application_Model_Preference::setLastTuneinMetadataUpdate(time());
}
}
private static function getCredentialsQueryString() {
$tuneInStationID = Application_Model_Preference::getTuneinStationId();
$tuneInPartnerID = Application_Model_Preference::getTuneinPartnerId();
$tuneInPartnerKey = Application_Model_Preference::getTuneinPartnerKey();
return "?partnerId=".$tuneInPartnerID."&partnerKey=".$tuneInPartnerKey."&id=".$tuneInStationID;
}
}

View file

@ -0,0 +1,221 @@
<?php
class Application_Common_UsabilityHints
{
/**
* @param $userPath User's current location in Airtime (i.e. /Plupload)
* @return string
*/
public static function getUsabilityHint($userPath=null)
{
// We want to display hints in this order:
// 1. Check if files are uploaded
// 2. Check if a show is scheduled
// 3. Check if current or next show needs content
// Once the user is on the page linked to from the hint we want to
// display a new message further describing what to do. Once this
// action has been done we can hide the message and get the next
// usability hint, if there is one.
$userIsOnCalendarPage = false;
$userIsOnAddMediaPage = false;
$userIsOnShowbuilderPage = false;
$userIsSuperAdmin = Application_Model_User::getCurrentUser()->isSuperAdmin();
// If $userPath is set the request came from AJAX so the user's
// current location inside Airtime gets passed in to this function.
if (!is_null($userPath)) {
// We check if the controller names are in the user's current location
// so we can ignore leading or trailing slashes, special characters like '#',
// and additional controller action names like '/user/add-user'
if (strpos(strtolower($userPath), 'plupload') !== false) {
$userIsOnAddMediaPage = true;
}
if (strpos(strtolower($userPath), 'schedule') !== false) {
$userIsOnCalendarPage = true;
}
if (strpos(strtolower($userPath), 'showbuilder') !== false) {
$userIsOnShowbuilderPage = true;
}
} else {
// If $userPath is not set the request came from inside Airtime so
// we can use Zend's Front Controller to get the user's current location.
$currentController = strtolower(Zend_Controller_Front::getInstance()->getRequest()->getControllerName());
if ($currentController == "schedule") {
$userIsOnCalendarPage = true;
}
if ($currentController == "plupload") {
$userIsOnAddMediaPage = true;
}
if ($currentController == 'showbuilder') {
$userIsOnShowbuilderPage = true;
}
}
if (self::zeroFilesUploaded()) {
if ($userIsOnAddMediaPage) {
return _("Upload some tracks below to add them to your library!");
} else {
return sprintf(_("It looks like you haven't uploaded any audio files yet. %sUpload a file now%s."),
"<a href=\"/plupload\">",
"</a>");
}
} else if (!self::isFutureOrCurrentShowScheduled()) {
if ($userIsOnCalendarPage) {
return _("Click the 'New Show' button and fill out the required fields.");
} else {
return sprintf(_("It looks like you don't have any shows scheduled. %sCreate a show now%s."),
"<a href=\"/schedule\">",
"</a>");
}
} else if (self::isCurrentShowEmpty()) {
// If the current show is linked users cannot add content to it so we have to provide a different message.
if (self::isCurrentShowLinked()) {
if ($userIsOnCalendarPage) {
return _("To start broadcasting, cancel the current linked show by clicking on it and selecting 'Cancel Show'.");
} else {
return sprintf(_("Linked shows need to be filled with tracks before it starts. To start broadcasting cancel the current linked show and schedule an unlinked show.
%sCreate an unlinked show now%s."), "<a href=\"/schedule\">", "</a>");
}
} else {
if ($userIsOnCalendarPage) {
return _("To start broadcasting, click on the current show and select 'Schedule Tracks'");
} else {
return sprintf(_("It looks like the current show needs more tracks. %sAdd tracks to your show now%s."),
"<a href=\"/schedule\">",
"</a>");
}
}
} else if (!self::getCurrentShow() && self::isNextShowEmpty()) {
if ($userIsOnCalendarPage) {
return _("Click on the show starting next and select 'Schedule Tracks'");
} else {
return sprintf(_("It looks like the next show is empty. %sAdd tracks to your show now%s."),
"<a href=\"/schedule\">",
"</a>");
}
}
return "";
}
/**
* Returns true if no files have been uploaded.
*/
private static function zeroFilesUploaded()
{
$fileCount = CcFilesQuery::create()
->filterByDbFileExists(true)
->filterByDbHidden(false)
->count();
if ($fileCount == 0) {
return true;
} else {
return false;
}
}
/**
* Returns true if there is at least one show currently scheduled
* or in the future.
*/
private static function isFutureOrCurrentShowScheduled()
{
$futureShow = self::getNextFutureShow();
$currentShow = self::getCurrentShow();
if (is_null($futureShow) && is_null($currentShow)) {
return false;
} else {
return true;
}
}
private static function isCurrentShowEmpty()
{
$currentShow = self::getCurrentShow();
if (is_null($currentShow)) {
return false;
} else {
$now = new DateTime("now", new DateTimeZone("UTC"));
$scheduledTracks = CcScheduleQuery::create()
->filterByDbInstanceId($currentShow->getDbId())
->filterByDbEnds($now, Criteria::GREATER_EQUAL)
->find();
if ($scheduledTracks->count() == 0) {
return true;
} else {
return false;
}
}
}
private static function isNextShowEmpty()
{
$futureShow = self::getNextFutureShow();
if (is_null($futureShow)) {
return false;
} else {
$now = new DateTime("now", new DateTimeZone("UTC"));
$scheduledTracks = CcScheduleQuery::create()
->filterByDbInstanceId($futureShow->getDbId())
->filterByDbStarts($now, Criteria::GREATER_EQUAL)
->find();
if ($scheduledTracks->count() == 0) {
return true;
} else {
return false;
}
}
}
private static function getCurrentShow()
{
$now = new DateTime("now", new DateTimeZone("UTC"));
return CcShowInstancesQuery::create()
->filterByDbStarts($now, Criteria::LESS_THAN)
->filterByDbEnds($now, Criteria::GREATER_THAN)
->filterByDbModifiedInstance(false)
->findOne();
}
private static function getNextFutureShow()
{
$now = new DateTime("now", new DateTimeZone("UTC"));
return CcShowInstancesQuery::create()
->filterByDbStarts($now, Criteria::GREATER_THAN)
->filterByDbModifiedInstance(false)
->orderByDbStarts()
->findOne();
}
private static function isCurrentShowLinked()
{
$currentShow = self::getCurrentShow();
if (!is_null($currentShow)) {
$show = CcShowQuery::create()
->filterByDbId($currentShow->getDbShowId())
->findOne();
if ($show->isLinked()) {
return true;
} else {
return false;
}
} else {
return false;
}
}
}

View file

@ -0,0 +1,175 @@
<?php
define("DAYS_PER_WEEK", 7);
class WidgetHelper
{
public static function getWeekInfo($userDefinedTimezone)
{
//weekStart is in station time.
$weekStartDateTime = Application_Common_DateHelper::getWeekStartDateTime();
$dow = array("monday", "tuesday", "wednesday", "thursday", "friday",
"saturday", "sunday", "nextmonday", "nexttuesday", "nextwednesday",
"nextthursday", "nextfriday", "nextsaturday", "nextsunday");
$result = array();
// default to the station timezone
$timezone = Application_Model_Preference::GetDefaultTimezone();
if ($userDefinedTimezone) {
$userDefinedTimezone = strtolower($userDefinedTimezone);
// if the timezone defined by the user exists, use that
if (array_key_exists($userDefinedTimezone, timezone_abbreviations_list())) {
$timezone = $userDefinedTimezone;
}
}
$utcTimezone = new DateTimeZone("UTC");
$weekStartDateTime->setTimezone($utcTimezone);
$utcDayStart = $weekStartDateTime->format(DEFAULT_TIMESTAMP_FORMAT);
for ($i = 0; $i < 14; $i++) {
//have to be in station timezone when adding 1 day for daylight savings.
$weekStartDateTime->setTimezone(new DateTimeZone($timezone));
$weekStartDateTime->add(new DateInterval('P1D'));
//convert back to UTC to get the actual timestamp used for search.
$weekStartDateTime->setTimezone($utcTimezone);
$utcDayEnd = $weekStartDateTime->format(DEFAULT_TIMESTAMP_FORMAT);
$shows = Application_Model_Show::getNextShows($utcDayStart, "ALL", $utcDayEnd);
$utcDayStart = $utcDayEnd;
// convert to user-defined timezone, or default to station
Application_Common_DateHelper::convertTimestampsToTimezone(
$shows,
array("starts", "ends", "start_timestamp","end_timestamp"),
$timezone
);
$result[$dow[$i]] = $shows;
}
// XSS exploit prevention
SecurityHelper::htmlescape_recursive($result);
// convert image paths to point to api endpoints
self::findAndConvertPaths($result);
return $result;
}
/**
* Returns a weeks worth of shows in UTC, and an info array of the current week's days.
* 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.
*
* *** This function does no HTML encoding. It is up to the caller to escape or encode the data appropriately.
*
* @return array
*/
public static function getWeekInfoV2()
{
$weekStartDateTime = new DateTime("now", new DateTimeZone(Application_Model_Preference::GetTimezone()));
$result = array();
$utcTimezone = new DateTimeZone("UTC");
$weekStartDateTime->setTimezone($utcTimezone);
// Use this variable as the start date/time range when querying
// for shows. We set it to 1 day prior to the beginning of the
// schedule widget data to account for show date changes when
// converting their start day/time to the client's local timezone.
$showQueryDateRangeStart = clone $weekStartDateTime;
$showQueryDateRangeStart->sub(new DateInterval("P1D"));
$showQueryDateRangeStart->setTime(0, 0, 0);
for ($dayOfWeekCounter = 0; $dayOfWeekCounter < DAYS_PER_WEEK; $dayOfWeekCounter++) {
$dateParse = date_parse($weekStartDateTime->format("Y-m-d H:i:s"));
// Associate data to its date so that when we convert this array
// 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));
$weekStartDateTime->add(new DateInterval('P1D'));
//convert back to UTC to get the actual timestamp used for search.
$weekStartDateTime->setTimezone($utcTimezone);
}
// Use this variable as the end date/time range when querying
// for shows. We set it to 1 day after the end of the schedule
// widget data to account for show date changes when converting
// their start day/time to the client's local timezone.
$showQueryDateRangeEnd = clone $weekStartDateTime;
$showQueryDateRangeEnd->setTime(23, 59, 0);
$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) {
$dtStarts = new DateTime($show["starts"], new DateTimeZone("UTC"));
$show["starts_timestamp"] = $dtStarts->getTimestamp();
$dtEnds = new DateTime($show["ends"], new DateTimeZone("UTC"));
$show["ends_timestamp"] = $dtEnds->getTimestamp();
}
$result["shows"] = $shows;
// convert image paths to point to api endpoints
//TODO: do we need this here?
self::findAndConvertPaths($result);
return $result;
}
/**
* Recursively find image_path keys in the various $result subarrays,
* and convert them to point to the show-logo endpoint
*
* @param unknown $arr the array to search
*/
public static function findAndConvertPaths(&$arr)
{
$CC_CONFIG = Config::getConfig();
$baseDir = Application_Common_OsPath::formatDirectoryWithDirectorySeparators($CC_CONFIG['baseDir']);
foreach ($arr as &$a) {
if (is_array($a)) {
if (array_key_exists("image_path", $a)) {
$a["image_path"] = $a["image_path"] && $a["image_path"] !== '' ?
Application_Common_HTTPHelper::getStationUrl()."api/show-logo?id=".$a["id"] : '';
} else {
self::findAndConvertPaths($a);
}
}
}
}
}

View file

@ -0,0 +1,12 @@
<?php
final class HttpRequestType {
const GET = "GET";
const POST = "POST";
const PUT = "PUT";
const DELETE = "DELETE";
const PATCH = "PATCH";
const OPTIONS = "OPTIONS";
}

View file

@ -0,0 +1,17 @@
<?php
final class MediaType {
const __default = self::FILE;
const FILE = 1;
const PLAYLIST = 2;
const BLOCK = 3;
const WEBSTREAM = 4;
const PODCAST = 5;
public static function getDefault() {
return static::__default;
}
}

View file

@ -0,0 +1,31 @@
<?php
interface OAuth2 {
/**
* Check whether an OAuth access token exists
*
* @return bool true if an access token exists, otherwise false
*/
public function hasAccessToken();
/**
* Get the OAuth authorization URL
*
* @return string the authorization URL
*/
public function getAuthorizeUrl();
/**
* Request a new OAuth access token and store it in CcPref
*
* @param $code string exchange authorization code for access token
*/
public function requestNewAccessToken($code);
/**
* Regenerate the OAuth access token
*/
public function accessTokenRefresh();
}

View file

@ -0,0 +1,27 @@
<?php
interface OAuth2Controller {
/**
* Send user to a third-party service to authorize before being redirected
*
* @return void
*/
public function authorizeAction();
/**
* Clear the previously saved request token from the preferences
*
* @return void
*/
public function deauthorizeAction();
/**
* Called when user successfully completes third-party authorization
* Store the returned request token for future requests
*
* @return void
*/
public function redirectAction();
}

View file

@ -0,0 +1,36 @@
<?php
interface Publish {
/**
* Publish the file with the given file ID
*
* @param int $fileId ID of the file to be published
*
* @return void
*/
public function publish($fileId);
/**
* Unpublish the file with the given file ID
*
* @param int $fileId ID of the file to be unpublished
*
* @return void
*/
public function unpublish($fileId);
/**
* Fetch the publication status for the file with the given ID
*
* @param int $fileId the ID of the file to check
*
* @return int 1 if the file has been published,
* 0 if the file has yet to be published,
* -1 if the file is in a pending state,
* 2 if the source is unreachable (disconnected)
*/
public function getPublishStatus($fileId);
}

View file

@ -0,0 +1,30 @@
<?php
/**
* Created by PhpStorm.
* User: asantoni
* Date: 11/09/15
* Time: 2:47 PM
*/
class AirtimeTableView {
private static function _getTableJavaScriptDependencies() {
return ['js/airtime/widgets/table.js',
'js/datatables/js/jquery.dataTables.js',
'js/datatables/plugin/dataTables.pluginAPI.js',
'js/datatables/plugin/dataTables.fnSetFilteringDelay.js',
'js/datatables/plugin/dataTables.ColVis.js',
'js/datatables/plugin/dataTables.colReorder.min.js?',
'js/datatables/plugin/dataTables.FixedColumns.js',
'js/datatables/plugin/dataTables.FixedHeader.js',
'js/datatables/plugin/dataTables.columnFilter.js?'];
}
public static function injectTableJavaScriptDependencies(&$headScript, $baseUrl, $airtimeVersion)
{
$deps = self::_getTableJavaScriptDependencies();
for ($i = 0; $i < count($deps); $i++) {
$headScript->appendFile($baseUrl . $deps[$i] .'?'. $airtimeVersion, 'text/javascript');
}
}
}