Completed
Push — release-2.1 ( af8dd6...5b3c87 )
by Michael
20:47 queued 10:04
created

Subs-Calendar.php ➔ getUserTimezone()   C

Complexity

Conditions 12
Paths 28

Size

Total Lines 43
Code Lines 21

Duplication

Lines 15
Ratio 34.88 %

Importance

Changes 0
Metric Value
cc 12
eloc 21
nc 28
nop 1
dl 15
loc 43
rs 5.1612
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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)
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
	require_once($sourcedir . '/Subs.php');
134
135
	$low_object = date_create($low_date);
136
	$high_object = date_create($high_date);
137
138
	// Find all the calendar info...
139
	$result = $smcFunc['db_query']('calendar_get_events', '
140
		SELECT
141
			cal.id_event, cal.title, cal.id_member, cal.id_topic, cal.id_board,
142
			cal.start_date, cal.end_date, cal.start_time, cal.end_time, cal.timezone, cal.location,
143
			b.member_groups, t.id_first_msg, t.approved, b.id_board
144
		FROM {db_prefix}calendar AS cal
145
			LEFT JOIN {db_prefix}boards AS b ON (b.id_board = cal.id_board)
146
			LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = cal.id_topic)
147
		WHERE cal.start_date <= {date:high_date}
148
			AND cal.end_date >= {date:low_date}' . ($use_permissions ? '
149
			AND (cal.id_board = {int:no_board_link} OR {query_wanna_see_board})' : ''),
150
		array(
151
			'high_date' => $high_date,
152
			'low_date' => $low_date,
153
			'no_board_link' => 0,
154
		)
155
	);
156
	$events = array();
157
	while ($row = $smcFunc['db_fetch_assoc']($result))
158
	{
159
		// If the attached topic is not approved then for the moment pretend it doesn't exist
160
		if (!empty($row['id_first_msg']) && $modSettings['postmod_active'] && !$row['approved'])
161
			continue;
162
163
		// Force a censor of the title - as often these are used by others.
164
		censorText($row['title'], $use_permissions ? false : true);
165
166
		// Get the various time and date properties for this event
167
		list($start, $end, $allday, $span, $tz, $tz_abbrev) = buildEventDatetimes($row);
168
169
		// Sanity check
170 View Code Duplication
		if (!empty($start['error_count']) || !empty($start['warning_count']) || !empty($end['error_count']) || !empty($end['warning_count']))
171
			continue;
172
173
		// Get set up for the loop
174
		$start_object = date_create($row['start_date'] . (!$allday ? ' ' . $row['start_time'] : ''), timezone_open($tz));
175
		$end_object = date_create($row['end_date'] . (!$allday ? ' ' . $row['end_time'] : ''), timezone_open($tz));
176
		date_timezone_set($start_object, timezone_open(date_default_timezone_get()));
177
		date_timezone_set($end_object, timezone_open(date_default_timezone_get()));
178
		date_time_set($start_object, 0, 0, 0);
179
		date_time_set($end_object, 0, 0, 0);
180
		$start_date_string = date_format($start_object, 'Y-m-d');
181
		$end_date_string = date_format($end_object, 'Y-m-d');
182
183
		$cal_date = ($start_object >= $low_object) ? $start_object : $low_object;
184
		while ($cal_date <= $end_object && $cal_date <= $high_object)
185
		{
186
			$starts_today = (date_format($cal_date, 'Y-m-d') == $start_date_string);
187
			$ends_today = (date_format($cal_date, 'Y-m-d') == $end_date_string);
188
189
			$eventProperties = array(
190
					'id' => $row['id_event'],
191
					'title' => $row['title'],
192
					'year' => $start['year'],
193
					'month' => $start['month'],
194
					'day' => $start['day'],
195
					'hour' => !$allday ? $start['hour'] : null,
196
					'minute' => !$allday ? $start['minute'] : null,
197
					'second' => !$allday ? $start['second'] : null,
198
					'start_date' => $row['start_date'],
199
					'start_date_local' => $start['date_local'],
200
					'start_date_orig' => $start['date_orig'],
201
					'start_time' => !$allday ? $row['start_time'] : null,
202
					'start_time_local' => !$allday ? $start['time_local'] : null,
203
					'start_time_orig' => !$allday ? $start['time_orig'] : null,
204
					'start_timestamp' => $start['timestamp'],
205
					'start_datetime' => $start['datetime'],
206
					'start_iso_gmdate' => $start['iso_gmdate'],
207
					'end_year' => $end['year'],
208
					'end_month' => $end['month'],
209
					'end_day' => $end['day'],
210
					'end_hour' => !$allday ? $end['hour'] : null,
211
					'end_minute' => !$allday ? $end['minute'] : null,
212
					'end_second' => !$allday ? $end['second'] : null,
213
					'end_date' => $row['end_date'],
214
					'end_date_local' => $end['date_local'],
215
					'end_date_orig' => $end['date_orig'],
216
					'end_time' => !$allday ? $row['end_time'] : null,
217
					'end_time_local' => !$allday ? $end['time_local'] : null,
218
					'end_time_orig' => !$allday ? $end['time_orig'] : null,
219
					'end_timestamp' => $end['timestamp'],
220
					'end_datetime' => $end['datetime'],
221
					'end_iso_gmdate' => $end['iso_gmdate'],
222
					'allday' => $allday,
223
					'tz' => !$allday ? $tz : null,
224
					'tz_abbrev' => !$allday ? $tz_abbrev : null,
225
					'span' => $span,
226
					'is_last' => false,
227
					'id_board' => $row['id_board'],
228
					'is_selected' => !empty($context['selected_event']) && $context['selected_event'] == $row['id_event'],
229
					'starts_today' => $starts_today,
230
					'ends_today' => $ends_today,
231
					'location' => $row['location'],
232
			);
233
234
			// If we're using permissions (calendar pages?) then just ouput normal contextual style information.
235
			if ($use_permissions)
236
				$events[date_format($cal_date, 'Y-m-d')][] = array_merge($eventProperties, array(
237
					'href' => $row['id_board'] == 0 ? '' : $scripturl . '?topic=' . $row['id_topic'] . '.0',
238
					'link' => $row['id_board'] == 0 ? $row['title'] : '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['title'] . '</a>',
239
					'can_edit' => allowedTo('calendar_edit_any') || ($row['id_member'] == $user_info['id'] && allowedTo('calendar_edit_own')),
240
					'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'],
241
					'can_export' => !empty($modSettings['cal_export']) ? true : false,
242
					'export_href' => $scripturl . '?action=calendar;sa=ical;eventid=' . $row['id_event'] . ';' . $context['session_var'] . '=' . $context['session_id'],
243
				));
244
			// Otherwise, this is going to be cached and the VIEWER'S permissions should apply... just put together some info.
245
			else
246
				$events[date_format($cal_date, 'Y-m-d')][] = array_merge($eventProperties, array(
247
					'href' => $row['id_topic'] == 0 ? '' : $scripturl . '?topic=' . $row['id_topic'] . '.0',
248
					'link' => $row['id_topic'] == 0 ? $row['title'] : '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['title'] . '</a>',
249
					'can_edit' => false,
250
					'can_export' => !empty($modSettings['cal_export']) ? true : false,
251
					'topic' => $row['id_topic'],
252
					'msg' => $row['id_first_msg'],
253
					'poster' => $row['id_member'],
254
					'allowed_groups' => explode(',', $row['member_groups']),
255
				));
256
257
			date_add($cal_date, date_interval_create_from_date_string('1 day'));
258
		}
259
	}
260
	$smcFunc['db_free_result']($result);
261
262
	// If we're doing normal contextual data, go through and make things clear to the templates ;).
263
	if ($use_permissions)
264
	{
265 View Code Duplication
		foreach ($events as $mday => $array)
266
			$events[$mday][count($array) - 1]['is_last'] = true;
267
	}
268
269
	ksort($events);
270
271
	return $events;
272
}
273
274
/**
275
 * Get all holidays within the given time range.
276
 *
277
 * @param string $low_date The low end of the range, inclusive, in YYYY-MM-DD format
278
 * @param string $high_date The high end of the range, inclusive, in YYYY-MM-DD format
279
 * @return array An array of days, which are all arrays of holiday names.
280
 */
281
function getHolidayRange($low_date, $high_date)
282
{
283
	global $smcFunc;
284
285
	// Get the lowest and highest dates for "all years".
286
	if (substr($low_date, 0, 4) != substr($high_date, 0, 4))
287
		$allyear_part = 'event_date BETWEEN {date:all_year_low} AND {date:all_year_dec}
288
			OR event_date BETWEEN {date:all_year_jan} AND {date:all_year_high}';
289
	else
290
		$allyear_part = 'event_date BETWEEN {date:all_year_low} AND {date:all_year_high}';
291
292
	// Find some holidays... ;).
293
	$result = $smcFunc['db_query']('', '
294
		SELECT event_date, YEAR(event_date) AS year, title
295
		FROM {db_prefix}calendar_holidays
296
		WHERE event_date BETWEEN {date:low_date} AND {date:high_date}
297
			OR ' . $allyear_part,
298
		array(
299
			'low_date' => $low_date,
300
			'high_date' => $high_date,
301
			'all_year_low' => '1004' . substr($low_date, 4),
302
			'all_year_high' => '1004' . substr($high_date, 4),
303
			'all_year_jan' => '1004-01-01',
304
			'all_year_dec' => '1004-12-31',
305
		)
306
	);
307
	$holidays = array();
308
	while ($row = $smcFunc['db_fetch_assoc']($result))
309
	{
310
		if (substr($low_date, 0, 4) != substr($high_date, 0, 4))
311
			$event_year = substr($row['event_date'], 5) < substr($high_date, 5) ? substr($high_date, 0, 4) : substr($low_date, 0, 4);
312
		else
313
			$event_year = substr($low_date, 0, 4);
314
315
		$holidays[$event_year . substr($row['event_date'], 4)][] = $row['title'];
316
	}
317
	$smcFunc['db_free_result']($result);
318
319
	ksort($holidays);
320
321
	return $holidays;
322
}
323
324
/**
325
 * Does permission checks to see if an event can be linked to a board/topic.
326
 * checks if the current user can link the current topic to the calendar, permissions et al.
327
 * this requires the calendar_post permission, a forum moderator, or a topic starter.
328
 * expects the $topic and $board variables to be set.
329
 * if the user doesn't have proper permissions, an error will be shown.
330
 */
331
function canLinkEvent()
332
{
333
	global $user_info, $topic, $board, $smcFunc;
334
335
	// If you can't post, you can't link.
336
	isAllowedTo('calendar_post');
337
338
	// No board?  No topic?!?
339
	if (empty($board))
340
		fatal_lang_error('missing_board_id', false);
341
	if (empty($topic))
342
		fatal_lang_error('missing_topic_id', false);
343
344
	// Administrator, Moderator, or owner.  Period.
345
	if (!allowedTo('admin_forum') && !allowedTo('moderate_board'))
346
	{
347
		// Not admin or a moderator of this board. You better be the owner - or else.
348
		$result = $smcFunc['db_query']('', '
349
			SELECT id_member_started
350
			FROM {db_prefix}topics
351
			WHERE id_topic = {int:current_topic}
352
			LIMIT 1',
353
			array(
354
				'current_topic' => $topic,
355
			)
356
		);
357
		if ($row = $smcFunc['db_fetch_assoc']($result))
358
		{
359
			// Not the owner of the topic.
360
			if ($row['id_member_started'] != $user_info['id'])
361
				fatal_lang_error('not_your_topic', 'user');
362
		}
363
		// Topic/Board doesn't exist.....
364
		else
365
			fatal_lang_error('calendar_no_topic', 'general');
366
		$smcFunc['db_free_result']($result);
367
	}
368
}
369
370
/**
371
 * Returns date information about 'today' relative to the users time offset.
372
 * returns an array with the current date, day, month, and year.
373
 * takes the users time offset into account.
374
 * @return array An array of info about today, based on forum time. Has 'day', 'month', 'year' and 'date' (in YYYY-MM-DD format)
375
 */
376
function getTodayInfo()
377
{
378
	return array(
379
		'day' => (int) strftime('%d', forum_time()),
380
		'month' => (int) strftime('%m', forum_time()),
381
		'year' => (int) strftime('%Y', forum_time()),
382
		'date' => strftime('%Y-%m-%d', forum_time()),
383
	);
384
}
385
386
/**
387
 * Provides information (link, month, year) about the previous and next month.
388
 * @param int $month The month to display
389
 * @param int $year The year
390
 * @param array $calendarOptions An array of calendar options
391
 * @param bool $is_previous Whether this is the previous month
392
 * @return array A large array containing all the information needed to show a calendar grid for the given month
393
 */
394
function getCalendarGrid($month, $year, $calendarOptions, $is_previous = false)
395
{
396
	global $scripturl, $modSettings;
397
398
	// Eventually this is what we'll be returning.
399
	$calendarGrid = array(
400
		'week_days' => array(),
401
		'weeks' => array(),
402
		'short_day_titles' => !empty($calendarOptions['short_day_titles']),
403
		'short_month_titles' => !empty($calendarOptions['short_month_titles']),
404
		'highlight' => array(
405
			'events' => !empty($calendarOptions['highlight']['events']) && !empty($calendarOptions['show_events']) ? $calendarOptions['highlight']['events'] : 0,
406
			'holidays' => !empty($calendarOptions['highlight']['holidays']) && !empty($calendarOptions['show_holidays']) ? $calendarOptions['highlight']['holidays'] : 0,
407
			'birthdays' => !empty($calendarOptions['highlight']['birthdays']) && !empty($calendarOptions['show_birthdays']) ? $calendarOptions['highlight']['birthdays'] : 0,
408
		),
409
		'current_month' => $month,
410
		'current_year' => $year,
411
		'show_next_prev' => !empty($calendarOptions['show_next_prev']),
412
		'show_week_links' => isset($calendarOptions['show_week_links']) ? $calendarOptions['show_week_links'] : 0,
413
		'previous_calendar' => array(
414
			'year' => $month == 1 ? $year - 1 : $year,
415
			'month' => $month == 1 ? 12 : $month - 1,
416
			'disabled' => $modSettings['cal_minyear'] > ($month == 1 ? $year - 1 : $year),
417
		),
418
		'next_calendar' => array(
419
			'year' => $month == 12 ? $year + 1 : $year,
420
			'month' => $month == 12 ? 1 : $month + 1,
421
			'disabled' => $modSettings['cal_maxyear'] < ($month == 12 ? $year + 1 : $year),
422
		),
423
		'size' => empty($modSettings['cal_display_type']) ? 'large' : 'small',
424
	);
425
426
	// Get today's date.
427
	$today = getTodayInfo();
428
429
	// Get information about this month.
430
	$month_info = array(
431
		'first_day' => array(
432
			'day_of_week' => (int) strftime('%w', mktime(0, 0, 0, $month, 1, $year)),
433
			'week_num' => (int) strftime('%U', mktime(0, 0, 0, $month, 1, $year)),
434
			'date' => strftime('%Y-%m-%d', mktime(0, 0, 0, $month, 1, $year)),
435
		),
436
		'last_day' => array(
437
			'day_of_month' => (int) strftime('%d', mktime(0, 0, 0, $month == 12 ? 1 : $month + 1, 0, $month == 12 ? $year + 1 : $year)),
438
			'date' => strftime('%Y-%m-%d', mktime(0, 0, 0, $month == 12 ? 1 : $month + 1, 0, $month == 12 ? $year + 1 : $year)),
439
		),
440
		'first_day_of_year' => (int) strftime('%w', mktime(0, 0, 0, 1, 1, $year)),
441
		'first_day_of_next_year' => (int) strftime('%w', mktime(0, 0, 0, 1, 1, $year + 1)),
442
	);
443
444
	// The number of days the first row is shifted to the right for the starting day.
445
	$nShift = $month_info['first_day']['day_of_week'];
446
447
	$calendarOptions['start_day'] = empty($calendarOptions['start_day']) ? 0 : (int) $calendarOptions['start_day'];
448
449
	// Starting any day other than Sunday means a shift...
450
	if (!empty($calendarOptions['start_day']))
451
	{
452
		$nShift -= $calendarOptions['start_day'];
453
		if ($nShift < 0)
454
			$nShift = 7 + $nShift;
455
	}
456
457
	// Number of rows required to fit the month.
458
	$nRows = floor(($month_info['last_day']['day_of_month'] + $nShift) / 7);
459
	if (($month_info['last_day']['day_of_month'] + $nShift) % 7)
460
		$nRows++;
461
462
	// Fetch the arrays for birthdays, posted events, and holidays.
463
	$bday = $calendarOptions['show_birthdays'] ? getBirthdayRange($month_info['first_day']['date'], $month_info['last_day']['date']) : array();
464
	$events = $calendarOptions['show_events'] ? getEventRange($month_info['first_day']['date'], $month_info['last_day']['date']) : array();
465
	$holidays = $calendarOptions['show_holidays'] ? getHolidayRange($month_info['first_day']['date'], $month_info['last_day']['date']) : array();
466
467
	// Days of the week taking into consideration that they may want it to start on any day.
468
	$count = $calendarOptions['start_day'];
469
	for ($i = 0; $i < 7; $i++)
470
	{
471
		$calendarGrid['week_days'][] = $count;
472
		$count++;
473
		if ($count == 7)
474
			$count = 0;
475
	}
476
477
	// Iterate through each week.
478
	$calendarGrid['weeks'] = array();
479
	for ($nRow = 0; $nRow < $nRows; $nRow++)
480
	{
481
		// Start off the week - and don't let it go above 52, since that's the number of weeks in a year.
482
		$calendarGrid['weeks'][$nRow] = array(
483
			'days' => array(),
484
		);
485
486
		// And figure out all the days.
487
		for ($nCol = 0; $nCol < 7; $nCol++)
488
		{
489
			$nDay = ($nRow * 7) + $nCol - $nShift + 1;
490
491
			if ($nDay < 1 || $nDay > $month_info['last_day']['day_of_month'])
492
				$nDay = 0;
493
494
			$date = sprintf('%04d-%02d-%02d', $year, $month, $nDay);
495
496
			$calendarGrid['weeks'][$nRow]['days'][$nCol] = array(
497
				'day' => $nDay,
498
				'date' => $date,
499
				'is_today' => $date == $today['date'],
500
				'is_first_day' => !empty($calendarOptions['show_week_num']) && (($month_info['first_day']['day_of_week'] + $nDay - 1) % 7 == $calendarOptions['start_day']),
501
				'is_first_of_month' => $nDay === 1,
502
				'holidays' => !empty($holidays[$date]) ? $holidays[$date] : array(),
503
				'events' => !empty($events[$date]) ? $events[$date] : array(),
504
				'birthdays' => !empty($bday[$date]) ? $bday[$date] : array(),
505
			);
506
		}
507
	}
508
509
	// What is the last day of the month?
510
	if ($is_previous === true)
511
		$calendarGrid['last_of_month'] = $month_info['last_day']['day_of_month'];
512
513
	// We'll use the shift in the template.
514
	$calendarGrid['shift'] = $nShift;
515
516
	// Set the previous and the next month's links.
517
	$calendarGrid['previous_calendar']['href'] = $scripturl . '?action=calendar;viewmonth;year=' . $calendarGrid['previous_calendar']['year'] . ';month=' . $calendarGrid['previous_calendar']['month'];
518
	$calendarGrid['next_calendar']['href'] = $scripturl . '?action=calendar;viewmonth;year=' . $calendarGrid['next_calendar']['year'] . ';month=' . $calendarGrid['next_calendar']['month'];
519
520
	return $calendarGrid;
521
}
522
523
/**
524
 * Returns the information needed to show a calendar for the given week.
525
 * @param int $month The month
526
 * @param int $year The year
527
 * @param int $day The day
528
 * @param array $calendarOptions An array of calendar options
529
 * @return array An array of information needed to display the grid for a single week on the calendar
530
 */
531
function getCalendarWeek($month, $year, $day, $calendarOptions)
532
{
533
	global $scripturl, $modSettings, $txt;
534
535
	// Get today's date.
536
	$today = getTodayInfo();
537
538
	// What is the actual "start date" for the passed day.
539
	$calendarOptions['start_day'] = empty($calendarOptions['start_day']) ? 0 : (int) $calendarOptions['start_day'];
540
	$day_of_week = (int) strftime('%w', mktime(0, 0, 0, $month, $day, $year));
541
	if ($day_of_week != $calendarOptions['start_day'])
542
	{
543
		// Here we offset accordingly to get things to the real start of a week.
544
		$date_diff = $day_of_week - $calendarOptions['start_day'];
545
		if ($date_diff < 0)
546
			$date_diff += 7;
547
		$new_timestamp = mktime(0, 0, 0, $month, $day, $year) - $date_diff * 86400;
548
		$day = (int) strftime('%d', $new_timestamp);
549
		$month = (int) strftime('%m', $new_timestamp);
550
		$year = (int) strftime('%Y', $new_timestamp);
551
	}
552
553
	// Now start filling in the calendar grid.
554
	$calendarGrid = array(
555
		'show_next_prev' => !empty($calendarOptions['show_next_prev']),
556
		// Previous week is easy - just step back one day.
557
		'previous_week' => array(
558
			'year' => $day == 1 ? ($month == 1 ? $year - 1 : $year) : $year,
559
			'month' => $day == 1 ? ($month == 1 ? 12 : $month - 1) : $month,
560
			'day' => $day == 1 ? 28 : $day - 1,
561
			'disabled' => $day < 7 && $modSettings['cal_minyear'] > ($month == 1 ? $year - 1 : $year),
562
		),
563
		'next_week' => array(
564
			'disabled' => $day > 25 && $modSettings['cal_maxyear'] < ($month == 12 ? $year + 1 : $year),
565
		),
566
		'size' => empty($modSettings['cal_display_type']) ? 'large' : 'small',
567
	);
568
569
	// The next week calculation requires a bit more work.
570
	$curTimestamp = mktime(0, 0, 0, $month, $day, $year);
571
	$nextWeekTimestamp = $curTimestamp + 604800;
572
	$calendarGrid['next_week']['day'] = (int) strftime('%d', $nextWeekTimestamp);
573
	$calendarGrid['next_week']['month'] = (int) strftime('%m', $nextWeekTimestamp);
574
	$calendarGrid['next_week']['year'] = (int) strftime('%Y', $nextWeekTimestamp);
575
576
	// Fetch the arrays for birthdays, posted events, and holidays.
577
	$startDate = strftime('%Y-%m-%d', $curTimestamp);
578
	$endDate = strftime('%Y-%m-%d', $nextWeekTimestamp);
579
	$bday = $calendarOptions['show_birthdays'] ? getBirthdayRange($startDate, $endDate) : array();
580
	$events = $calendarOptions['show_events'] ? getEventRange($startDate, $endDate) : array();
581
	$holidays = $calendarOptions['show_holidays'] ? getHolidayRange($startDate, $endDate) : array();
582
583
	// An adjustment value to apply to all calculated week numbers.
584
	if (!empty($calendarOptions['show_week_num']))
585
	{
586
		$timestamp = mktime(0, 0, 0, $month, $day, $year);
587
		$calendarGrid['week_title'] = sprintf($txt['calendar_week_beginning'], date('F', $timestamp), date('j', $timestamp), date('Y', $timestamp));
588
	}
589
590
	// This holds all the main data - there is at least one month!
591
	$calendarGrid['months'] = array();
592
	$lastDay = 99;
593
	$curDay = $day;
594
	$curDayOfWeek = $calendarOptions['start_day'];
595
	for ($i = 0; $i < 7; $i++)
596
	{
597
		// Have we gone into a new month (Always happens first cycle too)
598
		if ($lastDay > $curDay)
599
		{
600
			$curMonth = $lastDay == 99 ? $month : ($month == 12 ? 1 : $month + 1);
601
			$curYear = $lastDay == 99 ? $year : ($curMonth == 1 && $month == 12 ? $year + 1 : $year);
602
			$calendarGrid['months'][$curMonth] = array(
603
				'current_month' => $curMonth,
604
				'current_year' => $curYear,
605
				'days' => array(),
606
			);
607
		}
608
609
		// Add todays information to the pile!
610
		$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...
611
612
		$calendarGrid['months'][$curMonth]['days'][$curDay] = array(
613
			'day' => $curDay,
614
			'day_of_week' => $curDayOfWeek,
615
			'date' => $date,
616
			'is_today' => $date == $today['date'],
617
			'holidays' => !empty($holidays[$date]) ? $holidays[$date] : array(),
618
			'events' => !empty($events[$date]) ? $events[$date] : array(),
619
			'birthdays' => !empty($bday[$date]) ? $bday[$date] : array()
620
		);
621
622
		// Make the last day what the current day is and work out what the next day is.
623
		$lastDay = $curDay;
624
		$curTimestamp += 86400;
625
		$curDay = (int) strftime('%d', $curTimestamp);
626
627
		// Also increment the current day of the week.
628
		$curDayOfWeek = $curDayOfWeek >= 6 ? 0 : ++$curDayOfWeek;
629
	}
630
631
	// Set the previous and the next week's links.
632
	$calendarGrid['previous_week']['href'] = $scripturl . '?action=calendar;viewweek;year=' . $calendarGrid['previous_week']['year'] . ';month=' . $calendarGrid['previous_week']['month'] . ';day=' . $calendarGrid['previous_week']['day'];
633
	$calendarGrid['next_week']['href'] = $scripturl . '?action=calendar;viewweek;year=' . $calendarGrid['next_week']['year'] . ';month=' . $calendarGrid['next_week']['month'] . ';day=' . $calendarGrid['next_week']['day'];
634
635
	return $calendarGrid;
636
}
637
638
639
/**
640
 * Returns the information needed to show a list of upcoming events, birthdays, and holidays on the calendar.
641
 * @param int $start_date The start of a date range
642
 * @param int $end_date The end of a date range
643
 * @param array $calendarOptions An array of calendar options
644
 * @return array An array of information needed to display a list of upcoming events, etc., on the calendar
645
 */
646
function getCalendarList($start_date, $end_date, $calendarOptions)
647
{
648
	global $modSettings, $user_info, $txt, $context, $sourcedir;
649
	require_once($sourcedir . '/Subs.php');
650
651
	// DateTime objects make life easier
652
	$start_object = date_create($start_date);
653
	$end_object = date_create($end_date);
654
655
	$calendarGrid = array(
656
		'start_date' => $start_date,
657
		'start_year' => date_format($start_object, 'Y'),
658
		'start_month' => date_format($start_object, 'm'),
659
		'start_day' => date_format($start_object, 'd'),
660
		'end_date' => $end_date,
661
		'end_year' => date_format($end_object, 'Y'),
662
		'end_month' => date_format($end_object, 'm'),
663
		'end_day' => date_format($end_object, 'd'),
664
	);
665
666
	$calendarGrid['birthdays'] = $calendarOptions['show_birthdays'] ? getBirthdayRange($start_date, $end_date) : array();
667
	$calendarGrid['holidays'] = $calendarOptions['show_holidays'] ? getHolidayRange($start_date, $end_date) : array();
668
	$calendarGrid['events'] = $calendarOptions['show_events'] ? getEventRange($start_date, $end_date) : array();
669
670
	// Get rid of duplicate events
671
	$temp = array();
672
	foreach ($calendarGrid['events'] as $date => $date_events)
673
	{
674
		foreach ($date_events as $event_key => $event_val)
675
		{
676
			if (in_array($event_val['id'], $temp))
677
				unset($calendarGrid['events'][$date][$event_key]);
678
			else
679
				$temp[] = $event_val['id'];
680
		}
681
	}
682
683
	// Give birthdays and holidays a friendly format, without the year
684 View Code Duplication
	if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
685
		$date_format = '%b %d';
686
	else
687
		$date_format = str_replace(array('%Y', '%y', '%G', '%g', '%C', '%c', '%D'), array('', '', '', '', '', '%b %d', '%m/%d'), $matches[0]);
688
689
	foreach (array('birthdays', 'holidays') as $type)
690
	{
691
		foreach ($calendarGrid[$type] as $date => $date_content)
692
		{
693
			$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...
694
695
			$calendarGrid[$type][$date]['date_local'] = $date_local;
696
		}
697
	}
698
699
	loadCSSFile('jquery-ui.datepicker.css', array('defer' => false), 'smf_datepicker');
700
	loadJavaScriptFile('jquery-ui.datepicker.min.js', array('defer' => true), 'smf_datepicker');
701
	addInlineJavaScript('
702
	$("#calendar_range .date_input").datepicker({
703
		dateFormat: "yy-mm-dd",
704
		autoSize: true,
705
		isRTL: ' . ($context['right_to_left'] ? 'true' : 'false') . ',
706
		constrainInput: true,
707
		showAnim: "",
708
		showButtonPanel: false,
709
		minDate: "' . $modSettings['cal_minyear'] . '-01-01",
710
		maxDate: "' . $modSettings['cal_maxyear'] . '-12-31",
711
		yearRange: "' . $modSettings['cal_minyear'] . ':' . $modSettings['cal_maxyear'] . '",
712
		hideIfNoPrevNext: true,
713
		monthNames: ["' . implode('", "', $txt['months_titles']) . '"],
714
		monthNamesShort: ["' . implode('", "', $txt['months_short']) . '"],
715
		dayNames: ["' . implode('", "', $txt['days']) . '"],
716
		dayNamesShort: ["' . implode('", "', $txt['days_short']) . '"],
717
		dayNamesMin: ["' . implode('", "', $txt['days_short']) . '"],
718
		prevText: "' . $txt['prev_month'] . '",
719
		nextText: "' . $txt['next_month'] . '",
720
	});
721
	var date_entry = document.getElementById("calendar_range");
722
	', true);
723
724
	return $calendarGrid;
725
}
726
727
/**
728
 * Retrieve all events for the given days, independently of the users offset.
729
 * cache callback function used to retrieve the birthdays, holidays, and events between now and now + days_to_index.
730
 * widens the search range by an extra 24 hours to support time offset shifts.
731
 * used by the cache_getRecentEvents function to get the information needed to calculate the events taking the users time offset into account.
732
 *
733
 * @param int $days_to_index How many days' worth of info to index
734
 * @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
735
 */
736
function cache_getOffsetIndependentEvents($days_to_index)
737
{
738
	$low_date = strftime('%Y-%m-%d', forum_time(false) - 24 * 3600);
739
	$high_date = strftime('%Y-%m-%d', forum_time(false) + $days_to_index * 24 * 3600);
740
741
	return array(
742
		'data' => array(
743
			'holidays' => getHolidayRange($low_date, $high_date),
744
			'birthdays' => getBirthdayRange($low_date, $high_date),
745
			'events' => getEventRange($low_date, $high_date, false),
746
		),
747
		'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\']);',
748
		'expires' => time() + 3600,
749
	);
750
}
751
752
/**
753
 * cache callback function used to retrieve the upcoming birthdays, holidays, and events within the given period, taking into account the users time offset.
754
 * Called from the BoardIndex to display the current day's events on the board index
755
 * used by the board index and SSI to show the upcoming events.
756
 * @param array $eventOptions An array of event options. Only 'num_days_shown' is used here
757
 * @return array An array containing the info that was cached as well as a few other relevant things
758
 */
759
function cache_getRecentEvents($eventOptions)
760
{
761
	// With the 'static' cached data we can calculate the user-specific data.
762
	$cached_data = cache_quick_get('calendar_index', 'Subs-Calendar.php', 'cache_getOffsetIndependentEvents', array($eventOptions['num_days_shown']));
763
764
	// Get the information about today (from user perspective).
765
	$today = getTodayInfo();
766
767
	$return_data = array(
768
		'calendar_holidays' => array(),
769
		'calendar_birthdays' => array(),
770
		'calendar_events' => array(),
771
	);
772
773
	// Set the event span to be shown in seconds.
774
	$days_for_index = $eventOptions['num_days_shown'] * 86400;
775
776
	// Get the current member time/date.
777
	$now = forum_time();
778
779
	// Holidays between now and now + days.
780
	for ($i = $now; $i < $now + $days_for_index; $i += 86400)
781
	{
782
		if (isset($cached_data['holidays'][strftime('%Y-%m-%d', $i)]))
783
			$return_data['calendar_holidays'] = array_merge($return_data['calendar_holidays'], $cached_data['holidays'][strftime('%Y-%m-%d', $i)]);
784
	}
785
786
	// Happy Birthday, guys and gals!
787
	for ($i = $now; $i < $now + $days_for_index; $i += 86400)
788
	{
789
		$loop_date = strftime('%Y-%m-%d', $i);
790
		if (isset($cached_data['birthdays'][$loop_date]))
791
		{
792
			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...
793
				$cached_data['birthdays'][strftime('%Y-%m-%d', $i)][$index]['is_today'] = $loop_date === $today['date'];
794
			$return_data['calendar_birthdays'] = array_merge($return_data['calendar_birthdays'], $cached_data['birthdays'][$loop_date]);
795
		}
796
	}
797
798
	$duplicates = array();
799
	for ($i = $now; $i < $now + $days_for_index; $i += 86400)
800
	{
801
		// Determine the date of the current loop step.
802
		$loop_date = strftime('%Y-%m-%d', $i);
803
804
		// No events today? Check the next day.
805
		if (empty($cached_data['events'][$loop_date]))
806
			continue;
807
808
		// Loop through all events to add a few last-minute values.
809
		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...
810
		{
811
			// Create a shortcut variable for easier access.
812
			$this_event = &$cached_data['events'][$loop_date][$ev];
813
814
			// Skip duplicates.
815
			if (isset($duplicates[$this_event['topic'] . $this_event['title']]))
816
			{
817
				unset($cached_data['events'][$loop_date][$ev]);
818
				continue;
819
			}
820
			else
821
				$duplicates[$this_event['topic'] . $this_event['title']] = true;
822
823
			// Might be set to true afterwards, depending on the permissions.
824
			$this_event['can_edit'] = false;
825
			$this_event['is_today'] = $loop_date === $today['date'];
826
			$this_event['date'] = $loop_date;
827
		}
828
829
		if (!empty($cached_data['events'][$loop_date]))
830
			$return_data['calendar_events'] = array_merge($return_data['calendar_events'], $cached_data['events'][$loop_date]);
831
	}
832
833
	// Mark the last item so that a list separator can be used in the template.
834 View Code Duplication
	for ($i = 0, $n = count($return_data['calendar_birthdays']); $i < $n; $i++)
835
		$return_data['calendar_birthdays'][$i]['is_last'] = !isset($return_data['calendar_birthdays'][$i + 1]);
836 View Code Duplication
	for ($i = 0, $n = count($return_data['calendar_events']); $i < $n; $i++)
837
		$return_data['calendar_events'][$i]['is_last'] = !isset($return_data['calendar_events'][$i + 1]);
838
839
	return array(
840
		'data' => $return_data,
841
		'expires' => time() + 3600,
842
		'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\']);',
843
		'post_retri_eval' => '
844
			global $context, $scripturl, $user_info;
845
846
			foreach ($cache_block[\'data\'][\'calendar_events\'] as $k => $event)
847
			{
848
				// Remove events that the user may not see or wants to ignore.
849
				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\']))
850
					unset($cache_block[\'data\'][\'calendar_events\'][$k]);
851
				else
852
				{
853
					// Whether the event can be edited depends on the permissions.
854
					$cache_block[\'data\'][\'calendar_events\'][$k][\'can_edit\'] = allowedTo(\'calendar_edit_any\') || ($event[\'poster\'] == $user_info[\'id\'] && allowedTo(\'calendar_edit_own\'));
855
856
					// The added session code makes this URL not cachable.
857
					$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\'];
858
				}
859
			}
860
861
			if (empty($params[0][\'include_holidays\']))
862
				$cache_block[\'data\'][\'calendar_holidays\'] = array();
863
			if (empty($params[0][\'include_birthdays\']))
864
				$cache_block[\'data\'][\'calendar_birthdays\'] = array();
865
			if (empty($params[0][\'include_events\']))
866
				$cache_block[\'data\'][\'calendar_events\'] = array();
867
868
			$cache_block[\'data\'][\'show_calendar\'] = !empty($cache_block[\'data\'][\'calendar_holidays\']) || !empty($cache_block[\'data\'][\'calendar_birthdays\']) || !empty($cache_block[\'data\'][\'calendar_events\']);',
869
	);
870
}
871
872
/**
873
 * Makes sure the calendar post is valid.
874
 */
875
function validateEventPost()
876
{
877
	global $modSettings, $smcFunc;
878
879
	if (!isset($_POST['deleteevent']))
880
	{
881
		// The 2.1 way
882
		if (isset($_POST['start_date']))
883
		{
884
			$d = date_parse($_POST['start_date']);
885 View Code Duplication
			if (!empty($d['error_count']) || !empty($d['warning_count']))
886
				fatal_lang_error('invalid_date', false);
887
			if (empty($d['year']))
888
				fatal_lang_error('event_year_missing', false);
889
			if (empty($d['month']))
890
				fatal_lang_error('event_month_missing', false);
891
		}
892
		elseif (isset($_POST['start_datetime']))
893
		{
894
			$d = date_parse($_POST['start_datetime']);
895 View Code Duplication
			if (!empty($d['error_count']) || !empty($d['warning_count']))
896
				fatal_lang_error('invalid_date', false);
897
			if (empty($d['year']))
898
				fatal_lang_error('event_year_missing', false);
899
			if (empty($d['month']))
900
				fatal_lang_error('event_month_missing', false);
901
		}
902
		// The 2.0 way
903
		else
904
		{
905
			// No month?  No year?
906
			if (!isset($_POST['month']))
907
				fatal_lang_error('event_month_missing', false);
908
			if (!isset($_POST['year']))
909
				fatal_lang_error('event_year_missing', false);
910
911
			// Check the month and year...
912
			if ($_POST['month'] < 1 || $_POST['month'] > 12)
913
				fatal_lang_error('invalid_month', false);
914 View Code Duplication
			if ($_POST['year'] < $modSettings['cal_minyear'] || $_POST['year'] > $modSettings['cal_maxyear'])
915
				fatal_lang_error('invalid_year', false);
916
		}
917
	}
918
919
	// Make sure they're allowed to post...
920
	isAllowedTo('calendar_post');
921
922
	// If they want to us to calculate an end date, make sure it will fit in an acceptable range.
923
	if (isset($_POST['span']))
924
	{
925
		if (($_POST['span'] < 1) || (!empty($modSettings['cal_maxspan']) && $_POST['span'] > $modSettings['cal_maxspan']))
926
			fatal_lang_error('invalid_days_numb', false);
927
	}
928
929
	// There is no need to validate the following values if we are just deleting the event.
930
	if (!isset($_POST['deleteevent']))
931
	{
932
		// If we're doing things the 2.0 way, check the day
933
		if (empty($_POST['start_date']) && empty($_POST['start_datetime']))
934
		{
935
			// No day?
936
			if (!isset($_POST['day']))
937
				fatal_lang_error('event_day_missing', false);
938
939
			// Bad day?
940
			if (!checkdate($_POST['month'], $_POST['day'], $_POST['year']))
941
				fatal_lang_error('invalid_date', false);
942
		}
943
944
		if (!isset($_POST['evtitle']) && !isset($_POST['subject']))
945
			fatal_lang_error('event_title_missing', false);
946
		elseif (!isset($_POST['evtitle']))
947
			$_POST['evtitle'] = $_POST['subject'];
948
949
		// No title?
950
		if ($smcFunc['htmltrim']($_POST['evtitle']) === '')
951
			fatal_lang_error('no_event_title', false);
952 View Code Duplication
		if ($smcFunc['strlen']($_POST['evtitle']) > 100)
953
			$_POST['evtitle'] = $smcFunc['substr']($_POST['evtitle'], 0, 100);
954
		$_POST['evtitle'] = str_replace(';', '', $_POST['evtitle']);
955
	}
956
}
957
958
/**
959
 * Get the event's poster.
960
 *
961
 * @param int $event_id The ID of the event
962
 * @return int|bool The ID of the poster or false if the event was not found
963
 */
964 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...
965
{
966
	global $smcFunc;
967
968
	// A simple database query, how hard can that be?
969
	$request = $smcFunc['db_query']('', '
970
		SELECT id_member
971
		FROM {db_prefix}calendar
972
		WHERE id_event = {int:id_event}
973
		LIMIT 1',
974
		array(
975
			'id_event' => $event_id,
976
		)
977
	);
978
979
	// No results, return false.
980
	if ($smcFunc['db_num_rows'] === 0)
981
		return false;
982
983
	// Grab the results and return.
984
	list ($poster) = $smcFunc['db_fetch_row']($request);
985
	$smcFunc['db_free_result']($request);
986
	return (int) $poster;
987
}
988
989
/**
990
 * Consolidating the various INSERT statements into this function.
991
 * Inserts the passed event information into the calendar table.
992
 * Allows to either set a time span (in days) or an end_date.
993
 * Does not check any permissions of any sort.
994
 *
995
 * @param array $eventOptions An array of event options ('title', 'span', 'start_date', 'end_date', etc.)
996
 */
997
function insertEvent(&$eventOptions)
998
{
999
	global $smcFunc, $context;
1000
1001
	// Add special chars to the title.
1002
	$eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES);
1003
1004
	$eventOptions['location'] = isset($eventOptions['location']) ? $smcFunc['htmlspecialchars']($eventOptions['location'], ENT_QUOTES) : '';
1005
1006
	// Set the start and end dates and times
1007
	list($start_date, $end_date, $start_time, $end_time, $tz) = setEventStartEnd($eventOptions);
1008
1009
	// If no topic and board are given, they are not linked to a topic.
1010
	$eventOptions['board'] = isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0;
1011
	$eventOptions['topic'] = isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0;
1012
1013
	$event_columns = array(
1014
		'id_board' => 'int', 'id_topic' => 'int', 'title' => 'string-60', 'id_member' => 'int',
1015
		'start_date' => 'date', 'end_date' => 'date', 'location' => 'string-255',
1016
	);
1017
	$event_parameters = array(
1018
		$eventOptions['board'], $eventOptions['topic'], $eventOptions['title'], $eventOptions['member'],
1019
		$start_date, $end_date, $eventOptions['location'],
1020
	);
1021 View Code Duplication
	if (!empty($start_time) && !empty($end_time) && !empty($tz) && in_array($tz, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1022
	{
1023
		$event_columns['start_time'] = 'time';
1024
		$event_parameters[] = $start_time;
1025
		$event_columns['end_time'] = 'time';
1026
		$event_parameters[] = $end_time;
1027
		$event_columns['timezone'] = 'string';
1028
		$event_parameters[] = $tz;
1029
	}
1030
1031
	call_integration_hook('integrate_create_event', array(&$eventOptions, &$event_columns, &$event_parameters));
1032
1033
	// Insert the event!
1034
	$eventOptions['id'] = $smcFunc['db_insert']('',
1035
		'{db_prefix}calendar',
1036
		$event_columns,
1037
		$event_parameters,
1038
		array('id_event'),
1039
		1
1040
	);
1041
1042
	// If this isn't tied to a topic, we need to notify people about it.
1043
	if (empty($eventOptions['topic']))
1044
	{
1045
		$smcFunc['db_insert']('insert',
1046
			'{db_prefix}background_tasks',
1047
			array('task_file' => 'string', 'task_class' => 'string', 'task_data' => 'string', 'claimed_time' => 'int'),
1048
			array('$sourcedir/tasks/EventNew-Notify.php', 'EventNew_Notify_Background', $smcFunc['json_encode'](array(
1049
				'event_title' => $eventOptions['title'],
1050
				'event_id' => $eventOptions['id'],
1051
				'sender_id' => $eventOptions['member'],
1052
				'sender_name' => $eventOptions['member'] == $context['user']['id'] ? $context['user']['name'] : '',
1053
				'time' => time(),
1054
			)), 0),
1055
			array('id_task')
1056
		);
1057
	}
1058
1059
	// Update the settings to show something calendar-ish was updated.
1060
	updateSettings(array(
1061
		'calendar_updated' => time(),
1062
	));
1063
}
1064
1065
/**
1066
 * modifies an event.
1067
 * allows to either set a time span (in days) or an end_date.
1068
 * does not check any permissions of any sort.
1069
 *
1070
 * @param int $event_id The ID of the event
1071
 * @param array $eventOptions An array of event information
1072
 */
1073
function modifyEvent($event_id, &$eventOptions)
1074
{
1075
	global $smcFunc;
1076
1077
	// Properly sanitize the title and location
1078
	$eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES);
1079
	$eventOptions['location'] = $smcFunc['htmlspecialchars']($eventOptions['location'], ENT_QUOTES);
1080
1081
	// Set the new start and end dates and times
1082
	list($start_date, $end_date, $start_time, $end_time, $tz) = setEventStartEnd($eventOptions);
1083
1084
	$event_columns = array(
1085
		'start_date' => '{date:start_date}',
1086
		'end_date' => '{date:end_date}',
1087
		'title' => 'SUBSTRING({string:title}, 1, 60)',
1088
		'id_board' => '{int:id_board}',
1089
		'id_topic' => '{int:id_topic}',
1090
		'location' => 'SUBSTRING({string:location}, 1, 255)',
1091
	);
1092
	$event_parameters = array(
1093
		'start_date' => $start_date,
1094
		'end_date' => $end_date,
1095
		'title' => $eventOptions['title'],
1096
		'location' => $eventOptions['location'],
1097
		'id_board' => isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0,
1098
		'id_topic' => isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0,
1099
	);
1100 View Code Duplication
	if (!empty($start_time) && !empty($end_time) && !empty($tz) && in_array($tz, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1101
	{
1102
		$event_columns['start_time'] = '{time:start_time}';
1103
		$event_parameters['start_time'] = $start_time;
1104
		$event_columns['end_time'] = '{time:end_time}';
1105
		$event_parameters['end_time'] = $end_time;
1106
		$event_columns['timezone'] = '{string:timezone}';
1107
		$event_parameters['timezone'] = $tz;
1108
	}
1109
1110
	// This is to prevent hooks to modify the id of the event
1111
	$real_event_id = $event_id;
1112
	call_integration_hook('integrate_modify_event', array($event_id, &$eventOptions, &$event_columns, &$event_parameters));
1113
1114
	$column_clauses = array();
1115
	foreach ($event_columns as $col => $crit)
1116
		$column_clauses[] = $col . ' = ' . $crit;
1117
1118
	$smcFunc['db_query']('', '
1119
		UPDATE {db_prefix}calendar
1120
		SET
1121
			' . implode(', ', $column_clauses) . '
1122
		WHERE id_event = {int:id_event}',
1123
		array_merge(
1124
			$event_parameters,
1125
			array(
1126
				'id_event' => $real_event_id
1127
			)
1128
		)
1129
	);
1130
1131
	if (empty($start_time) || empty($end_time) || empty($tz) || !in_array($tz, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1132
	{
1133
		$smcFunc['db_query']('', '
1134
			UPDATE {db_prefix}calendar
1135
			SET start_time = NULL, end_time = NULL, timezone = NULL
1136
			WHERE id_event = {int:id_event}',
1137
			array(
1138
				'id_event' => $real_event_id
1139
			)
1140
		);
1141
	}
1142
1143
	updateSettings(array(
1144
		'calendar_updated' => time(),
1145
	));
1146
}
1147
1148
/**
1149
 * Remove an event
1150
 * removes an event.
1151
 * does no permission checks.
1152
 *
1153
 * @param int $event_id The ID of the event to remove
1154
 */
1155
function removeEvent($event_id)
1156
{
1157
	global $smcFunc;
1158
1159
	$smcFunc['db_query']('', '
1160
		DELETE FROM {db_prefix}calendar
1161
		WHERE id_event = {int:id_event}',
1162
		array(
1163
			'id_event' => $event_id,
1164
		)
1165
	);
1166
1167
	call_integration_hook('integrate_remove_event', array($event_id));
1168
1169
	updateSettings(array(
1170
		'calendar_updated' => time(),
1171
	));
1172
}
1173
1174
/**
1175
 * Gets all the events properties
1176
 *
1177
 * @param int $event_id The ID of the event
1178
 * @return array An array of event information
1179
 */
1180
function getEventProperties($event_id)
1181
{
1182
	global $smcFunc;
1183
1184
	$request = $smcFunc['db_query']('', '
1185
		SELECT
1186
			c.id_event, c.id_board, c.id_topic, c.id_member, c.title,
1187
			c.start_date, c.end_date, c.start_time, c.end_time, c.timezone, c.location,
1188
			t.id_first_msg, t.id_member_started,
1189
			mb.real_name, m.modified_time
1190
		FROM {db_prefix}calendar AS c
1191
			LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = c.id_topic)
1192
			LEFT JOIN {db_prefix}members AS mb ON (mb.id_member = t.id_member_started)
1193
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg  = t.id_first_msg)
1194
		WHERE c.id_event = {int:id_event}',
1195
		array(
1196
			'id_event' => $event_id,
1197
		)
1198
	);
1199
1200
	// If nothing returned, we are in poo, poo.
1201
	if ($smcFunc['db_num_rows']($request) === 0)
1202
		return false;
1203
1204
	$row = $smcFunc['db_fetch_assoc']($request);
1205
	$smcFunc['db_free_result']($request);
1206
1207
	list($start, $end, $allday, $span, $tz, $tz_abbrev) = buildEventDatetimes($row);
1208
1209
	// Sanity check
1210 View Code Duplication
	if (!empty($start['error_count']) || !empty($start['warning_count']) || !empty($end['error_count']) || !empty($end['warning_count']))
1211
		return false;
1212
1213
	$return_value = array(
1214
		'boards' => array(),
1215
		'board' => $row['id_board'],
1216
		'new' => 0,
1217
		'eventid' => $event_id,
1218
		'year' => $start['year'],
1219
		'month' => $start['month'],
1220
		'day' => $start['day'],
1221
		'hour' => !$allday ? $start['hour'] : null,
1222
		'minute' => !$allday ? $start['minute'] : null,
1223
		'second' => !$allday ? $start['second'] : null,
1224
		'start_date' => $row['start_date'],
1225
		'start_date_local' => $start['date_local'],
1226
		'start_date_orig' => $start['date_orig'],
1227
		'start_time' => !$allday ? $row['start_time'] : null,
1228
		'start_time_local' => !$allday ? $start['time_local'] : null,
1229
		'start_time_orig' => !$allday ? $start['time_orig'] : null,
1230
		'start_timestamp' => $start['timestamp'],
1231
		'start_datetime' => $start['datetime'],
1232
		'start_iso_gmdate' => $start['iso_gmdate'],
1233
		'end_year' => $end['year'],
1234
		'end_month' => $end['month'],
1235
		'end_day' => $end['day'],
1236
		'end_hour' => !$allday ? $end['hour'] : null,
1237
		'end_minute' => !$allday ? $end['minute'] : null,
1238
		'end_second' => !$allday ? $end['second'] : null,
1239
		'end_date' => $row['end_date'],
1240
		'end_date_local' => $end['date_local'],
1241
		'end_date_orig' => $end['date_orig'],
1242
		'end_time' => !$allday ? $row['end_time'] : null,
1243
		'end_time_local' => !$allday ? $end['time_local'] : null,
1244
		'end_time_orig' => !$allday ? $end['time_orig'] : null,
1245
		'end_timestamp' => $end['timestamp'],
1246
		'end_datetime' => $end['datetime'],
1247
		'end_iso_gmdate' => $end['iso_gmdate'],
1248
		'allday' => $allday,
1249
		'tz' => !$allday ? $tz : null,
1250
		'tz_abbrev' => !$allday ? $tz_abbrev : null,
1251
		'span' => $span,
1252
		'title' => $row['title'],
1253
		'location' => $row['location'],
1254
		'member' => $row['id_member'],
1255
		'realname' => $row['real_name'],
1256
		'sequence' => $row['modified_time'],
1257
		'topic' => array(
1258
			'id' => $row['id_topic'],
1259
			'member_started' => $row['id_member_started'],
1260
			'first_msg' => $row['id_first_msg'],
1261
		),
1262
	);
1263
1264
	$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']));
1265
1266
	return $return_value;
1267
}
1268
1269
/**
1270
 * Gets an initial set of date and time values for creating a new event.
1271
 *
1272
 * @return array An array containing an initial set of date and time values for an event.
1273
 */
1274
function getNewEventDatetimes()
1275
{
1276
	// Ensure setEventStartEnd() has something to work with
1277
	$now = date_create();
1278
	$_POST['year'] = !empty($_POST['year']) ? $_POST['year'] : date_format($now, 'Y');
1279
	$_POST['month'] = !empty($_POST['month']) ? $_POST['month'] : date_format($now, 'm');
1280
	$_POST['day'] = !empty($_POST['day']) ? $_POST['day'] : date_format($now, 'd');
1281
	$_POST['hour'] = !empty($_POST['hour']) ? $_POST['hour'] : date_format($now, 'H');
1282
	$_POST['minute'] = !empty($_POST['minute']) ? $_POST['minute'] : date_format($now, 'i');
1283
	$_POST['second'] = !empty($_POST['second']) ? $_POST['second'] : date_format($now, 's');
1284
1285
	// Set the basic values for the new event
1286
	$row_keys = array('start_date', 'end_date', 'start_time', 'end_time', 'timezone');
1287
	$row = array_combine($row_keys, setEventStartEnd());
1288
1289
	// And now set the full suite of values
1290
	list($start, $end, $allday, $span, $tz, $tz_abbrev) = buildEventDatetimes($row);
1291
1292
	// Default theme only uses some of this info, but others might want it all
1293
	$eventProperties = array(
1294
		'year' => $start['year'],
1295
		'month' => $start['month'],
1296
		'day' => $start['day'],
1297
		'hour' => !$allday ? $start['hour'] : null,
1298
		'minute' => !$allday ? $start['minute'] : null,
1299
		'second' => !$allday ? $start['second'] : null,
1300
		'start_date' => $row['start_date'],
1301
		'start_date_local' => $start['date_local'],
1302
		'start_date_orig' => $start['date_orig'],
1303
		'start_time' => !$allday ? $row['start_time'] : null,
1304
		'start_time_local' => !$allday ? $start['time_local'] : null,
1305
		'start_time_orig' => !$allday ? $start['time_orig'] : null,
1306
		'start_timestamp' => $start['timestamp'],
1307
		'start_datetime' => $start['datetime'],
1308
		'start_iso_gmdate' => $start['iso_gmdate'],
1309
		'end_year' => $end['year'],
1310
		'end_month' => $end['month'],
1311
		'end_day' => $end['day'],
1312
		'end_hour' => !$allday ? $end['hour'] : null,
1313
		'end_minute' => !$allday ? $end['minute'] : null,
1314
		'end_second' => !$allday ? $end['second'] : null,
1315
		'end_date' => $row['end_date'],
1316
		'end_date_local' => $end['date_local'],
1317
		'end_date_orig' => $end['date_orig'],
1318
		'end_time' => !$allday ? $row['end_time'] : null,
1319
		'end_time_local' => !$allday ? $end['time_local'] : null,
1320
		'end_time_orig' => !$allday ? $end['time_orig'] : null,
1321
		'end_timestamp' => $end['timestamp'],
1322
		'end_datetime' => $end['datetime'],
1323
		'end_iso_gmdate' => $end['iso_gmdate'],
1324
		'allday' => $allday,
1325
		'tz' => !$allday ? $tz : null,
1326
		'tz_abbrev' => !$allday ? $tz_abbrev : null,
1327
		'span' => $span,
1328
	);
1329
1330
	return $eventProperties;
1331
}
1332
1333
/**
1334
 * Set the start and end dates and times for a posted event for insertion into the database.
1335
 * Validates all date and times given to it.
1336
 * Makes sure events do not exceed the maximum allowed duration (if any).
1337
 * If passed an array that defines any time or date parameters, they will be used. Otherwise, gets the values from $_POST.
1338
 *
1339
 * @param array $eventOptions An array of optional time and date parameters (span, start_year, end_month, etc., etc.)
1340
 * @return array An array containing $start_date, $end_date, $start_time, $end_time
1341
 */
1342
function setEventStartEnd($eventOptions = array())
1343
{
1344
	global $modSettings;
1345
1346
	// Set $span, in case we need it
1347
	$span = isset($eventOptions['span']) ? $eventOptions['span'] : (isset($_POST['span']) ? $_POST['span'] : 0);
1348
	if ($span > 0)
1349
		$span = !empty($modSettings['cal_maxspan']) ? min($modSettings['cal_maxspan'], $span - 1) : $span - 1;
1350
1351
	// Define the timezone for this event, falling back to the default if not provided
1352
	if (!empty($eventOptions['tz']) && in_array($eventOptions['tz'], timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1353
		$tz = $eventOptions['tz'];
1354 View Code Duplication
	elseif (!empty($_POST['tz']) && in_array($_POST['tz'], timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1355
		$tz = $_POST['tz'];
1356
	else
1357
		$tz = getUserTimezone();
1358
1359
	// Is this supposed to be an all day event, or should it have specific start and end times?
1360
	if (isset($eventOptions['allday']))
1361
		$allday = $eventOptions['allday'];
1362
	elseif (empty($_POST['allday']))
1363
		$allday = false;
1364
	else
1365
		$allday = true;
1366
1367
	// Input might come as individual parameters...
1368
	$start_year = isset($eventOptions['year']) ? $eventOptions['year'] : (isset($_POST['year']) ? $_POST['year'] : null);
1369
	$start_month = isset($eventOptions['month']) ? $eventOptions['month'] : (isset($_POST['month']) ? $_POST['month'] : null);
1370
	$start_day = isset($eventOptions['day']) ? $eventOptions['day'] : (isset($_POST['day']) ? $_POST['day'] : null);
1371
	$start_hour = isset($eventOptions['hour']) ? $eventOptions['hour'] : (isset($_POST['hour']) ? $_POST['hour'] : null);
1372
	$start_minute = isset($eventOptions['minute']) ? $eventOptions['minute'] : (isset($_POST['minute']) ? $_POST['minute'] : null);
1373
	$start_second = isset($eventOptions['second']) ? $eventOptions['second'] : (isset($_POST['second']) ? $_POST['second'] : null);
1374
	$end_year = isset($eventOptions['end_year']) ? $eventOptions['end_year'] : (isset($_POST['end_year']) ? $_POST['end_year'] : null);
1375
	$end_month = isset($eventOptions['end_month']) ? $eventOptions['end_month'] : (isset($_POST['end_month']) ? $_POST['end_month'] : null);
1376
	$end_day = isset($eventOptions['end_day']) ? $eventOptions['end_day'] : (isset($_POST['end_day']) ? $_POST['end_day'] : null);
1377
	$end_hour = isset($eventOptions['end_hour']) ? $eventOptions['end_hour'] : (isset($_POST['end_hour']) ? $_POST['end_hour'] : null);
1378
	$end_minute = isset($eventOptions['end_minute']) ? $eventOptions['end_minute'] : (isset($_POST['end_minute']) ? $_POST['end_minute'] : null);
1379
	$end_second = isset($eventOptions['end_second']) ? $eventOptions['end_second'] : (isset($_POST['end_second']) ? $_POST['end_second'] : null);
1380
1381
	// ... or as datetime strings ...
1382
	$start_string = isset($eventOptions['start_datetime']) ? $eventOptions['start_datetime'] : (isset($_POST['start_datetime']) ? $_POST['start_datetime'] : null);
1383
	$end_string = isset($eventOptions['end_datetime']) ? $eventOptions['end_datetime'] : (isset($_POST['end_datetime']) ? $_POST['end_datetime'] : null);
1384
1385
	// ... or as date strings and time strings.
1386
	$start_date_string = isset($eventOptions['start_date']) ? $eventOptions['start_date'] : (isset($_POST['start_date']) ? $_POST['start_date'] : null);
1387
	$start_time_string = isset($eventOptions['start_time']) ? $eventOptions['start_time'] : (isset($_POST['start_time']) ? $_POST['start_time'] : null);
1388
	$end_date_string = isset($eventOptions['end_date']) ? $eventOptions['end_date'] : (isset($_POST['end_date']) ? $_POST['end_date'] : null);
1389
	$end_time_string = isset($eventOptions['end_time']) ? $eventOptions['end_time'] : (isset($_POST['end_time']) ? $_POST['end_time'] : null);
1390
1391
	// If the date and time were given in separate strings, combine them
1392
	if (empty($start_string) && isset($start_date_string))
1393
		$start_string = $start_date_string . (isset($start_time_string) ? ' ' . $start_time_string : '');
1394
	if (empty($end_string) && isset($end_date_string))
1395
		$end_string = $end_date_string . (isset($end_time_string) ? ' ' . $end_time_string : '');
1396
1397
	// If some form of string input was given, override individually defined options with it
1398 View Code Duplication
	if (isset($start_string))
1399
	{
1400
		$start_string_parsed = date_parse($start_string);
1401
		if (empty($start_string_parsed['error_count']) && empty($start_string_parsed['warning_count']))
1402
		{
1403
			if ($start_string_parsed['year'] != false)
1404
			{
1405
				$start_year = $start_string_parsed['year'];
1406
				$start_month = $start_string_parsed['month'];
1407
				$start_day = $start_string_parsed['day'];
1408
			}
1409
			if ($start_string_parsed['hour'] != false)
1410
			{
1411
				$start_hour = $start_string_parsed['hour'];
1412
				$start_minute = $start_string_parsed['minute'];
1413
				$start_second = $start_string_parsed['second'];
1414
			}
1415
		}
1416
	}
1417 View Code Duplication
	if (isset($end_string))
1418
	{
1419
		$end_string_parsed = date_parse($end_string);
1420
		if (empty($end_string_parsed['error_count']) && empty($end_string_parsed['warning_count']))
1421
		{
1422
			if ($end_string_parsed['year'] != false)
1423
			{
1424
				$end_year = $end_string_parsed['year'];
1425
				$end_month = $end_string_parsed['month'];
1426
				$end_day = $end_string_parsed['day'];
1427
			}
1428
			if ($end_string_parsed['hour'] != false)
1429
			{
1430
				$end_hour = $end_string_parsed['hour'];
1431
				$end_minute = $end_string_parsed['minute'];
1432
				$end_second = $end_string_parsed['second'];
1433
			}
1434
		}
1435
	}
1436
1437
	// Validate input
1438
	$start_date_isvalid = checkdate($start_month, $start_day, $start_year);
1439
	$end_date_isvalid = checkdate($end_month, $end_day, $end_year);
1440
1441
	$start_time_isset = (isset($start_hour) && isset($start_minute) && isset($start_second));
1442
	$d = date_parse(sprintf('%02d:%02d:%02d', $start_hour, $start_minute, $start_second));
1443
	$start_time_isvalid = ($d['error_count'] == 0 && $d['warning_count'] == 0) ? true : false;
1444
1445
	$end_time_isset = (isset($end_hour) && isset($end_minute) && isset($end_second));
1446
	$d = date_parse(sprintf('%02d:%02d:%02d', $end_hour, $end_minute, $end_second));
1447
	$end_time_isvalid = ($d['error_count'] == 0 && $d['warning_count'] == 0) ? true : false;
1448
1449
	// Uh-oh...
1450
	if ($start_date_isvalid === false)
1451
	{
1452
		fatal_lang_error('invalid_date', false);
1453
	}
1454
1455
	// Make sure we use valid values for everything
1456
	if ($end_date_isvalid === false)
1457
	{
1458
		$end_year = $start_year;
1459
		$end_month = $start_month;
1460
		$end_day = $start_day;
1461
	}
1462
1463
	if ($allday === true || $start_time_isset === false || $start_time_isvalid === false)
1464
	{
1465
		$allday = true;
1466
		$start_hour = 0;
1467
		$start_minute = 0;
1468
		$start_second = 0;
1469
	}
1470
1471
	if ($allday === true || $end_time_isvalid === false || $end_time_isset === false)
1472
	{
1473
		$end_hour = $start_hour;
1474
		$end_minute = $start_minute;
1475
		$end_second = $start_second;
1476
	}
1477
1478
	// Now create our datetime objects
1479
	$start_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, $start_hour, $start_minute, $start_second) . ' ' . $tz);
1480
	$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $end_year, $end_month, $end_day, $end_hour, $end_minute, $end_second) . ' ' . $tz);
1481
1482
	// Is $end_object too early?
1483
	if ($start_object >= $end_object)
1484
	{
1485
		$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, $start_hour, $start_minute, $start_second) . ' ' . $tz);
1486
		if ($span > 0)
1487
			date_add($end_object, date_interval_create_from_date_string($span . ' days'));
1488
		else
1489
			date_add($end_object, date_interval_create_from_date_string('1 hour'));
1490
	}
1491
1492
	// Is $end_object too late?
1493
	if (!empty($modSettings['cal_maxspan']))
1494
	{
1495
		$date_diff = date_diff($start_object, $end_object);
1496
		if ($date_diff->days > $modSettings['cal_maxspan'])
1497
		{
1498
			if ($modSettings['cal_maxspan'] > 1)
1499
			{
1500
				$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, $start_hour, $start_minute, $start_second) . ' ' . $tz);
1501
				date_add($end_object, date_interval_create_from_date_string($modSettings['cal_maxspan'] . ' days'));
1502
			}
1503
			else
1504
				$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, '11', '59', '59') . ' ' . $tz);
1505
		}
1506
	}
1507
1508
	// Finally, make our strings
1509
	$start_date = date_format($start_object, 'Y-m-d');
1510
	$end_date = date_format($end_object, 'Y-m-d');
1511
1512
	if ($allday == true)
1513
	{
1514
		$start_time = null;
1515
		$end_time = null;
1516
		$tz = null;
1517
	}
1518
	else
1519
	{
1520
		$start_time = date_format($start_object, 'H:i:s');
1521
		$end_time = date_format($end_object, 'H:i:s');
1522
	}
1523
1524
	return array($start_date, $end_date, $start_time, $end_time, $tz);
1525
}
1526
1527
/**
1528
 * Helper function for getEventRange, getEventProperties, getNewEventDatetimes, etc.
1529
 *
1530
 * @param array $row A database row representing an event from the calendar table
1531
 * @return array An array containing the start and end date and time properties for the event
1532
 */
1533
function buildEventDatetimes($row)
1534
{
1535
	global $sourcedir, $user_info;
1536
	require_once($sourcedir . '/Subs.php');
1537
1538
	// First, try to create a better date format, ignoring the "time" elements.
1539
	if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
1540
		$date_format = '%F';
1541
	else
1542
		$date_format = $matches[0];
1543
1544
	// We want a fairly compact version of the time, but as close as possible to the user's settings.
1545 View Code Duplication
	if (preg_match('~%[HkIlMpPrRSTX](?:[^%]*%[HkIlMpPrRSTX])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
1546
		$time_format = '%k:%M';
1547
	else
1548
		$time_format = str_replace(array('%I', '%H', '%S', '%r', '%R', '%T'), array('%l', '%k', '', '%l:%M %p', '%k:%M', '%l:%M'), $matches[0]);
1549
1550
	// Should this be an all day event?
1551
	$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;
1552
1553
	// How many days does this event span?
1554
	$span = 1 + date_interval_format(date_diff(date_create($row['start_date']), date_create($row['end_date'])), '%d');
1555
1556
	// We need to have a defined timezone in the steps below
1557
	if (empty($row['timezone']))
1558
		$row['timezone'] = getUserTimezone();
1559
1560
	// Get most of the standard date information for the start and end datetimes
1561
	$start = date_parse($row['start_date'] . (!$allday ? ' ' . $row['start_time'] : ''));
1562
	$end = date_parse($row['end_date'] . (!$allday ? ' ' . $row['end_time'] : ''));
1563
1564
	// But we also want more info, so make some DateTime objects we can use
1565
	$start_object = date_create($row['start_date'] . (!$allday ? ' ' . $row['start_time'] : ''), timezone_open($row['timezone']));
1566
	$end_object = date_create($row['end_date'] . (!$allday ? ' ' . $row['end_time'] : ''), timezone_open($row['timezone']));
1567
1568
	// Unix timestamps are good
1569
	$start['timestamp'] = date_format($start_object, 'U');
1570
	$end['timestamp'] = date_format($end_object, 'U');
1571
1572
	// Datetime string without timezone  (e.g. '2016-12-28 22:45:30')
1573
	$start['datetime'] = date_format($start_object, 'Y-m-d H:i:s');
1574
	$end['datetime'] = date_format($start_object, 'Y-m-d H:i:s');
1575
1576
	// ISO formatted datetime string, relative to UTC (e.g. '2016-12-29T05:45:30+00:00')
1577
	$start['iso_gmdate'] = gmdate('c', $start['timestamp']);
1578
	$end['iso_gmdate'] = gmdate('c', $end['timestamp']);
1579
1580
	// Strings showing the datetimes in the user's preferred format, relative to the user's time zone
1581
	$start['date_local'] = timeformat($start['timestamp'], $date_format);
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...
1582
	$start['time_local'] = timeformat($start['timestamp'], $time_format);
0 ignored issues
show
Documentation introduced by
$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...
1583
	$end['date_local'] = timeformat($end['timestamp'], $date_format);
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...
1584
	$end['time_local'] = timeformat($end['timestamp'], $time_format);
0 ignored issues
show
Documentation introduced by
$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...
1585
1586
	// Strings showing the datetimes in the user's preferred format, relative to the event's time zone
1587
	$start['date_orig'] = timeformat(strtotime(date_format($start_object, 'Y-m-d H:i:s')), $date_format, 'none');
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...
1588
	$start['time_orig'] = timeformat(strtotime(date_format($start_object, 'Y-m-d H:i:s')), $time_format, 'none');
0 ignored issues
show
Documentation introduced by
$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...
1589
	$end['date_orig'] = timeformat(strtotime(date_format($end_object, 'Y-m-d H:i:s')), $date_format, 'none');
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...
1590
	$end['time_orig'] = timeformat(strtotime(date_format($end_object, 'Y-m-d H:i:s')), $time_format, 'none');
0 ignored issues
show
Documentation introduced by
$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...
1591
1592
	// The time zone identifier (e.g. 'Europe/London') and abbreviation (e.g. 'GMT')
1593
	$tz = date_format($start_object, 'e');
1594
	$tz_abbrev = fix_tz_abbrev($row['timezone'], date_format($start_object, 'T'));
1595
1596
	return array($start, $end, $allday, $span, $tz, $tz_abbrev);
1597
}
1598
1599
/**
1600
 * Gets a member's selected timezone identifier directly from the database
1601
 *
1602
 * @param int $id_member The member id to look up. If not provided, the current user's id will be used.
1603
 * @return string The timezone identifier string for the user's timezone.
1604
 */
1605
function getUserTimezone($id_member = null)
1606
{
1607
	global $smcFunc, $context, $user_info, $modSettings, $user_settings;
1608
	static $member_cache = array();
1609
1610
	if (is_null($id_member) && $user_info['is_guest'] == false)
1611
		$id_member = $context['user']['id'];
1612
1613
	//check if the cache got the data
1614
	if (isset($id_member) && isset($member_cache[$id_member]))
1615
	{
1616
		return $member_cache[$id_member];
1617
	}
1618
1619
	//maybe the current user is the one
1620
	if (isset($user_settings['id_member']) && $user_settings['id_member'] == $id_member)
1621
	{
1622
		$member_cache[$id_member] = $user_settings['timezone'];
1623
		return $user_settings['timezone'];
1624
	}
1625
1626 View Code Duplication
	if (isset($id_member))
1627
	{
1628
		$request = $smcFunc['db_query']('', '
1629
			SELECT timezone
1630
			FROM {db_prefix}members
1631
			WHERE id_member = {int:id_member}',
1632
			array(
1633
				'id_member' => $id_member,
1634
			)
1635
		);
1636
		list($timezone) = $smcFunc['db_fetch_row']($request);
1637
		$smcFunc['db_free_result']($request);
1638
	}
1639
1640 View Code Duplication
	if (empty($timezone) || !in_array($timezone, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1641
		$timezone = isset($modSettings['default_timezone']) ? $modSettings['default_timezone'] : date_default_timezone_get();
1642
1643
	if (isset($id_member))
1644
		$member_cache[$id_member] = $timezone;
1645
1646
	return $timezone;
1647
}
1648
1649
/**
1650
 * Gets all of the holidays for the listing
1651
 *
1652
 * @param int $start The item to start with (for pagination purposes)
1653
 * @param int $items_per_page How many items to show on each page
1654
 * @param string $sort A string indicating how to sort the results
1655
 * @return array An array of holidays, each of which is an array containing the id, year, month, day and title of the holiday
1656
 */
1657 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...
1658
{
1659
	global $smcFunc;
1660
1661
	$request = $smcFunc['db_query']('', '
1662
		SELECT id_holiday, YEAR(event_date) AS year, MONTH(event_date) AS month, DAYOFMONTH(event_date) AS day, title
1663
		FROM {db_prefix}calendar_holidays
1664
		ORDER BY {raw:sort}
1665
		LIMIT {int:start}, {int:max}',
1666
		array(
1667
			'sort' => $sort,
1668
			'start' => $start,
1669
			'max' => $items_per_page,
1670
		)
1671
	);
1672
	$holidays = array();
1673
	while ($row = $smcFunc['db_fetch_assoc']($request))
1674
		$holidays[] = $row;
1675
	$smcFunc['db_free_result']($request);
1676
1677
	return $holidays;
1678
}
1679
1680
/**
1681
 * Helper function to get the total number of holidays
1682
 *
1683
 * @return int The total number of holidays
1684
 */
1685
function list_getNumHolidays()
1686
{
1687
	global $smcFunc;
1688
1689
	$request = $smcFunc['db_query']('', '
1690
		SELECT COUNT(*)
1691
		FROM {db_prefix}calendar_holidays',
1692
		array(
1693
		)
1694
	);
1695
	list($num_items) = $smcFunc['db_fetch_row']($request);
1696
	$smcFunc['db_free_result']($request);
1697
1698
	return (int) $num_items;
1699
}
1700
1701
/**
1702
 * Remove a holiday from the calendar
1703
 *
1704
 * @param array $holiday_ids An array of IDs of holidays to delete
1705
 */
1706
function removeHolidays($holiday_ids)
1707
{
1708
	global $smcFunc;
1709
1710
	$smcFunc['db_query']('', '
1711
		DELETE FROM {db_prefix}calendar_holidays
1712
		WHERE id_holiday IN ({array_int:id_holiday})',
1713
		array(
1714
			'id_holiday' => $holiday_ids,
1715
		)
1716
	);
1717
1718
	updateSettings(array(
1719
		'calendar_updated' => time(),
1720
	));
1721
}
1722
1723
?>
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...