Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / src / view / phui / calendar / PHUICalendarDayView.php
blob11ebc200e75682bb24a59262bdfab9bada4b2255
1 <?php
3 final class PHUICalendarDayView extends AphrontView {
4 private $rangeStart;
5 private $rangeEnd;
7 private $day;
8 private $month;
9 private $year;
10 private $browseURI;
11 private $query;
12 private $events = array();
14 private $allDayEvents = array();
16 public function addEvent(AphrontCalendarEventView $event) {
17 $this->events[] = $event;
18 return $this;
21 public function setBrowseURI($browse_uri) {
22 $this->browseURI = $browse_uri;
23 return $this;
25 private function getBrowseURI() {
26 return $this->browseURI;
29 public function setQuery($query) {
30 $this->query = $query;
31 return $this;
33 private function getQuery() {
34 return $this->query;
37 public function __construct(
38 $range_start,
39 $range_end,
40 $year,
41 $month,
42 $day = null) {
44 $this->rangeStart = $range_start;
45 $this->rangeEnd = $range_end;
47 $this->day = $day;
48 $this->month = $month;
49 $this->year = $year;
52 public function render() {
53 require_celerity_resource('phui-calendar-day-css');
55 $viewer = $this->getUser();
57 $hours = $this->getHoursOfDay();
58 $js_hours = array();
59 $js_today_events = array();
61 foreach ($hours as $hour) {
62 $js_hours[] = array(
63 'hour' => $hour->format('G'),
64 'displayTime' => phabricator_time($hour->format('U'), $viewer),
68 $first_event_hour = null;
69 $js_today_all_day_events = array();
70 $all_day_events = $this->getAllDayEvents();
72 $day_start = $this->getDateTime();
73 $day_end = id(clone $day_start)->modify('+1 day');
75 $day_start_epoch = $day_start->format('U');
76 $day_end_epoch = $day_end->format('U') - 1;
78 foreach ($all_day_events as $all_day_event) {
79 $all_day_start = $all_day_event->getEpochStart();
80 $all_day_end = $all_day_event->getEpochEnd();
82 if ($all_day_start < $day_end_epoch && $all_day_end > $day_start_epoch) {
83 $js_today_all_day_events[] = array(
84 'name' => $all_day_event->getName(),
85 'id' => $all_day_event->getEventID(),
86 'viewerIsInvited' => $all_day_event->getViewerIsInvited(),
87 'uri' => $all_day_event->getURI(),
88 'displayIcon' => $all_day_event->getIcon(),
89 'displayIconColor' => $all_day_event->getIconColor(),
94 $this->events = msort($this->events, 'getEpochStart');
95 $first_event_hour = $this->getDateTime()->setTime(8, 0, 0);
96 $midnight = $this->getDateTime()->setTime(0, 0, 0);
98 foreach ($this->events as $event) {
99 if ($event->getIsAllDay()) {
100 continue;
102 if ($event->getEpochStart() <= $day_end_epoch &&
103 $event->getEpochEnd() > $day_start_epoch) {
105 if ($event->getEpochStart() < $midnight->format('U') &&
106 $event->getEpochEnd() > $midnight->format('U')) {
107 $first_event_hour = clone $midnight;
110 if ($event->getEpochStart() < $first_event_hour->format('U') &&
111 $event->getEpochStart() > $midnight->format('U')) {
112 $first_event_hour = PhabricatorTime::getDateTimeFromEpoch(
113 $event->getEpochStart(),
114 $viewer);
115 $first_event_hour->setTime($first_event_hour->format('h'), 0, 0);
118 $event_start = max($event->getEpochStart(), $day_start_epoch);
119 $event_end = min($event->getEpochEnd(), $day_end_epoch);
121 $day_duration = ($day_end_epoch - $first_event_hour->format('U')) / 60;
123 $top = (($event_start - $first_event_hour->format('U'))
124 / ($day_end_epoch - $first_event_hour->format('U')))
125 * $day_duration;
126 $top = max(0, $top);
128 $height = (($event_end - $event_start)
129 / ($day_end_epoch - $first_event_hour->format('U')))
130 * $day_duration;
131 $height = min($day_duration, $height);
133 $js_today_events[] = array(
134 'eventStartEpoch' => $event->getEpochStart(),
135 'eventEndEpoch' => $event->getEpochEnd(),
136 'eventName' => $event->getName(),
137 'eventID' => $event->getEventID(),
138 'viewerIsInvited' => $event->getViewerIsInvited(),
139 'uri' => $event->getURI(),
140 'offset' => '0',
141 'width' => '100%',
142 'top' => $top.'px',
143 'height' => $height.'px',
144 'canEdit' => $event->getCanEdit(),
145 'displayIcon' => $event->getIcon(),
146 'displayIconColor' => $event->getIconColor(),
151 $header = $this->renderDayViewHeader();
152 $sidebar = id(new PHUICalendarWeekView())
153 ->setViewer($this->getViewer())
154 ->setEvents($this->events)
155 ->setDateTime($this->getDateTime())
156 ->render();
157 $warnings = $this->getQueryRangeWarning();
159 $table_id = celerity_generate_unique_node_id();
161 $table_wrapper = phutil_tag(
162 'div',
163 array(
164 'id' => $table_id,
166 '');
168 Javelin::initBehavior(
169 'day-view',
170 array(
171 'year' => $first_event_hour->format('Y'),
172 'month' => $first_event_hour->format('m'),
173 'day' => $first_event_hour->format('d'),
174 'query' => $this->getQuery(),
175 'allDayEvents' => $js_today_all_day_events,
176 'todayEvents' => $js_today_events,
177 'hours' => $js_hours,
178 'firstEventHour' => $first_event_hour->format('G'),
179 'firstEventHourEpoch' => $first_event_hour->format('U'),
180 'tableID' => $table_id,
183 $table_box = id(new PHUIObjectBoxView())
184 ->setHeader($header)
185 ->appendChild($table_wrapper)
186 ->setFormErrors($warnings)
187 ->setFlush(true);
189 $layout = id(new AphrontMultiColumnView())
190 ->addColumn($sidebar, 'third phui-day-view-upcoming')
191 ->addColumn($table_box, 'thirds phui-day-view-column')
192 ->setFluidLayout(true);
194 $layout = id(new PHUIBoxView())
195 ->appendChild($layout)
196 ->addClass('phui-calendar-box');
198 return $layout;
201 private function getAllDayEvents() {
202 $all_day_events = array();
204 foreach ($this->events as $event) {
205 if ($event->getIsAllDay()) {
206 $all_day_events[] = $event;
210 $all_day_events = array_values(msort($all_day_events, 'getEpochStart'));
211 return $all_day_events;
214 private function getQueryRangeWarning() {
215 $errors = array();
217 $range_start_epoch = null;
218 $range_end_epoch = null;
220 if ($this->rangeStart) {
221 $range_start_epoch = $this->rangeStart->getEpoch();
223 if ($this->rangeEnd) {
224 $range_end_epoch = $this->rangeEnd->getEpoch();
227 $day_start = $this->getDateTime();
228 $day_end = id(clone $day_start)->modify('+1 day');
230 $day_start = $day_start->format('U');
231 $day_end = $day_end->format('U') - 1;
233 if (($range_start_epoch != null &&
234 $range_start_epoch < $day_end &&
235 $range_start_epoch > $day_start) ||
236 ($range_end_epoch != null &&
237 $range_end_epoch < $day_end &&
238 $range_end_epoch > $day_start)) {
239 $errors[] = pht('Part of the day is out of range');
242 if (($range_end_epoch != null &&
243 $range_end_epoch < $day_start) ||
244 ($range_start_epoch != null &&
245 $range_start_epoch > $day_end)) {
246 $errors[] = pht('Day is out of query range');
248 return $errors;
251 private function renderDayViewHeader() {
252 $button_bar = null;
253 $uri = $this->getBrowseURI();
254 if ($uri) {
255 list($prev_year, $prev_month, $prev_day) = $this->getPrevDay();
256 $prev_uri = $uri.$prev_year.'/'.$prev_month.'/'.$prev_day.'/';
258 list($next_year, $next_month, $next_day) = $this->getNextDay();
259 $next_uri = $uri.$next_year.'/'.$next_month.'/'.$next_day.'/';
261 $button_bar = new PHUIButtonBarView();
263 $left_icon = id(new PHUIIconView())
264 ->setIcon('fa-chevron-left bluegrey');
265 $left = id(new PHUIButtonView())
266 ->setTag('a')
267 ->setColor(PHUIButtonView::GREY)
268 ->setHref($prev_uri)
269 ->setTitle(pht('Previous Day'))
270 ->setIcon($left_icon);
272 $right_icon = id(new PHUIIconView())
273 ->setIcon('fa-chevron-right bluegrey');
274 $right = id(new PHUIButtonView())
275 ->setTag('a')
276 ->setColor(PHUIButtonView::GREY)
277 ->setHref($next_uri)
278 ->setTitle(pht('Next Day'))
279 ->setIcon($right_icon);
281 $button_bar->addButton($left);
282 $button_bar->addButton($right);
286 $display_day = $this->getDateTime();
287 $header_text = $display_day->format('l, F j, Y');
289 $header = id(new PHUIHeaderView())
290 ->setHeader($header_text);
292 if ($button_bar) {
293 $header->setButtonBar($button_bar);
296 return $header;
299 private function updateEventsFromCluster($cluster, $hourly_events) {
300 $cluster_size = count($cluster);
301 $n = 0;
302 foreach ($cluster as $cluster_member) {
303 $event_id = $cluster_member->getEventID();
304 $offset = (($n / $cluster_size) * 100).'%';
305 $width = ((1 / $cluster_size) * 100).'%';
307 if (isset($hourly_events[$event_id])) {
308 $hourly_events[$event_id]['offset'] = $offset;
309 $hourly_events[$event_id]['width'] = $width;
311 $n++;
314 return $hourly_events;
317 // returns DateTime of each hour in the day
318 private function getHoursOfDay() {
319 $included_datetimes = array();
321 $day_datetime = $this->getDateTime();
322 $day_epoch = $day_datetime->format('U');
324 $day_datetime->modify('+1 day');
325 $next_day_epoch = $day_datetime->format('U');
327 $included_time = $day_epoch;
328 $included_datetime = $this->getDateTime();
330 while ($included_time < $next_day_epoch) {
331 $included_datetimes[] = clone $included_datetime;
333 $included_datetime->modify('+1 hour');
334 $included_time = $included_datetime->format('U');
337 return $included_datetimes;
340 private function getPrevDay() {
341 $prev = $this->getDateTime();
342 $prev->modify('-1 day');
343 return array(
344 $prev->format('Y'),
345 $prev->format('m'),
346 $prev->format('d'),
350 private function getNextDay() {
351 $next = $this->getDateTime();
352 $next->modify('+1 day');
353 return array(
354 $next->format('Y'),
355 $next->format('m'),
356 $next->format('d'),
360 private function getDateTime() {
361 $user = $this->getViewer();
362 $timezone = new DateTimeZone($user->getTimezoneIdentifier());
364 $day = $this->day;
365 $month = $this->month;
366 $year = $this->year;
368 $date = new DateTime("{$year}-{$month}-{$day} ", $timezone);
370 return $date;