CalendarService.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. <?php
  2. class Application_Service_CalendarService
  3. {
  4. private $currentUser;
  5. private $ccShowInstance;
  6. private $ccShow;
  7. public function __construct($instanceId = null)
  8. {
  9. if (!is_null($instanceId)) {
  10. $this->ccShowInstance = CcShowInstancesQuery::create()->findPk($instanceId);
  11. if (is_null($this->ccShowInstance)) {
  12. throw new Exception("Instance does not exist");
  13. }
  14. $this->ccShow = $this->ccShowInstance->getCcShow();
  15. }
  16. $service_user = new Application_Service_UserService();
  17. $this->currentUser = $service_user->getCurrentUser();
  18. }
  19. /**
  20. *
  21. * Enter description here ...
  22. */
  23. public function makeContextMenu()
  24. {
  25. $menu = array();
  26. $now = time();
  27. $baseUrl = Application_Common_OsPath::getBaseDir();
  28. $isAdminOrPM = $this->currentUser->isAdminOrPM();
  29. $isHostOfShow = $this->currentUser->isHostOfShow($this->ccShow->getDbId());
  30. //DateTime objects in UTC
  31. $startDT = $this->ccShowInstance->getDbStarts(null);
  32. $endDT = $this->ccShowInstance->getDbEnds(null);
  33. //timestamps
  34. $start = $startDT->getTimestamp();
  35. $end = $endDT->getTimestamp();
  36. //show has ended
  37. if ($now > $end) {
  38. if ($this->ccShowInstance->isRecorded()) {
  39. $ccFile = $this->ccShowInstance->getCcFiles();
  40. if (!isset($ccFile)) {
  41. $menu["error when recording"] = array (
  42. "name" => _("Record file doesn't exist"),
  43. "icon" => "error");
  44. }else {
  45. $menu["view_recorded"] = array(
  46. "name" => _("View Recorded File Metadata"),
  47. "icon" => "overview",
  48. "url" => $baseUrl."library/edit-file-md/id/".$ccFile->getDbId());
  49. }
  50. //recorded show can be uploaded to soundcloud
  51. if (Application_Model_Preference::GetUploadToSoundcloudOption()) {
  52. $scid = $ccFile->getDbSoundcloudId();
  53. if ($scid > 0) {
  54. $menu["soundcloud_view"] = array(
  55. "name" => _("View on Soundcloud"),
  56. "icon" => "soundcloud",
  57. "url" => $ccFile->getDbSoundcloudLinkToFile());
  58. }
  59. $text = is_null($scid) ? _('Upload to SoundCloud') : _('Re-upload to SoundCloud');
  60. $menu["soundcloud_upload"] = array(
  61. "name"=> $text,
  62. "icon" => "soundcloud");
  63. }
  64. } else {
  65. $menu["content"] = array(
  66. "name"=> _("Show Content"),
  67. "icon" => "overview",
  68. "url" => $baseUrl."schedule/show-content-dialog");
  69. }
  70. } else {
  71. // Show content can be modified from the calendar if:
  72. // the user is admin or hosting the show,
  73. // the show is not recorded
  74. $currentShow = Application_Model_Show::getCurrentShow();
  75. $currentShowId = count($currentShow) == 1 ? $currentShow[0]["id"] : null;
  76. $showIsLinked = $this->ccShow->isLinked();
  77. //user can add/remove content if the show has not ended
  78. if ($now < $end && ($isAdminOrPM || $isHostOfShow) && !$this->ccShowInstance->isRecorded()) {
  79. //if the show is not linked OR if the show is linked AND not the current playing show
  80. //the user can add/remove content
  81. if (!$showIsLinked || ($showIsLinked && $currentShowId != $this->ccShow->getDbId())) {
  82. $menu["schedule"] = array(
  83. "name"=> _("Add / Remove Content"),
  84. "icon" => "add-remove-content",
  85. "url" => $baseUrl."showbuilder/builder-dialog/");
  86. }
  87. }
  88. //user can remove all content if the show has not started
  89. if ($now < $start && ($isAdminOrPM || $isHostOfShow) && !$this->ccShowInstance->isRecorded() ) {
  90. //if the show is not linked OR if the show is linked AND not the current playing show
  91. //the user can remove all content
  92. if (!$showIsLinked || ($showIsLinked && $currentShowId != $this->ccShow->getDbId())) {
  93. $menu["clear"] = array(
  94. "name"=> _("Remove All Content"),
  95. "icon" => "remove-all-content",
  96. "url" => $baseUrl."schedule/clear-show");
  97. }
  98. }
  99. //"Show Content" should be a menu item at all times except when
  100. //the show is recorded
  101. if (!$this->ccShowInstance->isRecorded()) {
  102. $menu["content"] = array(
  103. "name"=> _("Show Content"),
  104. "icon" => "overview",
  105. "url" => $baseUrl."schedule/show-content-dialog");
  106. }
  107. //show is currently playing and user is admin
  108. if ($start <= $now && $now < $end && $isAdminOrPM) {
  109. if ($this->ccShowInstance->isRecorded()) {
  110. $menu["cancel_recorded"] = array(
  111. "name"=> _("Cancel Current Show"),
  112. "icon" => "delete");
  113. } else {
  114. $menu["cancel"] = array(
  115. "name"=> _("Cancel Current Show"),
  116. "icon" => "delete");
  117. }
  118. }
  119. $excludeIds = $this->ccShow->getEditedRepeatingInstanceIds();
  120. $isRepeating = $this->ccShow->isRepeating();
  121. $populateInstance = false;
  122. if ($isRepeating && in_array($this->ccShowInstance->getDbId(), $excludeIds)) {
  123. $populateInstance = true;
  124. }
  125. if (!$this->ccShowInstance->isRebroadcast() && $isAdminOrPM) {
  126. if ($isRepeating) {
  127. if ($populateInstance) {
  128. $menu["edit"] = array(
  129. "name" => _("Edit This Instance"),
  130. "icon" => "edit",
  131. "url" => $baseUrl."Schedule/populate-repeating-show-instance-form");
  132. } else {
  133. $menu["edit"] = array(
  134. "name" => _("Edit"),
  135. "icon" => "edit",
  136. "items" => array());
  137. $menu["edit"]["items"]["all"] = array(
  138. "name" => _("Edit Show"),
  139. "icon" => "edit",
  140. "url" => $baseUrl."Schedule/populate-show-form");
  141. $menu["edit"]["items"]["instance"] = array(
  142. "name" => _("Edit This Instance"),
  143. "icon" => "edit",
  144. "url" => $baseUrl."Schedule/populate-repeating-show-instance-form");
  145. }
  146. } else {
  147. $menu["edit"] = array(
  148. "name"=> _("Edit Show"),
  149. "icon" => "edit",
  150. "_type"=>"all",
  151. "url" => $baseUrl."Schedule/populate-show-form");
  152. }
  153. }
  154. //show hasn't started yet and user is admin
  155. if ($now < $start && $isAdminOrPM) {
  156. //show is repeating so give user the option to delete all
  157. //repeating instances or just the one
  158. if ($isRepeating) {
  159. $menu["del"] = array(
  160. "name"=> _("Delete"),
  161. "icon" => "delete",
  162. "items" => array());
  163. $menu["del"]["items"]["single"] = array(
  164. "name"=> _("Delete This Instance"),
  165. "icon" => "delete",
  166. "url" => $baseUrl."schedule/delete-show-instance");
  167. $menu["del"]["items"]["following"] = array(
  168. "name"=> _("Delete This Instance and All Following"),
  169. "icon" => "delete",
  170. "url" => $baseUrl."schedule/delete-show");
  171. } elseif ($populateInstance) {
  172. $menu["del"] = array(
  173. "name"=> _("Delete"),
  174. "icon" => "delete",
  175. "url" => $baseUrl."schedule/delete-show-instance");
  176. } else {
  177. $menu["del"] = array(
  178. "name"=> _("Delete"),
  179. "icon" => "delete",
  180. "url" => $baseUrl."schedule/delete-show");
  181. }
  182. }
  183. }
  184. return $menu;
  185. }
  186. /**
  187. *
  188. * Enter description here ...
  189. * @param DateTime $dateTime object to add deltas to
  190. * @param int $deltaDay delta days show moved
  191. * @param int $deltaMin delta minutes show moved
  192. */
  193. public static function addDeltas($dateTime, $deltaDay, $deltaMin)
  194. {
  195. $newDateTime = clone $dateTime;
  196. $days = abs($deltaDay);
  197. $mins = abs($deltaMin);
  198. $dayInterval = new DateInterval("P{$days}D");
  199. $minInterval = new DateInterval("PT{$mins}M");
  200. if ($deltaDay > 0) {
  201. $newDateTime->add($dayInterval);
  202. } elseif ($deltaDay < 0) {
  203. $newDateTime->sub($dayInterval);
  204. }
  205. if ($deltaMin > 0) {
  206. $newDateTime->add($minInterval);
  207. } elseif ($deltaMin < 0) {
  208. $newDateTime->sub($minInterval);
  209. }
  210. return $newDateTime;
  211. }
  212. private function validateShowMove($deltaDay, $deltaMin)
  213. {
  214. if (!$this->currentUser->isAdminOrPM()) {
  215. throw new Exception(_("Permission denied"));
  216. }
  217. if ($this->ccShow->isRepeating()) {
  218. throw new Exception(_("Can't drag and drop repeating shows"));
  219. }
  220. $today_timestamp = time();
  221. $startsDateTime = $this->ccShowInstance->getDbStarts(null);
  222. $endsDateTime = $this->ccShowInstance->getDbEnds(null);
  223. if ($today_timestamp > $startsDateTime->getTimestamp()) {
  224. throw new Exception(_("Can't move a past show"));
  225. }
  226. //the user is moving the show on the calendar from the perspective of local time.
  227. //incase a show is moved across a time change border offsets should be added to the localtime
  228. //stamp and then converted back to UTC to avoid show time changes!
  229. $showTimezone = $this->ccShow->getFirstCcShowDay()->getDbTimezone();
  230. $startsDateTime->setTimezone(new DateTimeZone($showTimezone));
  231. $endsDateTime->setTimezone(new DateTimeZone($showTimezone));
  232. $duration = $startsDateTime->diff($endsDateTime);
  233. $newStartsDateTime = self::addDeltas($startsDateTime, $deltaDay, $deltaMin);
  234. /* WARNING: Do not separately add a time delta to the start and end times because
  235. that does not preserve the duration across a DST time change.
  236. For example, 5am - 3 hours = 3am when DST occurs at 2am.
  237. BUT, 6am - 3 hours = 3am also!
  238. So when a DST change occurs, adding the deltas like this
  239. separately does not conserve the duration of a show.
  240. Since that's what we want (otherwise we'll get a zero length show),
  241. we calculate the show duration FIRST, then we just add that on
  242. to the start time to calculate the end time.
  243. This is a safer approach.
  244. The key lesson here is that in general: duration != end - start
  245. ... so be careful!
  246. */
  247. //$newEndsDateTime = self::addDeltas($endsDateTime, $deltaDay, $deltaMin); <--- Wrong, don't do it.
  248. $newEndsDateTime = clone $newStartsDateTime;
  249. $newEndsDateTime = $newEndsDateTime->add($duration);
  250. //convert our new starts/ends to UTC.
  251. $newStartsDateTime->setTimezone(new DateTimeZone("UTC"));
  252. $newEndsDateTime->setTimezone(new DateTimeZone("UTC"));
  253. if ($today_timestamp > $newStartsDateTime->getTimestamp()) {
  254. throw new Exception(_("Can't move show into past"));
  255. }
  256. //check if show is overlapping
  257. $overlapping = Application_Model_Schedule::checkOverlappingShows(
  258. $newStartsDateTime, $newEndsDateTime, true, $this->ccShowInstance->getDbId());
  259. if ($overlapping) {
  260. throw new Exception(_("Cannot schedule overlapping shows"));
  261. }
  262. if ($this->ccShow->isRecorded()) {
  263. //rebroadcasts should start at max 1 hour after a recorded show has ended.
  264. $minRebroadcastStart = self::addDeltas($newEndsDateTime, 0, 60);
  265. //check if we are moving a recorded show less than 1 hour before any of its own rebroadcasts.
  266. $rebroadcasts = CcShowInstancesQuery::create()
  267. ->filterByDbOriginalShow($this->ccShow->getDbId())
  268. ->filterByDbStarts($minRebroadcastStart->format('Y-m-d H:i:s'), Criteria::LESS_THAN)
  269. ->find();
  270. if (count($rebroadcasts) > 0) {
  271. throw new Exception(_("Can't move a recorded show less than 1 hour before its rebroadcasts."));
  272. }
  273. }
  274. if ($this->ccShow->isRebroadcast()) {
  275. $recordedShow = CcShowInstancesQuery::create()
  276. ->filterByCcShow($this->ccShowInstance->getDbOriginalShow())
  277. ->findOne();
  278. if (is_null($recordedShow)) {
  279. $this->ccShowInstance->delete();
  280. throw new Exception(_("Show was deleted because recorded show does not exist!"));
  281. }
  282. $recordEndDateTime = new DateTime($recordedShow->getDbEnds(), new DateTimeZone("UTC"));
  283. $newRecordEndDateTime = self::addDeltas($recordEndDateTime, 0, 60);
  284. if ($newStartsDateTime->getTimestamp() < $newRecordEndDateTime->getTimestamp()) {
  285. throw new Exception(_("Must wait 1 hour to rebroadcast."));
  286. }
  287. }
  288. return array($newStartsDateTime, $newEndsDateTime);
  289. }
  290. public function moveShow($deltaDay, $deltaMin)
  291. {
  292. try {
  293. $con = Propel::getConnection();
  294. $con->beginTransaction();
  295. //new starts,ends are in UTC
  296. list($newStartsDateTime, $newEndsDateTime) = $this->validateShowMove(
  297. $deltaDay, $deltaMin);
  298. $oldStartDateTime = $this->ccShowInstance->getDbStarts(null);
  299. $this->ccShowInstance
  300. ->setDbStarts($newStartsDateTime)
  301. ->setDbEnds($newEndsDateTime)
  302. ->save($con);
  303. if (!$this->ccShowInstance->getCcShow()->isRebroadcast()) {
  304. //we can get the first show day because we know the show is
  305. //not repeating, and therefore will only have one show day entry
  306. $ccShowDay = $this->ccShow->getFirstCcShowDay();
  307. $showTimezone = new DateTimeZone($ccShowDay->getDbTimezone());
  308. $ccShowDay
  309. ->setDbFirstShow($newStartsDateTime->setTimezone($showTimezone)->format("Y-m-d"))
  310. ->setDbStartTime($newStartsDateTime->format("H:i"))
  311. ->save($con);
  312. }
  313. $diff = $newStartsDateTime->getTimestamp() - $oldStartDateTime->getTimestamp();
  314. Application_Service_SchedulerService::updateScheduleStartTime(
  315. array($this->ccShowInstance->getDbId()), $diff);
  316. $con->commit();
  317. Application_Model_RabbitMq::PushSchedule();
  318. } catch (Exception $e) {
  319. $con->rollback();
  320. return $e->getMessage();
  321. }
  322. }
  323. //TODO move the method resizeShow from Application_Model_Show here.
  324. public function resizeShow($deltaDay, $deltaMin)
  325. {
  326. try {
  327. $con = Propel::getConnection();
  328. $con->beginTransaction();
  329. $con->commit();
  330. Application_Model_RabbitMq::PushSchedule();
  331. } catch (Exception $e) {
  332. return $e->getMessage();
  333. }
  334. }
  335. }