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,400 @@
<?php
class Application_Service_CalendarService
{
private $currentUser;
private $ccShowInstance;
private $ccShow;
public function __construct($instanceId = null)
{
if (!is_null($instanceId)) {
$this->ccShowInstance = CcShowInstancesQuery::create()->findPk($instanceId);
if (is_null($this->ccShowInstance)) {
throw new Exception("Instance does not exist");
}
$this->ccShow = $this->ccShowInstance->getCcShow();
}
$service_user = new Application_Service_UserService();
$this->currentUser = $service_user->getCurrentUser();
}
/**
*
* Enter description here ...
*/
public function makeContextMenu()
{
$menu = array();
$now = time();
$baseUrl = Application_Common_OsPath::getBaseDir();
$isAdminOrPM = $this->currentUser->isAdminOrPM();
$isHostOfShow = $this->currentUser->isHostOfShow($this->ccShow->getDbId());
//DateTime objects in UTC
$startDT = $this->ccShowInstance->getDbStarts(null);
$endDT = $this->ccShowInstance->getDbEnds(null);
//timestamps
$start = $startDT->getTimestamp();
$end = $endDT->getTimestamp();
//show has ended
if ($now > $end) {
if ($this->ccShowInstance->isRecorded()) {
$ccFile = $this->ccShowInstance->getCcFiles();
if (!isset($ccFile)) {
$menu["error when recording"] = array (
"name" => _("Record file doesn't exist"),
"icon" => "error");
}else {
$menu["view_recorded"] = array(
"name" => _("View Recorded File Metadata"),
"icon" => "overview",
"url" => $baseUrl."library/edit-file-md/id/".$ccFile->getDbId());
}
} else {
$menu["content"] = array(
// "name"=> _("Show Content"),
"name"=> _("View"),
"icon" => "overview",
"url" => $baseUrl."schedule/show-content-dialog");
}
} else {
// Show content can be modified from the calendar if:
// the user is admin or hosting the show,
// the show is not recorded
$currentShow = Application_Model_Show::getCurrentShow();
$currentShowId = count($currentShow) == 1 ? $currentShow[0]["id"] : null;
$showIsLinked = $this->ccShow->isLinked();
//user can add/remove content if the show has not ended
if ($now < $end && ($isAdminOrPM || $isHostOfShow) && !$this->ccShowInstance->isRecorded()) {
//if the show is not linked OR if the show is linked AND not the current playing show
//the user can add/remove content
if (!$showIsLinked || ($showIsLinked && $currentShowId != $this->ccShow->getDbId())) {
$menu["schedule"] = array(
// "name"=> _("Add / Remove Content"),
"name" => _("Schedule Tracks"),
"icon" => "add-remove-content",
"url" => $baseUrl."showbuilder/builder-dialog/");
}
}
//"Show Content" should be a menu item at all times except when
//the show is recorded
if (!$this->ccShowInstance->isRecorded()) {
$menu["content"] = array(
// "name"=> _("Show Content"),
"name"=> _("View"),
"icon" => "overview",
"url" => $baseUrl."schedule/show-content-dialog");
}
//user can remove all content if the show has not started
if ($now < $start && ($isAdminOrPM || $isHostOfShow) && !$this->ccShowInstance->isRecorded() ) {
//if the show is not linked OR if the show is linked AND not the current playing show
//the user can remove all content
if (!$showIsLinked || ($showIsLinked && $currentShowId != $this->ccShow->getDbId())) {
$menu["clear"] = array(
// "name"=> _("Remove All Content"),
"name"=> _("Clear Show"),
"icon" => "remove-all-content",
"url" => $baseUrl."schedule/clear-show");
}
}
//show is currently playing and user is admin
if ($start <= $now && $now < $end && $isAdminOrPM) {
// Menu separator
$menu["sep1"] = "-----------";
if ($this->ccShowInstance->isRecorded()) {
$menu["cancel_recorded"] = array(
// "name"=> _("Cancel Current Show"),
"name"=> _("Cancel Show"),
"icon" => "delete");
} else {
$menu["cancel"] = array(
// "name"=> _("Cancel Current Show"),
"name"=> _("Cancel Show"),
"icon" => "delete");
}
}
$excludeIds = $this->ccShow->getEditedRepeatingInstanceIds();
$isRepeating = $this->ccShow->isRepeating();
$populateInstance = false;
if ($isRepeating && in_array($this->ccShowInstance->getDbId(), $excludeIds)) {
$populateInstance = true;
}
if (!$this->ccShowInstance->isRebroadcast() && $isAdminOrPM) {
// Menu separator
$menu["sep2"] = "-----------";
if ($isRepeating) {
if ($populateInstance) {
$menu["edit"] = array(
// "name" => _("Edit This Instance"),
"name" => _("Edit Instance"),
"icon" => "edit",
"url" => $baseUrl . "Schedule/populate-repeating-show-instance-form"
);
} else {
$menu["edit"] = array(
"name" => _("Edit"),
"icon" => "edit",
"items" => array()
);
$menu["edit"]["items"]["all"] = array(
"name" => _("Edit Show"),
"icon" => "edit",
"url" => $baseUrl . "Schedule/populate-show-form"
);
$menu["edit"]["items"]["instance"] = array(
// "name" => _("Edit This Instance"),
"name" => _("Edit Instance"),
"icon" => "edit",
"url" => $baseUrl . "Schedule/populate-repeating-show-instance-form"
);
}
} else {
$menu["edit"] = array(
"name"=> _("Edit Show"),
"icon" => "edit",
"_type"=>"all",
"url" => $baseUrl."Schedule/populate-show-form");
}
}
//show hasn't started yet and user is admin
if ($now < $start && $isAdminOrPM) {
// Menu separator
$menu["sep3"] = "-----------";
//show is repeating so give user the option to delete all
//repeating instances or just the one
if ($isRepeating) {
$menu["del"] = array(
"name"=> _("Delete"),
"icon" => "delete",
"items" => array());
$menu["del"]["items"]["single"] = array(
// "name"=> _("Delete This Instance"),
"name"=> _("Delete Instance"),
"icon" => "delete",
"url" => $baseUrl."schedule/delete-show-instance");
$menu["del"]["items"]["following"] = array(
// "name"=> _("Delete This Instance and All Following"),
"name"=> _("Delete Instance and All Following"),
"icon" => "delete",
"url" => $baseUrl."schedule/delete-show");
} elseif ($populateInstance) {
$menu["del"] = array(
"name"=> _("Delete"),
"icon" => "delete",
"url" => $baseUrl."schedule/delete-show-instance");
} else {
$menu["del"] = array(
"name"=> _("Delete"),
"icon" => "delete",
"url" => $baseUrl."schedule/delete-show");
}
}
}
return $menu;
}
/**
*
* Enter description here ...
* @param DateTime $dateTime object to add deltas to
* @param int $deltaDay delta days show moved
* @param int $deltaMin delta minutes show moved
*/
public static function addDeltas($dateTime, $deltaDay, $deltaMin)
{
$newDateTime = clone $dateTime;
$days = abs($deltaDay);
$mins = abs($deltaMin);
$dayInterval = new DateInterval("P{$days}D");
$minInterval = new DateInterval("PT{$mins}M");
if ($deltaDay > 0) {
$newDateTime->add($dayInterval);
} elseif ($deltaDay < 0) {
$newDateTime->sub($dayInterval);
}
if ($deltaMin > 0) {
$newDateTime->add($minInterval);
} elseif ($deltaMin < 0) {
$newDateTime->sub($minInterval);
}
return $newDateTime;
}
private function validateShowMove($deltaDay, $deltaMin)
{
if (!$this->currentUser->isAdminOrPM()) {
throw new Exception(_("Permission denied"));
}
if ($this->ccShow->isRepeating()) {
throw new Exception(_("Can't drag and drop repeating shows"));
}
$today_timestamp = time();
$startsDateTime = $this->ccShowInstance->getDbStarts(null);
$endsDateTime = $this->ccShowInstance->getDbEnds(null);
if ($today_timestamp > $startsDateTime->getTimestamp()) {
throw new Exception(_("Can't move a past show"));
}
//the user is moving the show on the calendar from the perspective of local time.
//incase a show is moved across a time change border offsets should be added to the localtime
//stamp and then converted back to UTC to avoid show time changes!
$showTimezone = $this->ccShow->getFirstCcShowDay()->getDbTimezone();
$startsDateTime->setTimezone(new DateTimeZone($showTimezone));
$endsDateTime->setTimezone(new DateTimeZone($showTimezone));
$duration = $startsDateTime->diff($endsDateTime);
$newStartsDateTime = self::addDeltas($startsDateTime, $deltaDay, $deltaMin);
/* WARNING: Do not separately add a time delta to the start and end times because
that does not preserve the duration across a DST time change.
For example, 5am - 3 hours = 3am when DST occurs at 2am.
BUT, 6am - 3 hours = 3am also!
So when a DST change occurs, adding the deltas like this
separately does not conserve the duration of a show.
Since that's what we want (otherwise we'll get a zero length show),
we calculate the show duration FIRST, then we just add that on
to the start time to calculate the end time.
This is a safer approach.
The key lesson here is that in general: duration != end - start
... so be careful!
*/
//$newEndsDateTime = self::addDeltas($endsDateTime, $deltaDay, $deltaMin); <--- Wrong, don't do it.
$newEndsDateTime = clone $newStartsDateTime;
$newEndsDateTime = $newEndsDateTime->add($duration);
//convert our new starts/ends to UTC.
$newStartsDateTime->setTimezone(new DateTimeZone("UTC"));
$newEndsDateTime->setTimezone(new DateTimeZone("UTC"));
if ($today_timestamp > $newStartsDateTime->getTimestamp()) {
throw new Exception(_("Can't move show into past"));
}
//check if show is overlapping
$overlapping = Application_Model_Schedule::checkOverlappingShows(
$newStartsDateTime, $newEndsDateTime, true, $this->ccShowInstance->getDbId());
if ($overlapping) {
throw new Exception(_("Cannot schedule overlapping shows"));
}
if ($this->ccShow->isRecorded()) {
//rebroadcasts should start at max 1 hour after a recorded show has ended.
$minRebroadcastStart = self::addDeltas($newEndsDateTime, 0, 60);
//check if we are moving a recorded show less than 1 hour before any of its own rebroadcasts.
$rebroadcasts = CcShowInstancesQuery::create()
->filterByDbOriginalShow($this->ccShow->getDbId())
->filterByDbStarts($minRebroadcastStart->format(DEFAULT_TIMESTAMP_FORMAT), Criteria::LESS_THAN)
->find();
if (count($rebroadcasts) > 0) {
throw new Exception(_("Can't move a recorded show less than 1 hour before its rebroadcasts."));
}
}
if ($this->ccShow->isRebroadcast()) {
$recordedShow = CcShowInstancesQuery::create()
->filterByCcShow($this->ccShowInstance->getDbOriginalShow())
->findOne();
if (is_null($recordedShow)) {
$this->ccShowInstance->delete();
throw new Exception(_("Show was deleted because recorded show does not exist!"));
}
$recordEndDateTime = new DateTime($recordedShow->getDbEnds(), new DateTimeZone("UTC"));
$newRecordEndDateTime = self::addDeltas($recordEndDateTime, 0, 60);
if ($newStartsDateTime->getTimestamp() < $newRecordEndDateTime->getTimestamp()) {
throw new Exception(_("Must wait 1 hour to rebroadcast."));
}
}
return array($newStartsDateTime, $newEndsDateTime);
}
public function moveShow($deltaDay, $deltaMin)
{
try {
$con = Propel::getConnection();
$con->beginTransaction();
//new starts,ends are in UTC
list($newStartsDateTime, $newEndsDateTime) = $this->validateShowMove(
$deltaDay, $deltaMin);
$oldStartDateTime = $this->ccShowInstance->getDbStarts(null);
$this->ccShowInstance
->setDbStarts($newStartsDateTime)
->setDbEnds($newEndsDateTime)
->save($con);
if (!$this->ccShowInstance->getCcShow()->isRebroadcast()) {
//we can get the first show day because we know the show is
//not repeating, and therefore will only have one show day entry
$ccShowDay = $this->ccShow->getFirstCcShowDay();
$showTimezone = new DateTimeZone($ccShowDay->getDbTimezone());
$ccShowDay
->setDbFirstShow($newStartsDateTime->setTimezone($showTimezone)->format("Y-m-d"))
->setDbStartTime($newStartsDateTime->format("H:i"))
->save($con);
}
$diff = $newStartsDateTime->getTimestamp() - $oldStartDateTime->getTimestamp();
Application_Service_SchedulerService::updateScheduleStartTime(
array($this->ccShowInstance->getDbId()), $diff);
$con->commit();
Application_Model_RabbitMq::PushSchedule();
} catch (Exception $e) {
$con->rollback();
return $e->getMessage();
}
}
//TODO move the method resizeShow from Application_Model_Show here.
public function resizeShow($deltaDay, $deltaMin)
{
try {
$con = Propel::getConnection();
$con->beginTransaction();
$con->commit();
Application_Model_RabbitMq::PushSchedule();
} catch (Exception $e) {
return $e->getMessage();
}
}
}

View file

@ -0,0 +1,21 @@
<?php
class CeleryServiceFactory {
/**
* Given an identifying string, get a ThirdPartyCeleryService object of that type
*
* @param $serviceName string the name of the service to create
*
* @return Application_Service_ThirdPartyCeleryService|null
*/
public static function getService($serviceName) {
switch($serviceName) {
case PODCAST_SERVICE_NAME:
return new Application_Service_PodcastEpisodeService();
default:
return null;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,153 @@
<?php
class Application_Service_MediaService
{
const PENDING_FILE_TIMEOUT_SECONDS = 3600;
/**
* @var array store an internal array of the pending files so we don't have
* to go to the database twice
*/
private static $_pendingFiles;
/** Move (or copy) a file to the stor/organize directory and send it off to the
analyzer to be processed.
* @param $callbackUrl
* @param $filePath string Path to the local file to import to the library
* @param $originalFilename string The original filename, if you want it to be preserved after import.
* @param $ownerId string The ID of the user that will own the file inside Airtime.
* @param $copyFile bool True if you want to copy the file to the "organize" directory, false if you want to move it (default)
* @return Ambigous
* @throws Exception
*/
public static function importFileToLibrary($callbackUrl, $filePath, $originalFilename, $ownerId, $copyFile)
{
$CC_CONFIG = Config::getConfig();
$apiKey = $CC_CONFIG["apiKey"][0];
$importedStorageDirectory = "";
if ($CC_CONFIG["current_backend"] == "file") {
$storDir = Application_Model_MusicDir::getStorDir();
$importedStorageDirectory = $storDir->getDirectory() . "/imported/" . $ownerId;
}
//Copy the temporary file over to the "organize" folder so that it's off our webserver
//and accessible by libretime-analyzer which could be running on a different machine.
$newTempFilePath = Application_Model_StoredFile::moveFileToStor($filePath, $originalFilename, $copyFile);
//Dispatch a message to libretime-analyzer through RabbitMQ,
//notifying it that there's a new upload to process!
$storageBackend = new ProxyStorageBackend($CC_CONFIG["current_backend"]);
Application_Model_RabbitMq::SendMessageToAnalyzer($newTempFilePath,
$importedStorageDirectory, basename($originalFilename),
$callbackUrl, $apiKey,
$CC_CONFIG["current_backend"],
$storageBackend->getFilePrefix());
return $newTempFilePath;
}
/**
* @param $fileId
* @param bool $inline Set the Content-Disposition header to inline to prevent a download dialog from popping up (or attachment if false)
* @throws Exception
* @throws LibreTimeFileNotFoundException
*/
public static function streamFileDownload($fileId, $inline=false)
{
$media = Application_Model_StoredFile::RecallById($fileId);
if ($media == null) {
throw new LibreTimeFileNotFoundException();
}
// Make sure we don't have some wrong result because of caching
clearstatcache();
$filePath = "";
if ($media->getPropelOrm()->isValidPhysicalFile()) {
$filename = $media->getPropelOrm()->getFilename();
//Download user left clicks a track and selects Download.
if (!$inline) {
//We are using Content-Disposition to specify
//to the browser what name the file should be saved as.
header('Content-Disposition: attachment; filename="' . $filename . '"');
} else {
//user clicks play button for track and downloads it.
header('Content-Disposition: inline; filename="' . $filename . '"');
}
/*
In this block of code below, we're getting the list of download URLs for a track
and then streaming the file as the response. A file can be stored in more than one location,
with the alternate locations used as a fallback, so that's why we're looping until we
are able to actually send the file.
This mechanism is used to try fetching our file from our internal S3 caching proxy server first.
If the file isn't found there (or the cache is down), then we attempt to download the file
directly from Amazon S3. We do this to save bandwidth costs!
*/
$filePaths = $media->getFilePaths();
assert(is_array($filePaths));
do {
//Read from $filePath and stream it to the browser.
$filePath = array_shift($filePaths);
try {
$size= $media->getFileSize();
$mimeType = $media->getPropelOrm()->getDbMime();
Application_Common_FileIO::smartReadFile($filePath, $size, $mimeType);
break; //Break out of the loop if we successfully read the file!
} catch (LibreTimeFileNotFoundException $e) {
//If we have no alternate filepaths left, then let the exception bubble up.
if (sizeof($filePaths) == 0) {
throw $e;
}
}
//Retry with the next alternate filepath in the list
} while (sizeof($filePaths) > 0);
exit;
} else {
throw new LibreTimeFileNotFoundException($filePath);
}
}
/**
* 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 static function areFilesStuckInPending() {
$oneHourAgo = gmdate(DEFAULT_TIMESTAMP_FORMAT, (microtime(true) - self::PENDING_FILE_TIMEOUT_SECONDS));
self::$_pendingFiles = CcFilesQuery::create()
->filterByDbImportStatus(CcFiles::IMPORT_STATUS_PENDING)
->filterByDbUtime($oneHourAgo, Criteria::LESS_EQUAL)
->find();
$pendingEpisodes = Application_Service_PodcastEpisodeService::getStuckPendingImports();
return !self::$_pendingFiles->isEmpty() || !empty($pendingEpisodes);
}
/**
* Clean up stuck imports by changing their import status to Failed
*/
public static function clearStuckPendingImports() {
$pendingEpisodes = Application_Service_PodcastEpisodeService::getStuckPendingImports();
foreach (self::$_pendingFiles as $file) {
/** @var $file CcFiles */
$file->setDbImportStatus(CcFiles::IMPORT_STATUS_FAILED)->save();
}
foreach ($pendingEpisodes as $episode) {
/** @var $episode PodcastEpisodes */
$episode->delete();
}
}
}

View file

@ -0,0 +1,503 @@
<?php
class PodcastEpisodeNotFoundException extends Exception {}
class DuplicatePodcastEpisodeException extends Exception {}
class Application_Service_PodcastEpisodeService extends Application_Service_ThirdPartyCeleryService implements Publish
{
/**
* Arbitrary constant identifiers for the internal tasks array
*/
const DOWNLOAD = 'download';
const PENDING_EPISODE_TIMEOUT_SECONDS = 900;
/**
* @var string service name to store in ThirdPartyTrackReferences database
*/
protected static $_SERVICE_NAME = PODCAST_SERVICE_NAME; // Service name constant from constants.php
/**
* @var string exchange name for Podcast tasks
*/
protected static $_CELERY_EXCHANGE_NAME = 'podcast';
/**
* @var array map of constant identifiers to Celery task names
*/
protected static $_CELERY_TASKS = [
self::DOWNLOAD => 'podcast-download'
];
private static $privateFields = array(
"id"
);
/**
* Utility function to import and download a single episode
*
* @param int $podcastId ID of the podcast the episode should belong to
* @param array $episode array of episode data to store
*
* @return PodcastEpisodes the stored PodcastEpisodes object
*/
public function importEpisode($podcastId, $episode) {
$e = $this->addPlaceholder($podcastId, $episode);
$p = $e->getPodcast();
$this->_download($e->getDbId(), $e->getDbDownloadUrl(), $p->getDbTitle(), $this->_getAlbumOverride($p), $episode["title"]);
return $e;
}
/**
* Given an array of episodes, store them in the database as placeholder objects until
* they can be processed by Celery
*
* @param int $podcastId Podcast object identifier
* @param array $episodes array of podcast episodes
*
* @return array the stored PodcastEpisodes objects
*/
public function addPodcastEpisodePlaceholders($podcastId, $episodes) {
$storedEpisodes = array();
foreach ($episodes as $episode) {
try {
$e = $this->addPlaceholder($podcastId, $episode);
} catch(DuplicatePodcastEpisodeException $ex) {
Logging::warn($ex->getMessage());
continue;
}
array_push($storedEpisodes, $e);
}
return $storedEpisodes;
}
/**
* Given an episode, store it in the database as a placeholder object until
* it can be processed by Celery
*
* @param int $podcastId Podcast object identifier
* @param array $episode array of podcast episode data
*
* @return PodcastEpisodes the stored PodcastEpisodes object
*
* @throws DuplicatePodcastEpisodeException
*/
public function addPlaceholder($podcastId, $episode) {
$existingEpisode = PodcastEpisodesQuery::create()->findOneByDbEpisodeGuid($episode["guid"]);
if (!empty($existingEpisode)) {
throw new DuplicatePodcastEpisodeException(sprintf("Episode already exists for podcast: %s, guid: %s\n", $episode['podcast_id'], $episode['guid']));
}
// We need to check whether the array is parsed directly from the SimplePie
// feed object, or whether it's passed in as json
$enclosure = $episode["enclosure"];
$url = $enclosure instanceof SimplePie_Enclosure ? $enclosure->get_link() : $enclosure["link"];
return $this->_buildEpisode($podcastId, $url, $episode["guid"], $episode["pub_date"], $episode["title"], $episode["description"]);
}
/**
* Given episode parameters, construct and store a basic PodcastEpisodes object
*
* @param int $podcastId the podcast the episode belongs to
* @param string $url the download URL for the episode
* @param string $guid the unique id for the episode. Often the same as the download URL
* @param string $publicationDate the publication date of the episode
* @param string $title the title of the episode
* @param string $description the description of the epsiode
*
* @return PodcastEpisodes the newly created PodcastEpisodes object
*
* @throws Exception
* @throws PropelException
*/
private function _buildEpisode($podcastId, $url, $guid, $publicationDate, $title = NULL, $description = NULL) {
$e = new PodcastEpisodes();
$e->setDbPodcastId($podcastId);
$e->setDbDownloadUrl($url);
$e->setDbEpisodeGuid($guid);
$e->setDbPublicationDate($publicationDate);
$e->setDbEpisodeTitle($title);
$e->setDbEpisodeDescription($description);
$e->save();
return $e;
}
/**
* Given an array of episodes, extract the IDs and download URLs and send them to Celery
*
* @param array $episodes array of podcast episodes
*/
public function downloadEpisodes($episodes) {
/** @var PodcastEpisodes $episode */
foreach($episodes as $episode) {
$podcast = $episode->getPodcast();
$this->_download($episode->getDbId(), $episode->getDbDownloadUrl(), $podcast->getDbTitle(), $this->_getAlbumOverride($podcast), $episode->getDbEpisodeTitle());
}
}
/**
* check if there is a podcast specific album override
*
* @param object $podcast podcast object
*
* @return boolean
*/
private function _getAlbumOverride($podcast) {
$override = Application_Model_Preference::GetPodcastAlbumOverride();
$podcast_override = $podcast->toArray();
$podcast_override = $podcast_override['DbAlbumOverride'];
if ($podcast_override) {
$override = $podcast_override;
}
return $override;
}
/**
* Given an episode ID and a download URL, send a Celery task
* to download an RSS feed track
*
* @param int $id episode unique ID
* @param string $url download url for the episode
* @param string $title title of podcast to be downloaded - added as album to track metadata
* @param boolean $album_override should we override the album name when downloading
*/
private function _download($id, $url, $title, $album_override, $track_title = null) {
$CC_CONFIG = Config::getConfig();
$stationUrl = Application_Common_HTTPHelper::getStationUrl();
$stationUrl .= substr($stationUrl, -1) == '/' ? '' : '/';
$data = array(
'id' => $id,
'url' => $url,
'callback_url' => $stationUrl . 'rest/media',
'api_key' => $CC_CONFIG["apiKey"][0],
'podcast_name' => $title,
'album_override' => $album_override,
'track_title' => $track_title);
$task = $this->_executeTask(static::$_CELERY_TASKS[self::DOWNLOAD], $data);
// Get the created ThirdPartyTaskReference and set the episode ID so
// we can remove the placeholder if the import ends up stuck in a pending state
$ref = ThirdPartyTrackReferencesQuery::create()->findPk($task->getDbTrackReference());
$ref->setDbForeignId($id)->save();
}
/**
* Update a ThirdPartyTrackReferences object for a completed upload
*
* @param $task CeleryTasks the completed CeleryTasks object
* @param $episodeId int PodcastEpisodes identifier
* @param $episode stdClass simple object containing Podcast episode information
* @param $status string Celery task status
*
* @return ThirdPartyTrackReferences the updated ThirdPartyTrackReferences object
*
* @throws Exception
* @throws PropelException
*/
public function updateTrackReference($task, $episodeId, $episode, $status) {
$ref = parent::updateTrackReference($task, $episodeId, $episode, $status);
$ref->setDbForeignId($episode->episodeid)->save();
$dbEpisode = PodcastEpisodesQuery::create()->findOneByDbId($episode->episodeid);
try {
// If the placeholder for the episode is somehow removed, return with a warning
if (!$dbEpisode) {
Logging::warn("Celery task $task episode $episode->episodeid unsuccessful: episode placeholder removed");
return $ref;
}
// Even if the task itself succeeds, the download could have failed, so check the status
if ($status == CELERY_SUCCESS_STATUS && $episode->status == 1) {
$dbEpisode->setDbFileId($episode->fileid)->save();
} else {
Logging::warn("Celery task $task episode $episode->episodeid unsuccessful with message $episode->error");
$dbEpisode->delete();
}
} catch (Exception $e) {
$dbEpisode->delete();
Logging::warn("Catastrophic failure updating from task $task, recovering by deleting episode row.\n
This can occur if the episode's corresponding CcFile is deleted before being processed.");
}
return $ref;
}
/**
* Publish the file with the given file ID to the station podcast
*
* @param int $fileId ID of the file to be published
*/
public function publish($fileId) {
$id = Application_Model_Preference::getStationPodcastId();
$url = $guid = Application_Common_HTTPHelper::getStationUrl()."rest/media/$fileId/download";
if (!PodcastEpisodesQuery::create()
->filterByDbPodcastId($id)
->findOneByDbFileId($fileId)) { // Don't allow duplicate episodes
$e = $this->_buildEpisode($id, $url, $guid, date('r'));
$e->setDbFileId($fileId)->save();
}
}
/**
* Unpublish the file with the given file ID from the station podcast
*
* @param int $fileId ID of the file to be unpublished
*/
public function unpublish($fileId) {
$id = Application_Model_Preference::getStationPodcastId();
PodcastEpisodesQuery::create()
->filterByDbPodcastId($id)
->findOneByDbFileId($fileId)
->delete();
}
/**
* 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) {
$stationPodcast = StationPodcastQuery::create()
->findOneByDbPodcastId(Application_Model_Preference::getStationPodcastId());
return (int) $stationPodcast->hasEpisodeForFile($fileId);
}
/**
* Find any episode placeholders that have been stuck pending (empty file ID) for over
* PENDING_EPISODE_TIMEOUT_SECONDS
*
* @see Application_Service_PodcastEpisodeService::PENDING_EPISODE_TIMEOUT_SECONDS
*
* @return array the episode imports stuck in pending
*/
public static function getStuckPendingImports() {
$timeout = gmdate(DEFAULT_TIMESTAMP_FORMAT, (microtime(true) - self::PENDING_EPISODE_TIMEOUT_SECONDS));
$episodes = PodcastEpisodesQuery::create()
->filterByDbFileId()
->find();
$stuckImports = array();
foreach ($episodes as $episode) {
$ref = ThirdPartyTrackReferencesQuery::create()
->findOneByDbForeignId(strval($episode->getDbId()));
if (!empty($ref)) {
$task = CeleryTasksQuery::create()
->filterByDbDispatchTime($timeout, Criteria::LESS_EQUAL)
->findOneByDbTrackReference($ref->getDbId());
if (!empty($task)) {
array_push($stuckImports, $episode);
}
}
}
return $stuckImports;
}
/**
* @param $episodeId
* @return array
* @throws PodcastEpisodeNotFoundException
*/
public static function getPodcastEpisodeById($episodeId)
{
$episode = PodcastEpisodesQuery::create()->findPk($episodeId);
if (!$episode) {
throw new PodcastEpisodeNotFoundException();
}
return $episode->toArray(BasePeer::TYPE_FIELDNAME);
}
/**
* Returns an array of Podcast episodes, with the option to paginate the results.
*
* @param $podcastId
* @param int $offset
* @param int $limit
* @param string $sortColumn
* @param string $sortDir "ASC" || "DESC"
* @return array
* @throws PodcastNotFoundException
*/
public function getPodcastEpisodes($podcastId,
$offset=0,
$limit=10,
$sortColumn=PodcastEpisodesPeer::PUBLICATION_DATE,
$sortDir="ASC")
{
$podcast = PodcastQuery::create()->findPk($podcastId);
if (!$podcast) {
throw new PodcastNotFoundException();
}
$sortDir = ($sortDir === "DESC") ? $sortDir = Criteria::DESC : Criteria::ASC;
$isStationPodcast = $podcastId == Application_Model_Preference::getStationPodcastId();
$episodes = PodcastEpisodesQuery::create()
->filterByDbPodcastId($podcastId);
if ($isStationPodcast && $limit != 0) {
$episodes = $episodes->setLimit($limit);
}
// XXX: We should maybe try to alias this so we don't pass CcFiles as an array key to the frontend.
// It would require us to iterate over all the episodes and change the key for the response though...
$episodes = $episodes->joinWith('PodcastEpisodes.CcFiles', Criteria::LEFT_JOIN)
->setOffset($offset)
->orderBy($sortColumn, $sortDir)
->find();
return $isStationPodcast ? $this->_getStationPodcastEpisodeArray($episodes)
: $this->_getImportedPodcastEpisodeArray($podcast, $episodes);
}
/**
* Given an array of PodcastEpisodes objects from the Station Podcast,
* convert the episode data into array form
*
* @param array $episodes array of PodcastEpisodes to convert
* @return array
*/
private function _getStationPodcastEpisodeArray($episodes) {
$episodesArray = array();
foreach ($episodes as $episode) {
/** @var PodcastEpisodes $episode */
$episodeArr = $episode->toArray(BasePeer::TYPE_FIELDNAME, true, [], true);
array_push($episodesArray, $episodeArr);
}
return $episodesArray;
}
/**
* Given an ImportedPodcast object and an array of stored PodcastEpisodes objects,
* fetch all episodes from the podcast RSS feed, and serialize them in a readable form
*
* TODO: there's definitely a better approach than this... we should be trying to create
* PodcastEpisdoes objects instead of our own arrays
*
* @param ImportedPodcast $podcast Podcast object to fetch the episodes for
* @param array $episodes array of PodcastEpisodes objects to
*
* @return array array of episode data
*
* @throws CcFiles/LibreTimeFileNotFoundException
*/
public function _getImportedPodcastEpisodeArray($podcast, $episodes) {
$rss = Application_Service_PodcastService::getPodcastFeed($podcast->getDbUrl());
$episodeIds = array();
$episodeFiles = array();
foreach ($episodes as $e) {
/** @var PodcastEpisodes $e */
array_push($episodeIds, $e->getDbEpisodeGuid());
$episodeFiles[$e->getDbEpisodeGuid()] = $e->getDbFileId();
}
$episodesArray = array();
foreach ($rss->get_items() as $item) {
/** @var SimplePie_Item $item */
// If the enclosure is empty or has not URL, this isn't a podcast episode (there's no audio data)
// technically podcasts shouldn't have multiple enclosures but often CMS add non-audio files
$enclosure = $item->get_enclosure();
$url = $enclosure instanceof SimplePie_Enclosure ? $enclosure->get_link() : $enclosure["link"];
if (empty($url)) {
continue;
}
// next we check and see if the enclosure is not an audio file - this can happen from improperly
// formatted podcasts and we instead will search through the enclosures and see if there is an audio item
// then we pass that on, otherwise we just pass the first item since it is probably an audio file
elseif (!(substr($enclosure->get_type(), 0, 5) === 'audio')) {
// this is a rather hackish way of accessing the enclosures but get_enclosures() didnt detect multiple
// enclosures at certain points so we search through them and then manually create an enclosure object
// if we find an audio file in an enclosure and send it off
Logging::info('found a non audio');
$testenclosures = $enclosures = $item->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure');
Logging::info($testenclosures);
// we need to check if this is an array otherwise sizeof will fail and stop this whole script
if (is_array($testenclosures)) {
$numenclosures = sizeof($testenclosures);
// now we loop through and look for a audio file and then stop the loop at the first one we find
for ($i = 0; $i < $numenclosures + 1; $i++) {
$enclosure_attribs = array_values($testenclosures[$i]['attribs'])[0];
if (stripos($enclosure_attribs['type'], 'audio') !== false) {
$url = $enclosure_attribs['url'];
$enclosure = new SimplePie_Enclosure($enclosure_attribs['url'], $enclosure_attribs['type'], $length = $enclosure_attribs['length']);
break;
}
// if we didn't find an audio file we need to continue because there were no audio item enclosures
// so this should keep it from showing items without audio items on the episodes list
if ($i = $numenclosures) {
continue;
}
}
}
else {
continue;
}
} else {
$enclosure = $item->get_enclosure();
}
//Logging::info($enclosure);
$itemId = $item->get_id();
$ingested = in_array($itemId, $episodeIds) ? (empty($episodeFiles[$itemId]) ? -1 : 1) : 0;
$file = $ingested > 0 && !empty($episodeFiles[$itemId]) ?
CcFiles::getSanitizedFileById($episodeFiles[$itemId]) : array();
// If the analyzer hasn't finished with the file, leave it as pending
if (!empty($file) && $file["import_status"] == CcFiles::IMPORT_STATUS_PENDING) {
$ingested = -1;
}
array_push($episodesArray, array(
"podcast_id" => $podcast->getDbId(),
"guid" => $itemId,
"ingested" => $ingested,
"title" => $item->get_title(),
// From the RSS spec best practices:
// 'An item's author element provides the e-mail address of the person who wrote the item'
"author" => $this->_buildAuthorString($item),
"description" => htmlspecialchars($item->get_description()),
"pub_date" => $item->get_gmdate(),
"link" => $url,
"enclosure" => $enclosure,
"file" => $file
));
}
return $episodesArray;
}
/**
* Construct a string representation of the author fields of a SimplePie_Item object
*
* @param SimplePie_Item $item the SimplePie_Item to extract the author data from
*
* @return string the string representation of the author data
*/
private function _buildAuthorString(SimplePie_Item $item) {
$authorString = $author = $item->get_author();
if (!empty($author)) {
$authorString = $author->get_email();
$authorString = empty($authorString) ? $author->get_name() : $authorString;
}
return $authorString;
}
public function deletePodcastEpisodeById($episodeId)
{
$episode = PodcastEpisodesQuery::create()->findByDbId($episodeId);
if ($episode) {
$episode->delete();
} else {
throw new PodcastEpisodeNotFoundException();
}
}
private function removePrivateFields(&$data)
{
foreach (self::$privateFields as $key) {
unset($data[$key]);
}
}
}

View file

@ -0,0 +1,15 @@
<?php
class PodcastFactory
{
public static function create($feedUrl)
{
// check if station podcast exists and if not, create one
$stationPodcastId = Application_Model_Preference::getStationPodcastId();
if (empty($stationPodcastId)) {
Application_Service_PodcastService::createStationPodcast();
}
return Application_Service_PodcastService::createFromFeedUrl($feedUrl);
}
}

View file

@ -0,0 +1,515 @@
<?php
class InvalidPodcastException extends Exception {}
class PodcastNotFoundException extends Exception {}
class Application_Service_PodcastService
{
// These fields should never be modified with POST/PUT data
private static $privateFields = array(
"id",
"url",
"type",
"owner"
);
/**
* Returns parsed rss feed, or false if the given URL cannot be downloaded
*
* @param string $feedUrl String containing the podcast feed URL
*
* @return mixed
*/
public static function getPodcastFeed($feedUrl)
{
try {
$feed = new SimplePie();
$feed->set_feed_url($feedUrl);
$feed->enable_cache(false);
$feed->init();
return $feed;
} catch (Exception $e) {
return false;
}
}
/** Creates a Podcast object from the given podcast URL.
* This is used by our Podcast REST API
*
* @param string $feedUrl Podcast RSS Feed Url
*
* @return array Podcast Array with a full list of episodes
* @throws Exception
* @throws InvalidPodcastException
*/
public static function createFromFeedUrl($feedUrl)
{
//TODO: why is this so slow?
$rss = self::getPodcastFeed($feedUrl);
if (!$rss) {
throw new InvalidPodcastException();
}
$rssErr = $rss->error();
if (!empty($rssErr)) {
throw new InvalidPodcastException($rssErr);
}
// Ensure we are only creating Podcast with the given URL, and excluding
// any extra data fields that may have been POSTED
$podcastArray = array();
$podcastArray["url"] = $feedUrl;
$podcastArray["title"] = htmlspecialchars($rss->get_title());
$podcastArray["description"] = htmlspecialchars($rss->get_description());
$podcastArray["link"] = htmlspecialchars($rss->get_link());
$podcastArray["language"] = htmlspecialchars($rss->get_language());
$podcastArray["copyright"] = htmlspecialchars($rss->get_copyright());
$author = $rss->get_author();
$name = empty($author) ? "" : $author->get_name();
$podcastArray["creator"] = htmlspecialchars($name);
$categories = array();
if (is_array($rss->get_categories())) {
foreach ($rss->get_categories() as $category) {
array_push($categories, $category->get_scheme() . ":" . $category->get_term());
}
}
$podcastArray["category"] = htmlspecialchars(implode($categories));
//TODO: put in constants
$itunesChannel = "http://www.itunes.com/dtds/podcast-1.0.dtd";
$itunesSubtitle = $rss->get_channel_tags($itunesChannel, 'subtitle');
$podcastArray["itunes_subtitle"] = isset($itunesSubtitle[0]["data"]) ? $itunesSubtitle[0]["data"] : "";
$itunesCategory = $rss->get_channel_tags($itunesChannel, 'category');
$categoryArray = array();
if (is_array($itunesCategory)) {
foreach ($itunesCategory as $c => $data) {
foreach ($data["attribs"] as $attrib) {
array_push($categoryArray, $attrib["text"]);
}
}
}
$podcastArray["itunes_category"] = implode(",", $categoryArray);
$itunesAuthor = $rss->get_channel_tags($itunesChannel, 'author');
$podcastArray["itunes_author"] = isset($itunesAuthor[0]["data"]) ? $itunesAuthor[0]["data"] : "";
$itunesSummary = $rss->get_channel_tags($itunesChannel, 'summary');
$podcastArray["itunes_summary"] = isset($itunesSummary[0]["data"]) ? $itunesSummary[0]["data"] : "";
$itunesKeywords = $rss->get_channel_tags($itunesChannel, 'keywords');
$podcastArray["itunes_keywords"] = isset($itunesKeywords[0]["data"]) ? $itunesKeywords[0]["data"] : "";
$itunesExplicit = $rss->get_channel_tags($itunesChannel, 'explicit');
$podcastArray["itunes_explicit"] = isset($itunesExplicit[0]["data"]) ? $itunesExplicit[0]["data"] : "";
self::validatePodcastMetadata($podcastArray);
try {
// Base class
$podcast = new Podcast();
$podcast->fromArray($podcastArray, BasePeer::TYPE_FIELDNAME);
$podcast->setDbOwner(self::getOwnerId());
$podcast->save();
$importedPodcast = new ImportedPodcast();
$importedPodcast->fromArray($podcastArray, BasePeer::TYPE_FIELDNAME);
$importedPodcast->setPodcast($podcast);
$importedPodcast->setDbAutoIngest(true);
$importedPodcast->save();
// if the autosmartblock and album override are enabled then create a smartblock and playlist matching this podcast via the album name
if (Application_Model_Preference::GetPodcastAutoSmartblock() && Application_Model_Preference::GetPodcastAlbumOverride()) {
self::createPodcastSmartblockAndPlaylist($podcast);
}
return $podcast->toArray(BasePeer::TYPE_FIELDNAME);
} catch(Exception $e) {
$podcast->delete();
throw $e;
}
}
/**
* @param $podcast
* @param $title passed in directly from web UI input
* This will automatically create a smartblock and playlist for this podcast.
*/
public static function createPodcastSmartblockAndPlaylist($podcast, $title = null)
{
if (is_array($podcast)) {
$newpodcast = new Podcast();
$newpodcast->fromArray($podcast, BasePeer::TYPE_FIELDNAME);
$podcast = $newpodcast;
}
if ($title == null) {
$title = $podcast->getDbTitle();
}
// Base class
$newBl = new Application_Model_Block();
$newBl->setCreator(Application_Model_User::getCurrentUser()->getId());
$newBl->setName($title);
$newBl->setDescription(_("Auto-generated smartblock for podcast"));
$newBl->saveType('dynamic');
// limit the smartblock to 1 item
$row = new CcBlockcriteria();
$row->setDbCriteria('limit');
$row->setDbModifier('items');
$row->setDbValue(1);
$row->setDbBlockId($newBl->getId());
$row->save();
// sort so that it is the newest item
$row = new CcBlockcriteria();
$row->setDbCriteria('sort');
$row->setDbModifier('N/A');
$row->setDbValue('newest');
$row->setDbBlockId($newBl->getId());
$row->save();
// match the track by ensuring the album title matches the podcast
$row = new CcBlockcriteria();
$row->setDbCriteria('album_title');
$row->setDbModifier('is');
$row->setDbValue($title);
$row->setDbBlockId($newBl->getId());
$row->save();
$newPl = new Application_Model_Playlist();
$newPl->setName($title);
$newPl->setCreator(Application_Model_User::getCurrentUser()->getId());
$row = new CcPlaylistcontents();
$row->setDbBlockId($newBl->getId());
$row->setDbPlaylistId($newPl->getId());
$row->setDbType(2);
$row->save();
}
public static function createStationPodcast()
{
$podcast = new Podcast();
$podcast->setDbUrl(Application_Common_HTTPHelper::getStationUrl() . "feeds/station-rss");
$title = Application_Model_Preference::GetStationName();
$title = empty($title) ? "My Station's Podcast" : $title;
$podcast->setDbTitle($title);
$podcast->setDbDescription(Application_Model_Preference::GetStationDescription());
$podcast->setDbLink(Application_Common_HTTPHelper::getStationUrl());
$podcast->setDbLanguage(explode('_', Application_Model_Preference::GetLocale())[0]);
$podcast->setDbCreator(Application_Model_Preference::GetStationName());
$podcast->setDbOwner(self::getOwnerId());
$podcast->save();
$stationPodcast = new StationPodcast();
$stationPodcast->setPodcast($podcast);
$stationPodcast->save();
Application_Model_Preference::setStationPodcastId($podcast->getDbId());
// Set the download key when we create the station podcast
// The value is randomly generated in the setter
Application_Model_Preference::setStationPodcastDownloadKey();
return $podcast->getDbId();
}
//TODO move this somewhere where it makes sense
private static function getOwnerId()
{
try {
if (Zend_Auth::getInstance()->hasIdentity()) {
$service_user = new Application_Service_UserService();
return $service_user->getCurrentUser()->getDbId();
} else {
$defaultOwner = CcSubjsQuery::create()
->filterByDbType('A')
->orderByDbId()
->findOne();
if (!$defaultOwner) {
// what to do if there is no admin user?
// should we handle this case?
return null;
}
return $defaultOwner->getDbId();
}
} catch(Exception $e) {
Logging::info($e->getMessage());
}
}
/**
* Trims the podcast metadata to fit the table's column max size
*
* @param $podcastArray
*/
private static function validatePodcastMetadata(&$podcastArray)
{
$podcastTable = PodcastPeer::getTableMap();
foreach ($podcastArray as $key => &$value) {
try {
// Make sure column exists in table
$columnMaxSize = $podcastTable->getColumn($key)->getSize();
} catch (PropelException $e) {
continue;
}
if (strlen($value) > $columnMaxSize) {
$value = substr($value, 0, $podcastTable->getColumn($key)->getSize());
}
}
}
/**
* Fetches a Podcast's rss feed and returns all its episodes with
* the Podcast object
*
* @param $podcastId
*
* @throws PodcastNotFoundException
* @throws InvalidPodcastException
* @return array - Podcast Array with a full list of episodes
*/
public static function getPodcastById($podcastId)
{
$podcast = PodcastQuery::create()->findPk($podcastId);
if (!$podcast) {
throw new PodcastNotFoundException();
}
$podcast = $podcast->toArray(BasePeer::TYPE_FIELDNAME);
$podcast["itunes_explicit"] = ($podcast["itunes_explicit"] == "yes") ? true : false;
return $podcast;
}
/**
* Deletes a Podcast and its podcast episodes
*
* @param $podcastId
* @throws Exception
* @throws PodcastNotFoundException
*/
public static function deletePodcastById($podcastId)
{
$podcast = PodcastQuery::create()->findPk($podcastId);
if ($podcast) {
$podcast->delete();
// FIXME: I don't think we should be able to delete the station podcast...
if ($podcastId == Application_Model_Preference::getStationPodcastId()) {
Application_Model_Preference::setStationPodcastId(null);
}
} else {
throw new PodcastNotFoundException();
}
}
/**
* Build a response with podcast data and embedded HTML to load on the frontend
*
* @param int $podcastId ID of the podcast to build a response for
* @param Zend_View_Interface $view Zend view object to render the response HTML
*
* @return array the response array containing the podcast data and editor HTML
*
* @throws PodcastNotFoundException
*/
public static function buildPodcastEditorResponse($podcastId, $view) {
// Check the StationPodcast table rather than checking
// the station podcast ID key in preferences for extensibility
$podcast = StationPodcastQuery::create()->findOneByDbPodcastId($podcastId);
$path = $podcast ? 'podcast/station.phtml' : 'podcast/podcast.phtml';
$podcast = Application_Service_PodcastService::getPodcastById($podcastId);
return array(
"podcast" => json_encode($podcast),
"html" => $view->render($path),
);
}
/**
* Updates a Podcast object with the given metadata
*
* @param $podcastId
* @param $data
* @return array
* @throws Exception
* @throws PodcastNotFoundException
*/
public static function updatePodcastFromArray($podcastId, $data)
{
$podcast = PodcastQuery::create()->findPk($podcastId);
if (!$podcast) {
throw new PodcastNotFoundException();
}
self::removePrivateFields($data["podcast"]);
self::validatePodcastMetadata($data["podcast"]);
if (array_key_exists("auto_ingest", $data["podcast"])) {
self::_updateAutoIngestTimestamp($podcast, $data);
}
$data["podcast"]["itunes_explicit"] = $data["podcast"]["itunes_explicit"] ? "yes" : "clean";
$podcast->fromArray($data["podcast"], BasePeer::TYPE_FIELDNAME);
$podcast->save();
return $podcast->toArray(BasePeer::TYPE_FIELDNAME);
}
/**
* Update the automatic ingestion timestamp for the given Podcast
*
* @param Podcast $podcast Podcast object to update
* @param array $data Podcast update data array
*/
private static function _updateAutoIngestTimestamp($podcast, $data) {
// Get podcast data with lazy loaded columns since we can't directly call getDbAutoIngest()
$currData = $podcast->toArray(BasePeer::TYPE_FIELDNAME, true);
// Add an auto-ingest timestamp when turning auto-ingest on
if ($data["podcast"]["auto_ingest"] == 1 && $currData["auto_ingest"] != 1) {
$data["podcast"]["auto_ingest_timestamp"] = gmdate('r');
}
}
private static function removePrivateFields(&$data)
{
foreach (self::$privateFields as $key) {
unset($data[$key]);
}
}
private static function addEscapedChild($node, $name, $value = null, $namespace = null) {
if (empty($value)) {
return null;
}
$child = $node->addChild($name, null, $namespace);
$child[0] = $value;
return $child;
}
public static function createStationRssFeed()
{
$stationPodcastId = Application_Model_Preference::getStationPodcastId();
try {
$podcast = PodcastQuery::create()->findPk($stationPodcastId);
if (!$podcast) {
throw new PodcastNotFoundException();
}
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"/>');
$channel = $xml->addChild("channel");
self::addEscapedChild($channel, "title", $podcast->getDbTitle());
self::addEscapedChild($channel, "link", $podcast->getDbLink());
self::addEscapedChild($channel, "description", $podcast->getDbDescription());
self::addEscapedChild($channel, "language", $podcast->getDbLanguage());
self::addEscapedChild($channel, "copyright", $podcast->getDbCopyright());
$xml->addAttribute('xmlns:xmlns:atom', "http://www.w3.org/2005/Atom");
$atomLink = $channel->addChild("xmlns:atom:link");
$atomLink->addAttribute("href", Application_Common_HTTPHelper::getStationUrl() . "feeds/station-rss");
$atomLink->addAttribute("rel", "self");
$atomLink->addAttribute("type", "application/rss+xml");
$imageUrl = Application_Common_HTTPHelper::getStationUrl()."api/station-logo";
$image = $channel->addChild("image");
$image->addChild("title", htmlspecialchars($podcast->getDbTitle()));
self::addEscapedChild($image, "url", $imageUrl);
self::addEscapedChild($image, "link", Application_Common_HTTPHelper::getStationUrl());
$xml->addAttribute('xmlns:xmlns:itunes', ITUNES_XML_NAMESPACE_URL);
self::addEscapedChild($channel, "xmlns:itunes:author", $podcast->getDbItunesAuthor());
self::addEscapedChild($channel, "xmlns:itunes:keywords", $podcast->getDbItunesKeywords());
self::addEscapedChild($channel, "xmlns:itunes:summary", $podcast->getDbItunesSummary());
self::addEscapedChild($channel, "xmlns:itunes:subtitle", $podcast->getDbItunesSubtitle());
self::addEscapedChild($channel, "xmlns:itunes:explicit", $podcast->getDbItunesExplicit());
$owner = $channel->addChild("xmlns:itunes:owner");
self::addEscapedChild($owner, "xmlns:itunes:name", Application_Model_Preference::GetStationName());
self::addEscapedChild($owner, "xmlns:itunes:email", Application_Model_Preference::GetEmail());
$itunesImage = $channel->addChild("xmlns:itunes:image");
$itunesImage->addAttribute("href", $imageUrl);
// Need to split categories into separate tags
$itunesCategories = explode(",", $podcast->getDbItunesCategory());
foreach ($itunesCategories as $c) {
if (!empty($c)) {
$category = $channel->addChild("xmlns:itunes:category");
$category->addAttribute("text", $c);
}
}
$episodes = PodcastEpisodesQuery::create()->filterByDbPodcastId($stationPodcastId)->find();
foreach ($episodes as $episode) {
$item = $channel->addChild("item");
$publishedFile = CcFilesQuery::create()->findPk($episode->getDbFileId());
//title
self::addEscapedChild($item, "title", $publishedFile->getDbTrackTitle());
//link - do we need this?
//pubDate
self::addEscapedChild($item, "pubDate", gmdate(DATE_RFC2822, strtotime($episode->getDbPublicationDate())));
//category
foreach($itunesCategories as $c) {
if (!empty($c)) {
self::addEscapedChild($item, "category", $c);
}
}
//guid
$guid = self::addEscapedChild($item, "guid", $episode->getDbEpisodeGuid());
$guid->addAttribute("isPermaLink", "false");
//description
self::addEscapedChild($item, "description", $publishedFile->getDbDescription());
//encolsure - url, length, type attribs
$enclosure = $item->addChild("enclosure");
$enclosure->addAttribute("url", $episode->getDbDownloadUrl());
$enclosure->addAttribute("length", $publishedFile->getDbFilesize());
$enclosure->addAttribute("type", $publishedFile->getDbMime());
//itunes:subtitle
// From http://www.apple.com/ca/itunes/podcasts/specs.html#subtitle :
// 'The contents of the <itunes:subtitle> tag are displayed in the Description column in iTunes.'
// self::addEscapedChild($item, "xmlns:itunes:subtitle", $publishedFile->getDbTrackTitle());
self::addEscapedChild($item, "xmlns:itunes:subtitle", $publishedFile->getDbDescription());
//itunes:summary
self::addEscapedChild($item, "xmlns:itunes:summary", $publishedFile->getDbDescription());
//itunes:author
self::addEscapedChild($item, "xmlns:itunes:author", $publishedFile->getDbArtistName());
//itunes:explicit - skip this?
//itunes:duration
self::addEscapedChild($item, "xmlns:itunes:duration", explode('.', $publishedFile->getDbLength())[0]);
}
//Format it nicely with newlines...
$dom = new DOMDocument();
$dom->loadXML($xml->asXML());
$dom->formatOutput = true;
$formattedXML = $dom->saveXML();
return $formattedXML;
} catch (FeedException $e) {
return false;
}
}
}

View file

@ -0,0 +1,83 @@
<?php
class Application_Service_PublishService {
/**
* @var array map of arbitrary source names to descriptive labels
*/
private static $SOURCES = array(
"station_podcast" => "My Podcast"
);
/**
* Publish or remove the file with the given file ID from the services
* specified in the request data (ie. the station podcast)
*
* @param int $fileId ID of the file to be published
* @param array $data request data containing what services to publish to
*/
public static function publish($fileId, $data) {
foreach ($data as $k => $v) {
$service = PublishServiceFactory::getService($k);
$service->$v($fileId);
}
}
/**
* For the file with the given ID, check external sources and generate
* an array of source states and labels.
*
* Sources to which the file has been published should be passed back in a
* "published" array, while sources to which the file has not been published
* should be passed back in a "toPublish" array.
*
* @param int $fileId the ID of the file to check
*
* @return array array containing published and toPublish arrays. Has the form
* [
* "toPublish" => [
* "source" => "label",
* ...
* ]
* "published" => [
* "source" => "label",
* ...
* ]
* ]
*/
public static function getSourceLists($fileId) {
$sources = array();
foreach (self::$SOURCES as $source => $label) {
$service = PublishServiceFactory::getService($source);
$status = $service->getPublishStatus($fileId);
array_push($sources, array(
"source" => $source,
"label" => _($label),
"status" => $status
));
}
return $sources;
}
/**
* Given a cc_file identifier, check if the associated file has
* been published to any sources.
*
* @param int $fileId the cc_files identifier of the file to check
*
* @return bool true if the file has been published to any source,
* otherwise false
*/
public static function isPublished($fileId) {
foreach (self::$SOURCES as $source => $label) {
$service = PublishServiceFactory::getService($source);
// 1: published or -1: pending
if (abs($service->getPublishStatus($fileId)) == 1) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,21 @@
<?php
class PublishServiceFactory {
/**
* Given an identifying string, get a PublishService object of that type
*
* @param $serviceName string the name of the service to create
*
* @return Publish|null
*/
public static function getService($serviceName) {
switch($serviceName) {
case STATION_PODCAST_SERVICE_NAME:
return new Application_Service_PodcastEpisodeService();
default:
return null;
}
}
}

View file

@ -0,0 +1,490 @@
<?php
class Application_Service_SchedulerService
{
private $con;
private $fileInfo = array(
"id" => "",
"cliplength" => "",
"cuein" => "00:00:00",
"cueout" => "00:00:00",
"fadein" => "00:00:00",
"fadeout" => "00:00:00",
"sched_id" => null,
"type" => 0 //default type of '0' to represent files. type '1' represents a webstream
);
private $epochNow;
private $nowDT;
private $currentUser;
private $checkUserPermissions = true;
public function __construct()
{
$this->con = Propel::getConnection(CcSchedulePeer::DATABASE_NAME);
//subtracting one because sometimes when we cancel a track, we set its end time
//to epochNow and then send the new schedule to pypo. Sometimes the currently cancelled
//track can still be included in the new schedule because it may have a few ms left to play.
//subtracting 1 second from epochNow resolves this issue.
$this->epochNow = microtime(true)-1;
$this->nowDT = DateTime::createFromFormat("U.u", $this->epochNow, new DateTimeZone("UTC"));
if ($this->nowDT === false) {
// DateTime::createFromFormat does not support millisecond string formatting in PHP 5.3.2 (Ubuntu 10.04).
// In PHP 5.3.3 (Ubuntu 10.10), this has been fixed.
$this->nowDT = DateTime::createFromFormat("U", time(), new DateTimeZone("UTC"));
}
$user_service = new Application_Service_UserService();
$this->currentUser = $user_service->getCurrentUser();
}
/**
*
* Applies the show start difference to any scheduled items
*
* @param $instanceIds
* @param $diff (integer, difference between unix epoch in seconds)
*/
public static function updateScheduleStartTime($instanceIds, $diff)
{
$con = Propel::getConnection();
if (count($instanceIds) > 0) {
$showIdList = implode(",", $instanceIds);
$ccSchedules = CcScheduleQuery::create()
->filterByDbInstanceId($instanceIds, Criteria::IN)
->find($con);
$interval = new DateInterval("PT".abs($diff)."S");
if ($diff < 0) {
$interval->invert = 1;
}
foreach ($ccSchedules as $ccSchedule) {
$start = $ccSchedule->getDbStarts(null);
$newStart = $start->add($interval);
$end = $ccSchedule->getDbEnds(null);
$newEnd = $end->add($interval);
$ccSchedule
->setDbStarts($newStart)
->setDbEnds($newEnd)
->save($con);
}
}
}
/**
*
* Removes any time gaps in shows
*
* @param array $schedIds schedule ids to exclude
*/
public function removeGaps($showId, $schedIds=null)
{
$ccShowInstances = CcShowInstancesQuery::create()->filterByDbShowId($showId)->find();
foreach ($ccShowInstances as $instance) {
Logging::info("Removing gaps from show instance #".$instance->getDbId());
//DateTime object
$itemStart = $instance->getDbStarts(null);
$ccScheduleItems = CcScheduleQuery::create()
->filterByDbInstanceId($instance->getDbId())
->filterByDbId($schedIds, Criteria::NOT_IN)
->orderByDbStarts()
->find();
foreach ($ccScheduleItems as $ccSchedule) {
//DateTime object
$itemEnd = $this->findEndTime($itemStart, $ccSchedule->getDbClipLength());
$ccSchedule->setDbStarts($itemStart)
->setDbEnds($itemEnd);
$itemStart = $itemEnd;
}
$ccScheduleItems->save();
}
}
/**
*
* Enter description here ...
* @param DateTime $instanceStart
* @param string $clipLength
*/
private static function findEndTime($instanceStart, $clipLength)
{
$startEpoch = $instanceStart->format("U.u");
$durationSeconds = Application_Common_DateHelper::playlistTimeToSeconds($clipLength);
//add two float numbers to 6 subsecond precision
//DateTime::createFromFormat("U.u") will have a problem if there is no decimal in the resulting number.
$endEpoch = bcadd($startEpoch , (string) $durationSeconds, 6);
$dt = DateTime::createFromFormat("U.u", $endEpoch, new DateTimeZone("UTC"));
if ($dt === false) {
//PHP 5.3.2 problem
$dt = DateTime::createFromFormat("U", intval($endEpoch), new DateTimeZone("UTC"));
}
return $dt;
}
private static function findTimeDifference($p_startDT, $p_seconds)
{
$startEpoch = $p_startDT->format("U.u");
//add two float numbers to 6 subsecond precision
//DateTime::createFromFormat("U.u") will have a problem if there is no decimal in the resulting number.
$newEpoch = bcsub($startEpoch , (string) $p_seconds, 6);
$dt = DateTime::createFromFormat("U.u", $newEpoch, new DateTimeZone("UTC"));
if ($dt === false) {
//PHP 5.3.2 problem
$dt = DateTime::createFromFormat("U", intval($newEpoch), new DateTimeZone("UTC"));
}
return $dt;
}
/**
*
* Gets a copy of the linked show's schedule from cc_schedule table
*
* If $instanceId is not null, we use that variable to grab the linked
* show's schedule from cc_schedule table. (This is likely to be the case
* if a user has edited a show and changed it from un-linked to linked, in
* which case we copy the show content from the show instance that was clicked
* on to edit the show in the calendar.) Otherwise the schedule is taken
* from the most recent show instance that existed before new show
* instances were created. (This is likely to be the case when a user edits a
* show and a new repeat show day is added (i.e. mondays), or moves forward in the
* calendar triggering show creation)
*
* @param integer $showId
* @param array $instancsIdsToFill
* @param integer $instanceId
*/
private static function getLinkedShowSchedule($showId, $instancsIdsToFill, $instanceId)
{
$con = Propel::getConnection();
if (is_null($instanceId)) {
$showsPopulatedUntil = Application_Model_Preference::GetShowsPopulatedUntil();
$showInstanceWithMostRecentSchedule = CcShowInstancesQuery::create()
->filterByDbShowId($showId)
->filterByDbStarts($showsPopulatedUntil->format(DEFAULT_TIMESTAMP_FORMAT), Criteria::LESS_THAN)
->filterByDbId($instancsIdsToFill, Criteria::NOT_IN)
->orderByDbStarts(Criteria::DESC)
->limit(1)
->findOne();
if (is_null($showInstanceWithMostRecentSchedule)) {
return null;
}
$instanceId = $showInstanceWithMostRecentSchedule->getDbId();
}
$linkedShowSchedule_sql = $con->prepare(
"select * from cc_schedule where instance_id = :instance_id ".
"order by starts");
$linkedShowSchedule_sql->bindParam(':instance_id', $instanceId);
$linkedShowSchedule_sql->execute();
return $linkedShowSchedule_sql->fetchAll();
}
/**
*
* This function gets called after new linked show_instances are created, or after
* a show has been edited and went from being un-linked to linked.
* It fills the new show instances' schedules.
*
* @param CcShow_type $ccShow
* @param array $instanceIdsToFill ids of the new linked cc_show_instances that
* need their schedules filled
*/
public static function fillLinkedInstances($ccShow, $instanceIdsToFill, $instanceId=null)
{
//Get the "template" schedule for the linked show (contents of the linked show) that will be
//copied into to all the new show instances.
$linkedShowSchedule = self::getLinkedShowSchedule($ccShow->getDbId(), $instanceIdsToFill, $instanceId);
//get time_filled so we can update cc_show_instances
if (!empty($linkedShowSchedule)) {
$timeFilled_sql = "SELECT time_filled FROM cc_show_instances ".
"WHERE id = {$linkedShowSchedule[0]["instance_id"]}";
$timeFilled = Application_Common_Database::prepareAndExecute(
$timeFilled_sql, array(), Application_Common_Database::COLUMN);
} else {
//We probably shouldn't return here because the code below will
//set this on each empty show instance...
$timeFilled = "00:00:00";
}
$values = array();
$con = Propel::getConnection();
//Here we begin to fill the new show instances (as specified by $instanceIdsToFill)
//with content from $linkedShowSchedule.
try {
$con->beginTransaction();
if (!empty($linkedShowSchedule)) {
foreach ($instanceIdsToFill as $id) {
//Start by clearing the show instance that needs to be filling. This ensure
//we're not going to get in trouble in case there's an programming error somewhere else.
self::clearShowInstanceContents($id);
// Now fill the show instance with the same content that $linkedShowSchedule has.
$instanceStart_sql = "SELECT starts FROM cc_show_instances " .
"WHERE id = {$id} " . "ORDER BY starts";
//What's tricky here is that when we copy the content, we have to adjust
//the start and end times of each track so they're inside the new show instance's time slot.
$nextStartDT = new DateTime(
Application_Common_Database::prepareAndExecute(
$instanceStart_sql, array(),
Application_Common_Database::COLUMN),
new DateTimeZone("UTC"));
$defaultCrossfadeDuration = Application_Model_Preference::GetDefaultCrossfadeDuration();
unset($values);
$values = array();
foreach ($linkedShowSchedule as $item) {
$endTimeDT = self::findEndTime($nextStartDT,
$item["clip_length"]);
if (is_null($item["file_id"])) {
$item["file_id"] = "null";
}
if (is_null($item["stream_id"])) {
$item["stream_id"] = "null";
}
$values[] = "(" . "'{$nextStartDT->format(DEFAULT_TIMESTAMP_FORMAT)}', " .
"'{$endTimeDT->format(DEFAULT_TIMESTAMP_FORMAT)}', " .
"'{$item["clip_length"]}', " .
"'{$item["fade_in"]}', " . "'{$item["fade_out"]}', " .
"'{$item["cue_in"]}', " . "'{$item["cue_out"]}', " .
"{$item["file_id"]}, " . "{$item["stream_id"]}, " .
"{$id}, " . "{$item["position"]})";
$nextStartDT = self::findTimeDifference($endTimeDT,
$defaultCrossfadeDuration);
} //foreach show item
if (!empty($values)) {
$insert_sql = "INSERT INTO cc_schedule (starts, ends, " .
"clip_length, fade_in, fade_out, cue_in, cue_out, " .
"file_id, stream_id, instance_id, position) VALUES " .
implode($values, ",");
Application_Common_Database::prepareAndExecute(
$insert_sql, array(), Application_Common_Database::EXECUTE);
}
//update cc_schedule status column
$instance = CcShowInstancesQuery::create()->findPk($id);
$instance->updateScheduleStatus($con);
} //foreach linked instance
}
//update time_filled and last_scheduled in cc_show_instances
$now = gmdate(DEFAULT_TIMESTAMP_FORMAT);
$whereClause = new Criteria();
$whereClause->add(CcShowInstancesPeer::ID, $instanceIdsToFill, Criteria::IN);
$updateClause = new Criteria();
$updateClause->add(CcShowInstancesPeer::TIME_FILLED, $timeFilled);
$updateClause->add(CcShowInstancesPeer::LAST_SCHEDULED, $now);
BasePeer::doUpdate($whereClause, $updateClause, $con);
$con->commit();
Logging::info("finished fill");
} catch (Exception $e) {
$con->rollback();
Logging::info("Error filling linked shows: ".$e->getMessage());
exit();
}
}
public static function fillPreservedLinkedShowContent($ccShow, $showStamp)
{
$item = $showStamp->getFirst();
$timeFilled = $item->getCcShowInstances()->getDbTimeFilled();
foreach ($ccShow->getCcShowInstancess() as $ccShowInstance) {
$ccSchedules = CcScheduleQuery::create()
->filterByDbInstanceId($ccShowInstance->getDbId())
->find();
if ($ccSchedules->isEmpty()) {
$nextStartDT = $ccShowInstance->getDbStarts(null);
foreach ($showStamp as $item) {
$endTimeDT = self::findEndTime($nextStartDT, $item->getDbClipLength());
$ccSchedule = new CcSchedule();
$ccSchedule
->setDbStarts($nextStartDT)
->setDbEnds($endTimeDT)
->setDbFileId($item->getDbFileId())
->setDbStreamId($item->getDbStreamId())
->setDbClipLength($item->getDbClipLength())
->setDbFadeIn($item->getDbFadeIn())
->setDbFadeOut($item->getDbFadeOut())
->setDbCuein($item->getDbCueIn())
->setDbCueOut($item->getDbCueOut())
->setDbInstanceId($ccShowInstance->getDbId())
->setDbPosition($item->getDbPosition())
->save();
$nextStartDT = self::findTimeDifference($endTimeDT,
Application_Model_Preference::GetDefaultCrossfadeDuration());
} //foreach show item
$ccShowInstance
->setDbTimeFilled($timeFilled)
->setDbLastScheduled(gmdate(DEFAULT_TIMESTAMP_FORMAT))
->save();
}
}
}
/** Clears a show instance's schedule (which is actually clearing cc_schedule during that show instance's time slot.) */
private static function clearShowInstanceContents($instanceId)
{
//Application_Common_Database::prepareAndExecute($delete_sql, array(), Application_Common_Database::EXECUTE);
$con = Propel::getConnection();
$query = $con->prepare("DELETE FROM cc_schedule WHERE instance_id = :instance_id");
$query->bindParam(':instance_id', $instanceId);
$query->execute();
}
/*
private static function replaceInstanceContentCheck($currentShowStamp, $showStamp, $instance_id)
{
$counter = 0;
$erraseShow = false;
if (count($currentShowStamp) != count($showStamp)) {
$erraseShow = true;
} else {
foreach ($showStamp as $item) {
if ($item["file_id"] != $currentShowStamp[$counter]["file_id"] ||
$item["stream_id"] != $currentShowStamp[$counter]["stream_id"]) {
$erraseShow = true;
break;
//CcScheduleQuery::create()
// ->filterByDbInstanceId($ccShowInstance->getDbId())
// ->delete();
}
$counter += 1;
}
}
if ($erraseShow) {
$delete_sql = "DELETE FROM cc_schedule ".
"WHERE instance_id = :instance_id";
Application_Common_Database::prepareAndExecute(
$delete_sql, array(), Application_Common_Database::EXECUTE);
return true;
}
//If we get here, the content in the show instance is the same
// as what we want to replace it with, so we can leave as is
return false;
}*/
public function emptyShowContent($instanceId)
{
try {
$ccShowInstance = CcShowInstancesQuery::create()->findPk($instanceId);
$instances = array();
$instanceIds = array();
if ($ccShowInstance->getCcShow()->isLinked()) {
foreach ($ccShowInstance->getCcShow()->getFutureCcShowInstancess() as $instance) {
$instanceIds[] = $instance->getDbId();
$instances[] = $instance;
}
} else {
$instanceIds[] = $ccShowInstance->getDbId();
$instances[] = $ccShowInstance;
}
/* Get the file ids of the tracks we are about to delete
* from cc_schedule. We need these so we can update the
* is_scheduled flag in cc_files
*/
$ccSchedules = CcScheduleQuery::create()
->filterByDbInstanceId($instanceIds, Criteria::IN)
->setDistinct(CcSchedulePeer::FILE_ID)
->find();
$fileIds = array();
foreach ($ccSchedules as $ccSchedule) {
$fileIds[] = $ccSchedule->getDbFileId();
}
/* Clear out the schedule */
CcScheduleQuery::create()
->filterByDbInstanceId($instanceIds, Criteria::IN)
->delete();
/* Now that the schedule has been cleared we need to make
* sure we do not update the is_scheduled flag for tracks
* that are scheduled in other shows
*/
$futureScheduledFiles = Application_Model_Schedule::getAllFutureScheduledFiles();
foreach ($fileIds as $k => $v) {
if (in_array($v, $futureScheduledFiles)) {
unset($fileIds[$k]);
}
}
$selectCriteria = new Criteria();
$selectCriteria->add(CcFilesPeer::ID, $fileIds, Criteria::IN);
$updateCriteria = new Criteria();
$updateCriteria->add(CcFilesPeer::IS_SCHEDULED, false);
BasePeer::doUpdate($selectCriteria, $updateCriteria, Propel::getConnection());
Application_Model_RabbitMq::PushSchedule();
$con = Propel::getConnection(CcShowInstancesPeer::DATABASE_NAME);
foreach ($instances as $instance) {
$instance->updateDbTimeFilled($con);
}
return true;
} catch (Exception $e) {
Logging::info($e->getMessage());
return false;
}
}
/*
* TODO in the future this should probably support webstreams.
*/
public function updateFutureIsScheduled($scheduleId, $status)
{
$sched = CcScheduleQuery::create()->findPk($scheduleId);
$redraw = false;
if (isset($sched)) {
$fileId = $sched->getDbFileId();
if (isset($fileId)) {
$redraw = Application_Model_StoredFile::setIsScheduled($fileId, $status);
}
}
return $redraw;
}
}

View file

@ -0,0 +1,630 @@
<?php
class Application_Service_ShowFormService
{
private $ccShow;
private $instanceId;
public function __construct($showId = null, $instanceId = null)
{
if (!is_null($showId)) {
$this->ccShow = CcShowQuery::create()->findPk($showId);
}
$this->instanceId = $instanceId;
}
/**
*
* @return array of show forms
*/
public function createShowForms()
{
$formWhat = new Application_Form_AddShowWhat();
$formAutoPlaylist = new Application_Form_AddShowAutoPlaylist();
$formWho = new Application_Form_AddShowWho();
$formWhen = new Application_Form_AddShowWhen();
$formRepeats = new Application_Form_AddShowRepeats();
$formStyle = new Application_Form_AddShowStyle();
$formLive = new Application_Form_AddShowLiveStream();
$formRecord = new Application_Form_AddShowRR();
$formAbsoluteRebroadcast = new Application_Form_AddShowAbsoluteRebroadcastDates();
$formRebroadcast = new Application_Form_AddShowRebroadcastDates();
$formWhat->removeDecorator('DtDdWrapper');
$formAutoPlaylist->removeDecorator('DtDdWrapper');
$formWho->removeDecorator('DtDdWrapper');
$formWhen->removeDecorator('DtDdWrapper');
$formRepeats->removeDecorator('DtDdWrapper');
$formStyle->removeDecorator('DtDdWrapper');
$formLive->removeDecorator('DtDdWrapper');
$formRecord->removeDecorator('DtDdWrapper');
$formAbsoluteRebroadcast->removeDecorator('DtDdWrapper');
$formRebroadcast->removeDecorator('DtDdWrapper');
$forms = array();
$forms["what"] = $formWhat;
$forms["autoplaylist"] = $formAutoPlaylist;
$forms["who"] = $formWho;
$forms["when"] = $formWhen;
$forms["repeats"] = $formRepeats;
$forms["style"] = $formStyle;
$forms["live"] = $formLive;
$forms["record"] = $formRecord;
$forms["abs_rebroadcast"] = $formAbsoluteRebroadcast;
$forms["rebroadcast"] = $formRebroadcast;
return $forms;
}
/**
*
* Popluates the what, autoplaylist, when, and repeat forms
* with default values
*/
public function populateNewShowForms($formWhat, $formWhen, $formRepeats)
{
$formWhat->populate(
array('add_show_id' => '-1',
'add_show_instance_id' => '-1'));
$formWhen->populate(
array('add_show_start_date' => date("Y-m-d"),
'add_show_start_time' => '00:00',
'add_show_end_date_no_repeate' => date("Y-m-d"),
'add_show_end_time' => '01:00',
'add_show_duration' => '01h 00m'));
$formRepeats->populate(array('add_show_end_date' => date("Y-m-d")));
}
public function delegateShowInstanceFormPopulation($forms)
{
$this->populateFormWhat($forms["what"]);
$this->populateInstanceFormWhen($forms["when"]);
$this->populateFormWho($forms["who"]);
$this->populateFormLive($forms["live"]);
$this->populateFormStyle($forms["style"]);
/* Only the field on the 'when' form will get updated so we should
* make all other forms disabled or readonly
*
* 'what' needs to be readonly because zendform will not validate
* if they are disabled
*
* All other forms can be disabled because we do not update those values
* when the user edits a repeating instance
*/
$forms["what"]->makeReadonly();
$forms["what"]->enableInstanceDesc();
$forms["autoplaylist"]->disable();
$forms["repeats"]->disable();
$forms["who"]->disable();
$forms["style"]->disable();
// Hide the show logo fields when users are editing a single instance
$forms["style"]->hideShowLogo();
$forms["live"]->disable();
$forms["record"]->disable();
$forms["rebroadcast"]->disable();
$forms["abs_rebroadcast"]->disable();
}
/**
*
* Delegates populating each show form with the appropriate
* data of the current show being edited
*
* @param $forms
*/
public function delegateShowFormPopulation($forms)
{
$this->populateFormWhat($forms["what"]);
//local show start DT
$this->populateFormAutoPlaylist($forms["autoplaylist"]);
$showStart = $this->populateFormWhen($forms["when"]);
$this->populateFormRepeats($forms["repeats"], $showStart);
$this->populateFormWho($forms["who"]);
$this->populateFormStyle($forms["style"]);
$this->populateFormLive($forms["live"]);
$this->populateFormRecord($forms["record"]);
$this->populateFormRebroadcastRelative($forms["rebroadcast"]);
$this->populateFormRebroadcastAbsolute($forms["abs_rebroadcast"]);
}
private function populateFormWhat($form)
{
$ccShowInstance = CcShowInstancesQuery::create()->findPk($this->instanceId);
$form->populate(
array(
'add_show_instance_id' => $this->instanceId,
'add_show_id' => $this->ccShow->getDbId(),
'add_show_name' => $this->ccShow->getDbName(),
'add_show_url' => $this->ccShow->getDbUrl(),
'add_show_genre' => $this->ccShow->getDbGenre(),
'add_show_description' => $this->ccShow->getDbDescription(),
'add_show_instance_description' => $ccShowInstance->getDbDescription(),
));
}
private function populateFormAutoPlaylist($form)
{
$ccShowInstance = CcShowInstancesQuery::create()->findPk($this->instanceId);
$form->populate(
array(
'add_show_has_autoplaylist' => $this->ccShow->getDbHasAutoPlaylist() ? 1 : 0,
'add_show_autoplaylist_id' => $this->ccShow->getDbAutoPlaylistId(),
'add_show_autoplaylist_repeat' => $this->ccShow->getDbAutoPlaylistRepeat()
));
}
private function populateFormWhen($form)
{
if ($this->ccShow->isRepeating()) {
$ccShowDay = $this->ccShow->getFirstRepeatingCcShowDay();
} else {
$ccShowDay = $this->ccShow->getFirstCcShowDay();
}
$showStart = $ccShowDay->getLocalStartDateAndTime();
$showEnd = $ccShowDay->getLocalEndDateAndTime();
//check if the first show is in the past
if ($ccShowDay->isShowStartInPast()) {
//for a non-repeating show, we should never allow user to change the start time.
//for a repeating show, we should allow because the form works as repeating template form
if (!$ccShowDay->isRepeating()) {
$form->disableStartDateAndTime();
} else {
$showStartAndEnd = $this->getNextFutureRepeatShowTime();
if (!is_null($showStartAndEnd)) {
$showStart = $showStartAndEnd["starts"];
$showEnd = $showStartAndEnd["ends"];
}
if ($this->hasShowStarted($showStart)) {
$form->disableStartDateAndTime();
}
}
}
//Disable starting a show 'now' when editing an existing show.
$form->getElement('add_show_start_now')->setAttrib('disable', array('now'));
$form->populate(
array(
'add_show_start_date' => $showStart->format("Y-m-d"),
'add_show_start_time' => $showStart->format("H:i"),
'add_show_end_date_no_repeat' => $showEnd->format("Y-m-d"),
'add_show_end_time' => $showEnd->format("H:i"),
'add_show_duration' => $ccShowDay->formatDuration(true),
'add_show_timezone' => $ccShowDay->getDbTimezone(),
'add_show_repeats' => $ccShowDay->isRepeating() ? 1 : 0));
return $showStart;
}
public function getNextFutureRepeatShowTime()
{
$ccShowInstance = CcShowInstancesQuery::create()
->filterByDbShowId($this->ccShow->getDbId())
->filterByDbModifiedInstance(false)
->filterByDbStarts(gmdate("Y-m-d H:i:s"), Criteria::GREATER_THAN)
->orderByDbStarts()
->findOne();
if (!$ccShowInstance) {
return null;
}
$starts = new DateTime($ccShowInstance->getDbStarts(), new DateTimeZone("UTC"));
$ends = new DateTime($ccShowInstance->getDbEnds(), new DateTimeZone("UTC"));
$showTimezone = $this->ccShow->getFirstCcShowDay()->getDbTimezone();
$starts->setTimezone(new DateTimeZone($showTimezone));
$ends->setTimezone(new DateTimeZone($showTimezone));
return array("starts" => $starts, "ends" => $ends);
}
private function populateInstanceFormWhen($form)
{
$ccShowInstance = CcShowInstancesQuery::create()->findPk($this->instanceId);
//get timezone the show is created in
$timezone = $ccShowInstance->getCcShow()->getFirstCcShowDay()->getDbTimezone();
//$timezone = new DateTimeZone(Application_Model_Preference::GetDefaultTimezone());
//DateTime object in UTC
$showStart = $ccShowInstance->getDbStarts(null);
$showStart->setTimezone(new DateTimeZone($timezone));
$showEnd = $ccShowInstance->getDbEnds(null);
$showEnd->setTimezone(new DateTimeZone($timezone));
//if the show has started, do not allow editing on the start time
if ($showStart->getTimestamp() <= time()) {
$form->disableStartDateAndTime();
}
//Disable starting a show 'now' when editing an existing show.
$form->getElement('add_show_start_now')->setAttrib('disable', array('now'));
$form->populate(
array(
'add_show_start_now' => 'future',
'add_show_start_date' => $showStart->format("Y-m-d"),
'add_show_start_time' => $showStart->format("H:i"),
'add_show_end_date_no_repeat' => $showEnd->format("Y-m-d"),
'add_show_end_time' => $showEnd->format("H:i"),
'add_show_duration' => $this->calculateDuration(
$showStart->format(DEFAULT_TIMESTAMP_FORMAT), $showEnd->format(DEFAULT_TIMESTAMP_FORMAT), $timezone),
'add_show_timezone' => $timezone,
'add_show_repeats' => 0));
$form->getElement('add_show_repeats')->setOptions(array("disabled" => true));
}
/**
*
* Enter description here ...
* @param $form
* @param DateTime $nextFutureShowStart user's local timezone
*/
private function populateFormRepeats($form, $nextFutureShowStart)
{
if ($this->ccShow->isRepeating()) {
$ccShowDays = $this->ccShow->getRepeatingCcShowDays();
} else {
$ccShowDays = $this->ccShow->getCcShowDayss();
}
$days = array();
foreach ($ccShowDays as $ccShowDay) {
$showStart = $ccShowDay->getLocalStartDateAndTime();
array_push($days, $showStart->format("w"));
}
$service_show = new Application_Service_ShowService($this->ccShow->getDbId());
$repeatEndDate = $service_show->getRepeatingEndDate();
//end dates are stored non-inclusively so we need to
//subtract one day
if (!is_null($repeatEndDate)) {
$repeatEndDate->sub(new DateInterval("P1D"));
}
//default monthly repeat type
$monthlyRepeatType = 2;
$repeatType = $ccShowDays[0]->getDbRepeatType();
if ($repeatType == REPEAT_MONTHLY_WEEKLY) {
$monthlyRepeatType = $repeatType;
//a repeat type of 2 means the show is repeating monthly
$repeatType = 2;
} elseif ($repeatType == REPEAT_MONTHLY_MONTHLY) {
$monthlyRepeatType = $repeatType;
}
$form->populate(
array(
'add_show_linked' => $this->ccShow->getDbLinked(),
'add_show_repeat_type' => $repeatType,
'add_show_day_check' => $days,
'add_show_end_date' => (!is_null($repeatEndDate)) ? $repeatEndDate->format("Y-m-d"):null,
'add_show_no_end' => (is_null($repeatEndDate)),
'add_show_monthly_repeat_type' => $monthlyRepeatType));
if (!$this->ccShow->isLinkable() || $this->ccShow->isRecorded()) {
$form->getElement('add_show_linked')->setOptions(array('disabled' => true));
}
/* Because live editing of a linked show is disabled, we will make
* the linking option readonly if the current show is being edited. We
* dont' want the user to suddenly not be able to edit the current show
*
* Readonly does not work with checkboxes but we can't disable it
* because the value won't get posted. In add-show.js we stop the
* onclick event from firing by returning false
*/
if ($this->hasShowStarted($nextFutureShowStart)) {
$form->getElement('add_show_linked')->setAttrib('readonly', 'readonly');
}
}
private function populateFormWho($form)
{
$ccShowHosts = $this->ccShow->getCcShowHostss();
$hosts = array();
foreach ($ccShowHosts as $ccShowHost) {
array_push($hosts, $ccShowHost->getDbHost());
}
$form->populate(array('add_show_hosts' => $hosts));
}
private function populateFormStyle($form)
{
$src = $this->ccShow->getDbImagePath() ?
$this->imagePathToDataUri($this->ccShow->getDbImagePath()) : '';
$form->populate(
array(
'add_show_background_color' => $this->ccShow->getDbBackgroundColor(),
'add_show_color' => $this->ccShow->getDbColor(),
'add_show_logo_current' => $src));
}
/**
* Convert a static image from disk to a base64 data URI
*
* @param unknown $path
* - the path to the image on the disk
* @return string
* - the data URI representation of the image
*/
private function imagePathToDataUri($path) {
$imageData = null;
$bytesRead = 0;
try {
ob_start();
header("Content-type: image/*");
$bytesRead = @readfile($path);
$imageData = base64_encode(ob_get_contents());
ob_end_clean();
if ($bytesRead === FALSE) {
$imageData = null;
}
} catch (Exception $e) {
Logging::error("Failed to read image: " . $path);
$imageData = null;
}
// return the data URI - data:{mime};base64,{data}
return ($imageData === null || $imageData === '') ?
'' : 'data: '.mime_content_type($path).';base64,'.$imageData;
}
private function populateFormLive($form)
{
$form->populate(
array(
"cb_airtime_auth" => $this->ccShow->getDbLiveStreamUsingAirtimeAuth(),
"cb_custom_auth" => $this->ccShow->getDbLiveStreamUsingCustomAuth(),
"custom_username" => $this->ccShow->getDbLiveStreamUser(),
"custom_password" => $this->ccShow->getDbLiveStreamPass()));
}
private function populateFormRecord($form)
{
$form->populate(
array(
'add_show_record' => $this->ccShow->isRecorded(),
'add_show_rebroadcast' => $this->ccShow->isRebroadcast()));
$form->getElement('add_show_record')->setOptions(array('disabled' => true));
}
private function populateFormRebroadcastRelative($form)
{
$relativeRebroadcasts = $this->ccShow->getRebroadcastsRelative();
$formValues = array();
$i = 1;
foreach ($relativeRebroadcasts as $rr) {
$formValues["add_show_rebroadcast_date_$i"] = $rr->getDbDayOffset();
$formValues["add_show_rebroadcast_time_$i"] = Application_Common_DateHelper::removeSecondsFromTime(
$rr->getDbStartTime());
$i++;
}
$form->populate($formValues);
}
private function populateFormRebroadcastAbsolute($form)
{
$absolutRebroadcasts = $this->ccShow->getRebroadcastsAbsolute();
$timezone = $this->ccShow->getFirstCcShowDay()->getDbTimezone();
$formValues = array();
$i = 1;
foreach ($absolutRebroadcasts as $ar) {
//convert dates to user's local time
$start = new DateTime($ar->getDbStarts(), new DateTimeZone("UTC"));
$start->setTimezone(new DateTimeZone($timezone));
$formValues["add_show_rebroadcast_date_absolute_$i"] = $start->format("Y-m-d");
$formValues["add_show_rebroadcast_time_absolute_$i"] = $start->format("H:i");
$i++;
}
$form->populate($formValues);
}
/**
*
* Enter description here ...
* @param DateTime $showStart user's local time
*/
private function hasShowStarted($p_showStart) {
$showStart = clone $p_showStart;
$showStart->setTimeZone(new DateTimeZone("UTC"));
if ($showStart->format("Y-m-d H:i:s") < gmdate("Y-m-d H:i:s")) {
return true;
} else {
return false;
}
}
/**
* Before we send the form data in for validation, there
* are a few fields we may need to adjust first
*
* @param $formData
*/
public function preEditShowValidationCheck($formData)
{
// If the start date or time were disabled, don't validate them
$validateStartDate = $formData['start_date_disabled'] === "false";
$validateStartTime = $formData['start_time_disabled'] === "false";
//CcShowDays object of the show currently being edited
$currentShowDay = $this->ccShow->getFirstCcShowDay();
//DateTime object
$dt = $currentShowDay->getLocalStartDateAndTime();
$formData['add_show_record'] = $currentShowDay->getDbRecord();
//if the show is repeating, set the start date to the next
//repeating instance in the future
$originalShowStartDateTime = $this->getCurrentOrNextInstanceStartTime();
if (!$originalShowStartDateTime) {
$originalShowStartDateTime = $dt;
}
return array($formData, $validateStartDate, $validateStartTime, $originalShowStartDateTime);
}
/**
*
* Returns a DateTime object, in the user's local time,
* of the current or next show instance start time
*
* Returns null if there is no next future repeating show instance
*/
public function getCurrentOrNextInstanceStartTime()
{
$ccShowInstance = CcShowInstancesQuery::create()
->filterByDbShowId($this->ccShow->getDbId())
->filterByDbModifiedInstance(false)
->filterByDbStarts(gmdate("Y-m-d H:i:s"), Criteria::GREATER_EQUAL)
->orderByDbStarts()
->findOne();
if (!$ccShowInstance) {
return null;
}
$starts = new DateTime($ccShowInstance->getDbStarts(), new DateTimeZone("UTC"));
$showTimezone = $this->ccShow->getFirstCcShowDay()->getDbTimezone();
$starts->setTimezone(new DateTimeZone($showTimezone));
return $starts;
}
/**
*
* Validates show forms
*
* @return boolean
*/
public function validateShowForms($forms, $formData, $validateStartDate = true,
$originalStartDate=null, $editShow=false, $instanceId=null)
{
$what = $forms["what"]->isValid($formData);
$autoplaylist = $forms["autoplaylist"]->isValid($formData);
$live = $forms["live"]->isValid($formData);
$record = $forms["record"]->isValid($formData);
$who = $forms["who"]->isValid($formData);
/*
* hack to prevent validating the file upload field since it
* isn't passed into $data
*/
$upload = $forms["style"]->getElement("add_show_logo");
$forms["style"]->removeElement("add_show_logo");
$style = $forms["style"]->isValid($formData);
// re-add the upload element
$forms["style"]->addElement($upload);
$when = $forms["when"]->isWhenFormValid($formData, $validateStartDate,
$originalStartDate, $editShow, $instanceId);
$repeats = true;
if ($formData["add_show_repeats"]) {
$repeats = $forms["repeats"]->isValid($formData);
/*
* Make the absolute rebroadcast form valid since
* it does not get used if the show is repeating
*/
$forms["abs_rebroadcast"]->reset();
$absRebroadcast = true;
$rebroadcast = true;
if (isset($formData["add_show_rebroadcast"]) && $formData["add_show_rebroadcast"]) {
$formData["add_show_duration"] = Application_Service_ShowService::formatShowDuration(
$formData["add_show_duration"]);
$rebroadcast = $forms["rebroadcast"]->isValid($formData);
}
} else {
/*
* Make the rebroadcast form valid since it does
* not get used if the show is not repeating.
* Instead, we use the absolute rebroadcast form
*/
$forms["rebroadcast"]->reset();
$rebroadcast = true;
$absRebroadcast = true;
if (isset($formData["add_show_rebroadcast"]) && $formData["add_show_rebroadcast"]) {
$formData["add_show_duration"] = Application_Service_ShowService::formatShowDuration(
$formData["add_show_duration"]);
$absRebroadcast = $forms["abs_rebroadcast"]->isValid($formData);
}
}
return ($what && $autoplaylist && $live && $record && $who && $style && $when &&
$repeats && $absRebroadcast && $rebroadcast);
}
public function calculateDuration($start, $end, $timezone)
{
try {
$tz = new DateTimeZone($timezone);
$startDateTime = new DateTime($start, $tz);
$endDateTime = new DateTime($end, $tz);
$duration = $startDateTime->diff($endDateTime);
$day = intval($duration->format('%d'));
if ($day > 0) {
$hour = intval($duration->format('%h'));
$min = intval($duration->format('%i'));
$hour += $day * 24;
$hour = min($hour, 99);
$sign = $duration->format('%r');
return sprintf('%s%02dh %02dm', $sign, $hour, $min);
} else {
return $duration->format('%r%Hh %Im');
}
} catch (Exception $e) {
Logging::info($e->getMessage());
return "Invalid Date";
}
}
/**
* When the timezone is changed in add-show form this function
* applies the new timezone to the start and end time
*
* @param $date String
* @param $time String
* @param $timezone String
*/
public static function localizeDateTime($date, $time, $newTimezone, $oldTimezone)
{
$dt = new DateTime($date." ".$time, new DateTimeZone($oldTimezone));
$dt->setTimeZone(new DateTimeZone($newTimezone));
return array(
"date" => $dt->format("Y-m-d"),
"time" => $dt->format("H:i")
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,109 @@
<?php
abstract class Application_Service_ThirdPartyCeleryService extends Application_Service_ThirdPartyService {
/**
* @var string broker exchange name for third-party tasks
*/
protected static $_CELERY_EXCHANGE_NAME;
/**
* @var array map of celery identifiers to their task names
*/
protected static $_CELERY_TASKS;
/**
* Execute a Celery task with the given name and data parameters
*
* @param string $taskName the name of the celery task to execute
* @param array $data the data array to send as task parameters
* @param int $fileId the unique identifier for the file involved in the task
*
* @return CeleryTasks the created task
*
* @throws Exception
*/
protected function _executeTask($taskName, $data, $fileId = null) {
try {
$brokerTaskId = CeleryManager::sendCeleryMessage($taskName,
static::$_CELERY_EXCHANGE_NAME,
$data);
return $this->_createTaskReference($fileId, $brokerTaskId, $taskName);
} catch (Exception $e) {
Logging::error("Invalid request: " . $e->getMessage());
throw $e;
}
}
/**
* Create a CeleryTasks object for a pending task
* TODO: should we have a database layer class to handle Propel operations?
*
* @param $fileId int CcFiles identifier
* @param $brokerTaskId int broker task identifier to so we can asynchronously
* receive completed task messages
* @param $taskName string broker task name
*
* @return CeleryTasks the created task
*
* @throws Exception
* @throws PropelException
*/
protected function _createTaskReference($fileId, $brokerTaskId, $taskName) {
$trackReferenceId = $this->createTrackReference($fileId);
$task = new CeleryTasks();
$task->setDbTaskId($brokerTaskId);
$task->setDbName($taskName);
$utc = new DateTimeZone("UTC");
$task->setDbDispatchTime(new DateTime("now", $utc));
$task->setDbStatus(CELERY_PENDING_STATUS);
$task->setDbTrackReference($trackReferenceId);
$task->save();
return $task;
}
/**
* Update a CeleryTasks object for a completed task
* TODO: should we have a database layer class to handle Propel operations?
*
* @param $task CeleryTasks the completed CeleryTasks object
* @param $status string Celery task status
*
* @throws Exception
* @throws PropelException
*/
public function updateTask($task, $status) {
$task->setDbStatus($status);
$task->save();
}
/**
* Update a ThirdPartyTrackReferences object for a completed upload
*
* Manipulation and use of the track object is left up to child implementations
*
* @param $task CeleryTasks the completed CeleryTasks object
* @param $trackId int ThirdPartyTrackReferences identifier
* @param $result mixed Celery task result message
* @param $status string Celery task status
*
* @return ThirdPartyTrackReferences the updated ThirdPartyTrackReferences object
*
* @throws Exception
* @throws PropelException
*/
public function updateTrackReference($task, $trackId, $result, $status) {
static::updateTask($task, $status);
$ref = ThirdPartyTrackReferencesQuery::create()
->findOneByDbId($trackId);
if (is_null($ref)) {
$ref = new ThirdPartyTrackReferences();
}
$ref->setDbService(static::$_SERVICE_NAME);
$utc = new DateTimeZone("UTC");
$ref->setDbUploadTime(new DateTime("now", $utc));
$ref->save();
return $ref;
}
}

View file

@ -0,0 +1,100 @@
<?php
/**
* Class ServiceNotFoundException
*/
class ServiceNotFoundException extends Exception {}
/**
* Class ThirdPartyService generic superclass for third-party services
*/
abstract class Application_Service_ThirdPartyService {
/**
* @var string service name to store in ThirdPartyTrackReferences database
*/
protected static $_SERVICE_NAME;
/**
* Create a ThirdPartyTrackReferences object for a track that's been uploaded
* to an external service
* TODO: should we have a database layer class to handle Propel operations?
*
* @param $fileId int local CcFiles identifier
*
* @return string the new ThirdPartyTrackReferences identifier
*
* @throws Exception
* @throws PropelException
*/
public function createTrackReference($fileId) {
// First, check if the track already has an entry in the database
// If the file ID given is null, create a new reference
$ref = is_null($fileId) ? null : ThirdPartyTrackReferencesQuery::create()
->filterByDbService(static::$_SERVICE_NAME)
->findOneByDbFileId($fileId);
if (is_null($ref)) {
$ref = new ThirdPartyTrackReferences();
}
$ref->setDbService(static::$_SERVICE_NAME);
$ref->setDbFileId($fileId);
$ref->save();
return $ref->getDbId();
}
/**
* Remove a ThirdPartyTrackReferences row from the database.
* This is necessary if the track was removed from the service
* or the foreign id in our database is incorrect
*
* @param $fileId int cc_files identifier
*
* @throws Exception
* @throws PropelException
*/
public function removeTrackReference($fileId) {
$ref = ThirdPartyTrackReferencesQuery::create()
->filterByDbService(static::$_SERVICE_NAME)
->findOneByDbFileId($fileId);
$ref->delete();
}
/**
* Given a CcFiles identifier for a file that's been uploaded to a third-party service,
* return the third-party identifier for the remote file
*
* @param int $fileId the cc_files identifier
*
* @return string the service foreign identifier
*/
public function getServiceId($fileId) {
$ref = ThirdPartyTrackReferencesQuery::create()
->filterByDbService(static::$_SERVICE_NAME)
->findOneByDbFileId($fileId); // There shouldn't be duplicates!
return empty($ref) ? '' : $ref->getDbForeignId();
}
/**
* Check if a reference exists for a given CcFiles identifier
*
* @param int $fileId the cc_files identifier
*
* @return int 1 if the file has been published,
* 0 if the file has yet to be published,
* or -1 if the file is in a pending state
*/
public function referenceExists($fileId) {
$ref = ThirdPartyTrackReferencesQuery::create()
->filterByDbService(static::$_SERVICE_NAME)
->findOneByDbFileId($fileId);
if (!empty($ref)) {
$task = CeleryTasksQuery::create()
->orderByDbDispatchTime(Criteria::DESC)
->findOneByDbTrackReference($ref->getDbId());
return $task->getDbStatus() == CELERY_PENDING_STATUS ? -1
: ($task->getDbStatus() == CELERY_FAILED_STATUS ? 0 : 1);
}
return 0;
}
}

View file

@ -0,0 +1,28 @@
<?php
class Application_Service_UserService
{
private $currentUser;
public function __construct()
{
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
if (!is_null($userInfo->id)) {
$this->currentUser = CcSubjsQuery::create()->findPK($userInfo->id);
}
}
/**
*
* Returns a CcSubjs object
*/
public function getCurrentUser()
{
if (is_null($this->currentUser)) {
throw new Exception();
}
return $this->currentUser;
}
}