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 = []; $now = time(); $baseUrl = Config::getBasePath(); $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'] = [ 'name' => _("Record file doesn't exist"), 'icon' => 'error', ]; } else { $menu['view_recorded'] = [ 'name' => _('View Recorded File Metadata'), 'icon' => 'overview', 'url' => $baseUrl . 'library/edit-file-md/id/' . $ccFile->getDbId(), ]; } } else { $menu['content'] = [ // "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'] = [ // "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'] = [ // "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'] = [ // "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'] = [ // "name"=> _("Cancel Current Show"), 'name' => _('Cancel Show'), 'icon' => 'delete', ]; } else { $menu['cancel'] = [ // "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'] = [ // "name" => _("Edit This Instance"), 'name' => _('Edit Instance'), 'icon' => 'edit', 'url' => $baseUrl . 'Schedule/populate-repeating-show-instance-form', ]; } else { $menu['edit'] = [ 'name' => _('Edit'), 'icon' => 'edit', 'items' => [], ]; $menu['edit']['items']['all'] = [ 'name' => _('Edit Show'), 'icon' => 'edit', 'url' => $baseUrl . 'Schedule/populate-show-form', ]; $menu['edit']['items']['instance'] = [ // "name" => _("Edit This Instance"), 'name' => _('Edit Instance'), 'icon' => 'edit', 'url' => $baseUrl . 'Schedule/populate-repeating-show-instance-form', ]; } } else { $menu['edit'] = [ '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'] = [ 'name' => _('Delete'), 'icon' => 'delete', 'items' => [], ]; $menu['del']['items']['single'] = [ // "name"=> _("Delete This Instance"), 'name' => _('Delete Instance'), 'icon' => 'delete', 'url' => $baseUrl . 'schedule/delete-show-instance', ]; $menu['del']['items']['following'] = [ // "name"=> _("Delete This Instance and All Following"), 'name' => _('Delete Instance and All Following'), 'icon' => 'delete', 'url' => $baseUrl . 'schedule/delete-show', ]; } elseif ($populateInstance) { $menu['del'] = [ 'name' => _('Delete'), 'icon' => 'delete', 'url' => $baseUrl . 'schedule/delete-show-instance', ]; } else { $menu['del'] = [ '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 [$newStartsDateTime, $newEndsDateTime]; } public function moveShow($deltaDay, $deltaMin) { try { $con = Propel::getConnection(); $con->beginTransaction(); // new starts,ends are in UTC [$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( [$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(); } } }