Completed
Push — release-2.1 ( 6f6d35...abeae7 )
by Mathias
08:46
created

Subs-Calendar.php ➔ list_getHolidays()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 12

Duplication

Lines 22
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 2
nop 3
dl 22
loc 22
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file contains several functions for retrieving and manipulating calendar events, birthdays and holidays.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Get all birthdays within the given time range.
21
 * finds all the birthdays in the specified range of days.
22
 * works with birthdays set for no year, or any other year, and respects month and year boundaries.
23
 *
24
 * @param string $low_date The low end of the range, inclusive, in YYYY-MM-DD format
25
 * @param string $high_date The high end of the range, inclusive, in YYYY-MM-DD format
26
 * @return array An array of days, each of which is an array of birthday information for the context
27
 */
28
function getBirthdayRange($low_date, $high_date)
29
{
30
	global $smcFunc;
31
32
	// We need to search for any birthday in this range, and whatever year that birthday is on.
33
	$year_low = (int) substr($low_date, 0, 4);
34
	$year_high = (int) substr($high_date, 0, 4);
35
36
	if ($smcFunc['db_title'] != "PostgreSQL")
37
	{
38
		// Collect all of the birthdays for this month.  I know, it's a painful query.
39
		$result = $smcFunc['db_query']('birthday_array', '
40
			SELECT id_member, real_name, YEAR(birthdate) AS birth_year, birthdate
41
			FROM {db_prefix}members
42
			WHERE YEAR(birthdate) != {string:year_one}
43
				AND MONTH(birthdate) != {int:no_month}
44
				AND DAYOFMONTH(birthdate) != {int:no_day}
45
				AND YEAR(birthdate) <= {int:max_year}
46
				AND (
47
					DATE_FORMAT(birthdate, {string:year_low}) BETWEEN {date:low_date} AND {date:high_date}' . ($year_low == $year_high ? '' : '
48
					OR DATE_FORMAT(birthdate, {string:year_high}) BETWEEN {date:low_date} AND {date:high_date}') . '
49
				)
50
				AND is_activated = {int:is_activated}',
51
			array(
52
				'is_activated' => 1,
53
				'no_month' => 0,
54
				'no_day' => 0,
55
				'year_one' => '1004',
56
				'year_low' => $year_low . '-%m-%d',
57
				'year_high' => $year_high . '-%m-%d',
58
				'low_date' => $low_date,
59
				'high_date' => $high_date,
60
				'max_year' => $year_high,
61
			)
62
		);
63
	}
64
	else
65
	{
66
		$result = $smcFunc['db_query']('birthday_array', '
67
			SELECT id_member, real_name, YEAR(birthdate) AS birth_year, birthdate
68
			FROM {db_prefix}members
69
			WHERE YEAR(birthdate) != {string:year_one}
70
				AND MONTH(birthdate) != {int:no_month}
71
				AND DAYOFMONTH(birthdate) != {int:no_day}
72
				AND (
73
					indexable_month_day(birthdate) BETWEEN indexable_month_day({date:year_low_low_date}) AND indexable_month_day({date:year_low_high_date})' . ($year_low == $year_high ? '' : '
74
					OR  indexable_month_day(birthdate) BETWEEN indexable_month_day({date:year_high_low_date}) AND indexable_month_day({date:year_high_high_date})') . '
75
				)
76
				AND is_activated = {int:is_activated}',
77
			array(
78
				'is_activated' => 1,
79
				'no_month' => 0,
80
				'no_day' => 0,
81
				'year_one' => '1004',
82
				'year_low' => $year_low . '-%m-%d',
83
				'year_high' => $year_high . '-%m-%d',
84
				'year_low_low_date' => $low_date,
85
				'year_low_high_date' => ($year_low == $year_high ? $high_date : $year_low . '-12-31'),
86
				'year_high_low_date' => ($year_low == $year_high ? $low_date : $year_high . '-01-01'),
87
				'year_high_high_date' => $high_date,
88
			)
89
		);
90
	}
91
	$bday = array();
92
	while ($row = $smcFunc['db_fetch_assoc']($result))
93
	{
94
		if ($year_low != $year_high)
95
			$age_year = substr($row['birthdate'], 5) < substr($high_date, 5) ? $year_high : $year_low;
96
		else
97
			$age_year = $year_low;
98
99
		$bday[$age_year . substr($row['birthdate'], 4)][] = array(
100
			'id' => $row['id_member'],
101
			'name' => $row['real_name'],
102
			'age' => $row['birth_year'] > 1004 && $row['birth_year'] <= $age_year ? $age_year - $row['birth_year'] : null,
103
			'is_last' => false
104
		);
105
	}
106
	$smcFunc['db_free_result']($result);
107
108
	ksort($bday);
109
110
	// Set is_last, so the themes know when to stop placing separators.
111 View Code Duplication
	foreach ($bday as $mday => $array)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
112
		$bday[$mday][count($array) - 1]['is_last'] = true;
113
114
	return $bday;
115
}
116
117
/**
118
 * Get all calendar events within the given time range.
119
 *
120
 * - finds all the posted calendar events within a date range.
121
 * - both the earliest_date and latest_date should be in the standard YYYY-MM-DD format.
122
 * - censors the posted event titles.
123
 * - uses the current user's permissions if use_permissions is true, otherwise it does nothing "permission specific"
124
 *
125
 * @param string $low_date The low end of the range, inclusive, in YYYY-MM-DD format
126
 * @param string $high_date The high end of the range, inclusive, in YYYY-MM-DD format
127
 * @param bool $use_permissions Whether to use permissions
128
 * @return array Contextual information if use_permissions is true, and an array of the data needed to build that otherwise
129
 */
130
function getEventRange($low_date, $high_date, $use_permissions = true)
131
{
132
	global $scripturl, $modSettings, $user_info, $smcFunc, $context, $sourcedir;
133
	static $timezone_array = array();
134
	require_once($sourcedir . '/Subs.php');
135
	
136
	if (empty($timezone_array['default']))
137
		$timezone_array['default'] = timezone_open(date_default_timezone_get());
138
139
	$low_object = date_create($low_date);
140
	$high_object = date_create($high_date);
141
142
	// Find all the calendar info...
143
	$result = $smcFunc['db_query']('calendar_get_events', '
144
		SELECT
145
			cal.id_event, cal.title, cal.id_member, cal.id_topic, cal.id_board,
146
			cal.start_date, cal.end_date, cal.start_time, cal.end_time, cal.timezone, cal.location,
147
			b.member_groups, t.id_first_msg, t.approved, b.id_board
148
		FROM {db_prefix}calendar AS cal
149
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = cal.id_board)
150
			LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = cal.id_topic)
151
		WHERE cal.start_date <= {date:high_date}
152
			AND cal.end_date >= {date:low_date}' . ($use_permissions ? '
153
			AND (cal.id_board = {int:no_board_link} OR {query_wanna_see_board})' : ''),
154
		array(
155
			'high_date' => $high_date,
156
			'low_date' => $low_date,
157
			'no_board_link' => 0,
158
		)
159
	);
160
	$events = array();
161
	while ($row = $smcFunc['db_fetch_assoc']($result))
162
	{
163
		// If the attached topic is not approved then for the moment pretend it doesn't exist
164
		if (!empty($row['id_first_msg']) && $modSettings['postmod_active'] && !$row['approved'])
165
			continue;
166
167
		// Force a censor of the title - as often these are used by others.
168
		censorText($row['title'], $use_permissions ? false : true);
169
170
		// Get the various time and date properties for this event
171
		list($start, $end, $allday, $span, $tz, $tz_abbrev) = buildEventDatetimes($row);
172
		
173
		if (empty($timezone_array[$tz]))
174
			$timezone_array[$tz] = timezone_open($tz);
175
176
		// Sanity check
177 View Code Duplication
		if (!empty($start['error_count']) || !empty($start['warning_count']) || !empty($end['error_count']) || !empty($end['warning_count']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
			continue;
179
180
		// Get set up for the loop
181
		$start_object = date_create($row['start_date'] . (!$allday ? ' ' . $row['start_time'] : ''), $timezone_array[$tz]);
182
		$end_object = date_create($row['end_date'] . (!$allday ? ' ' . $row['end_time'] : ''), $timezone_array[$tz]);
183
		date_timezone_set($start_object, $timezone_array['default']);
184
		date_timezone_set($end_object, $timezone_array['default']);
185
		date_time_set($start_object, 0, 0, 0);
186
		date_time_set($end_object, 0, 0, 0);
187
		$start_date_string = date_format($start_object, 'Y-m-d');
188
		$end_date_string = date_format($end_object, 'Y-m-d');
189
190
		$cal_date = ($start_object >= $low_object) ? $start_object : $low_object;
191
		while ($cal_date <= $end_object && $cal_date <= $high_object)
192
		{
193
			$starts_today = (date_format($cal_date, 'Y-m-d') == $start_date_string);
194
			$ends_today = (date_format($cal_date, 'Y-m-d') == $end_date_string);
195
196
			$eventProperties = array(
197
					'id' => $row['id_event'],
198
					'title' => $row['title'],
199
					'year' => $start['year'],
200
					'month' => $start['month'],
201
					'day' => $start['day'],
202
					'hour' => !$allday ? $start['hour'] : null,
203
					'minute' => !$allday ? $start['minute'] : null,
204
					'second' => !$allday ? $start['second'] : null,
205
					'start_date' => $row['start_date'],
206
					'start_date_local' => $start['date_local'],
207
					'start_date_orig' => $start['date_orig'],
208
					'start_time' => !$allday ? $row['start_time'] : null,
209
					'start_time_local' => !$allday ? $start['time_local'] : null,
210
					'start_time_orig' => !$allday ? $start['time_orig'] : null,
211
					'start_timestamp' => $start['timestamp'],
212
					'start_datetime' => $start['datetime'],
213
					'start_iso_gmdate' => $start['iso_gmdate'],
214
					'end_year' => $end['year'],
215
					'end_month' => $end['month'],
216
					'end_day' => $end['day'],
217
					'end_hour' => !$allday ? $end['hour'] : null,
218
					'end_minute' => !$allday ? $end['minute'] : null,
219
					'end_second' => !$allday ? $end['second'] : null,
220
					'end_date' => $row['end_date'],
221
					'end_date_local' => $end['date_local'],
222
					'end_date_orig' => $end['date_orig'],
223
					'end_time' => !$allday ? $row['end_time'] : null,
224
					'end_time_local' => !$allday ? $end['time_local'] : null,
225
					'end_time_orig' => !$allday ? $end['time_orig'] : null,
226
					'end_timestamp' => $end['timestamp'],
227
					'end_datetime' => $end['datetime'],
228
					'end_iso_gmdate' => $end['iso_gmdate'],
229
					'allday' => $allday,
230
					'tz' => !$allday ? $tz : null,
231
					'tz_abbrev' => !$allday ? $tz_abbrev : null,
232
					'span' => $span,
233
					'is_last' => false,
234
					'id_board' => $row['id_board'],
235
					'is_selected' => !empty($context['selected_event']) && $context['selected_event'] == $row['id_event'],
236
					'starts_today' => $starts_today,
237
					'ends_today' => $ends_today,
238
					'location' => $row['location'],
239
			);
240
241
			// If we're using permissions (calendar pages?) then just ouput normal contextual style information.
242
			if ($use_permissions)
243
				$events[date_format($cal_date, 'Y-m-d')][] = array_merge($eventProperties, array(
244
					'href' => $row['id_board'] == 0 ? '' : $scripturl . '?topic=' . $row['id_topic'] . '.0',
245
					'link' => $row['id_board'] == 0 ? $row['title'] : '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['title'] . '</a>',
246
					'can_edit' => allowedTo('calendar_edit_any') || ($row['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own')),
247
					'modify_href' => $scripturl . '?action=' . ($row['id_board'] == 0 ? 'calendar;sa=post;' : 'post;msg=' . $row['id_first_msg'] . ';topic=' . $row['id_topic'] . '.0;calendar;') . 'eventid=' . $row['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'],
248
					'can_export' => !empty($modSettings['cal_export']) ? true : false,
249
					'export_href' => $scripturl . '?action=calendar;sa=ical;eventid=' . $row['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'],
250
				));
251
			// Otherwise, this is going to be cached and the VIEWER'S permissions should apply... just put together some info.
252
			else
253
				$events[date_format($cal_date, 'Y-m-d')][] = array_merge($eventProperties, array(
254
					'href' => $row['id_topic'] == 0 ? '' : $scripturl . '?topic=' . $row['id_topic'] . '.0',
255
					'link' => $row['id_topic'] == 0 ? $row['title'] : '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['title'] . '</a>',
256
					'can_edit' => false,
257
					'can_export' => !empty($modSettings['cal_export']) ? true : false,
258
					'topic' => $row['id_topic'],
259
					'msg' => $row['id_first_msg'],
260
					'poster' => $row['id_member'],
261
					'allowed_groups' => explode(',', $row['member_groups']),
262
				));
263
264
			date_add($cal_date, date_interval_create_from_date_string('1 day'));
265
		}
266
	}
267
	$smcFunc['db_free_result']($result);
268
269
	// If we're doing normal contextual data, go through and make things clear to the templates ;).
270
	if ($use_permissions)
271
	{
272 View Code Duplication
		foreach ($events as $mday => $array)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
273
			$events[$mday][count($array) - 1]['is_last'] = true;
274
	}
275
276
	ksort($events);
277
278
	return $events;
279
}
280
281
/**
282
 * Get all holidays within the given time range.
283
 *
284
 * @param string $low_date The low end of the range, inclusive, in YYYY-MM-DD format
285
 * @param string $high_date The high end of the range, inclusive, in YYYY-MM-DD format
286
 * @return array An array of days, which are all arrays of holiday names.
287
 */
288
function getHolidayRange($low_date, $high_date)
289
{
290
	global $smcFunc;
291
292
	// Get the lowest and highest dates for "all years".
293
	if (substr($low_date, 0, 4) != substr($high_date, 0, 4))
294
		$allyear_part = 'event_date BETWEEN {date:all_year_low} AND {date:all_year_dec}
295
			OR event_date BETWEEN {date:all_year_jan} AND {date:all_year_high}';
296
	else
297
		$allyear_part = 'event_date BETWEEN {date:all_year_low} AND {date:all_year_high}';
298
299
	// Find some holidays... ;).
300
	$result = $smcFunc['db_query']('', '
301
		SELECT event_date, YEAR(event_date) AS year, title
302
		FROM {db_prefix}calendar_holidays
303
		WHERE event_date BETWEEN {date:low_date} AND {date:high_date}
304
			OR ' . $allyear_part,
305
		array(
306
			'low_date' => $low_date,
307
			'high_date' => $high_date,
308
			'all_year_low' => '1004' . substr($low_date, 4),
309
			'all_year_high' => '1004' . substr($high_date, 4),
310
			'all_year_jan' => '1004-01-01',
311
			'all_year_dec' => '1004-12-31',
312
		)
313
	);
314
	$holidays = array();
315
	while ($row = $smcFunc['db_fetch_assoc']($result))
316
	{
317
		if (substr($low_date, 0, 4) != substr($high_date, 0, 4))
318
			$event_year = substr($row['event_date'], 5) < substr($high_date, 5) ? substr($high_date, 0, 4) : substr($low_date, 0, 4);
319
		else
320
			$event_year = substr($low_date, 0, 4);
321
322
		$holidays[$event_year . substr($row['event_date'], 4)][] = $row['title'];
323
	}
324
	$smcFunc['db_free_result']($result);
325
326
	ksort($holidays);
327
328
	return $holidays;
329
}
330
331
/**
332
 * Does permission checks to see if an event can be linked to a board/topic.
333
 * checks if the current user can link the current topic to the calendar, permissions et al.
334
 * this requires the calendar_post permission, a forum moderator, or a topic starter.
335
 * expects the $topic and $board variables to be set.
336
 * if the user doesn't have proper permissions, an error will be shown.
337
 */
338
function canLinkEvent()
339
{
340
	global $user_info, $topic, $board, $smcFunc;
341
342
	// If you can't post, you can't link.
343
	isAllowedTo('calendar_post');
344
345
	// No board?  No topic?!?
346
	if (empty($board))
347
		fatal_lang_error('missing_board_id', false);
348
	if (empty($topic))
349
		fatal_lang_error('missing_topic_id', false);
350
351
	// Administrator, Moderator, or owner.  Period.
352
	if (!allowedTo('admin_forum') && !allowedTo('moderate_board'))
353
	{
354
		// Not admin or a moderator of this board. You better be the owner - or else.
355
		$result = $smcFunc['db_query']('', '
356
			SELECT id_member_started
357
			FROM {db_prefix}topics
358
			WHERE id_topic = {int:current_topic}
359
			LIMIT 1',
360
			array(
361
				'current_topic' => $topic,
362
			)
363
		);
364
		if ($row = $smcFunc['db_fetch_assoc']($result))
365
		{
366
			// Not the owner of the topic.
367
			if ($row['id_member_started'] != $user_info['id'])
368
				fatal_lang_error('not_your_topic', 'user');
369
		}
370
		// Topic/Board doesn't exist.....
371
		else
372
			fatal_lang_error('calendar_no_topic', 'general');
373
		$smcFunc['db_free_result']($result);
374
	}
375
}
376
377
/**
378
 * Returns date information about 'today' relative to the users time offset.
379
 * returns an array with the current date, day, month, and year.
380
 * takes the users time offset into account.
381
 * @return array An array of info about today, based on forum time. Has 'day', 'month', 'year' and 'date' (in YYYY-MM-DD format)
382
 */
383
function getTodayInfo()
384
{
385
	return array(
386
		'day' => (int) strftime('%d', forum_time()),
387
		'month' => (int) strftime('%m', forum_time()),
388
		'year' => (int) strftime('%Y', forum_time()),
389
		'date' => strftime('%Y-%m-%d', forum_time()),
390
	);
391
}
392
393
/**
394
 * Provides information (link, month, year) about the previous and next month.
395
 * @param int $month The month to display
396
 * @param int $year The year
397
 * @param array $calendarOptions An array of calendar options
398
 * @param bool $is_previous Whether this is the previous month
399
 * @return array A large array containing all the information needed to show a calendar grid for the given month
400
 */
401
function getCalendarGrid($month, $year, $calendarOptions, $is_previous = false)
402
{
403
	global $scripturl, $modSettings;
404
405
	// Eventually this is what we'll be returning.
406
	$calendarGrid = array(
407
		'week_days' => array(),
408
		'weeks' => array(),
409
		'short_day_titles' => !empty($calendarOptions['short_day_titles']),
410
		'short_month_titles' => !empty($calendarOptions['short_month_titles']),
411
		'highlight' => array(
412
			'events' => !empty($calendarOptions['highlight']['events']) && !empty($calendarOptions['show_events']) ? $calendarOptions['highlight']['events'] : 0,
413
			'holidays' => !empty($calendarOptions['highlight']['holidays']) && !empty($calendarOptions['show_holidays']) ? $calendarOptions['highlight']['holidays'] : 0,
414
			'birthdays' => !empty($calendarOptions['highlight']['birthdays']) && !empty($calendarOptions['show_birthdays']) ? $calendarOptions['highlight']['birthdays'] : 0,
415
		),
416
		'current_month' => $month,
417
		'current_year' => $year,
418
		'show_next_prev' => !empty($calendarOptions['show_next_prev']),
419
		'show_week_links' => isset($calendarOptions['show_week_links']) ? $calendarOptions['show_week_links'] : 0,
420
		'previous_calendar' => array(
421
			'year' => $month == 1 ? $year - 1 : $year,
422
			'month' => $month == 1 ? 12 : $month - 1,
423
			'disabled' => $modSettings['cal_minyear'] > ($month == 1 ? $year - 1 : $year),
424
		),
425
		'next_calendar' => array(
426
			'year' => $month == 12 ? $year + 1 : $year,
427
			'month' => $month == 12 ? 1 : $month + 1,
428
			'disabled' => $modSettings['cal_maxyear'] < ($month == 12 ? $year + 1 : $year),
429
		),
430
		'size' => empty($modSettings['cal_display_type']) ? 'large' : 'small',
431
	);
432
433
	// Get today's date.
434
	$today = getTodayInfo();
435
436
	// Get information about this month.
437
	$month_info = array(
438
		'first_day' => array(
439
			'day_of_week' => (int) strftime('%w', mktime(0, 0, 0, $month, 1, $year)),
440
			'week_num' => (int) strftime('%U', mktime(0, 0, 0, $month, 1, $year)),
441
			'date' => strftime('%Y-%m-%d', mktime(0, 0, 0, $month, 1, $year)),
442
		),
443
		'last_day' => array(
444
			'day_of_month' => (int) strftime('%d', mktime(0, 0, 0, $month == 12 ? 1 : $month + 1, 0, $month == 12 ? $year + 1 : $year)),
445
			'date' => strftime('%Y-%m-%d', mktime(0, 0, 0, $month == 12 ? 1 : $month + 1, 0, $month == 12 ? $year + 1 : $year)),
446
		),
447
		'first_day_of_year' => (int) strftime('%w', mktime(0, 0, 0, 1, 1, $year)),
448
		'first_day_of_next_year' => (int) strftime('%w', mktime(0, 0, 0, 1, 1, $year + 1)),
449
	);
450
451
	// The number of days the first row is shifted to the right for the starting day.
452
	$nShift = $month_info['first_day']['day_of_week'];
453
454
	$calendarOptions['start_day'] = empty($calendarOptions['start_day']) ? 0 : (int) $calendarOptions['start_day'];
455
456
	// Starting any day other than Sunday means a shift...
457
	if (!empty($calendarOptions['start_day']))
458
	{
459
		$nShift -= $calendarOptions['start_day'];
460
		if ($nShift < 0)
461
			$nShift = 7 + $nShift;
462
	}
463
464
	// Number of rows required to fit the month.
465
	$nRows = floor(($month_info['last_day']['day_of_month'] + $nShift) / 7);
466
	if (($month_info['last_day']['day_of_month'] + $nShift) % 7)
467
		$nRows++;
468
469
	// Fetch the arrays for birthdays, posted events, and holidays.
470
	$bday = $calendarOptions['show_birthdays'] ? getBirthdayRange($month_info['first_day']['date'], $month_info['last_day']['date']) : array();
471
	$events = $calendarOptions['show_events'] ? getEventRange($month_info['first_day']['date'], $month_info['last_day']['date']) : array();
472
	$holidays = $calendarOptions['show_holidays'] ? getHolidayRange($month_info['first_day']['date'], $month_info['last_day']['date']) : array();
473
474
	// Days of the week taking into consideration that they may want it to start on any day.
475
	$count = $calendarOptions['start_day'];
476
	for ($i = 0; $i < 7; $i++)
477
	{
478
		$calendarGrid['week_days'][] = $count;
479
		$count++;
480
		if ($count == 7)
481
			$count = 0;
482
	}
483
484
	// Iterate through each week.
485
	$calendarGrid['weeks'] = array();
486
	for ($nRow = 0; $nRow < $nRows; $nRow++)
487
	{
488
		// Start off the week - and don't let it go above 52, since that's the number of weeks in a year.
489
		$calendarGrid['weeks'][$nRow] = array(
490
			'days' => array(),
491
		);
492
493
		// And figure out all the days.
494
		for ($nCol = 0; $nCol < 7; $nCol++)
495
		{
496
			$nDay = ($nRow * 7) + $nCol - $nShift + 1;
497
498
			if ($nDay < 1 || $nDay > $month_info['last_day']['day_of_month'])
499
				$nDay = 0;
500
501
			$date = sprintf('%04d-%02d-%02d', $year, $month, $nDay);
502
503
			$calendarGrid['weeks'][$nRow]['days'][$nCol] = array(
504
				'day' => $nDay,
505
				'date' => $date,
506
				'is_today' => $date == $today['date'],
507
				'is_first_day' => !empty($calendarOptions['show_week_num']) && (($month_info['first_day']['day_of_week'] + $nDay - 1) % 7 == $calendarOptions['start_day']),
508
				'is_first_of_month' => $nDay === 1,
509
				'holidays' => !empty($holidays[$date]) ? $holidays[$date] : array(),
510
				'events' => !empty($events[$date]) ? $events[$date] : array(),
511
				'birthdays' => !empty($bday[$date]) ? $bday[$date] : array(),
512
			);
513
		}
514
	}
515
516
	// What is the last day of the month?
517
	if ($is_previous === true)
518
		$calendarGrid['last_of_month'] = $month_info['last_day']['day_of_month'];
519
520
	// We'll use the shift in the template.
521
	$calendarGrid['shift'] = $nShift;
522
523
	// Set the previous and the next month's links.
524
	$calendarGrid['previous_calendar']['href'] = $scripturl . '?action=calendar;viewmonth;year=' . $calendarGrid['previous_calendar']['year'] . ';month=' . $calendarGrid['previous_calendar']['month'];
525
	$calendarGrid['next_calendar']['href'] = $scripturl . '?action=calendar;viewmonth;year=' . $calendarGrid['next_calendar']['year'] . ';month=' . $calendarGrid['next_calendar']['month'];
526
527
	return $calendarGrid;
528
}
529
530
/**
531
 * Returns the information needed to show a calendar for the given week.
532
 * @param int $month The month
533
 * @param int $year The year
534
 * @param int $day The day
535
 * @param array $calendarOptions An array of calendar options
536
 * @return array An array of information needed to display the grid for a single week on the calendar
537
 */
538
function getCalendarWeek($month, $year, $day, $calendarOptions)
539
{
540
	global $scripturl, $modSettings, $txt;
541
542
	// Get today's date.
543
	$today = getTodayInfo();
544
545
	// What is the actual "start date" for the passed day.
546
	$calendarOptions['start_day'] = empty($calendarOptions['start_day']) ? 0 : (int) $calendarOptions['start_day'];
547
	$day_of_week = (int) strftime('%w', mktime(0, 0, 0, $month, $day, $year));
548
	if ($day_of_week != $calendarOptions['start_day'])
549
	{
550
		// Here we offset accordingly to get things to the real start of a week.
551
		$date_diff = $day_of_week - $calendarOptions['start_day'];
552
		if ($date_diff < 0)
553
			$date_diff += 7;
554
		$new_timestamp = mktime(0, 0, 0, $month, $day, $year) - $date_diff * 86400;
555
		$day = (int) strftime('%d', $new_timestamp);
556
		$month = (int) strftime('%m', $new_timestamp);
557
		$year = (int) strftime('%Y', $new_timestamp);
558
	}
559
560
	// Now start filling in the calendar grid.
561
	$calendarGrid = array(
562
		'show_next_prev' => !empty($calendarOptions['show_next_prev']),
563
		// Previous week is easy - just step back one day.
564
		'previous_week' => array(
565
			'year' => $day == 1 ? ($month == 1 ? $year - 1 : $year) : $year,
566
			'month' => $day == 1 ? ($month == 1 ? 12 : $month - 1) : $month,
567
			'day' => $day == 1 ? 28 : $day - 1,
568
			'disabled' => $day < 7 && $modSettings['cal_minyear'] > ($month == 1 ? $year - 1 : $year),
569
		),
570
		'next_week' => array(
571
			'disabled' => $day > 25 && $modSettings['cal_maxyear'] < ($month == 12 ? $year + 1 : $year),
572
		),
573
		'size' => empty($modSettings['cal_display_type']) ? 'large' : 'small',
574
	);
575
576
	// The next week calculation requires a bit more work.
577
	$curTimestamp = mktime(0, 0, 0, $month, $day, $year);
578
	$nextWeekTimestamp = $curTimestamp + 604800;
579
	$calendarGrid['next_week']['day'] = (int) strftime('%d', $nextWeekTimestamp);
580
	$calendarGrid['next_week']['month'] = (int) strftime('%m', $nextWeekTimestamp);
581
	$calendarGrid['next_week']['year'] = (int) strftime('%Y', $nextWeekTimestamp);
582
583
	// Fetch the arrays for birthdays, posted events, and holidays.
584
	$startDate = strftime('%Y-%m-%d', $curTimestamp);
585
	$endDate = strftime('%Y-%m-%d', $nextWeekTimestamp);
586
	$bday = $calendarOptions['show_birthdays'] ? getBirthdayRange($startDate, $endDate) : array();
587
	$events = $calendarOptions['show_events'] ? getEventRange($startDate, $endDate) : array();
588
	$holidays = $calendarOptions['show_holidays'] ? getHolidayRange($startDate, $endDate) : array();
589
590
	// An adjustment value to apply to all calculated week numbers.
591
	if (!empty($calendarOptions['show_week_num']))
592
	{
593
		$timestamp = mktime(0, 0, 0, $month, $day, $year);
594
		$calendarGrid['week_title'] = sprintf($txt['calendar_week_beginning'], date('F', $timestamp), date('j', $timestamp), date('Y', $timestamp));
595
	}
596
597
	// This holds all the main data - there is at least one month!
598
	$calendarGrid['months'] = array();
599
	$lastDay = 99;
600
	$curDay = $day;
601
	$curDayOfWeek = $calendarOptions['start_day'];
602
	for ($i = 0; $i < 7; $i++)
603
	{
604
		// Have we gone into a new month (Always happens first cycle too)
605
		if ($lastDay > $curDay)
606
		{
607
			$curMonth = $lastDay == 99 ? $month : ($month == 12 ? 1 : $month + 1);
608
			$curYear = $lastDay == 99 ? $year : ($curMonth == 1 && $month == 12 ? $year + 1 : $year);
609
			$calendarGrid['months'][$curMonth] = array(
610
				'current_month' => $curMonth,
611
				'current_year' => $curYear,
612
				'days' => array(),
613
			);
614
		}
615
616
		// Add todays information to the pile!
617
		$date = sprintf('%04d-%02d-%02d', $curYear, $curMonth, $curDay);
0 ignored issues
show
Bug introduced by
The variable $curYear does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $curMonth does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
618
619
		$calendarGrid['months'][$curMonth]['days'][$curDay] = array(
620
			'day' => $curDay,
621
			'day_of_week' => $curDayOfWeek,
622
			'date' => $date,
623
			'is_today' => $date == $today['date'],
624
			'holidays' => !empty($holidays[$date]) ? $holidays[$date] : array(),
625
			'events' => !empty($events[$date]) ? $events[$date] : array(),
626
			'birthdays' => !empty($bday[$date]) ? $bday[$date] : array()
627
		);
628
629
		// Make the last day what the current day is and work out what the next day is.
630
		$lastDay = $curDay;
631
		$curTimestamp += 86400;
632
		$curDay = (int) strftime('%d', $curTimestamp);
633
634
		// Also increment the current day of the week.
635
		$curDayOfWeek = $curDayOfWeek >= 6 ? 0 : ++$curDayOfWeek;
636
	}
637
638
	// Set the previous and the next week's links.
639
	$calendarGrid['previous_week']['href'] = $scripturl . '?action=calendar;viewweek;year=' . $calendarGrid['previous_week']['year'] . ';month=' . $calendarGrid['previous_week']['month'] . ';day=' . $calendarGrid['previous_week']['day'];
640
	$calendarGrid['next_week']['href'] = $scripturl . '?action=calendar;viewweek;year=' . $calendarGrid['next_week']['year'] . ';month=' . $calendarGrid['next_week']['month'] . ';day=' . $calendarGrid['next_week']['day'];
641
642
	return $calendarGrid;
643
}
644
645
646
/**
647
 * Returns the information needed to show a list of upcoming events, birthdays, and holidays on the calendar.
648
 * @param int $start_date The start of a date range
649
 * @param int $end_date The end of a date range
650
 * @param array $calendarOptions An array of calendar options
651
 * @return array An array of information needed to display a list of upcoming events, etc., on the calendar
652
 */
653
function getCalendarList($start_date, $end_date, $calendarOptions)
654
{
655
	global $modSettings, $user_info, $txt, $context, $sourcedir;
656
	require_once($sourcedir . '/Subs.php');
657
658
	// DateTime objects make life easier
659
	$start_object = date_create($start_date);
660
	$end_object = date_create($end_date);
661
662
	$calendarGrid = array(
663
		'start_date' => $start_date,
664
		'start_year' => date_format($start_object, 'Y'),
665
		'start_month' => date_format($start_object, 'm'),
666
		'start_day' => date_format($start_object, 'd'),
667
		'end_date' => $end_date,
668
		'end_year' => date_format($end_object, 'Y'),
669
		'end_month' => date_format($end_object, 'm'),
670
		'end_day' => date_format($end_object, 'd'),
671
	);
672
673
	$calendarGrid['birthdays'] = $calendarOptions['show_birthdays'] ? getBirthdayRange($start_date, $end_date) : array();
674
	$calendarGrid['holidays'] = $calendarOptions['show_holidays'] ? getHolidayRange($start_date, $end_date) : array();
675
	$calendarGrid['events'] = $calendarOptions['show_events'] ? getEventRange($start_date, $end_date) : array();
676
677
	// Get rid of duplicate events
678
	$temp = array();
679
	foreach ($calendarGrid['events'] as $date => $date_events)
680
	{
681
		foreach ($date_events as $event_key => $event_val)
682
		{
683
			if (in_array($event_val['id'], $temp))
684
				unset($calendarGrid['events'][$date][$event_key]);
685
			else
686
				$temp[] = $event_val['id'];
687
		}
688
	}
689
690
	// Give birthdays and holidays a friendly format, without the year
691 View Code Duplication
	if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
692
		$date_format = '%b %d';
693
	else
694
		$date_format = str_replace(array('%Y', '%y', '%G', '%g', '%C', '%c', '%D'), array('', '', '', '', '', '%b %d', '%m/%d'), $matches[0]);
695
696
	foreach (array('birthdays', 'holidays') as $type)
697
	{
698
		foreach ($calendarGrid[$type] as $date => $date_content)
699
		{
700
			$date_local = preg_replace('~(?<=\s)0+(\d)~', '$1', trim(timeformat(strtotime($date), $date_format), " \t\n\r\0\x0B,./;:<>()[]{}\\|-_=+"));
0 ignored issues
show
Documentation introduced by
$date_format is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
701
702
			$calendarGrid[$type][$date]['date_local'] = $date_local;
703
		}
704
	}
705
706
	loadCSSFile('jquery-ui.datepicker.css', array('defer' => false), 'smf_datepicker');
707
	loadJavaScriptFile('jquery-ui.datepicker.min.js', array('defer' => true), 'smf_datepicker');
708
	addInlineJavaScript('
709
	$("#calendar_range .date_input").datepicker({
710
		dateFormat: "yy-mm-dd",
711
		autoSize: true,
712
		isRTL: ' . ($context['right_to_left'] ? 'true' : 'false') . ',
713
		constrainInput: true,
714
		showAnim: "",
715
		showButtonPanel: false,
716
		minDate: "' . $modSettings['cal_minyear'] . '-01-01",
717
		maxDate: "' . $modSettings['cal_maxyear'] . '-12-31",
718
		yearRange: "' . $modSettings['cal_minyear'] . ':' . $modSettings['cal_maxyear'] . '",
719
		hideIfNoPrevNext: true,
720
		monthNames: ["' . implode('", "', $txt['months_titles']) . '"],
721
		monthNamesShort: ["' . implode('", "', $txt['months_short']) . '"],
722
		dayNames: ["' . implode('", "', $txt['days']) . '"],
723
		dayNamesShort: ["' . implode('", "', $txt['days_short']) . '"],
724
		dayNamesMin: ["' . implode('", "', $txt['days_short']) . '"],
725
		prevText: "' . $txt['prev_month'] . '",
726
		nextText: "' . $txt['next_month'] . '",
727
	});
728
	var date_entry = document.getElementById("calendar_range");
729
	', true);
730
731
	return $calendarGrid;
732
}
733
734
/**
735
 * Retrieve all events for the given days, independently of the users offset.
736
 * cache callback function used to retrieve the birthdays, holidays, and events between now and now + days_to_index.
737
 * widens the search range by an extra 24 hours to support time offset shifts.
738
 * used by the cache_getRecentEvents function to get the information needed to calculate the events taking the users time offset into account.
739
 *
740
 * @param array $eventOptions With the keys 'num_days_shown', 'include_holidays', 'include_birthdays' and 'include_events'
741
 * @return array An array containing the data that was cached as well as an expression to calculate whether the data should be refreshed and when it expires
742
 */
743
function cache_getOffsetIndependentEvents($eventOptions)
744
{
745
	$days_to_index = $eventOptions['num_days_shown'];
746
	
747
	$low_date = strftime('%Y-%m-%d', forum_time(false) - 24 * 3600);
748
	$high_date = strftime('%Y-%m-%d', forum_time(false) + $days_to_index * 24 * 3600);
749
750
	return array(
751
		'data' => array(
752
			'holidays' => ($eventOptions['include_holidays'] ? getHolidayRange($low_date, $high_date) : array()),
753
			'birthdays' => ($eventOptions['include_birthdays'] ? getBirthdayRange($low_date, $high_date) : array()),
754
			'events' => ($eventOptions['include_events'] ? getEventRange($low_date, $high_date, false) : array()),
755
		),
756
		'refresh_eval' => 'return \'' . strftime('%Y%m%d', forum_time(false)) . '\' != strftime(\'%Y%m%d\', forum_time(false)) || (!empty($modSettings[\'calendar_updated\']) && ' . time() . ' < $modSettings[\'calendar_updated\']);',
757
		'expires' => time() + 3600,
758
	);
759
}
760
761
/**
762
 * cache callback function used to retrieve the upcoming birthdays, holidays, and events within the given period, taking into account the users time offset.
763
 * Called from the BoardIndex to display the current day's events on the board index
764
 * used by the board index and SSI to show the upcoming events.
765
 * @param array $eventOptions An array of event options.
766
 * @return array An array containing the info that was cached as well as a few other relevant things
767
 */
768
function cache_getRecentEvents($eventOptions)
769
{
770
	// With the 'static' cached data we can calculate the user-specific data.
771
	$cached_data = cache_quick_get('calendar_index', 'Subs-Calendar.php', 'cache_getOffsetIndependentEvents', array($eventOptions));
772
773
	// Get the information about today (from user perspective).
774
	$today = getTodayInfo();
775
776
	$return_data = array(
777
		'calendar_holidays' => array(),
778
		'calendar_birthdays' => array(),
779
		'calendar_events' => array(),
780
	);
781
782
	// Set the event span to be shown in seconds.
783
	$days_for_index = $eventOptions['num_days_shown'] * 86400;
784
785
	// Get the current member time/date.
786
	$now = forum_time();
787
788
	if ($eventOptions['include_holidays'])
789
	{
790
		// Holidays between now and now + days.
791
		for ($i = $now; $i < $now + $days_for_index; $i += 86400)
792
		{
793
			if (isset($cached_data['holidays'][strftime('%Y-%m-%d', $i)]))
794
				$return_data['calendar_holidays'] = array_merge($return_data['calendar_holidays'], $cached_data['holidays'][strftime('%Y-%m-%d', $i)]);
795
		}
796
	}
797
	
798
	if ($eventOptions['include_birthdays'])
799
	{
800
		// Happy Birthday, guys and gals!
801
		for ($i = $now; $i < $now + $days_for_index; $i += 86400)
802
		{
803
			$loop_date = strftime('%Y-%m-%d', $i);
804
			if (isset($cached_data['birthdays'][$loop_date]))
805
			{
806
				foreach ($cached_data['birthdays'][$loop_date] as $index => $dummy)
0 ignored issues
show
Bug introduced by
The expression $cached_data['birthdays'][$loop_date] of type string is not traversable.
Loading history...
807
					$cached_data['birthdays'][strftime('%Y-%m-%d', $i)][$index]['is_today'] = $loop_date === $today['date'];
808
				$return_data['calendar_birthdays'] = array_merge($return_data['calendar_birthdays'], $cached_data['birthdays'][$loop_date]);
809
			}
810
		}	
811
	}
812
	
813
	if ($eventOptions['include_events'])
814
	{
815
		$duplicates = array();
816
		for ($i = $now; $i < $now + $days_for_index; $i += 86400)
817
		{
818
			// Determine the date of the current loop step.
819
			$loop_date = strftime('%Y-%m-%d', $i);
820
821
			// No events today? Check the next day.
822
			if (empty($cached_data['events'][$loop_date]))
823
				continue;
824
825
			// Loop through all events to add a few last-minute values.
826
			foreach ($cached_data['events'][$loop_date] as $ev => $event)
0 ignored issues
show
Bug introduced by
The expression $cached_data['events'][$loop_date] of type string is not traversable.
Loading history...
827
			{
828
				// Create a shortcut variable for easier access.
829
				$this_event = &$cached_data['events'][$loop_date][$ev];
830
831
				// Skip duplicates.
832
				if (isset($duplicates[$this_event['topic'] . $this_event['title']]))
833
				{
834
					unset($cached_data['events'][$loop_date][$ev]);
835
					continue;
836
				}
837
				else
838
					$duplicates[$this_event['topic'] . $this_event['title']] = true;
839
840
				// Might be set to true afterwards, depending on the permissions.
841
				$this_event['can_edit'] = false;
842
				$this_event['is_today'] = $loop_date === $today['date'];
843
				$this_event['date'] = $loop_date;
844
			}
845
846
			if (!empty($cached_data['events'][$loop_date]))
847
				$return_data['calendar_events'] = array_merge($return_data['calendar_events'], $cached_data['events'][$loop_date]);
848
		}
849
	}
850
851
	// Mark the last item so that a list separator can be used in the template.
852 View Code Duplication
	for ($i = 0, $n = count($return_data['calendar_birthdays']); $i < $n; $i++)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
853
		$return_data['calendar_birthdays'][$i]['is_last'] = !isset($return_data['calendar_birthdays'][$i + 1]);
854 View Code Duplication
	for ($i = 0, $n = count($return_data['calendar_events']); $i < $n; $i++)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
855
		$return_data['calendar_events'][$i]['is_last'] = !isset($return_data['calendar_events'][$i + 1]);
856
857
	return array(
858
		'data' => $return_data,
859
		'expires' => time() + 3600,
860
		'refresh_eval' => 'return \'' . strftime('%Y%m%d', forum_time(false)) . '\' != strftime(\'%Y%m%d\', forum_time(false)) || (!empty($modSettings[\'calendar_updated\']) && ' . time() . ' < $modSettings[\'calendar_updated\']);',
861
		'post_retri_eval' => '
862
			global $context, $scripturl, $user_info;
863
864
			foreach ($cache_block[\'data\'][\'calendar_events\'] as $k => $event)
865
			{
866
				// Remove events that the user may not see or wants to ignore.
867
				if ((count(array_intersect($user_info[\'groups\'], $event[\'allowed_groups\'])) === 0 && !allowedTo(\'admin_forum\') && !empty($event[\'id_board\'])) || in_array($event[\'id_board\'], $user_info[\'ignoreboards\']))
868
					unset($cache_block[\'data\'][\'calendar_events\'][$k]);
869
				else
870
				{
871
					// Whether the event can be edited depends on the permissions.
872
					$cache_block[\'data\'][\'calendar_events\'][$k][\'can_edit\'] = allowedTo(\'calendar_edit_any\') || ($event[\'poster\'] == $user_info[\'id\'] && allowedTo(\'calendar_edit_own\'));
873
874
					// The added session code makes this URL not cachable.
875
					$cache_block[\'data\'][\'calendar_events\'][$k][\'modify_href\'] = $scripturl . \'?action=\' . ($event[\'topic\'] == 0 ? \'calendar;sa=post;\' : \'post;msg=\' . $event[\'msg\'] . \';topic=\' . $event[\'topic\'] . \'.0;calendar;\') . \'eventid=\' . $event[\'id\'] . \';\' . $context[\'session_var\'] . \'=\' . $context[\'session_id\'];
876
				}
877
			}
878
879
			if (empty($params[0][\'include_holidays\']))
880
				$cache_block[\'data\'][\'calendar_holidays\'] = array();
881
			if (empty($params[0][\'include_birthdays\']))
882
				$cache_block[\'data\'][\'calendar_birthdays\'] = array();
883
			if (empty($params[0][\'include_events\']))
884
				$cache_block[\'data\'][\'calendar_events\'] = array();
885
886
			$cache_block[\'data\'][\'show_calendar\'] = !empty($cache_block[\'data\'][\'calendar_holidays\']) || !empty($cache_block[\'data\'][\'calendar_birthdays\']) || !empty($cache_block[\'data\'][\'calendar_events\']);',
887
	);
888
}
889
890
/**
891
 * Makes sure the calendar post is valid.
892
 */
893
function validateEventPost()
894
{
895
	global $modSettings, $smcFunc;
896
897
	if (!isset($_POST['deleteevent']))
898
	{
899
		// The 2.1 way
900
		if (isset($_POST['start_date']))
901
		{
902
			$d = date_parse($_POST['start_date']);
903 View Code Duplication
			if (!empty($d['error_count']) || !empty($d['warning_count']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
904
				fatal_lang_error('invalid_date', false);
905
			if (empty($d['year']))
906
				fatal_lang_error('event_year_missing', false);
907
			if (empty($d['month']))
908
				fatal_lang_error('event_month_missing', false);
909
		}
910
		elseif (isset($_POST['start_datetime']))
911
		{
912
			$d = date_parse($_POST['start_datetime']);
913 View Code Duplication
			if (!empty($d['error_count']) || !empty($d['warning_count']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
914
				fatal_lang_error('invalid_date', false);
915
			if (empty($d['year']))
916
				fatal_lang_error('event_year_missing', false);
917
			if (empty($d['month']))
918
				fatal_lang_error('event_month_missing', false);
919
		}
920
		// The 2.0 way
921
		else
922
		{
923
			// No month?  No year?
924
			if (!isset($_POST['month']))
925
				fatal_lang_error('event_month_missing', false);
926
			if (!isset($_POST['year']))
927
				fatal_lang_error('event_year_missing', false);
928
929
			// Check the month and year...
930
			if ($_POST['month'] < 1 || $_POST['month'] > 12)
931
				fatal_lang_error('invalid_month', false);
932 View Code Duplication
			if ($_POST['year'] < $modSettings['cal_minyear'] || $_POST['year'] > $modSettings['cal_maxyear'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
933
				fatal_lang_error('invalid_year', false);
934
		}
935
	}
936
937
	// Make sure they're allowed to post...
938
	isAllowedTo('calendar_post');
939
940
	// If they want to us to calculate an end date, make sure it will fit in an acceptable range.
941
	if (isset($_POST['span']))
942
	{
943
		if (($_POST['span'] < 1) || (!empty($modSettings['cal_maxspan']) && $_POST['span'] > $modSettings['cal_maxspan']))
944
			fatal_lang_error('invalid_days_numb', false);
945
	}
946
947
	// There is no need to validate the following values if we are just deleting the event.
948
	if (!isset($_POST['deleteevent']))
949
	{
950
		// If we're doing things the 2.0 way, check the day
951
		if (empty($_POST['start_date']) && empty($_POST['start_datetime']))
952
		{
953
			// No day?
954
			if (!isset($_POST['day']))
955
				fatal_lang_error('event_day_missing', false);
956
957
			// Bad day?
958
			if (!checkdate($_POST['month'], $_POST['day'], $_POST['year']))
959
				fatal_lang_error('invalid_date', false);
960
		}
961
962
		if (!isset($_POST['evtitle']) && !isset($_POST['subject']))
963
			fatal_lang_error('event_title_missing', false);
964
		elseif (!isset($_POST['evtitle']))
965
			$_POST['evtitle'] = $_POST['subject'];
966
967
		// No title?
968
		if ($smcFunc['htmltrim']($_POST['evtitle']) === '')
969
			fatal_lang_error('no_event_title', false);
970 View Code Duplication
		if ($smcFunc['strlen']($_POST['evtitle']) > 100)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
971
			$_POST['evtitle'] = $smcFunc['substr']($_POST['evtitle'], 0, 100);
972
		$_POST['evtitle'] = str_replace(';', '', $_POST['evtitle']);
973
	}
974
}
975
976
/**
977
 * Get the event's poster.
978
 *
979
 * @param int $event_id The ID of the event
980
 * @return int|bool The ID of the poster or false if the event was not found
981
 */
982 View Code Duplication
function getEventPoster($event_id)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
983
{
984
	global $smcFunc;
985
986
	// A simple database query, how hard can that be?
987
	$request = $smcFunc['db_query']('', '
988
		SELECT id_member
989
		FROM {db_prefix}calendar
990
		WHERE id_event = {int:id_event}
991
		LIMIT 1',
992
		array(
993
			'id_event' => $event_id,
994
		)
995
	);
996
997
	// No results, return false.
998
	if ($smcFunc['db_num_rows'] === 0)
999
		return false;
1000
1001
	// Grab the results and return.
1002
	list ($poster) = $smcFunc['db_fetch_row']($request);
1003
	$smcFunc['db_free_result']($request);
1004
	return (int) $poster;
1005
}
1006
1007
/**
1008
 * Consolidating the various INSERT statements into this function.
1009
 * Inserts the passed event information into the calendar table.
1010
 * Allows to either set a time span (in days) or an end_date.
1011
 * Does not check any permissions of any sort.
1012
 *
1013
 * @param array $eventOptions An array of event options ('title', 'span', 'start_date', 'end_date', etc.)
1014
 */
1015
function insertEvent(&$eventOptions)
1016
{
1017
	global $smcFunc, $context;
1018
1019
	// Add special chars to the title.
1020
	$eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES);
1021
1022
	$eventOptions['location'] = isset($eventOptions['location']) ? $smcFunc['htmlspecialchars']($eventOptions['location'], ENT_QUOTES) : '';
1023
1024
	// Set the start and end dates and times
1025
	list($start_date, $end_date, $start_time, $end_time, $tz) = setEventStartEnd($eventOptions);
1026
1027
	// If no topic and board are given, they are not linked to a topic.
1028
	$eventOptions['board'] = isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0;
1029
	$eventOptions['topic'] = isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0;
1030
1031
	$event_columns = array(
1032
		'id_board' => 'int', 'id_topic' => 'int', 'title' => 'string-60', 'id_member' => 'int',
1033
		'start_date' => 'date', 'end_date' => 'date', 'location' => 'string-255',
1034
	);
1035
	$event_parameters = array(
1036
		$eventOptions['board'], $eventOptions['topic'], $eventOptions['title'], $eventOptions['member'],
1037
		$start_date, $end_date, $eventOptions['location'],
1038
	);
1039 View Code Duplication
	if (!empty($start_time) && !empty($end_time) && !empty($tz) && in_array($tz, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1040
	{
1041
		$event_columns['start_time'] = 'time';
1042
		$event_parameters[] = $start_time;
1043
		$event_columns['end_time'] = 'time';
1044
		$event_parameters[] = $end_time;
1045
		$event_columns['timezone'] = 'string';
1046
		$event_parameters[] = $tz;
1047
	}
1048
1049
	call_integration_hook('integrate_create_event', array(&$eventOptions, &$event_columns, &$event_parameters));
1050
1051
	// Insert the event!
1052
	$eventOptions['id'] = $smcFunc['db_insert']('',
1053
		'{db_prefix}calendar',
1054
		$event_columns,
1055
		$event_parameters,
1056
		array('id_event'),
1057
		1
1058
	);
1059
1060
	// If this isn't tied to a topic, we need to notify people about it.
1061
	if (empty($eventOptions['topic']))
1062
	{
1063
		$smcFunc['db_insert']('insert',
1064
			'{db_prefix}background_tasks',
1065
			array('task_file' => 'string', 'task_class' => 'string', 'task_data' => 'string', 'claimed_time' => 'int'),
1066
			array('$sourcedir/tasks/EventNew-Notify.php', 'EventNew_Notify_Background', $smcFunc['json_encode'](array(
1067
				'event_title' => $eventOptions['title'],
1068
				'event_id' => $eventOptions['id'],
1069
				'sender_id' => $eventOptions['member'],
1070
				'sender_name' => $eventOptions['member'] == $context['user']['id'] ? $context['user']['name'] : '',
1071
				'time' => time(),
1072
			)), 0),
1073
			array('id_task')
1074
		);
1075
	}
1076
1077
	// Update the settings to show something calendar-ish was updated.
1078
	updateSettings(array(
1079
		'calendar_updated' => time(),
1080
	));
1081
}
1082
1083
/**
1084
 * modifies an event.
1085
 * allows to either set a time span (in days) or an end_date.
1086
 * does not check any permissions of any sort.
1087
 *
1088
 * @param int $event_id The ID of the event
1089
 * @param array $eventOptions An array of event information
1090
 */
1091
function modifyEvent($event_id, &$eventOptions)
1092
{
1093
	global $smcFunc;
1094
1095
	// Properly sanitize the title and location
1096
	$eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES);
1097
	$eventOptions['location'] = $smcFunc['htmlspecialchars']($eventOptions['location'], ENT_QUOTES);
1098
1099
	// Set the new start and end dates and times
1100
	list($start_date, $end_date, $start_time, $end_time, $tz) = setEventStartEnd($eventOptions);
1101
1102
	$event_columns = array(
1103
		'start_date' => '{date:start_date}',
1104
		'end_date' => '{date:end_date}',
1105
		'title' => 'SUBSTRING({string:title}, 1, 60)',
1106
		'id_board' => '{int:id_board}',
1107
		'id_topic' => '{int:id_topic}',
1108
		'location' => 'SUBSTRING({string:location}, 1, 255)',
1109
	);
1110
	$event_parameters = array(
1111
		'start_date' => $start_date,
1112
		'end_date' => $end_date,
1113
		'title' => $eventOptions['title'],
1114
		'location' => $eventOptions['location'],
1115
		'id_board' => isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0,
1116
		'id_topic' => isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0,
1117
	);
1118 View Code Duplication
	if (!empty($start_time) && !empty($end_time) && !empty($tz) && in_array($tz, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1119
	{
1120
		$event_columns['start_time'] = '{time:start_time}';
1121
		$event_parameters['start_time'] = $start_time;
1122
		$event_columns['end_time'] = '{time:end_time}';
1123
		$event_parameters['end_time'] = $end_time;
1124
		$event_columns['timezone'] = '{string:timezone}';
1125
		$event_parameters['timezone'] = $tz;
1126
	}
1127
1128
	// This is to prevent hooks to modify the id of the event
1129
	$real_event_id = $event_id;
1130
	call_integration_hook('integrate_modify_event', array($event_id, &$eventOptions, &$event_columns, &$event_parameters));
1131
1132
	$column_clauses = array();
1133
	foreach ($event_columns as $col => $crit)
1134
		$column_clauses[] = $col . ' = ' . $crit;
1135
1136
	$smcFunc['db_query']('', '
1137
		UPDATE {db_prefix}calendar
1138
		SET
1139
			' . implode(', ', $column_clauses) . '
1140
		WHERE id_event = {int:id_event}',
1141
		array_merge(
1142
			$event_parameters,
1143
			array(
1144
				'id_event' => $real_event_id
1145
			)
1146
		)
1147
	);
1148
1149
	if (empty($start_time) || empty($end_time) || empty($tz) || !in_array($tz, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1150
	{
1151
		$smcFunc['db_query']('', '
1152
			UPDATE {db_prefix}calendar
1153
			SET start_time = NULL, end_time = NULL, timezone = NULL
1154
			WHERE id_event = {int:id_event}',
1155
			array(
1156
				'id_event' => $real_event_id
1157
			)
1158
		);
1159
	}
1160
1161
	updateSettings(array(
1162
		'calendar_updated' => time(),
1163
	));
1164
}
1165
1166
/**
1167
 * Remove an event
1168
 * removes an event.
1169
 * does no permission checks.
1170
 *
1171
 * @param int $event_id The ID of the event to remove
1172
 */
1173
function removeEvent($event_id)
1174
{
1175
	global $smcFunc;
1176
1177
	$smcFunc['db_query']('', '
1178
		DELETE FROM {db_prefix}calendar
1179
		WHERE id_event = {int:id_event}',
1180
		array(
1181
			'id_event' => $event_id,
1182
		)
1183
	);
1184
1185
	call_integration_hook('integrate_remove_event', array($event_id));
1186
1187
	updateSettings(array(
1188
		'calendar_updated' => time(),
1189
	));
1190
}
1191
1192
/**
1193
 * Gets all the events properties
1194
 *
1195
 * @param int $event_id The ID of the event
1196
 * @return array An array of event information
1197
 */
1198
function getEventProperties($event_id)
1199
{
1200
	global $smcFunc;
1201
1202
	$request = $smcFunc['db_query']('', '
1203
		SELECT
1204
			c.id_event, c.id_board, c.id_topic, c.id_member, c.title,
1205
			c.start_date, c.end_date, c.start_time, c.end_time, c.timezone, c.location,
1206
			t.id_first_msg, t.id_member_started,
1207
			mb.real_name, m.modified_time
1208
		FROM {db_prefix}calendar AS c
1209
			LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = c.id_topic)
1210
			LEFT JOIN {db_prefix}members AS mb ON (mb.id_member = t.id_member_started)
1211
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg  = t.id_first_msg)
1212
		WHERE c.id_event = {int:id_event}',
1213
		array(
1214
			'id_event' => $event_id,
1215
		)
1216
	);
1217
1218
	// If nothing returned, we are in poo, poo.
1219
	if ($smcFunc['db_num_rows']($request) === 0)
1220
		return false;
1221
1222
	$row = $smcFunc['db_fetch_assoc']($request);
1223
	$smcFunc['db_free_result']($request);
1224
1225
	list($start, $end, $allday, $span, $tz, $tz_abbrev) = buildEventDatetimes($row);
1226
1227
	// Sanity check
1228 View Code Duplication
	if (!empty($start['error_count']) || !empty($start['warning_count']) || !empty($end['error_count']) || !empty($end['warning_count']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1229
		return false;
1230
1231
	$return_value = array(
1232
		'boards' => array(),
1233
		'board' => $row['id_board'],
1234
		'new' => 0,
1235
		'eventid' => $event_id,
1236
		'year' => $start['year'],
1237
		'month' => $start['month'],
1238
		'day' => $start['day'],
1239
		'hour' => !$allday ? $start['hour'] : null,
1240
		'minute' => !$allday ? $start['minute'] : null,
1241
		'second' => !$allday ? $start['second'] : null,
1242
		'start_date' => $row['start_date'],
1243
		'start_date_local' => $start['date_local'],
1244
		'start_date_orig' => $start['date_orig'],
1245
		'start_time' => !$allday ? $row['start_time'] : null,
1246
		'start_time_local' => !$allday ? $start['time_local'] : null,
1247
		'start_time_orig' => !$allday ? $start['time_orig'] : null,
1248
		'start_timestamp' => $start['timestamp'],
1249
		'start_datetime' => $start['datetime'],
1250
		'start_iso_gmdate' => $start['iso_gmdate'],
1251
		'end_year' => $end['year'],
1252
		'end_month' => $end['month'],
1253
		'end_day' => $end['day'],
1254
		'end_hour' => !$allday ? $end['hour'] : null,
1255
		'end_minute' => !$allday ? $end['minute'] : null,
1256
		'end_second' => !$allday ? $end['second'] : null,
1257
		'end_date' => $row['end_date'],
1258
		'end_date_local' => $end['date_local'],
1259
		'end_date_orig' => $end['date_orig'],
1260
		'end_time' => !$allday ? $row['end_time'] : null,
1261
		'end_time_local' => !$allday ? $end['time_local'] : null,
1262
		'end_time_orig' => !$allday ? $end['time_orig'] : null,
1263
		'end_timestamp' => $end['timestamp'],
1264
		'end_datetime' => $end['datetime'],
1265
		'end_iso_gmdate' => $end['iso_gmdate'],
1266
		'allday' => $allday,
1267
		'tz' => !$allday ? $tz : null,
1268
		'tz_abbrev' => !$allday ? $tz_abbrev : null,
1269
		'span' => $span,
1270
		'title' => $row['title'],
1271
		'location' => $row['location'],
1272
		'member' => $row['id_member'],
1273
		'realname' => $row['real_name'],
1274
		'sequence' => $row['modified_time'],
1275
		'topic' => array(
1276
			'id' => $row['id_topic'],
1277
			'member_started' => $row['id_member_started'],
1278
			'first_msg' => $row['id_first_msg'],
1279
		),
1280
	);
1281
1282
	$return_value['last_day'] = (int) strftime('%d', mktime(0, 0, 0, $return_value['month'] == 12 ? 1 : $return_value['month'] + 1, 0, $return_value['month'] == 12 ? $return_value['year'] + 1 : $return_value['year']));
1283
1284
	return $return_value;
1285
}
1286
1287
/**
1288
 * Gets an initial set of date and time values for creating a new event.
1289
 *
1290
 * @return array An array containing an initial set of date and time values for an event.
1291
 */
1292
function getNewEventDatetimes()
1293
{
1294
	// Ensure setEventStartEnd() has something to work with
1295
	$now = date_create();
1296
	$_POST['year'] = !empty($_POST['year']) ? $_POST['year'] : date_format($now, 'Y');
1297
	$_POST['month'] = !empty($_POST['month']) ? $_POST['month'] : date_format($now, 'm');
1298
	$_POST['day'] = !empty($_POST['day']) ? $_POST['day'] : date_format($now, 'd');
1299
	$_POST['hour'] = !empty($_POST['hour']) ? $_POST['hour'] : date_format($now, 'H');
1300
	$_POST['minute'] = !empty($_POST['minute']) ? $_POST['minute'] : date_format($now, 'i');
1301
	$_POST['second'] = !empty($_POST['second']) ? $_POST['second'] : date_format($now, 's');
1302
1303
	// Set the basic values for the new event
1304
	$row_keys = array('start_date', 'end_date', 'start_time', 'end_time', 'timezone');
1305
	$row = array_combine($row_keys, setEventStartEnd());
1306
1307
	// And now set the full suite of values
1308
	list($start, $end, $allday, $span, $tz, $tz_abbrev) = buildEventDatetimes($row);
1309
1310
	// Default theme only uses some of this info, but others might want it all
1311
	$eventProperties = array(
1312
		'year' => $start['year'],
1313
		'month' => $start['month'],
1314
		'day' => $start['day'],
1315
		'hour' => !$allday ? $start['hour'] : null,
1316
		'minute' => !$allday ? $start['minute'] : null,
1317
		'second' => !$allday ? $start['second'] : null,
1318
		'start_date' => $row['start_date'],
1319
		'start_date_local' => $start['date_local'],
1320
		'start_date_orig' => $start['date_orig'],
1321
		'start_time' => !$allday ? $row['start_time'] : null,
1322
		'start_time_local' => !$allday ? $start['time_local'] : null,
1323
		'start_time_orig' => !$allday ? $start['time_orig'] : null,
1324
		'start_timestamp' => $start['timestamp'],
1325
		'start_datetime' => $start['datetime'],
1326
		'start_iso_gmdate' => $start['iso_gmdate'],
1327
		'end_year' => $end['year'],
1328
		'end_month' => $end['month'],
1329
		'end_day' => $end['day'],
1330
		'end_hour' => !$allday ? $end['hour'] : null,
1331
		'end_minute' => !$allday ? $end['minute'] : null,
1332
		'end_second' => !$allday ? $end['second'] : null,
1333
		'end_date' => $row['end_date'],
1334
		'end_date_local' => $end['date_local'],
1335
		'end_date_orig' => $end['date_orig'],
1336
		'end_time' => !$allday ? $row['end_time'] : null,
1337
		'end_time_local' => !$allday ? $end['time_local'] : null,
1338
		'end_time_orig' => !$allday ? $end['time_orig'] : null,
1339
		'end_timestamp' => $end['timestamp'],
1340
		'end_datetime' => $end['datetime'],
1341
		'end_iso_gmdate' => $end['iso_gmdate'],
1342
		'allday' => $allday,
1343
		'tz' => !$allday ? $tz : null,
1344
		'tz_abbrev' => !$allday ? $tz_abbrev : null,
1345
		'span' => $span,
1346
	);
1347
1348
	return $eventProperties;
1349
}
1350
1351
/**
1352
 * Set the start and end dates and times for a posted event for insertion into the database.
1353
 * Validates all date and times given to it.
1354
 * Makes sure events do not exceed the maximum allowed duration (if any).
1355
 * If passed an array that defines any time or date parameters, they will be used. Otherwise, gets the values from $_POST.
1356
 *
1357
 * @param array $eventOptions An array of optional time and date parameters (span, start_year, end_month, etc., etc.)
1358
 * @return array An array containing $start_date, $end_date, $start_time, $end_time
1359
 */
1360
function setEventStartEnd($eventOptions = array())
1361
{
1362
	global $modSettings;
1363
1364
	// Set $span, in case we need it
1365
	$span = isset($eventOptions['span']) ? $eventOptions['span'] : (isset($_POST['span']) ? $_POST['span'] : 0);
1366
	if ($span > 0)
1367
		$span = !empty($modSettings['cal_maxspan']) ? min($modSettings['cal_maxspan'], $span - 1) : $span - 1;
1368
1369
	// Define the timezone for this event, falling back to the default if not provided
1370
	if (!empty($eventOptions['tz']) && in_array($eventOptions['tz'], timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1371
		$tz = $eventOptions['tz'];
1372 View Code Duplication
	elseif (!empty($_POST['tz']) && in_array($_POST['tz'], timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1373
		$tz = $_POST['tz'];
1374
	else
1375
		$tz = getUserTimezone();
1376
1377
	// Is this supposed to be an all day event, or should it have specific start and end times?
1378
	if (isset($eventOptions['allday']))
1379
		$allday = $eventOptions['allday'];
1380
	elseif (empty($_POST['allday']))
1381
		$allday = false;
1382
	else
1383
		$allday = true;
1384
1385
	// Input might come as individual parameters...
1386
	$start_year = isset($eventOptions['year']) ? $eventOptions['year'] : (isset($_POST['year']) ? $_POST['year'] : null);
1387
	$start_month = isset($eventOptions['month']) ? $eventOptions['month'] : (isset($_POST['month']) ? $_POST['month'] : null);
1388
	$start_day = isset($eventOptions['day']) ? $eventOptions['day'] : (isset($_POST['day']) ? $_POST['day'] : null);
1389
	$start_hour = isset($eventOptions['hour']) ? $eventOptions['hour'] : (isset($_POST['hour']) ? $_POST['hour'] : null);
1390
	$start_minute = isset($eventOptions['minute']) ? $eventOptions['minute'] : (isset($_POST['minute']) ? $_POST['minute'] : null);
1391
	$start_second = isset($eventOptions['second']) ? $eventOptions['second'] : (isset($_POST['second']) ? $_POST['second'] : null);
1392
	$end_year = isset($eventOptions['end_year']) ? $eventOptions['end_year'] : (isset($_POST['end_year']) ? $_POST['end_year'] : null);
1393
	$end_month = isset($eventOptions['end_month']) ? $eventOptions['end_month'] : (isset($_POST['end_month']) ? $_POST['end_month'] : null);
1394
	$end_day = isset($eventOptions['end_day']) ? $eventOptions['end_day'] : (isset($_POST['end_day']) ? $_POST['end_day'] : null);
1395
	$end_hour = isset($eventOptions['end_hour']) ? $eventOptions['end_hour'] : (isset($_POST['end_hour']) ? $_POST['end_hour'] : null);
1396
	$end_minute = isset($eventOptions['end_minute']) ? $eventOptions['end_minute'] : (isset($_POST['end_minute']) ? $_POST['end_minute'] : null);
1397
	$end_second = isset($eventOptions['end_second']) ? $eventOptions['end_second'] : (isset($_POST['end_second']) ? $_POST['end_second'] : null);
1398
1399
	// ... or as datetime strings ...
1400
	$start_string = isset($eventOptions['start_datetime']) ? $eventOptions['start_datetime'] : (isset($_POST['start_datetime']) ? $_POST['start_datetime'] : null);
1401
	$end_string = isset($eventOptions['end_datetime']) ? $eventOptions['end_datetime'] : (isset($_POST['end_datetime']) ? $_POST['end_datetime'] : null);
1402
1403
	// ... or as date strings and time strings.
1404
	$start_date_string = isset($eventOptions['start_date']) ? $eventOptions['start_date'] : (isset($_POST['start_date']) ? $_POST['start_date'] : null);
1405
	$start_time_string = isset($eventOptions['start_time']) ? $eventOptions['start_time'] : (isset($_POST['start_time']) ? $_POST['start_time'] : null);
1406
	$end_date_string = isset($eventOptions['end_date']) ? $eventOptions['end_date'] : (isset($_POST['end_date']) ? $_POST['end_date'] : null);
1407
	$end_time_string = isset($eventOptions['end_time']) ? $eventOptions['end_time'] : (isset($_POST['end_time']) ? $_POST['end_time'] : null);
1408
1409
	// If the date and time were given in separate strings, combine them
1410
	if (empty($start_string) && isset($start_date_string))
1411
		$start_string = $start_date_string . (isset($start_time_string) ? ' ' . $start_time_string : '');
1412
	if (empty($end_string) && isset($end_date_string))
1413
		$end_string = $end_date_string . (isset($end_time_string) ? ' ' . $end_time_string : '');
1414
1415
	// If some form of string input was given, override individually defined options with it
1416 View Code Duplication
	if (isset($start_string))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1417
	{
1418
		$start_string_parsed = date_parse($start_string);
1419
		if (empty($start_string_parsed['error_count']) && empty($start_string_parsed['warning_count']))
1420
		{
1421
			if ($start_string_parsed['year'] != false)
1422
			{
1423
				$start_year = $start_string_parsed['year'];
1424
				$start_month = $start_string_parsed['month'];
1425
				$start_day = $start_string_parsed['day'];
1426
			}
1427
			if ($start_string_parsed['hour'] != false)
1428
			{
1429
				$start_hour = $start_string_parsed['hour'];
1430
				$start_minute = $start_string_parsed['minute'];
1431
				$start_second = $start_string_parsed['second'];
1432
			}
1433
		}
1434
	}
1435 View Code Duplication
	if (isset($end_string))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1436
	{
1437
		$end_string_parsed = date_parse($end_string);
1438
		if (empty($end_string_parsed['error_count']) && empty($end_string_parsed['warning_count']))
1439
		{
1440
			if ($end_string_parsed['year'] != false)
1441
			{
1442
				$end_year = $end_string_parsed['year'];
1443
				$end_month = $end_string_parsed['month'];
1444
				$end_day = $end_string_parsed['day'];
1445
			}
1446
			if ($end_string_parsed['hour'] != false)
1447
			{
1448
				$end_hour = $end_string_parsed['hour'];
1449
				$end_minute = $end_string_parsed['minute'];
1450
				$end_second = $end_string_parsed['second'];
1451
			}
1452
		}
1453
	}
1454
1455
	// Validate input
1456
	$start_date_isvalid = checkdate($start_month, $start_day, $start_year);
1457
	$end_date_isvalid = checkdate($end_month, $end_day, $end_year);
1458
1459
	$start_time_isset = (isset($start_hour) && isset($start_minute) && isset($start_second));
1460
	$d = date_parse(sprintf('%02d:%02d:%02d', $start_hour, $start_minute, $start_second));
1461
	$start_time_isvalid = ($d['error_count'] == 0 && $d['warning_count'] == 0) ? true : false;
1462
1463
	$end_time_isset = (isset($end_hour) && isset($end_minute) && isset($end_second));
1464
	$d = date_parse(sprintf('%02d:%02d:%02d', $end_hour, $end_minute, $end_second));
1465
	$end_time_isvalid = ($d['error_count'] == 0 && $d['warning_count'] == 0) ? true : false;
1466
1467
	// Uh-oh...
1468
	if ($start_date_isvalid === false)
1469
	{
1470
		fatal_lang_error('invalid_date', false);
1471
	}
1472
1473
	// Make sure we use valid values for everything
1474
	if ($end_date_isvalid === false)
1475
	{
1476
		$end_year = $start_year;
1477
		$end_month = $start_month;
1478
		$end_day = $start_day;
1479
	}
1480
1481
	if ($allday === true || $start_time_isset === false || $start_time_isvalid === false)
1482
	{
1483
		$allday = true;
1484
		$start_hour = 0;
1485
		$start_minute = 0;
1486
		$start_second = 0;
1487
	}
1488
1489
	if ($allday === true || $end_time_isvalid === false || $end_time_isset === false)
1490
	{
1491
		$end_hour = $start_hour;
1492
		$end_minute = $start_minute;
1493
		$end_second = $start_second;
1494
	}
1495
1496
	// Now create our datetime objects
1497
	$start_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, $start_hour, $start_minute, $start_second) . ' ' . $tz);
1498
	$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $end_year, $end_month, $end_day, $end_hour, $end_minute, $end_second) . ' ' . $tz);
1499
1500
	// Is $end_object too early?
1501
	if ($start_object >= $end_object)
1502
	{
1503
		$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, $start_hour, $start_minute, $start_second) . ' ' . $tz);
1504
		if ($span > 0)
1505
			date_add($end_object, date_interval_create_from_date_string($span . ' days'));
1506
		else
1507
			date_add($end_object, date_interval_create_from_date_string('1 hour'));
1508
	}
1509
1510
	// Is $end_object too late?
1511
	if (!empty($modSettings['cal_maxspan']))
1512
	{
1513
		$date_diff = date_diff($start_object, $end_object);
1514
		if ($date_diff->days > $modSettings['cal_maxspan'])
1515
		{
1516
			if ($modSettings['cal_maxspan'] > 1)
1517
			{
1518
				$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, $start_hour, $start_minute, $start_second) . ' ' . $tz);
1519
				date_add($end_object, date_interval_create_from_date_string($modSettings['cal_maxspan'] . ' days'));
1520
			}
1521
			else
1522
				$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, '11', '59', '59') . ' ' . $tz);
1523
		}
1524
	}
1525
1526
	// Finally, make our strings
1527
	$start_date = date_format($start_object, 'Y-m-d');
1528
	$end_date = date_format($end_object, 'Y-m-d');
1529
1530
	if ($allday == true)
1531
	{
1532
		$start_time = null;
1533
		$end_time = null;
1534
		$tz = null;
1535
	}
1536
	else
1537
	{
1538
		$start_time = date_format($start_object, 'H:i:s');
1539
		$end_time = date_format($end_object, 'H:i:s');
1540
	}
1541
1542
	return array($start_date, $end_date, $start_time, $end_time, $tz);
1543
}
1544
1545
/**
1546
 * Helper function for getEventRange, getEventProperties, getNewEventDatetimes, etc.
1547
 *
1548
 * @param array $row A database row representing an event from the calendar table
1549
 * @return array An array containing the start and end date and time properties for the event
1550
 */
1551
function buildEventDatetimes($row)
1552
{
1553
	global $sourcedir, $user_info;
1554
	static $date_format = '', $time_format = '';
1555
1556
	require_once($sourcedir . '/Subs.php');
1557
	static $timezone_array = array();
1558
1559
	// First, try to create a better date format, ignoring the "time" elements.
1560
	if (empty($date_format))
1561
	{
1562
		if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
1563
			$date_format = '%F';
1564
		else
1565
			$date_format = $matches[0];
1566
	}
1567
1568
	// We want a fairly compact version of the time, but as close as possible to the user's settings.
1569 View Code Duplication
	if (empty($time_format))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1570
	{
1571
		if (preg_match('~%[HkIlMpPrRSTX](?:[^%]*%[HkIlMpPrRSTX])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
1572
			$time_format = '%k:%M';
1573
		else
1574
			$time_format = str_replace(array('%I', '%H', '%S', '%r', '%R', '%T'), array('%l', '%k', '', '%l:%M %p', '%k:%M', '%l:%M'), $matches[0]);
1575
	}
1576
1577
	// Should this be an all day event?
1578
	$allday = (empty($row['start_time']) || empty($row['end_time']) || empty($row['timezone']) || !in_array($row['timezone'], timezone_identifiers_list(DateTimeZone::ALL_WITH_BC))) ? true : false;
1579
1580
	// How many days does this event span?
1581
	$span = 1 + date_interval_format(date_diff(date_create($row['start_date']), date_create($row['end_date'])), '%d');
1582
1583
	// We need to have a defined timezone in the steps below
1584
	if (empty($row['timezone']))
1585
		$row['timezone'] = getUserTimezone();
1586
1587
	if (empty($timezone_array[$row['timezone']]))
1588
		$timezone_array[$row['timezone']] = timezone_open($row['timezone']);
1589
1590
	// Get most of the standard date information for the start and end datetimes
1591
	$start = date_parse($row['start_date'] . (!$allday ? ' ' . $row['start_time'] : ''));
1592
	$end = date_parse($row['end_date'] . (!$allday ? ' ' . $row['end_time'] : ''));
1593
1594
	// But we also want more info, so make some DateTime objects we can use
1595
	$start_object = date_create($row['start_date'] . (!$allday ? ' ' . $row['start_time'] : ''), $timezone_array[$row['timezone']]);
1596
	$end_object = date_create($row['end_date'] . (!$allday ? ' ' . $row['end_time'] : ''), $timezone_array[$row['timezone']]);
1597
1598
	// Unix timestamps are good
1599
	$start['timestamp'] = date_format($start_object, 'U');
1600
	$end['timestamp'] = date_format($end_object, 'U');
1601
1602
	// Datetime string without timezone  (e.g. '2016-12-28 22:45:30')
1603
	$start['datetime'] = date_format($start_object, 'Y-m-d H:i:s');
1604
	$end['datetime'] = date_format($start_object, 'Y-m-d H:i:s');
1605
1606
	// ISO formatted datetime string, relative to UTC (e.g. '2016-12-29T05:45:30+00:00')
1607
	$start['iso_gmdate'] = gmdate('c', $start['timestamp']);
1608
	$end['iso_gmdate'] = gmdate('c', $end['timestamp']);
1609
1610
	// Strings showing the datetimes in the user's preferred format, relative to the user's time zone
1611
	list($start['date_local'], $start['time_local']) = explode(' § ', timeformat($start['timestamp'], $date_format . ' § ' . $time_format));
0 ignored issues
show
Documentation introduced by
$date_format . ' § ' . $time_format is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1612
	list($end['date_local'], $end['time_local']) = explode(' § ', timeformat($end['timestamp'], $date_format . ' § ' . $time_format));
0 ignored issues
show
Documentation introduced by
$date_format . ' § ' . $time_format is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1613
1614
	// Strings showing the datetimes in the user's preferred format, relative to the event's time zone
1615
	list($start['date_orig'], $start['time_orig']) = explode(' § ', timeformat($start['timestamp'], $date_format . ' § ' . $time_format, 'none'));
0 ignored issues
show
Documentation introduced by
$date_format . ' § ' . $time_format is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1616
	list($end['date_orig'], $end['time_orig']) = explode(' § ', timeformat($end['timestamp'], $date_format . ' § ' . $time_format, 'none'));
0 ignored issues
show
Documentation introduced by
$date_format . ' § ' . $time_format is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1617
1618
	// The time zone identifier (e.g. 'Europe/London') and abbreviation (e.g. 'GMT')
1619
	$tz = date_format($start_object, 'e');
1620
	$tz_abbrev = fix_tz_abbrev($row['timezone'], date_format($start_object, 'T'));
1621
1622
	return array($start, $end, $allday, $span, $tz, $tz_abbrev);
1623
}
1624
1625
/**
1626
 * Gets a member's selected timezone identifier directly from the database
1627
 *
1628
 * @param int $id_member The member id to look up. If not provided, the current user's id will be used.
1629
 * @return string The timezone identifier string for the user's timezone.
1630
 */
1631
function getUserTimezone($id_member = null)
1632
{
1633
	global $smcFunc, $context, $user_info, $modSettings, $user_settings;
1634
	static $member_cache = array();
1635
1636
	if (is_null($id_member) && $user_info['is_guest'] == false)
1637
		$id_member = $context['user']['id'];
1638
1639
	//check if the cache got the data
1640
	if (isset($id_member) && isset($member_cache[$id_member]))
1641
	{
1642
		return $member_cache[$id_member];
1643
	}
1644
1645
	//maybe the current user is the one
1646
	if (isset($user_settings['id_member']) && $user_settings['id_member'] == $id_member)
1647
	{
1648
		$member_cache[$id_member] = $user_settings['timezone'];
1649
		return $user_settings['timezone'];
1650
	}
1651
1652 View Code Duplication
	if (isset($id_member))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1653
	{
1654
		$request = $smcFunc['db_query']('', '
1655
			SELECT timezone
1656
			FROM {db_prefix}members
1657
			WHERE id_member = {int:id_member}',
1658
			array(
1659
				'id_member' => $id_member,
1660
			)
1661
		);
1662
		list($timezone) = $smcFunc['db_fetch_row']($request);
1663
		$smcFunc['db_free_result']($request);
1664
	}
1665
1666 View Code Duplication
	if (empty($timezone) || !in_array($timezone, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1667
		$timezone = isset($modSettings['default_timezone']) ? $modSettings['default_timezone'] : date_default_timezone_get();
1668
1669
	if (isset($id_member))
1670
		$member_cache[$id_member] = $timezone;
1671
1672
	return $timezone;
1673
}
1674
1675
/**
1676
 * Gets all of the holidays for the listing
1677
 *
1678
 * @param int $start The item to start with (for pagination purposes)
1679
 * @param int $items_per_page How many items to show on each page
1680
 * @param string $sort A string indicating how to sort the results
1681
 * @return array An array of holidays, each of which is an array containing the id, year, month, day and title of the holiday
1682
 */
1683 View Code Duplication
function list_getHolidays($start, $items_per_page, $sort)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1684
{
1685
	global $smcFunc;
1686
1687
	$request = $smcFunc['db_query']('', '
1688
		SELECT id_holiday, YEAR(event_date) AS year, MONTH(event_date) AS month, DAYOFMONTH(event_date) AS day, title
1689
		FROM {db_prefix}calendar_holidays
1690
		ORDER BY {raw:sort}
1691
		LIMIT {int:start}, {int:max}',
1692
		array(
1693
			'sort' => $sort,
1694
			'start' => $start,
1695
			'max' => $items_per_page,
1696
		)
1697
	);
1698
	$holidays = array();
1699
	while ($row = $smcFunc['db_fetch_assoc']($request))
1700
		$holidays[] = $row;
1701
	$smcFunc['db_free_result']($request);
1702
1703
	return $holidays;
1704
}
1705
1706
/**
1707
 * Helper function to get the total number of holidays
1708
 *
1709
 * @return int The total number of holidays
1710
 */
1711
function list_getNumHolidays()
1712
{
1713
	global $smcFunc;
1714
1715
	$request = $smcFunc['db_query']('', '
1716
		SELECT COUNT(*)
1717
		FROM {db_prefix}calendar_holidays',
1718
		array(
1719
		)
1720
	);
1721
	list($num_items) = $smcFunc['db_fetch_row']($request);
1722
	$smcFunc['db_free_result']($request);
1723
1724
	return (int) $num_items;
1725
}
1726
1727
/**
1728
 * Remove a holiday from the calendar
1729
 *
1730
 * @param array $holiday_ids An array of IDs of holidays to delete
1731
 */
1732
function removeHolidays($holiday_ids)
1733
{
1734
	global $smcFunc;
1735
1736
	$smcFunc['db_query']('', '
1737
		DELETE FROM {db_prefix}calendar_holidays
1738
		WHERE id_holiday IN ({array_int:id_holiday})',
1739
		array(
1740
			'id_holiday' => $holiday_ids,
1741
		)
1742
	);
1743
1744
	updateSettings(array(
1745
		'calendar_updated' => time(),
1746
	));
1747
}
1748
1749
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...