public function render() { require_celerity_resource('phui-calendar-day-css'); $viewer = $this->getUser(); $hours = $this->getHoursOfDay(); $js_hours = array(); $js_today_events = array(); foreach ($hours as $hour) { $js_hours[] = array('hour' => $hour->format('G'), 'displayTime' => phabricator_time($hour->format('U'), $viewer)); } $first_event_hour = null; $js_today_all_day_events = array(); $all_day_events = $this->getAllDayEvents(); $day_start = $this->getDateTime(); $day_end = id(clone $day_start)->modify('+1 day'); $day_start_epoch = $day_start->format('U'); $day_end_epoch = $day_end->format('U') - 1; foreach ($all_day_events as $all_day_event) { $all_day_start = $all_day_event->getEpochStart(); $all_day_end = $all_day_event->getEpochEnd(); if ($all_day_start < $day_end_epoch && $all_day_end > $day_start_epoch) { $js_today_all_day_events[] = array('name' => $all_day_event->getName(), 'id' => $all_day_event->getEventID(), 'viewerIsInvited' => $all_day_event->getViewerIsInvited(), 'uri' => $all_day_event->getURI(), 'displayIcon' => $all_day_event->getIcon(), 'displayIconColor' => $all_day_event->getIconColor()); } } $this->events = msort($this->events, 'getEpochStart'); $first_event_hour = $this->getDateTime()->setTime(8, 0, 0); $midnight = $this->getDateTime()->setTime(0, 0, 0); foreach ($this->events as $event) { if ($event->getIsAllDay()) { continue; } if ($event->getEpochStart() <= $day_end_epoch && $event->getEpochEnd() > $day_start_epoch) { if ($event->getEpochStart() < $midnight->format('U') && $event->getEpochEnd() > $midnight->format('U')) { $first_event_hour = clone $midnight; } if ($event->getEpochStart() < $first_event_hour->format('U') && $event->getEpochStart() > $midnight->format('U')) { $first_event_hour = PhabricatorTime::getDateTimeFromEpoch($event->getEpochStart(), $viewer); $first_event_hour->setTime($first_event_hour->format('h'), 0, 0); } $event_start = max($event->getEpochStart(), $day_start_epoch); $event_end = min($event->getEpochEnd(), $day_end_epoch); $day_duration = ($day_end_epoch - $first_event_hour->format('U')) / 60; $top = ($event_start - $first_event_hour->format('U')) / ($day_end_epoch - $first_event_hour->format('U')) * $day_duration; $top = max(0, $top); $height = ($event_end - $event_start) / ($day_end_epoch - $first_event_hour->format('U')) * $day_duration; $height = min($day_duration, $height); $js_today_events[] = array('eventStartEpoch' => $event->getEpochStart(), 'eventEndEpoch' => $event->getEpochEnd(), 'eventName' => $event->getName(), 'eventID' => $event->getEventID(), 'viewerIsInvited' => $event->getViewerIsInvited(), 'uri' => $event->getURI(), 'offset' => '0', 'width' => '100%', 'top' => $top . 'px', 'height' => $height . 'px', 'canEdit' => $event->getCanEdit(), 'displayIcon' => $event->getIcon(), 'displayIconColor' => $event->getIconColor()); } } $header = $this->renderDayViewHeader(); $sidebar = id(new PHUICalendarWeekView())->setViewer($this->getViewer())->setEvents($this->events)->setDateTime($this->getDateTime())->render(); $warnings = $this->getQueryRangeWarning(); $table_id = celerity_generate_unique_node_id(); $table_wrapper = phutil_tag('div', array('id' => $table_id), ''); Javelin::initBehavior('day-view', array('year' => $first_event_hour->format('Y'), 'month' => $first_event_hour->format('m'), 'day' => $first_event_hour->format('d'), 'query' => $this->getQuery(), 'allDayEvents' => $js_today_all_day_events, 'todayEvents' => $js_today_events, 'hours' => $js_hours, 'firstEventHour' => $first_event_hour->format('G'), 'firstEventHourEpoch' => $first_event_hour->format('U'), 'tableID' => $table_id)); $table_box = id(new PHUIObjectBoxView())->setHeader($header)->appendChild($table_wrapper)->setFormErrors($warnings)->setFlush(true); $layout = id(new AphrontMultiColumnView())->addColumn($sidebar, 'third phui-day-view-upcoming')->addColumn($table_box, 'thirds phui-day-view-column')->setFluidLayout(true); $layout = id(new PHUIBoxView())->appendChild($layout)->addClass('phui-calendar-box'); return $layout; }
protected function loadPage() { $events = $this->loadStandardPage($this->newResultObject()); $viewer = $this->getViewer(); foreach ($events as $event) { $event->applyViewerTimezone($viewer); } if (!$this->generateGhosts) { return $events; } $enforced_end = null; $raw_limit = $this->getRawResultLimit(); if (!$raw_limit && !$this->rangeEnd) { throw new Exception(pht('Event queries which generate ghost events must include either a ' . 'result limit or an end date, because they may otherwise generate ' . 'an infinite number of results. This query has neither.')); } foreach ($events as $key => $event) { $sequence_start = 0; $sequence_end = null; $duration = $event->getDateTo() - $event->getDateFrom(); $end = null; $instance_of = $event->getInstanceOfEventPHID(); if ($instance_of == null && $this->isCancelled !== null) { if ($event->getIsCancelled() != $this->isCancelled) { unset($events[$key]); continue; } } if ($event->getIsRecurring() && $instance_of == null) { $frequency = $event->getFrequencyUnit(); $modify_key = '+1 ' . $frequency; if ($this->rangeBegin && $this->rangeBegin > $event->getDateFrom()) { $max_date = $this->rangeBegin - $duration; $date = $event->getDateFrom(); $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); while ($date < $max_date) { // TODO: optimize this to not loop through all off-screen events $sequence_start++; $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); $date = $datetime->modify($modify_key)->format('U'); } $start = $this->rangeBegin; } else { $start = $event->getDateFrom() - $duration; } $date = $start; $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); if ($this->rangeEnd && $event->getRecurrenceEndDate() && $this->rangeEnd < $event->getRecurrenceEndDate()) { $end = $this->rangeEnd; } else { if ($event->getRecurrenceEndDate()) { $end = $event->getRecurrenceEndDate(); } else { if ($this->rangeEnd) { $end = $this->rangeEnd; } else { if ($enforced_end) { if ($end) { $end = min($end, $enforced_end); } else { $end = $enforced_end; } } } } } if ($end) { $sequence_end = $sequence_start; while ($date < $end) { $sequence_end++; $datetime->modify($modify_key); $date = $datetime->format('U'); if ($sequence_end > $raw_limit + $sequence_start) { break; } } } else { $sequence_end = $raw_limit + $sequence_start; } $sequence_start = max(1, $sequence_start); for ($index = $sequence_start; $index < $sequence_end; $index++) { $events[] = $event->generateNthGhost($index, $viewer); } // NOTE: We're slicing results every time because this makes it cheaper // to generate future ghosts. If we already have 100 events that occur // before July 1, we know we never need to generate ghosts after that // because they couldn't possibly ever appear in the result set. if ($raw_limit) { if (count($events) >= $raw_limit) { $events = msort($events, 'getDateFrom'); $events = array_slice($events, 0, $raw_limit, true); $enforced_end = last($events)->getDateFrom(); } } } } $map = array(); $instance_sequence_pairs = array(); foreach ($events as $key => $event) { if ($event->getIsGhostEvent()) { $index = $event->getSequenceIndex(); $instance_sequence_pairs[] = array($event->getPHID(), $index); $map[$event->getPHID()][$index] = $key; } } if (count($instance_sequence_pairs) > 0) { $sub_query = id(new PhabricatorCalendarEventQuery())->setViewer($viewer)->setParentQuery($this)->withInstanceSequencePairs($instance_sequence_pairs)->execute(); foreach ($sub_query as $edited_ghost) { $indexes = idx($map, $edited_ghost->getInstanceOfEventPHID()); $key = idx($indexes, $edited_ghost->getSequenceIndex()); $events[$key] = $edited_ghost; } $id_map = array(); foreach ($events as $key => $event) { if ($event->getIsGhostEvent()) { continue; } if (isset($id_map[$event->getID()])) { unset($events[$key]); } else { $id_map[$event->getID()] = true; } } } return $events; }
private function getEventDateLabel($event) { $viewer = $this->requireViewer(); $from_datetime = PhabricatorTime::getDateTimeFromEpoch($event->getDateFrom(), $viewer); $to_datetime = PhabricatorTime::getDateTimeFromEpoch($event->getDateTo(), $viewer); $from_date_formatted = $from_datetime->format('Y m d'); $to_date_formatted = $to_datetime->format('Y m d'); if ($event->getIsAllDay()) { if ($from_date_formatted == $to_date_formatted) { return pht('%s, All Day', phabricator_date($event->getDateFrom(), $viewer)); } else { return pht('%s - %s, All Day', phabricator_date($event->getDateFrom(), $viewer), phabricator_date($event->getDateTo(), $viewer)); } } else { if ($from_date_formatted == $to_date_formatted) { return pht('%s - %s', phabricator_datetime($event->getDateFrom(), $viewer), phabricator_time($event->getDateTo(), $viewer)); } else { return pht('%s - %s', phabricator_datetime($event->getDateFrom(), $viewer), phabricator_datetime($event->getDateTo(), $viewer)); } } }
protected function loadPage() { $table = new PhabricatorCalendarEvent(); $conn_r = $table->establishConnection('r'); $viewer = $this->getViewer(); $data = queryfx_all($conn_r, 'SELECT event.* FROM %T event %Q %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinClause($conn_r), $this->buildWhereClause($conn_r), $this->buildGroupClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $events = $table->loadAllFromArray($data); foreach ($events as $event) { $event->applyViewerTimezone($this->getViewer()); } if (!$this->generateGhosts) { return $events; } $enforced_end = null; foreach ($events as $key => $event) { $sequence_start = 0; $sequence_end = null; $duration = $event->getDateTo() - $event->getDateFrom(); $end = null; $instance_of = $event->getInstanceOfEventPHID(); if ($instance_of == null && $this->isCancelled !== null) { if ($event->getIsCancelled() != $this->isCancelled) { unset($events[$key]); continue; } } if ($event->getIsRecurring() && $instance_of == null) { $frequency = $event->getFrequencyUnit(); $modify_key = '+1 ' . $frequency; if ($this->rangeBegin && $this->rangeBegin > $event->getDateFrom()) { $max_date = $this->rangeBegin - $duration; $date = $event->getDateFrom(); $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); while ($date < $max_date) { // TODO: optimize this to not loop through all off-screen events $sequence_start++; $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); $date = $datetime->modify($modify_key)->format('U'); } $start = $this->rangeBegin; } else { $start = $event->getDateFrom() - $duration; } $date = $start; $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); if ($this->rangeEnd && $event->getRecurrenceEndDate() && $this->rangeEnd < $event->getRecurrenceEndDate()) { $end = $this->rangeEnd; } else { if ($event->getRecurrenceEndDate()) { $end = $event->getRecurrenceEndDate(); } else { if ($this->rangeEnd) { $end = $this->rangeEnd; } else { if ($enforced_end) { if ($end) { $end = min($end, $enforced_end); } else { $end = $enforced_end; } } } } } if ($end) { $sequence_end = $sequence_start; while ($date < $end) { $sequence_end++; $datetime->modify($modify_key); $date = $datetime->format('U'); if ($sequence_end > $this->getRawResultLimit() + $sequence_start) { break; } } } else { $sequence_end = $this->getRawResultLimit() + $sequence_start; } $sequence_start = max(1, $sequence_start); for ($index = $sequence_start; $index < $sequence_end; $index++) { $events[] = $event->generateNthGhost($index, $viewer); } if (count($events) >= $this->getRawResultLimit()) { $events = msort($events, 'getDateFrom'); $events = array_slice($events, 0, $this->getRawResultLimit(), true); $enforced_end = last($events)->getDateFrom(); } } } $map = array(); $instance_sequence_pairs = array(); foreach ($events as $key => $event) { if ($event->getIsGhostEvent()) { $index = $event->getSequenceIndex(); $instance_sequence_pairs[] = array($event->getPHID(), $index); $map[$event->getPHID()][$index] = $key; } } if (count($instance_sequence_pairs) > 0) { $sub_query = id(new PhabricatorCalendarEventQuery())->setViewer($viewer)->setParentQuery($this)->withInstanceSequencePairs($instance_sequence_pairs)->execute(); foreach ($sub_query as $edited_ghost) { $indexes = idx($map, $edited_ghost->getInstanceOfEventPHID()); $key = idx($indexes, $edited_ghost->getSequenceIndex()); $events[$key] = $edited_ghost; } $id_map = array(); foreach ($events as $key => $event) { if ($event->getIsGhostEvent()) { continue; } if (isset($id_map[$event->getID()])) { unset($events[$key]); } else { $id_map[$event->getID()] = true; } } } return $events; }
public function generateNthGhost($sequence_index, PhabricatorUser $actor) { $frequency = $this->getFrequencyUnit(); $modify_key = '+' . $sequence_index . ' ' . $frequency; $instance_of = $this->getPHID() ? $this->getPHID() : $this->instanceOfEventPHID; $date = $this->dateFrom; $date_time = PhabricatorTime::getDateTimeFromEpoch($date, $actor); $date_time->modify($modify_key); $date = $date_time->format('U'); $duration = $this->dateTo - $this->dateFrom; $edit_policy = PhabricatorPolicies::POLICY_NOONE; $ghost_event = id(clone $this)->setIsGhostEvent(true)->setDateFrom($date)->setDateTo($date + $duration)->setIsRecurring(true)->setRecurrenceFrequency($this->recurrenceFrequency)->setInstanceOfEventPHID($instance_of)->setSequenceIndex($sequence_index)->setEditPolicy($edit_policy); return $ghost_event; }
public function renderEventDate(PhabricatorUser $viewer, $show_end) { if ($show_end) { $min_date = PhabricatorTime::getDateTimeFromEpoch($this->getViewerDateFrom(), $viewer); $max_date = PhabricatorTime::getDateTimeFromEpoch($this->getViewerDateTo(), $viewer); $min_day = $min_date->format('Y m d'); $max_day = $max_date->format('Y m d'); $show_end_date = $min_day != $max_day; } else { $show_end_date = false; } $min_epoch = $this->getViewerDateFrom(); $max_epoch = $this->getViewerDateTo(); if ($this->getIsAllDay()) { if ($show_end_date) { return pht('%s - %s, All Day', phabricator_date($min_epoch, $viewer), phabricator_date($max_epoch, $viewer)); } else { return pht('%s, All Day', phabricator_date($min_epoch, $viewer)); } } else { if ($show_end_date) { return pht('%s - %s', phabricator_datetime($min_epoch, $viewer), phabricator_datetime($max_epoch, $viewer)); } else { if ($show_end) { return pht('%s - %s', phabricator_datetime($min_epoch, $viewer), phabricator_time($max_epoch, $viewer)); } else { return pht('%s', phabricator_datetime($min_epoch, $viewer)); } } } }
protected function loadPage() { $events = $this->loadStandardPage($this->newResultObject()); $viewer = $this->getViewer(); foreach ($events as $event) { $event->applyViewerTimezone($viewer); } if (!$this->generateGhosts) { return $events; } $raw_limit = $this->getRawResultLimit(); if (!$raw_limit && !$this->rangeEnd) { throw new Exception(pht('Event queries which generate ghost events must include either a ' . 'result limit or an end date, because they may otherwise generate ' . 'an infinite number of results. This query has neither.')); } foreach ($events as $key => $event) { $sequence_start = 0; $sequence_end = null; $end = null; $instance_of = $event->getInstanceOfEventPHID(); if ($instance_of == null && $this->isCancelled !== null) { if ($event->getIsCancelled() != $this->isCancelled) { unset($events[$key]); continue; } } } // Pull out all of the parents first. We may discard them as we begin // generating ghost events, but we still want to process all of them. $parents = array(); foreach ($events as $key => $event) { if ($event->isParentEvent()) { $parents[$key] = $event; } } // Now that we've picked out all the parent events, we can immediately // discard anything outside of the time window. $events = $this->getEventsInRange($events); $enforced_end = null; foreach ($parents as $key => $event) { $sequence_start = 0; $sequence_end = null; $start = null; $duration = $event->getDuration(); $frequency = $event->getFrequencyUnit(); $modify_key = '+1 ' . $frequency; if ($this->rangeBegin !== null && $this->rangeBegin > $event->getViewerDateFrom()) { $max_date = $this->rangeBegin - $duration; $date = $event->getViewerDateFrom(); $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); while ($date < $max_date) { // TODO: optimize this to not loop through all off-screen events $sequence_start++; $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); $date = $datetime->modify($modify_key)->format('U'); } $start = $this->rangeBegin; } else { $start = $event->getViewerDateFrom() - $duration; } $date = $start; $datetime = PhabricatorTime::getDateTimeFromEpoch($date, $viewer); // Select the minimum end time we need to generate events until. $end_times = array(); if ($this->rangeEnd) { $end_times[] = $this->rangeEnd; } if ($event->getRecurrenceEndDate()) { $end_times[] = $event->getRecurrenceEndDate(); } if ($enforced_end) { $end_times[] = $enforced_end; } if ($end_times) { $end = min($end_times); $sequence_end = $sequence_start; while ($date < $end) { $sequence_end++; $datetime->modify($modify_key); $date = $datetime->format('U'); if ($sequence_end > $raw_limit + $sequence_start) { break; } } } else { $sequence_end = $raw_limit + $sequence_start; } $sequence_start = max(1, $sequence_start); for ($index = $sequence_start; $index < $sequence_end; $index++) { $events[] = $event->newGhost($viewer, $index); } // NOTE: We're slicing results every time because this makes it cheaper // to generate future ghosts. If we already have 100 events that occur // before July 1, we know we never need to generate ghosts after that // because they couldn't possibly ever appear in the result set. if ($raw_limit) { if (count($events) > $raw_limit) { $events = msort($events, 'getViewerDateFrom'); $events = array_slice($events, 0, $raw_limit, true); $enforced_end = last($events)->getViewerDateFrom(); } } } // Now that we're done generating ghost events, we're going to remove any // ghosts that we have concrete events for (or which we can load the // concrete events for). These concrete events are generated when users // edit a ghost, and replace the ghost events. // First, generate a map of all concrete <parentPHID, sequence> events we // already loaded. We don't need to load these again. $have_pairs = array(); foreach ($events as $event) { if ($event->getIsGhostEvent()) { continue; } $parent_phid = $event->getInstanceOfEventPHID(); $sequence = $event->getSequenceIndex(); $have_pairs[$parent_phid][$sequence] = true; } // Now, generate a map of all <parentPHID, sequence> events we generated // ghosts for. We need to try to load these if we don't already have them. $map = array(); $parent_pairs = array(); foreach ($events as $key => $event) { if (!$event->getIsGhostEvent()) { continue; } $parent_phid = $event->getInstanceOfEventPHID(); $sequence = $event->getSequenceIndex(); // We already loaded the concrete version of this event, so we can just // throw out the ghost and move on. if (isset($have_pairs[$parent_phid][$sequence])) { unset($events[$key]); continue; } // We didn't load the concrete version of this event, so we need to // try to load it if it exists. $parent_pairs[] = array($parent_phid, $sequence); $map[$parent_phid][$sequence] = $key; } if ($parent_pairs) { $instances = id(new self())->setViewer($viewer)->setParentQuery($this)->withInstanceSequencePairs($parent_pairs)->execute(); foreach ($instances as $instance) { $parent_phid = $instance->getInstanceOfEventPHID(); $sequence = $instance->getSequenceIndex(); $indexes = idx($map, $parent_phid); $key = idx($indexes, $sequence); // Replace the ghost with the corresponding concrete event. $events[$key] = $instance; } } $events = msort($events, 'getViewerDateFrom'); return $events; }