Completed
Push — release-2.1 ( 52bb58...901660 )
by Michael
11:11
created

Subs-Calendar.php ➔ buildEventDatetimes()   D

Complexity

Conditions 17
Paths 288

Size

Total Lines 73
Code Lines 38

Duplication

Lines 7
Ratio 9.59 %

Importance

Changes 0
Metric Value
cc 17
eloc 38
nc 288
nop 1
dl 7
loc 73
rs 4.0746
c 0
b 0
f 0

How to fix   Long Method    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
	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']))
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)
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]))
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 int $days_to_index How many days' worth of info to index
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($days_to_index)
744
{
745
	$low_date = strftime('%Y-%m-%d', forum_time(false) - 24 * 3600);
746
	$high_date = strftime('%Y-%m-%d', forum_time(false) + $days_to_index * 24 * 3600);
747
748
	return array(
749
		'data' => array(
750
			'holidays' => getHolidayRange($low_date, $high_date),
751
			'birthdays' => getBirthdayRange($low_date, $high_date),
752
			'events' => getEventRange($low_date, $high_date, false),
753
		),
754
		'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\']);',
755
		'expires' => time() + 3600,
756
	);
757
}
758
759
/**
760
 * cache callback function used to retrieve the upcoming birthdays, holidays, and events within the given period, taking into account the users time offset.
761
 * Called from the BoardIndex to display the current day's events on the board index
762
 * used by the board index and SSI to show the upcoming events.
763
 * @param array $eventOptions An array of event options. Only 'num_days_shown' is used here
764
 * @return array An array containing the info that was cached as well as a few other relevant things
765
 */
766
function cache_getRecentEvents($eventOptions)
767
{
768
	// With the 'static' cached data we can calculate the user-specific data.
769
	$cached_data = cache_quick_get('calendar_index', 'Subs-Calendar.php', 'cache_getOffsetIndependentEvents', array($eventOptions['num_days_shown']));
770
771
	// Get the information about today (from user perspective).
772
	$today = getTodayInfo();
773
774
	$return_data = array(
775
		'calendar_holidays' => array(),
776
		'calendar_birthdays' => array(),
777
		'calendar_events' => array(),
778
	);
779
780
	// Set the event span to be shown in seconds.
781
	$days_for_index = $eventOptions['num_days_shown'] * 86400;
782
783
	// Get the current member time/date.
784
	$now = forum_time();
785
786
	// Holidays between now and now + days.
787
	for ($i = $now; $i < $now + $days_for_index; $i += 86400)
788
	{
789
		if (isset($cached_data['holidays'][strftime('%Y-%m-%d', $i)]))
790
			$return_data['calendar_holidays'] = array_merge($return_data['calendar_holidays'], $cached_data['holidays'][strftime('%Y-%m-%d', $i)]);
791
	}
792
793
	// Happy Birthday, guys and gals!
794
	for ($i = $now; $i < $now + $days_for_index; $i += 86400)
795
	{
796
		$loop_date = strftime('%Y-%m-%d', $i);
797
		if (isset($cached_data['birthdays'][$loop_date]))
798
		{
799
			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...
800
				$cached_data['birthdays'][strftime('%Y-%m-%d', $i)][$index]['is_today'] = $loop_date === $today['date'];
801
			$return_data['calendar_birthdays'] = array_merge($return_data['calendar_birthdays'], $cached_data['birthdays'][$loop_date]);
802
		}
803
	}
804
805
	$duplicates = array();
806
	for ($i = $now; $i < $now + $days_for_index; $i += 86400)
807
	{
808
		// Determine the date of the current loop step.
809
		$loop_date = strftime('%Y-%m-%d', $i);
810
811
		// No events today? Check the next day.
812
		if (empty($cached_data['events'][$loop_date]))
813
			continue;
814
815
		// Loop through all events to add a few last-minute values.
816
		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...
817
		{
818
			// Create a shortcut variable for easier access.
819
			$this_event = &$cached_data['events'][$loop_date][$ev];
820
821
			// Skip duplicates.
822
			if (isset($duplicates[$this_event['topic'] . $this_event['title']]))
823
			{
824
				unset($cached_data['events'][$loop_date][$ev]);
825
				continue;
826
			}
827
			else
828
				$duplicates[$this_event['topic'] . $this_event['title']] = true;
829
830
			// Might be set to true afterwards, depending on the permissions.
831
			$this_event['can_edit'] = false;
832
			$this_event['is_today'] = $loop_date === $today['date'];
833
			$this_event['date'] = $loop_date;
834
		}
835
836
		if (!empty($cached_data['events'][$loop_date]))
837
			$return_data['calendar_events'] = array_merge($return_data['calendar_events'], $cached_data['events'][$loop_date]);
838
	}
839
840
	// Mark the last item so that a list separator can be used in the template.
841 View Code Duplication
	for ($i = 0, $n = count($return_data['calendar_birthdays']); $i < $n; $i++)
842
		$return_data['calendar_birthdays'][$i]['is_last'] = !isset($return_data['calendar_birthdays'][$i + 1]);
843 View Code Duplication
	for ($i = 0, $n = count($return_data['calendar_events']); $i < $n; $i++)
844
		$return_data['calendar_events'][$i]['is_last'] = !isset($return_data['calendar_events'][$i + 1]);
845
846
	return array(
847
		'data' => $return_data,
848
		'expires' => time() + 3600,
849
		'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\']);',
850
		'post_retri_eval' => '
851
			global $context, $scripturl, $user_info;
852
853
			foreach ($cache_block[\'data\'][\'calendar_events\'] as $k => $event)
854
			{
855
				// Remove events that the user may not see or wants to ignore.
856
				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\']))
857
					unset($cache_block[\'data\'][\'calendar_events\'][$k]);
858
				else
859
				{
860
					// Whether the event can be edited depends on the permissions.
861
					$cache_block[\'data\'][\'calendar_events\'][$k][\'can_edit\'] = allowedTo(\'calendar_edit_any\') || ($event[\'poster\'] == $user_info[\'id\'] && allowedTo(\'calendar_edit_own\'));
862
863
					// The added session code makes this URL not cachable.
864
					$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\'];
865
				}
866
			}
867
868
			if (empty($params[0][\'include_holidays\']))
869
				$cache_block[\'data\'][\'calendar_holidays\'] = array();
870
			if (empty($params[0][\'include_birthdays\']))
871
				$cache_block[\'data\'][\'calendar_birthdays\'] = array();
872
			if (empty($params[0][\'include_events\']))
873
				$cache_block[\'data\'][\'calendar_events\'] = array();
874
875
			$cache_block[\'data\'][\'show_calendar\'] = !empty($cache_block[\'data\'][\'calendar_holidays\']) || !empty($cache_block[\'data\'][\'calendar_birthdays\']) || !empty($cache_block[\'data\'][\'calendar_events\']);',
876
	);
877
}
878
879
/**
880
 * Makes sure the calendar post is valid.
881
 */
882
function validateEventPost()
883
{
884
	global $modSettings, $smcFunc;
885
886
	if (!isset($_POST['deleteevent']))
887
	{
888
		// The 2.1 way
889
		if (isset($_POST['start_date']))
890
		{
891
			$d = date_parse($_POST['start_date']);
892 View Code Duplication
			if (!empty($d['error_count']) || !empty($d['warning_count']))
893
				fatal_lang_error('invalid_date', false);
894
			if (empty($d['year']))
895
				fatal_lang_error('event_year_missing', false);
896
			if (empty($d['month']))
897
				fatal_lang_error('event_month_missing', false);
898
		}
899
		elseif (isset($_POST['start_datetime']))
900
		{
901
			$d = date_parse($_POST['start_datetime']);
902 View Code Duplication
			if (!empty($d['error_count']) || !empty($d['warning_count']))
903
				fatal_lang_error('invalid_date', false);
904
			if (empty($d['year']))
905
				fatal_lang_error('event_year_missing', false);
906
			if (empty($d['month']))
907
				fatal_lang_error('event_month_missing', false);
908
		}
909
		// The 2.0 way
910
		else
911
		{
912
			// No month?  No year?
913
			if (!isset($_POST['month']))
914
				fatal_lang_error('event_month_missing', false);
915
			if (!isset($_POST['year']))
916
				fatal_lang_error('event_year_missing', false);
917
918
			// Check the month and year...
919
			if ($_POST['month'] < 1 || $_POST['month'] > 12)
920
				fatal_lang_error('invalid_month', false);
921 View Code Duplication
			if ($_POST['year'] < $modSettings['cal_minyear'] || $_POST['year'] > $modSettings['cal_maxyear'])
922
				fatal_lang_error('invalid_year', false);
923
		}
924
	}
925
926
	// Make sure they're allowed to post...
927
	isAllowedTo('calendar_post');
928
929
	// If they want to us to calculate an end date, make sure it will fit in an acceptable range.
930
	if (isset($_POST['span']))
931
	{
932
		if (($_POST['span'] < 1) || (!empty($modSettings['cal_maxspan']) && $_POST['span'] > $modSettings['cal_maxspan']))
933
			fatal_lang_error('invalid_days_numb', false);
934
	}
935
936
	// There is no need to validate the following values if we are just deleting the event.
937
	if (!isset($_POST['deleteevent']))
938
	{
939
		// If we're doing things the 2.0 way, check the day
940
		if (empty($_POST['start_date']) && empty($_POST['start_datetime']))
941
		{
942
			// No day?
943
			if (!isset($_POST['day']))
944
				fatal_lang_error('event_day_missing', false);
945
946
			// Bad day?
947
			if (!checkdate($_POST['month'], $_POST['day'], $_POST['year']))
948
				fatal_lang_error('invalid_date', false);
949
		}
950
951
		if (!isset($_POST['evtitle']) && !isset($_POST['subject']))
952
			fatal_lang_error('event_title_missing', false);
953
		elseif (!isset($_POST['evtitle']))
954
			$_POST['evtitle'] = $_POST['subject'];
955
956
		// No title?
957
		if ($smcFunc['htmltrim']($_POST['evtitle']) === '')
958
			fatal_lang_error('no_event_title', false);
959 View Code Duplication
		if ($smcFunc['strlen']($_POST['evtitle']) > 100)
960
			$_POST['evtitle'] = $smcFunc['substr']($_POST['evtitle'], 0, 100);
961
		$_POST['evtitle'] = str_replace(';', '', $_POST['evtitle']);
962
	}
963
}
964
965
/**
966
 * Get the event's poster.
967
 *
968
 * @param int $event_id The ID of the event
969
 * @return int|bool The ID of the poster or false if the event was not found
970
 */
971 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...
972
{
973
	global $smcFunc;
974
975
	// A simple database query, how hard can that be?
976
	$request = $smcFunc['db_query']('', '
977
		SELECT id_member
978
		FROM {db_prefix}calendar
979
		WHERE id_event = {int:id_event}
980
		LIMIT 1',
981
		array(
982
			'id_event' => $event_id,
983
		)
984
	);
985
986
	// No results, return false.
987
	if ($smcFunc['db_num_rows'] === 0)
988
		return false;
989
990
	// Grab the results and return.
991
	list ($poster) = $smcFunc['db_fetch_row']($request);
992
	$smcFunc['db_free_result']($request);
993
	return (int) $poster;
994
}
995
996
/**
997
 * Consolidating the various INSERT statements into this function.
998
 * Inserts the passed event information into the calendar table.
999
 * Allows to either set a time span (in days) or an end_date.
1000
 * Does not check any permissions of any sort.
1001
 *
1002
 * @param array $eventOptions An array of event options ('title', 'span', 'start_date', 'end_date', etc.)
1003
 */
1004
function insertEvent(&$eventOptions)
1005
{
1006
	global $smcFunc, $context;
1007
1008
	// Add special chars to the title.
1009
	$eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES);
1010
1011
	$eventOptions['location'] = isset($eventOptions['location']) ? $smcFunc['htmlspecialchars']($eventOptions['location'], ENT_QUOTES) : '';
1012
1013
	// Set the start and end dates and times
1014
	list($start_date, $end_date, $start_time, $end_time, $tz) = setEventStartEnd($eventOptions);
1015
1016
	// If no topic and board are given, they are not linked to a topic.
1017
	$eventOptions['board'] = isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0;
1018
	$eventOptions['topic'] = isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0;
1019
1020
	$event_columns = array(
1021
		'id_board' => 'int', 'id_topic' => 'int', 'title' => 'string-60', 'id_member' => 'int',
1022
		'start_date' => 'date', 'end_date' => 'date', 'location' => 'string-255',
1023
	);
1024
	$event_parameters = array(
1025
		$eventOptions['board'], $eventOptions['topic'], $eventOptions['title'], $eventOptions['member'],
1026
		$start_date, $end_date, $eventOptions['location'],
1027
	);
1028 View Code Duplication
	if (!empty($start_time) && !empty($end_time) && !empty($tz) && in_array($tz, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1029
	{
1030
		$event_columns['start_time'] = 'time';
1031
		$event_parameters[] = $start_time;
1032
		$event_columns['end_time'] = 'time';
1033
		$event_parameters[] = $end_time;
1034
		$event_columns['timezone'] = 'string';
1035
		$event_parameters[] = $tz;
1036
	}
1037
1038
	call_integration_hook('integrate_create_event', array(&$eventOptions, &$event_columns, &$event_parameters));
1039
1040
	// Insert the event!
1041
	$eventOptions['id'] = $smcFunc['db_insert']('',
1042
		'{db_prefix}calendar',
1043
		$event_columns,
1044
		$event_parameters,
1045
		array('id_event'),
1046
		1
1047
	);
1048
1049
	// If this isn't tied to a topic, we need to notify people about it.
1050
	if (empty($eventOptions['topic']))
1051
	{
1052
		$smcFunc['db_insert']('insert',
1053
			'{db_prefix}background_tasks',
1054
			array('task_file' => 'string', 'task_class' => 'string', 'task_data' => 'string', 'claimed_time' => 'int'),
1055
			array('$sourcedir/tasks/EventNew-Notify.php', 'EventNew_Notify_Background', $smcFunc['json_encode'](array(
1056
				'event_title' => $eventOptions['title'],
1057
				'event_id' => $eventOptions['id'],
1058
				'sender_id' => $eventOptions['member'],
1059
				'sender_name' => $eventOptions['member'] == $context['user']['id'] ? $context['user']['name'] : '',
1060
				'time' => time(),
1061
			)), 0),
1062
			array('id_task')
1063
		);
1064
	}
1065
1066
	// Update the settings to show something calendar-ish was updated.
1067
	updateSettings(array(
1068
		'calendar_updated' => time(),
1069
	));
1070
}
1071
1072
/**
1073
 * modifies an event.
1074
 * allows to either set a time span (in days) or an end_date.
1075
 * does not check any permissions of any sort.
1076
 *
1077
 * @param int $event_id The ID of the event
1078
 * @param array $eventOptions An array of event information
1079
 */
1080
function modifyEvent($event_id, &$eventOptions)
1081
{
1082
	global $smcFunc;
1083
1084
	// Properly sanitize the title and location
1085
	$eventOptions['title'] = $smcFunc['htmlspecialchars']($eventOptions['title'], ENT_QUOTES);
1086
	$eventOptions['location'] = $smcFunc['htmlspecialchars']($eventOptions['location'], ENT_QUOTES);
1087
1088
	// Set the new start and end dates and times
1089
	list($start_date, $end_date, $start_time, $end_time, $tz) = setEventStartEnd($eventOptions);
1090
1091
	$event_columns = array(
1092
		'start_date' => '{date:start_date}',
1093
		'end_date' => '{date:end_date}',
1094
		'title' => 'SUBSTRING({string:title}, 1, 60)',
1095
		'id_board' => '{int:id_board}',
1096
		'id_topic' => '{int:id_topic}',
1097
		'location' => 'SUBSTRING({string:location}, 1, 255)',
1098
	);
1099
	$event_parameters = array(
1100
		'start_date' => $start_date,
1101
		'end_date' => $end_date,
1102
		'title' => $eventOptions['title'],
1103
		'location' => $eventOptions['location'],
1104
		'id_board' => isset($eventOptions['board']) ? (int) $eventOptions['board'] : 0,
1105
		'id_topic' => isset($eventOptions['topic']) ? (int) $eventOptions['topic'] : 0,
1106
	);
1107 View Code Duplication
	if (!empty($start_time) && !empty($end_time) && !empty($tz) && in_array($tz, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1108
	{
1109
		$event_columns['start_time'] = '{time:start_time}';
1110
		$event_parameters['start_time'] = $start_time;
1111
		$event_columns['end_time'] = '{time:end_time}';
1112
		$event_parameters['end_time'] = $end_time;
1113
		$event_columns['timezone'] = '{string:timezone}';
1114
		$event_parameters['timezone'] = $tz;
1115
	}
1116
1117
	// This is to prevent hooks to modify the id of the event
1118
	$real_event_id = $event_id;
1119
	call_integration_hook('integrate_modify_event', array($event_id, &$eventOptions, &$event_columns, &$event_parameters));
1120
1121
	$column_clauses = array();
1122
	foreach ($event_columns as $col => $crit)
1123
		$column_clauses[] = $col . ' = ' . $crit;
1124
1125
	$smcFunc['db_query']('', '
1126
		UPDATE {db_prefix}calendar
1127
		SET
1128
			' . implode(', ', $column_clauses) . '
1129
		WHERE id_event = {int:id_event}',
1130
		array_merge(
1131
			$event_parameters,
1132
			array(
1133
				'id_event' => $real_event_id
1134
			)
1135
		)
1136
	);
1137
1138
	if (empty($start_time) || empty($end_time) || empty($tz) || !in_array($tz, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1139
	{
1140
		$smcFunc['db_query']('', '
1141
			UPDATE {db_prefix}calendar
1142
			SET start_time = NULL, end_time = NULL, timezone = NULL
1143
			WHERE id_event = {int:id_event}',
1144
			array(
1145
				'id_event' => $real_event_id
1146
			)
1147
		);
1148
	}
1149
1150
	updateSettings(array(
1151
		'calendar_updated' => time(),
1152
	));
1153
}
1154
1155
/**
1156
 * Remove an event
1157
 * removes an event.
1158
 * does no permission checks.
1159
 *
1160
 * @param int $event_id The ID of the event to remove
1161
 */
1162
function removeEvent($event_id)
1163
{
1164
	global $smcFunc;
1165
1166
	$smcFunc['db_query']('', '
1167
		DELETE FROM {db_prefix}calendar
1168
		WHERE id_event = {int:id_event}',
1169
		array(
1170
			'id_event' => $event_id,
1171
		)
1172
	);
1173
1174
	call_integration_hook('integrate_remove_event', array($event_id));
1175
1176
	updateSettings(array(
1177
		'calendar_updated' => time(),
1178
	));
1179
}
1180
1181
/**
1182
 * Gets all the events properties
1183
 *
1184
 * @param int $event_id The ID of the event
1185
 * @return array An array of event information
1186
 */
1187
function getEventProperties($event_id)
1188
{
1189
	global $smcFunc;
1190
1191
	$request = $smcFunc['db_query']('', '
1192
		SELECT
1193
			c.id_event, c.id_board, c.id_topic, c.id_member, c.title,
1194
			c.start_date, c.end_date, c.start_time, c.end_time, c.timezone, c.location,
1195
			t.id_first_msg, t.id_member_started,
1196
			mb.real_name, m.modified_time
1197
		FROM {db_prefix}calendar AS c
1198
			LEFT JOIN {db_prefix}topics AS t ON (t.id_topic = c.id_topic)
1199
			LEFT JOIN {db_prefix}members AS mb ON (mb.id_member = t.id_member_started)
1200
			LEFT JOIN {db_prefix}messages AS m ON (m.id_msg  = t.id_first_msg)
1201
		WHERE c.id_event = {int:id_event}',
1202
		array(
1203
			'id_event' => $event_id,
1204
		)
1205
	);
1206
1207
	// If nothing returned, we are in poo, poo.
1208
	if ($smcFunc['db_num_rows']($request) === 0)
1209
		return false;
1210
1211
	$row = $smcFunc['db_fetch_assoc']($request);
1212
	$smcFunc['db_free_result']($request);
1213
1214
	list($start, $end, $allday, $span, $tz, $tz_abbrev) = buildEventDatetimes($row);
1215
1216
	// Sanity check
1217 View Code Duplication
	if (!empty($start['error_count']) || !empty($start['warning_count']) || !empty($end['error_count']) || !empty($end['warning_count']))
1218
		return false;
1219
1220
	$return_value = array(
1221
		'boards' => array(),
1222
		'board' => $row['id_board'],
1223
		'new' => 0,
1224
		'eventid' => $event_id,
1225
		'year' => $start['year'],
1226
		'month' => $start['month'],
1227
		'day' => $start['day'],
1228
		'hour' => !$allday ? $start['hour'] : null,
1229
		'minute' => !$allday ? $start['minute'] : null,
1230
		'second' => !$allday ? $start['second'] : null,
1231
		'start_date' => $row['start_date'],
1232
		'start_date_local' => $start['date_local'],
1233
		'start_date_orig' => $start['date_orig'],
1234
		'start_time' => !$allday ? $row['start_time'] : null,
1235
		'start_time_local' => !$allday ? $start['time_local'] : null,
1236
		'start_time_orig' => !$allday ? $start['time_orig'] : null,
1237
		'start_timestamp' => $start['timestamp'],
1238
		'start_datetime' => $start['datetime'],
1239
		'start_iso_gmdate' => $start['iso_gmdate'],
1240
		'end_year' => $end['year'],
1241
		'end_month' => $end['month'],
1242
		'end_day' => $end['day'],
1243
		'end_hour' => !$allday ? $end['hour'] : null,
1244
		'end_minute' => !$allday ? $end['minute'] : null,
1245
		'end_second' => !$allday ? $end['second'] : null,
1246
		'end_date' => $row['end_date'],
1247
		'end_date_local' => $end['date_local'],
1248
		'end_date_orig' => $end['date_orig'],
1249
		'end_time' => !$allday ? $row['end_time'] : null,
1250
		'end_time_local' => !$allday ? $end['time_local'] : null,
1251
		'end_time_orig' => !$allday ? $end['time_orig'] : null,
1252
		'end_timestamp' => $end['timestamp'],
1253
		'end_datetime' => $end['datetime'],
1254
		'end_iso_gmdate' => $end['iso_gmdate'],
1255
		'allday' => $allday,
1256
		'tz' => !$allday ? $tz : null,
1257
		'tz_abbrev' => !$allday ? $tz_abbrev : null,
1258
		'span' => $span,
1259
		'title' => $row['title'],
1260
		'location' => $row['location'],
1261
		'member' => $row['id_member'],
1262
		'realname' => $row['real_name'],
1263
		'sequence' => $row['modified_time'],
1264
		'topic' => array(
1265
			'id' => $row['id_topic'],
1266
			'member_started' => $row['id_member_started'],
1267
			'first_msg' => $row['id_first_msg'],
1268
		),
1269
	);
1270
1271
	$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']));
1272
1273
	return $return_value;
1274
}
1275
1276
/**
1277
 * Gets an initial set of date and time values for creating a new event.
1278
 *
1279
 * @return array An array containing an initial set of date and time values for an event.
1280
 */
1281
function getNewEventDatetimes()
1282
{
1283
	// Ensure setEventStartEnd() has something to work with
1284
	$now = date_create();
1285
	$_POST['year'] = !empty($_POST['year']) ? $_POST['year'] : date_format($now, 'Y');
1286
	$_POST['month'] = !empty($_POST['month']) ? $_POST['month'] : date_format($now, 'm');
1287
	$_POST['day'] = !empty($_POST['day']) ? $_POST['day'] : date_format($now, 'd');
1288
	$_POST['hour'] = !empty($_POST['hour']) ? $_POST['hour'] : date_format($now, 'H');
1289
	$_POST['minute'] = !empty($_POST['minute']) ? $_POST['minute'] : date_format($now, 'i');
1290
	$_POST['second'] = !empty($_POST['second']) ? $_POST['second'] : date_format($now, 's');
1291
1292
	// Set the basic values for the new event
1293
	$row_keys = array('start_date', 'end_date', 'start_time', 'end_time', 'timezone');
1294
	$row = array_combine($row_keys, setEventStartEnd());
1295
1296
	// And now set the full suite of values
1297
	list($start, $end, $allday, $span, $tz, $tz_abbrev) = buildEventDatetimes($row);
1298
1299
	// Default theme only uses some of this info, but others might want it all
1300
	$eventProperties = array(
1301
		'year' => $start['year'],
1302
		'month' => $start['month'],
1303
		'day' => $start['day'],
1304
		'hour' => !$allday ? $start['hour'] : null,
1305
		'minute' => !$allday ? $start['minute'] : null,
1306
		'second' => !$allday ? $start['second'] : null,
1307
		'start_date' => $row['start_date'],
1308
		'start_date_local' => $start['date_local'],
1309
		'start_date_orig' => $start['date_orig'],
1310
		'start_time' => !$allday ? $row['start_time'] : null,
1311
		'start_time_local' => !$allday ? $start['time_local'] : null,
1312
		'start_time_orig' => !$allday ? $start['time_orig'] : null,
1313
		'start_timestamp' => $start['timestamp'],
1314
		'start_datetime' => $start['datetime'],
1315
		'start_iso_gmdate' => $start['iso_gmdate'],
1316
		'end_year' => $end['year'],
1317
		'end_month' => $end['month'],
1318
		'end_day' => $end['day'],
1319
		'end_hour' => !$allday ? $end['hour'] : null,
1320
		'end_minute' => !$allday ? $end['minute'] : null,
1321
		'end_second' => !$allday ? $end['second'] : null,
1322
		'end_date' => $row['end_date'],
1323
		'end_date_local' => $end['date_local'],
1324
		'end_date_orig' => $end['date_orig'],
1325
		'end_time' => !$allday ? $row['end_time'] : null,
1326
		'end_time_local' => !$allday ? $end['time_local'] : null,
1327
		'end_time_orig' => !$allday ? $end['time_orig'] : null,
1328
		'end_timestamp' => $end['timestamp'],
1329
		'end_datetime' => $end['datetime'],
1330
		'end_iso_gmdate' => $end['iso_gmdate'],
1331
		'allday' => $allday,
1332
		'tz' => !$allday ? $tz : null,
1333
		'tz_abbrev' => !$allday ? $tz_abbrev : null,
1334
		'span' => $span,
1335
	);
1336
1337
	return $eventProperties;
1338
}
1339
1340
/**
1341
 * Set the start and end dates and times for a posted event for insertion into the database.
1342
 * Validates all date and times given to it.
1343
 * Makes sure events do not exceed the maximum allowed duration (if any).
1344
 * If passed an array that defines any time or date parameters, they will be used. Otherwise, gets the values from $_POST.
1345
 *
1346
 * @param array $eventOptions An array of optional time and date parameters (span, start_year, end_month, etc., etc.)
1347
 * @return array An array containing $start_date, $end_date, $start_time, $end_time
1348
 */
1349
function setEventStartEnd($eventOptions = array())
1350
{
1351
	global $modSettings;
1352
1353
	// Set $span, in case we need it
1354
	$span = isset($eventOptions['span']) ? $eventOptions['span'] : (isset($_POST['span']) ? $_POST['span'] : 0);
1355
	if ($span > 0)
1356
		$span = !empty($modSettings['cal_maxspan']) ? min($modSettings['cal_maxspan'], $span - 1) : $span - 1;
1357
1358
	// Define the timezone for this event, falling back to the default if not provided
1359
	if (!empty($eventOptions['tz']) && in_array($eventOptions['tz'], timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1360
		$tz = $eventOptions['tz'];
1361 View Code Duplication
	elseif (!empty($_POST['tz']) && in_array($_POST['tz'], timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1362
		$tz = $_POST['tz'];
1363
	else
1364
		$tz = getUserTimezone();
1365
1366
	// Is this supposed to be an all day event, or should it have specific start and end times?
1367
	if (isset($eventOptions['allday']))
1368
		$allday = $eventOptions['allday'];
1369
	elseif (empty($_POST['allday']))
1370
		$allday = false;
1371
	else
1372
		$allday = true;
1373
1374
	// Input might come as individual parameters...
1375
	$start_year = isset($eventOptions['year']) ? $eventOptions['year'] : (isset($_POST['year']) ? $_POST['year'] : null);
1376
	$start_month = isset($eventOptions['month']) ? $eventOptions['month'] : (isset($_POST['month']) ? $_POST['month'] : null);
1377
	$start_day = isset($eventOptions['day']) ? $eventOptions['day'] : (isset($_POST['day']) ? $_POST['day'] : null);
1378
	$start_hour = isset($eventOptions['hour']) ? $eventOptions['hour'] : (isset($_POST['hour']) ? $_POST['hour'] : null);
1379
	$start_minute = isset($eventOptions['minute']) ? $eventOptions['minute'] : (isset($_POST['minute']) ? $_POST['minute'] : null);
1380
	$start_second = isset($eventOptions['second']) ? $eventOptions['second'] : (isset($_POST['second']) ? $_POST['second'] : null);
1381
	$end_year = isset($eventOptions['end_year']) ? $eventOptions['end_year'] : (isset($_POST['end_year']) ? $_POST['end_year'] : null);
1382
	$end_month = isset($eventOptions['end_month']) ? $eventOptions['end_month'] : (isset($_POST['end_month']) ? $_POST['end_month'] : null);
1383
	$end_day = isset($eventOptions['end_day']) ? $eventOptions['end_day'] : (isset($_POST['end_day']) ? $_POST['end_day'] : null);
1384
	$end_hour = isset($eventOptions['end_hour']) ? $eventOptions['end_hour'] : (isset($_POST['end_hour']) ? $_POST['end_hour'] : null);
1385
	$end_minute = isset($eventOptions['end_minute']) ? $eventOptions['end_minute'] : (isset($_POST['end_minute']) ? $_POST['end_minute'] : null);
1386
	$end_second = isset($eventOptions['end_second']) ? $eventOptions['end_second'] : (isset($_POST['end_second']) ? $_POST['end_second'] : null);
1387
1388
	// ... or as datetime strings ...
1389
	$start_string = isset($eventOptions['start_datetime']) ? $eventOptions['start_datetime'] : (isset($_POST['start_datetime']) ? $_POST['start_datetime'] : null);
1390
	$end_string = isset($eventOptions['end_datetime']) ? $eventOptions['end_datetime'] : (isset($_POST['end_datetime']) ? $_POST['end_datetime'] : null);
1391
1392
	// ... or as date strings and time strings.
1393
	$start_date_string = isset($eventOptions['start_date']) ? $eventOptions['start_date'] : (isset($_POST['start_date']) ? $_POST['start_date'] : null);
1394
	$start_time_string = isset($eventOptions['start_time']) ? $eventOptions['start_time'] : (isset($_POST['start_time']) ? $_POST['start_time'] : null);
1395
	$end_date_string = isset($eventOptions['end_date']) ? $eventOptions['end_date'] : (isset($_POST['end_date']) ? $_POST['end_date'] : null);
1396
	$end_time_string = isset($eventOptions['end_time']) ? $eventOptions['end_time'] : (isset($_POST['end_time']) ? $_POST['end_time'] : null);
1397
1398
	// If the date and time were given in separate strings, combine them
1399
	if (empty($start_string) && isset($start_date_string))
1400
		$start_string = $start_date_string . (isset($start_time_string) ? ' ' . $start_time_string : '');
1401
	if (empty($end_string) && isset($end_date_string))
1402
		$end_string = $end_date_string . (isset($end_time_string) ? ' ' . $end_time_string : '');
1403
1404
	// If some form of string input was given, override individually defined options with it
1405 View Code Duplication
	if (isset($start_string))
1406
	{
1407
		$start_string_parsed = date_parse($start_string);
1408
		if (empty($start_string_parsed['error_count']) && empty($start_string_parsed['warning_count']))
1409
		{
1410
			if ($start_string_parsed['year'] != false)
1411
			{
1412
				$start_year = $start_string_parsed['year'];
1413
				$start_month = $start_string_parsed['month'];
1414
				$start_day = $start_string_parsed['day'];
1415
			}
1416
			if ($start_string_parsed['hour'] != false)
1417
			{
1418
				$start_hour = $start_string_parsed['hour'];
1419
				$start_minute = $start_string_parsed['minute'];
1420
				$start_second = $start_string_parsed['second'];
1421
			}
1422
		}
1423
	}
1424 View Code Duplication
	if (isset($end_string))
1425
	{
1426
		$end_string_parsed = date_parse($end_string);
1427
		if (empty($end_string_parsed['error_count']) && empty($end_string_parsed['warning_count']))
1428
		{
1429
			if ($end_string_parsed['year'] != false)
1430
			{
1431
				$end_year = $end_string_parsed['year'];
1432
				$end_month = $end_string_parsed['month'];
1433
				$end_day = $end_string_parsed['day'];
1434
			}
1435
			if ($end_string_parsed['hour'] != false)
1436
			{
1437
				$end_hour = $end_string_parsed['hour'];
1438
				$end_minute = $end_string_parsed['minute'];
1439
				$end_second = $end_string_parsed['second'];
1440
			}
1441
		}
1442
	}
1443
1444
	// Validate input
1445
	$start_date_isvalid = checkdate($start_month, $start_day, $start_year);
1446
	$end_date_isvalid = checkdate($end_month, $end_day, $end_year);
1447
1448
	$start_time_isset = (isset($start_hour) && isset($start_minute) && isset($start_second));
1449
	$d = date_parse(sprintf('%02d:%02d:%02d', $start_hour, $start_minute, $start_second));
1450
	$start_time_isvalid = ($d['error_count'] == 0 && $d['warning_count'] == 0) ? true : false;
1451
1452
	$end_time_isset = (isset($end_hour) && isset($end_minute) && isset($end_second));
1453
	$d = date_parse(sprintf('%02d:%02d:%02d', $end_hour, $end_minute, $end_second));
1454
	$end_time_isvalid = ($d['error_count'] == 0 && $d['warning_count'] == 0) ? true : false;
1455
1456
	// Uh-oh...
1457
	if ($start_date_isvalid === false)
1458
	{
1459
		fatal_lang_error('invalid_date', false);
1460
	}
1461
1462
	// Make sure we use valid values for everything
1463
	if ($end_date_isvalid === false)
1464
	{
1465
		$end_year = $start_year;
1466
		$end_month = $start_month;
1467
		$end_day = $start_day;
1468
	}
1469
1470
	if ($allday === true || $start_time_isset === false || $start_time_isvalid === false)
1471
	{
1472
		$allday = true;
1473
		$start_hour = 0;
1474
		$start_minute = 0;
1475
		$start_second = 0;
1476
	}
1477
1478
	if ($allday === true || $end_time_isvalid === false || $end_time_isset === false)
1479
	{
1480
		$end_hour = $start_hour;
1481
		$end_minute = $start_minute;
1482
		$end_second = $start_second;
1483
	}
1484
1485
	// Now create our datetime objects
1486
	$start_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, $start_hour, $start_minute, $start_second) . ' ' . $tz);
1487
	$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $end_year, $end_month, $end_day, $end_hour, $end_minute, $end_second) . ' ' . $tz);
1488
1489
	// Is $end_object too early?
1490
	if ($start_object >= $end_object)
1491
	{
1492
		$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, $start_hour, $start_minute, $start_second) . ' ' . $tz);
1493
		if ($span > 0)
1494
			date_add($end_object, date_interval_create_from_date_string($span . ' days'));
1495
		else
1496
			date_add($end_object, date_interval_create_from_date_string('1 hour'));
1497
	}
1498
1499
	// Is $end_object too late?
1500
	if (!empty($modSettings['cal_maxspan']))
1501
	{
1502
		$date_diff = date_diff($start_object, $end_object);
1503
		if ($date_diff->days > $modSettings['cal_maxspan'])
1504
		{
1505
			if ($modSettings['cal_maxspan'] > 1)
1506
			{
1507
				$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, $start_hour, $start_minute, $start_second) . ' ' . $tz);
1508
				date_add($end_object, date_interval_create_from_date_string($modSettings['cal_maxspan'] . ' days'));
1509
			}
1510
			else
1511
				$end_object = date_create(sprintf('%04d-%02d-%02d %02d:%02d:%02d', $start_year, $start_month, $start_day, '11', '59', '59') . ' ' . $tz);
1512
		}
1513
	}
1514
1515
	// Finally, make our strings
1516
	$start_date = date_format($start_object, 'Y-m-d');
1517
	$end_date = date_format($end_object, 'Y-m-d');
1518
1519
	if ($allday == true)
1520
	{
1521
		$start_time = null;
1522
		$end_time = null;
1523
		$tz = null;
1524
	}
1525
	else
1526
	{
1527
		$start_time = date_format($start_object, 'H:i:s');
1528
		$end_time = date_format($end_object, 'H:i:s');
1529
	}
1530
1531
	return array($start_date, $end_date, $start_time, $end_time, $tz);
1532
}
1533
1534
/**
1535
 * Helper function for getEventRange, getEventProperties, getNewEventDatetimes, etc.
1536
 *
1537
 * @param array $row A database row representing an event from the calendar table
1538
 * @return array An array containing the start and end date and time properties for the event
1539
 */
1540
function buildEventDatetimes($row)
1541
{
1542
	global $sourcedir, $user_info;
1543
	static $date_format = '', $time_format = '';
1544
1545
	require_once($sourcedir . '/Subs.php');
1546
	static $timezone_array = array();
1547
1548
	// First, try to create a better date format, ignoring the "time" elements.
1549
	if (empty($date_format))
1550
	{
1551
		if (preg_match('~%[AaBbCcDdeGghjmuYy](?:[^%]*%[AaBbCcDdeGghjmuYy])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
1552
			$date_format = '%F';
1553
		else
1554
			$date_format = $matches[0];
1555
	}
1556
1557
	// We want a fairly compact version of the time, but as close as possible to the user's settings.
1558 View Code Duplication
	if (empty($time_format))
1559
	{
1560
		if (preg_match('~%[HkIlMpPrRSTX](?:[^%]*%[HkIlMpPrRSTX])*~', $user_info['time_format'], $matches) == 0 || empty($matches[0]))
1561
			$time_format = '%k:%M';
1562
		else
1563
			$time_format = str_replace(array('%I', '%H', '%S', '%r', '%R', '%T'), array('%l', '%k', '', '%l:%M %p', '%k:%M', '%l:%M'), $matches[0]);
1564
	}
1565
1566
	// Should this be an all day event?
1567
	$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;
1568
1569
	// How many days does this event span?
1570
	$span = 1 + date_interval_format(date_diff(date_create($row['start_date']), date_create($row['end_date'])), '%d');
1571
1572
	// We need to have a defined timezone in the steps below
1573
	if (empty($row['timezone']))
1574
		$row['timezone'] = getUserTimezone();
1575
1576
	if (empty($timezone_array[$row['timezone']]))
1577
		$timezone_array[$row['timezone']] = timezone_open($row['timezone']);
1578
1579
	// Get most of the standard date information for the start and end datetimes
1580
	$start = date_parse($row['start_date'] . (!$allday ? ' ' . $row['start_time'] : ''));
1581
	$end = date_parse($row['end_date'] . (!$allday ? ' ' . $row['end_time'] : ''));
1582
1583
	// But we also want more info, so make some DateTime objects we can use
1584
	$start_object = date_create($row['start_date'] . (!$allday ? ' ' . $row['start_time'] : ''), $timezone_array[$row['timezone']]);
1585
	$end_object = date_create($row['end_date'] . (!$allday ? ' ' . $row['end_time'] : ''), $timezone_array[$row['timezone']]);
1586
1587
	// Unix timestamps are good
1588
	$start['timestamp'] = date_format($start_object, 'U');
1589
	$end['timestamp'] = date_format($end_object, 'U');
1590
1591
	// Datetime string without timezone  (e.g. '2016-12-28 22:45:30')
1592
	$start['datetime'] = date_format($start_object, 'Y-m-d H:i:s');
1593
	$end['datetime'] = date_format($start_object, 'Y-m-d H:i:s');
1594
1595
	// ISO formatted datetime string, relative to UTC (e.g. '2016-12-29T05:45:30+00:00')
1596
	$start['iso_gmdate'] = gmdate('c', $start['timestamp']);
1597
	$end['iso_gmdate'] = gmdate('c', $end['timestamp']);
1598
1599
	// Strings showing the datetimes in the user's preferred format, relative to the user's time zone
1600
	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...
1601
	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...
1602
1603
	// Strings showing the datetimes in the user's preferred format, relative to the event's time zone
1604
	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...
1605
	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...
1606
1607
	// The time zone identifier (e.g. 'Europe/London') and abbreviation (e.g. 'GMT')
1608
	$tz = date_format($start_object, 'e');
1609
	$tz_abbrev = fix_tz_abbrev($row['timezone'], date_format($start_object, 'T'));
1610
1611
	return array($start, $end, $allday, $span, $tz, $tz_abbrev);
1612
}
1613
1614
/**
1615
 * Gets a member's selected timezone identifier directly from the database
1616
 *
1617
 * @param int $id_member The member id to look up. If not provided, the current user's id will be used.
1618
 * @return string The timezone identifier string for the user's timezone.
1619
 */
1620
function getUserTimezone($id_member = null)
1621
{
1622
	global $smcFunc, $context, $user_info, $modSettings, $user_settings;
1623
	static $member_cache = array();
1624
1625
	if (is_null($id_member) && $user_info['is_guest'] == false)
1626
		$id_member = $context['user']['id'];
1627
1628
	//check if the cache got the data
1629
	if (isset($id_member) && isset($member_cache[$id_member]))
1630
	{
1631
		return $member_cache[$id_member];
1632
	}
1633
1634
	//maybe the current user is the one
1635
	if (isset($user_settings['id_member']) && $user_settings['id_member'] == $id_member)
1636
	{
1637
		$member_cache[$id_member] = $user_settings['timezone'];
1638
		return $user_settings['timezone'];
1639
	}
1640
1641 View Code Duplication
	if (isset($id_member))
1642
	{
1643
		$request = $smcFunc['db_query']('', '
1644
			SELECT timezone
1645
			FROM {db_prefix}members
1646
			WHERE id_member = {int:id_member}',
1647
			array(
1648
				'id_member' => $id_member,
1649
			)
1650
		);
1651
		list($timezone) = $smcFunc['db_fetch_row']($request);
1652
		$smcFunc['db_free_result']($request);
1653
	}
1654
1655 View Code Duplication
	if (empty($timezone) || !in_array($timezone, timezone_identifiers_list(DateTimeZone::ALL_WITH_BC)))
1656
		$timezone = isset($modSettings['default_timezone']) ? $modSettings['default_timezone'] : date_default_timezone_get();
1657
1658
	if (isset($id_member))
1659
		$member_cache[$id_member] = $timezone;
1660
1661
	return $timezone;
1662
}
1663
1664
/**
1665
 * Gets all of the holidays for the listing
1666
 *
1667
 * @param int $start The item to start with (for pagination purposes)
1668
 * @param int $items_per_page How many items to show on each page
1669
 * @param string $sort A string indicating how to sort the results
1670
 * @return array An array of holidays, each of which is an array containing the id, year, month, day and title of the holiday
1671
 */
1672 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...
1673
{
1674
	global $smcFunc;
1675
1676
	$request = $smcFunc['db_query']('', '
1677
		SELECT id_holiday, YEAR(event_date) AS year, MONTH(event_date) AS month, DAYOFMONTH(event_date) AS day, title
1678
		FROM {db_prefix}calendar_holidays
1679
		ORDER BY {raw:sort}
1680
		LIMIT {int:start}, {int:max}',
1681
		array(
1682
			'sort' => $sort,
1683
			'start' => $start,
1684
			'max' => $items_per_page,
1685
		)
1686
	);
1687
	$holidays = array();
1688
	while ($row = $smcFunc['db_fetch_assoc']($request))
1689
		$holidays[] = $row;
1690
	$smcFunc['db_free_result']($request);
1691
1692
	return $holidays;
1693
}
1694
1695
/**
1696
 * Helper function to get the total number of holidays
1697
 *
1698
 * @return int The total number of holidays
1699
 */
1700
function list_getNumHolidays()
1701
{
1702
	global $smcFunc;
1703
1704
	$request = $smcFunc['db_query']('', '
1705
		SELECT COUNT(*)
1706
		FROM {db_prefix}calendar_holidays',
1707
		array(
1708
		)
1709
	);
1710
	list($num_items) = $smcFunc['db_fetch_row']($request);
1711
	$smcFunc['db_free_result']($request);
1712
1713
	return (int) $num_items;
1714
}
1715
1716
/**
1717
 * Remove a holiday from the calendar
1718
 *
1719
 * @param array $holiday_ids An array of IDs of holidays to delete
1720
 */
1721
function removeHolidays($holiday_ids)
1722
{
1723
	global $smcFunc;
1724
1725
	$smcFunc['db_query']('', '
1726
		DELETE FROM {db_prefix}calendar_holidays
1727
		WHERE id_holiday IN ({array_int:id_holiday})',
1728
		array(
1729
			'id_holiday' => $holiday_ids,
1730
		)
1731
	);
1732
1733
	updateSettings(array(
1734
		'calendar_updated' => time(),
1735
	));
1736
}
1737
1738
?>
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...