- <?php
- /*
- * Lokorin.com
- * Copyright 2004-2006
- * Licensed under the GNU LGPL. See COPYING for full terms.
- */
- /**
- * A library that contains functions and classes related to calendars.
- * @author Andreas Launila
- * @version $Revision: 1.15 $
- * @package com.lokorin.lokorin.lib
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
- */
-
- /**
- * For access to common constants and functions.
- */
- require_once('common.php');
- doInclude('lib_forms');
- doInclude('lib_search');
- doInclude('lib_tables');
- CssHandler::getInstance()->includeCss('calendar');
-
- define('DATE_CALENDAR_NAV', 'F Y');
-
- /**
- * Gets the number of days in a specified month.
- * @param integer $month The number of the month of the year, has to be in the
- * range [1, 12].
- * @param integer $year The four digit representation of the year in question.
- * @return integer The number of days that occur in the speicified month of the
- * specified year.
- * @access private
- */
- function getDaysInMonth($month, $year) {
- $daysInMonths = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
- if($month != 2) {
- return($daysInMonths[$month-1]);
- } else {
- return(checkdate($month, 29, $year) ? 29 : 28);
- }
- }
-
- /**
- * Gets the names of all the years months.
- * @return array All the months' names in order, starting with January.
- * @access private
- */
- function getAllMonthNames() {
- return array('January', 'February', 'March', 'April', 'May', 'June',
- 'July', 'August', 'September', 'October', 'November', 'December');
- }
-
- /**
- * Internal function to convert days to timestamps equal to the last
- * second of the specified day.
- * @param integer day The unix time day that should be converted.
- * @return integer A unix timestamp representing the last second of
- * a day.
- * @access private
- */
- function convertDayToLastSecond($day) {
- return ($day+1) * 3600 * 24 - 1;
- }
-
- /**
- * Internal function to convert days to timestamps equal to the first
- * second of the specified day.
- * @param integer day The unix time day that should be converted.
- * @return integer A unix timestamp representing the first second of
- * a day.
- * @access private
- */
- function convertDayToFirstSecond($day) {
- return $day * 3600 * 24;
- }
-
- /**
- * Gets the month of the year that a specified day is included in.
- * @param integer $day The day, as an unix timestamp converted into
- * days, that represents the day for which the month should be
- * retrieved.
- * @return integer The months number (1-12).
- * @access private
- */
- function getMonth($day) {
- return date('n', $day * 3600 * 24 + 1);
- }
-
- /**
- * Gets the year that a specified day is included in.
- * @param integer $day The day, as an unix timestamp converted into
- * days, that represents the day for which the yearshould be
- * retrieved.
- * @return integer The year (four digits).
- * @access private
- */
- function getYear($day) {
- return date('Y', $day * 3600 * 24 + 1);
- }
-
- /**
- * Describes a calendar that displays a database table as a calendar with
- * entries. Users can select specific days from the calendar and view the
- * connected table rows. The calendar gives the user drop downs to select year
- * and month, the days are represented in a grid. Additional filters can also
- * be placed on the calendar.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class Calendar {
- /**
- * The name of the table that the calendar should display.
- * @var string
- */
- private $table;
- /**
- * The table searcher that should be used by the calendar.
- * @var TableSearcher
- */
- private $searcher;
- /**
- * The form that should be displayed to the user along with the calendar
- * grid.
- * @var Form
- */
- private $form;
- /**
- * The currently selected unix timestamp as days. I.e. this is not day of
- * the week, this is timestamp/(3600*24).
- * @var integer
- */
- private $day;
- /**
- * The text that should be included in the calendar's style attribute.
- * @var string
- */
- private $style;
- /**
- * The name of the table field that contains the unix timestamp values.
- * @var string
- */
- private $timestampField;
- /**
- * The last day, as an unix timestamp converted into days, that the
- * calendar should display.
- * @var integer
- */
- private $dayMax;
- /**
- * The first day, as an unix timestamp converted into days, that the
- * calendar should display.
- * @var integer
- */
- private $dayMin;
- /**
- * The root that should be used for all link urls.
- * @var string
- */
- protected $urlRoot;
-
- /**
- * Constructor for Calendar.
- * @param string $tableToDisplay The name of the table that the calendar
- * should display.
- * @param string $style An optional parameter to specify custom style code
- * to use for the calendars "style" attribute.
- * @param string $urlRoot The root that should be used for all urls
- * presented by the calendar.
- * @param string $timestampField The name of the field in the specified
- * table that contains the unix timestamps.
- */
- public function __construct($tableToDisplay, $timestampField,
- $urlRoot = SELF, $style = '') {
- global $db;
-
- $this->timestampField = $timestampField;
- $this->style = $style;
- $this->table = $tableToDisplay;
- $this->urlRoot = $urlRoot;
- $form = new Form('', 'get');
- $searcher = new TableSearcher($this->table);
-
- //Get the first and last tiemstamps that should be displayed.
- $result = $db->query("SELECT min(".$timestampField."), " .
- "max(".$timestampField.") FROM ".$this->table);
- list($timeMin, $timeMax) = mysql_fetch_row($result);
- $this->dayMin = floor($timeMin / (3600*24));
- $this->dayMax = floor($timeMax / (3600*24));
-
- //See if a day has been selected.
- if(isset($_GET['day']) && is_numeric($_GET['day'])) {
- $this->day = $_GET['day'];
- } else {
- $this->day = $this->dayMax;
- }
-
- //Set the search parameters.
- $searcher->addField(new GreaterEqualSearch('day', $timestampField,
- 'convertDayToFirstSecond'));
- $searcher->addField(new LesserEqualSearch('day', $timestampField,
- 'convertDayToLastSecond'));
- $searcher->setSearchParameter('day', $this->day);
-
- $this->form = $form;
- $this->searcher = $searcher;
-
- //Add the year and month selection field.
- $this->addYearSelection();
- $this->addMonthSelection();
- $form->addField(new SubmitButton('submit', 'Display month'));
- }
-
- /**
- * Adds a form selection field to the form. The selection field only
- * contains years that include at least one day in the calendar's range.
- */
- private function addYearSelection() {
- //Calculate the range.
- $yearMin = getYear($this->dayMin);
- $yearMax = getYear($this->dayMax);
-
- $years = array();
- for($i = $yearMin; $i<=$yearMax; $i++) {
- $years[$i] = $i;
- }
- $form = $this->form;
- $form->addField(new Select('year', $years), 'Year');
- if(!$form->hasValue('year')) {
- if(isset($this->day)) {
- $form->setValue('year', getYear($this->day));
- } else {
- //There is no specified value, so use max year.
- $form->setValue('year', getYear($this->maxDay));
- }
- }
- }
-
- /**
- * Adds a form selection field to the form. The selection field only
- * contains months that include at least one day in the calendar's range.
- */
- private function addMonthSelection() {
- $yearMin = getYear($this->dayMin);
- $yearMax = getYear($this->dayMax);
-
- //Construct the month options that should be available.
- $monthsNumbers = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
- //Calculate the month max and min.
- $monthMin = 1;
- $monthMax = 12;
- $form = $this->form;
- if($form->getValue('year') == $yearMin) {
- //We have to limit the minimum month.
- $monthMin = getMonth($this->dayMin);
- }
- if($form->getValue('year') == $yearMax) {
- //We have to limit the maximum month.
- $monthMax = getMonth($this->dayMax);
- }
- //Slice the months with the min and max.
- $monthsNumbers = array_slice($monthsNumbers, $monthMin - 1,
- $monthMax - ($monthMin - 1));
- //Get the names.
- $monthNames = getAllMonthNames();
- $months = array();
- foreach($monthsNumbers as $monthNumber) {
- $months[$monthNumber] = $monthNames[$monthNumber-1];
- }
-
- //Construct and add the field.
- $form->addField(new Select('month', $months), 'Month');
- if(!$form->hasValue('month')) {
- if(isset($this->day)) {
- $form->setValue('month', getMonth($this->day));
- } else {
- //There is no specified value, so use the max month.
- $form->setValue('month', getMonth($this->dayMax));
- }
- }
- }
-
- /**
- * Adds a pair of fields to the calendar. If both fields are spceified then
- * they should be connected, i.e. their request names should be identical.
- * This can be used to further filter the results shown.
- * @param SearchField $searchField The search field that should be added,
- * the search field places restrictions on what will be displayed in
- * the calendar. I.e. only rows that satisfy the search fields are
- * shown.
- * @param FormField $formField An optional form field that should be
- * associated with $searchField. The form field is shown with the
- * calendar.
- * @param string $displayName An optional display name for the specified
- * form field.
- */
- public function addFieldPair($searchField, $formField = null, $displayName = '') {
- $this->searcher->addField($searchField);
- if($formField != null) {
- //Make sure to place it before the submit button.
- $this->form->removeField('submit');
- if($displayName != '') {
- $this->form->addField($formField, $displayName);
- } else {
- $this->form->addField($formField);
- }
- $this->form->addField(new SubmitButton('submit', 'Display'));
- }
- }
-
- /**
- * Gets the sql selector that represents the current configuration of the
- * calendar. The selector describes what rows match the calendar
- * selections.
- * @return string A sql statement describing matching rows.
- */
- public function getSqlSelector() {
- $searcher = $this->searcher;
- if($searcher->isValid()) {
- return $searcher->getSqlSelector();
- } else {
- return '';
- }
- }
-
- /**
- * Gets the XHTML code that represent the calendar.
- * @return string XHTML compliant code.
- */
- public function getCalendar() {
- global $db;
-
- //Get the limits of the day grid.
- $form = $this->form;
- $table = new Table();
- $table->setHeader(new Header(array('Mon', 'Tue', 'Wen', 'Thu',
- 'Fri', 'Sat', 'Sun')));
- $firstTime = mktime(0, 0, 0, $form->getValue('month'), 1,
- $form->getValue('year'));
- $lastTime = mktime(0, 0, 0, $form->getValue('month') + 1, 0,
- $form->getValue('year'));
- $firstDay = ceil($firstTime / (3600*24));
- $lastDay = ceil($lastTime / (3600*24));
- $days = getDaysInMonth($form->getValue('month'), $form->getValue('year'));
-
- //Skip forward until the first day.
- $row = array();
- for($i = date('w', $firstTime); $i>1; $i--) {
- $row[] = '';
- }
-
- //Construct the day grid.
- $haveRows = $this->doDaysHaveRows($firstDay, $lastDay);
- for($day = $firstDay; $day <= $lastDay; $day++) {
- //Divide the month into weeks.
- if(count($row) >= 7) {
- $table->addRow($row);
- $row = array();
- }
-
- //Add the representation for the day.
- $dayRepresentation = $day - $firstDay + 1;
- if($haveRows[$day]) {
- $class = '';
- if($day == $this->day) {
- $class = ' class="currentDay"';
- }
- $row[] = '<a href="'.$this->getDayUrl($day).'"' .
- $class . '>' . $dayRepresentation . '</a>';
- } else {
- //The day doesn't have any rows, so don't bother making a link.
- $row[] = $dayRepresentation;
- }
- }
- if(count($row) != 0) {
- //Fill the row.
- while(count($row) < 7) {
- $row[] = "";
- }
- $table->addRow($row);
- }
-
- //Construct the output
- $output = '<div class="calendar"';
- if(strlen($this->style) != 0) {
- $output .= ' style="'.$this->style.'"';
- }
- $output .= '>';
- $output .= $this->getMonthNavigation();
- $output .= '<div class="calendarDays">'.$table->getTable().'</div>';
- $output .= '<div class="calendarForm">'.$form->getForm().'</div>';
- $output .= '</div>';
- return $output;
- }
-
- /**
- * Gets an XHTML compliant month navigation. The navigation allows users
- * the move one month forward or backwards or to move to the first or last
- * month.
- * @return string XHTML compliant code describing navigation elements.
- */
- private function getMonthNavigation() {
- $form = $this->form;
- $day = floor(mktime(0, 0, 0, $form->getValue('month'), 2,
- $form->getValue('year')) / (3600*24));
- $timePrev = floor(mktime(0, 0, 0, $form->getValue('month')-1, 2,
- $form->getValue('year')) / (3600*24));
- $timeNext = floor(mktime(0, 0, 0, $form->getValue('month')+1, 2,
- $form->getValue('year')) / (3600*24));
- $linkPattern = '<a href="%s">%s</a>';
- $output = '<div class="calendarNav">';
-
- $minMonth = getMonth($this->dayMin);
- $maxMonth = getMonth($this->dayMax);
- $minYear = getYear($this->dayMin);
- $maxYear = getYear($this->dayMax);
- $nextMonth = getMonth($timeNext);
- $nextYear = getYear($timeNext);
- $prevMonth = getMonth($timePrev);
- $prevYear = getYear($timePrev);
- $isFirstMonth = $prevYear == $minYear && $prevMonth < $minMonth;
- $isLastMonth = $nextYear == $maxYear && $nextMonth > $maxMonth;
-
- //<<
- if(!$isFirstMonth) {
- $output .= sprintf(
- $linkPattern,
- $this->getMonthUrl($minMonth, $minYear),
- '<<'
- );
- } else {
- $output .= '<span><<</span>';
- }
-
- //<
- if(!$isFirstMonth) {
- $output .= sprintf(
- $linkPattern,
- $this->getMonthUrl($prevMonth, $prevYear),
- '<'
- );
- } else {
- $output .= '<span><</span>';
- }
-
- //Current month.
- $output .= '<span class="calendarMonth">'.date(DATE_CALENDAR_NAV, convertDayToFirstSecond($day)).'</span>';
-
- //>
- if(!$isLastMonth) {
- $output .= sprintf(
- $linkPattern,
- $this->getMonthUrl($nextMonth, $nextYear),
- '>'
- );
- } else {
- $output .= '<span>></span>';
- }
-
- //>>
- if(!$isLastMonth) {
- $output .= sprintf(
- $linkPattern,
- $this->getMonthUrl($maxMonth, $maxYear),
- '>>'
- );
- } else {
- $output .= '<span>>></span>';
- }
-
- return $output.'</div>';
- }
-
-
-
- /**
- * Gets the calendar's selected timestamp. The timestamp has a resolution
- * of 3600*24 seconds, i.e. days.
- * @return integer A unix timestamp.
- */
- public function getSelectedTimestamp() {
- return $this->day * 3600 * 24 + 1;
- }
-
- /**
- * Checks if the user has selected a day or not in the calendar. If the
- * user has not then getting results from the calendar should not be
- * attempted.
- * @return boolean True if the user has selected a day, false otherwise.
- */
- public function hasUserValue() {
- return isset($_GET['day']);
- }
-
- /**
- * Check if an specified interval of days have updates. Optionally
- * constraints can be placed on the public function so that it only checks for
- * updates of a specific type. This public function should be concidered private.
- * @param integer $dayMin The day, as an unix timestamp converted into
- days, which represents the first day in the interval that the
- * public function should look at.
- * @param integer $dayMax The day, as an unix timestamp converted into
- * days, which represents the last day in the interval that the
- * public function should look at.
- * @return array An array with the day index as key and a boolean value
- * as value. The boolean value is true if the corresponding day has
- * rows, false otherwise.
- */
- private function doDaysHaveRows($dayMin, $dayMax) {
- global $db;
-
- //Get all timestamps within $dayMin and $dayMax.
- $searcher = $this->searcher;
- $sqlSelector = $searcher->getSqlSelector();
- $timestampsFrom = array(convertDayToFirstSecond($this->day),
- convertDayToLastSecond($this->day));
- $timestampsTo = array(convertDayToFirstSecond($dayMin),
- convertDayToLastSecond($dayMax));
- $sqlSelector = str_replace($timestampsFrom, $timestampsTo,
- $sqlSelector);
- $rows = $db->querySelect($this->table,
- array($this->timestampField), $sqlSelector);
-
- $hasRows = array();
- //Mark the rows that have entries.
- foreach($rows as $row) {
- $time = $row[$this->timestampField];
- $time += date("Z", $time); //Compensate for the timezone.
- $hasRows[floor($time/ (3600*24))] = true;
- }
- //Make sure that all days have values.
- for($day = $dayMin; $day <= $dayMax; $day++) {
- if(!isset($hasRows[$day])) {
- $hasRows[$day] = false;
- }
- }
- return $hasRows;
- }
-
- /**
- * Gets the url to a specified day in the calendar.
- * @param integer $day The unix timestamp in days for the day that the url
- * should represent.
- * @return string An encoded URL, may be relative to the current page.
- */
- protected function getDayUrl($day) {
- return $this->urlRoot.'?day='.$day;
- }
-
- /**
- * Gets the url to a specified month in the calendar.
- * @param integer $month The month in the year (1-12) that the url should
- * lead to.
- * @param integer $year the year (four digits) that the url should lead to.
- * @return string An encoded URL, may b relative to the current page.
- */
- protected function getMonthUrl($month, $year) {
- return sprintf(SELF.'?month=%d&year=%d', $month, $year);
- }
- }
-
- /**
- * Describes a calendar that link to aliases in a ./year/month/day format.
- * @author Andreas Launila
- * @package com.lokorin.lokorin.lib
- */
- class AliasCalendar extends Calendar {
- /**
- * Constructor for AliasCalendar.
- * @param string $tableToDisplay The name of the table that the calendar
- * should display.
- * @param string $style An optional parameter to specify custom style code
- * to use for the calendars "style" attribute.
- * @param string $urlRoot The root that should be used for all urls
- * presented by the calendar.
- * @param string $timestampField The name of the field in the specified
- * table that contains the unix timestamps.
- */
- public function __construct($tableToDisplay, $timestampField, $urlRoot, $style = '') {
- parent::__construct($tableToDisplay, $timestampField, $urlRoot, $style);
- }
-
- /**
- * @see com.lokorin.dfcrafters.lib.Calendar.getDayUrl($day)
- */
- protected function getDayUrl($days) {
- $timestamp = convertDayToFirstSecond($days);
- return parent::$this->urlRoot.date('Y', $timestamp)."/".date('n', $timestamp)."/".date('j', $timestamp);
- }
-
- /**
- * @see com.lokorin.dfcrafters.lib.Calendar.getMonthUrl($month,$year)
- */
- protected function getMonthUrl($month, $year) {
- return parent::$this->urlRoot.$year."/".$month;
- }
- }
-
- ?>