public function testFirstDayOfWeek() { $this->assertEquals('2006-01-02', Horde_Date_Utils::firstDayOfWeek(1, 2006)->format('Y-m-d')); $this->assertEquals('2007-01-01', Horde_Date_Utils::firstDayOfWeek(1, 2007)->format('Y-m-d')); $this->assertEquals('2007-12-31', Horde_Date_Utils::firstDayOfWeek(1, 2008)->format('Y-m-d')); $this->assertEquals('2010-01-04', Horde_Date_Utils::firstDayOfWeek(1, 2010)->format('Y-m-d')); }
protected function _render(Horde_Date $day = null) { $this->_start = new Horde_Date($day); $this->_start->mday = 1; $this->_days = Horde_Date_Utils::daysInMonth($day->month, $day->year); $this->_end = new Horde_Date($this->_start); $this->_end->hour = 23; $this->_end->min = $this->_end->sec = 59; $this->_end->mday = $this->_days; }
/** * * @global Horde_Prefs $prefs * @param Horde_Date $date * * @return Kronolith_View_Month */ public function __construct(Horde_Date $date) { global $prefs; $this->month = $date->month; $this->year = $date->year; // Need to calculate the start and length of the view. $this->date = new Horde_Date($date); $this->date->mday = 1; $this->_startday = $this->date->dayOfWeek(); $this->_daysInView = Date_Calc::weeksInMonth($this->month, $this->year) * 7; if (!$prefs->getValue('week_start_monday')) { $this->_startOfView = 1 - $this->_startday; // We may need to adjust the number of days in the view if // we're starting weeks on Sunday. if ($this->_startday == Horde_Date::DATE_SUNDAY) { $this->_daysInView -= 7; } $endday = new Horde_Date(array('mday' => Horde_Date_Utils::daysInMonth($this->month, $this->year), 'month' => $this->month, 'year' => $this->year)); $endday = $endday->dayOfWeek(); if ($endday == Horde_Date::DATE_SUNDAY) { $this->_daysInView += 7; } } else { if ($this->_startday == Horde_Date::DATE_SUNDAY) { $this->_startOfView = -5; } else { $this->_startOfView = 2 - $this->_startday; } } $startDate = new Horde_Date(array('year' => $this->year, 'month' => $this->month, 'mday' => $this->_startOfView)); $endDate = new Horde_Date(array('year' => $this->year, 'month' => $this->month, 'mday' => $this->_startOfView + $this->_daysInView)); if ($prefs->getValue('show_shared_side_by_side')) { $allCalendars = Kronolith::listInternalCalendars(); $this->_currentCalendars = array(); foreach ($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS) as $id) { $this->_currentCalendars[$id] = $allCalendars[$id]; } } else { $this->_currentCalendars = array('internal_0' => true); } try { $this->_events = Kronolith::listEvents($startDate, $endDate); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); $this->_events = array(); } if (!is_array($this->_events)) { $this->_events = array(); } }
public function __construct(Horde_Date $date) { $week = $date->weekOfYear(); $year = $date->year; if (!$GLOBALS['prefs']->getValue('week_start_monday') && $date->dayOfWeek() == Horde_Date::DATE_SUNDAY) { ++$week; } if ($week > 51 && $date->month == 1) { --$year; } elseif ($week == 1 && $date->month == 12) { ++$year; } $this->year = $year; $this->week = $week; $day = Horde_Date_Utils::firstDayOfWeek($week, $year); if (!isset($this->startDay)) { if ($GLOBALS['prefs']->getValue('week_start_monday')) { $this->startDay = Horde_Date::DATE_MONDAY; $this->endDay = Horde_Date::DATE_SUNDAY + 7; } else { $day->mday--; $this->startDay = Horde_Date::DATE_SUNDAY; $this->endDay = Horde_Date::DATE_SATURDAY; } } $this->startDate = new Horde_Date($day); for ($i = $this->startDay; $i <= $this->endDay; ++$i) { $this->days[$i] = new Kronolith_View_Day($day, array()); $day->mday++; } $endDate = new Horde_Date($day); try { $allevents = Kronolith::listEvents($this->startDate, $endDate); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); $allevents = array(); } for ($i = $this->startDay; $i <= $this->endDay; ++$i) { $date_stamp = $this->days[$i]->dateString(); $this->days[$i]->events = isset($allevents[$date_stamp]) ? $allevents[$date_stamp] : array(); } $this->sidebyside = $this->days[$this->startDay]->sidebyside; $this->_currentCalendars = $this->days[$this->startDay]->currentCalendars; $this->slotsPerHour = $this->days[$this->startDay]->slotsPerHour; $this->slotsPerDay = $this->days[$this->startDay]->slotsPerDay; $this->slotLength = $this->days[$this->startDay]->slotLength; }
/** * * @global Horde_Prefs $prefs * @param Horde_Date $date * * @return Kronolith_View_Month */ public function __construct(Horde_Date $date) { global $prefs; $this->month = $date->month; $this->year = $date->year; // Need to calculate the start and length of the view. $this->date = new Horde_Date($date); $this->date->mday = 1; $this->_startday = $this->date->dayOfWeek(); if (!$prefs->getValue('week_start_monday')) { $this->_startOfView = 1 - $this->_startday; } else { if ($this->_startday == Horde_Date::DATE_SUNDAY) { $this->_startOfView = -5; } else { $this->_startOfView = 2 - $this->_startday; } } $startDate = new Horde_Date($this->year, $this->month, $this->_startOfView); $this->_endDate = new Horde_Date($this->year, $this->month, Horde_Date_Utils::daysInMonth($this->month, $this->year) + 1); $this->_endDate->mday += (7 - ($this->_endDate->format('w') - $prefs->getValue('week_start_monday'))) % 7; if ($prefs->getValue('show_shared_side_by_side')) { $allCalendars = Kronolith::listInternalCalendars(); $this->_currentCalendars = array(); foreach ($GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS) as $id) { $this->_currentCalendars[$id] = $allCalendars[$id]; } } else { $this->_currentCalendars = array('internal_0' => true); } try { $this->_events = Kronolith::listEvents($startDate, $this->_endDate); } catch (Exception $e) { $GLOBALS['notification']->push($e, 'horde.error'); $this->_events = array(); } if (!is_array($this->_events)) { $this->_events = array(); } }
public function __construct($title, &$vars) { parent::__construct($vars, $title); // FIXME: Generate a list of clients from Turba? //$clients = $now = time(); if (!$vars->exists('startdate')) { // Default to the beginning of the previous calendar month $startdate = array('day' => 1, 'month' => date('n', $now), 'year' => date('Y', $now), 'hour' => 0, 'minute' => 0, 'second' => 0); $vars->set('startdate', $startdate); } if (!$vars->exists('enddate')) { // Default to the end of the previous calendar month $month = date('n', $now); $year = date('Y', $now); $lastday = Horde_Date_Utils::daysInMonth($month, $year); $enddate = array('day' => $lastday, 'month' => $month, 'year' => $year, 'hour' => 23, 'minute' => 59, 'second' => 59); $vars->set('enddate', $enddate); } try { $accountcodes = Operator::getAccountCodes(true); } catch (Exception $e) { $GLOBALS['notification']->push($e); $accountcodes = array(); } // Parameters for Horde_Form_datetime $start_year = date('Y', $now) - 5; $end_year = ''; $picker = true; $format_in = null; $format_out = '%x'; $show_seconds = true; $params = array($start_year, $end_year, $picker, $format_in, $format_out, $show_seconds); $this->addVariable(_("Account Code"), 'accountcode', 'enum', false, false, null, array($accountcodes)); $this->addVariable(_("Destination Context"), 'dcontext', 'text', false, false, _("An empty destination context will match all destination contexts.")); $this->addVariable(_("Start Date & Time"), 'startdate', 'datetime', true, false, null, $params); $this->addVariable(_("End Date & Time"), 'enddate', 'datetime', true, false, null, $params); }
/** * Corrects any over- or underflows in any of the date's members. * * @param integer $mask We may not want to correct some overflows. * @param integer $down Whether to correct the date up or down. */ protected function _correct($mask = self::MASK_ALLPARTS, $down = false) { if ($mask & self::MASK_SECOND) { if ($this->_sec < 0 || $this->_sec > 59) { $mask |= self::MASK_MINUTE; $this->_min += (int) ($this->_sec / 60); $this->_sec %= 60; if ($this->_sec < 0) { $this->_min--; $this->_sec += 60; } } } if ($mask & self::MASK_MINUTE) { if ($this->_min < 0 || $this->_min > 59) { $mask |= self::MASK_HOUR; $this->_hour += (int) ($this->_min / 60); $this->_min %= 60; if ($this->_min < 0) { $this->_hour--; $this->_min += 60; } } } if ($mask & self::MASK_HOUR) { if ($this->_hour < 0 || $this->_hour > 23) { $mask |= self::MASK_DAY; $this->_mday += (int) ($this->_hour / 24); $this->_hour %= 24; if ($this->_hour < 0) { $this->_mday--; $this->_hour += 24; } } } if ($mask & self::MASK_MONTH) { $this->_correctMonth(); /* When correcting the month, always correct the day too. Months * have different numbers of days. */ if (isset($this->_mday)) { $mask |= self::MASK_DAY; } } if ($mask & self::MASK_DAY) { while ($this->_mday > 366 + 31) { if (Horde_Date_Utils::isLeapYear($this->_year) && $this->_month <= 2 || Horde_Date_Utils::isLeapYear($this->_year + 1) && $this->_month > 2) { $this->_mday -= 366; } else { $this->_mday -= 365; } $this->_year++; } while ($this->_mday > 28 && $this->_mday > Horde_Date_Utils::daysInMonth($this->_month, $this->_year)) { if ($down) { $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month + 1, $this->_year) - Horde_Date_Utils::daysInMonth($this->_month, $this->_year); } else { $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month, $this->_year); $this->_month++; } $this->_correctMonth(); } while ($this->_mday < 1) { --$this->_month; $this->_correctMonth(); $this->_mday += Horde_Date_Utils::daysInMonth($this->_month, $this->_year); } } }
/** * Adds rules from this ruleset to a VTIMEZONE component. * * @param Horde_Icalendar_Vtimezone $tz A VTIMEZONE component. * @param string $tzid The timezone ID of the component. * @param string $name A timezone name abbreviation. * May contain a placeholder that is * replaced the Rules' "Letter(s)" * entry. * @param array $startOffset An offset hash describing the * base offset of a timezone. * @param Horde_Date $start Start of the period to add rules * for. * @param Horde_Date $end End of the period to add rules * for. */ public function addRules(Horde_Icalendar_Vtimezone $tz, $tzid, $name, $startOffset, Horde_Date $start, Horde_Date $end = null) { $offset = $startOffset; foreach ($this->_rules as $rule) { $year = $rule[3]; if ($year[0] == 'o') { // TO is "only" $rule[3] = $rule[2]; } if ($rule[3][0] != 'm' && $rule[3] < $start->year) { // TO is not maximum and is before the searched period continue; } if ($end && $rule[2][0] != 'm' && $rule[2] > $end->year) { // FROM is not "minimum" and is after the searched period break; } if ($rule[2][0] != 'm' && $rule[2] < $start->year) { $rule[2] = $start->year; } if ($rule[8] == 0) { $component = new Horde_Icalendar_Standard(); $component->setAttribute('TZOFFSETFROM', $offset); $component->setAttribute('TZOFFSETTO', $startOffset); $offset = $startOffset; } else { $component = new Horde_Icalendar_Daylight(); $component->setAttribute('TZOFFSETFROM', $offset); $offset = $this->_getOffset($startOffset, $rule[8]); $component->setAttribute('TZOFFSETTO', $offset); } $month = Horde_Timezone::getMonth($rule[5]); // Retrieve time of rule start. preg_match('/(\\d+)(?::(\\d+))?(?::(\\d+))?(w|s|u)?/', $rule[7], $match); if (!isset($match[2])) { $match[2] = 0; } if ($rule[2] == $rule[3] && preg_match('/^\\d+$/', $rule[6])) { // Rule lasts only for a single year and starts on a specific // date. $rdate = new Horde_Date(array('year' => $rule[2], 'month' => Horde_Timezone::getMonth($rule[5]), 'mday' => $rule[6], 'hour' => $match[1], 'min' => $match[2], 'sec' => 0)); $component->setAttribute('DTSTART', $rdate); } elseif (substr($rule[6], 0, 4) == 'last') { // Rule starts on the last of a certain weekday of the month. $weekday = $this->_weekdays[substr($rule[6], 4, 3)]; $last = new Horde_Date(array('year' => $rule[2], 'month' => $month, 'mday' => Horde_Date_Utils::daysInMonth($month, $rule[2]), 'hour' => $match[1], 'min' => $match[2], 'sec' => 0)); while ($last->dayOfWeek() != $weekday) { $last->mday--; } $component->setAttribute('DTSTART', $last); if ($rule[3][0] == 'm') { $until = ''; } else { $last = new Horde_Date(array('year' => $rule[3], 'month' => $month, 'mday' => Horde_Date_Utils::daysInMonth($month, $rule[2]), 'hour' => $match[1], 'min' => $match[2], 'sec' => 0), $tzid); while ($last->dayOfWeek() != $weekday) { $last->mday--; } $last->setTimezone('UTC'); $until = ';UNTIL=' . $last->format('Ymd\\THIs') . 'Z'; } $component->setAttribute('RRULE', 'FREQ=YEARLY;BYDAY=-1' . Horde_String::upper(substr($rule[6], 4, 2)) . ';BYMONTH=' . $month . $until); } elseif (strpos($rule[6], '>=')) { // Rule starts on a certain weekday after a certain day of // month. list($weekday, $day) = explode('>=', $rule[6]); $weekdayInt = $this->_weekdays[substr($weekday, 0, 3)]; $first = new Horde_Date(array('year' => $rule[2], 'month' => $month, 'mday' => $day, 'hour' => $match[1], 'min' => $match[2], 'sec' => 0)); while ($first->dayOfWeek() != $weekdayInt) { $first->mday++; } $component->setAttribute('DTSTART', $first); if ($rule[3][0] == 'm') { $until = ''; } else { $last = new Horde_Date(array('year' => $rule[3], 'month' => $month, 'mday' => $day, 'hour' => $match[1], 'min' => $match[2], 'sec' => 0), $tzid); while ($last->dayOfWeek() != $weekday) { $last->mday++; } $last->setTimezone('UTC'); $until = ';UNTIL=' . $last->format('Ymd\\THIs') . 'Z'; } for ($days = array(), $i = $day, $lastDay = min(Horde_Date_Utils::daysInMonth($month, $rule[2]), $i + 6); $day > 1 && $i <= $lastDay; $i++) { $days[] = $i; } $component->setAttribute('RRULE', 'FREQ=YEARLY;BYMONTH=' . $month . ($days ? ';BYMONTHDAY=' . implode(',', $days) : '') . ';BYDAY=1' . Horde_String::upper(substr($weekday, 0, 2)) . $until); } elseif (strpos($rule[6], '<=')) { // Rule starts on a certain weekday before a certain day of // month. list($weekday, $day) = explode('>=', $rule[6]); $weekdayInt = $this->_weekdays[substr($weekday, 0, 3)]; $last = new Horde_Date(array('year' => $rule[2], 'month' => $month, 'mday' => $day, 'hour' => $match[1], 'min' => $match[2], 'sec' => 0)); while ($last->dayOfWeek() != $weekdayInt) { $last->mday--; } $component->setAttribute('DTSTART', $last); if ($rule[3][0] == 'm') { $until = ''; } else { $last = new Horde_Date(array('year' => $rule[3], 'month' => $month, 'mday' => $day, 'hour' => $match[1], 'min' => $match[2], 'sec' => 0), $tzid); while ($last->dayOfWeek() != $weekday) { $last->mday--; } $last->setTimezone('UTC'); $until = ';UNTIL=' . $last->format('Ymd\\THIs') . 'Z'; } for ($days = array(), $i = 1; $i <= $day; $i++) { $days[] = $i; } $component->setAttribute('RRULE', 'FREQ=YEARLY;BYMONTH=' . $month . ';BYMONTHDAY=' . implode(',', $days) . ';BYDAY=-1' . Horde_String::upper(substr($weekday, 0, 2)) . $until); } $component->setAttribute('TZNAME', sprintf($name, $rule[9])); $tz->addComponent($component); } }
/** * Build the Horde_View object for a FB Post. * * @param stdClass $post The Facebook post object. * * @return string The HTML to render the $post. */ protected function _buildPost($post) { global $prefs; $facebook = $this->_getFacebookObject(); $instance = $this->vars->instance; $uid = $facebook->auth->getLoggedInUser(); $postView = new Horde_View(array('templatePath' => HORDE_TEMPLATES . '/block')); $postView->actorImgUrl = $facebook->users->getThumbnail($post->from->id); $postView->actorProfileLink = Horde::externalUrl($facebook->users->getProfileLink($post->from->id), true); $postView->actorName = $post->from->name; $postView->message = empty($post->message) ? '' : $post->message; $postView->likes = $post->likes->count; $postView->postId = $post->id; $postView->privacy = $post->privacy; $postView->postInfo = sprintf(_("Posted %s"), Horde_Date_Utils::relativeDateTime($post->created_time, $prefs->getValue('date_format'), $prefs->getValue('twentyFour') ? "%H:%M %P" : "%I %M %P")) . ' ' . sprintf(_("Comments: %d"), $post->comments->count); $postView->type = $post->type; if (!empty($post->picture)) { $postView->attachment = new stdClass(); $postView->attachment->image = $post->picture; if (!empty($post->link)) { $postView->attachment->link = Horde::externalUrl($post->link, true); } if (!empty($post->name)) { $postView->attachment->name = $post->name; } if (!empty($post->caption)) { $postView->attachment->caption = $post->caption; } if (!empty($post->icon)) { $postView->icon = $post->icon; } if (!empty($post->description)) { $postView->attachment->description = $post->description; } } if (!empty($post->place)) { $postView->place = array('name' => $post->place->name, 'link' => Horde::externalUrl($facebook->getFacebookUrl() . '/' . $post->place->id, true), 'location' => $post->place->location); } if (!empty($post->with_tags)) { $postView->with = array(); foreach ($post->with_tags->data as $with) { $postView->with[] = array('name' => $with->name, 'link' => Horde::externalUrl($facebook->users->getProfileLink($with->id), true)); } } // Actions $like = ''; foreach ($post->actions as $availableAction) { if ($availableAction->name == 'Like') { $like = '<a href="#" onclick="Horde[\'' . $instance . '_facebook\'].addLike(\'' . $post->id . '\');return false;">' . _("Like") . '</a>'; } } $likes = ''; if ($post->likes->count) { foreach ($post->likes->data as $likeData) { if ($likeData->id == $uid && $post->likes->count > 1) { $likes = sprintf(ngettext("You and %d other person likes this", "You and %d other people like this", $post->likes->count - 1), $post->likes->count - 1); break; } elseif ($likeData->id == $uid) { $likes = _("You like this"); break; } } if (empty($likes)) { $likes = sprintf(ngettext("%d person likes this", "%d persons like this", $post->likes->count), $post->likes->count) . (!empty($like) ? ' ' . $like : ''); } else { $likes = $likes . !empty($like) ? ' ' . $like : ''; } } else { $likes = $like; } $postView->likesInfo = $likes; return $postView->render('facebook_story'); }
/** */ protected function _content() { global $page_output; /* Get the twitter driver */ try { $twitter = $this->_getTwitterObject(); } catch (Horde_Exception $e) { throw new Horde_Exception(sprintf(_("There was an error contacting Twitter: %s"), $e->getMessage())); } /* Get a unique ID in case we have multiple Twitter blocks. */ $instance = (string) new Horde_Support_Randomid(); /* Latest status */ if (empty($this->_profile->status)) { // status might not be set if only updating the block via ajax try { $this->_profile = Horde_Serialize::unserialize($twitter->account->verifyCredentials(), Horde_Serialize::JSON); if (empty($this->_profile)) { return _("Temporarily unable to contact Twitter. Please try again later."); } } catch (Horde_Service_Twitter_Exception $e) { $msg = Horde_Serialize::unserialize($e->getMessage(), Horde_Serialize::JSON); if (is_object($msg)) { $msg = $msg->errors[0]->message; } return sprintf(_("There was an error contacting Twitter: %s"), $msg); } } /* Build values to pass to the javascript twitter client */ $defaultText = addslashes(_("What are you working on now?")); $endpoint = Horde::url('services/twitter/', true); $inReplyToNode = $instance . '_inReplyTo'; $inReplyToText = addslashes(_("In reply to:")); $justNowText = addslashes(_("Just now...")); $refresh = empty($this->_params['refresh_rate']) ? 300 : $this->_params['refresh_rate']; /* Add the client javascript / initialize it */ $page_output->addScriptFile('twitterclient.js', 'horde'); $page_output->addScriptFile('scriptaculous/effects.js', 'horde'); $favorite = _("Favorite"); $unfavorite = _("Unfavorite"); $script = <<<EOT Horde = window.Horde = window.Horde || {}; Horde['twitter{$instance}'] = new Horde_Twitter({ instanceid: '{$instance}', getmore: '{$instance}_getmore', input: '{$instance}_newStatus', spinner: '{$instance}_loading', content: '{$instance}_stream', contenttab: '{$instance}_contenttab', mentiontab: '{$instance}_mentiontab', mentions: '{$instance}_mentions', endpoint: '{$endpoint}', inreplyto: '{$inReplyToNode}', refreshrate: {$refresh}, counter: '{$instance}_counter', strings: { inreplyto: '{$inReplyToText}', defaultText: '{$defaultText}', justnow: '{$justNowText}', favorite: '{$favorite}', unfavorite: '{$unfavorite}' } }); EOT; $page_output->addInlineScript($script, true); /* Build the UI */ $view = new Horde_View(array('templatePath' => HORDE_TEMPLATES . '/block')); $view->addHelper('Tag'); $view->instance = $instance; $view->defaultText = $defaultText; $view->loadingImg = Horde_Themes_Image::tag('loading.gif', array('attr' => array('id' => $instance . '_loading', 'style' => 'display:none;'))); $view->latestStatus = !empty($this->_profile->status) ? htmlspecialchars($this->_profile->status->text) : ''; $view->latestDate = !empty($this->_profile->status) ? Horde_Date_Utils::relativeDateTime(strtotime($this->_profile->status->created_at), $GLOBALS['prefs']->getValue('date_format'), $GLOBALS['prefs']->getValue('twentyFour') ? "%H:%M" : "%I:%M %P") : ''; $view->bodyHeight = empty($this->_params['height']) ? 350 : $this->_params['height']; return $view->render('twitter-layout'); }
/** * Finds the next recurrence of this event that's after $afterDate. * * @param Horde_Date|string $after Return events after this date. * * @return Horde_Date|boolean The date of the next recurrence or false * if the event does not recur after * $afterDate. */ public function nextRecurrence($after) { if (!$after instanceof Horde_Date) { $after = new Horde_Date($after); } else { $after = clone $after; } // Make sure $after and $this->start are in the same TZ $after->setTimezone($this->start->timezone); if ($this->start->compareDateTime($after) >= 0) { return clone $this->start; } if ($this->recurInterval == 0 && empty($this->rdates)) { return false; } switch ($this->getRecurType()) { case self::RECUR_DAILY: $diff = $this->start->diff($after); $recur = ceil($diff / $this->recurInterval); if ($this->recurCount && $recur >= $this->recurCount) { return false; } $recur *= $this->recurInterval; $next = $this->start->add(array('day' => $recur)); if ((!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) && $next->compareDateTime($after) >= 0) { return $next; } break; case self::RECUR_WEEKLY: if (empty($this->recurData)) { return false; } $start_week = Horde_Date_Utils::firstDayOfWeek($this->start->format('W'), $this->start->year); $start_week->timezone = $this->start->timezone; $start_week->hour = $this->start->hour; $start_week->min = $this->start->min; $start_week->sec = $this->start->sec; // Make sure we are not at the ISO-8601 first week of year while // still in month 12...OR in the ISO-8601 last week of year while // in month 1 and adjust the year accordingly. $week = $after->format('W'); if ($week == 1 && $after->month == 12) { $theYear = $after->year + 1; } elseif ($week >= 52 && $after->month == 1) { $theYear = $after->year - 1; } else { $theYear = $after->year; } $after_week = Horde_Date_Utils::firstDayOfWeek($week, $theYear); $after_week->timezone = $this->start->timezone; $after_week_end = clone $after_week; $after_week_end->mday += 7; $diff = $start_week->diff($after_week); $interval = $this->recurInterval * 7; $repeats = floor($diff / $interval); if ($diff % $interval < 7) { $recur = $diff; } else { /** * If the after_week is not in the first week interval the * search needs to skip ahead a complete interval. The way it is * calculated here means that an event that occurs every second * week on Monday and Wednesday with the event actually starting * on Tuesday or Wednesday will only have one incidence in the * first week. */ $recur = $interval * ($repeats + 1); } if ($this->hasRecurCount()) { $recurrences = 0; /** * Correct the number of recurrences by the number of events * that lay between the start of the start week and the * recurrence start. */ $next = clone $start_week; while ($next->compareDateTime($this->start) < 0) { if ($this->recurOnDay((int) pow(2, $next->dayOfWeek()))) { $recurrences--; } ++$next->mday; } if ($repeats > 0) { $weekdays = $this->recurData; $total_recurrences_per_week = 0; while ($weekdays > 0) { if ($weekdays % 2) { $total_recurrences_per_week++; } $weekdays = ($weekdays - $weekdays % 2) / 2; } $recurrences += $total_recurrences_per_week * $repeats; } } $next = clone $start_week; $next->mday += $recur; while ($next->compareDateTime($after) < 0 && $next->compareDateTime($after_week_end) < 0) { if ($this->hasRecurCount() && $next->compareDateTime($after) < 0 && $this->recurOnDay((int) pow(2, $next->dayOfWeek()))) { $recurrences++; } ++$next->mday; } if ($this->hasRecurCount() && $recurrences >= $this->recurCount) { return false; } if (!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) { if ($next->compareDateTime($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } while (!$this->recurOnDay((int) pow(2, $next->dayOfWeek())) && $next->compareDateTime($after_week_end) < 0) { ++$next->mday; } if (!$this->hasRecurEnd() || $next->compareDateTime($this->recurEnd) <= 0) { if ($next->compareDateTime($after_week_end) >= 0) { return $this->nextRecurrence($after_week_end); } else { return $next; } } } break; case self::RECUR_MONTHLY_DATE: $start = clone $this->start; if ($after->compareDateTime($start) < 0) { $after = clone $start; } else { $after = clone $after; } // If we're starting past this month's recurrence of the event, // look in the next month on the day the event recurs. if ($after->mday > $start->mday) { ++$after->month; $after->mday = $start->mday; } // Adjust $start to be the first match. $offset = $after->month - $start->month + ($after->year - $start->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; if ($this->recurCount && $offset / $this->recurInterval >= $this->recurCount) { return false; } $start->month += $offset; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } // Bail if we've gone past the end of recurrence. if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($start) < 0) { return false; } if ($start->isValid()) { return $start; } // If the interval is 12, and the date isn't valid, then we // need to see if February 29th is an option. If not, then the // event will _never_ recur, and we need to stop checking to // avoid an infinite loop. if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) { return false; } // Add the recurrence interval. $start->month += $this->recurInterval; } while (true); break; case self::RECUR_MONTHLY_WEEKDAY: // Start with the start date of the event. $estart = clone $this->start; // What day of the week, and week of the month, do we recur on? if (isset($this->recurNthDay)) { $nth = $this->recurNthDay; $weekday = log($this->recurData, 2); } else { $nth = ceil($this->start->mday / 7); $weekday = $estart->dayOfWeek(); } // Adjust $estart to be the first candidate. $offset = $after->month - $estart->month + ($after->year - $estart->year) * 12; $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; // Adjust our working date until it's after $after. $estart->month += $offset - $this->recurInterval; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } $estart->month += $this->recurInterval; $next = clone $estart; $next->setNthWeekday($weekday, $nth); if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. continue; } if ($this->hasRecurEnd() && $next->compareDateTime($this->recurEnd) > 0) { // We've gone past the end of recurrence; we can give up // now. return false; } // We have a candidate to return. break; } while (true); return $next; case self::RECUR_YEARLY_DATE: // Start with the start date of the event. $estart = clone $this->start; $after = clone $after; if ($after->month > $estart->month || $after->month == $estart->month && $after->mday > $estart->mday) { ++$after->year; $after->month = $estart->month; $after->mday = $estart->mday; } // Seperate case here for February 29th if ($estart->month == 2 && $estart->mday == 29) { while (!Horde_Date_Utils::isLeapYear($after->year)) { ++$after->year; } } // Adjust $estart to be the first candidate. $offset = $after->year - $estart->year; if ($offset > 0) { $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; $estart->year += $offset; } // We've gone past the end of recurrence; give up. if ($this->recurCount && $offset >= $this->recurCount) { return false; } if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($estart) < 0) { return false; } return $estart; case self::RECUR_YEARLY_DAY: // Check count first. $dayofyear = $this->start->dayOfYear(); $count = ($after->year - $this->start->year) / $this->recurInterval + 1; if ($this->recurCount && ($count > $this->recurCount || $count == $this->recurCount && $after->dayOfYear() > $dayofyear)) { return false; } // Start with a rough interval. $estart = clone $this->start; $estart->year += floor($count - 1) * $this->recurInterval; // Now add the difference to the required day of year. $estart->mday += $dayofyear - $estart->dayOfYear(); // Add an interval if the estimation was wrong. if ($estart->compareDate($after) < 0) { $estart->year += $this->recurInterval; $estart->mday += $dayofyear - $estart->dayOfYear(); } // We've gone past the end of recurrence; give up. if ($this->hasRecurEnd() && $this->recurEnd->compareDateTime($estart) < 0) { return false; } return $estart; case self::RECUR_YEARLY_WEEKDAY: // Start with the start date of the event. $estart = clone $this->start; // What day of the week, and week of the month, do we recur on? if (isset($this->recurNthDay)) { $nth = $this->recurNthDay; $weekday = log($this->recurData, 2); } else { $nth = ceil($this->start->mday / 7); $weekday = $estart->dayOfWeek(); } // Adjust $estart to be the first candidate. $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval; // Adjust our working date until it's after $after. $estart->year += $offset - $this->recurInterval; $count = $offset / $this->recurInterval; do { if ($this->recurCount && $count++ >= $this->recurCount) { return false; } $estart->year += $this->recurInterval; $next = clone $estart; $next->setNthWeekday($weekday, $nth); if ($next->compareDateTime($after) < 0) { // We haven't made it past $after yet, try again. continue; } if ($this->hasRecurEnd() && $next->compareDateTime($this->recurEnd) > 0) { // We've gone past the end of recurrence; we can give up // now. return false; } // We have a candidate to return. break; } while (true); return $next; } // fall-back to RDATE properties if (!empty($this->rdates)) { $next = clone $this->start; foreach ($this->rdates as $rdate) { $next->year = $rdate->year; $next->month = $rdate->month; $next->mday = $rdate->mday; if ($next->compareDateTime($after) > 0) { return $next; } } } // We didn't find anything, the recurType was bad, or something else // went wrong - return false. return false; }
/** * Get the children of this gallery. * * @param integer $perm The permissions to limit to. * @param integer $from The child to start at. * @param integer $to The child to end with. * @param boolean $noauto Whether or not to automatically drill down to the * first grouping with more then one group. * * @return array A mixed array of Ansel_Gallery_Decorator_Date and * Ansel_Image objects. */ public function getGalleryChildren($perm = Horde_Perms::SHOW, $from = 0, $to = 0, $noauto = false) { // Cache the results static $children = array(); $fullkey = md5($noauto . $perm . $this->_gallery->id . serialize($this->_date) . 0 . 0); $cache_key = md5($noauto . $perm . $this->_gallery->id . serialize($this->_date) . $from . $to); if (!empty($children[$cache_key])) { return $children[$cache_key]; } elseif (!empty($children[$fullkey])) { return $this->_getArraySlice($children[$fullkey], $from, $to, true); } $ansel_storage = $GLOBALS['injector']->getInstance('Ansel_Storage'); // Get a list of all the subgalleries $this->_loadSubGalleries(); $params = array('fields' => array('image_id', 'image_original_date')); if (count($this->_subGalleries)) { $params['gallery_id'] = array_merge($this->_subGalleries, array($this->_gallery->id)); } else { $params['gallery_id'] = $this->_gallery->id; } $sorted_dates = array(); // See how specific the date is if (!count($this->_date) || empty($this->_date['year'])) { // All available images - grouped by year $images = $ansel_storage->listImages($params); $dates = array(); foreach ($images as $key => $image) { $dates[date('Y', $image['image_original_date'])][] = $key; } $keys = array_keys($dates); // Drill down further if we only have a single group if (!$noauto && count($keys) == 1) { $this->_date['year'] = array_pop($keys); return $this->getGalleryChildren($perm, $from, $to, $noauto); } sort($keys, SORT_NUMERIC); foreach ($keys as $key) { $sorted_dates[$key] = $dates[$key]; } $display_unit = 'year'; } elseif (empty($this->_date['month'])) { // Specific year - grouped by month $start = new Horde_Date(array('year' => $this->_date['year'], 'month' => 1, 'day' => 1)); // Last second of the year $end = new Horde_Date($start); $end->mday = 31; $end->month = 12; $end->hour = 23; $end->min = 59; $end->sec = 59; // Get the image ids and dates $params['filter'] = array(array('property' => 'originalDate', 'op' => '<=', 'value' => (int) $end->timestamp()), array('property' => 'originalDate', 'op' => '>=', 'value' => (int) $start->timestamp())); $images = $ansel_storage->listImages($params); $dates = array(); foreach ($images as $key => $image) { $dates[date('n', $image['image_original_date'])][] = $key; } $keys = array_keys($dates); // Only 1 date grouping here, automatically drill down if (!$noauto && count($keys) == 1) { $this->_date['month'] = array_pop($keys); return $this->getGalleryChildren($perm, $from, $to, $noauto); } sort($keys, SORT_NUMERIC); foreach ($keys as $key) { $sorted_dates[$key] = $dates[$key]; } $display_unit = 'month'; } elseif (empty($this->_date['day'])) { // A single month - group by day $start = new Horde_Date(array('year' => $this->_date['year'], 'month' => $this->_date['month'], 'day' => 1)); // Last second of the month $end = new Horde_Date($start); $end->mday = Horde_Date_Utils::daysInMonth($end->month, $end->year); $end->hour = 23; $end->min = 59; $end->sec = 59; $params['filter'] = array(array('property' => 'originalDate', 'op' => '<=', 'value' => (int) $end->timestamp()), array('property' => 'originalDate', 'op' => '>=', 'value' => (int) $start->timestamp())); $images = $ansel_storage->listImages($params); $dates = array(); foreach ($images as $key => $image) { $dates[date('d', $image['image_original_date'])][] = $key; } $keys = array_keys($dates); // Only a single grouping, go deeper if (!$noauto && count($keys) == 1) { $this->_date['day'] = array_pop($keys); return $this->getGalleryChildren($perm, $from, $to, $noauto); } sort($keys, SORT_NUMERIC); foreach ($keys as $key) { $sorted_dates[$key] = $dates[$key]; } $dates = $sorted_dates; $display_unit = 'day'; } else { // We are down to a specific day $start = new Horde_Date($this->_date); // Last second of this day $end = new Horde_Date($start->timestamp()); $end->hour = 23; $end->min = 59; $end->sec = 59; // Filter for this day $params['filter'] = array(array('property' => 'originalDate', 'op' => '<=', 'value' => (int) $end->timestamp()), array('property' => 'originalDate', 'op' => '>=', 'value' => (int) $start->timestamp())); // Only get what we need $params['offset'] = $from; $params['limit'] = $to; // Default to asking for just image_ids unset($params['fields']); // Get the image list $images = $ansel_storage->listImages($params); if ($images) { $results = $ansel_storage->getImages(array('ids' => $images, 'preserve' => true)); } else { $results = array(); } if ($this->_gallery->get('has_subgalleries')) { $images = array(); foreach ($results as $id => $image) { $image->gallery = $this->_gallery->id; $images[$id] = $image; } $children[$cache_key] = $images; } else { $children[$cache_key] = $results; } return $children[$cache_key]; } $results = array(); foreach ($sorted_dates as $key => $images) { /* Get the new date parameter */ switch ($display_unit) { case 'year': $date = array('year' => $key); break; case 'month': $date = array('year' => $this->_date['year'], 'month' => (int) $key); break; case 'day': $date = array('year' => (int) $this->_date['year'], 'month' => (int) $this->_date['month'], 'day' => (int) $key); } $obj = new Ansel_Gallery_Decorator_Date($this->_gallery, $images); $obj->setDate($date); $results[$key] = $obj; } $children[$cache_key] = $results; if ($from > 0 || $to > 0) { return $this->_getArraySlice($results, $from, $to, true); } return $results; }
/** */ protected function _content() { global $prefs; if (isset($this->_params['calendar']) && $this->_params['calendar'] != '__all') { $calendars = Kronolith::listCalendars(); if (!isset($calendars[$this->_params['calendar']])) { return _("Calendar not found"); } if (!$calendars[$this->_params['calendar']]->hasPermission(Horde_Perms::READ)) { return _("Permission Denied"); } } $year = date('Y'); $month = date('m'); $startday = new Horde_Date(array('mday' => 1, 'month' => $month, 'year' => $year)); $startday = $startday->dayOfWeek(); if (!$prefs->getValue('week_start_monday')) { $startOfView = 1 - $startday; $endday = new Horde_Date(array('mday' => Horde_Date_Utils::daysInMonth($month, $year), 'month' => $month, 'year' => $year)); } else { if ($startday == Horde_Date::DATE_SUNDAY) { $startOfView = -5; } else { $startOfView = 2 - $startday; } } $startDate = new Horde_Date($year, $month, $startOfView); $endDate = new Horde_Date($year, $month, Horde_Date_Utils::daysInMonth($month, $year) + 1); $endDate->mday += (7 - ($endDate->format('w') - $prefs->getValue('week_start_monday'))) % 7; /* Table start. and current month indicator. */ $html = '<table cellspacing="1" class="monthgrid" width="100%"><tr>'; /* Set up the weekdays. */ $weekdays = array(_("Mo"), _("Tu"), _("We"), _("Th"), _("Fr"), _("Sa")); if (!$prefs->getValue('week_start_monday')) { array_unshift($weekdays, _("Su")); } else { $weekdays[] = _("Su"); } foreach ($weekdays as $weekday) { $html .= '<th class="item">' . $weekday . '</th>'; } try { if (isset($this->_params['calendar']) && $this->_params['calendar'] != '__all') { list($type, $calendar) = explode('_', $this->_params['calendar'], 2); $driver = Kronolith::getDriver($type, $calendar); $all_events = $driver->listEvents($startDate, $endDate, array('show_recurrence' => true)); } else { $all_events = Kronolith::listEvents($startDate, $endDate, $GLOBALS['calendar_manager']->get(Kronolith::DISPLAY_CALENDARS)); } } catch (Exception $e) { return '<em>' . $e->getMessage() . '</em>'; } $weekday = 0; $week = -1; $weekStart = $prefs->getValue('week_start_monday'); for ($date_ob = new Kronolith_Day($month, $startOfView, $year); $date_ob->compareDate($endDate) < 0; $date_ob->mday++) { if ($weekday == 7) { $weekday = 0; } if ($weekday == 0) { ++$week; $html .= '</tr><tr>'; } if ($date_ob->isToday()) { $td_class = 'kronolith-today'; } elseif ($date_ob->month != $month) { $td_class = 'kronolith-othermonth'; } elseif ($date_ob->dayOfWeek() == 0 || $date_ob->dayOfWeek() == 6) { $td_class = 'kronolith-weekend'; } else { $td_class = ''; } $html .= '<td align="center" class="' . $td_class . '">'; /* Set up the link to the day view. */ $url = Horde::url('day.php', true)->add('date', $date_ob->dateString()); if (isset($this->_params['calendar']) && $this->_params['calendar'] != '__all') { $url->add('display_cal', $this->_params['calendar']); } $date_stamp = $date_ob->dateString(); if (empty($all_events[$date_stamp])) { /* No events, plain link to the day. */ $cell = Horde::linkTooltip($url, _("View Day")) . $date_ob->mday . '</a>'; } else { /* There are events; create a cell with tooltip to * list them. */ $day_events = ''; foreach ($all_events[$date_stamp] as $event) { if ($event->isAllDay()) { $day_events .= _("All day"); } else { $day_events .= $event->start->strftime($prefs->getValue('twentyFour') ? '%R' : '%I:%M%p') . '-' . $event->end->strftime($prefs->getValue('twentyFour') ? '%R' : '%I:%M%p'); } $location = $event->getLocation(); $day_events .= ':' . ($location ? ' (' . htmlspecialchars($location) . ')' : '') . ' ' . $event->getTitle() . "\n"; } $cell = Horde::linkTooltip($url, _("View Day"), '', '', '', $day_events) . $date_ob->mday . '</a>'; } /* Bold the cell if there are events. */ if (!empty($all_events[$date_stamp])) { $cell = '<strong>' . $cell . '</strong>'; } $html .= $cell . '</td>'; ++$weekday; } return $html . '</tr></table>'; }
/** * Finds a date matching a rule definition. * * @param array $rule A rule definition hash from addRules(). * @param integer $year A year when the rule should be applied. * * @return Horde_Date The first matching date. */ protected function _getFirstMatch($rule, $year) { $month = Horde_Timezone::getMonth($rule[5]); if (preg_match('/^\\d+$/', $rule[6])) { // Rule starts on a specific date. $date = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $rule[6])); } elseif (substr($rule[6], 0, 4) == 'last') { // Rule starts on the last of a certain weekday of the month. $weekday = $this->_weekdays[substr($rule[6], 4, 3)]; $date = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => Horde_Date_Utils::daysInMonth($month, $rule[2]))); while ($date->dayOfWeek() != $weekday) { $date->mday--; } } elseif (strpos($rule[6], '>=')) { // Rule starts on a certain weekday after a certain day of month. list($weekday, $day) = explode('>=', $rule[6]); $weekdayInt = $this->_weekdays[substr($weekday, 0, 3)]; $date = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $day)); while ($date->dayOfWeek() != $weekdayInt) { $date->mday++; } } elseif (strpos($rule[6], '<=')) { // Rule starts on a certain weekday before a certain day of month. list($weekday, $day) = explode('>=', $rule[6]); $weekdayInt = $this->_weekdays[substr($weekday, 0, 3)]; $date = new Horde_Date(array('year' => $year, 'month' => $month, 'mday' => $day)); while ($date->dayOfWeek() != $weekdayInt) { $date->mday--; } } else { throw new Horde_Timezone_Exception('Cannot parse rule ' . $rule[6]); } return $date; }
echo $this->profileLink; ?> <strong><?php echo $this->authorFullname; ?> </strong> <em><?php echo $this->authorName; ?> </em></a></div> <div class="hordeSmStreambody"> <?php echo $this->body; ?> <div class="hordeSmStreaminfo"> <?php echo sprintf(_("Posted %s via %s"), Horde_Date_Utils::relativeDateTime(strtotime($this->createdAt), $GLOBALS['prefs']->getValue('date_format')), $this->clientText); ?> </div> <?php if (!empty($this->tweet->retweeted_status)) { ?> <div class="hordeSmStreaminfo"> <?php echo sprintf(_("Retweeted by %s"), Horde::externalUrl('http://twitter.com/' . $this->escape($this->tweet->user->screen_name), true)) . '@' . $this->escape($this->tweet->user->screen_name); ?> </a> </div> <?php } ?> <div class="hordeSmStreaminfo">
public function html() { global $prefs; $html = '<table id="kronolith-view-year" class="kronolith-minical"><tr>'; for ($month = 1; $month <= 12; ++$month) { $html .= '<td>'; // Heading for each month. $date = new Horde_Date(sprintf('%04d%02d01010101', $this->year, $month)); $html .= '<table><thead><tr class="kronolith-minical-nav"><th colspan="7">' . Horde::url('month.php')->add('date', $date->dateString())->link() . $date->strftime('%B') . '</a></th></tr><tr><th class="kronolith-minical-empty"> </th>'; if (!$prefs->getValue('week_start_monday')) { $html .= '<th>' . _("Su") . '</th>'; } $html .= '<th>' . _("Mo") . '</th>' . '<th>' . _("Tu") . '</th>' . '<th>' . _("We") . '</th>' . '<th>' . _("Th") . '</th>' . '<th>' . _("Fr") . '</th>' . '<th>' . _("Sa") . '</th>'; if ($prefs->getValue('week_start_monday')) { $html .= '<th>' . _("Su") . '</th>'; } $html .= '</tr></thead><tbody><tr><td class="kronolith-minical-week">'; $startday = new Horde_Date(array('mday' => 1, 'month' => $month, 'year' => $this->year)); $startday = $startday->dayOfWeek(); $daysInView = Date_Calc::weeksInMonth($month, $this->year) * 7; if (!$prefs->getValue('week_start_monday')) { $startOfView = 1 - $startday; // We may need to adjust the number of days in the // view if we're starting weeks on Sunday. if ($startday == Horde_Date::DATE_SUNDAY) { $daysInView -= 7; } $endday = new Horde_Date(array('mday' => Horde_Date_Utils::daysInMonth($month, $this->year), 'month' => $month, 'year' => $this->year)); $endday = $endday->dayOfWeek(); if ($endday == Horde_Date::DATE_SUNDAY) { $daysInView += 7; } } else { if ($startday == Horde_Date::DATE_SUNDAY) { $startOfView = -5; } else { $startOfView = 2 - $startday; } } $currentCalendars = array(true); foreach ($currentCalendars as $id => $cal) { $cell = 0; for ($day = $startOfView; $day < $startOfView + $daysInView; ++$day) { $date = new Kronolith_Day($month, $day, $this->year); $date->hour = $prefs->getValue('twentyFour') ? 12 : 6; $week = $date->weekOfYear(); if ($cell % 7 == 0) { if ($cell != 0) { $html .= "</tr>\n<tr><td class=\"kronolith-minical-week\">"; } $html .= (int) $date->weekOfYear() . '</td>'; } if ($date->month != $month) { $style = 'kronolith-other-month'; } else { $style = ''; } /* Set up the link to the day view. */ $url = Horde::url('day.php', true)->add('date', $date->dateString()); if ($date->month == $month && !empty($this->_events[$date->dateString()])) { /* There are events; create a cell with tooltip to list * them. */ $day_events = ''; foreach ($this->_events[$date->dateString()] as $event) { if ($event->status == Kronolith::STATUS_CONFIRMED) { /* Set the background color to distinguish the * day */ $style = 'year-event'; } if ($event->isAllDay()) { $day_events .= _("All day"); } else { $day_events .= $event->start->strftime($prefs->getValue('twentyFour') ? '%R' : '%I:%M%p') . '-' . $event->end->strftime($prefs->getValue('twentyFour') ? '%R' : '%I:%M%p'); } $day_events .= ':' . ($event->getLocation() ? ' (' . $event->getLocation() . ')' : '') . ' ' . $event->getTitle() . "\n"; } /* Bold the cell if there are events. */ $cellday = '<strong>' . Horde::linkTooltip($url, _("View Day"), '', '', '', $day_events) . $date->mday . '</a></strong>'; } else { /* No events, plain link to the day. */ $cellday = Horde::linkTooltip($url, _("View Day")) . $date->mday . '</a>'; } if ($date->isToday() && $date->month == $month) { $style .= ' kronolith-today'; } $html .= '<td align="center" class="' . $style . '" height="10" width="5%" valign="top">' . $cellday . '</td>'; ++$cell; } } $html .= '</tr></tbody></table></td>'; if ($month % 3 == 0 && $month != 12) { $html .= '</tr><tr>'; } } echo $html . '</tr></table>'; }