com.lokorin.lokorin.lib
[ class tree: com.lokorin.lokorin.lib ] [ index: com.lokorin.lokorin.lib ] [ all elements ]

Source for file lib_calendar.php

Documentation is available at lib_calendar.php

  1. <?php
  2. /*
  3. * Lokorin.com
  4. * Copyright 2004-2006
  5. * Licensed under the GNU LGPL. See COPYING for full terms.
  6. */
  7. /**
  8. * A library that contains functions and classes related to calendars.
  9. * @author Andreas Launila
  10. * @version $Revision: 1.15 $
  11. * @package com.lokorin.lokorin.lib
  12. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  13. */
  14.  
  15. /**
  16. * For access to common constants and functions.
  17. */
  18. require_once('common.php');
  19. doInclude('lib_forms');
  20. doInclude('lib_search');
  21. doInclude('lib_tables');
  22. CssHandler::getInstance()->includeCss('calendar');
  23.  
  24. define('DATE_CALENDAR_NAV', 'F Y');
  25.  
  26. /**
  27. * Gets the number of days in a specified month.
  28. * @param integer $month The number of the month of the year, has to be in the
  29. * range [1, 12].
  30. * @param integer $year The four digit representation of the year in question.
  31. * @return integer The number of days that occur in the speicified month of the
  32. * specified year.
  33. * @access private
  34. */
  35. function getDaysInMonth($month, $year) {
  36. $daysInMonths = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
  37. if($month != 2) {
  38. return($daysInMonths[$month-1]);
  39. } else {
  40. return(checkdate($month, 29, $year) ? 29 : 28);
  41. }
  42. }
  43.  
  44. /**
  45. * Gets the names of all the years months.
  46. * @return array All the months' names in order, starting with January.
  47. * @access private
  48. */
  49. function getAllMonthNames() {
  50. return array('January', 'February', 'March', 'April', 'May', 'June',
  51. 'July', 'August', 'September', 'October', 'November', 'December');
  52. }
  53.  
  54. /**
  55. * Internal function to convert days to timestamps equal to the last
  56. * second of the specified day.
  57. * @param integer day The unix time day that should be converted.
  58. * @return integer A unix timestamp representing the last second of
  59. * a day.
  60. * @access private
  61. */
  62. function convertDayToLastSecond($day) {
  63. return ($day+1) * 3600 * 24 - 1;
  64. }
  65.  
  66. /**
  67. * Internal function to convert days to timestamps equal to the first
  68. * second of the specified day.
  69. * @param integer day The unix time day that should be converted.
  70. * @return integer A unix timestamp representing the first second of
  71. * a day.
  72. * @access private
  73. */
  74. function convertDayToFirstSecond($day) {
  75. return $day * 3600 * 24;
  76. }
  77.  
  78. /**
  79. * Gets the month of the year that a specified day is included in.
  80. * @param integer $day The day, as an unix timestamp converted into
  81. * days, that represents the day for which the month should be
  82. * retrieved.
  83. * @return integer The months number (1-12).
  84. * @access private
  85. */
  86. function getMonth($day) {
  87. return date('n', $day * 3600 * 24 + 1);
  88. }
  89.  
  90. /**
  91. * Gets the year that a specified day is included in.
  92. * @param integer $day The day, as an unix timestamp converted into
  93. * days, that represents the day for which the yearshould be
  94. * retrieved.
  95. * @return integer The year (four digits).
  96. * @access private
  97. */
  98. function getYear($day) {
  99. return date('Y', $day * 3600 * 24 + 1);
  100. }
  101.  
  102. /**
  103. * Describes a calendar that displays a database table as a calendar with
  104. * entries. Users can select specific days from the calendar and view the
  105. * connected table rows. The calendar gives the user drop downs to select year
  106. * and month, the days are represented in a grid. Additional filters can also
  107. * be placed on the calendar.
  108. * @author Andreas Launila
  109. * @package com.lokorin.lokorin.lib
  110. */
  111. class Calendar {
  112. /**
  113. * The name of the table that the calendar should display.
  114. * @var string
  115. */
  116. private $table;
  117. /**
  118. * The table searcher that should be used by the calendar.
  119. * @var TableSearcher
  120. */
  121. private $searcher;
  122. /**
  123. * The form that should be displayed to the user along with the calendar
  124. * grid.
  125. * @var Form
  126. */
  127. private $form;
  128. /**
  129. * The currently selected unix timestamp as days. I.e. this is not day of
  130. * the week, this is timestamp/(3600*24).
  131. * @var integer
  132. */
  133. private $day;
  134. /**
  135. * The text that should be included in the calendar's style attribute.
  136. * @var string
  137. */
  138. private $style;
  139. /**
  140. * The name of the table field that contains the unix timestamp values.
  141. * @var string
  142. */
  143. private $timestampField;
  144. /**
  145. * The last day, as an unix timestamp converted into days, that the
  146. * calendar should display.
  147. * @var integer
  148. */
  149. private $dayMax;
  150. /**
  151. * The first day, as an unix timestamp converted into days, that the
  152. * calendar should display.
  153. * @var integer
  154. */
  155. private $dayMin;
  156. /**
  157. * The root that should be used for all link urls.
  158. * @var string
  159. */
  160. protected $urlRoot;
  161. /**
  162. * Constructor for Calendar.
  163. * @param string $tableToDisplay The name of the table that the calendar
  164. * should display.
  165. * @param string $style An optional parameter to specify custom style code
  166. * to use for the calendars "style" attribute.
  167. * @param string $urlRoot The root that should be used for all urls
  168. * presented by the calendar.
  169. * @param string $timestampField The name of the field in the specified
  170. * table that contains the unix timestamps.
  171. */
  172. public function __construct($tableToDisplay, $timestampField,
  173. $urlRoot = SELF, $style = '') {
  174. global $db;
  175. $this->timestampField = $timestampField;
  176. $this->style = $style;
  177. $this->table = $tableToDisplay;
  178. $this->urlRoot = $urlRoot;
  179. $form = new Form('', 'get');
  180. $searcher = new TableSearcher($this->table);
  181. //Get the first and last tiemstamps that should be displayed.
  182. $result = $db->query("SELECT min(".$timestampField."), " .
  183. "max(".$timestampField.") FROM ".$this->table);
  184. list($timeMin, $timeMax) = mysql_fetch_row($result);
  185. $this->dayMin = floor($timeMin / (3600*24));
  186. $this->dayMax = floor($timeMax / (3600*24));
  187. //See if a day has been selected.
  188. if(isset($_GET['day']) && is_numeric($_GET['day'])) {
  189. $this->day = $_GET['day'];
  190. } else {
  191. $this->day = $this->dayMax;
  192. }
  193. //Set the search parameters.
  194. $searcher->addField(new GreaterEqualSearch('day', $timestampField,
  195. 'convertDayToFirstSecond'));
  196. $searcher->addField(new LesserEqualSearch('day', $timestampField,
  197. 'convertDayToLastSecond'));
  198. $searcher->setSearchParameter('day', $this->day);
  199. $this->form = $form;
  200. $this->searcher = $searcher;
  201. //Add the year and month selection field.
  202. $this->addYearSelection();
  203. $this->addMonthSelection();
  204. $form->addField(new SubmitButton('submit', 'Display month'));
  205. }
  206. /**
  207. * Adds a form selection field to the form. The selection field only
  208. * contains years that include at least one day in the calendar's range.
  209. */
  210. private function addYearSelection() {
  211. //Calculate the range.
  212. $yearMin = getYear($this->dayMin);
  213. $yearMax = getYear($this->dayMax);
  214. $years = array();
  215. for($i = $yearMin; $i<=$yearMax; $i++) {
  216. $years[$i] = $i;
  217. }
  218. $form = $this->form;
  219. $form->addField(new Select('year', $years), 'Year');
  220. if(!$form->hasValue('year')) {
  221. if(isset($this->day)) {
  222. $form->setValue('year', getYear($this->day));
  223. } else {
  224. //There is no specified value, so use max year.
  225. $form->setValue('year', getYear($this->maxDay));
  226. }
  227. }
  228. }
  229. /**
  230. * Adds a form selection field to the form. The selection field only
  231. * contains months that include at least one day in the calendar's range.
  232. */
  233. private function addMonthSelection() {
  234. $yearMin = getYear($this->dayMin);
  235. $yearMax = getYear($this->dayMax);
  236. //Construct the month options that should be available.
  237. $monthsNumbers = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
  238. //Calculate the month max and min.
  239. $monthMin = 1;
  240. $monthMax = 12;
  241. $form = $this->form;
  242. if($form->getValue('year') == $yearMin) {
  243. //We have to limit the minimum month.
  244. $monthMin = getMonth($this->dayMin);
  245. }
  246. if($form->getValue('year') == $yearMax) {
  247. //We have to limit the maximum month.
  248. $monthMax = getMonth($this->dayMax);
  249. }
  250. //Slice the months with the min and max.
  251. $monthsNumbers = array_slice($monthsNumbers, $monthMin - 1,
  252. $monthMax - ($monthMin - 1));
  253. //Get the names.
  254. $monthNames = getAllMonthNames();
  255. $months = array();
  256. foreach($monthsNumbers as $monthNumber) {
  257. $months[$monthNumber] = $monthNames[$monthNumber-1];
  258. }
  259. //Construct and add the field.
  260. $form->addField(new Select('month', $months), 'Month');
  261. if(!$form->hasValue('month')) {
  262. if(isset($this->day)) {
  263. $form->setValue('month', getMonth($this->day));
  264. } else {
  265. //There is no specified value, so use the max month.
  266. $form->setValue('month', getMonth($this->dayMax));
  267. }
  268. }
  269. }
  270. /**
  271. * Adds a pair of fields to the calendar. If both fields are spceified then
  272. * they should be connected, i.e. their request names should be identical.
  273. * This can be used to further filter the results shown.
  274. * @param SearchField $searchField The search field that should be added,
  275. * the search field places restrictions on what will be displayed in
  276. * the calendar. I.e. only rows that satisfy the search fields are
  277. * shown.
  278. * @param FormField $formField An optional form field that should be
  279. * associated with $searchField. The form field is shown with the
  280. * calendar.
  281. * @param string $displayName An optional display name for the specified
  282. * form field.
  283. */
  284. public function addFieldPair($searchField, $formField = null, $displayName = '') {
  285. $this->searcher->addField($searchField);
  286. if($formField != null) {
  287. //Make sure to place it before the submit button.
  288. $this->form->removeField('submit');
  289. if($displayName != '') {
  290. $this->form->addField($formField, $displayName);
  291. } else {
  292. $this->form->addField($formField);
  293. }
  294. $this->form->addField(new SubmitButton('submit', 'Display'));
  295. }
  296. }
  297. /**
  298. * Gets the sql selector that represents the current configuration of the
  299. * calendar. The selector describes what rows match the calendar
  300. * selections.
  301. * @return string A sql statement describing matching rows.
  302. */
  303. public function getSqlSelector() {
  304. $searcher = $this->searcher;
  305. if($searcher->isValid()) {
  306. return $searcher->getSqlSelector();
  307. } else {
  308. return '';
  309. }
  310. }
  311. /**
  312. * Gets the XHTML code that represent the calendar.
  313. * @return string XHTML compliant code.
  314. */
  315. public function getCalendar() {
  316. global $db;
  317. //Get the limits of the day grid.
  318. $form = $this->form;
  319. $table = new Table();
  320. $table->setHeader(new Header(array('Mon', 'Tue', 'Wen', 'Thu',
  321. 'Fri', 'Sat', 'Sun')));
  322. $firstTime = mktime(0, 0, 0, $form->getValue('month'), 1,
  323. $form->getValue('year'));
  324. $lastTime = mktime(0, 0, 0, $form->getValue('month') + 1, 0,
  325. $form->getValue('year'));
  326. $firstDay = ceil($firstTime / (3600*24));
  327. $lastDay = ceil($lastTime / (3600*24));
  328. $days = getDaysInMonth($form->getValue('month'), $form->getValue('year'));
  329.  
  330. //Skip forward until the first day.
  331. $row = array();
  332. for($i = date('w', $firstTime); $i>1; $i--) {
  333. $row[] = '';
  334. }
  335. //Construct the day grid.
  336. $haveRows = $this->doDaysHaveRows($firstDay, $lastDay);
  337. for($day = $firstDay; $day <= $lastDay; $day++) {
  338. //Divide the month into weeks.
  339. if(count($row) >= 7) {
  340. $table->addRow($row);
  341. $row = array();
  342. }
  343.  
  344. //Add the representation for the day.
  345. $dayRepresentation = $day - $firstDay + 1;
  346. if($haveRows[$day]) {
  347. $class = '';
  348. if($day == $this->day) {
  349. $class = ' class="currentDay"';
  350. }
  351. $row[] = '<a href="'.$this->getDayUrl($day).'"' .
  352. $class . '>' . $dayRepresentation . '</a>';
  353. } else {
  354. //The day doesn't have any rows, so don't bother making a link.
  355. $row[] = $dayRepresentation;
  356. }
  357. }
  358. if(count($row) != 0) {
  359. //Fill the row.
  360. while(count($row) < 7) {
  361. $row[] = "";
  362. }
  363. $table->addRow($row);
  364. }
  365. //Construct the output
  366. $output = '<div class="calendar"';
  367. if(strlen($this->style) != 0) {
  368. $output .= ' style="'.$this->style.'"';
  369. }
  370. $output .= '>';
  371. $output .= $this->getMonthNavigation();
  372. $output .= '<div class="calendarDays">'.$table->getTable().'</div>';
  373. $output .= '<div class="calendarForm">'.$form->getForm().'</div>';
  374. $output .= '</div>';
  375. return $output;
  376. }
  377. /**
  378. * Gets an XHTML compliant month navigation. The navigation allows users
  379. * the move one month forward or backwards or to move to the first or last
  380. * month.
  381. * @return string XHTML compliant code describing navigation elements.
  382. */
  383. private function getMonthNavigation() {
  384. $form = $this->form;
  385. $day = floor(mktime(0, 0, 0, $form->getValue('month'), 2,
  386. $form->getValue('year')) / (3600*24));
  387. $timePrev = floor(mktime(0, 0, 0, $form->getValue('month')-1, 2,
  388. $form->getValue('year')) / (3600*24));
  389. $timeNext = floor(mktime(0, 0, 0, $form->getValue('month')+1, 2,
  390. $form->getValue('year')) / (3600*24));
  391. $linkPattern = '<a href="%s">%s</a>';
  392. $output = '<div class="calendarNav">';
  393. $minMonth = getMonth($this->dayMin);
  394. $maxMonth = getMonth($this->dayMax);
  395. $minYear = getYear($this->dayMin);
  396. $maxYear = getYear($this->dayMax);
  397. $nextMonth = getMonth($timeNext);
  398. $nextYear = getYear($timeNext);
  399. $prevMonth = getMonth($timePrev);
  400. $prevYear = getYear($timePrev);
  401. $isFirstMonth = $prevYear == $minYear && $prevMonth < $minMonth;
  402. $isLastMonth = $nextYear == $maxYear && $nextMonth > $maxMonth;
  403. //<<
  404. if(!$isFirstMonth) {
  405. $output .= sprintf(
  406. $linkPattern,
  407. $this->getMonthUrl($minMonth, $minYear),
  408. '&lt;&lt;'
  409. );
  410. } else {
  411. $output .= '<span>&lt;&lt;</span>';
  412. }
  413. //<
  414. if(!$isFirstMonth) {
  415. $output .= sprintf(
  416. $linkPattern,
  417. $this->getMonthUrl($prevMonth, $prevYear),
  418. '&lt;'
  419. );
  420. } else {
  421. $output .= '<span>&lt;</span>';
  422. }
  423. //Current month.
  424. $output .= '<span class="calendarMonth">'.date(DATE_CALENDAR_NAV, convertDayToFirstSecond($day)).'</span>';
  425. //>
  426. if(!$isLastMonth) {
  427. $output .= sprintf(
  428. $linkPattern,
  429. $this->getMonthUrl($nextMonth, $nextYear),
  430. '&gt;'
  431. );
  432. } else {
  433. $output .= '<span>&gt;</span>';
  434. }
  435. //>>
  436. if(!$isLastMonth) {
  437. $output .= sprintf(
  438. $linkPattern,
  439. $this->getMonthUrl($maxMonth, $maxYear),
  440. '&gt;&gt;'
  441. );
  442. } else {
  443. $output .= '<span>&gt;&gt;</span>';
  444. }
  445. return $output.'</div>';
  446. }
  447. /**
  448. * Gets the calendar's selected timestamp. The timestamp has a resolution
  449. * of 3600*24 seconds, i.e. days.
  450. * @return integer A unix timestamp.
  451. */
  452. public function getSelectedTimestamp() {
  453. return $this->day * 3600 * 24 + 1;
  454. }
  455. /**
  456. * Checks if the user has selected a day or not in the calendar. If the
  457. * user has not then getting results from the calendar should not be
  458. * attempted.
  459. * @return boolean True if the user has selected a day, false otherwise.
  460. */
  461. public function hasUserValue() {
  462. return isset($_GET['day']);
  463. }
  464. /**
  465. * Check if an specified interval of days have updates. Optionally
  466. * constraints can be placed on the public function so that it only checks for
  467. * updates of a specific type. This public function should be concidered private.
  468. * @param integer $dayMin The day, as an unix timestamp converted into
  469. days, which represents the first day in the interval that the
  470. * public function should look at.
  471. * @param integer $dayMax The day, as an unix timestamp converted into
  472. * days, which represents the last day in the interval that the
  473. * public function should look at.
  474. * @return array An array with the day index as key and a boolean value
  475. * as value. The boolean value is true if the corresponding day has
  476. * rows, false otherwise.
  477. */
  478. private function doDaysHaveRows($dayMin, $dayMax) {
  479. global $db;
  480. //Get all timestamps within $dayMin and $dayMax.
  481. $searcher = $this->searcher;
  482. $sqlSelector = $searcher->getSqlSelector();
  483. $timestampsFrom = array(convertDayToFirstSecond($this->day),
  484. convertDayToLastSecond($this->day));
  485. $timestampsTo = array(convertDayToFirstSecond($dayMin),
  486. convertDayToLastSecond($dayMax));
  487. $sqlSelector = str_replace($timestampsFrom, $timestampsTo,
  488. $sqlSelector);
  489. $rows = $db->querySelect($this->table,
  490. array($this->timestampField), $sqlSelector);
  491. $hasRows = array();
  492. //Mark the rows that have entries.
  493. foreach($rows as $row) {
  494. $time = $row[$this->timestampField];
  495. $time += date("Z", $time); //Compensate for the timezone.
  496. $hasRows[floor($time/ (3600*24))] = true;
  497. }
  498. //Make sure that all days have values.
  499. for($day = $dayMin; $day <= $dayMax; $day++) {
  500. if(!isset($hasRows[$day])) {
  501. $hasRows[$day] = false;
  502. }
  503. }
  504. return $hasRows;
  505. }
  506. /**
  507. * Gets the url to a specified day in the calendar.
  508. * @param integer $day The unix timestamp in days for the day that the url
  509. * should represent.
  510. * @return string An encoded URL, may be relative to the current page.
  511. */
  512. protected function getDayUrl($day) {
  513. return $this->urlRoot.'?day='.$day;
  514. }
  515. /**
  516. * Gets the url to a specified month in the calendar.
  517. * @param integer $month The month in the year (1-12) that the url should
  518. * lead to.
  519. * @param integer $year the year (four digits) that the url should lead to.
  520. * @return string An encoded URL, may b relative to the current page.
  521. */
  522. protected function getMonthUrl($month, $year) {
  523. return sprintf(SELF.'?month=%d&amp;year=%d', $month, $year);
  524. }
  525. }
  526.  
  527. /**
  528. * Describes a calendar that link to aliases in a ./year/month/day format.
  529. * @author Andreas Launila
  530. * @package com.lokorin.lokorin.lib
  531. */
  532. class AliasCalendar extends Calendar {
  533. /**
  534. * Constructor for AliasCalendar.
  535. * @param string $tableToDisplay The name of the table that the calendar
  536. * should display.
  537. * @param string $style An optional parameter to specify custom style code
  538. * to use for the calendars "style" attribute.
  539. * @param string $urlRoot The root that should be used for all urls
  540. * presented by the calendar.
  541. * @param string $timestampField The name of the field in the specified
  542. * table that contains the unix timestamps.
  543. */
  544. public function __construct($tableToDisplay, $timestampField, $urlRoot, $style = '') {
  545. parent::__construct($tableToDisplay, $timestampField, $urlRoot, $style);
  546. }
  547. /**
  548. * @see com.lokorin.dfcrafters.lib.Calendar.getDayUrl($day)
  549. */
  550. protected function getDayUrl($days) {
  551. $timestamp = convertDayToFirstSecond($days);
  552. return parent::$this->urlRoot.date('Y', $timestamp)."/".date('n', $timestamp)."/".date('j', $timestamp);
  553. }
  554. /**
  555. * @see com.lokorin.dfcrafters.lib.Calendar.getMonthUrl($month,$year)
  556. */
  557. protected function getMonthUrl($month, $year) {
  558. return parent::$this->urlRoot.$year."/".$month;
  559. }
  560. }
  561.  
  562. ?>

Documentation generated on Sun, 16 Apr 2006 21:03:12 +0200 by phpDocumentor 1.3.0RC4