Rename airtime_mvc/ to legacy/
This commit is contained in:
parent
f0879322c2
commit
3e18d42c8b
1316 changed files with 0 additions and 0 deletions
400
legacy/application/services/CalendarService.php
Normal file
400
legacy/application/services/CalendarService.php
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
21
legacy/application/services/CeleryServiceFactory.php
Normal file
21
legacy/application/services/CeleryServiceFactory.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1535
legacy/application/services/HistoryService.php
Normal file
1535
legacy/application/services/HistoryService.php
Normal file
File diff suppressed because it is too large
Load diff
153
legacy/application/services/MediaService.php
Normal file
153
legacy/application/services/MediaService.php
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
503
legacy/application/services/PodcastEpisodeService.php
Normal file
503
legacy/application/services/PodcastEpisodeService.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
15
legacy/application/services/PodcastFactory.php
Normal file
15
legacy/application/services/PodcastFactory.php
Normal 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);
|
||||
}
|
||||
}
|
515
legacy/application/services/PodcastService.php
Normal file
515
legacy/application/services/PodcastService.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
83
legacy/application/services/PublishService.php
Normal file
83
legacy/application/services/PublishService.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
21
legacy/application/services/PublishServiceFactory.php
Normal file
21
legacy/application/services/PublishServiceFactory.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
490
legacy/application/services/SchedulerService.php
Normal file
490
legacy/application/services/SchedulerService.php
Normal 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;
|
||||
}
|
||||
}
|
630
legacy/application/services/ShowFormService.php
Normal file
630
legacy/application/services/ShowFormService.php
Normal 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")
|
||||
);
|
||||
}
|
||||
}
|
1842
legacy/application/services/ShowService.php
Normal file
1842
legacy/application/services/ShowService.php
Normal file
File diff suppressed because it is too large
Load diff
109
legacy/application/services/ThirdPartyCeleryService.php
Normal file
109
legacy/application/services/ThirdPartyCeleryService.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
100
legacy/application/services/ThirdPartyService.php
Normal file
100
legacy/application/services/ThirdPartyService.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
28
legacy/application/services/UserService.php
Normal file
28
legacy/application/services/UserService.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue