1<?php defined('SYSPATH') OR die('No direct access allowed.');
2/**
3 * Calendar creation library.
4 *
5 * $Id: Calendar.php 3769 2008-12-15 00:48:56Z zombor $
6 *
7 * @package    Calendar
8 * @author     Kohana Team
9 * @copyright  (c) 2007-2008 Kohana Team
10 * @license    http://kohanaphp.com/license.html
11 */
12class Calendar_Core extends Event_Subject {
13
14	// Start the calendar on Sunday by default
15	public static $start_monday = FALSE;
16
17	// Month and year to use for calendaring
18	protected $month;
19	protected $year;
20
21	// Week starts on Sunday
22	protected $week_start = 0;
23
24	// Observed data
25	protected $observed_data;
26
27	/**
28	 * Returns an array of the names of the days, using the current locale.
29	 *
30	 * @param   integer  left of day names
31	 * @return  array
32	 */
33	public static function days($length = TRUE)
34	{
35		// strftime day format
36		$format = ($length > 3) ? '%A' : '%a';
37
38		// Days of the week
39		$days = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
40
41		if (Calendar::$start_monday === TRUE)
42		{
43			// Push Sunday to the end of the days
44			array_push($days, array_shift($days));
45		}
46
47		if (strpos(Kohana::config('locale.language.0'), 'en') !== 0)
48		{
49			// This is a bit awkward, but it works properly and is reliable
50			foreach ($days as $i => $day)
51			{
52				// Convert the English names to i18n names
53				$days[$i] = strftime($format, strtotime($day));
54			}
55		}
56
57		if (is_int($length) OR ctype_digit($length))
58		{
59			foreach ($days as $i => $day)
60			{
61				// Shorten the days to the expected length
62				$days[$i] = utf8::substr($day, 0, $length);
63			}
64		}
65
66		return $days;
67	}
68
69	/**
70	 * Create a new Calendar instance. A month and year can be specified.
71	 * By default, the current month and year are used.
72	 *
73	 * @param   integer  month number
74	 * @param   integer  year number
75	 * @return  object
76	 */
77	public static function factory($month = NULL, $year = NULL)
78	{
79		return new Calendar($month, $year);
80	}
81
82	/**
83	 * Create a new Calendar instance. A month and year can be specified.
84	 * By default, the current month and year are used.
85	 *
86	 * @param   integer  month number
87	 * @param   integer  year number
88	 * @return  void
89	 */
90	public function __construct($month = NULL, $year = NULL)
91	{
92		empty($month) and $month = date('n'); // Current month
93		empty($year)  and $year  = date('Y'); // Current year
94
95		// Set the month and year
96		$this->month = (int) $month;
97		$this->year  = (int) $year;
98
99		if (Calendar::$start_monday === TRUE)
100		{
101			// Week starts on Monday
102			$this->week_start = 1;
103		}
104	}
105
106	/**
107	 * Allows fetching the current month and year.
108	 *
109	 * @param   string  key to get
110	 * @return  mixed
111	 */
112	public function __get($key)
113	{
114		if ($key === 'month' OR $key === 'year')
115		{
116			return $this->$key;
117		}
118	}
119
120	/**
121	 * Calendar_Event factory method.
122	 *
123	 * @param   string  unique name for the event
124	 * @return  object  Calendar_Event
125	 */
126	public function event($name = NULL)
127	{
128		return new Calendar_Event($this);
129	}
130
131	/**
132	 * Calendar_Event factory method.
133	 *
134	 * @chainable
135	 * @param   string  standard event type
136	 * @return  object
137	 */
138	public function standard($name)
139	{
140		switch ($name)
141		{
142			case 'today':
143				// Add an event for the current day
144				$this->attach($this->event()->condition('timestamp', strtotime('today'))->add_class('today'));
145			break;
146			case 'prev-next':
147				// Add an event for padding days
148				$this->attach($this->event()->condition('current', FALSE)->add_class('prev-next'));
149			break;
150			case 'holidays':
151				// Base event
152				$event = $this->event()->condition('current', TRUE)->add_class('holiday');
153
154				// Attach New Years
155				$holiday = clone $event;
156				$this->attach($holiday->condition('month', 1)->condition('day', 1));
157
158				// Attach Valentine's Day
159				$holiday = clone $event;
160				$this->attach($holiday->condition('month', 2)->condition('day', 14));
161
162				// Attach St. Patrick's Day
163				$holiday = clone $event;
164				$this->attach($holiday->condition('month', 3)->condition('day', 17));
165
166				// Attach Easter
167				$holiday = clone $event;
168				$this->attach($holiday->condition('easter', TRUE));
169
170				// Attach Memorial Day
171				$holiday = clone $event;
172				$this->attach($holiday->condition('month', 5)->condition('day_of_week', 1)->condition('last_occurrence', TRUE));
173
174				// Attach Independance Day
175				$holiday = clone $event;
176				$this->attach($holiday->condition('month', 7)->condition('day', 4));
177
178				// Attach Labor Day
179				$holiday = clone $event;
180				$this->attach($holiday->condition('month', 9)->condition('day_of_week', 1)->condition('occurrence', 1));
181
182				// Attach Halloween
183				$holiday = clone $event;
184				$this->attach($holiday->condition('month', 10)->condition('day', 31));
185
186				// Attach Thanksgiving
187				$holiday = clone $event;
188				$this->attach($holiday->condition('month', 11)->condition('day_of_week', 4)->condition('occurrence', 4));
189
190				// Attach Christmas
191				$holiday = clone $event;
192				$this->attach($holiday->condition('month', 12)->condition('day', 25));
193			break;
194			case 'weekends':
195				// Weekend events
196				$this->attach($this->event()->condition('weekend', TRUE)->add_class('weekend'));
197			break;
198		}
199
200		return $this;
201	}
202
203	/**
204	 * Returns an array for use with a view. The array contains an array for
205	 * each week. Each week contains 7 arrays, with a day number and status:
206	 * TRUE if the day is in the month, FALSE if it is padding.
207	 *
208	 * @return  array
209	 */
210	public function weeks()
211	{
212		// First day of the month as a timestamp
213		$first = mktime(1, 0, 0, $this->month, 1, $this->year);
214
215		// Total number of days in this month
216		$total = (int) date('t', $first);
217
218		// Last day of the month as a timestamp
219		$last  = mktime(1, 0, 0, $this->month, $total, $this->year);
220
221		// Make the month and week empty arrays
222		$month = $week = array();
223
224		// Number of days added. When this reaches 7, start a new week
225		$days = 0;
226		$week_number = 1;
227
228		if (($w = (int) date('w', $first) - $this->week_start) < 0)
229		{
230			$w = 6;
231		}
232
233		if ($w > 0)
234		{
235			// Number of days in the previous month
236			$n = (int) date('t', mktime(1, 0, 0, $this->month - 1, 1, $this->year));
237
238			// i = number of day, t = number of days to pad
239			for ($i = $n - $w + 1, $t = $w; $t > 0; $t--, $i++)
240			{
241				// Notify the listeners
242				$this->notify(array($this->month - 1, $i, $this->year, $week_number, FALSE));
243
244				// Add previous month padding days
245				$week[] = array($i, FALSE, $this->observed_data);
246				$days++;
247			}
248		}
249
250		// i = number of day
251		for ($i = 1; $i <= $total; $i++)
252		{
253			if ($days % 7 === 0)
254			{
255				// Start a new week
256				$month[] = $week;
257				$week = array();
258
259				$week_number++;
260			}
261
262			// Notify the listeners
263			$this->notify(array($this->month, $i, $this->year, $week_number, TRUE));
264
265			// Add days to this month
266			$week[] = array($i, TRUE, $this->observed_data);
267			$days++;
268		}
269
270		if (($w = (int) date('w', $last) - $this->week_start) < 0)
271		{
272			$w = 6;
273		}
274
275		if ($w >= 0)
276		{
277			// i = number of day, t = number of days to pad
278			for ($i = 1, $t = 6 - $w; $t > 0; $t--, $i++)
279			{
280				// Notify the listeners
281				$this->notify(array($this->month + 1, $i, $this->year, $week_number, FALSE));
282
283				// Add next month padding days
284				$week[] = array($i, FALSE, $this->observed_data);
285			}
286		}
287
288		if ( ! empty($week))
289		{
290			// Append the remaining days
291			$month[] = $week;
292		}
293
294		return $month;
295	}
296
297	/**
298	 * Adds new data from an observer. All event data contains and array of CSS
299	 * classes and an array of output messages.
300	 *
301	 * @param   array  observer data.
302	 * @return  void
303	 */
304	public function add_data(array $data)
305	{
306		// Add new classes
307		$this->observed_data['classes'] += $data['classes'];
308
309		if ( ! empty($data['output']))
310		{
311			// Only add output if it's not empty
312			$this->observed_data['output'][] = $data['output'];
313		}
314	}
315
316	/**
317	 * Resets the observed data and sends a notify to all attached events.
318	 *
319	 * @param   array  UNIX timestamp
320	 * @return  void
321	 */
322	public function notify($data)
323	{
324		// Reset observed data
325		$this->observed_data = array
326		(
327			'classes' => array(),
328			'output' => array(),
329		);
330
331		// Send a notify
332		parent::notify($data);
333	}
334
335	/**
336	 * Convert the calendar to HTML using the kohana_calendar view.
337	 *
338	 * @return  string
339	 */
340	public function render()
341	{
342		$view =  new View('kohana_calendar', array
343		(
344			'month'  => $this->month,
345			'year'   => $this->year,
346			'weeks'  => $this->weeks(),
347		));
348
349		return $view->render();
350	}
351
352	/**
353	 * Magically convert this object to a string, the rendered calendar.
354	 *
355	 * @return  string
356	 */
357	public function __toString()
358	{
359		return $this->render();
360	}
361
362} // End Calendar