Overview

Packages

  • Kronolith
  • None

Classes

  • Kronolith
  • Kronolith_Ajax_Application
  • Kronolith_Ajax_Imple_ContactAutoCompleter
  • Kronolith_Ajax_Imple_Embed
  • Kronolith_Ajax_Imple_TagActions
  • Kronolith_Ajax_Imple_TagAutoCompleter
  • Kronolith_Api
  • Kronolith_Calendar
  • Kronolith_Calendar_External
  • Kronolith_Calendar_External_Tasks
  • Kronolith_Calendar_Holiday
  • Kronolith_Calendar_Internal
  • Kronolith_Calendar_Remote
  • Kronolith_Calendar_Resource
  • Kronolith_Calendars_Base
  • Kronolith_Calendars_Default
  • Kronolith_Calendars_Kolab
  • Kronolith_Day
  • Kronolith_Driver
  • Kronolith_Driver_Holidays
  • Kronolith_Driver_Horde
  • Kronolith_Driver_Ical
  • Kronolith_Driver_Kolab
  • Kronolith_Driver_Mock
  • Kronolith_Driver_Resource
  • Kronolith_Driver_Sql
  • Kronolith_Event
  • Kronolith_Event_Holidays
  • Kronolith_Event_Horde
  • Kronolith_Event_Ical
  • Kronolith_Event_Kolab
  • Kronolith_Event_Resource
  • Kronolith_Event_Sql
  • Kronolith_Exception
  • Kronolith_Factory_Calendars
  • Kronolith_Factory_Geo
  • Kronolith_Form_CreateCalendar
  • Kronolith_Form_CreateResource
  • Kronolith_Form_CreateResourceGroup
  • Kronolith_Form_DeleteCalendar
  • Kronolith_Form_DeleteResource
  • Kronolith_Form_DeleteResourceGroup
  • Kronolith_Form_EditCalendar
  • Kronolith_Form_EditRemoteCalendar
  • Kronolith_Form_EditResource
  • Kronolith_Form_EditResourceGroup
  • Kronolith_Form_SubscribeRemoteCalendar
  • Kronolith_Form_UnsubscribeRemoteCalendar
  • Kronolith_FreeBusy
  • Kronolith_FreeBusy_View
  • Kronolith_FreeBusy_View_Day
  • Kronolith_FreeBusy_View_Month
  • Kronolith_FreeBusy_View_Week
  • Kronolith_FreeBusy_View_Workweek
  • Kronolith_Geo_Base
  • Kronolith_Geo_Mysql
  • Kronolith_Geo_Sql
  • Kronolith_LoginTasks_SystemTask_Upgrade
  • Kronolith_LoginTasks_Task_PurgeEvents
  • Kronolith_Notification_Listener_AjaxStatus
  • Kronolith_Resource
  • Kronolith_Resource_Base
  • Kronolith_Resource_Group
  • Kronolith_Resource_Single
  • Kronolith_Storage
  • Kronolith_Storage_Kolab
  • Kronolith_Storage_Sql
  • Kronolith_Tagger
  • Kronolith_Test
  • Kronolith_View_Day
  • Kronolith_View_DeleteEvent
  • Kronolith_View_EditEvent
  • Kronolith_View_Event
  • Kronolith_View_ExportEvent
  • Kronolith_View_Month
  • Kronolith_View_Week
  • Kronolith_View_WorkWeek
  • Kronolith_View_Year
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * The Kronolith_View_Day:: class provides an API for viewing days.
  4:  *
  5:  * @author  Chuck Hagenbuch <chuck@horde.org>
  6:  * @author  Jan Schneider <jan@horde.org>
  7:  * @package Kronolith
  8:  */
  9: class Kronolith_View_Day extends Kronolith_Day
 10: {
 11:     public $all_day_events = array();
 12:     public $all_day_rowspan = array();
 13:     public $all_day_maxrowspan = 0;
 14:     public $span = array();
 15:     public $totalspan = 0;
 16:     public $sidebyside = false;
 17:     public $events = array();
 18:     protected $_event_matrix = array();
 19:     protected $_parsed = false;
 20:     protected $_currentCalendars = array();
 21:     protected $_first;
 22:     protected $_last;
 23: 
 24:     /**
 25:      *
 26:      * @param Horde_Date $date  The day for this view
 27:      * @param array $events     An array of Kronolith_Event objects
 28:      *
 29:      * @return Kronolith_View_Day
 30:      */
 31:     public function __construct(Horde_Date $date, array $events = null)
 32:     {
 33:         parent::__construct($date->month, $date->mday, $date->year);
 34: 
 35:         $this->sidebyside = $GLOBALS['prefs']->getValue('show_shared_side_by_side');
 36:         if ($this->sidebyside) {
 37:             $allCalendars = Kronolith::listInternalCalendars();
 38:             foreach ($GLOBALS['display_calendars'] as $cid) {
 39:                  $this->_currentCalendars[$cid] = $allCalendars[$cid];
 40:                  $this->all_day_events[$cid] = array();
 41:             }
 42:         } else {
 43:             $this->_currentCalendars = array(0);
 44:         }
 45: 
 46:         if ($events === null) {
 47:             try {
 48:                 $events = Kronolith::listEvents(
 49:                     $this,
 50:                     new Horde_Date(array('year' => $this->year,
 51:                                          'month' => $this->month,
 52:                                          'mday' => $this->mday)));
 53:                 $this->events = array_shift($events);
 54:             } catch (Exception $e) {
 55:                 $GLOBALS['notification']->push($e, 'horde.error');
 56:                 $this->events = array();
 57:             }
 58:         } else {
 59:             $this->events = $events;
 60:         }
 61: 
 62:         if (!is_array($this->events)) {
 63:             $this->events = array();
 64:         }
 65:     }
 66: 
 67:     public function html()
 68:     {
 69:         global $prefs;
 70: 
 71:         if (!$this->_parsed) {
 72:             $this->parse();
 73:         }
 74: 
 75:         $started = false;
 76:         $first_row = true;
 77:         $addLinks = Kronolith::getDefaultCalendar(Horde_Perms::EDIT) &&
 78:             ($GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('max_events') === true ||
 79:              $GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission('max_events') > Kronolith::countEvents());
 80:         $showLocation = Kronolith::viewShowLocation();
 81:         $showTime = Kronolith::viewShowTime();
 82: 
 83:         require KRONOLITH_TEMPLATES . '/day/head.inc';
 84:         if ($this->sidebyside) {
 85:             require KRONOLITH_TEMPLATES . '/day/head_side_by_side.inc';
 86:         }
 87:         echo '<tbody>';
 88: 
 89:         if ($addLinks) {
 90:             $newEventUrl = Horde::url('new.php')
 91:                 ->add(array('datetime' => sprintf($this->dateString() . '%02d%02d00',
 92:                                                   $this->slots[0]['hour'], $this->slots[0]['min']),
 93:                             'allday' => 1,
 94:                             'url' => $this->link(0, true)))
 95:                 ->link(array('title' => _("Create a New Event"), 'class' =>
 96:             'hour'))
 97:                 . _("All day")
 98:                 . Horde::img('new_small.png', '+', array('class' =>
 99:             'iconAdd'))
100:                 . '</a>';
101:         } else {
102:             $newEventUrl = '<span class="hour">' . _("All day") . '</span>';
103:         }
104: 
105:         /* The all day events are not listed in different columns, but in
106:          * different rows.  In side by side view we do not spread an event
107:          * over multiple rows if there are different numbers of all day events
108:          * for different calendars.  We just put one event in a single row
109:          * with no rowspan.  We put in a rowspan in the row after the last
110:          * event to fill all remaining rows. */
111:         $row = '';
112:         $rowspan = ($this->all_day_maxrowspan) ? ' rowspan="' . $this->all_day_maxrowspan . '"' : '';
113:         for ($k = 0; $k < $this->all_day_maxrowspan; ++$k) {
114:             $row = '';
115:             foreach (array_keys($this->_currentCalendars) as $cid) {
116:                 if (count($this->all_day_events[$cid]) === $k) {
117:                     // There are no events or all events for this calendar
118:                     // have already been printed.
119:                     $row .= '<td class="allday" width="1%" rowspan="' . ($this->all_day_maxrowspan - $k) . '" colspan="'.  $this->span[$cid] . '">&nbsp;</td>';
120:                 } elseif (count($this->all_day_events[$cid]) > $k) {
121:                     // We have not printed every all day event yet. Put one
122:                     // into this row.
123:                     $event = $this->all_day_events[$cid][$k];
124:                     $row .= '<td class="day-eventbox"'
125:                         . $event->getCSSColors()
126:                         . 'width="' . round(90 / count($this->_currentCalendars))  . '%" '
127:                         . 'valign="top" colspan="' . $this->span[$cid] . '">'
128:                         . $event->getLink($this, true, $this->link(0, true));
129:                     if (!$event->isPrivate() && $showLocation) {
130:                         $row .= '<div class="event-location">' . htmlspecialchars($event->getLocation()) . '</div>';
131:                     }
132:                     $row .= '</td>';
133:                 }
134:             }
135:             require KRONOLITH_TEMPLATES . '/day/all_day.inc';
136:             $first_row = false;
137:         }
138: 
139:         if ($first_row) {
140:             $row .= '<td colspan="' . $this->totalspan . '" width="100%">&nbsp;</td>';
141:             require KRONOLITH_TEMPLATES . '/day/all_day.inc';
142:         }
143: 
144:         $day_hour_force = $prefs->getValue('day_hour_force');
145:         $day_hour_start = $prefs->getValue('day_hour_start') / 2 * $this->slotsPerHour;
146:         $day_hour_end = $prefs->getValue('day_hour_end') / 2 * $this->slotsPerHour;
147:         $rows = array();
148:         $covered = array();
149: 
150:         for ($i = 0; $i < $this->slotsPerDay; ++$i) {
151:             if ($i >= $day_hour_end && $i > $this->_last) {
152:                 break;
153:             }
154:             if ($i < $this->_first && $i < $day_hour_start) {
155:                 continue;
156:             }
157: 
158:             $row = '';
159:             if (($m = $i % $this->slotsPerHour) != 0) {
160:                 $time = ':' . $m * $this->slotLength;
161:                 $hourclass = 'halfhour';
162:             } else {
163:                 $time = Kronolith_View_Day::prefHourFormat($this->slots[$i]['hour']);
164:                 $hourclass = 'hour';
165:             }
166: 
167:             if (!count($this->_currentCalendars)) {
168:                 $row .= '<td>&nbsp;</td>';
169:             }
170: 
171:             foreach (array_keys($this->_currentCalendars) as $cid) {
172:                 $hspan = 0;
173:                 foreach ($this->_event_matrix[$cid][$i] as $key) {
174:                     $event = &$this->events[$key];
175: 
176:                     // Since we've made sure that this event's overlap is a
177:                     // factor of the total span, we get this event's
178:                     // individual span by dividing the total span by this
179:                     // event's overlap.
180:                     $span = $this->span[$cid] / $event->overlap;
181: 
182:                     // Store the indent we're starting this event at
183:                     // for future use.
184:                     if (!isset($event->indent)) {
185:                         $event->indent = $hspan;
186:                     }
187: 
188:                     // If the first node that we would cover is
189:                     // already covered, we can assume that table
190:                     // rendering will take care of pushing the event
191:                     // over. However, if the first node _isn't_
192:                     // covered but any others that we would cover
193:                     // _are_, we only cover the available nodes.
194:                     if (!isset($covered[$i][$event->indent])) {
195:                         $collision = false;
196:                         $available = 0;
197:                         for ($y = $event->indent; $y < ($span + $event->indent); ++$y) {
198:                             if (isset($covered[$i][$y])) {
199:                                 $collision = true;
200:                                 break;
201:                             }
202:                             $available++;
203:                         }
204: 
205:                         if ($collision) {
206:                             $span = $available;
207:                         }
208:                     }
209: 
210:                     $hspan += $span;
211: 
212:                     $start = new Horde_Date(array(
213:                         'hour'  => floor($i / $this->slotsPerHour),
214:                         'min'   => ($i % $this->slotsPerHour) * $this->slotLength,
215:                         'month' => $this->month,
216:                         'mday'  => $this->mday,
217:                         'year'  => $this->year));
218:                     $end_slot = new Horde_Date($start);
219:                     $end_slot->min += $this->slotLength;
220:                     if (((!$day_hour_force || $i >= $day_hour_start) &&
221:                          $event->start->compareDateTime($start) >= 0 &&
222:                          $event->start->compareDateTime($end_slot) < 0 ||
223:                          $start->compareDateTime($this) == 0) ||
224:                         ($day_hour_force &&
225:                          $i == $day_hour_start &&
226:                          $event->start->compareDateTime($start) < 0)) {
227: 
228:                         // Store the nodes that we're covering for
229:                         // this event in the coverage graph.
230:                         for ($x = $i; $x < ($i + $event->rowspan); ++$x) {
231:                             for ($y = $event->indent; $y < $hspan; ++$y) {
232:                                 $covered[$x][$y] = true;
233:                             }
234:                         }
235: 
236:                         $row .= '<td class="day-eventbox"'
237:                             . $event->getCSSColors()
238:                             . 'width="' . round((90 / count($this->_currentCalendars)) * ($span / $this->span[$cid]))  . '%" '
239:                             . 'valign="top" colspan="' . $span . '" rowspan="' . $event->rowspan . '">'
240:                             . $event->getLink($this, true, $this->link(0, true));
241:                         if ($showTime) {
242:                             $row .= '<div class="event-time">' . htmlspecialchars($event->getTimeRange()) . '</div>';
243:                         }
244:                         if (!$event->isPrivate() && $showLocation) {
245:                             $row .= '<div class="event-location">' . htmlspecialchars($event->getLocation()) . '</div>';
246:                         }
247:                         $row .= '</td>';
248:                     }
249:                 }
250: 
251:                 $diff = $this->span[$cid] - $hspan;
252:                 if ($diff > 0) {
253:                     $row .= str_repeat('<td>&nbsp;</td>', $diff);
254:                 }
255:             }
256: 
257:             if ($addLinks) {
258:                 $newEventUrl = Horde::url('new.php')
259:                     ->add(array('datetime' => sprintf($this->dateString() . '%02d%02d00',
260:                                                       $this->slots[$i]['hour'], $this->slots[$i]['min']),
261:                                 'url' => $this->link(0, true)))
262:                     ->link(array('title' =>_("Create a New Event"), 'class' => $hourclass))
263:                     . $time
264:                     . Horde::img('new_small.png', '+', array('class' => 'iconAdd'))
265:                     . '</a>';
266:             } else {
267:                 $newEventUrl = '<span class="' . $hourclass . '">' . $time . '</span>';
268:             }
269: 
270:             $rows[] = array('row' => $row, 'slot' => $newEventUrl);
271:         }
272: 
273:         $template = $GLOBALS['injector']->createInstance('Horde_Template');
274:         $template->set('row_height', round(20 / $this->slotsPerHour));
275:         $template->set('rows', $rows);
276:         $template->set('show_slots', true, true);
277:         echo $template->fetch(KRONOLITH_TEMPLATES . '/day/rows.html')
278:             . '</tbody></table>';
279:     }
280: 
281:     /**
282:      * This function runs through the events and tries to figure out
283:      * what should be on each line of the output table. This is a
284:      * little tricky.
285:      */
286:     public function parse()
287:     {
288:         global $prefs;
289: 
290:         $tmp = array();
291:         $this->all_day_maxrowspan = 0;
292:         $day_hour_force = $prefs->getValue('day_hour_force');
293:         $day_hour_start = $prefs->getValue('day_hour_start') / 2 * $this->slotsPerHour;
294:         $day_hour_end = $prefs->getValue('day_hour_end') / 2 * $this->slotsPerHour;
295: 
296:         // Separate out all day events and do some initialization/prep
297:         // for parsing.
298:         foreach (array_keys($this->_currentCalendars) as $cid) {
299:             $this->all_day_events[$cid] = array();
300:             $this->all_day_rowspan[$cid] = 0;
301:         }
302: 
303:         foreach ($this->events as $key => $event) {
304:             // If we have side_by_side we only want to include the
305:             // event in the proper calendar.
306:             if ($this->sidebyside) {
307:                 $cid = $event->calendar;
308:             } else {
309:                 $cid = 0;
310:             }
311: 
312:             // All day events are easy; store them seperately.
313:             if ($event->isAllDay()) {
314:                 $this->all_day_events[$cid][] = clone $event;
315:                 ++$this->all_day_rowspan[$cid];
316:                 $this->all_day_maxrowspan = max($this->all_day_maxrowspan, $this->all_day_rowspan[$cid]);
317:             } else {
318:                 // Initialize the number of events that this event
319:                 // overlaps with.
320:                 $event->overlap = 0;
321: 
322:                 // Initialize this event's vertical span.
323:                 $event->rowspan = 0;
324: 
325:                 $tmp[] = clone $event;
326:             }
327:         }
328:         $this->events = $tmp;
329: 
330:         // Initialize the set of different rowspans needed.
331:         $spans = array(1 => true);
332: 
333:         // Track the first and last slots in which we have an event
334:         // (they each start at the other end of the day and move
335:         // towards/past each other as we find events).
336:         $this->_first = $this->slotsPerDay;
337:         $this->_last = 0;
338: 
339:         // Run through every slot, adding in entries for every event
340:         // that we have here.
341:         for ($i = 0; $i < $this->slotsPerDay; ++$i) {
342:             // Initialize this slot in the event matrix.
343:             foreach (array_keys($this->_currentCalendars) as $cid) {
344:                 $this->_event_matrix[$cid][$i] = array();
345:             }
346: 
347:             // Calculate the start and end times for this slot.
348:             $start = new Horde_Date(array(
349:                 'hour'  => floor($i / $this->slotsPerHour),
350:                 'min'   => ($i % $this->slotsPerHour) * $this->slotLength,
351:                 'month' => $this->month,
352:                 'mday'  => $this->mday,
353:                 'year'  => $this->year));
354:             $end = clone $start;
355:             $end->min += $this->slotLength;
356: 
357:             // Search through our events.
358:             foreach ($this->events as $key => $event) {
359:                 // If we have side_by_side we only want to include the
360:                 // event in the proper calendar.
361:                 if ($this->sidebyside) {
362:                     $cid = $event->calendar;
363:                 } else {
364:                     $cid = 0;
365:                 }
366: 
367:                 // If the event falls anywhere inside this slot, add
368:                 // it, make sure other events know that they overlap
369:                 // it, and increment the event's vertical span.
370:                 if (($event->end->compareDateTime($start) > 0 &&
371:                      $event->start->compareDateTime($end) < 0) ||
372:                     ($event->end->compareDateTime($event->start) == 0 &&
373:                      $event->start->compareDateTime($start) == 0)) {
374: 
375:                     // Make sure we keep the latest hour that an event
376:                     // reaches up-to-date.
377:                     if ($i > $this->_last &&
378:                         (!$day_hour_force || $i <= $day_hour_end)) {
379:                         $this->_last = $i;
380:                     }
381: 
382:                     // Make sure we keep the first hour that an event
383:                     // reaches up-to-date.
384:                     if ($i < $this->_first &&
385:                         (!$day_hour_force || $i >= $day_hour_start)) {
386:                         $this->_first = $i;
387:                     }
388: 
389:                     if (!$day_hour_force ||
390:                         ($i >= $day_hour_start && $i <= $day_hour_end)) {
391:                         // Add this event to the events which are in this row.
392:                         $this->_event_matrix[$cid][$i][] = $key;
393: 
394:                         // Increment the event's vertical span.
395:                         ++$this->events[$key]->rowspan;
396:                     }
397:                 }
398:             }
399: 
400:             foreach (array_keys($this->_currentCalendars) as $cid) {
401:                 // Update the number of events that events in this row
402:                 // overlap with.
403:                 $max = 0;
404:                 $count = count($this->_event_matrix[$cid][$i]);
405:                 foreach ($this->_event_matrix[$cid][$i] as $ev) {
406:                     $this->events[$ev]->overlap = max($this->events[$ev]->overlap, $count);
407:                     $max = max($max, $this->events[$ev]->overlap);
408:                 }
409: 
410:                 // Update the set of rowspans to include the value for
411:                 // this row.
412:                 $spans[$cid][$max] = true;
413:             }
414:         }
415: 
416:         foreach (array_keys($this->_currentCalendars) as $cid) {
417:             // Sort every row by start time so that events always show
418:             // up here in the same order.
419:             for ($i = $this->_first; $i <= $this->_last; ++$i) {
420:                 if (count($this->_event_matrix[$cid][$i])) {
421:                     usort($this->_event_matrix[$cid][$i], array($this, '_sortByStart'));
422:                 }
423:             }
424: 
425:             // Now that we have the number of events in each row, we
426:             // can calculate the total span needed.
427:             $span[$cid] = 1;
428: 
429:             // Turn keys into array values.
430:             $spans[$cid] = array_keys($spans[$cid]);
431: 
432:             // Start with the biggest one first.
433:             rsort($spans[$cid]);
434:             foreach ($spans[$cid] as $s) {
435:                 // If the number of events in this row doesn't divide
436:                 // cleanly into the current total span, we need to
437:                 // multiply the total span by the number of events in
438:                 // this row.
439:                 if ($s != 0 && $span[$cid] % $s != 0) {
440:                     $span[$cid] *= $s;
441:                 }
442:             }
443:             $this->totalspan += $span[$cid];
444:         }
445:         // Set the final span.
446:         if (isset($span)) {
447:             $this->span = $span;
448:         } else {
449:             $this->totalspan = 1;
450:         }
451: 
452:         // We're now parsed and ready to go.
453:         $this->_parsed = true;
454:     }
455: 
456:     public function link($offset = 0, $full = false)
457:     {
458:         return Horde::url('day.php', $full)
459:             ->add('date', $this->getTime('%Y%m%d', $offset));
460:     }
461: 
462:     public function getName()
463:     {
464:         return 'Day';
465:     }
466: 
467:     public function prefHourFormat($hour)
468:     {
469:         $hour = $hour % 24;
470:         if ($GLOBALS['prefs']->getValue('twentyFour')) {
471:             return $hour;
472:         }
473:         return ($hour % 12 == 0 ? 12 : $hour % 12)
474:             . ($hour < 12 ? 'am' : 'pm');
475:     }
476: 
477:     protected function _sortByStart($evA, $evB)
478:     {
479:         $sA = $this->events[$evA]->start;
480:         $sB = $this->events[$evB]->start;
481: 
482:         return $sB->compareTime($sA);
483:     }
484: 
485: }
486: 
API documentation generated by ApiGen