You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

546 lines
15 KiB

7 years ago
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP
  6. *
  7. * This content is released under the MIT License (MIT)
  8. *
  9. * Copyright (c) 2014 - 2017, British Columbia Institute of Technology
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in
  19. * all copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  27. * THE SOFTWARE.
  28. *
  29. * @package CodeIgniter
  30. * @author EllisLab Dev Team
  31. * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
  32. * @copyright Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/)
  33. * @license http://opensource.org/licenses/MIT MIT License
  34. * @link https://codeigniter.com
  35. * @since Version 1.0.0
  36. * @filesource
  37. */
  38. defined('BASEPATH') OR exit('No direct script access allowed');
  39. /**
  40. * CodeIgniter Calendar Class
  41. *
  42. * This class enables the creation of calendars
  43. *
  44. * @package CodeIgniter
  45. * @subpackage Libraries
  46. * @category Libraries
  47. * @author EllisLab Dev Team
  48. * @link https://codeigniter.com/user_guide/libraries/calendar.html
  49. */
  50. class CI_Calendar {
  51. /**
  52. * Calendar layout template
  53. *
  54. * @var mixed
  55. */
  56. public $template = '';
  57. /**
  58. * Replacements array for template
  59. *
  60. * @var array
  61. */
  62. public $replacements = array();
  63. /**
  64. * Day of the week to start the calendar on
  65. *
  66. * @var string
  67. */
  68. public $start_day = 'sunday';
  69. /**
  70. * How to display months
  71. *
  72. * @var string
  73. */
  74. public $month_type = 'long';
  75. /**
  76. * How to display names of days
  77. *
  78. * @var string
  79. */
  80. public $day_type = 'abr';
  81. /**
  82. * Whether to show next/prev month links
  83. *
  84. * @var bool
  85. */
  86. public $show_next_prev = FALSE;
  87. /**
  88. * Url base to use for next/prev month links
  89. *
  90. * @var bool
  91. */
  92. public $next_prev_url = '';
  93. /**
  94. * Show days of other months
  95. *
  96. * @var bool
  97. */
  98. public $show_other_days = FALSE;
  99. // --------------------------------------------------------------------
  100. /**
  101. * CI Singleton
  102. *
  103. * @var object
  104. */
  105. protected $CI;
  106. // --------------------------------------------------------------------
  107. /**
  108. * Class constructor
  109. *
  110. * Loads the calendar language file and sets the default time reference.
  111. *
  112. * @uses CI_Lang::$is_loaded
  113. *
  114. * @param array $config Calendar options
  115. * @return void
  116. */
  117. public function __construct($config = array())
  118. {
  119. $this->CI =& get_instance();
  120. $this->CI->lang->load('calendar');
  121. empty($config) OR $this->initialize($config);
  122. log_message('info', 'Calendar Class Initialized');
  123. }
  124. // --------------------------------------------------------------------
  125. /**
  126. * Initialize the user preferences
  127. *
  128. * Accepts an associative array as input, containing display preferences
  129. *
  130. * @param array config preferences
  131. * @return CI_Calendar
  132. */
  133. public function initialize($config = array())
  134. {
  135. foreach ($config as $key => $val)
  136. {
  137. if (isset($this->$key))
  138. {
  139. $this->$key = $val;
  140. }
  141. }
  142. // Set the next_prev_url to the controller if required but not defined
  143. if ($this->show_next_prev === TRUE && empty($this->next_prev_url))
  144. {
  145. $this->next_prev_url = $this->CI->config->site_url($this->CI->router->class.'/'.$this->CI->router->method);
  146. }
  147. return $this;
  148. }
  149. // --------------------------------------------------------------------
  150. /**
  151. * Generate the calendar
  152. *
  153. * @param int the year
  154. * @param int the month
  155. * @param array the data to be shown in the calendar cells
  156. * @return string
  157. */
  158. public function generate($year = '', $month = '', $data = array())
  159. {
  160. $local_time = time();
  161. // Set and validate the supplied month/year
  162. if (empty($year))
  163. {
  164. $year = date('Y', $local_time);
  165. }
  166. elseif (strlen($year) === 1)
  167. {
  168. $year = '200'.$year;
  169. }
  170. elseif (strlen($year) === 2)
  171. {
  172. $year = '20'.$year;
  173. }
  174. if (empty($month))
  175. {
  176. $month = date('m', $local_time);
  177. }
  178. elseif (strlen($month) === 1)
  179. {
  180. $month = '0'.$month;
  181. }
  182. $adjusted_date = $this->adjust_date($month, $year);
  183. $month = $adjusted_date['month'];
  184. $year = $adjusted_date['year'];
  185. // Determine the total days in the month
  186. $total_days = $this->get_total_days($month, $year);
  187. // Set the starting day of the week
  188. $start_days = array('sunday' => 0, 'monday' => 1, 'tuesday' => 2, 'wednesday' => 3, 'thursday' => 4, 'friday' => 5, 'saturday' => 6);
  189. $start_day = isset($start_days[$this->start_day]) ? $start_days[$this->start_day] : 0;
  190. // Set the starting day number
  191. $local_date = mktime(12, 0, 0, $month, 1, $year);
  192. $date = getdate($local_date);
  193. $day = $start_day + 1 - $date['wday'];
  194. while ($day > 1)
  195. {
  196. $day -= 7;
  197. }
  198. // Set the current month/year/day
  199. // We use this to determine the "today" date
  200. $cur_year = date('Y', $local_time);
  201. $cur_month = date('m', $local_time);
  202. $cur_day = date('j', $local_time);
  203. $is_current_month = ($cur_year == $year && $cur_month == $month);
  204. // Generate the template data array
  205. $this->parse_template();
  206. // Begin building the calendar output
  207. $out = $this->replacements['table_open']."\n\n".$this->replacements['heading_row_start']."\n";
  208. // "previous" month link
  209. if ($this->show_next_prev === TRUE)
  210. {
  211. // Add a trailing slash to the URL if needed
  212. $this->next_prev_url = preg_replace('/(.+?)\/*$/', '\\1/', $this->next_prev_url);
  213. $adjusted_date = $this->adjust_date($month - 1, $year);
  214. $out .= str_replace('{previous_url}', $this->next_prev_url.$adjusted_date['year'].'/'.$adjusted_date['month'], $this->replacements['heading_previous_cell'])."\n";
  215. }
  216. // Heading containing the month/year
  217. $colspan = ($this->show_next_prev === TRUE) ? 5 : 7;
  218. $this->replacements['heading_title_cell'] = str_replace('{colspan}', $colspan,
  219. str_replace('{heading}', $this->get_month_name($month).'&nbsp;'.$year, $this->replacements['heading_title_cell']));
  220. $out .= $this->replacements['heading_title_cell']."\n";
  221. // "next" month link
  222. if ($this->show_next_prev === TRUE)
  223. {
  224. $adjusted_date = $this->adjust_date($month + 1, $year);
  225. $out .= str_replace('{next_url}', $this->next_prev_url.$adjusted_date['year'].'/'.$adjusted_date['month'], $this->replacements['heading_next_cell']);
  226. }
  227. $out .= "\n".$this->replacements['heading_row_end']."\n\n"
  228. // Write the cells containing the days of the week
  229. .$this->replacements['week_row_start']."\n";
  230. $day_names = $this->get_day_names();
  231. for ($i = 0; $i < 7; $i ++)
  232. {
  233. $out .= str_replace('{week_day}', $day_names[($start_day + $i) %7], $this->replacements['week_day_cell']);
  234. }
  235. $out .= "\n".$this->replacements['week_row_end']."\n";
  236. // Build the main body of the calendar
  237. while ($day <= $total_days)
  238. {
  239. $out .= "\n".$this->replacements['cal_row_start']."\n";
  240. for ($i = 0; $i < 7; $i++)
  241. {
  242. if ($day > 0 && $day <= $total_days)
  243. {
  244. $out .= ($is_current_month === TRUE && $day == $cur_day) ? $this->replacements['cal_cell_start_today'] : $this->replacements['cal_cell_start'];
  245. if (isset($data[$day]))
  246. {
  247. // Cells with content
  248. $temp = ($is_current_month === TRUE && $day == $cur_day) ?
  249. $this->replacements['cal_cell_content_today'] : $this->replacements['cal_cell_content'];
  250. $out .= str_replace(array('{content}', '{day}'), array($data[$day], $day), $temp);
  251. }
  252. else
  253. {
  254. // Cells with no content
  255. $temp = ($is_current_month === TRUE && $day == $cur_day) ?
  256. $this->replacements['cal_cell_no_content_today'] : $this->replacements['cal_cell_no_content'];
  257. $out .= str_replace('{day}', $day, $temp);
  258. }
  259. $out .= ($is_current_month === TRUE && $day == $cur_day) ? $this->replacements['cal_cell_end_today'] : $this->replacements['cal_cell_end'];
  260. }
  261. elseif ($this->show_other_days === TRUE)
  262. {
  263. $out .= $this->replacements['cal_cell_start_other'];
  264. if ($day <= 0)
  265. {
  266. // Day of previous month
  267. $prev_month = $this->adjust_date($month - 1, $year);
  268. $prev_month_days = $this->get_total_days($prev_month['month'], $prev_month['year']);
  269. $out .= str_replace('{day}', $prev_month_days + $day, $this->replacements['cal_cell_other']);
  270. }
  271. else
  272. {
  273. // Day of next month
  274. $out .= str_replace('{day}', $day - $total_days, $this->replacements['cal_cell_other']);
  275. }
  276. $out .= $this->replacements['cal_cell_end_other'];
  277. }
  278. else
  279. {
  280. // Blank cells
  281. $out .= $this->replacements['cal_cell_start'].$this->replacements['cal_cell_blank'].$this->replacements['cal_cell_end'];
  282. }
  283. $day++;
  284. }
  285. $out .= "\n".$this->replacements['cal_row_end']."\n";
  286. }
  287. return $out .= "\n".$this->replacements['table_close'];
  288. }
  289. // --------------------------------------------------------------------
  290. /**
  291. * Get Month Name
  292. *
  293. * Generates a textual month name based on the numeric
  294. * month provided.
  295. *
  296. * @param int the month
  297. * @return string
  298. */
  299. public function get_month_name($month)
  300. {
  301. if ($this->month_type === 'short')
  302. {
  303. $month_names = array('01' => 'cal_jan', '02' => 'cal_feb', '03' => 'cal_mar', '04' => 'cal_apr', '05' => 'cal_may', '06' => 'cal_jun', '07' => 'cal_jul', '08' => 'cal_aug', '09' => 'cal_sep', '10' => 'cal_oct', '11' => 'cal_nov', '12' => 'cal_dec');
  304. }
  305. else
  306. {
  307. $month_names = array('01' => 'cal_january', '02' => 'cal_february', '03' => 'cal_march', '04' => 'cal_april', '05' => 'cal_mayl', '06' => 'cal_june', '07' => 'cal_july', '08' => 'cal_august', '09' => 'cal_september', '10' => 'cal_october', '11' => 'cal_november', '12' => 'cal_december');
  308. }
  309. return ($this->CI->lang->line($month_names[$month]) === FALSE)
  310. ? ucfirst(substr($month_names[$month], 4))
  311. : $this->CI->lang->line($month_names[$month]);
  312. }
  313. // --------------------------------------------------------------------
  314. /**
  315. * Get Day Names
  316. *
  317. * Returns an array of day names (Sunday, Monday, etc.) based
  318. * on the type. Options: long, short, abr
  319. *
  320. * @param string
  321. * @return array
  322. */
  323. public function get_day_names($day_type = '')
  324. {
  325. if ($day_type !== '')
  326. {
  327. $this->day_type = $day_type;
  328. }
  329. if ($this->day_type === 'long')
  330. {
  331. $day_names = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
  332. }
  333. elseif ($this->day_type === 'short')
  334. {
  335. $day_names = array('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat');
  336. }
  337. else
  338. {
  339. $day_names = array('su', 'mo', 'tu', 'we', 'th', 'fr', 'sa');
  340. }
  341. $days = array();
  342. for ($i = 0, $c = count($day_names); $i < $c; $i++)
  343. {
  344. $days[] = ($this->CI->lang->line('cal_'.$day_names[$i]) === FALSE) ? ucfirst($day_names[$i]) : $this->CI->lang->line('cal_'.$day_names[$i]);
  345. }
  346. return $days;
  347. }
  348. // --------------------------------------------------------------------
  349. /**
  350. * Adjust Date
  351. *
  352. * This function makes sure that we have a valid month/year.
  353. * For example, if you submit 13 as the month, the year will
  354. * increment and the month will become January.
  355. *
  356. * @param int the month
  357. * @param int the year
  358. * @return array
  359. */
  360. public function adjust_date($month, $year)
  361. {
  362. $date = array();
  363. $date['month'] = $month;
  364. $date['year'] = $year;
  365. while ($date['month'] > 12)
  366. {
  367. $date['month'] -= 12;
  368. $date['year']++;
  369. }
  370. while ($date['month'] <= 0)
  371. {
  372. $date['month'] += 12;
  373. $date['year']--;
  374. }
  375. if (strlen($date['month']) === 1)
  376. {
  377. $date['month'] = '0'.$date['month'];
  378. }
  379. return $date;
  380. }
  381. // --------------------------------------------------------------------
  382. /**
  383. * Total days in a given month
  384. *
  385. * @param int the month
  386. * @param int the year
  387. * @return int
  388. */
  389. public function get_total_days($month, $year)
  390. {
  391. $this->CI->load->helper('date');
  392. return days_in_month($month, $year);
  393. }
  394. // --------------------------------------------------------------------
  395. /**
  396. * Set Default Template Data
  397. *
  398. * This is used in the event that the user has not created their own template
  399. *
  400. * @return array
  401. */
  402. public function default_template()
  403. {
  404. return array(
  405. 'table_open' => '<table border="0" cellpadding="4" cellspacing="0">',
  406. 'heading_row_start' => '<tr>',
  407. 'heading_previous_cell' => '<th><a href="{previous_url}">&lt;&lt;</a></th>',
  408. 'heading_title_cell' => '<th colspan="{colspan}">{heading}</th>',
  409. 'heading_next_cell' => '<th><a href="{next_url}">&gt;&gt;</a></th>',
  410. 'heading_row_end' => '</tr>',
  411. 'week_row_start' => '<tr>',
  412. 'week_day_cell' => '<td>{week_day}</td>',
  413. 'week_row_end' => '</tr>',
  414. 'cal_row_start' => '<tr>',
  415. 'cal_cell_start' => '<td>',
  416. 'cal_cell_start_today' => '<td>',
  417. 'cal_cell_start_other' => '<td style="color: #666;">',
  418. 'cal_cell_content' => '<a href="{content}">{day}</a>',
  419. 'cal_cell_content_today' => '<a href="{content}"><strong>{day}</strong></a>',
  420. 'cal_cell_no_content' => '{day}',
  421. 'cal_cell_no_content_today' => '<strong>{day}</strong>',
  422. 'cal_cell_blank' => '&nbsp;',
  423. 'cal_cell_other' => '{day}',
  424. 'cal_cell_end' => '</td>',
  425. 'cal_cell_end_today' => '</td>',
  426. 'cal_cell_end_other' => '</td>',
  427. 'cal_row_end' => '</tr>',
  428. 'table_close' => '</table>'
  429. );
  430. }
  431. // --------------------------------------------------------------------
  432. /**
  433. * Parse Template
  434. *
  435. * Harvests the data within the template {pseudo-variables}
  436. * used to display the calendar
  437. *
  438. * @return CI_Calendar
  439. */
  440. public function parse_template()
  441. {
  442. $this->replacements = $this->default_template();
  443. if (empty($this->template))
  444. {
  445. return $this;
  446. }
  447. if (is_string($this->template))
  448. {
  449. $today = array('cal_cell_start_today', 'cal_cell_content_today', 'cal_cell_no_content_today', 'cal_cell_end_today');
  450. foreach (array('table_open', 'table_close', 'heading_row_start', 'heading_previous_cell', 'heading_title_cell', 'heading_next_cell', 'heading_row_end', 'week_row_start', 'week_day_cell', 'week_row_end', 'cal_row_start', 'cal_cell_start', 'cal_cell_content', 'cal_cell_no_content', 'cal_cell_blank', 'cal_cell_end', 'cal_row_end', 'cal_cell_start_today', 'cal_cell_content_today', 'cal_cell_no_content_today', 'cal_cell_end_today', 'cal_cell_start_other', 'cal_cell_other', 'cal_cell_end_other') as $val)
  451. {
  452. if (preg_match('/\{'.$val.'\}(.*?)\{\/'.$val.'\}/si', $this->template, $match))
  453. {
  454. $this->replacements[$val] = $match[1];
  455. }
  456. elseif (in_array($val, $today, TRUE))
  457. {
  458. $this->replacements[$val] = $this->replacements[substr($val, 0, -6)];
  459. }
  460. }
  461. }
  462. elseif (is_array($this->template))
  463. {
  464. $this->replacements = array_merge($this->replacements, $this->template);
  465. }
  466. return $this;
  467. }
  468. }