Completed
Push — release-2.1 ( 7b49ae...3d25b8 )
by Mert
06:32
created

Subs.php ➔ smf_json_decode()   C

Complexity

Conditions 11
Paths 22

Size

Total Lines 59
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 11
eloc 39
c 1
b 0
f 1
nc 22
nop 2
dl 0
loc 59
rs 6.3545

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 has all the main functions in it that relate to, well, everything.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2016 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 3
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Update some basic statistics.
21
 *
22
 * 'member' statistic updates the latest member, the total member
23
 *  count, and the number of unapproved members.
24
 * 'member' also only counts approved members when approval is on, but
25
 *  is much more efficient with it off.
26
 *
27
 * 'message' changes the total number of messages, and the
28
 *  highest message id by id_msg - which can be parameters 1 and 2,
29
 *  respectively.
30
 *
31
 * 'topic' updates the total number of topics, or if parameter1 is true
32
 *  simply increments them.
33
 *
34
 * 'subject' updates the log_search_subjects in the event of a topic being
35
 *  moved, removed or split.  parameter1 is the topicid, parameter2 is the new subject
36
 *
37
 * 'postgroups' case updates those members who match condition's
38
 *  post-based membergroups in the database (restricted by parameter1).
39
 *
40
 * @param string $type Stat type - can be 'member', 'message', 'topic', 'subject' or 'postgroups'
41
 * @param mixed $parameter1 A parameter for updating the stats
42
 * @param mixed $parameter2 A 2nd parameter for updating the stats
43
 */
44
function updateStats($type, $parameter1 = null, $parameter2 = null)
45
{
46
	global $modSettings, $smcFunc;
47
48
	switch ($type)
49
	{
50
		case 'member':
51
			$changes = array(
52
				'memberlist_updated' => time(),
53
			);
54
55
			// #1 latest member ID, #2 the real name for a new registration.
56
			if (is_numeric($parameter1))
57
			{
58
				$changes['latestMember'] = $parameter1;
59
				$changes['latestRealName'] = $parameter2;
60
61
				updateSettings(array('totalMembers' => true), true);
62
			}
63
64
			// We need to calculate the totals.
65
			else
66
			{
67
				// Update the latest activated member (highest id_member) and count.
68
				$result = $smcFunc['db_query']('', '
69
				SELECT COUNT(*), MAX(id_member)
70
				FROM {db_prefix}members
71
				WHERE is_activated = {int:is_activated}',
72
					array(
73
						'is_activated' => 1,
74
					)
75
				);
76
				list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result);
77
				$smcFunc['db_free_result']($result);
78
79
				// Get the latest activated member's display name.
80
				$result = $smcFunc['db_query']('', '
81
				SELECT real_name
82
				FROM {db_prefix}members
83
				WHERE id_member = {int:id_member}
84
				LIMIT 1',
85
					array(
86
						'id_member' => (int) $changes['latestMember'],
87
					)
88
				);
89
				list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result);
90
				$smcFunc['db_free_result']($result);
91
92
				if (!empty($modSettings['registration_method']))
93
				{
94
					// Are we using registration approval?
95
					if ($modSettings['registration_method'] == 2 || !empty($modSettings['approveAccountDeletion']))
96
					{
97
						// Update the amount of members awaiting approval
98
						$result = $smcFunc['db_query']('', '
99
						SELECT COUNT(*)
100
						FROM {db_prefix}members
101
						WHERE is_activated IN ({array_int:activation_status})',
102
							array(
103
								'activation_status' => array(3, 4),
104
							)
105
						);
106
						list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result);
107
						$smcFunc['db_free_result']($result);
108
					}
109
110
					// What about unapproved COPPA registrations?
111 View Code Duplication
					if (!empty($modSettings['coppaType']) && $modSettings['coppaType'] != 1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
112
					{
113
						$result = $smcFunc['db_query']('', '
114
						SELECT COUNT(*)
115
						FROM {db_prefix}members
116
						WHERE is_activated = {int:coppa_approval}',
117
							array(
118
								'coppa_approval' => 5,
119
							)
120
						);
121
						list ($coppa_approvals) = $smcFunc['db_fetch_row']($result);
122
						$smcFunc['db_free_result']($result);
123
124
						// Add this to the number of unapproved members
125
						if (!empty($changes['unapprovedMembers']))
126
							$changes['unapprovedMembers'] += $coppa_approvals;
127
						else
128
							$changes['unapprovedMembers'] = $coppa_approvals;
129
					}
130
				}
131
			}
132
			updateSettings($changes);
133
			break;
134
135
		case 'message':
136
			if ($parameter1 === true && $parameter2 !== null)
137
				updateSettings(array('totalMessages' => true, 'maxMsgID' => $parameter2), true);
138
			else
139
			{
140
				// SUM and MAX on a smaller table is better for InnoDB tables.
141
				$result = $smcFunc['db_query']('', '
142
				SELECT SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id
143
				FROM {db_prefix}boards
144
				WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
145
					AND id_board != {int:recycle_board}' : ''),
146
					array(
147
						'recycle_board' => isset($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
148
						'blank_redirect' => '',
149
					)
150
				);
151
				$row = $smcFunc['db_fetch_assoc']($result);
152
				$smcFunc['db_free_result']($result);
153
154
				updateSettings(array(
155
					'totalMessages' => $row['total_messages'] === null ? 0 : $row['total_messages'],
156
					'maxMsgID' => $row['max_msg_id'] === null ? 0 : $row['max_msg_id']
157
				));
158
			}
159
			break;
160
161
		case 'subject':
162
			// Remove the previous subject (if any).
163
			$smcFunc['db_query']('', '
164
			DELETE FROM {db_prefix}log_search_subjects
165
			WHERE id_topic = {int:id_topic}',
166
				array(
167
					'id_topic' => (int) $parameter1,
168
				)
169
			);
170
171
			// Insert the new subject.
172
			if ($parameter2 !== null)
173
			{
174
				$parameter1 = (int) $parameter1;
175
				$parameter2 = text2words($parameter2);
176
177
				$inserts = array();
178
				foreach ($parameter2 as $word)
179
					$inserts[] = array($word, $parameter1);
180
181 View Code Duplication
				if (!empty($inserts))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
182
					$smcFunc['db_insert']('ignore',
183
						'{db_prefix}log_search_subjects',
184
						array('word' => 'string', 'id_topic' => 'int'),
185
						$inserts,
186
						array('word', 'id_topic')
187
					);
188
			}
189
			break;
190
191
		case 'topic':
192
			if ($parameter1 === true)
193
				updateSettings(array('totalTopics' => true), true);
194
			else
195
			{
196
				// Get the number of topics - a SUM is better for InnoDB tables.
197
				// We also ignore the recycle bin here because there will probably be a bunch of one-post topics there.
198
				$result = $smcFunc['db_query']('', '
199
				SELECT SUM(num_topics + unapproved_topics) AS total_topics
200
				FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
201
				WHERE id_board != {int:recycle_board}' : ''),
202
					array(
203
						'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
204
					)
205
				);
206
				$row = $smcFunc['db_fetch_assoc']($result);
207
				$smcFunc['db_free_result']($result);
208
209
				updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics']));
210
			}
211
			break;
212
213
		case 'postgroups':
214
			// Parameter two is the updated columns: we should check to see if we base groups off any of these.
215
			if ($parameter2 !== null && !in_array('posts', $parameter2))
216
				return;
217
218
			$postgroups = cache_get_data('updateStats:postgroups', 360);
219
			if ($postgroups == null || $parameter1 == null)
220
			{
221
				// Fetch the postgroups!
222
				$request = $smcFunc['db_query']('', '
223
				SELECT id_group, min_posts
224
				FROM {db_prefix}membergroups
225
				WHERE min_posts != {int:min_posts}',
226
					array(
227
						'min_posts' => -1,
228
					)
229
				);
230
				$postgroups = array();
231
				while ($row = $smcFunc['db_fetch_assoc']($request))
232
					$postgroups[$row['id_group']] = $row['min_posts'];
233
				$smcFunc['db_free_result']($request);
234
235
				// Sort them this way because if it's done with MySQL it causes a filesort :(.
236
				arsort($postgroups);
237
238
				cache_put_data('updateStats:postgroups', $postgroups, 360);
239
			}
240
241
			// Oh great, they've screwed their post groups.
242
			if (empty($postgroups))
243
				return;
244
245
			// Set all membergroups from most posts to least posts.
246
			$conditions = '';
247
			$lastMin = 0;
248
			foreach ($postgroups as $id => $min_posts)
249
			{
250
				$conditions .= '
251
					WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id;
252
				$lastMin = $min_posts;
253
			}
254
255
			// A big fat CASE WHEN... END is faster than a zillion UPDATE's ;).
256
			$smcFunc['db_query']('', '
257
			UPDATE {db_prefix}members
258
			SET id_post_group = CASE ' . $conditions . '
259
					ELSE 0
260
				END' . ($parameter1 != null ? '
261
			WHERE ' . (is_array($parameter1) ? 'id_member IN ({array_int:members})' : 'id_member = {int:members}') : ''),
262
				array(
263
					'members' => $parameter1,
264
				)
265
			);
266
			break;
267
268
		default:
269
			trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE);
270
	}
271
}
272
273
/**
274
 * Updates the columns in the members table.
275
 * Assumes the data has been htmlspecialchar'd.
276
 * this function should be used whenever member data needs to be
277
 * updated in place of an UPDATE query.
278
 *
279
 * id_member is either an int or an array of ints to be updated.
280
 *
281
 * data is an associative array of the columns to be updated and their respective values.
282
 * any string values updated should be quoted and slashed.
283
 *
284
 * the value of any column can be '+' or '-', which mean 'increment'
285
 * and decrement, respectively.
286
 *
287
 * if the member's post number is updated, updates their post groups.
288
 *
289
 * @param mixed $members An array of member IDs, null to update this for all members or the ID of a single member
290
 * @param array $data The info to update for the members
291
 */
292
function updateMemberData($members, $data)
293
{
294
	global $modSettings, $user_info, $smcFunc;
295
296
	$parameters = array();
297
	if (is_array($members))
298
	{
299
		$condition = 'id_member IN ({array_int:members})';
300
		$parameters['members'] = $members;
301
	}
302
	elseif ($members === null)
303
		$condition = '1=1';
304
	else
305
	{
306
		$condition = 'id_member = {int:member}';
307
		$parameters['member'] = $members;
308
	}
309
310
	// Everything is assumed to be a string unless it's in the below.
311
	$knownInts = array(
312
		'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages',
313
		'new_pm', 'pm_prefs', 'gender', 'show_online', 'pm_receive_from', 'alerts',
314
		'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning',
315
	);
316
	$knownFloats = array(
317
		'time_offset',
318
	);
319
320
	if (!empty($modSettings['integrate_change_member_data']))
321
	{
322
		// Only a few member variables are really interesting for integration.
323
		$integration_vars = array(
324
			'member_name',
325
			'real_name',
326
			'email_address',
327
			'id_group',
328
			'gender',
329
			'birthdate',
330
			'website_title',
331
			'website_url',
332
			'location',
333
			'time_format',
334
			'time_offset',
335
			'avatar',
336
			'lngfile',
337
		);
338
		$vars_to_integrate = array_intersect($integration_vars, array_keys($data));
339
340
		// Only proceed if there are any variables left to call the integration function.
341
		if (count($vars_to_integrate) != 0)
342
		{
343
			// Fetch a list of member_names if necessary
344
			if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members)))
345
				$member_names = array($user_info['username']);
346
			else
347
			{
348
				$member_names = array();
349
				$request = $smcFunc['db_query']('', '
350
					SELECT member_name
351
					FROM {db_prefix}members
352
					WHERE ' . $condition,
353
					$parameters
354
				);
355
				while ($row = $smcFunc['db_fetch_assoc']($request))
356
					$member_names[] = $row['member_name'];
357
				$smcFunc['db_free_result']($request);
358
			}
359
360
			if (!empty($member_names))
361
				foreach ($vars_to_integrate as $var)
362
					call_integration_hook('integrate_change_member_data', array($member_names, $var, &$data[$var], &$knownInts, &$knownFloats));
363
		}
364
	}
365
366
	$setString = '';
367
	foreach ($data as $var => $val)
368
	{
369
		$type = 'string';
370
		if (in_array($var, $knownInts))
371
			$type = 'int';
372
		elseif (in_array($var, $knownFloats))
373
			$type = 'float';
374
		elseif ($var == 'birthdate')
375
			$type = 'date';
376
		elseif ($var == 'member_ip')
377
			$type = 'inet';
378
		elseif ($var == 'member_ip2')
379
			$type = 'inet';
380
381
		// Doing an increment?
382
		if ($type == 'int' && ($val === '+' || $val === '-'))
383
		{
384
			$val = $var . ' ' . $val . ' 1';
385
			$type = 'raw';
386
		}
387
388
		// Ensure posts, instant_messages, and unread_messages don't overflow or underflow.
389
		if (in_array($var, array('posts', 'instant_messages', 'unread_messages')))
390
		{
391
			if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match))
392
			{
393
				if ($match[1] != '+ ')
394
					$val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END';
395
				$type = 'raw';
396
			}
397
		}
398
399
		$setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},';
400
		$parameters['p_' . $var] = $val;
401
	}
402
403
	$smcFunc['db_query']('', '
404
		UPDATE {db_prefix}members
405
		SET' . substr($setString, 0, -1) . '
406
		WHERE ' . $condition,
407
		$parameters
408
	);
409
410
	updateStats('postgroups', $members, array_keys($data));
411
412
	// Clear any caching?
413
	if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members))
414
	{
415
		if (!is_array($members))
416
			$members = array($members);
417
418
		foreach ($members as $member)
419
		{
420
			if ($modSettings['cache_enable'] >= 3)
421
			{
422
				cache_put_data('member_data-profile-' . $member, null, 120);
423
				cache_put_data('member_data-normal-' . $member, null, 120);
424
				cache_put_data('member_data-minimal-' . $member, null, 120);
425
			}
426
			cache_put_data('user_settings-' . $member, null, 60);
427
		}
428
	}
429
}
430
431
/**
432
 * Updates the settings table as well as $modSettings... only does one at a time if $update is true.
433
 *
434
 * - updates both the settings table and $modSettings array.
435
 * - all of changeArray's indexes and values are assumed to have escaped apostrophes (')!
436
 * - if a variable is already set to what you want to change it to, that
437
 *   variable will be skipped over; it would be unnecessary to reset.
438
 * - When use_update is true, UPDATEs will be used instead of REPLACE.
439
 * - when use_update is true, the value can be true or false to increment
440
 *  or decrement it, respectively.
441
 *
442
 * @param array $changeArray An array of info about what we're changing in 'setting' => 'value' format
443
 * @param bool $update Whether to use an UPDATE query instead of a REPLACE query
444
 */
445
function updateSettings($changeArray, $update = false)
446
{
447
	global $modSettings, $smcFunc;
448
449
	if (empty($changeArray) || !is_array($changeArray))
450
		return;
451
452
	$toRemove = array();
453
454
	// Go check if there is any setting to be removed.
455
	foreach ($changeArray as $k => $v)
456
		if ($v === null)
457
		{
458
			// Found some, remove them from the original array and add them to ours.
459
			unset($changeArray[$k]);
460
			$toRemove[] = $k;
461
		}
462
463
	// Proceed with the deletion.
464
	if (!empty($toRemove))
465
		$smcFunc['db_query']('', '
466
			DELETE FROM {db_prefix}settings
467
			WHERE variable IN ({array_string:remove})',
468
			array(
469
				'remove' => $toRemove,
470
			)
471
		);
472
473
	// In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs.
474
	if ($update)
475
	{
476
		foreach ($changeArray as $variable => $value)
477
		{
478
			$smcFunc['db_query']('', '
479
				UPDATE {db_prefix}settings
480
				SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value}
481
				WHERE variable = {string:variable}',
482
				array(
483
					'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value),
484
					'variable' => $variable,
485
				)
486
			);
487
			$modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value);
488
		}
489
490
		// Clean out the cache and make sure the cobwebs are gone too.
491
		cache_put_data('modSettings', null, 90);
492
493
		return;
494
	}
495
496
	$replaceArray = array();
497
	foreach ($changeArray as $variable => $value)
498
	{
499
		// Don't bother if it's already like that ;).
500
		if (isset($modSettings[$variable]) && $modSettings[$variable] == $value)
501
			continue;
502
		// If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it.
503
		elseif (!isset($modSettings[$variable]) && empty($value))
504
			continue;
505
506
		$replaceArray[] = array($variable, $value);
507
508
		$modSettings[$variable] = $value;
509
	}
510
511
	if (empty($replaceArray))
512
		return;
513
514
	$smcFunc['db_insert']('replace',
515
		'{db_prefix}settings',
516
		array('variable' => 'string-255', 'value' => 'string-65534'),
517
		$replaceArray,
518
		array('variable')
519
	);
520
521
	// Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
522
	cache_put_data('modSettings', null, 90);
523
}
524
525
/**
526
 * Constructs a page list.
527
 *
528
 * - builds the page list, e.g. 1 ... 6 7 [8] 9 10 ... 15.
529
 * - flexible_start causes it to use "url.page" instead of "url;start=page".
530
 * - very importantly, cleans up the start value passed, and forces it to
531
 *   be a multiple of num_per_page.
532
 * - checks that start is not more than max_value.
533
 * - base_url should be the URL without any start parameter on it.
534
 * - uses the compactTopicPagesEnable and compactTopicPagesContiguous
535
 *   settings to decide how to display the menu.
536
 *
537
 * an example is available near the function definition.
538
 * $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true);
539
 *
540
 * @param string $base_url The basic URL to be used for each link.
541
 * @param int &$start The start position, by reference. If this is not a multiple of the number of items per page, it is sanitized to be so and the value will persist upon the function's return.
542
 * @param int $max_value The total number of items you are paginating for.
543
 * @param int $num_per_page The number of items to be displayed on a given page. $start will be forced to be a multiple of this value.
544
 * @param bool $flexible_start Whether a ;start=x component should be introduced into the URL automatically (see above)
545
 * @param bool $show_prevnext Whether the Previous and Next links should be shown (should be on only when navigating the list)
546
 *
547
 * @return string The complete HTML of the page index that was requested, formatted by the template.
548
 */
549
function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false, $show_prevnext = true)
550
{
551
	global $modSettings, $context, $smcFunc, $settings, $txt;
552
553
	// Save whether $start was less than 0 or not.
554
	$start = (int) $start;
555
	$start_invalid = $start < 0;
556
557
	// Make sure $start is a proper variable - not less than 0.
558
	if ($start_invalid)
559
		$start = 0;
560
	// Not greater than the upper bound.
561
	elseif ($start >= $max_value)
562
		$start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page)));
563
	// And it has to be a multiple of $num_per_page!
564
	else
565
		$start = max(0, (int) $start - ((int) $start % (int) $num_per_page));
566
567
	$context['current_page'] = $start / $num_per_page;
568
569
	// Define some default page index settings if we don't already have it...
570
	if (!isset($settings['page_index']))
571
	{
572
		// This defines the formatting for the page indexes used throughout the forum.
573
		$settings['page_index'] = array(
574
			'extra_before' => '<span class="pages">' . $txt['pages'] . '</span>',
575
			'previous_page' => '<span class="generic_icons previous_page"></span>',
576
			'current_page' => '<span class="current_page">%1$d</span> ',
577
			'page' => '<a class="navPages" href="{URL}">%2$s</a> ',
578
			'expand_pages' => '<span class="expand_pages" onclick="expandPages(this, {LINK}, {FIRST_PAGE}, {LAST_PAGE}, {PER_PAGE});"> ... </span>',
579
			'next_page' => '<span class="generic_icons next_page"></span>',
580
			'extra_after' => '',
581
		);
582
	}
583
584
	$base_link = strtr($settings['page_index']['page'], array('{URL}' => $flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d'));
585
	$pageindex = $settings['page_index']['extra_before'];
586
587
	// Compact pages is off or on?
588
	if (empty($modSettings['compactTopicPagesEnable']))
589
	{
590
		// Show the left arrow.
591
		$pageindex .= $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']);
592
593
		// Show all the pages.
594
		$display_page = 1;
595
		for ($counter = 0; $counter < $max_value; $counter += $num_per_page)
596
			$pageindex .= $start == $counter && !$start_invalid ? sprintf($settings['page_index']['current_page'], $display_page++) : sprintf($base_link, $counter, $display_page++);
597
598
		// Show the right arrow.
599
		$display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page);
600
		if ($start != $counter - $max_value && !$start_invalid)
601
			$pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, $settings['page_index']['next_page']);
602
	}
603
	else
604
	{
605
		// If they didn't enter an odd value, pretend they did.
606
		$PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2;
607
608
		// Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page)
609
		if (!empty($start) && $show_prevnext)
610
			$pageindex .= sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']);
611
		else
612
			$pageindex .= '';
613
614
		// Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15)
615
		if ($start > $num_per_page * $PageContiguous)
616
			$pageindex .= sprintf($base_link, 0, '1');
617
618
		// Show the ... after the first page.  (prev page 1 >...< 6 7 [8] 9 10 ... 15 next page)
619
		if ($start > $num_per_page * ($PageContiguous + 1))
620
			$pageindex .= strtr($settings['page_index']['expand_pages'], array(
621
				'{LINK}' => JavaScriptEscape($smcFunc['htmlspecialchars']($base_link)),
622
				'{FIRST_PAGE}' => $num_per_page,
623
				'{LAST_PAGE}' => $start - $num_per_page * $PageContiguous,
624
				'{PER_PAGE}' => $num_per_page,
625
			));
626
627
		// Show the pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
628
		for ($nCont = $PageContiguous; $nCont >= 1; $nCont--)
629 View Code Duplication
			if ($start >= $num_per_page * $nCont)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
630
			{
631
				$tmpStart = $start - $num_per_page * $nCont;
632
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
633
			}
634
635
		// Show the current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
636
		if (!$start_invalid)
637
			$pageindex .= sprintf($settings['page_index']['current_page'], $start / $num_per_page + 1);
638
		else
639
			$pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1);
640
641
		// Show the pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
642
		$tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page;
643
		for ($nCont = 1; $nCont <= $PageContiguous; $nCont++)
644 View Code Duplication
			if ($start + $num_per_page * $nCont <= $tmpMaxPages)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
645
			{
646
				$tmpStart = $start + $num_per_page * $nCont;
647
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
648
			}
649
650
		// Show the '...' part near the end. (prev page 1 ... 6 7 [8] 9 10 >...< 15 next page)
651
		if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages)
652
			$pageindex .= strtr($settings['page_index']['expand_pages'], array(
653
				'{LINK}' => JavaScriptEscape($smcFunc['htmlspecialchars']($base_link)),
654
				'{FIRST_PAGE}' => $start + $num_per_page * ($PageContiguous + 1),
655
				'{LAST_PAGE}' => $tmpMaxPages,
656
				'{PER_PAGE}' => $num_per_page,
657
			));
658
659
		// Show the last number in the list. (prev page 1 ... 6 7 [8] 9 10 ... >15<  next page)
660
		if ($start + $num_per_page * $PageContiguous < $tmpMaxPages)
661
			$pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1);
662
663
		// Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
664
		if ($start != $tmpMaxPages && $show_prevnext)
665
			$pageindex .= sprintf($base_link, $start + $num_per_page, $settings['page_index']['next_page']);
666
	}
667
	$pageindex .= $settings['page_index']['extra_after'];
668
669
	return $pageindex;
670
}
671
672
/**
673
 * - Formats a number.
674
 * - uses the format of number_format to decide how to format the number.
675
 *   for example, it might display "1 234,50".
676
 * - caches the formatting data from the setting for optimization.
677
 *
678
 * @param float $number A number
679
 * @param bool|int $override_decimal_count If set, will use the specified number of decimal places. Otherwise it's automatically determined
680
 * @return string A formatted number
681
 */
682
function comma_format($number, $override_decimal_count = false)
683
{
684
	global $txt;
685
	static $thousands_separator = null, $decimal_separator = null, $decimal_count = null;
686
687
	// Cache these values...
688
	if ($decimal_separator === null)
689
	{
690
		// Not set for whatever reason?
691
		if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1)
692
			return $number;
693
694
		// Cache these each load...
695
		$thousands_separator = $matches[1];
696
		$decimal_separator = $matches[2];
697
		$decimal_count = strlen($matches[3]);
698
	}
699
700
	// Format the string with our friend, number_format.
701
	return number_format($number, (float) $number === $number ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator);
702
}
703
704
/**
705
 * Format a time to make it look purdy.
706
 *
707
 * - returns a pretty formatted version of time based on the user's format in $user_info['time_format'].
708
 * - applies all necessary time offsets to the timestamp, unless offset_type is set.
709
 * - if todayMod is set and show_today was not not specified or true, an
710
 *   alternate format string is used to show the date with something to show it is "today" or "yesterday".
711
 * - performs localization (more than just strftime would do alone.)
712
 *
713
 * @param int $log_time A timestamp
714
 * @param bool $show_today Whether to show "Today"/"Yesterday" or just a date
715
 * @param bool|string $offset_type If false, uses both user time offset and forum offset. If 'forum', uses only the forum offset. Otherwise no offset is applied.
716
 * @return string A formatted timestamp
717
 */
718
function timeformat($log_time, $show_today = true, $offset_type = false)
719
{
720
	global $context, $user_info, $txt, $modSettings;
721
	static $non_twelve_hour;
722
723
	// Offset the time.
724
	if (!$offset_type)
725
		$time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600;
726
	// Just the forum offset?
727
	elseif ($offset_type == 'forum')
728
		$time = $log_time + $modSettings['time_offset'] * 3600;
729
	else
730
		$time = $log_time;
731
732
	// We can't have a negative date (on Windows, at least.)
733
	if ($log_time < 0)
734
		$log_time = 0;
735
736
	// Today and Yesterday?
737
	if ($modSettings['todayMod'] >= 1 && $show_today === true)
738
	{
739
		// Get the current time.
740
		$nowtime = forum_time();
741
742
		$then = @getdate($time);
743
		$now = @getdate($nowtime);
744
745
		// Try to make something of a time format string...
746
		$s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S';
747
		if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false)
748
		{
749
			$h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l';
750
			$today_fmt = $h . ':%M' . $s . ' %p';
751
		}
752
		else
753
			$today_fmt = '%H:%M' . $s;
754
755
		// Same day of the year, same year.... Today!
756
		if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
757
			return $txt['today'] . timeformat($log_time, $today_fmt, $offset_type);
0 ignored issues
show
Documentation introduced by
$today_fmt 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...
758
759
		// Day-of-year is one less and same year, or it's the first of the year and that's the last of the year...
760
		if ($modSettings['todayMod'] == '2' && (($then['yday'] == $now['yday'] - 1 && $then['year'] == $now['year']) || ($now['yday'] == 0 && $then['year'] == $now['year'] - 1) && $then['mon'] == 12 && $then['mday'] == 31))
761
			return $txt['yesterday'] . timeformat($log_time, $today_fmt, $offset_type);
0 ignored issues
show
Documentation introduced by
$today_fmt 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...
762
	}
763
764
	$str = !is_bool($show_today) ? $show_today : $user_info['time_format'];
765
766
	if (setlocale(LC_TIME, $txt['lang_locale']))
767
	{
768
		if (!isset($non_twelve_hour))
769
			$non_twelve_hour = trim(strftime('%p')) === '';
770 View Code Duplication
		if ($non_twelve_hour && strpos($str, '%p') !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
771
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
772
773
		foreach (array('%a', '%A', '%b', '%B') as $token)
774
			if (strpos($str, $token) !== false)
775
				$str = str_replace($token, strftime($token, $time), $str);
776
	}
777
	else
778
	{
779
		// Do-it-yourself time localization.  Fun.
780
		foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label)
781
			if (strpos($str, $token) !== false)
782
				$str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str);
783
784 View Code Duplication
		if (strpos($str, '%p') !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
785
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
786
	}
787
788
	// Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that.
789
	if ($context['server']['is_windows'] && strpos($str, '%e') !== false)
790
		$str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str);
791
792
	// Format any other characters..
793
	return strftime($str, $time);
794
}
795
796
/**
797
 * Removes special entities from strings.  Compatibility...
798
 * Should be used instead of html_entity_decode for PHP version compatibility reasons.
799
 *
800
 * - removes the base entities (&lt;, &quot;, etc.) from text.
801
 * - additionally converts &nbsp; and &#039;.
802
 *
803
 * @param string $string A string
804
 * @return string The string without entities
805
 */
806
function un_htmlspecialchars($string)
0 ignored issues
show
Best Practice introduced by
The function un_htmlspecialchars() has been defined more than once; this definition is ignored, only the first definition in other/upgrade.php (L149-152) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
807
{
808
	global $context;
809
	static $translation = array();
810
811
	// Determine the character set... Default to UTF-8
812
	if (empty($context['character_set']))
813
		$charset = 'UTF-8';
814
	// Use ISO-8859-1 in place of non-suppported ISO-8859 charsets...
815
	elseif (strpos($context['character_set'], 'ISO-8859-') !== false && !in_array($context['character_set'], array('ISO-8859-5', 'ISO-8859-15')))
816
		$charset = 'ISO-8859-1';
817
	else
818
		$charset = $context['character_set'];
819
820
	if (empty($translation))
821
		$translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES, $charset)) + array('&#039;' => '\'', '&#39;' => '\'', '&nbsp;' => ' ');
822
823
	return strtr($string, $translation);
824
}
825
826
/**
827
 * Shorten a subject + internationalization concerns.
828
 *
829
 * - shortens a subject so that it is either shorter than length, or that length plus an ellipsis.
830
 * - respects internationalization characters and entities as one character.
831
 * - avoids trailing entities.
832
 * - returns the shortened string.
833
 *
834
 * @param string $subject The subject
835
 * @param int $len How many characters to limit it to
836
 * @return string The shortened subject - either the entire subject (if it's <= $len) or the subject shortened to $len characters with "..." appended
837
 */
838
function shorten_subject($subject, $len)
839
{
840
	global $smcFunc;
841
842
	// It was already short enough!
843
	if ($smcFunc['strlen']($subject) <= $len)
844
		return $subject;
845
846
	// Shorten it by the length it was too long, and strip off junk from the end.
847
	return $smcFunc['substr']($subject, 0, $len) . '...';
848
}
849
850
/**
851
 * Gets the current time with offset.
852
 *
853
 * - always applies the offset in the time_offset setting.
854
 *
855
 * @param bool $use_user_offset Whether to apply the user's offset as well
856
 * @param int $timestamp A timestamp (null to use current time)
857
 * @return int Seconds since the unix epoch, with forum time offset and (optionally) user time offset applied
858
 */
859
function forum_time($use_user_offset = true, $timestamp = null)
860
{
861
	global $user_info, $modSettings;
862
863
	if ($timestamp === null)
864
		$timestamp = time();
865
	elseif ($timestamp == 0)
866
		return 0;
867
868
	return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600;
869
}
870
871
/**
872
 * Calculates all the possible permutations (orders) of array.
873
 * should not be called on huge arrays (bigger than like 10 elements.)
874
 * returns an array containing each permutation.
875
 *
876
 * @deprecated since 2.1
877
 * @param array $array An array
878
 * @return array An array containing each permutation
879
 */
880
function permute($array)
881
{
882
	$orders = array($array);
883
884
	$n = count($array);
885
	$p = range(0, $n);
886
	for ($i = 1; $i < $n; null)
887
	{
888
		$p[$i]--;
889
		$j = $i % 2 != 0 ? $p[$i] : 0;
890
891
		$temp = $array[$i];
892
		$array[$i] = $array[$j];
893
		$array[$j] = $temp;
894
895
		for ($i = 1; $p[$i] == 0; $i++)
896
			$p[$i] = 1;
897
898
		$orders[] = $array;
899
	}
900
901
	return $orders;
902
}
903
904
/**
905
 * Parse bulletin board code in a string, as well as smileys optionally.
906
 *
907
 * - only parses bbc tags which are not disabled in disabledBBC.
908
 * - handles basic HTML, if enablePostHTML is on.
909
 * - caches the from/to replace regular expressions so as not to reload them every time a string is parsed.
910
 * - only parses smileys if smileys is true.
911
 * - does nothing if the enableBBC setting is off.
912
 * - uses the cache_id as a unique identifier to facilitate any caching it may do.
913
 *  -returns the modified message.
914
 *
915
 * @param string $message The message
916
 * @param bool $smileys Whether to parse smileys as well
917
 * @param string $cache_id The cache ID
918
 * @param array $parse_tags If set, only parses these tags rather than all of them
919
 * @return string The parsed message
920
 */
921
function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array())
922
{
923
	global $txt, $scripturl, $context, $modSettings, $user_info, $sourcedir;
924
	static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
925
	static $disabled;
926
927
	// Don't waste cycles
928
	if ($message === '')
929
		return '';
930
931
	// Just in case it wasn't determined yet whether UTF-8 is enabled.
932
	if (!isset($context['utf8']))
933
		$context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
934
935
	// Clean up any cut/paste issues we may have
936
	$message = sanitizeMSCutPaste($message);
937
938
	// If the load average is too high, don't parse the BBC.
939
	if (!empty($context['load_average']) && !empty($modSettings['bbc']) && $context['load_average'] >= $modSettings['bbc'])
940
	{
941
		$context['disabled_parse_bbc'] = true;
942
		return $message;
943
	}
944
945
	if ($smileys !== null && ($smileys == '1' || $smileys == '0'))
946
		$smileys = (bool) $smileys;
947
948
	if (empty($modSettings['enableBBC']) && $message !== false)
949
	{
950
		if ($smileys === true)
951
			parsesmileys($message);
952
953
		return $message;
954
	}
955
956
	// If we are not doing every tag then we don't cache this run.
957
	if (!empty($parse_tags) && !empty($bbc_codes))
958
	{
959
		$temp_bbc = $bbc_codes;
960
		$bbc_codes = array();
961
	}
962
963
	// Allow mods access before entering the main parse_bbc loop
964
	call_integration_hook('integrate_pre_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
965
966
	// Sift out the bbc for a performance improvement.
967
	if (empty($bbc_codes) || $message === false || !empty($parse_tags))
968
	{
969
		if (!empty($modSettings['disabledBBC']))
970
		{
971
			$disabled = array();
972
973
			$temp = explode(',', strtolower($modSettings['disabledBBC']));
974
975
			foreach ($temp as $tag)
976
				$disabled[trim($tag)] = true;
977
		}
978
979
		if (empty($modSettings['enableEmbeddedFlash']))
980
			$disabled['flash'] = true;
981
982
		/* The following bbc are formatted as an array, with keys as follows:
983
984
			tag: the tag's name - should be lowercase!
985
986
			type: one of...
987
				- (missing): [tag]parsed content[/tag]
988
				- unparsed_equals: [tag=xyz]parsed content[/tag]
989
				- parsed_equals: [tag=parsed data]parsed content[/tag]
990
				- unparsed_content: [tag]unparsed content[/tag]
991
				- closed: [tag], [tag/], [tag /]
992
				- unparsed_commas: [tag=1,2,3]parsed content[/tag]
993
				- unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
994
				- unparsed_equals_content: [tag=...]unparsed content[/tag]
995
996
			parameters: an optional array of parameters, for the form
997
			  [tag abc=123]content[/tag].  The array is an associative array
998
			  where the keys are the parameter names, and the values are an
999
			  array which may contain the following:
1000
				- match: a regular expression to validate and match the value.
1001
				- quoted: true if the value should be quoted.
1002
				- validate: callback to evaluate on the data, which is $data.
1003
				- value: a string in which to replace $1 with the data.
1004
				  either it or validate may be used, not both.
1005
				- optional: true if the parameter is optional.
1006
1007
			test: a regular expression to test immediately after the tag's
1008
			  '=', ' ' or ']'.  Typically, should have a \] at the end.
1009
			  Optional.
1010
1011
			content: only available for unparsed_content, closed,
1012
			  unparsed_commas_content, and unparsed_equals_content.
1013
			  $1 is replaced with the content of the tag.  Parameters
1014
			  are replaced in the form {param}.  For unparsed_commas_content,
1015
			  $2, $3, ..., $n are replaced.
1016
1017
			before: only when content is not used, to go before any
1018
			  content.  For unparsed_equals, $1 is replaced with the value.
1019
			  For unparsed_commas, $1, $2, ..., $n are replaced.
1020
1021
			after: similar to before in every way, except that it is used
1022
			  when the tag is closed.
1023
1024
			disabled_content: used in place of content when the tag is
1025
			  disabled.  For closed, default is '', otherwise it is '$1' if
1026
			  block_level is false, '<div>$1</div>' elsewise.
1027
1028
			disabled_before: used in place of before when disabled.  Defaults
1029
			  to '<div>' if block_level, '' if not.
1030
1031
			disabled_after: used in place of after when disabled.  Defaults
1032
			  to '</div>' if block_level, '' if not.
1033
1034
			block_level: set to true the tag is a "block level" tag, similar
1035
			  to HTML.  Block level tags cannot be nested inside tags that are
1036
			  not block level, and will not be implicitly closed as easily.
1037
			  One break following a block level tag may also be removed.
1038
1039
			trim: if set, and 'inside' whitespace after the begin tag will be
1040
			  removed.  If set to 'outside', whitespace after the end tag will
1041
			  meet the same fate.
1042
1043
			validate: except when type is missing or 'closed', a callback to
1044
			  validate the data as $data.  Depending on the tag's type, $data
1045
			  may be a string or an array of strings (corresponding to the
1046
			  replacement.)
1047
1048
			quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
1049
			  may be not set, 'optional', or 'required' corresponding to if
1050
			  the content may be quoted.  This allows the parser to read
1051
			  [tag="abc]def[esdf]"] properly.
1052
1053
			require_parents: an array of tag names, or not set.  If set, the
1054
			  enclosing tag *must* be one of the listed tags, or parsing won't
1055
			  occur.
1056
1057
			require_children: similar to require_parents, if set children
1058
			  won't be parsed if they are not in the list.
1059
1060
			disallow_children: similar to, but very different from,
1061
			  require_children, if it is set the listed tags will not be
1062
			  parsed inside the tag.
1063
1064
			parsed_tags_allowed: an array restricting what BBC can be in the
1065
			  parsed_equals parameter, if desired.
1066
		*/
1067
1068
		$codes = array(
1069
			array(
1070
				'tag' => 'abbr',
1071
				'type' => 'unparsed_equals',
1072
				'before' => '<abbr title="$1">',
1073
				'after' => '</abbr>',
1074
				'quoted' => 'optional',
1075
				'disabled_after' => ' ($1)',
1076
			),
1077
			array(
1078
				'tag' => 'anchor',
1079
				'type' => 'unparsed_equals',
1080
				'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]',
1081
				'before' => '<span id="post_$1">',
1082
				'after' => '</span>',
1083
			),
1084
			array(
1085
				'tag' => 'attach',
1086
				'type' => 'unparsed_content',
1087
				'parameters' => array(
1088
					'name' => array('optional' => true),
1089
				),
1090
				'content' => '$1',
1091
				'validate' => function (&$tag, &$data, $disabled) use ($modSettings, $context, $sourcedir, $txt)
1092
				{
1093
					$returnContext = '';
1094
1095
					// BBC or the entire attachments feature is disabled
1096
					if (empty($modSettings['attachmentEnable']) || !empty($disabled['attach']))
1097
						return $data;
1098
1099
					// Save the attach ID.
1100
					$attachID = $data;
1101
1102
					// Kinda need this.
1103
					require_once($sourcedir . '/Subs-Attachments.php');
1104
1105
					$currentAttachment = parseAttachBBC($attachID);
1106
1107
					// parseAttachBBC will return a string ($txt key) rather than diying with a fatal_error. Up to you to decide what to do.
1108
					if (is_string($currentAttachment))
1109
						return $data = !empty($txt[$currentAttachment]) ? $txt[$currentAttachment] : $currentAttachment;
1110
1111
					if (!empty($currentAttachment['is_image']))
1112
					{
1113
						if ($currentAttachment['thumbnail']['has_thumb'])
1114
							$returnContext .= '
1115
													<a href="'. $currentAttachment['href']. ';image" id="link_'. $currentAttachment['id']. '" onclick="'. $currentAttachment['thumbnail']['javascript']. '"><img src="'. $currentAttachment['thumbnail']['href']. '" alt="" id="thumb_'. $currentAttachment['id']. '"></a>';
1116
						else
1117
							$returnContext .= '
1118
													<img src="' . $currentAttachment['href'] . ';image" alt="" width="' . $currentAttachment['width'] . '" height="' . $currentAttachment['height'] . '"/>';
1119
					}
1120
1121
					// No image. Show a link.
1122
					else
1123
						$returnContext .= $currentAttachment['link'];
1124
1125
					// Gotta append what we just did.
1126
					$data = $returnContext;
1127
				},
1128
			),
1129
			array(
1130
				'tag' => 'b',
1131
				'before' => '<b>',
1132
				'after' => '</b>',
1133
			),
1134
			array(
1135
				'tag' => 'center',
1136
				'before' => '<div class="centertext">',
1137
				'after' => '</div>',
1138
				'block_level' => true,
1139
			),
1140
			array(
1141
				'tag' => 'code',
1142
				'type' => 'unparsed_content',
1143
				'content' => '<div class="codeheader"><span class="code floatleft">' . $txt['code'] . '</span> <a class="codeoperation smf_select_text">' . $txt['code_select'] . '</a></div><code class="bbc_code">$1</code>',
1144
				// @todo Maybe this can be simplified?
1145
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1146
				{
1147
					if (!isset($disabled['code']))
1148
					{
1149
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
1150
1151 View Code Duplication
						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1152
						{
1153
							// Do PHP code coloring?
1154
							if ($php_parts[$php_i] != '&lt;?php')
1155
								continue;
1156
1157
							$php_string = '';
1158
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1159
							{
1160
								$php_string .= $php_parts[$php_i];
1161
								$php_parts[$php_i++] = '';
1162
							}
1163
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1164
						}
1165
1166
						// Fix the PHP code stuff...
1167
						$data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1168
						$data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);
1169
1170
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1171
						if ($context['browser']['is_opera'])
1172
							$data .= '&nbsp;';
1173
					}
1174
				},
1175
				'block_level' => true,
1176
			),
1177
			array(
1178
				'tag' => 'code',
1179
				'type' => 'unparsed_equals_content',
1180
				'content' => '<div class="codeheader"><span class="code floatleft">' . $txt['code'] . '</span> ($2) <a class="codeoperation smf_select_text">' . $txt['code_select'] . '</a></div><code class="bbc_code">$1</code>',
1181
				// @todo Maybe this can be simplified?
1182
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1183
				{
1184
					if (!isset($disabled['code']))
1185
					{
1186
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);
1187
1188 View Code Duplication
						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1189
						{
1190
							// Do PHP code coloring?
1191
							if ($php_parts[$php_i] != '&lt;?php')
1192
								continue;
1193
1194
							$php_string = '';
1195
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1196
							{
1197
								$php_string .= $php_parts[$php_i];
1198
								$php_parts[$php_i++] = '';
1199
							}
1200
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1201
						}
1202
1203
						// Fix the PHP code stuff...
1204
						$data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1205
						$data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]);
1206
1207
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1208
						if ($context['browser']['is_opera'])
1209
							$data[0] .= '&nbsp;';
1210
					}
1211
				},
1212
				'block_level' => true,
1213
			),
1214
			array(
1215
				'tag' => 'color',
1216
				'type' => 'unparsed_equals',
1217
				'test' => '(#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\s?,\s?){2}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\))\]',
1218
				'before' => '<span style="color: $1;" class="bbc_color">',
1219
				'after' => '</span>',
1220
			),
1221
			array(
1222
				'tag' => 'email',
1223
				'type' => 'unparsed_content',
1224
				'content' => '<a href="mailto:$1" class="bbc_email">$1</a>',
1225
				// @todo Should this respect guest_hideContacts?
1226
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1227
				{
1228
					$data = strtr($data, array('<br>' => ''));
1229
				},
1230
			),
1231
			array(
1232
				'tag' => 'email',
1233
				'type' => 'unparsed_equals',
1234
				'before' => '<a href="mailto:$1" class="bbc_email">',
1235
				'after' => '</a>',
1236
				// @todo Should this respect guest_hideContacts?
1237
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1238
				'disabled_after' => ' ($1)',
1239
			),
1240
			array(
1241
				'tag' => 'flash',
1242
				'type' => 'unparsed_commas_content',
1243
				'test' => '\d+,\d+\]',
1244
				'content' => '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never">',
1245
				'validate' => function (&$tag, &$data, $disabled)
1246
				{
1247
					if (isset($disabled['url']))
1248
						$tag['content'] = '$1';
1249
					elseif (strpos($data[0], 'http://') !== 0 && strpos($data[0], 'https://') !== 0)
1250
						$data[0] = 'http://' . $data[0];
1251
				},
1252
				'disabled_content' => '<a href="$1" target="_blank" class="new_win">$1</a>',
1253
			),
1254
			array(
1255
				'tag' => 'font',
1256
				'type' => 'unparsed_equals',
1257
				'test' => '[A-Za-z0-9_,\-\s]+?\]',
1258
				'before' => '<span style="font-family: $1;" class="bbc_font">',
1259
				'after' => '</span>',
1260
			),
1261
			array(
1262
				'tag' => 'html',
1263
				'type' => 'unparsed_content',
1264
				'content' => '$1',
1265
				'block_level' => true,
1266
				'disabled_content' => '$1',
1267
			),
1268
			array(
1269
				'tag' => 'hr',
1270
				'type' => 'closed',
1271
				'content' => '<hr>',
1272
				'block_level' => true,
1273
			),
1274
			array(
1275
				'tag' => 'i',
1276
				'before' => '<i>',
1277
				'after' => '</i>',
1278
			),
1279
			array(
1280
				'tag' => 'img',
1281
				'type' => 'unparsed_content',
1282
				'parameters' => array(
1283
					'alt' => array('optional' => true),
1284
					'title' => array('optional' => true),
1285
					'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
1286
					'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
1287
				),
1288
				'content' => '<img src="$1" alt="{alt}" title="{title}"{width}{height} class="bbc_img resized">',
1289 View Code Duplication
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1290
				{
1291
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1292
1293
					$data = strtr($data, array('<br>' => ''));
1294
					if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1295
						$data = 'http://' . $data;
1296
1297
					if (substr($data, 0, 8) != 'https://' && $image_proxy_enabled)
1298
						$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1299
				},
1300
				'disabled_content' => '($1)',
1301
			),
1302
			array(
1303
				'tag' => 'img',
1304
				'type' => 'unparsed_content',
1305
				'content' => '<img src="$1" alt="" class="bbc_img">',
1306 View Code Duplication
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1307
				{
1308
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1309
1310
					$data = strtr($data, array('<br>' => ''));
1311
					if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1312
						$data = 'http://' . $data;
1313
1314
					if (substr($data, 0, 8) != 'https://' && $image_proxy_enabled)
1315
						$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1316
				},
1317
				'disabled_content' => '($1)',
1318
			),
1319
			array(
1320
				'tag' => 'iurl',
1321
				'type' => 'unparsed_content',
1322
				'content' => '<a href="$1" class="bbc_link">$1</a>',
1323 View Code Duplication
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1324
				{
1325
					$data = strtr($data, array('<br>' => ''));
1326
					if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1327
						$data = 'http://' . $data;
1328
				},
1329
			),
1330
			array(
1331
				'tag' => 'iurl',
1332
				'type' => 'unparsed_equals',
1333
				'before' => '<a href="$1" class="bbc_link">',
1334
				'after' => '</a>',
1335
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1336
				{
1337
					if (substr($data, 0, 1) == '#')
1338
						$data = '#post_' . substr($data, 1);
1339
					elseif (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1340
						$data = 'http://' . $data;
1341
				},
1342
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1343
				'disabled_after' => ' ($1)',
1344
			),
1345
			array(
1346
				'tag' => 'left',
1347
				'before' => '<div style="text-align: left;">',
1348
				'after' => '</div>',
1349
				'block_level' => true,
1350
			),
1351
			array(
1352
				'tag' => 'li',
1353
				'before' => '<li>',
1354
				'after' => '</li>',
1355
				'trim' => 'outside',
1356
				'require_parents' => array('list'),
1357
				'block_level' => true,
1358
				'disabled_before' => '',
1359
				'disabled_after' => '<br>',
1360
			),
1361
			array(
1362
				'tag' => 'list',
1363
				'before' => '<ul class="bbc_list">',
1364
				'after' => '</ul>',
1365
				'trim' => 'inside',
1366
				'require_children' => array('li', 'list'),
1367
				'block_level' => true,
1368
			),
1369
			array(
1370
				'tag' => 'list',
1371
				'parameters' => array(
1372
					'type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)'),
1373
				),
1374
				'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
1375
				'after' => '</ul>',
1376
				'trim' => 'inside',
1377
				'require_children' => array('li'),
1378
				'block_level' => true,
1379
			),
1380
			array(
1381
				'tag' => 'ltr',
1382
				'before' => '<bdo dir="ltr">',
1383
				'after' => '</bdo>',
1384
				'block_level' => true,
1385
			),
1386
			array(
1387
				'tag' => 'me',
1388
				'type' => 'unparsed_equals',
1389
				'before' => '<div class="meaction">* $1 ',
1390
				'after' => '</div>',
1391
				'quoted' => 'optional',
1392
				'block_level' => true,
1393
				'disabled_before' => '/me ',
1394
				'disabled_after' => '<br>',
1395
			),
1396
			array(
1397
				'tag' => 'member',
1398
				'type' => 'unparsed_equals',
1399
				'before' => '<a href="' . $scripturl . '?action=profile;u=$1" class="mention">@',
1400
				'after' => '</a>',
1401
			),
1402
			array(
1403
				'tag' => 'nobbc',
1404
				'type' => 'unparsed_content',
1405
				'content' => '$1',
1406
			),
1407
			array(
1408
				'tag' => 'php',
1409
				'type' => 'unparsed_content',
1410
				'content' => '<span class="phpcode">$1</span>',
1411
				'validate' => isset($disabled['php']) ? null : function (&$tag, &$data, $disabled)
1412
				{
1413
					if (!isset($disabled['php']))
1414
					{
1415
						$add_begin = substr(trim($data), 0, 5) != '&lt;?';
1416
						$data = highlight_php_code($add_begin ? '&lt;?php ' . $data . '?&gt;' : $data);
1417
						if ($add_begin)
1418
							$data = preg_replace(array('~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~', '~\?&gt;((?:</(font|span)>)*)$~'), '$1', $data, 2);
1419
					}
1420
				},
1421
				'block_level' => false,
1422
				'disabled_content' => '$1',
1423
			),
1424
			array(
1425
				'tag' => 'pre',
1426
				'before' => '<pre>',
1427
				'after' => '</pre>',
1428
			),
1429
			array(
1430
				'tag' => 'quote',
1431
				'before' => '<blockquote><cite>' . $txt['quote'] . '</cite>',
1432
				'after' => '</blockquote>',
1433
				'trim' => 'both',
1434
				'block_level' => true,
1435
			),
1436
			array(
1437
				'tag' => 'quote',
1438
				'parameters' => array(
1439
					'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
1440
				),
1441
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1442
				'after' => '</blockquote>',
1443
				'trim' => 'both',
1444
				'block_level' => true,
1445
			),
1446
			array(
1447
				'tag' => 'quote',
1448
				'type' => 'parsed_equals',
1449
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': $1</cite>',
1450
				'after' => '</blockquote>',
1451
				'trim' => 'both',
1452
				'quoted' => 'optional',
1453
				// Don't allow everything to be embedded with the author name.
1454
				'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
1455
				'block_level' => true,
1456
			),
1457
			array(
1458
				'tag' => 'quote',
1459
				'parameters' => array(
1460
					'author' => array('match' => '([^<>]{1,192}?)'),
1461
					'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|msg=\d+?|action=profile;u=\d+)'),
1462
					'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
1463
				),
1464
				'before' => '<blockquote><cite><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></cite>',
1465
				'after' => '</blockquote>',
1466
				'trim' => 'both',
1467
				'block_level' => true,
1468
			),
1469
			array(
1470
				'tag' => 'quote',
1471
				'parameters' => array(
1472
					'author' => array('match' => '(.{1,192}?)'),
1473
				),
1474
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1475
				'after' => '</blockquote>',
1476
				'trim' => 'both',
1477
				'block_level' => true,
1478
			),
1479
			array(
1480
				'tag' => 'right',
1481
				'before' => '<div style="text-align: right;">',
1482
				'after' => '</div>',
1483
				'block_level' => true,
1484
			),
1485
			array(
1486
				'tag' => 'rtl',
1487
				'before' => '<bdo dir="rtl">',
1488
				'after' => '</bdo>',
1489
				'block_level' => true,
1490
			),
1491
			array(
1492
				'tag' => 's',
1493
				'before' => '<s>',
1494
				'after' => '</s>',
1495
			),
1496
			array(
1497
				'tag' => 'size',
1498
				'type' => 'unparsed_equals',
1499
				'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]',
1500
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1501
				'after' => '</span>',
1502
			),
1503
			array(
1504
				'tag' => 'size',
1505
				'type' => 'unparsed_equals',
1506
				'test' => '[1-7]\]',
1507
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1508
				'after' => '</span>',
1509
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1510
				{
1511
					$sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
1512
					$data = $sizes[$data] . 'em';
1513
				},
1514
			),
1515
			array(
1516
				'tag' => 'sub',
1517
				'before' => '<sub>',
1518
				'after' => '</sub>',
1519
			),
1520
			array(
1521
				'tag' => 'sup',
1522
				'before' => '<sup>',
1523
				'after' => '</sup>',
1524
			),
1525
			array(
1526
				'tag' => 'table',
1527
				'before' => '<table class="bbc_table">',
1528
				'after' => '</table>',
1529
				'trim' => 'inside',
1530
				'require_children' => array('tr'),
1531
				'block_level' => true,
1532
			),
1533
			array(
1534
				'tag' => 'td',
1535
				'before' => '<td>',
1536
				'after' => '</td>',
1537
				'require_parents' => array('tr'),
1538
				'trim' => 'outside',
1539
				'block_level' => true,
1540
				'disabled_before' => '',
1541
				'disabled_after' => '',
1542
			),
1543
			array(
1544
				'tag' => 'time',
1545
				'type' => 'unparsed_content',
1546
				'content' => '$1',
1547
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1548
				{
1549
					if (is_numeric($data))
1550
						$data = timeformat($data);
1551
					else
1552
						$tag['content'] = '[time]$1[/time]';
1553
				},
1554
			),
1555
			array(
1556
				'tag' => 'tr',
1557
				'before' => '<tr>',
1558
				'after' => '</tr>',
1559
				'require_parents' => array('table'),
1560
				'require_children' => array('td'),
1561
				'trim' => 'both',
1562
				'block_level' => true,
1563
				'disabled_before' => '',
1564
				'disabled_after' => '',
1565
			),
1566
			array(
1567
				'tag' => 'u',
1568
				'before' => '<u>',
1569
				'after' => '</u>',
1570
			),
1571
			array(
1572
				'tag' => 'url',
1573
				'type' => 'unparsed_content',
1574
				'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>',
1575 View Code Duplication
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1576
				{
1577
					$data = strtr($data, array('<br>' => ''));
1578
					if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1579
						$data = 'http://' . $data;
1580
				},
1581
			),
1582
			array(
1583
				'tag' => 'url',
1584
				'type' => 'unparsed_equals',
1585
				'before' => '<a href="$1" class="bbc_link" target="_blank">',
1586
				'after' => '</a>',
1587
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1588
				{
1589
					if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1590
						$data = 'http://' . $data;
1591
				},
1592
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1593
				'disabled_after' => ' ($1)',
1594
			),
1595
		);
1596
1597
		// Inside these tags autolink is not recommendable.
1598
		$no_autolink_tags = array(
1599
			'url',
1600
			'iurl',
1601
			'email',
1602
		);
1603
1604
		// Let mods add new BBC without hassle.
1605
		call_integration_hook('integrate_bbc_codes', array(&$codes, &$no_autolink_tags));
1606
1607
		// This is mainly for the bbc manager, so it's easy to add tags above.  Custom BBC should be added above this line.
1608
		if ($message === false)
1609
		{
1610
			if (isset($temp_bbc))
1611
				$bbc_codes = $temp_bbc;
1612
			usort($codes, 'sort_bbc_tags');
1613
			return $codes;
1614
		}
1615
1616
		// So the parser won't skip them.
1617
		$itemcodes = array(
1618
			'*' => 'disc',
1619
			'@' => 'disc',
1620
			'+' => 'square',
1621
			'x' => 'square',
1622
			'#' => 'square',
1623
			'o' => 'circle',
1624
			'O' => 'circle',
1625
			'0' => 'circle',
1626
		);
1627
		if (!isset($disabled['li']) && !isset($disabled['list']))
1628
		{
1629
			foreach ($itemcodes as $c => $dummy)
1630
				$bbc_codes[$c] = array();
1631
		}
1632
1633
		// Shhhh!
1634
		if (!isset($disabled['color']))
1635
		{
1636
			$codes[] = array(
1637
				'tag' => 'chrissy',
1638
				'before' => '<span style="color: #cc0099;">',
1639
				'after' => ' :-*</span>',
1640
			);
1641
			$codes[] = array(
1642
				'tag' => 'kissy',
1643
				'before' => '<span style="color: #cc0099;">',
1644
				'after' => ' :-*</span>',
1645
			);
1646
		}
1647
1648
		foreach ($codes as $code)
1649
		{
1650
			// Make it easier to process parameters later
1651
			if (!empty($code['parameters']))
1652
				ksort($code['parameters'], SORT_STRING);
1653
1654
			// If we are not doing every tag only do ones we are interested in.
1655
			if (empty($parse_tags) || in_array($code['tag'], $parse_tags))
1656
				$bbc_codes[substr($code['tag'], 0, 1)][] = $code;
1657
		}
1658
		$codes = null;
0 ignored issues
show
Unused Code introduced by
$codes is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1659
	}
1660
1661
	// Shall we take the time to cache this?
1662
	if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && isset($message[1000])) || isset($message[2400])) && empty($parse_tags))
1663
	{
1664
		// It's likely this will change if the message is modified.
1665
		$cache_key = 'parse:' . $cache_id . '-' . md5(md5($message) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . json_encode($context['browser']) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']);
1666
1667
		if (($temp = cache_get_data($cache_key, 240)) != null)
1668
			return $temp;
1669
1670
		$cache_t = microtime();
1671
	}
1672
1673
	if ($smileys === 'print')
1674
	{
1675
		// [glow], [shadow], and [move] can't really be printed.
1676
		$disabled['glow'] = true;
1677
		$disabled['shadow'] = true;
1678
		$disabled['move'] = true;
1679
1680
		// Colors can't well be displayed... supposed to be black and white.
1681
		$disabled['color'] = true;
1682
		$disabled['black'] = true;
1683
		$disabled['blue'] = true;
1684
		$disabled['white'] = true;
1685
		$disabled['red'] = true;
1686
		$disabled['green'] = true;
1687
		$disabled['me'] = true;
1688
1689
		// Color coding doesn't make sense.
1690
		$disabled['php'] = true;
1691
1692
		// Links are useless on paper... just show the link.
1693
		$disabled['ftp'] = true;
1694
		$disabled['url'] = true;
1695
		$disabled['iurl'] = true;
1696
		$disabled['email'] = true;
1697
		$disabled['flash'] = true;
1698
1699
		// @todo Change maybe?
1700
		if (!isset($_GET['images']))
1701
			$disabled['img'] = true;
1702
1703
		// @todo Interface/setting to add more?
1704
	}
1705
1706
	$open_tags = array();
1707
	$message = strtr($message, array("\n" => '<br>'));
1708
1709
	foreach ($bbc_codes as $section) {
1710
		foreach ($section as $code) {
1711
			$alltags[] = $code['tag'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$alltags was never initialized. Although not strictly required by PHP, it is generally a good practice to add $alltags = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1712
		}
1713
	}
1714
	$alltags_regex = '\b' . implode("\b|\b", array_unique($alltags)) . '\b';
0 ignored issues
show
Bug introduced by
The variable $alltags 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...
1715
1716
	// The non-breaking-space looks a bit different each time.
1717
	$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0';
1718
1719
	$pos = -1;
1720
	while ($pos !== false)
1721
	{
1722
		$last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
1723
		$pos = strpos($message, '[', $pos + 1);
1724
1725
		// Failsafe.
1726
		if ($pos === false || $last_pos > $pos)
1727
			$pos = strlen($message) + 1;
1728
1729
		// Can't have a one letter smiley, URL, or email! (sorry.)
1730
		if ($last_pos < $pos - 1)
1731
		{
1732
			// Make sure the $last_pos is not negative.
1733
			$last_pos = max($last_pos, 0);
1734
1735
			// Pick a block of data to do some raw fixing on.
1736
			$data = substr($message, $last_pos, $pos - $last_pos);
1737
1738
			// Take care of some HTML!
1739
			if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false)
1740
			{
1741
				$data = preg_replace('~&lt;a\s+href=((?:&quot;)?)((?:https?://|ftps?://|mailto:)\S+?)\\1&gt;~i', '[url=$2]', $data);
1742
				$data = preg_replace('~&lt;/a&gt;~i', '[/url]', $data);
1743
1744
				// <br> should be empty.
1745
				$empty_tags = array('br', 'hr');
1746
				foreach ($empty_tags as $tag)
1747
					$data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '[' . $tag . ' /]', $data);
1748
1749
				// b, u, i, s, pre... basic tags.
1750
				$closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote');
1751
				foreach ($closable_tags as $tag)
1752
				{
1753
					$diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
1754
					$data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
1755
1756
					if ($diff > 0)
1757
						$data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
1758
				}
1759
1760
				// Do <img ...> - with security... action= -> action-.
1761
				preg_match_all('~&lt;img\s+src=((?:&quot;)?)((?:https?://|ftps?://)\S+?)\\1(?:\s+alt=(&quot;.*?&quot;|\S*?))?(?:\s?/)?&gt;~i', $data, $matches, PREG_PATTERN_ORDER);
1762
				if (!empty($matches[0]))
1763
				{
1764
					$replaces = array();
1765
					foreach ($matches[2] as $match => $imgtag)
1766
					{
1767
						$alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
1768
1769
						// Remove action= from the URL - no funny business, now.
1770
						if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0)
1771
							$imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
1772
1773
						// Check if the image is larger than allowed.
1774
						if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height']))
1775
						{
1776
							list ($width, $height) = url_image_size($imgtag);
1777
1778 View Code Duplication
							if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1779
							{
1780
								$height = (int) (($modSettings['max_image_width'] * $height) / $width);
1781
								$width = $modSettings['max_image_width'];
1782
							}
1783
1784 View Code Duplication
							if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1785
							{
1786
								$width = (int) (($modSettings['max_image_height'] * $width) / $height);
1787
								$height = $modSettings['max_image_height'];
1788
							}
1789
1790
							// Set the new image tag.
1791
							$replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
1792
						}
1793
						else
1794
							$replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
1795
					}
1796
1797
					$data = strtr($data, $replaces);
1798
				}
1799
			}
1800
1801
			if (!empty($modSettings['autoLinkUrls']))
1802
			{
1803
				// Are we inside tags that should be auto linked?
1804
				$no_autolink_area = false;
1805
				if (!empty($open_tags))
1806
				{
1807
					foreach ($open_tags as $open_tag)
1808
						if (in_array($open_tag['tag'], $no_autolink_tags))
1809
							$no_autolink_area = true;
1810
				}
1811
1812
				// Don't go backwards.
1813
				// @todo Don't think is the real solution....
1814
				$lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
1815
				if ($pos < $lastAutoPos)
1816
					$no_autolink_area = true;
1817
				$lastAutoPos = $pos;
1818
1819
				if (!$no_autolink_area)
1820
				{
1821
					// Parse any URLs.... have to get rid of the @ problems some things cause... stupid email addresses.
1822
					if (!isset($disabled['url']) && (strpos($data, '://') !== false || strpos($data, 'www.') !== false) && strpos($data, '[url') === false)
1823
					{
1824
						// Switch out quotes really quick because they can cause problems.
1825
						$data = strtr($data, array('&#039;' => '\'', '&nbsp;' => $context['utf8'] ? "\xC2\xA0" : "\xA0", '&quot;' => '>">', '"' => '<"<', '&lt;' => '<lt<'));
1826
1827
						// Only do this if the preg survives.
1828
						if (is_string($result = preg_replace(array(
1829
							'~(?<=[\s>\.(;\'"]|^)((?:http|https)://[\w\-_%@:|]+(?:\.[\w\-_%]+)*(?::\d+)?(?:/[\w\-_\~%\.@!,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\]?)~i',
1830
							'~(?<=[\s>\.(;\'"]|^)((?:ftp|ftps)://[\w\-_%@:|]+(?:\.[\w\-_%]+)*(?::\d+)?(?:/[\w\-_\~%\.@,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\]?)~i',
1831
							'~(?<=[\s>(\'<]|^)(www(?:\.[\w\-_]+)+(?::\d+)?(?:/[\w\-_\~%\.@!,\?&;=#(){}+:\'\\\\]*)*[/\w\-_\~%@\?;=#}\\\\])~i'
1832
						), array(
1833
							'[url]$1[/url]',
1834
							'[ftp]$1[/ftp]',
1835
							'[url=http://$1]$1[/url]'
1836
						), $data)))
1837
							$data = $result;
1838
1839
						$data = strtr($data, array('\'' => '&#039;', $context['utf8'] ? "\xC2\xA0" : "\xA0" => '&nbsp;', '>">' => '&quot;', '<"<' => '"', '<lt<' => '&lt;'));
1840
					}
1841
1842
					// Next, emails...
1843
					if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false)
1844
					{
1845
						$data = preg_replace('~(?<=[\?\s' . $non_breaking_space . '\[\]()*\\\;>]|^)([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?,\s' . $non_breaking_space . '\[\]()*\\\]|$|<br>|&nbsp;|&gt;|&lt;|&quot;|&#039;|\.(?:\.|;|&nbsp;|\s|$|<br>))~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data);
1846
						$data = preg_replace('~(?<=<br>)([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?\.,;\s' . $non_breaking_space . '\[\]()*\\\]|$|<br>|&nbsp;|&gt;|&lt;|&quot;|&#039;)~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data);
1847
					}
1848
				}
1849
			}
1850
1851
			$data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
1852
1853
			// If it wasn't changed, no copying or other boring stuff has to happen!
1854
			if ($data != substr($message, $last_pos, $pos - $last_pos))
1855
			{
1856
				$message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
1857
1858
				// Since we changed it, look again in case we added or removed a tag.  But we don't want to skip any.
1859
				$old_pos = strlen($data) + $last_pos;
1860
				$pos = strpos($message, '[', $last_pos);
1861
				$pos = $pos === false ? $old_pos : min($pos, $old_pos);
1862
			}
1863
		}
1864
1865
		// Are we there yet?  Are we there yet?
1866
		if ($pos >= strlen($message) - 1)
1867
			break;
1868
1869
		$tags = strtolower($message[$pos + 1]);
1870
1871
		if ($tags == '/' && !empty($open_tags))
1872
		{
1873
			$pos2 = strpos($message, ']', $pos + 1);
1874
			if ($pos2 == $pos + 2)
1875
				continue;
1876
1877
			$look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
1878
1879
			$to_close = array();
1880
			$block_level = null;
1881
1882
			do
1883
			{
1884
				$tag = array_pop($open_tags);
1885
				if (!$tag)
1886
					break;
1887
1888
				if (!empty($tag['block_level']))
1889
				{
1890
					// Only find out if we need to.
1891
					if ($block_level === false)
1892
					{
1893
						array_push($open_tags, $tag);
1894
						break;
1895
					}
1896
1897
					// The idea is, if we are LOOKING for a block level tag, we can close them on the way.
1898 View Code Duplication
					if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1899
					{
1900
						foreach ($bbc_codes[$look_for[0]] as $temp)
1901
							if ($temp['tag'] == $look_for)
1902
							{
1903
								$block_level = !empty($temp['block_level']);
1904
								break;
1905
							}
1906
					}
1907
1908
					if ($block_level !== true)
1909
					{
1910
						$block_level = false;
1911
						array_push($open_tags, $tag);
1912
						break;
1913
					}
1914
				}
1915
1916
				$to_close[] = $tag;
1917
			}
1918
			while ($tag['tag'] != $look_for);
1919
1920
			// Did we just eat through everything and not find it?
1921
			if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)))
1922
			{
1923
				$open_tags = $to_close;
1924
				continue;
1925
			}
1926
			elseif (!empty($to_close) && $tag['tag'] != $look_for)
1927
			{
1928 View Code Duplication
				if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1929
				{
1930
					foreach ($bbc_codes[$look_for[0]] as $temp)
1931
						if ($temp['tag'] == $look_for)
1932
						{
1933
							$block_level = !empty($temp['block_level']);
1934
							break;
1935
						}
1936
				}
1937
1938
				// We're not looking for a block level tag (or maybe even a tag that exists...)
1939
				if (!$block_level)
0 ignored issues
show
Bug Best Practice introduced by
The expression $block_level of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
1940
				{
1941
					foreach ($to_close as $tag)
1942
						array_push($open_tags, $tag);
1943
					continue;
1944
				}
1945
			}
1946
1947
			foreach ($to_close as $tag)
1948
			{
1949
				$message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
1950
				$pos += strlen($tag['after']) + 2;
1951
				$pos2 = $pos - 1;
1952
1953
				// See the comment at the end of the big loop - just eating whitespace ;).
1954 View Code Duplication
				if (!empty($tag['block_level']) && substr($message, $pos, 4) == '<br>')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1955
					$message = substr($message, 0, $pos) . substr($message, $pos + 4);
1956 View Code Duplication
				if (!empty($tag['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos), $matches) != 0)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1957
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
1958
			}
1959
1960
			if (!empty($to_close))
1961
			{
1962
				$to_close = array();
0 ignored issues
show
Unused Code introduced by
$to_close is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1963
				$pos--;
1964
			}
1965
1966
			continue;
1967
		}
1968
1969
		// No tags for this character, so just keep going (fastest possible course.)
1970
		if (!isset($bbc_codes[$tags]))
1971
			continue;
1972
1973
		$inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
1974
		$tag = null;
1975
		foreach ($bbc_codes[$tags] as $possible)
1976
		{
1977
			$pt_strlen = strlen($possible['tag']);
1978
1979
			// Not a match?
1980
			if (strtolower(substr($message, $pos + 1, $pt_strlen)) != $possible['tag'])
1981
				continue;
1982
1983
			$next_c = $message[$pos + 1 + $pt_strlen];
1984
1985
			// A test validation?
1986
			if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + $pt_strlen + 1)) === 0)
1987
				continue;
1988
			// Do we want parameters?
1989
			elseif (!empty($possible['parameters']))
1990
			{
1991
				if ($next_c != ' ')
1992
					continue;
1993
			}
1994
			elseif (isset($possible['type']))
1995
			{
1996
				// Do we need an equal sign?
1997
				if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=')
1998
					continue;
1999
				// Maybe we just want a /...
2000
				if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + $pt_strlen, 2) != '/]' && substr($message, $pos + 1 + $pt_strlen, 3) != ' /]')
2001
					continue;
2002
				// An immediate ]?
2003
				if ($possible['type'] == 'unparsed_content' && $next_c != ']')
2004
					continue;
2005
			}
2006
			// No type means 'parsed_content', which demands an immediate ] without parameters!
2007
			elseif ($next_c != ']')
2008
				continue;
2009
2010
			// Check allowed tree?
2011
			if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents'])))
2012
				continue;
2013
			elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children']))
2014
				continue;
2015
			// If this is in the list of disallowed child tags, don't parse it.
2016
			elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children']))
2017
				continue;
2018
2019
			$pos1 = $pos + 1 + $pt_strlen + 1;
2020
2021
			// Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
2022
			if ($possible['tag'] == 'quote')
2023
			{
2024
				// Start with standard
2025
				$quote_alt = false;
2026
				foreach ($open_tags as $open_quote)
2027
				{
2028
					// Every parent quote this quote has flips the styling
2029
					if ($open_quote['tag'] == 'quote')
2030
						$quote_alt = !$quote_alt;
2031
				}
2032
				// Add a class to the quote to style alternating blockquotes
2033
				$possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
2034
			}
2035
2036
			// This is long, but it makes things much easier and cleaner.
2037
			if (!empty($possible['parameters']))
2038
			{
2039
				// Build a regular expression for each parameter for the current tag.
2040
				$preg = array();
2041
				foreach ($possible['parameters'] as $p => $info)
2042
					$preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . '\s*)' . (empty($info['optional']) ? '' : '?');
2043
2044
				// Extract the string that potentially holds our parameters.
2045
				$blob = preg_split('~\[/?(?:' . $alltags_regex . ')~i', substr($message, $pos));
2046
				$blobs = preg_split('~\]~i', $blob[1]);
2047
2048
				$splitters = implode('=|', array_keys($possible['parameters'])) . '=';
2049
2050
				// Progressively append more blobs until we find our parameters or run out of blobs
2051
				$blob_counter = 1;
2052
				while ($blob_counter <= count($blobs))
2053
				{
2054
2055
					$given_param_string = implode(']', array_slice($blobs, 0, $blob_counter++));
2056
2057
					$given_params = preg_split('~\s(?=(' . $splitters . '))~i', $given_param_string);
2058
					sort($given_params, SORT_STRING);
2059
2060
					$match = preg_match('~^' . implode('', $preg) . '$~i', implode(' ', $given_params), $matches) !== 0;
2061
2062
					if ($match)
2063
						$blob_counter = count($blobs) + 1;
2064
				}
2065
2066
				// Didn't match our parameter list, try the next possible.
2067
				if (!$match)
0 ignored issues
show
Bug introduced by
The variable $match 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...
2068
					continue;
2069
2070
				$params = array();
2071
				for ($i = 1, $n = count($matches); $i < $n; $i += 2)
0 ignored issues
show
Bug introduced by
The variable $matches 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...
2072
				{
2073
					$key = strtok(ltrim($matches[$i]), '=');
2074
					if (isset($possible['parameters'][$key]['value']))
2075
						$params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
2076
					elseif (isset($possible['parameters'][$key]['validate']))
2077
						$params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
2078
					else
2079
						$params['{' . $key . '}'] = $matches[$i + 1];
2080
2081
					// Just to make sure: replace any $ or { so they can't interpolate wrongly.
2082
					$params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
2083
				}
2084
2085
				foreach ($possible['parameters'] as $p => $info)
2086
				{
2087
					if (!isset($params['{' . $p . '}']))
2088
						$params['{' . $p . '}'] = '';
2089
				}
2090
2091
				$tag = $possible;
2092
2093
				// Put the parameters into the string.
2094
				if (isset($tag['before']))
2095
					$tag['before'] = strtr($tag['before'], $params);
2096
				if (isset($tag['after']))
2097
					$tag['after'] = strtr($tag['after'], $params);
2098
				if (isset($tag['content']))
2099
					$tag['content'] = strtr($tag['content'], $params);
2100
2101
				$pos1 += strlen($given_param_string);
0 ignored issues
show
Bug introduced by
The variable $given_param_string 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...
2102
			}
2103
			else
2104
				$tag = $possible;
2105
			break;
2106
		}
2107
2108
		// Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
2109
		if ($smileys !== false && $tag === null && isset($itemcodes[$message[$pos + 1]]) && $message[$pos + 2] == ']' && !isset($disabled['list']) && !isset($disabled['li']))
2110
		{
2111
			if ($message[$pos + 1] == '0' && !in_array($message[$pos - 1], array(';', ' ', "\t", "\n", '>')))
2112
				continue;
2113
2114
			$tag = $itemcodes[$message[$pos + 1]];
2115
2116
			// First let's set up the tree: it needs to be in a list, or after an li.
2117
			if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li'))
2118
			{
2119
				$open_tags[] = array(
2120
					'tag' => 'list',
2121
					'after' => '</ul>',
2122
					'block_level' => true,
2123
					'require_children' => array('li'),
2124
					'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2125
				);
2126
				$code = '<ul class="bbc_list">';
2127
			}
2128
			// We're in a list item already: another itemcode?  Close it first.
2129
			elseif ($inside['tag'] == 'li')
2130
			{
2131
				array_pop($open_tags);
2132
				$code = '</li>';
2133
			}
2134
			else
2135
				$code = '';
2136
2137
			// Now we open a new tag.
2138
			$open_tags[] = array(
2139
				'tag' => 'li',
2140
				'after' => '</li>',
2141
				'trim' => 'outside',
2142
				'block_level' => true,
2143
				'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2144
			);
2145
2146
			// First, open the tag...
2147
			$code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>';
2148
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
2149
			$pos += strlen($code) - 1 + 2;
2150
2151
			// Next, find the next break (if any.)  If there's more itemcode after it, keep it going - otherwise close!
2152
			$pos2 = strpos($message, '<br>', $pos);
2153
			$pos3 = strpos($message, '[/', $pos);
2154
			if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false))
2155
			{
2156
				preg_match('~^(<br>|&nbsp;|\s|\[)+~', substr($message, $pos2 + 4), $matches);
2157
				$message = substr($message, 0, $pos2) . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . substr($message, $pos2);
2158
2159
				$open_tags[count($open_tags) - 2]['after'] = '</ul>';
2160
			}
2161
			// Tell the [list] that it needs to close specially.
2162
			else
2163
			{
2164
				// Move the li over, because we're not sure what we'll hit.
2165
				$open_tags[count($open_tags) - 1]['after'] = '';
2166
				$open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
2167
			}
2168
2169
			continue;
2170
		}
2171
2172
		// Implicitly close lists and tables if something other than what's required is in them.  This is needed for itemcode.
2173
		if ($tag === null && $inside !== null && !empty($inside['require_children']))
2174
		{
2175
			array_pop($open_tags);
2176
2177
			$message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
2178
			$pos += strlen($inside['after']) - 1 + 2;
2179
		}
2180
2181
		// No tag?  Keep looking, then.  Silly people using brackets without actual tags.
2182
		if ($tag === null)
2183
			continue;
2184
2185
		// Propagate the list to the child (so wrapping the disallowed tag won't work either.)
2186
		if (isset($inside['disallow_children']))
2187
			$tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
2188
2189
		// Is this tag disabled?
2190
		if (isset($disabled[$tag['tag']]))
2191
		{
2192
			if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content']))
2193
			{
2194
				$tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
2195
				$tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
2196
				$tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
2197
			}
2198
			elseif (isset($tag['disabled_before']) || isset($tag['disabled_after']))
2199
			{
2200
				$tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
2201
				$tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
2202
			}
2203
			else
2204
				$tag['content'] = $tag['disabled_content'];
2205
		}
2206
2207
		// we use this a lot
2208
		$tag_strlen = strlen($tag['tag']);
2209
2210
		// The only special case is 'html', which doesn't need to close things.
2211
		if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level']))
2212
		{
2213
			$n = count($open_tags) - 1;
2214
			while (empty($open_tags[$n]['block_level']) && $n >= 0)
2215
				$n--;
2216
2217
			// Close all the non block level tags so this tag isn't surrounded by them.
2218
			for ($i = count($open_tags) - 1; $i > $n; $i--)
2219
			{
2220
				$message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
2221
				$ot_strlen = strlen($open_tags[$i]['after']);
2222
				$pos += $ot_strlen + 2;
2223
				$pos1 += $ot_strlen + 2;
0 ignored issues
show
Bug introduced by
The variable $pos1 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...
2224
2225
				// Trim or eat trailing stuff... see comment at the end of the big loop.
2226 View Code Duplication
				if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 4) == '<br>')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2227
					$message = substr($message, 0, $pos) . substr($message, $pos + 4);
2228 View Code Duplication
				if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos), $matches) != 0)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2229
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2230
2231
				array_pop($open_tags);
2232
			}
2233
		}
2234
2235
		// No type means 'parsed_content'.
2236
		if (!isset($tag['type']))
2237
		{
2238
			// @todo Check for end tag first, so people can say "I like that [i] tag"?
2239
			$open_tags[] = $tag;
2240
			$message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
2241
			$pos += strlen($tag['before']) - 1 + 2;
2242
		}
2243
		// Don't parse the content, just skip it.
2244
		elseif ($tag['type'] == 'unparsed_content')
2245
		{
2246
			$pos2 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos1);
2247
			if ($pos2 === false)
2248
				continue;
2249
2250
			$data = substr($message, $pos1, $pos2 - $pos1);
2251
2252
			if (!empty($tag['block_level']) && substr($data, 0, 4) == '<br>')
2253
				$data = substr($data, 4);
2254
2255
			if (isset($tag['validate']))
2256
				$tag['validate']($tag, $data, $disabled);
2257
2258
			$code = strtr($tag['content'], array('$1' => $data));
2259
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + $tag_strlen);
2260
2261
			$pos += strlen($code) - 1 + 2;
2262
			$last_pos = $pos + 1;
2263
2264
		}
2265
		// Don't parse the content, just skip it.
2266
		elseif ($tag['type'] == 'unparsed_equals_content')
2267
		{
2268
			// The value may be quoted for some tags - check.
2269 View Code Duplication
			if (isset($tag['quoted']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2270
			{
2271
				$quoted = substr($message, $pos1, 6) == '&quot;';
2272
				if ($tag['quoted'] != 'optional' && !$quoted)
2273
					continue;
2274
2275
				if ($quoted)
2276
					$pos1 += 6;
2277
			}
2278
			else
2279
				$quoted = false;
2280
2281
			$pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2282
			if ($pos2 === false)
2283
				continue;
2284
2285
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2286
			if ($pos3 === false)
2287
				continue;
2288
2289
			$data = array(
2290
				substr($message, $pos2 + ($quoted == false ? 1 : 7), $pos3 - ($pos2 + ($quoted == false ? 1 : 7))),
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2291
				substr($message, $pos1, $pos2 - $pos1)
2292
			);
2293
2294
			if (!empty($tag['block_level']) && substr($data[0], 0, 4) == '<br>')
2295
				$data[0] = substr($data[0], 4);
2296
2297
			// Validation for my parking, please!
2298
			if (isset($tag['validate']))
2299
				$tag['validate']($tag, $data, $disabled);
2300
2301
			$code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
2302
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2303
			$pos += strlen($code) - 1 + 2;
2304
		}
2305
		// A closed tag, with no content or value.
2306
		elseif ($tag['type'] == 'closed')
2307
		{
2308
			$pos2 = strpos($message, ']', $pos);
2309
			$message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
2310
			$pos += strlen($tag['content']) - 1 + 2;
2311
		}
2312
		// This one is sorta ugly... :/.  Unfortunately, it's needed for flash.
2313
		elseif ($tag['type'] == 'unparsed_commas_content')
2314
		{
2315
			$pos2 = strpos($message, ']', $pos1);
2316
			if ($pos2 === false)
2317
				continue;
2318
2319
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2320
			if ($pos3 === false)
2321
				continue;
2322
2323
			// We want $1 to be the content, and the rest to be csv.
2324
			$data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
2325
			$data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
2326
2327
			if (isset($tag['validate']))
2328
				$tag['validate']($tag, $data, $disabled);
2329
2330
			$code = $tag['content'];
2331 View Code Duplication
			foreach ($data as $k => $d)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2332
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2333
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2334
			$pos += strlen($code) - 1 + 2;
2335
		}
2336
		// This has parsed content, and a csv value which is unparsed.
2337
		elseif ($tag['type'] == 'unparsed_commas')
2338
		{
2339
			$pos2 = strpos($message, ']', $pos1);
2340
			if ($pos2 === false)
2341
				continue;
2342
2343
			$data = explode(',', substr($message, $pos1, $pos2 - $pos1));
2344
2345
			if (isset($tag['validate']))
2346
				$tag['validate']($tag, $data, $disabled);
2347
2348
			// Fix after, for disabled code mainly.
2349 View Code Duplication
			foreach ($data as $k => $d)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2350
				$tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
2351
2352
			$open_tags[] = $tag;
2353
2354
			// Replace them out, $1, $2, $3, $4, etc.
2355
			$code = $tag['before'];
2356 View Code Duplication
			foreach ($data as $k => $d)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2357
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2358
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
2359
			$pos += strlen($code) - 1 + 2;
2360
		}
2361
		// A tag set to a value, parsed or not.
2362
		elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals')
2363
		{
2364
			// The value may be quoted for some tags - check.
2365 View Code Duplication
			if (isset($tag['quoted']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2366
			{
2367
				$quoted = substr($message, $pos1, 6) == '&quot;';
2368
				if ($tag['quoted'] != 'optional' && !$quoted)
2369
					continue;
2370
2371
				if ($quoted)
2372
					$pos1 += 6;
2373
			}
2374
			else
2375
				$quoted = false;
2376
2377
			$pos2 = strpos($message, $quoted == false ? ']' : '&quot;]', $pos1);
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2378
			if ($pos2 === false)
2379
				continue;
2380
2381
			$data = substr($message, $pos1, $pos2 - $pos1);
2382
2383
			// Validation for my parking, please!
2384
			if (isset($tag['validate']))
2385
				$tag['validate']($tag, $data, $disabled);
2386
2387
			// For parsed content, we must recurse to avoid security problems.
2388
			if ($tag['type'] != 'unparsed_equals')
2389
				$data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
2390
2391
			$tag['after'] = strtr($tag['after'], array('$1' => $data));
2392
2393
			$open_tags[] = $tag;
2394
2395
			$code = strtr($tag['before'], array('$1' => $data));
2396
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + ($quoted == false ? 1 : 7));
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2397
			$pos += strlen($code) - 1 + 2;
2398
		}
2399
2400
		// If this is block level, eat any breaks after it.
2401 View Code Duplication
		if (!empty($tag['block_level']) && substr($message, $pos + 1, 4) == '<br>')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2402
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 5);
2403
2404
		// Are we trimming outside this tag?
2405
		if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos + 1), $matches) != 0)
2406
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
2407
	}
2408
2409
	// Close any remaining tags.
2410
	while ($tag = array_pop($open_tags))
2411
		$message .= "\n" . $tag['after'] . "\n";
2412
2413
	// Parse the smileys within the parts where it can be done safely.
2414
	if ($smileys === true)
2415
	{
2416
		$message_parts = explode("\n", $message);
2417
		for ($i = 0, $n = count($message_parts); $i < $n; $i += 2)
2418
			parsesmileys($message_parts[$i]);
2419
2420
		$message = implode('', $message_parts);
2421
	}
2422
2423
	// No smileys, just get rid of the markers.
2424
	else
2425
		$message = strtr($message, array("\n" => ''));
2426
2427
	if ($message !== '' && $message[0] === ' ')
2428
		$message = '&nbsp;' . substr($message, 1);
2429
2430
	// Cleanup whitespace.
2431
	$message = strtr($message, array('  ' => ' &nbsp;', "\r" => '', "\n" => '<br>', '<br> ' => '<br>&nbsp;', '&#13;' => "\n"));
2432
2433
	// Allow mods access to what parse_bbc created
2434
	call_integration_hook('integrate_post_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
2435
2436
	// Cache the output if it took some time...
2437
	if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05)
2438
		cache_put_data($cache_key, $message, 240);
2439
2440
	// If this was a force parse revert if needed.
2441
	if (!empty($parse_tags))
2442
	{
2443
		if (empty($temp_bbc))
2444
			$bbc_codes = array();
2445
		else
2446
		{
2447
			$bbc_codes = $temp_bbc;
2448
			unset($temp_bbc);
2449
		}
2450
	}
2451
2452
	return $message;
2453
}
2454
2455
/**
2456
 * Helper function for usort(), used in parse_bbc().
2457
 * @param array $a An array containing a tag
2458
 * @param array $b Another array containing a tag
2459
 * @return int A number indicating whether $a is bigger than $b
2460
 */
2461
function sort_bbc_tags($a, $b)
2462
{
2463
	return strcmp($a['tag'], $b['tag']);
2464
}
2465
2466
/**
2467
 * Parse smileys in the passed message.
2468
 *
2469
 * The smiley parsing function which makes pretty faces appear :).
2470
 * If custom smiley sets are turned off by smiley_enable, the default set of smileys will be used.
2471
 * These are specifically not parsed in code tags [url=mailto:[email protected]]
2472
 * Caches the smileys from the database or array in memory.
2473
 * Doesn't return anything, but rather modifies message directly.
2474
 *
2475
 * @param string &$message The message to parse smileys in
2476
 */
2477
function parsesmileys(&$message)
2478
{
2479
	global $modSettings, $txt, $user_info, $context, $smcFunc;
2480
	static $smileyPregSearch = null, $smileyPregReplacements = array();
2481
2482
	// No smiley set at all?!
2483
	if ($user_info['smiley_set'] == 'none' || trim($message) == '')
2484
		return;
2485
2486
	// If smileyPregSearch hasn't been set, do it now.
2487
	if (empty($smileyPregSearch))
2488
	{
2489
		// Use the default smileys if it is disabled. (better for "portability" of smileys.)
2490
		if (empty($modSettings['smiley_enable']))
2491
		{
2492
			$smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)');
2493
			$smileysto = array('evil.gif', 'cheesy.gif', 'rolleyes.gif', 'angry.gif', 'laugh.gif', 'smiley.gif', 'wink.gif', 'grin.gif', 'sad.gif', 'shocked.gif', 'cool.gif', 'tongue.gif', 'huh.gif', 'embarrassed.gif', 'lipsrsealed.gif', 'kiss.gif', 'cry.gif', 'undecided.gif', 'azn.gif', 'afro.gif', 'police.gif', 'angel.gif');
2494
			$smileysdescs = array('', $txt['icon_cheesy'], $txt['icon_rolleyes'], $txt['icon_angry'], '', $txt['icon_smiley'], $txt['icon_wink'], $txt['icon_grin'], $txt['icon_sad'], $txt['icon_shocked'], $txt['icon_cool'], $txt['icon_tongue'], $txt['icon_huh'], $txt['icon_embarrassed'], $txt['icon_lips'], $txt['icon_kiss'], $txt['icon_cry'], $txt['icon_undecided'], '', '', '', '');
2495
		}
2496
		else
2497
		{
2498
			// Load the smileys in reverse order by length so they don't get parsed wrong.
2499
			if (($temp = cache_get_data('parsing_smileys', 480)) == null)
2500
			{
2501
				$result = $smcFunc['db_query']('', '
2502
					SELECT code, filename, description
2503
					FROM {db_prefix}smileys
2504
					ORDER BY LENGTH(code) DESC',
2505
					array(
2506
					)
2507
				);
2508
				$smileysfrom = array();
2509
				$smileysto = array();
2510
				$smileysdescs = array();
2511
				while ($row = $smcFunc['db_fetch_assoc']($result))
2512
				{
2513
					$smileysfrom[] = $row['code'];
2514
					$smileysto[] = $smcFunc['htmlspecialchars']($row['filename']);
2515
					$smileysdescs[] = $row['description'];
2516
				}
2517
				$smcFunc['db_free_result']($result);
2518
2519
				cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480);
2520
			}
2521
			else
2522
				list ($smileysfrom, $smileysto, $smileysdescs) = $temp;
2523
		}
2524
2525
		// The non-breaking-space is a complex thing...
2526
		$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0';
2527
2528
		// This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:[email protected]] doesn't parse the :D smiley)
2529
		$smileyPregReplacements = array();
2530
		$searchParts = array();
2531
		$smileys_path = $smcFunc['htmlspecialchars']($modSettings['smileys_url'] . '/' . $user_info['smiley_set'] . '/');
2532
2533
		for ($i = 0, $n = count($smileysfrom); $i < $n; $i++)
2534
		{
2535
			$specialChars = $smcFunc['htmlspecialchars']($smileysfrom[$i], ENT_QUOTES);
2536
			$smileyCode = '<img src="' . $smileys_path . $smileysto[$i] . '" alt="' . strtr($specialChars, array(':' => '&#58;', '(' => '&#40;', ')' => '&#41;', '$' => '&#36;', '[' => '&#091;')). '" title="' . strtr($smcFunc['htmlspecialchars']($smileysdescs[$i]), array(':' => '&#58;', '(' => '&#40;', ')' => '&#41;', '$' => '&#36;', '[' => '&#091;')) . '" class="smiley">';
2537
2538
			$smileyPregReplacements[$smileysfrom[$i]] = $smileyCode;
2539
2540
			$searchParts[] = preg_quote($smileysfrom[$i], '~');
2541
			if ($smileysfrom[$i] != $specialChars)
2542
			{
2543
				$smileyPregReplacements[$specialChars] = $smileyCode;
2544
				$searchParts[] = preg_quote($specialChars, '~');
2545
			}
2546
		}
2547
2548
		$smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|(?<![a-zA-Z0-9])\(|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~' . ($context['utf8'] ? 'u' : '');
2549
	}
2550
2551
	// Replace away!
2552
	$message = preg_replace_callback($smileyPregSearch,
2553
		function ($matches) use ($smileyPregReplacements)
2554
		{
2555
			return $smileyPregReplacements[$matches[1]];
2556
		}, $message);
2557
}
2558
2559
/**
2560
 * Highlight any code.
2561
 *
2562
 * Uses PHP's highlight_string() to highlight PHP syntax
2563
 * does special handling to keep the tabs in the code available.
2564
 * used to parse PHP code from inside [code] and [php] tags.
2565
 *
2566
 * @param string $code The code
2567
 * @return string The code with highlighted HTML.
2568
 */
2569
function highlight_php_code($code)
2570
{
2571
	// Remove special characters.
2572
	$code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", '<br>' => "\n", "\t" => 'SMF_TAB();', '&#91;' => '[')));
2573
2574
	$oldlevel = error_reporting(0);
2575
2576
	$buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true));
2577
2578
	error_reporting($oldlevel);
2579
2580
	// Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P.
2581
	$buffer = preg_replace('~SMF_TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer);
2582
2583
	return strtr($buffer, array('\'' => '&#039;', '<code>' => '', '</code>' => ''));
2584
}
2585
2586
/**
2587
 * Make sure the browser doesn't come back and repost the form data.
2588
 * Should be used whenever anything is posted.
2589
 *
2590
 * @param string $setLocation The URL to redirect them to
2591
 * @param bool $refresh Whether to use a meta refresh instead
2592
 * @param bool $permanent Whether to send a 301 Moved Permanently instead of a 302 Moved Temporarily
2593
 */
2594
function redirectexit($setLocation = '', $refresh = false, $permanent = false)
2595
{
2596
	global $scripturl, $context, $modSettings, $db_show_debug, $db_cache;
2597
2598
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
2599
	if (!empty($context['flush_mail']))
2600
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
2601
		AddMailQueue(true);
2602
2603
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
2604
2605
	if ($add)
2606
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
2607
2608
	// Put the session ID in.
2609
	if (defined('SID') && SID != '')
2610
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
2611
	// Keep that debug in their for template debugging!
2612 View Code Duplication
	elseif (isset($_GET['debug']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2613
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
2614
2615
	if (!empty($modSettings['queryless_urls']) && (empty($context['server']['is_cgi']) || ini_get('cgi.fix_pathinfo') == 1 || @get_cfg_var('cgi.fix_pathinfo') == 1) && (!empty($context['server']['is_apache']) || !empty($context['server']['is_lighttpd']) || !empty($context['server']['is_litespeed'])))
2616
	{
2617
		if (defined('SID') && SID != '')
2618
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~',
2619
				function ($m) use ($scripturl)
2620
				{
2621
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html?' . SID. (isset($m[2]) ? "$m[2]" : "");
2622
				}, $setLocation);
2623 View Code Duplication
		else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2624
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~',
2625
				function ($m) use ($scripturl)
2626
				{
2627
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? "$m[2]" : "");
2628
				}, $setLocation);
2629
	}
2630
2631
	// Maybe integrations want to change where we are heading?
2632
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh, &$permanent));
2633
2634
	// Set the header.
2635
	header('Location: ' . str_replace(' ', '%20', $setLocation), true, $permanent ? 301 : 302);
2636
2637
	// Debugging.
2638
	if (isset($db_show_debug) && $db_show_debug === true)
2639
		$_SESSION['debug_redirect'] = $db_cache;
2640
2641
	obExit(false);
2642
}
2643
2644
/**
2645
 * Ends execution.  Takes care of template loading and remembering the previous URL.
2646
 * @param bool $header Whether to do the header
2647
 * @param bool $do_footer Whether to do the footer
2648
 * @param bool $from_index Whether we're coming from the board index
2649
 * @param bool $from_fatal_error Whether we're coming from a fatal error
2650
 */
2651
function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false)
2652
{
2653
	global $context, $settings, $modSettings, $txt, $smcFunc;
2654
	static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false;
2655
2656
	// Attempt to prevent a recursive loop.
2657
	++$level;
2658
	if ($level > 1 && !$from_fatal_error && !$has_fatal_error)
2659
		exit;
2660
	if ($from_fatal_error)
2661
		$has_fatal_error = true;
2662
2663
	// Clear out the stat cache.
2664
	trackStats();
2665
2666
	// If we have mail to send, send it.
2667
	if (!empty($context['flush_mail']))
2668
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
2669
		AddMailQueue(true);
2670
2671
	$do_header = $header === null ? !$header_done : $header;
2672
	if ($do_footer === null)
2673
		$do_footer = $do_header;
2674
2675
	// Has the template/header been done yet?
2676
	if ($do_header)
2677
	{
2678
		// Was the page title set last minute? Also update the HTML safe one.
2679
		if (!empty($context['page_title']) && empty($context['page_title_html_safe']))
2680
			$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
2681
2682
		// Start up the session URL fixer.
2683
		ob_start('ob_sessrewrite');
2684
2685
		if (!empty($settings['output_buffers']) && is_string($settings['output_buffers']))
2686
			$buffers = explode(',', $settings['output_buffers']);
2687
		elseif (!empty($settings['output_buffers']))
2688
			$buffers = $settings['output_buffers'];
2689
		else
2690
			$buffers = array();
2691
2692
		if (isset($modSettings['integrate_buffer']))
2693
			$buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers);
2694
2695
		if (!empty($buffers))
2696
			foreach ($buffers as $function)
2697
			{
2698
				$call = call_helper($function, true);
2699
2700
				// Is it valid?
2701
				if (!empty($call))
2702
					ob_start($call);
2703
			}
2704
2705
		// Display the screen in the logical order.
2706
		template_header();
2707
		$header_done = true;
2708
	}
2709
	if ($do_footer)
2710
	{
2711
		loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main');
2712
2713
		// Anything special to put out?
2714
		if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml']))
2715
			echo $context['insert_after_template'];
2716
2717
		// Just so we don't get caught in an endless loop of errors from the footer...
2718
		if (!$footer_done)
2719
		{
2720
			$footer_done = true;
2721
			template_footer();
2722
2723
			// (since this is just debugging... it's okay that it's after </html>.)
2724
			if (!isset($_REQUEST['xml']))
2725
				displayDebug();
2726
		}
2727
	}
2728
2729
	// Remember this URL in case someone doesn't like sending HTTP_REFERER.
2730
	if (strpos($_SERVER['REQUEST_URL'], 'action=dlattach') === false && strpos($_SERVER['REQUEST_URL'], 'action=viewsmfile') === false)
2731
		$_SESSION['old_url'] = $_SERVER['REQUEST_URL'];
2732
2733
	// For session check verification.... don't switch browsers...
2734
	$_SESSION['USER_AGENT'] = empty($_SERVER['HTTP_USER_AGENT']) ? '' : $_SERVER['HTTP_USER_AGENT'];
2735
2736
	// Hand off the output to the portal, etc. we're integrated with.
2737
	call_integration_hook('integrate_exit', array($do_footer));
2738
2739
	// Don't exit if we're coming from index.php; that will pass through normally.
2740
	if (!$from_index)
2741
		exit;
2742
}
2743
2744
/**
2745
 * Get the size of a specified image with better error handling.
2746
 * @todo see if it's better in Subs-Graphics, but one step at the time.
2747
 * Uses getimagesize() to determine the size of a file.
2748
 * Attempts to connect to the server first so it won't time out.
2749
 *
2750
 * @param string $url The URL of the image
2751
 * @return array|false The image size as array (width, height), or false on failure
2752
 */
2753
function url_image_size($url)
2754
{
2755
	global $sourcedir;
2756
2757
	// Make sure it is a proper URL.
2758
	$url = str_replace(' ', '%20', $url);
2759
2760
	// Can we pull this from the cache... please please?
2761
	if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null)
2762
		return $temp;
2763
	$t = microtime();
2764
2765
	// Get the host to pester...
2766
	preg_match('~^\w+://(.+?)/(.*)$~', $url, $match);
2767
2768
	// Can't figure it out, just try the image size.
2769
	if ($url == '' || $url == 'http://' || $url == 'https://')
2770
	{
2771
		return false;
2772
	}
2773
	elseif (!isset($match[1]))
2774
	{
2775
		$size = @getimagesize($url);
2776
	}
2777
	else
2778
	{
2779
		// Try to connect to the server... give it half a second.
2780
		$temp = 0;
2781
		$fp = @fsockopen($match[1], 80, $temp, $temp, 0.5);
2782
2783
		// Successful?  Continue...
2784
		if ($fp != false)
2785
		{
2786
			// Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.)
2787
			fwrite($fp, 'HEAD /' . $match[2] . ' HTTP/1.1' . "\r\n" . 'Host: ' . $match[1] . "\r\n" . 'User-Agent: PHP/SMF' . "\r\n" . 'Connection: close' . "\r\n\r\n");
2788
2789
			// Read in the HTTP/1.1 or whatever.
2790
			$test = substr(fgets($fp, 11), -1);
2791
			fclose($fp);
2792
2793
			// See if it returned a 404/403 or something.
2794
			if ($test < 4)
2795
			{
2796
				$size = @getimagesize($url);
2797
2798
				// This probably means allow_url_fopen is off, let's try GD.
2799
				if ($size === false && function_exists('imagecreatefromstring'))
2800
				{
2801
					include_once($sourcedir . '/Subs-Package.php');
2802
2803
					// It's going to hate us for doing this, but another request...
2804
					$image = @imagecreatefromstring(fetch_web_data($url));
2805
					if ($image !== false)
2806
					{
2807
						$size = array(imagesx($image), imagesy($image));
2808
						imagedestroy($image);
2809
					}
2810
				}
2811
			}
2812
		}
2813
	}
2814
2815
	// If we didn't get it, we failed.
2816
	if (!isset($size))
2817
		$size = false;
2818
2819
	// If this took a long time, we may never have to do it again, but then again we might...
2820 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.8)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
2821
		cache_put_data('url_image_size-' . md5($url), $size, 240);
2822
2823
	// Didn't work.
2824
	return $size;
2825
}
2826
2827
/**
2828
 * Sets up the basic theme context stuff.
2829
 * @param bool $forceload Whether to load the theme even if it's already loaded
2830
 */
2831
function setupThemeContext($forceload = false)
2832
{
2833
	global $modSettings, $user_info, $scripturl, $context, $settings, $options, $txt, $maintenance;
2834
	global $smcFunc;
2835
	static $loaded = false;
2836
2837
	// Under SSI this function can be called more then once.  That can cause some problems.
2838
	//   So only run the function once unless we are forced to run it again.
2839
	if ($loaded && !$forceload)
2840
		return;
2841
2842
	$loaded = true;
2843
2844
	$context['in_maintenance'] = !empty($maintenance);
2845
	$context['current_time'] = timeformat(time(), false);
2846
	$context['current_action'] = isset($_GET['action']) ? $smcFunc['htmlspecialchars']($_GET['action']) : '';
2847
2848
	// Get some news...
2849
	$context['news_lines'] = array_filter(explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news'])))));
2850
	for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++)
2851
	{
2852
		if (trim($context['news_lines'][$i]) == '')
2853
			continue;
2854
2855
		// Clean it up for presentation ;).
2856
		$context['news_lines'][$i] = parse_bbc(stripslashes(trim($context['news_lines'][$i])), true, 'news' . $i);
2857
	}
2858
	if (!empty($context['news_lines']))
2859
		$context['random_news_line'] = $context['news_lines'][mt_rand(0, count($context['news_lines']) - 1)];
2860
2861
	if (!$user_info['is_guest'])
2862
	{
2863
		$context['user']['messages'] = &$user_info['messages'];
2864
		$context['user']['unread_messages'] = &$user_info['unread_messages'];
2865
		$context['user']['alerts'] = &$user_info['alerts'];
2866
2867
		// Personal message popup...
2868
		if ($user_info['unread_messages'] > (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0))
2869
			$context['user']['popup_messages'] = true;
2870
		else
2871
			$context['user']['popup_messages'] = false;
2872
		$_SESSION['unread_messages'] = $user_info['unread_messages'];
2873
2874
		if (allowedTo('moderate_forum'))
2875
			$context['unapproved_members'] = (!empty($modSettings['registration_method']) && ($modSettings['registration_method'] == 2 || (!empty($modSettings['coppaType']) && $modSettings['coppaType'] == 2))) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0;
2876
2877
		$context['user']['avatar'] = array();
2878
2879
		// Check for gravatar first since we might be forcing them...
2880
		if (($modSettings['gravatarEnabled'] && substr($user_info['avatar']['url'], 0, 11) == 'gravatar://') || !empty($modSettings['gravatarOverride']))
2881
		{
2882
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($user_info['avatar']['url'], 'gravatar://') && strlen($user_info['avatar']['url']) > 11)
2883
				$context['user']['avatar']['href'] = get_gravatar_url($smcFunc['substr']($user_info['avatar']['url'], 11));
2884
			else
2885
				$context['user']['avatar']['href'] = get_gravatar_url($user_info['email']);
2886
		}
2887
		// Uploaded?
2888
		elseif ($user_info['avatar']['url'] == '' && !empty($user_info['avatar']['id_attach']))
2889
			$context['user']['avatar']['href'] = $user_info['avatar']['custom_dir'] ? $modSettings['custom_avatar_url'] . '/' . $user_info['avatar']['filename'] : $scripturl . '?action=dlattach;attach=' . $user_info['avatar']['id_attach'] . ';type=avatar';
2890
		// Full URL?
2891
		elseif (strpos($user_info['avatar']['url'], 'http://') === 0 || strpos($user_info['avatar']['url'], 'https://') === 0)
2892
			$context['user']['avatar']['href'] = $user_info['avatar']['url'];
2893
		// Otherwise we assume it's server stored.
2894
		elseif ($user_info['avatar']['url'] != '')
2895
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/' . $smcFunc['htmlspecialchars']($user_info['avatar']['url']);
2896
		// No avatar at all? Fine, we have a big fat default avatar ;)
2897
		else
2898
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/default.png';
2899
2900
		if (!empty($context['user']['avatar']))
2901
			$context['user']['avatar']['image'] = '<img src="' . $context['user']['avatar']['href'] . '" alt="" class="avatar">';
2902
2903
		// Figure out how long they've been logged in.
2904
		$context['user']['total_time_logged_in'] = array(
2905
			'days' => floor($user_info['total_time_logged_in'] / 86400),
2906
			'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600),
2907
			'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60)
2908
		);
2909
	}
2910
	else
2911
	{
2912
		$context['user']['messages'] = 0;
2913
		$context['user']['unread_messages'] = 0;
2914
		$context['user']['avatar'] = array();
2915
		$context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0);
2916
		$context['user']['popup_messages'] = false;
2917
2918
		if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1)
2919
			$txt['welcome_guest'] .= $txt['welcome_guest_activate'];
2920
2921
		// If we've upgraded recently, go easy on the passwords.
2922
		if (!empty($modSettings['disableHashTime']) && ($modSettings['disableHashTime'] == 1 || time() < $modSettings['disableHashTime']))
2923
			$context['disable_login_hashing'] = true;
2924
	}
2925
2926
	// Setup the main menu items.
2927
	setupMenuContext();
2928
2929
	// This is here because old index templates might still use it.
2930
	$context['show_news'] = !empty($settings['enable_news']);
2931
2932
	// This is done to allow theme authors to customize it as they want.
2933
	$context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != 'pm');
2934
2935
	// 2.1+: Add the PM popup here instead. Theme authors can still override it simply by editing/removing the 'fPmPopup' in the array.
2936
	if ($context['show_pm_popup'])
2937
		addInlineJavascript('
2938
		jQuery(document).ready(function($) {
2939
			new smc_Popup({
2940
				heading: ' . JavaScriptEscape($txt['show_personal_messages_heading']) . ',
2941
				content: ' . JavaScriptEscape(sprintf($txt['show_personal_messages'], $context['user']['unread_messages'], $scripturl . '?action=pm')) . ',
2942
				icon_class: \'generic_icons mail_new\'
2943
			});
2944
		});');
2945
2946
	// Add a generic "Are you sure?" confirmation message.
2947
	addInlineJavascript('
2948
	var smf_you_sure =' . JavaScriptEscape($txt['quickmod_confirm']) .';');
2949
2950
	// Now add the capping code for avatars.
2951
	if (!empty($modSettings['avatar_max_width_external']) && !empty($modSettings['avatar_max_height_external']) && !empty($modSettings['avatar_action_too_large']) && $modSettings['avatar_action_too_large'] == 'option_css_resize')
2952
		addInlineCss('
2953
img.avatar { max-width: ' . $modSettings['avatar_max_width_external'] . 'px; max-height: ' . $modSettings['avatar_max_height_external'] . 'px; }');
2954
2955
	// This looks weird, but it's because BoardIndex.php references the variable.
2956
	$context['common_stats']['latest_member'] = array(
2957
		'id' => $modSettings['latestMember'],
2958
		'name' => $modSettings['latestRealName'],
2959
		'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'],
2960
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $modSettings['latestRealName'] . '</a>',
2961
	);
2962
	$context['common_stats'] = array(
2963
		'total_posts' => comma_format($modSettings['totalMessages']),
2964
		'total_topics' => comma_format($modSettings['totalTopics']),
2965
		'total_members' => comma_format($modSettings['totalMembers']),
2966
		'latest_member' => $context['common_stats']['latest_member'],
2967
	);
2968
	$context['common_stats']['boardindex_total_posts'] = sprintf($txt['boardindex_total_posts'], $context['common_stats']['total_posts'], $context['common_stats']['total_topics'], $context['common_stats']['total_members']);
2969
2970
	if (empty($settings['theme_version']))
2971
		addJavascriptVar('smf_scripturl', $scripturl);
2972
2973
	if (!isset($context['page_title']))
2974
		$context['page_title'] = '';
2975
2976
	// Set some specific vars.
2977
	$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
2978
	$context['meta_keywords'] = !empty($modSettings['meta_keywords']) ? $smcFunc['htmlspecialchars']($modSettings['meta_keywords']) : '';
2979
2980
	call_integration_hook('integrate_theme_context');
2981
}
2982
2983
/**
2984
 * Helper function to set the system memory to a needed value
2985
 * - If the needed memory is greater than current, will attempt to get more
2986
 * - if in_use is set to true, will also try to take the current memory usage in to account
2987
 *
2988
 * @param string $needed The amount of memory to request, if needed, like 256M
2989
 * @param bool $in_use Set to true to account for current memory usage of the script
2990
 * @return boolean True if we have at least the needed memory
2991
 */
2992
function setMemoryLimit($needed, $in_use = false)
2993
{
2994
	// everything in bytes
2995
	$memory_used = 0;
0 ignored issues
show
Unused Code introduced by
$memory_used is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2996
	$memory_current = memoryReturnBytes(ini_get('memory_limit'));
2997
	$memory_needed = memoryReturnBytes($needed);
2998
2999
	// should we account for how much is currently being used?
3000
	if ($in_use)
3001
		$memory_needed += function_exists('memory_get_usage') ? memory_get_usage() : (2 * 1048576);
3002
3003
	// if more is needed, request it
3004
	if ($memory_current < $memory_needed)
3005
	{
3006
		@ini_set('memory_limit', ceil($memory_needed / 1048576) . 'M');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3007
		$memory_current = memoryReturnBytes(ini_get('memory_limit'));
3008
	}
3009
3010
	$memory_current = max($memory_current, memoryReturnBytes(get_cfg_var('memory_limit')));
3011
3012
	// return success or not
3013
	return (bool) ($memory_current >= $memory_needed);
3014
}
3015
3016
/**
3017
 * Helper function to convert memory string settings to bytes
3018
 *
3019
 * @param string $val The byte string, like 256M or 1G
3020
 * @return integer The string converted to a proper integer in bytes
3021
 */
3022
function memoryReturnBytes($val)
3023
{
3024
	if (is_integer($val))
3025
		return $val;
3026
3027
	// Separate the number from the designator
3028
	$val = trim($val);
3029
	$num = intval(substr($val, 0, strlen($val) - 1));
3030
	$last = strtolower(substr($val, -1));
3031
3032
	// convert to bytes
3033
	switch ($last)
3034
	{
3035
		case 'g':
3036
			$num *= 1024;
3037
		case 'm':
3038
			$num *= 1024;
3039
		case 'k':
3040
			$num *= 1024;
3041
	}
3042
	return $num;
3043
}
3044
3045
/**
3046
 * The header template
3047
 */
3048
function template_header()
3049
{
3050
	global $txt, $modSettings, $context, $user_info, $boarddir, $cachedir;
3051
3052
	setupThemeContext();
3053
3054
	// Print stuff to prevent caching of pages (except on attachment errors, etc.)
3055
	if (empty($context['no_last_modified']))
3056
	{
3057
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
3058
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3059
3060
		// Are we debugging the template/html content?
3061
		if (!isset($_REQUEST['xml']) && isset($_GET['debug']) && !isBrowser('ie'))
3062
			header('Content-Type: application/xhtml+xml');
3063 View Code Duplication
		elseif (!isset($_REQUEST['xml']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3064
			header('Content-Type: text/html; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3065
	}
3066
3067
	header('Content-Type: text/' . (isset($_REQUEST['xml']) ? 'xml' : 'html') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3068
3069
	// We need to splice this in after the body layer, or after the main layer for older stuff.
3070
	if ($context['in_maintenance'] && $context['user']['is_admin'])
3071
	{
3072
		$position = array_search('body', $context['template_layers']);
3073
		if ($position === false)
3074
			$position = array_search('main', $context['template_layers']);
3075
3076
		if ($position !== false)
3077
		{
3078
			$before = array_slice($context['template_layers'], 0, $position + 1);
3079
			$after = array_slice($context['template_layers'], $position + 1);
3080
			$context['template_layers'] = array_merge($before, array('maint_warning'), $after);
3081
		}
3082
	}
3083
3084
	$checked_securityFiles = false;
3085
	$showed_banned = false;
3086
	foreach ($context['template_layers'] as $layer)
3087
	{
3088
		loadSubTemplate($layer . '_above', true);
3089
3090
		// May seem contrived, but this is done in case the body and main layer aren't there...
3091
		if (in_array($layer, array('body', 'main')) && allowedTo('admin_forum') && !$user_info['is_guest'] && !$checked_securityFiles)
3092
		{
3093
			$checked_securityFiles = true;
3094
3095
			$securityFiles = array('install.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~');
3096
3097
			// Add your own files.
3098
			call_integration_hook('integrate_security_files', array(&$securityFiles));
3099
3100
			foreach ($securityFiles as $i => $securityFile)
3101
			{
3102
				if (!file_exists($boarddir . '/' . $securityFile))
3103
					unset($securityFiles[$i]);
3104
			}
3105
3106
			// We are already checking so many files...just few more doesn't make any difference! :P
3107 View Code Duplication
			if (!empty($modSettings['currentAttachmentUploadDir']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3108
			{
3109
				if (!is_array($modSettings['attachmentUploadDir']))
3110
					$modSettings['attachmentUploadDir'] = smf_json_decode($modSettings['attachmentUploadDir'], true);
3111
				$path = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
3112
			}
3113
			else
3114
			{
3115
				$path = $modSettings['attachmentUploadDir'];
3116
				$id_folder_thumb = 1;
0 ignored issues
show
Unused Code introduced by
$id_folder_thumb is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3117
			}
3118
			secureDirectory($path, true);
3119
			secureDirectory($cachedir);
3120
3121
			// If agreement is enabled, at least the english version shall exists
3122
			if ($modSettings['requireAgreement'])
3123
				$agreement = !file_exists($boarddir . '/agreement.txt');
3124
3125
			if (!empty($securityFiles) || (!empty($modSettings['cache_enable']) && !is_writable($cachedir)) || !empty($agreement))
3126
			{
3127
				echo '
3128
		<div class="errorbox">
3129
			<p class="alert">!!</p>
3130
			<h3>', empty($securityFiles) ? $txt['generic_warning'] : $txt['security_risk'], '</h3>
3131
			<p>';
3132
3133
				foreach ($securityFiles as $securityFile)
3134
				{
3135
					echo '
3136
				', $txt['not_removed'], '<strong>', $securityFile, '</strong>!<br>';
3137
3138
					if ($securityFile == 'Settings.php~' || $securityFile == 'Settings_bak.php~')
3139
						echo '
3140
				', sprintf($txt['not_removed_extra'], $securityFile, substr($securityFile, 0, -1)), '<br>';
3141
				}
3142
3143
				if (!empty($modSettings['cache_enable']) && !is_writable($cachedir))
3144
					echo '
3145
				<strong>', $txt['cache_writable'], '</strong><br>';
3146
3147
				if (!empty($agreement))
3148
					echo '
3149
				<strong>', $txt['agreement_missing'], '</strong><br>';
3150
3151
				echo '
3152
			</p>
3153
		</div>';
3154
			}
3155
		}
3156
		// If the user is banned from posting inform them of it.
3157
		elseif (in_array($layer, array('main', 'body')) && isset($_SESSION['ban']['cannot_post']) && !$showed_banned)
3158
		{
3159
			$showed_banned = true;
3160
			echo '
3161
				<div class="windowbg alert" style="margin: 2ex; padding: 2ex; border: 2px dashed red;">
3162
					', sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']);
3163
3164
			if (!empty($_SESSION['ban']['cannot_post']['reason']))
3165
				echo '
3166
					<div style="padding-left: 4ex; padding-top: 1ex;">', $_SESSION['ban']['cannot_post']['reason'], '</div>';
3167
3168
			if (!empty($_SESSION['ban']['expire_time']))
3169
				echo '
3170
					<div>', sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)), '</div>';
3171
			else
3172
				echo '
3173
					<div>', $txt['your_ban_expires_never'], '</div>';
3174
3175
			echo '
3176
				</div>';
3177
		}
3178
	}
3179
}
3180
3181
/**
3182
 * Show the copyright.
3183
 */
3184
function theme_copyright()
3185
{
3186
	global $forum_copyright, $software_year, $forum_version;
3187
3188
	// Don't display copyright for things like SSI.
3189
	if (!isset($forum_version) || !isset($software_year))
3190
		return;
3191
3192
	// Put in the version...
3193
	printf($forum_copyright, $forum_version, $software_year);
3194
}
3195
3196
/**
3197
 * The template footer
3198
 */
3199
function template_footer()
3200
{
3201
	global $context, $modSettings, $time_start, $db_count;
3202
3203
	// Show the load time?  (only makes sense for the footer.)
3204
	$context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']);
3205
	$context['load_time'] = comma_format(round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)), 3));
3206
	$context['load_queries'] = $db_count;
3207
3208
	foreach (array_reverse($context['template_layers']) as $layer)
3209
		loadSubTemplate($layer . '_below', true);
3210
}
3211
3212
/**
3213
 * Output the Javascript files
3214
 * 	- tabbing in this function is to make the HTML source look good proper
3215
 *  - if defered is set function will output all JS (source & inline) set to load at page end
3216
 *
3217
 * @param bool $do_deferred If true will only output the deferred JS (the stuff that goes right before the closing body tag)
3218
 */
3219
function template_javascript($do_deferred = false)
3220
{
3221
	global $context, $modSettings, $settings;
3222
3223
	// Use this hook to minify/optimize Javascript files and vars
3224
	call_integration_hook('integrate_pre_javascript_output', array(&$do_deferred));
3225
3226
	$toMinify = array();
3227
	$toMinifyDefer = array();
3228
3229
	// Ouput the declared Javascript variables.
3230
	if (!empty($context['javascript_vars']) && !$do_deferred)
3231
	{
3232
		echo '
3233
	<script>';
3234
3235
		foreach ($context['javascript_vars'] as $key => $value)
3236
		{
3237
			if (empty($value))
3238
			{
3239
				echo '
3240
		var ', $key, ';';
3241
			}
3242
			else
3243
			{
3244
				echo '
3245
		var ', $key, ' = ', $value, ';';
3246
			}
3247
		}
3248
3249
		echo '
3250
	</script>';
3251
	}
3252
3253
	// While we have JavaScript files to place in the template.
3254
	foreach ($context['javascript_files'] as $id => $js_file)
3255
	{
3256
		// Last minute call! allow theme authors to disable single files.
3257
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3258
			continue;
3259
3260
		// By default all files don't get minimized unless the file explicitly says so!
3261
		if (!empty($js_file['options']['minimize']) && !empty($modSettings['minimize_files']))
3262
		{
3263
			if ($do_deferred && !empty($js_file['options']['defer']))
3264
				$toMinifyDefer[] = $js_file;
3265
3266
			elseif (!$do_deferred && empty($js_file['options']['defer']))
3267
				$toMinify[] = $js_file;
3268
3269
			// Grab a random seed.
3270
			if (!isset($minSeed))
3271
				$minSeed = $js_file['options']['seed'];
3272
		}
3273
3274
		elseif ((!$do_deferred && empty($js_file['options']['defer'])) || ($do_deferred && !empty($js_file['options']['defer'])))
3275
			echo '
3276
	<script src="', $js_file['fileUrl'], '"', !empty($js_file['options']['async']) ? ' async="async"' : '', '></script>';
3277
	}
3278
3279
	if ((!$do_deferred && !empty($toMinify)) || ($do_deferred && !empty($toMinifyDefer)))
3280
	{
3281
		$result = custMinify(($do_deferred ? $toMinifyDefer : $toMinify), 'js', $do_deferred);
3282
3283
		// Minify process couldn't work, print each individual files.
3284
		if (!empty($result) && is_array($result))
3285
			foreach ($result as $minFailedFile)
3286
				echo '
3287
	<script src="', $minFailedFile['fileUrl'], '"', !empty($minFailedFile['options']['async']) ? ' async="async"' : '', '></script>';
3288
3289
		else
3290
			echo '
3291
	<script src="', $settings['theme_url'] ,'/scripts/minified', ($do_deferred ? '_deferred' : '') ,'.js', $minSeed ,'"></script>';
0 ignored issues
show
Bug introduced by
The variable $minSeed 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...
3292
	}
3293
3294
	// Inline JavaScript - Actually useful some times!
3295
	if (!empty($context['javascript_inline']))
3296
	{
3297 View Code Duplication
		if (!empty($context['javascript_inline']['defer']) && $do_deferred)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3298
		{
3299
			echo '
3300
<script>';
3301
3302
			foreach ($context['javascript_inline']['defer'] as $js_code)
3303
				echo $js_code;
3304
3305
			echo '
3306
</script>';
3307
		}
3308
3309 View Code Duplication
		if (!empty($context['javascript_inline']['standard']) && !$do_deferred)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3310
		{
3311
			echo '
3312
	<script>';
3313
3314
			foreach ($context['javascript_inline']['standard'] as $js_code)
3315
				echo $js_code;
3316
3317
			echo '
3318
	</script>';
3319
		}
3320
	}
3321
}
3322
3323
/**
3324
 * Output the CSS files
3325
 *
3326
 */
3327
function template_css()
3328
{
3329
	global $context, $db_show_debug, $boardurl, $settings, $modSettings;
3330
3331
	// Use this hook to minify/optimize CSS files
3332
	call_integration_hook('integrate_pre_css_output');
3333
3334
	$toMinify = array();
3335
	$normal = array();
3336
3337
	foreach ($context['css_files'] as $id => $file)
3338
	{
3339
		// Last minute call! allow theme authors to disable single files.
3340
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3341
			continue;
3342
3343
		// By default all files don't get minimized unless the file explicitly says so!
3344
		if (!empty($file['options']['minimize']) && !empty($modSettings['minimize_files']))
3345
		{
3346
			$toMinify[] = $file;
3347
3348
			// Grab a random seed.
3349
			if (!isset($minSeed))
3350
				$minSeed = $file['options']['seed'];
3351
		}
3352
3353
		else
3354
			$normal[] = $file['fileUrl'];
3355
	}
3356
3357
	if (!empty($toMinify))
3358
	{
3359
		$result = custMinify($toMinify, 'css');
3360
3361
		// Minify process couldn't work, print each individual files.
3362
		if (!empty($result) && is_array($result))
3363
			foreach ($result as $minFailedFile)
3364
				echo '
3365
	<link rel="stylesheet" href="', $minFailedFile['fileUrl'], '">';
3366
3367
		else
3368
			echo '
3369
	<link rel="stylesheet" href="', $settings['theme_url'] ,'/css/minified.css', $minSeed ,'">';
0 ignored issues
show
Bug introduced by
The variable $minSeed 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...
3370
	}
3371
3372
	// Print the rest after the minified files.
3373
	if (!empty($normal))
3374
		foreach ($normal as $nf)
3375
			echo '
3376
	<link rel="stylesheet" href="', $nf ,'">';
3377
3378
	if ($db_show_debug === true)
3379
	{
3380
		// Try to keep only what's useful.
3381
		$repl = array($boardurl . '/Themes/' => '', $boardurl . '/' => '');
3382
		foreach ($context['css_files'] as $file)
3383
			$context['debug']['sheets'][] = strtr($file['fileName'], $repl);
3384
	}
3385
3386
	if (!empty($context['css_header']))
3387
	{
3388
		echo '
3389
	<style>';
3390
3391
		foreach ($context['css_header'] as $css)
3392
			echo $css .'
3393
	';
3394
3395
		echo'
3396
	</style>';
3397
	}
3398
}
3399
3400
/**
3401
 * Get an array of previously defined files and adds them to our main minified file.
3402
 * Sets a one day cache to avoid re-creating a file on every request.
3403
 *
3404
 * @param array $data The files to minify.
3405
 * @param string $type either css or js.
3406
 * @param bool $do_deferred use for type js to indicate if the minified file will be deferred, IE, put at the closing </body> tag.
3407
 * @return bool|array If an array the minify process failed and the data is returned intact.
3408
 */
3409
function custMinify($data, $type, $do_deferred = false)
3410
{
3411
	global $sourcedir, $smcFunc, $settings, $txt, $context;
3412
3413
	$types = array('css', 'js');
3414
	$type = !empty($type) && in_array($type, $types) ? $type : false;
3415
	$data = !empty($data) ? $data : false;
3416
	$minFailed = array();
0 ignored issues
show
Unused Code introduced by
$minFailed is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
3417
3418
	if (empty($type) || empty($data))
3419
		return false;
3420
3421
	// Did we already did this?
3422
	$toCache = cache_get_data('minimized_'. $settings['theme_id'] .'_'. $type, 86400);
3423
3424
	// Already done?
3425
	if (!empty($toCache))
3426
		return true;
3427
3428
	// Yep, need a bunch of files.
3429
	require_once($sourcedir . '/minify/src/Minify.php');
3430
	require_once($sourcedir . '/minify/src/'. strtoupper($type) .'.php');
3431
	require_once($sourcedir . '/minify/src/Exception.php');
3432
	require_once($sourcedir . '/minify/src/Converter.php');
3433
3434
	// No namespaces, sorry!
3435
	$classType = 'MatthiasMullie\\Minify\\'. strtoupper($type);
3436
3437
	// Temp path.
3438
	$cTempPath = $settings['theme_dir'] .'/'. ($type == 'css' ? 'css' : 'scripts') .'/';
3439
3440
	// What kind of file are we going to create?
3441
	$toCreate = $cTempPath .'minified'. ($do_deferred ? '_deferred' : '') .'.'. $type;
3442
3443
	// File has to exists, if it isn't try to create it.
3444
	if ((!file_exists($toCreate) && @fopen($toCreate, 'w') === false) || !smf_chmod($toCreate))
3445
	{
3446
		loadLanguage('Errors');
3447
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3448
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3449
3450
		// The process failed so roll back to print each individual file.
3451
		return $data;
3452
	}
3453
3454
	$minifier = new $classType();
3455
3456
	foreach ($data as $file)
3457
	{
3458
		$tempFile = str_replace($file['options']['seed'], '', $file['filePath']);
3459
		$toAdd = file_exists($tempFile) ? $tempFile : false;
3460
3461
		// The file couldn't be located so it won't be added, log this error.
3462
		if (empty($toAdd))
3463
		{
3464
			loadLanguage('Errors');
3465
			log_error(sprintf($txt['file_minimize_fail'], $file['fileName']), 'general');
3466
			continue;
3467
		}
3468
3469
		// Add this file to the list.
3470
		$minifier->add($toAdd);
3471
	}
3472
3473
	// Create the file.
3474
	$minifier->minify($toCreate);
3475
	unset($minifier);
3476
	clearstatcache();
3477
3478
	// Minify process failed.
3479
	if (!filesize($toCreate))
3480
	{
3481
		loadLanguage('Errors');
3482
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3483
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3484
3485
		// The process failed so roll back to print each individual file.
3486
		return $data;
3487
	}
3488
3489
	// And create a long lived cache entry.
3490
	cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, $toCreate, 86400);
3491
3492
	return true;
3493
}
3494
3495
/**
3496
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
3497
 * @todo this currently returns the hash if new, and the full filename otherwise.
3498
 * Something messy like that.
3499
 * @todo and of course everything relies on this behavior and work around it. :P.
3500
 * Converters included.
3501
 *
3502
 * @param string $filename The name of the file
3503
 * @param int $attachment_id The ID of the attachment
3504
 * @param string $dir Which directory it should be in (null to use current one)
3505
 * @param bool $new Whether this is a new attachment
3506
 * @param string $file_hash The file hash
3507
 * @return string The path to the file
3508
 */
3509
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
3510
{
3511
	global $modSettings, $smcFunc;
3512
3513
	// Just make up a nice hash...
3514
	if ($new)
3515
		return sha1(md5($filename . time()) . mt_rand());
3516
3517
	// Grab the file hash if it wasn't added.
3518
	// Left this for legacy.
3519
	if ($file_hash === '')
3520
	{
3521
		$request = $smcFunc['db_query']('', '
3522
			SELECT file_hash
3523
			FROM {db_prefix}attachments
3524
			WHERE id_attach = {int:id_attach}',
3525
			array(
3526
				'id_attach' => $attachment_id,
3527
			));
3528
3529
		if ($smcFunc['db_num_rows']($request) === 0)
3530
			return false;
3531
3532
		list ($file_hash) = $smcFunc['db_fetch_row']($request);
3533
		$smcFunc['db_free_result']($request);
3534
	}
3535
3536
	// Still no hash? mmm...
3537
	if (empty($file_hash))
3538
		$file_hash = sha1(md5($filename . time()) . mt_rand());
3539
3540
	// Are we using multiple directories?
3541
	if (!empty($modSettings['currentAttachmentUploadDir']))
3542
	{
3543
		if (!is_array($modSettings['attachmentUploadDir']))
3544
			$modSettings['attachmentUploadDir'] = smf_json_decode($modSettings['attachmentUploadDir'], true);
3545
		$path = $modSettings['attachmentUploadDir'][$dir];
3546
	}
3547
	else
3548
		$path = $modSettings['attachmentUploadDir'];
3549
3550
	return $path . '/' . $attachment_id . '_' . $file_hash .'.dat';
3551
}
3552
3553
/**
3554
 * Convert a single IP to a ranged IP.
3555
 * internal function used to convert a user-readable format to a format suitable for the database.
3556
 *
3557
 * @param string $fullip The full IP
3558
 * @return array An array of IP parts
3559
 */
3560
function ip2range($fullip)
3561
{
3562
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
3563
	if ($fullip == 'unknown')
3564
		$fullip = '255.255.255.255';
3565
3566
	$ip_parts = explode('-', $fullip);
3567
	$ip_array = array();
3568
3569
	// if ip 22.12.31.21
3570
	if (count($ip_parts) == 1 && isValidIP($fullip))
3571
	{
3572
		$ip_array['low'] = $fullip;
3573
		$ip_array['high'] = $fullip;
3574
		return $ip_array;
3575
	} // if ip 22.12.* -> 22.12.* - 22.12.*
3576
	elseif (count($ip_parts) == 1)
3577
	{
3578
		$ip_parts[0] = $fullip;
3579
		$ip_parts[1] = $fullip;
3580
	}
3581
3582
	// if ip 22.12.31.21-12.21.31.21
3583
	if (count($ip_parts) == 2 && isValidIP($ip_parts[0]) && isValidIP($ip_parts[1]))
3584
	{
3585
		$ip_array['low'] = $ip_parts[0];
3586
		$ip_array['high'] = $ip_parts[1];
3587
		return $ip_array;
3588
	}
3589
	elseif (count($ip_parts) == 2) // if ip 22.22.*-22.22.*
3590
	{
3591
		$valid_low = isValidIP($ip_parts[0]);
3592
		$valid_high = isValidIP($ip_parts[1]);
3593
		$count = 0;
3594
		$mode = (preg_match('/:/',$ip_parts[0]) > 0 ? ':' : '.');
3595
		$max = ($mode == ':' ? 'ffff' : '255');
3596
		$min = 0;
3597 View Code Duplication
		if(!$valid_low)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3598
		{
3599
			$ip_parts[0] = preg_replace('/\*/', '0', $ip_parts[0]);
3600
			$valid_low = isValidIP($ip_parts[0]);
3601
			while (!$valid_low)
3602
			{
3603
				$ip_parts[0] .= $mode . $min;
3604
				$valid_low = isValidIP($ip_parts[0]);
3605
				$count++;
3606
				if ($count > 9) break;
3607
			}
3608
		}
3609
3610
		$count = 0;
3611 View Code Duplication
		if(!$valid_high)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3612
		{
3613
			$ip_parts[1] = preg_replace('/\*/', $max, $ip_parts[1]);
3614
			$valid_high = isValidIP($ip_parts[1]);
3615
			while (!$valid_high)
3616
			{
3617
				$ip_parts[1] .= $mode . $max;
3618
				$valid_high = isValidIP($ip_parts[1]);
3619
				$count++;
3620
				if ($count > 9) break;
3621
			}
3622
		}
3623
3624
		if($valid_high && $valid_low)
3625
		{
3626
			$ip_array['low'] = $ip_parts[0];
3627
			$ip_array['high'] = $ip_parts[1];
3628
		}
3629
3630
	}
3631
3632
	return $ip_array;
3633
}
3634
3635
/**
3636
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
3637
 *
3638
 * @param string $ip The IP to get the hostname from
3639
 * @return string The hostname
3640
 */
3641
function host_from_ip($ip)
3642
{
3643
	global $modSettings;
3644
3645
	if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null)
3646
		return $host;
3647
	$t = microtime();
3648
3649
	// Try the Linux host command, perhaps?
3650
	if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
3651
	{
3652
		if (!isset($modSettings['host_to_dis']))
3653
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
3654
		else
3655
			$test = @shell_exec('host ' . @escapeshellarg($ip));
3656
3657
		// Did host say it didn't find anything?
3658
		if (strpos($test, 'not found') !== false)
3659
			$host = '';
3660
		// Invalid server option?
3661
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
3662
			updateSettings(array('host_to_dis' => 1));
3663
		// Maybe it found something, after all?
3664
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
3665
			$host = $match[1];
3666
	}
3667
3668
	// This is nslookup; usually only Windows, but possibly some Unix?
3669
	if (!isset($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
3670
	{
3671
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
3672
		if (strpos($test, 'Non-existent domain') !== false)
3673
			$host = '';
3674
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
3675
			$host = $match[1];
3676
	}
3677
3678
	// This is the last try :/.
3679
	if (!isset($host) || $host === false)
3680
		$host = @gethostbyaddr($ip);
3681
3682
	// It took a long time, so let's cache it!
3683 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3684
		cache_put_data('hostlookup-' . $ip, $host, 600);
3685
3686
	return $host;
3687
}
3688
3689
/**
3690
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
3691
 *
3692
 * @param string $text The text to split into words
3693
 * @param int $max_chars The maximum number of characters per word
3694
 * @param bool $encrypt Whether to encrypt the results
3695
 * @return array An array of ints or words depending on $encrypt
3696
 */
3697
function text2words($text, $max_chars = 20, $encrypt = false)
0 ignored issues
show
Best Practice introduced by
The function text2words() has been defined more than once; this definition is ignored, only the first definition in other/upgrade.php (L157-177) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
3698
{
3699
	global $smcFunc, $context;
3700
3701
	// Step 1: Remove entities/things we don't consider words:
3702
	$words = preg_replace('~(?:[\x0B\0' . ($context['utf8'] ? '\x{A0}' : '\xA0') . '\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~' . ($context['utf8'] ? 'u' : ''), ' ', strtr($text, array('<br>' => ' ')));
3703
3704
	// Step 2: Entities we left to letters, where applicable, lowercase.
3705
	$words = un_htmlspecialchars($smcFunc['strtolower']($words));
3706
3707
	// Step 3: Ready to split apart and index!
3708
	$words = explode(' ', $words);
3709
3710
	if ($encrypt)
3711
	{
3712
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
3713
		$returned_ints = array();
3714
		foreach ($words as $word)
3715
		{
3716
			if (($word = trim($word, '-_\'')) !== '')
3717
			{
3718
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
3719
				$total = 0;
3720
				for ($i = 0; $i < $max_chars; $i++)
3721
					$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
3722
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
3723
			}
3724
		}
3725
		return array_unique($returned_ints);
3726
	}
3727
	else
3728
	{
3729
		// Trim characters before and after and add slashes for database insertion.
3730
		$returned_words = array();
3731
		foreach ($words as $word)
3732
			if (($word = trim($word, '-_\'')) !== '')
3733
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
3734
3735
		// Filter out all words that occur more than once.
3736
		return array_unique($returned_words);
3737
	}
3738
}
3739
3740
/**
3741
 * Creates an image/text button
3742
 *
3743
 * @param string $name The name of the button (should be a generic_icons class or the name of an image)
3744
 * @param string $alt The alt text
3745
 * @param string $label The $txt string to use as the label
3746
 * @param string $custom Custom text/html to add to the img tag (only when using an actual image)
3747
 * @param boolean $force_use Whether to force use of this when template_create_button is available
3748
 * @return string The HTML to display the button
3749
 */
3750
function create_button($name, $alt, $label = '', $custom = '', $force_use = false)
3751
{
3752
	global $settings, $txt;
3753
3754
	// Does the current loaded theme have this and we are not forcing the usage of this function?
3755
	if (function_exists('template_create_button') && !$force_use)
3756
		return template_create_button($name, $alt, $label = '', $custom = '');
3757
3758
	if (!$settings['use_image_buttons'])
3759
		return $txt[$alt];
3760
	elseif (!empty($settings['use_buttons']))
3761
		return '<span class="generic_icons ' . $name . '" alt="' . $txt[$alt] . '"></span>' . ($label != '' ? '&nbsp;<strong>' . $txt[$label] . '</strong>' : '');
3762
	else
3763
		return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . '>';
3764
}
3765
3766
/**
3767
 * Empty out the cache in use as best it can
3768
 *
3769
 * It may only remove the files of a certain type (if the $type parameter is given)
3770
 * Type can be user, data or left blank
3771
 * 	- user clears out user data
3772
 *  - data clears out system / opcode data
3773
 *  - If no type is specified will perform a complete cache clearing
3774
 * For cache engines that do not distinguish on types, a full cache flush will be done
3775
 *
3776
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3777
 */
3778
function clean_cache($type = '')
0 ignored issues
show
Best Practice introduced by
The function clean_cache() has been defined more than once; this definition is ignored, only the first definition in other/upgrade.php (L183-204) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
3779
{
3780
	global $cachedir, $sourcedir, $cache_accelerator, $modSettings, $memcached;
3781
3782
	switch ($cache_accelerator)
3783
	{
3784
		case 'memcached':
3785
			if (function_exists('memcache_flush') || function_exists('memcached_flush') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '')
3786
			{
3787
				// Not connected yet?
3788
				if (empty($memcached))
3789
					get_memcached_server();
3790
				if (!$memcached)
3791
					return;
3792
3793
				// clear it out
3794
				if (function_exists('memcache_flush'))
3795
					memcache_flush($memcached);
3796
				else
3797
					memcached_flush($memcached);
3798
			}
3799
			break;
3800
		case 'apc':
3801
			if (function_exists('apc_clear_cache'))
3802
			{
3803
				// if passed a type, clear that type out
3804
				if ($type === '' || $type === 'data')
3805
				{
3806
					apc_clear_cache('user');
3807
					apc_clear_cache('system');
3808
				}
3809
				elseif ($type === 'user')
3810
					apc_clear_cache('user');
3811
			}
3812
			break;
3813
		case 'zend':
3814
			if (function_exists('zend_shm_cache_clear'))
3815
				zend_shm_cache_clear('SMF');
3816
			break;
3817
		case 'xcache':
3818
			if (function_exists('xcache_clear_cache'))
3819
			{
3820
				//
3821
				if ($type === '')
3822
				{
3823
					xcache_clear_cache(XC_TYPE_VAR, 0);
3824
					xcache_clear_cache(XC_TYPE_PHP, 0);
3825
				}
3826
				if ($type === 'user')
3827
					xcache_clear_cache(XC_TYPE_VAR, 0);
3828
				if ($type === 'data')
3829
					xcache_clear_cache(XC_TYPE_PHP, 0);
3830
			}
3831
			break;
3832
		default:
3833
			// No directory = no game.
3834
			if (!is_dir($cachedir))
3835
				return;
3836
3837
			// Remove the files in SMF's own disk cache, if any
3838
			$dh = opendir($cachedir);
3839 View Code Duplication
			while ($file = readdir($dh))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3840
			{
3841
				if ($file != '.' && $file != '..' && $file != 'index.php' && $file != '.htaccess' && (!$type || substr($file, 0, strlen($type)) == $type))
3842
					@unlink($cachedir . '/' . $file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3843
			}
3844
			closedir($dh);
3845
			break;
3846
	}
3847
3848
	// Invalidate cache, to be sure!
3849
	// ... as long as index.php can be modified, anyway.
3850
	if (empty($type))
3851
		@touch($cachedir . '/' . 'index.php');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3852
3853
	call_integration_hook('integrate_clean_cache');
3854
	clearstatcache();
3855
}
3856
3857
/**
3858
 * Sets up all of the top menu buttons
3859
 * Saves them in the cache if it is available and on
3860
 * Places the results in $context
3861
 *
3862
 */
3863
function setupMenuContext()
3864
{
3865
	global $context, $modSettings, $user_info, $txt, $scripturl, $sourcedir, $settings;
3866
3867
	// Set up the menu privileges.
3868
	$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts'));
3869
	$context['allow_admin'] = allowedTo(array('admin_forum', 'manage_boards', 'manage_permissions', 'moderate_forum', 'manage_membergroups', 'manage_bans', 'send_mail', 'edit_news', 'manage_attachments', 'manage_smileys'));
3870
3871
	$context['allow_memberlist'] = allowedTo('view_mlist');
3872
	$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
3873
	$context['allow_moderation_center'] = $context['user']['can_mod'];
3874
	$context['allow_pm'] = allowedTo('pm_read');
3875
3876
	$cacheTime = $modSettings['lastActive'] * 60;
3877
3878
	// Initial "can you post an event in the calendar" option - but this might have been set in the calendar already.
3879
	if (!isset($context['allow_calendar_event']))
3880
	{
3881
		$context['allow_calendar_event'] = $context['allow_calendar'] && allowedTo('calendar_post');
3882
3883
		// If you don't allow events not linked to posts and you're not an admin, we have more work to do...
3884 View Code Duplication
		if ($context['allow_calendar'] && $context['allow_calendar_event'] && empty($modSettings['cal_allow_unlinked']) && !$user_info['is_admin'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
3885
		{
3886
			$boards_can_post = boardsAllowedTo('post_new');
3887
			$context['allow_calendar_event'] &= !empty($boards_can_post);
3888
		}
3889
	}
3890
3891
	// There is some menu stuff we need to do if we're coming at this from a non-guest perspective.
3892
	if (!$context['user']['is_guest'])
3893
	{
3894
		addInlineJavascript('
3895
	var user_menus = new smc_PopupMenu();
3896
	user_menus.add("profile", "' . $scripturl . '?action=profile;area=popup");
3897
	user_menus.add("alerts", "' . $scripturl . '?action=profile;area=alerts_popup;u='. $context['user']['id'] .'");', true);
3898
		if ($context['allow_pm'])
3899
			addInlineJavascript('
3900
	user_menus.add("pm", "' . $scripturl . '?action=pm;sa=popup");', true);
3901
3902
		if (!empty($modSettings['enable_ajax_alerts']))
3903
		{
3904
			require_once($sourcedir . '/Subs-Notify.php');
3905
3906
			$timeout = getNotifyPrefs($context['user']['id'], 'alert_timeout', true);
3907
			$timeout = empty($timeout) ? 10000 : $timeout[$context['user']['id']]['alert_timeout'] * 1000;
3908
3909
			addInlineJavascript('
3910
	var new_alert_title = "' . $context['forum_name'] . '";
3911
	var alert_timeout = ' . $timeout . ';');
3912
			loadJavascriptFile('alerts.js', array(), 'smf_alerts');
3913
		}
3914
	}
3915
3916
	// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
3917
	if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
3918
	{
3919
		$buttons = array(
3920
			'home' => array(
3921
				'title' => $txt['home'],
3922
				'href' => $scripturl,
3923
				'show' => true,
3924
				'sub_buttons' => array(
3925
				),
3926
				'is_last' => $context['right_to_left'],
3927
			),
3928
			'search' => array(
3929
				'title' => $txt['search'],
3930
				'href' => $scripturl . '?action=search',
3931
				'show' => $context['allow_search'],
3932
				'sub_buttons' => array(
3933
				),
3934
			),
3935
			'admin' => array(
3936
				'title' => $txt['admin'],
3937
				'href' => $scripturl . '?action=admin',
3938
				'show' => $context['allow_admin'],
3939
				'sub_buttons' => array(
3940
					'featuresettings' => array(
3941
						'title' => $txt['modSettings_title'],
3942
						'href' => $scripturl . '?action=admin;area=featuresettings',
3943
						'show' => allowedTo('admin_forum'),
3944
					),
3945
					'packages' => array(
3946
						'title' => $txt['package'],
3947
						'href' => $scripturl . '?action=admin;area=packages',
3948
						'show' => allowedTo('admin_forum'),
3949
					),
3950
					'errorlog' => array(
3951
						'title' => $txt['errlog'],
3952
						'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
3953
						'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']),
3954
					),
3955
					'permissions' => array(
3956
						'title' => $txt['edit_permissions'],
3957
						'href' => $scripturl . '?action=admin;area=permissions',
3958
						'show' => allowedTo('manage_permissions'),
3959
					),
3960
					'memberapprove' => array(
3961
						'title' => $txt['approve_members_waiting'],
3962
						'href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve',
3963
						'show' => !empty($context['unapproved_members']),
3964
						'is_last' => true,
3965
					),
3966
				),
3967
			),
3968
			'moderate' => array(
3969
				'title' => $txt['moderate'],
3970
				'href' => $scripturl . '?action=moderate',
3971
				'show' => $context['allow_moderation_center'],
3972
				'sub_buttons' => array(
3973
					'modlog' => array(
3974
						'title' => $txt['modlog_view'],
3975
						'href' => $scripturl . '?action=moderate;area=modlog',
3976
						'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
3977
					),
3978
					'poststopics' => array(
3979
						'title' => $txt['mc_unapproved_poststopics'],
3980
						'href' => $scripturl . '?action=moderate;area=postmod;sa=posts',
3981
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
3982
					),
3983
					'attachments' => array(
3984
						'title' => $txt['mc_unapproved_attachments'],
3985
						'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments',
3986
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
3987
					),
3988
					'reports' => array(
3989
						'title' => $txt['mc_reported_posts'],
3990
						'href' => $scripturl . '?action=moderate;area=reportedposts',
3991
						'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
3992
					),
3993
					'reported_members' => array(
3994
						'title' => $txt['mc_reported_members'],
3995
						'href' => $scripturl . '?action=moderate;area=reportedmembers',
3996
						'show' => allowedTo('moderate_forum'),
3997
						'is_last' => true,
3998
					)
3999
				),
4000
			),
4001
			'calendar' => array(
4002
				'title' => $txt['calendar'],
4003
				'href' => $scripturl . '?action=calendar',
4004
				'show' => $context['allow_calendar'],
4005
				'sub_buttons' => array(
4006
					'view' => array(
4007
						'title' => $txt['calendar_menu'],
4008
						'href' => $scripturl . '?action=calendar',
4009
						'show' => $context['allow_calendar_event'],
4010
					),
4011
					'post' => array(
4012
						'title' => $txt['calendar_post_event'],
4013
						'href' => $scripturl . '?action=calendar;sa=post',
4014
						'show' => $context['allow_calendar_event'],
4015
						'is_last' => true,
4016
					),
4017
				),
4018
			),
4019
			'mlist' => array(
4020
				'title' => $txt['members_title'],
4021
				'href' => $scripturl . '?action=mlist',
4022
				'show' => $context['allow_memberlist'],
4023
				'sub_buttons' => array(
4024
					'mlist_view' => array(
4025
						'title' => $txt['mlist_menu_view'],
4026
						'href' => $scripturl . '?action=mlist',
4027
						'show' => true,
4028
					),
4029
					'mlist_search' => array(
4030
						'title' => $txt['mlist_search'],
4031
						'href' => $scripturl . '?action=mlist;sa=search',
4032
						'show' => true,
4033
						'is_last' => true,
4034
					),
4035
				),
4036
			),
4037
			'signup' => array(
4038
				'title' => $txt['register'],
4039
				'href' => $scripturl . '?action=signup',
4040
				'show' => $user_info['is_guest'] && $context['can_register'],
4041
				'sub_buttons' => array(
4042
				),
4043
				'is_last' => !$context['right_to_left'],
4044
			),
4045
			'logout' => array(
4046
				'title' => $txt['logout'],
4047
				'href' => $scripturl . '?action=logout;%1$s=%2$s',
4048
				'show' => !$user_info['is_guest'],
4049
				'sub_buttons' => array(
4050
				),
4051
				'is_last' => !$context['right_to_left'],
4052
			),
4053
		);
4054
4055
		// Allow editing menu buttons easily.
4056
		call_integration_hook('integrate_menu_buttons', array(&$buttons));
4057
4058
		// Now we put the buttons in the context so the theme can use them.
4059
		$menu_buttons = array();
4060
		foreach ($buttons as $act => $button)
4061
			if (!empty($button['show']))
4062
			{
4063
				$button['active_button'] = false;
4064
4065
				// This button needs some action.
4066
				if (isset($button['action_hook']))
4067
					$needs_action_hook = true;
4068
4069
				// Make sure the last button truly is the last button.
4070
				if (!empty($button['is_last']))
4071
				{
4072
					if (isset($last_button))
4073
						unset($menu_buttons[$last_button]['is_last']);
4074
					$last_button = $act;
4075
				}
4076
4077
				// Go through the sub buttons if there are any.
4078
				if (!empty($button['sub_buttons']))
4079
					foreach ($button['sub_buttons'] as $key => $subbutton)
4080
					{
4081
						if (empty($subbutton['show']))
4082
							unset($button['sub_buttons'][$key]);
4083
4084
						// 2nd level sub buttons next...
4085
						if (!empty($subbutton['sub_buttons']))
4086
						{
4087
							foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2)
4088
							{
4089
								if (empty($sub_button2['show']))
4090
									unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
4091
							}
4092
						}
4093
					}
4094
4095
				// Does this button have its own icon?
4096
				if (isset($button['icon']) && file_exists($settings['theme_dir'] . '/images/' . $button['icon']))
4097
					$button['icon'] = '<img src="' . $settings['images_url'] . '/' . $button['icon'] . '" alt="">';
4098
				elseif (isset($button['icon']) && file_exists($settings['default_theme_dir'] . '/images/' . $button['icon']))
4099
					$button['icon'] = '<img src="' . $settings['default_images_url'] . '/' . $button['icon'] . '" alt="">';
4100
				elseif (isset($button['icon']))
4101
					$button['icon'] = '<span class="generic_icons ' . $button['icon'] . '"></span>';
4102
				else
4103
					$button['icon'] = '<span class="generic_icons ' . $act . '"></span>';
4104
4105
				$menu_buttons[$act] = $button;
4106
			}
4107
4108
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
4109
			cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
4110
	}
4111
4112
	$context['menu_buttons'] = $menu_buttons;
4113
4114
	// Logging out requires the session id in the url.
4115
	if (isset($context['menu_buttons']['logout']))
4116
		$context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']);
4117
4118
	// Figure out which action we are doing so we can set the active tab.
4119
	// Default to home.
4120
	$current_action = 'home';
4121
4122
	if (isset($context['menu_buttons'][$context['current_action']]))
4123
		$current_action = $context['current_action'];
4124
	elseif ($context['current_action'] == 'search2')
4125
		$current_action = 'search';
4126
	elseif ($context['current_action'] == 'theme')
4127
		$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin';
4128
	elseif ($context['current_action'] == 'register2')
4129
		$current_action = 'register';
4130
	elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder'))
4131
		$current_action = 'login';
4132
	elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center'])
4133
		$current_action = 'moderate';
4134
4135
	// There are certain exceptions to the above where we don't want anything on the menu highlighted.
4136
	if ($context['current_action'] == 'profile' && !empty($context['user']['is_owner']))
4137
	{
4138
		$current_action = !empty($_GET['area']) && $_GET['area'] == 'showalerts' ? 'self_alerts' : 'self_profile';
4139
		$context[$current_action] = true;
4140
	}
4141
	elseif ($context['current_action'] == 'pm')
4142
	{
4143
		$current_action = 'self_pm';
4144
		$context['self_pm'] = true;
4145
	}
4146
4147
	$total_mod_reports = 0;
4148
4149
	if (!empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1' && !empty($context['open_mod_reports']))
4150
	{
4151
		$total_mod_reports = $context['open_mod_reports'];
4152
		$context['menu_buttons']['moderate']['sub_buttons']['reports']['title'] .= ' <span class="amt">' . $context['open_mod_reports'] . '</span>';
4153
	}
4154
4155
	// Show how many errors there are
4156 View Code Duplication
	if (!empty($context['num_errors']) && allowedTo('admin_forum'))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4157
	{
4158
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4159
		$context['menu_buttons']['admin']['sub_buttons']['errorlog']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4160
	}
4161
4162
	/**
4163
	 * @todo For some reason, $context['open_member_reports'] isn't getting set
4164
	 */
4165
	if (!empty($context['open_member_reports']) && allowedTo('moderate_forum'))
4166
	{
4167
		$total_mod_reports += $context['open_member_reports'];
4168
		$context['menu_buttons']['moderate']['sub_buttons']['reported_members']['title'] .= ' <span class="amt">' . $context['open_member_reports'] . '</span>';
4169
	}
4170
4171 View Code Duplication
	if (!empty($context['unapproved_members']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4172
	{
4173
		$context['menu_buttons']['admin']['sub_buttons']['memberapprove']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4174
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4175
	}
4176
4177
	// Do we have any open reports?
4178
	if ($total_mod_reports > 0)
4179
	{
4180
		$context['menu_buttons']['moderate']['title'] .= ' <span class="amt">' . $total_mod_reports . '</span>';
4181
	}
4182
4183
	// Not all actions are simple.
4184
	if (!empty($needs_action_hook))
4185
		call_integration_hook('integrate_current_action', array(&$current_action));
4186
4187
	if (isset($context['menu_buttons'][$current_action]))
4188
		$context['menu_buttons'][$current_action]['active_button'] = true;
4189
}
4190
4191
/**
4192
 * Generate a random seed and ensure it's stored in settings.
4193
 */
4194
function smf_seed_generator()
4195
{
4196
	updateSettings(array('rand_seed' => microtime() * 1000000));
4197
}
4198
4199
/**
4200
 * Process functions of an integration hook.
4201
 * calls all functions of the given hook.
4202
 * supports static class method calls.
4203
 *
4204
 * @param string $hook The hook name
4205
 * @param array $parameters An array of parameters this hook implements
4206
 * @return array The results of the functions
4207
 */
4208
function call_integration_hook($hook, $parameters = array())
4209
{
4210
	global $modSettings, $settings, $boarddir, $sourcedir, $db_show_debug;
4211
	global $context, $txt;
4212
4213
	if ($db_show_debug === true)
4214
		$context['debug']['hooks'][] = $hook;
4215
4216
	// Need to have some control.
4217
	if (!isset($context['instances']))
4218
		$context['instances'] = array();
4219
4220
	$results = array();
4221
	if (empty($modSettings[$hook]))
4222
		return $results;
4223
4224
	// Define some needed vars.
4225
	$function = false;
4226
4227
	$functions = explode(',', $modSettings[$hook]);
4228
	// Loop through each function.
4229
	foreach ($functions as $function)
4230
	{
4231
		// Hook has been marked as "disabled". Skip it!
4232
		if (strpos($function, '!') !== false)
4233
			continue;
4234
4235
		$call = call_helper($function, true);
4236
4237
		// Is it valid?
4238
		if (!empty($call))
4239
			$results[$function] = call_user_func_array($call, $parameters);
4240
4241
		// Whatever it was suppose to call, it failed :(
4242
		elseif (!empty($function))
4243
		{
4244
			loadLanguage('Errors');
4245
4246
			// Get a full path to show on error.
4247
			if (strpos($function, '|') !== false)
4248
			{
4249
				list ($file, $string) = explode('|', $function);
4250
				$absPath = empty($settings['theme_dir']) ? (strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir))) : (strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir'])));
4251
				log_error(sprintf($txt['hook_fail_call_to'], $string, $absPath), 'general');
4252
			}
4253
4254
			// "Assume" the file resides on $boarddir somewhere...
4255
			else
4256
				log_error(sprintf($txt['hook_fail_call_to'], $function, $boarddir), 'general');
4257
		}
4258
	}
4259
4260
	return $results;
4261
}
4262
4263
/**
4264
 * Add a function for integration hook.
4265
 * does nothing if the function is already added.
4266
 *
4267
 * @param string $hook The complete hook name.
4268
 * @param string $function The function name. Can be a call to a method via Class::method.
4269
 * @param bool $permanent If true, updates the value in settings table.
4270
 * @param string $file The file. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4271
 * @param bool $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4272
 */
4273
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
4274
{
4275
	global $smcFunc, $modSettings;
4276
4277
	// Any objects?
4278
	if ($object)
4279
		$function = $function . '#';
4280
4281
	// Any files  to load?
4282
	if (!empty($file) && is_string($file))
4283
		$function = $file . '|' . $function;
4284
4285
	// Get the correct string.
4286
	$integration_call = $function;
4287
4288
	// Is it going to be permanent?
4289
	if ($permanent)
4290
	{
4291
		$request = $smcFunc['db_query']('', '
4292
			SELECT value
4293
			FROM {db_prefix}settings
4294
			WHERE variable = {string:variable}',
4295
			array(
4296
				'variable' => $hook,
4297
			)
4298
		);
4299
		list ($current_functions) = $smcFunc['db_fetch_row']($request);
4300
		$smcFunc['db_free_result']($request);
4301
4302
		if (!empty($current_functions))
4303
		{
4304
			$current_functions = explode(',', $current_functions);
4305
			if (in_array($integration_call, $current_functions))
4306
				return;
4307
4308
			$permanent_functions = array_merge($current_functions, array($integration_call));
4309
		}
4310
		else
4311
			$permanent_functions = array($integration_call);
4312
4313
		updateSettings(array($hook => implode(',', $permanent_functions)));
4314
	}
4315
4316
	// Make current function list usable.
4317
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4318
4319
	// Do nothing, if it's already there.
4320
	if (in_array($integration_call, $functions))
4321
		return;
4322
4323
	$functions[] = $integration_call;
4324
	$modSettings[$hook] = implode(',', $functions);
4325
}
4326
4327
/**
4328
 * Remove an integration hook function.
4329
 * Removes the given function from the given hook.
4330
 * Does nothing if the function is not available.
4331
 *
4332
 * @param string $hook The complete hook name.
4333
 * @param string $function The function name. Can be a call to a method via Class::method.
4334
 * @params boolean $permanent Irrelevant for the function itself but need to declare it to match
4335
 * @param string $file The filename. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4336
add_integration_function
4337
 * @param boolean $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4338
 * @see add_integration_function
4339
 */
4340
function remove_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
0 ignored issues
show
Unused Code introduced by
The parameter $permanent is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
4341
{
4342
	global $smcFunc, $modSettings;
4343
4344
	// Any objects?
4345
	if ($object)
4346
		$function = $function . '#';
4347
4348
	// Any files  to load?
4349
	if (!empty($file) && is_string($file))
4350
		$function = $file . '|' . $function;
4351
4352
	// Get the correct string.
4353
	$integration_call = $function;
4354
4355
	// Get the permanent functions.
4356
	$request = $smcFunc['db_query']('', '
4357
		SELECT value
4358
		FROM {db_prefix}settings
4359
		WHERE variable = {string:variable}',
4360
		array(
4361
			'variable' => $hook,
4362
		)
4363
	);
4364
	list ($current_functions) = $smcFunc['db_fetch_row']($request);
4365
	$smcFunc['db_free_result']($request);
4366
4367
	if (!empty($current_functions))
4368
	{
4369
		$current_functions = explode(',', $current_functions);
4370
4371
		if (in_array($integration_call, $current_functions))
4372
			updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call)))));
4373
	}
4374
4375
	// Turn the function list into something usable.
4376
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4377
4378
	// You can only remove it if it's available.
4379
	if (!in_array($integration_call, $functions))
4380
		return;
4381
4382
	$functions = array_diff($functions, array($integration_call));
4383
	$modSettings[$hook] = implode(',', $functions);
4384
}
4385
4386
/**
4387
 * Receives a string and tries to figure it out if its a method or a function.
4388
 * If a method is found, it looks for a "#" which indicates SMF should create a new instance of the given class.
4389
 * Checks the string/array for is_callable() and return false/fatal_lang_error is the given value results in a non callable string/array.
4390
 * Prepare and returns a callable depending on the type of method/function found.
4391
 *
4392
 * @param mixed $string The string containing a function name or a static call. The function can also accept a closure, object or a callable array (object/class, valid_callable)
4393
 * @param boolean $return If true, the function will not call the function/method but instead will return the formatted string.
4394
 * @return string|array|boolean Either a string or an array that contains a callable function name or an array with a class and method to call. Boolean false if the given string cannot produce a callable var.
4395
 */
4396
function call_helper($string, $return = false)
4397
{
4398
	global $context, $smcFunc, $txt, $db_show_debug;
4399
4400
	// Really?
4401
	if (empty($string))
4402
		return false;
4403
4404
	// An array? should be a "callable" array IE array(object/class, valid_callable).
4405
	// A closure? should be a callable one.
4406
	if (is_array($string) || $string instanceof Closure)
4407
		return $return ? $string : (is_callable($string) ? call_user_func($string) : false);
4408
4409
	// No full objects, sorry! pass a method or a property instead!
4410
	if (is_object($string))
4411
		return false;
4412
4413
	// Stay vitaminized my friends...
4414
	$string = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($string));
4415
4416
	// The soon to be populated var.
4417
	$func = false;
0 ignored issues
show
Unused Code introduced by
$func is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
4418
4419
	// Is there a file to load?
4420
	$string = load_file($string);
4421
4422
	// Loaded file failed
4423
	if (empty($string))
4424
		return false;
4425
4426
	// Found a method.
4427
	if (strpos($string, '::') !== false)
4428
	{
4429
		list ($class, $method) = explode('::', $string);
4430
4431
		// Check if a new object will be created.
4432
		if (strpos($method, '#') !== false)
4433
		{
4434
			// Need to remove the # thing.
4435
			$method = str_replace('#', '', $method);
4436
4437
			// Don't need to create a new instance for every method.
4438
			if (empty($context['instances'][$class]) || !($context['instances'][$class] instanceof $class))
4439
			{
4440
				$context['instances'][$class] = new $class;
4441
4442
				// Add another one to the list.
4443
				if ($db_show_debug === true)
4444
				{
4445
					if (!isset($context['debug']['instances']))
4446
						$context['debug']['instances'] = array();
4447
4448
					$context['debug']['instances'][$class] = $class;
4449
				}
4450
			}
4451
4452
			$func = array($context['instances'][$class], $method);
4453
		}
4454
4455
		// Right then. This is a call to a static method.
4456
		else
4457
			$func = array($class, $method);
4458
	}
4459
4460
	// Nope! just a plain regular function.
4461
	else
4462
		$func = $string;
4463
4464
	// Right, we got what we need, time to do some checks.
4465
	if (!is_callable($func, false, $callable_name))
4466
	{
4467
		loadLanguage('Errors');
4468
		log_error(sprintf($txt['subAction_fail'], $callable_name), 'general');
4469
4470
		// Gotta tell everybody.
4471
		return false;
4472
	}
4473
4474
	// Everything went better than expected.
4475
	else
4476
	{
4477
		// What are we gonna do about it?
4478
		if ($return)
4479
			return $func;
4480
4481
		// If this is a plain function, avoid the heat of calling call_user_func().
4482
		else
4483
		{
4484
			if (is_array($func))
4485
				call_user_func($func);
4486
4487
			else
4488
				$func();
4489
		}
4490
	}
4491
}
4492
4493
/**
4494
 * Receives a string and tries to figure it out if it contains info to load a file.
4495
 * Checks for a | (pipe) symbol and tries to load a file with the info given.
4496
 * The string should be format as follows File.php|. You can use the following wildcards: $boarddir, $sourcedir and if available at the moment of execution, $themedir.
4497
 *
4498
 * @param string $string The string containing a valid format.
4499
 * @return string|boolean The given string with the pipe and file info removed. Boolean false if the file couldn't be loaded.
4500
 */
4501
function load_file($string)
4502
{
4503
	global $sourcedir, $txt, $boarddir, $settings;
4504
4505
	if (empty($string))
4506
		return false;
4507
4508
	if (strpos($string, '|') !== false)
4509
	{
4510
		list ($file, $string) = explode('|', $string);
4511
4512
		// Match the wildcards to their regular vars.
4513
		if (empty($settings['theme_dir']))
4514
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
4515
4516
		else
4517
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
4518
4519
		// Load the file if it can be loaded.
4520
		if (file_exists($absPath))
4521
			require_once($absPath);
4522
4523
		// No? try a fallback to $sourcedir
4524
		else
4525
		{
4526
			$absPath = $sourcedir .'/'. $file;
4527
4528
			if (file_exists($absPath))
4529
				require_once($absPath);
4530
4531
			// Sorry, can't do much for you at this point.
4532
			else
4533
			{
4534
				loadLanguage('Errors');
4535
				log_error(sprintf($txt['hook_fail_loading_file'], $absPath), 'general');
4536
4537
				// File couldn't be loaded.
4538
				return false;
4539
			}
4540
		}
4541
	}
4542
4543
	return $string;
4544
}
4545
4546
/**
4547
 * Prepares an array of "likes" info for the topic specified by $topic
4548
 * @param integer $topic The topic ID to fetch the info from.
4549
 * @return array An array of IDs of messages in the specified topic that the current user likes
4550
 */
4551
function prepareLikesContext($topic)
4552
{
4553
	global $user_info, $smcFunc;
4554
4555
	// Make sure we have something to work with.
4556
	if (empty($topic))
4557
		return array();
4558
4559
4560
	// We already know the number of likes per message, we just want to know whether the current user liked it or not.
4561
	$user = $user_info['id'];
4562
	$cache_key = 'likes_topic_' . $topic . '_' . $user;
4563
	$ttl = 180;
4564
4565
	if (($temp = cache_get_data($cache_key, $ttl)) === null)
4566
	{
4567
		$temp = array();
4568
		$request = $smcFunc['db_query']('', '
4569
			SELECT content_id
4570
			FROM {db_prefix}user_likes AS l
4571
				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
4572
			WHERE l.id_member = {int:current_user}
4573
				AND l.content_type = {literal:msg}
4574
				AND m.id_topic = {int:topic}',
4575
			array(
4576
				'current_user' => $user,
4577
				'topic' => $topic,
4578
			)
4579
		);
4580
		while ($row = $smcFunc['db_fetch_assoc']($request))
4581
			$temp[] = (int) $row['content_id'];
4582
4583
		cache_put_data($cache_key, $temp, $ttl);
4584
	}
4585
4586
	return $temp;
4587
}
4588
4589
/**
4590
 * Microsoft uses their own character set Code Page 1252 (CP1252), which is a
4591
 * superset of ISO 8859-1, defining several characters between DEC 128 and 159
4592
 * that are not normally displayable.  This converts the popular ones that
4593
 * appear from a cut and paste from windows.
4594
 *
4595
 * @param string $string The string
4596
 * @return string The sanitized string
4597
 */
4598
function sanitizeMSCutPaste($string)
4599
{
4600
	global $context;
4601
4602
	if (empty($string))
4603
		return $string;
4604
4605
	// UTF-8 occurences of MS special characters
4606
	$findchars_utf8 = array(
4607
		"\xe2\80\x9a",	// single low-9 quotation mark
4608
		"\xe2\80\x9e",	// double low-9 quotation mark
4609
		"\xe2\80\xa6",	// horizontal ellipsis
4610
		"\xe2\x80\x98",	// left single curly quote
4611
		"\xe2\x80\x99",	// right single curly quote
4612
		"\xe2\x80\x9c",	// left double curly quote
4613
		"\xe2\x80\x9d",	// right double curly quote
4614
		"\xe2\x80\x93",	// en dash
4615
		"\xe2\x80\x94",	// em dash
4616
	);
4617
4618
	// windows 1252 / iso equivalents
4619
	$findchars_iso = array(
4620
		chr(130),
4621
		chr(132),
4622
		chr(133),
4623
		chr(145),
4624
		chr(146),
4625
		chr(147),
4626
		chr(148),
4627
		chr(150),
4628
		chr(151),
4629
	);
4630
4631
	// safe replacements
4632
	$replacechars = array(
4633
		',',	// &sbquo;
4634
		',,',	// &bdquo;
4635
		'...',	// &hellip;
4636
		"'",	// &lsquo;
4637
		"'",	// &rsquo;
4638
		'"',	// &ldquo;
4639
		'"',	// &rdquo;
4640
		'-',	// &ndash;
4641
		'--',	// &mdash;
4642
	);
4643
4644
	if ($context['utf8'])
4645
		$string = str_replace($findchars_utf8, $replacechars, $string);
4646
	else
4647
		$string = str_replace($findchars_iso, $replacechars, $string);
4648
4649
	return $string;
4650
}
4651
4652
/**
4653
 * Decode numeric html entities to their ascii or UTF8 equivalent character.
4654
 *
4655
 * Callback function for preg_replace_callback in subs-members
4656
 * Uses capture group 2 in the supplied array
4657
 * Does basic scan to ensure characters are inside a valid range
4658
 *
4659
 * @param array $matches An array of matches (relevant info should be the 3rd item)
4660
 * @return string A fixed string
4661
 */
4662
function replaceEntities__callback($matches)
4663
{
4664
	global $context;
4665
4666
	if (!isset($matches[2]))
4667
		return '';
4668
4669
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4670
4671
	// remove left to right / right to left overrides
4672
	if ($num === 0x202D || $num === 0x202E)
4673
		return '';
4674
4675
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
4676
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
4677
		return '&#' . $num . ';';
4678
4679
	if (empty($context['utf8']))
4680
	{
4681
		// no control characters
4682
		if ($num < 0x20)
4683
			return '';
4684
		// text is text
4685
		elseif ($num < 0x80)
4686
			return chr($num);
4687
		// all others get html-ised
4688
		else
4689
			return '&#' . $matches[2] . ';';
4690
	}
4691
	else
4692
	{
4693
		// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
4694
		// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
4695
		if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
4696
			return '';
4697
		// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4698
		elseif ($num < 0x80)
4699
			return chr($num);
4700
		// <0x800 (2048)
4701 View Code Duplication
		elseif ($num < 0x800)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4702
			return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4703
		// < 0x10000 (65536)
4704 View Code Duplication
		elseif ($num < 0x10000)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4705
			return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4706
		// <= 0x10FFFF (1114111)
4707 View Code Duplication
		else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4708
			return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4709
	}
4710
}
4711
4712
/**
4713
 * Converts html entities to utf8 equivalents
4714
 *
4715
 * Callback function for preg_replace_callback
4716
 * Uses capture group 1 in the supplied array
4717
 * Does basic checks to keep characters inside a viewable range.
4718
 *
4719
 * @param array $matches An array of matches (relevant info should be the 2nd item in the array)
4720
 * @return string The fixed string
4721
 */
4722
function fixchar__callback($matches)
4723
{
4724
	if (!isset($matches[1]))
4725
		return '';
4726
4727
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
4728
4729
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
4730
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
4731
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
4732
		return '';
4733
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and puncuation
4734
	elseif ($num < 0x80)
4735
		return chr($num);
4736
	// <0x800 (2048)
4737 View Code Duplication
	elseif ($num < 0x800)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4738
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4739
	// < 0x10000 (65536)
4740 View Code Duplication
	elseif ($num < 0x10000)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4741
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4742
	// <= 0x10FFFF (1114111)
4743 View Code Duplication
	else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4744
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4745
}
4746
4747
/**
4748
 * Strips out invalid html entities, replaces others with html style &#123; codes
4749
 *
4750
 * Callback function used of preg_replace_callback in smcFunc $ent_checks, for example
4751
 * strpos, strlen, substr etc
4752
 *
4753
 * @param array $matches An array of matches (relevant info should be the 3rd item in the array)
4754
 * @return string The fixed string
4755
 */
4756
function entity_fix__callback($matches)
4757
{
4758
	if (!isset($matches[2]))
4759
		return '';
4760
4761
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4762
4763
	// we don't allow control characters, characters out of range, byte markers, etc
4764
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
4765
		return '';
4766
	else
4767
		return '&#' . $num . ';';
4768
}
4769
4770
/**
4771
 * Return a Gravatar URL based on
4772
 * - the supplied email address,
4773
 * - the global maximum rating,
4774
 * - the global default fallback,
4775
 * - maximum sizes as set in the admin panel.
4776
 *
4777
 * It is SSL aware, and caches most of the parameters.
4778
 *
4779
 * @param string $email_address The user's email address
4780
 * @return string The gravatar URL
4781
 */
4782
function get_gravatar_url($email_address)
4783
{
4784
	global $modSettings, $smcFunc;
4785
	static $url_params = null;
4786
4787
	if ($url_params === null)
4788
	{
4789
		$ratings = array('G', 'PG', 'R', 'X');
4790
		$defaults = array('mm', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank');
4791
		$url_params = array();
4792 View Code Duplication
		if (!empty($modSettings['gravatarMaxRating']) && in_array($modSettings['gravatarMaxRating'], $ratings))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4793
			$url_params[] = 'rating=' . $modSettings['gravatarMaxRating'];
4794 View Code Duplication
		if (!empty($modSettings['gravatarDefault']) && in_array($modSettings['gravatarDefault'], $defaults))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
4795
			$url_params[] = 'default=' . $modSettings['gravatarDefault'];
4796
		if (!empty($modSettings['avatar_max_width_external']))
4797
			$size_string = (int) $modSettings['avatar_max_width_external'];
4798
		if (!empty($modSettings['avatar_max_height_external']) && !empty($size_string))
4799
			if ((int) $modSettings['avatar_max_height_external'] < $size_string)
4800
				$size_string = $modSettings['avatar_max_height_external'];
4801
4802
		if (!empty($size_string))
4803
			$url_params[] = 's=' . $size_string;
4804
	}
4805
	$http_method = !empty($modSettings['force_ssl']) && $modSettings['force_ssl'] == 2 ? 'https://secure' : 'http://www';
4806
4807
	return $http_method . '.gravatar.com/avatar/' . md5($smcFunc['strtolower']($email_address)) . '?' . implode('&', $url_params);
4808
}
4809
4810
/**
4811
 * Get a list of timezoned.
4812
 *
4813
 * @return array An array of timezone info.
4814
 */
4815
function smf_list_timezones()
4816
{
4817
	return array(
4818
		'' => '(Forum Default)',
4819
		'UTC' => '[UTC] UTC',
4820
		'Pacific/Midway' => '[UTC-11:00] Midway Island, Samoa',
4821
		'America/Adak' => '[UTC-10:00] Hawaii-Aleutian',
4822
		'Pacific/Honolulu' => '[UTC-10:00] Hawaii',
4823
		'Pacific/Marquesas' => '[UTC-09:30] Marquesas Islands',
4824
		'Pacific/Gambier' => '[UTC-09:00] Gambier Islands',
4825
		'America/Anchorage' => '[UTC-09:00] Alaska',
4826
		'America/Ensenada' => '[UTC-08:00] Tijuana, Baja California',
4827
		'Pacific/Pitcairn' => '[UTC-08:00] Pitcairn Islands',
4828
		'America/Los_Angeles' => '[UTC-08:00] Pacific Time (USA, Canada)',
4829
		'America/Denver' => '[UTC-07:00] Mountain Time (USA, Canada)',
4830
		'America/Phoenix' => '[UTC-07:00] Arizona',
4831
		'America/Chihuahua' => '[UTC-07:00] Chihuahua, Mazatlan',
4832
		'America/Belize' => '[UTC-06:00] Saskatchewan, Central America',
4833
		'America/Cancun' => '[UTC-06:00] Guadalajara, Mexico City, Monterrey',
4834
		'Chile/EasterIsland' => '[UTC-06:00] Easter Island',
4835
		'America/Chicago' => '[UTC-06:00] Central Time (USA, Canada)',
4836
		'America/New_York' => '[UTC-05:00] Eastern Time (USA, Canada)',
4837
		'America/Havana' => '[UTC-05:00] Cuba',
4838
		'America/Bogota' => '[UTC-05:00] Bogota, Lima, Quito',
4839
		'America/Caracas' => '[UTC-04:30] Caracas',
4840
		'America/Santiago' => '[UTC-04:00] Santiago',
4841
		'America/La_Paz' => '[UTC-04:00] La Paz, San Juan, Manaus',
4842
		'Atlantic/Stanley' => '[UTC-04:00] Falkland Islands',
4843
		'America/Cuiaba' => '[UTC-04:00] Cuiaba',
4844
		'America/Goose_Bay' => '[UTC-04:00] Atlantic Time (Goose Bay)',
4845
		'America/Glace_Bay' => '[UTC-04:00] Atlantic Time (Canada)',
4846
		'America/St_Johns' => '[UTC-03:30] Newfoundland',
4847
		'America/Araguaina' => '[UTC-03:00] Araguaina',
4848
		'America/Montevideo' => '[UTC-03:00] Montevideo',
4849
		'America/Miquelon' => '[UTC-03:00] Saint Pierre and Miquelon',
4850
		'America/Argentina/Buenos_Aires' => '[UTC-03:00] Buenos Aires',
4851
		'America/Sao_Paulo' => '[UTC-03:00] Brasilia',
4852
		'America/Godthab' => '[UTC-02:00] Greenland',
4853
		'America/Noronha' => '[UTC-02:00] Fernando de Noronha',
4854
		'Atlantic/Cape_Verde' => '[UTC-01:00] Cape Verde',
4855
		'Atlantic/Azores' => '[UTC-01:00] Azores',
4856
		'Africa/Abidjan' => '[UTC] Monrovia, Reykjavik',
4857
		'Europe/London' => '[UTC] London, Edinburgh, Dublin, Lisbon (Greenwich Mean Time)',
4858
		'Europe/Brussels' => '[UTC+01:00] Central European Time',
4859
		'Africa/Algiers' => '[UTC+01:00] West Central Africa',
4860
		'Africa/Windhoek' => '[UTC+01:00] Windhoek',
4861
		'Africa/Cairo' => '[UTC+02:00] Cairo',
4862
		'Africa/Blantyre' => '[UTC+02:00] Harare, Maputo, Pretoria',
4863
		'Asia/Jerusalem' => '[UTC+02:00] Jerusalem',
4864
		'Europe/Minsk' => '[UTC+02:00] Minsk',
4865
		'Asia/Damascus' => '[UTC+02:00] Damascus, Nicosia, Gaza, Beirut',
4866
		'Africa/Addis_Ababa' => '[UTC+03:00] Addis Ababa, Nairobi',
4867
		'Asia/Tehran' => '[UTC+03:30] Tehran',
4868
		'Europe/Moscow' => '[UTC+04:00] Moscow, St. Petersburg, Volgograd',
4869
		'Asia/Dubai' => '[UTC+04:00] Abu Dhabi, Muscat',
4870
		'Asia/Baku' => '[UTC+04:00] Baku',
4871
		'Asia/Yerevan' => '[UTC+04:00] Yerevan',
4872
		'Asia/Kabul' => '[UTC+04:30] Kabul',
4873
		'Asia/Tashkent' => '[UTC+05:00] Tashkent',
4874
		'Asia/Kolkata' => '[UTC+05:30] Chennai, Kolkata, Mumbai, New Delhi',
4875
		'Asia/Katmandu' => '[UTC+05:45] Kathmandu',
4876
		'Asia/Yekaterinburg' => '[UTC+06:00] Yekaterinburg, Tyumen',
4877
		'Asia/Dhaka' => '[UTC+06:00] Astana, Thimphu, Dhaka',
4878
		'Asia/Novosibirsk' => '[UTC+06:00] Omsk, Novosibirsk',
4879
		'Asia/Rangoon' => '[UTC+06:30] Yangon Rangoon',
4880
		'Asia/Bangkok' => '[UTC+07:00] Bangkok, Hanoi, Jakarta',
4881
		'Asia/Krasnoyarsk' => '[UTC+08:00] Krasnoyarsk',
4882
		'Asia/Hong_Kong' => '[UTC+08:00] Beijing, Chongqing, Hong Kong, Urumqi',
4883
		'Asia/Ulaanbaatar' => '[UTC+08:00] Ulaan Bataar',
4884
		'Asia/Irkutsk' => '[UTC+09:00] Irkutsk',
4885
		'Australia/Perth' => '[UTC+08:00] Perth',
4886
		'Australia/Eucla' => '[UTC+08:45] Eucla',
4887
		'Asia/Tokyo' => '[UTC+09:00] Tokyo, Osaka, Sapporo',
4888
		'Asia/Seoul' => '[UTC+09:00] Seoul',
4889
		'Australia/Adelaide' => '[UTC+09:30] Adelaide',
4890
		'Australia/Darwin' => '[UTC+09:30] Darwin',
4891
		'Australia/Brisbane' => '[UTC+10:00] Brisbane, Guam',
4892
		'Australia/Sydney' => '[UTC+10:00] Sydney, Hobart',
4893
		'Asia/Yakutsk' => '[UTC+10:00] Yakutsk',
4894
		'Australia/Lord_Howe' => '[UTC+10:30] Lord Howe Island',
4895
		'Asia/Vladivostok' => '[UTC+11:00] Vladivostok',
4896
		'Pacific/Noumea' => '[UTC+11:00] Solomon Islands, New Caledonia',
4897
		'Pacific/Norfolk' => '[UTC+11:30] Norfolk Island',
4898
		'Pacific/Auckland' => '[UTC+12:00] Auckland, Wellington',
4899
		'Asia/Magadan' => '[UTC+12:00] Magadan, Kamchatka, Anadyr',
4900
		'Pacific/Fiji' => '[UTC+12:00] Fiji',
4901
		'Pacific/Majuro' => '[UTC+12:00] Marshall Islands',
4902
		'Pacific/Chatham' => '[UTC+12:45] Chatham Islands',
4903
		'Pacific/Tongatapu' => '[UTC+13:00] Nuku\'alofa',
4904
		'Pacific/Kiritimati' => '[UTC+14:00] Kiritimati',
4905
	);
4906
}
4907
4908
/**
4909
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
4910
 * @return binary The IP address in binary or false
4911
 */
4912
function inet_ptod($ip_address)
4913
{
4914
	if (!isValidIP($ip_address))
4915
		return $ip_address;
4916
4917
	$bin = inet_pton($ip_address);
4918
	return $bin;
4919
}
4920
4921
/**
4922
 * @param binary $bin An IP address in IPv4, IPv6
4923
 * @return string The IP address in presentation format or false on error
4924
 */
4925
function inet_dtop($bin)
4926
{
4927
	if(empty($bin))
4928
		return '';
4929
4930
	if(strpos($bin,'.')!==false || strpos($bin,':')!==false)
4931
		return $bin;
4932
4933
	$ip_address = inet_ntop($bin);
4934
4935
	return $ip_address;
4936
}
4937
4938
/**
4939
 * Safe serialize() and unserialize() replacements
4940
 *
4941
 * @license Public Domain
4942
 *
4943
 * @author anthon (dot) pang (at) gmail (dot) com
4944
 */
4945
4946
/*
4947
 * Arbitrary limits for safe_unserialize()
4948
 */
4949
define('MAX_SERIALIZED_INPUT_LENGTH', 4096);
4950
define('MAX_SERIALIZED_ARRAY_LENGTH', 256);
4951
define('MAX_SERIALIZED_ARRAY_DEPTH', 3);
4952
4953
/**
4954
 * Safe serialize() replacement. Recursive
4955
 * - output a strict subset of PHP's native serialized representation
4956
 * - does not serialize objects
4957
 *
4958
 * @param mixed $value
4959
 * @return string
4960
 */
4961
function _safe_serialize($value)
4962
{
4963
	if(is_null($value))
4964
		return 'N;';
4965
4966
	if(is_bool($value))
4967
		return 'b:'. (int) $value .';';
4968
4969
	if(is_int($value))
4970
		return 'i:'. $value .';';
4971
4972
	if(is_float($value))
4973
		return 'd:'. str_replace(',', '.', $value) .';';
4974
4975
	if(is_string($value))
4976
		return 's:'. strlen($value) .':"'. $value .'";';
4977
4978
	if(is_array($value))
4979
	{
4980
		$out = '';
4981
		foreach($value as $k => $v)
4982
			$out .= _safe_serialize($k) . _safe_serialize($v);
4983
4984
		return 'a:'. count($value) .':{'. $out .'}';
4985
	}
4986
4987
	// safe_serialize cannot serialize resources or objects.
4988
	return false;
4989
}
4990
/**
4991
 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issues.
4992
 *
4993
 * @param mixed $value
4994
 * @return string
4995
 */
4996 View Code Duplication
function safe_serialize($value)
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...
4997
{
4998
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
4999
	if (function_exists('mb_internal_encoding') &&
5000
		(((int) ini_get('mbstring.func_overload')) & 2))
5001
	{
5002
		$mbIntEnc = mb_internal_encoding();
5003
		mb_internal_encoding('ASCII');
5004
	}
5005
5006
	$out = _safe_serialize($value);
5007
5008
	if (isset($mbIntEnc))
5009
		mb_internal_encoding($mbIntEnc);
5010
5011
	return $out;
5012
}
5013
5014
/**
5015
 * Safe unserialize() replacement
5016
 * - accepts a strict subset of PHP's native serialized representation
5017
 * - does not unserialize objects
5018
 *
5019
 * @param string $str
5020
 * @return mixed
5021
 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
5022
 */
5023
function _safe_unserialize($str)
5024
{
5025
	// Input exceeds MAX_SERIALIZED_INPUT_LENGTH.
5026
	if(strlen($str) > MAX_SERIALIZED_INPUT_LENGTH)
5027
		return false;
5028
5029
	// Input  is not a string.
5030
	if(empty($str) || !is_string($str))
5031
		return false;
5032
5033
	$stack = array();
5034
	$expected = array();
5035
5036
	/*
5037
	 * states:
5038
	 *   0 - initial state, expecting a single value or array
5039
	 *   1 - terminal state
5040
	 *   2 - in array, expecting end of array or a key
5041
	 *   3 - in array, expecting value or another array
5042
	 */
5043
	$state = 0;
5044
	while($state != 1)
5045
	{
5046
		$type = isset($str[0]) ? $str[0] : '';
5047
		if($type == '}')
5048
			$str = substr($str, 1);
5049
5050
		else if($type == 'N' && $str[1] == ';')
5051
		{
5052
			$value = null;
5053
			$str = substr($str, 2);
5054
		}
5055
		else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
5056
		{
5057
			$value = $matches[1] == '1' ? true : false;
5058
			$str = substr($str, 4);
5059
		}
5060
		else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
5061
		{
5062
			$value = (int)$matches[1];
5063
			$str = $matches[2];
5064
		}
5065
		else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
5066
		{
5067
			$value = (float)$matches[1];
5068
			$str = $matches[3];
5069
		}
5070
		else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
5071
		{
5072
			$value = substr($matches[2], 0, (int)$matches[1]);
5073
			$str = substr($matches[2], (int)$matches[1] + 2);
5074
		}
5075
		else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches) && $matches[1] < MAX_SERIALIZED_ARRAY_LENGTH)
5076
		{
5077
			$expectedLength = (int)$matches[1];
5078
			$str = $matches[2];
5079
		}
5080
5081
		// Object or unknown/malformed type.
5082
		else
5083
			return false;
5084
5085
		switch($state)
5086
		{
5087
			case 3: // In array, expecting value or another array.
5088
				if($type == 'a')
5089
				{
5090
					// Array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH.
5091
					if(count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
5092
						return false;
5093
5094
					$stack[] = &$list;
0 ignored issues
show
Bug introduced by
The variable $list 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...
5095
					$list[$key] = array();
0 ignored issues
show
Bug introduced by
The variable $key 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...
5096
					$list = &$list[$key];
5097
					$expected[] = $expectedLength;
0 ignored issues
show
Bug introduced by
The variable $expectedLength 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...
5098
					$state = 2;
5099
					break;
5100
				}
5101
				if($type != '}')
5102
				{
5103
					$list[$key] = $value;
0 ignored issues
show
Bug introduced by
The variable $value 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...
5104
					$state = 2;
5105
					break;
5106
				}
5107
5108
				// Missing array value.
5109
				return false;
5110
5111
			case 2: // in array, expecting end of array or a key
5112
				if($type == '}')
5113
				{
5114
					// Array size is less than expected.
5115
					if(count($list) < end($expected))
5116
						return false;
5117
5118
					unset($list);
5119
					$list = &$stack[count($stack)-1];
5120
					array_pop($stack);
5121
5122
					// Go to terminal state if we're at the end of the root array.
5123
					array_pop($expected);
5124
5125
					if(count($expected) == 0)
5126
						$state = 1;
5127
5128
					break;
5129
				}
5130
5131
				if($type == 'i' || $type == 's')
5132
				{
5133
					// Array size exceeds MAX_SERIALIZED_ARRAY_LENGTH.
5134
					if(count($list) >= MAX_SERIALIZED_ARRAY_LENGTH)
5135
						return false;
5136
5137
					// Array size exceeds expected length.
5138
					if(count($list) >= end($expected))
5139
						return false;
5140
5141
					$key = $value;
5142
					$state = 3;
5143
					break;
5144
				}
5145
5146
				// Illegal array index type.
5147
				return false;
5148
5149
			// Expecting array or value.
5150
			case 0:
5151
				if($type == 'a')
5152
				{
5153
					// Array nesting exceeds MAX_SERIALIZED_ARRAY_DEPTH.
5154
					if(count($stack) >= MAX_SERIALIZED_ARRAY_DEPTH)
5155
						return false;
5156
5157
					$data = array();
5158
					$list = &$data;
5159
					$expected[] = $expectedLength;
5160
					$state = 2;
5161
					break;
5162
				}
5163
5164
				if($type != '}')
5165
				{
5166
					$data = $value;
5167
					$state = 1;
5168
					break;
5169
				}
5170
5171
				// Not in array.
5172
				return false;
5173
		}
5174
	}
5175
5176
	// Trailing data in input.
5177
	if(!empty($str))
5178
		return false;
5179
5180
	return $data;
0 ignored issues
show
Bug introduced by
The variable $data 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...
5181
}
5182
5183
/**
5184
 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
5185
 *
5186
 * @param string $str
5187
 * @return mixed
5188
 */
5189 View Code Duplication
function safe_unserialize($str)
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...
5190
{
5191
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5192
	if (function_exists('mb_internal_encoding') &&
5193
		(((int) ini_get('mbstring.func_overload')) & 0x02))
5194
	{
5195
		$mbIntEnc = mb_internal_encoding();
5196
		mb_internal_encoding('ASCII');
5197
	}
5198
5199
	$out = _safe_unserialize($str);
5200
5201
	if (isset($mbIntEnc))
5202
		mb_internal_encoding($mbIntEnc);
5203
5204
	return $out;
5205
}
5206
5207
/**
5208
 * Tries different modes to make file/dirs writable. Wrapper function for chmod()
5209
5210
 * @param string $file The file/dir full path.
5211
 * @param int $value Not needed, added for legacy reasons.
5212
 * @return boolean  true if the file/dir is already writable or the function was able to make it writable, false if the function couldn't make the file/dir writable.
5213
 */
5214
function smf_chmod($file, $value = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
5215
{
5216
	// No file? no checks!
5217
	if (empty($file))
5218
		return false;
5219
5220
	// Already writable?
5221
	if (is_writable($file))
5222
		return true;
5223
5224
	// Do we have a file or a dir?
5225
	$isDir = is_dir($file);
5226
	$isWritable = false;
5227
5228
	// Set different modes.
5229
	$chmodValues = $isDir ? array(0750, 0755, 0775, 0777) : array(0644, 0664, 0666);
5230
5231
	foreach($chmodValues as $val)
5232
	{
5233
		// If it's writable, break out of the loop.
5234
		if (is_writable($file))
5235
		{
5236
			$isWritable = true;
5237
			break;
5238
		}
5239
5240
		else
5241
			@chmod($file, $val);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
5242
	}
5243
5244
	return $isWritable;
5245
}
5246
5247
/**
5248
 * Wrapper function for smf_json_decode() with error handling.
5249
5250
 * @param string $json The string to decode.
5251
 * @param bool $returnAsArray To return the decoded string as an array or an object, SMF only uses Arrays but to keep on compatibility with json_decode its set to false as default.
5252
 * @return array Either an empty array or the decoded data as an array.
5253
 */
5254
function smf_json_decode($json, $returnAsArray = false)
5255
{
5256
	global $txt;
5257
5258
	// Come on...
5259
	if (empty($json) || !is_string($json))
5260
		return array();
5261
5262
	$returnArray = array();
0 ignored issues
show
Unused Code introduced by
$returnArray is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5263
	$jsonError = false;
0 ignored issues
show
Unused Code introduced by
$jsonError is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
5264
5265
	$returnArray = @json_decode($json, $returnAsArray);
5266
5267
	// PHP 5.3 so no json_last_error_msg()
5268
	switch(json_last_error())
5269
	{
5270
		case JSON_ERROR_NONE:
5271
			$jsonError = false;
5272
			break;
5273
		case JSON_ERROR_DEPTH:
5274
			$jsonError =  'JSON_ERROR_DEPTH';
5275
			break;
5276
		case JSON_ERROR_STATE_MISMATCH:
5277
			$jsonError = 'JSON_ERROR_STATE_MISMATCH';
5278
			break;
5279
		case JSON_ERROR_CTRL_CHAR:
5280
			$jsonError = 'JSON_ERROR_CTRL_CHAR';
5281
			break;
5282
		case JSON_ERROR_SYNTAX:
5283
			$jsonError = 'JSON_ERROR_SYNTAX';
5284
			break;
5285
		case JSON_ERROR_UTF8:
5286
			$jsonError = 'JSON_ERROR_UTF8';
5287
			break;
5288
		default:
5289
			$jsonError = 'unknown';
5290
			break;
5291
	}
5292
5293
	// Something went wrong!
5294
	if (!empty($jsonError))
5295
	{
5296
		// Being a wrapper means we lost our smf_error_handler() privileges :(
5297
		$jsonDebug = debug_backtrace();
5298
		$jsonDebug = $jsonDebug[0];
5299
		loadLanguage('Errors');
5300
5301
		if (!empty($jsonDebug))
5302
			log_error($txt['json_'. $jsonError], 'critical', $jsonDebug['file'], $jsonDebug['line']);
5303
5304
		else
5305
			log_error($txt['json_'. $jsonError], 'critical');
5306
5307
		// Everyone expects an array.
5308
		return array();
5309
	}
5310
5311
	return $returnArray;
5312
}
5313
5314
/**
5315
 * Check the given String if he is a valid IPv4 or IPv6
5316
 * return true or false
5317
 */
5318
function isValidIP($IPString)
5319
{
5320
	return filter_var($IPString, FILTER_VALIDATE_IP) !== false;
5321
}
5322
5323
?>
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...
5324