Completed
Push — release-2.1 ( e15e64...996344 )
by Mathias
11s
created

Subs.php ➔ template_css()   D

Complexity

Conditions 17
Paths 180

Size

Total Lines 75
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 38
nc 180
nop 0
dl 0
loc 75
rs 4.9162
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file 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 2018 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * 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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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)
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))
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)
0 ignored issues
show
Bug introduced by
The expression $postgroups of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
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, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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 View Code Duplication
			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 ($var == 'alerts' && ($val === '+' || $val === '-'))
383
		{
384
			include_once($sourcedir . '/Profile-View.php');
385
			if (is_array($members))
386
			{
387
				$val = 'CASE ';
388
				foreach ($members as $k => $v)
389
					$val .= 'WHEN id_member = ' . $v . ' THEN '. count(fetch_alerts($v, false, 0, array(), false)) . ' ';
390
				$val = $val . ' END';
391
				$type = 'raw';
392
			}
393
			else
394
			{
395
				$blub = fetch_alerts($members, false, 0, array(), false);
396
				$val = count($blub);
397
			}
398
		}
399
		else if ($type == 'int' && ($val === '+' || $val === '-'))
400
		{
401
			$val = $var . ' ' . $val . ' 1';
402
			$type = 'raw';
403
		}
404
405
		// Ensure posts, instant_messages, and unread_messages don't overflow or underflow.
406
		if (in_array($var, array('posts', 'instant_messages', 'unread_messages')))
407
		{
408
			if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match))
409
			{
410
				if ($match[1] != '+ ')
411
					$val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END';
412
				$type = 'raw';
413
			}
414
		}
415
416
		$setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},';
417
		$parameters['p_' . $var] = $val;
418
	}
419
420
	$smcFunc['db_query']('', '
421
		UPDATE {db_prefix}members
422
		SET' . substr($setString, 0, -1) . '
423
		WHERE ' . $condition,
424
		$parameters
425
	);
426
427
	updateStats('postgroups', $members, array_keys($data));
428
429
	// Clear any caching?
430
	if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members))
431
	{
432
		if (!is_array($members))
433
			$members = array($members);
434
435
		foreach ($members as $member)
436
		{
437
			if ($modSettings['cache_enable'] >= 3)
438
			{
439
				cache_put_data('member_data-profile-' . $member, null, 120);
440
				cache_put_data('member_data-normal-' . $member, null, 120);
441
				cache_put_data('member_data-minimal-' . $member, null, 120);
442
			}
443
			cache_put_data('user_settings-' . $member, null, 60);
444
		}
445
	}
446
}
447
448
/**
449
 * Updates the settings table as well as $modSettings... only does one at a time if $update is true.
450
 *
451
 * - updates both the settings table and $modSettings array.
452
 * - all of changeArray's indexes and values are assumed to have escaped apostrophes (')!
453
 * - if a variable is already set to what you want to change it to, that
454
 *   variable will be skipped over; it would be unnecessary to reset.
455
 * - When use_update is true, UPDATEs will be used instead of REPLACE.
456
 * - when use_update is true, the value can be true or false to increment
457
 *  or decrement it, respectively.
458
 *
459
 * @param array $changeArray An array of info about what we're changing in 'setting' => 'value' format
460
 * @param bool $update Whether to use an UPDATE query instead of a REPLACE query
461
 */
462
function updateSettings($changeArray, $update = false)
463
{
464
	global $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
465
466
	if (empty($changeArray) || !is_array($changeArray))
467
		return;
468
469
	$toRemove = array();
470
471
	// Go check if there is any setting to be removed.
472
	foreach ($changeArray as $k => $v)
473
		if ($v === null)
474
		{
475
			// Found some, remove them from the original array and add them to ours.
476
			unset($changeArray[$k]);
477
			$toRemove[] = $k;
478
		}
479
480
	// Proceed with the deletion.
481
	if (!empty($toRemove))
482
		$smcFunc['db_query']('', '
483
			DELETE FROM {db_prefix}settings
484
			WHERE variable IN ({array_string:remove})',
485
			array(
486
				'remove' => $toRemove,
487
			)
488
		);
489
490
	// In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs.
491
	if ($update)
492
	{
493
		foreach ($changeArray as $variable => $value)
494
		{
495
			$smcFunc['db_query']('', '
496
				UPDATE {db_prefix}settings
497
				SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value}
498
				WHERE variable = {string:variable}',
499
				array(
500
					'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value),
501
					'variable' => $variable,
502
				)
503
			);
504
			$modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value);
505
		}
506
507
		// Clean out the cache and make sure the cobwebs are gone too.
508
		cache_put_data('modSettings', null, 90);
509
510
		return;
511
	}
512
513
	$replaceArray = array();
514
	foreach ($changeArray as $variable => $value)
515
	{
516
		// Don't bother if it's already like that ;).
517
		if (isset($modSettings[$variable]) && $modSettings[$variable] == $value)
518
			continue;
519
		// If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it.
520
		elseif (!isset($modSettings[$variable]) && empty($value))
521
			continue;
522
523
		$replaceArray[] = array($variable, $value);
524
525
		$modSettings[$variable] = $value;
526
	}
527
528
	if (empty($replaceArray))
529
		return;
530
531
	$smcFunc['db_insert']('replace',
532
		'{db_prefix}settings',
533
		array('variable' => 'string-255', 'value' => 'string-65534'),
534
		$replaceArray,
535
		array('variable')
536
	);
537
538
	// Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
539
	cache_put_data('modSettings', null, 90);
540
}
541
542
/**
543
 * Constructs a page list.
544
 *
545
 * - builds the page list, e.g. 1 ... 6 7 [8] 9 10 ... 15.
546
 * - flexible_start causes it to use "url.page" instead of "url;start=page".
547
 * - very importantly, cleans up the start value passed, and forces it to
548
 *   be a multiple of num_per_page.
549
 * - checks that start is not more than max_value.
550
 * - base_url should be the URL without any start parameter on it.
551
 * - uses the compactTopicPagesEnable and compactTopicPagesContiguous
552
 *   settings to decide how to display the menu.
553
 *
554
 * an example is available near the function definition.
555
 * $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true);
556
 *
557
 * @param string $base_url The basic URL to be used for each link.
558
 * @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.
559
 * @param int $max_value The total number of items you are paginating for.
560
 * @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.
561
 * @param bool $flexible_start Whether a ;start=x component should be introduced into the URL automatically (see above)
562
 * @param bool $show_prevnext Whether the Previous and Next links should be shown (should be on only when navigating the list)
563
 *
564
 * @return string The complete HTML of the page index that was requested, formatted by the template.
565
 */
566
function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false, $show_prevnext = true)
567
{
568
	global $modSettings, $context, $smcFunc, $settings, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
569
570
	// Save whether $start was less than 0 or not.
571
	$start = (int) $start;
572
	$start_invalid = $start < 0;
573
574
	// Make sure $start is a proper variable - not less than 0.
575
	if ($start_invalid)
576
		$start = 0;
577
	// Not greater than the upper bound.
578
	elseif ($start >= $max_value)
579
		$start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page)));
580
	// And it has to be a multiple of $num_per_page!
581
	else
582
		$start = max(0, (int) $start - ((int) $start % (int) $num_per_page));
583
584
	$context['current_page'] = $start / $num_per_page;
585
586
	// Define some default page index settings if we don't already have it...
587
	if (!isset($settings['page_index']))
588
	{
589
		// This defines the formatting for the page indexes used throughout the forum.
590
		$settings['page_index'] = array(
591
			'extra_before' => '<span class="pages">' . $txt['pages'] . '</span>',
592
			'previous_page' => '<span class="generic_icons previous_page"></span>',
593
			'current_page' => '<span class="current_page">%1$d</span> ',
594
			'page' => '<a class="navPages" href="{URL}">%2$s</a> ',
595
			'expand_pages' => '<span class="expand_pages" onclick="expandPages(this, {LINK}, {FIRST_PAGE}, {LAST_PAGE}, {PER_PAGE});"> ... </span>',
596
			'next_page' => '<span class="generic_icons next_page"></span>',
597
			'extra_after' => '',
598
		);
599
	}
600
601
	$base_link = strtr($settings['page_index']['page'], array('{URL}' => $flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d'));
602
	$pageindex = $settings['page_index']['extra_before'];
603
604
	// Compact pages is off or on?
605
	if (empty($modSettings['compactTopicPagesEnable']))
606
	{
607
		// Show the left arrow.
608
		$pageindex .= $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']);
609
610
		// Show all the pages.
611
		$display_page = 1;
612
		for ($counter = 0; $counter < $max_value; $counter += $num_per_page)
613
			$pageindex .= $start == $counter && !$start_invalid ? sprintf($settings['page_index']['current_page'], $display_page++) : sprintf($base_link, $counter, $display_page++);
614
615
		// Show the right arrow.
616
		$display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page);
617
		if ($start != $counter - $max_value && !$start_invalid)
618
			$pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, $settings['page_index']['next_page']);
619
	}
620
	else
621
	{
622
		// If they didn't enter an odd value, pretend they did.
623
		$PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2;
624
625
		// Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page)
626
		if (!empty($start) && $show_prevnext)
627
			$pageindex .= sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']);
628
		else
629
			$pageindex .= '';
630
631
		// Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15)
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
632
		if ($start > $num_per_page * $PageContiguous)
633
			$pageindex .= sprintf($base_link, 0, '1');
634
635
		// Show the ... after the first page.  (prev page 1 >...< 6 7 [8] 9 10 ... 15 next page)
636
		if ($start > $num_per_page * ($PageContiguous + 1))
637
			$pageindex .= strtr($settings['page_index']['expand_pages'], array(
638
				'{LINK}' => JavaScriptEscape($smcFunc['htmlspecialchars']($base_link)),
639
				'{FIRST_PAGE}' => $num_per_page,
640
				'{LAST_PAGE}' => $start - $num_per_page * $PageContiguous,
641
				'{PER_PAGE}' => $num_per_page,
642
			));
643
644
		// Show the pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
645
		for ($nCont = $PageContiguous; $nCont >= 1; $nCont--)
646 View Code Duplication
			if ($start >= $num_per_page * $nCont)
647
			{
648
				$tmpStart = $start - $num_per_page * $nCont;
649
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
650
			}
651
652
		// Show the current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
653
		if (!$start_invalid)
654
			$pageindex .= sprintf($settings['page_index']['current_page'], $start / $num_per_page + 1);
655
		else
656
			$pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1);
657
658
		// Show the pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
659
		$tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page;
660
		for ($nCont = 1; $nCont <= $PageContiguous; $nCont++)
661 View Code Duplication
			if ($start + $num_per_page * $nCont <= $tmpMaxPages)
662
			{
663
				$tmpStart = $start + $num_per_page * $nCont;
664
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
665
			}
666
667
		// Show the '...' part near the end. (prev page 1 ... 6 7 [8] 9 10 >...< 15 next page)
668
		if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages)
669
			$pageindex .= strtr($settings['page_index']['expand_pages'], array(
670
				'{LINK}' => JavaScriptEscape($smcFunc['htmlspecialchars']($base_link)),
671
				'{FIRST_PAGE}' => $start + $num_per_page * ($PageContiguous + 1),
672
				'{LAST_PAGE}' => $tmpMaxPages,
673
				'{PER_PAGE}' => $num_per_page,
674
			));
675
676
		// Show the last number in the list. (prev page 1 ... 6 7 [8] 9 10 ... >15<  next page)
677
		if ($start + $num_per_page * $PageContiguous < $tmpMaxPages)
678
			$pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1);
679
680
		// Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
681
		if ($start != $tmpMaxPages && $show_prevnext)
682
			$pageindex .= sprintf($base_link, $start + $num_per_page, $settings['page_index']['next_page']);
683
	}
684
	$pageindex .= $settings['page_index']['extra_after'];
685
686
	return $pageindex;
687
}
688
689
/**
690
 * - Formats a number.
691
 * - uses the format of number_format to decide how to format the number.
692
 *   for example, it might display "1 234,50".
693
 * - caches the formatting data from the setting for optimization.
694
 *
695
 * @param float $number A number
696
 * @param bool|int $override_decimal_count If set, will use the specified number of decimal places. Otherwise it's automatically determined
697
 * @return string A formatted number
0 ignored issues
show
Documentation introduced by
Should the return type not be double|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
698
 */
699
function comma_format($number, $override_decimal_count = false)
700
{
701
	global $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
702
	static $thousands_separator = null, $decimal_separator = null, $decimal_count = null;
703
704
	// Cache these values...
705
	if ($decimal_separator === null)
706
	{
707
		// Not set for whatever reason?
708
		if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1)
709
			return $number;
710
711
		// Cache these each load...
712
		$thousands_separator = $matches[1];
713
		$decimal_separator = $matches[2];
714
		$decimal_count = strlen($matches[3]);
715
	}
716
717
	// Format the string with our friend, number_format.
718
	return number_format($number, (float) $number === $number ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator);
719
}
720
721
/**
722
 * Format a time to make it look purdy.
723
 *
724
 * - returns a pretty formatted version of time based on the user's format in $user_info['time_format'].
725
 * - applies all necessary time offsets to the timestamp, unless offset_type is set.
726
 * - if todayMod is set and show_today was not not specified or true, an
727
 *   alternate format string is used to show the date with something to show it is "today" or "yesterday".
728
 * - performs localization (more than just strftime would do alone.)
729
 *
730
 * @param int $log_time A timestamp
731
 * @param bool $show_today Whether to show "Today"/"Yesterday" or just a date
732
 * @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.
733
 * @param bool $process_safe Activate setlocale check for changes at runtime. Slower, but safer.
734
 * @return string A formatted timestamp
735
 */
736
function timeformat($log_time, $show_today = true, $offset_type = false, $process_safe = false)
737
{
738
	global $context, $user_info, $txt, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
739
	static $non_twelve_hour, $locale_cache;
740
	static $unsupportedFormats, $finalizedFormats;
741
742
	// Offset the time.
743
	if (!$offset_type)
744
		$time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600;
745
	// Just the forum offset?
746
	elseif ($offset_type == 'forum')
747
		$time = $log_time + $modSettings['time_offset'] * 3600;
748
	else
749
		$time = $log_time;
750
751
	// We can't have a negative date (on Windows, at least.)
752
	if ($log_time < 0)
753
		$log_time = 0;
754
755
	// Today and Yesterday?
756
	if ($modSettings['todayMod'] >= 1 && $show_today === true)
757
	{
758
		// Get the current time.
759
		$nowtime = forum_time();
760
761
		$then = @getdate($time);
762
		$now = @getdate($nowtime);
763
764
		// Try to make something of a time format string...
765
		$s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S';
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $s. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
766
		if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false)
767
		{
768
			$h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l';
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $h. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
769
			$today_fmt = $h . ':%M' . $s . ' %p';
770
		}
771
		else
772
			$today_fmt = '%H:%M' . $s;
773
774
		// Same day of the year, same year.... Today!
775
		if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
776
			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...
777
778
		// 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...
779
		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))
780
			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...
781
	}
782
783
	$str = !is_bool($show_today) ? $show_today : $user_info['time_format'];
784
785
	// Use the cached formats if available
786
	if (is_null($finalizedFormats))
787
		$finalizedFormats = (array) cache_get_data('timeformatstrings', 86400);
788
789
	// Make a supported version for this format if we don't already have one
790
	if (empty($finalizedFormats[$str]))
791
	{
792
		$timeformat = $str;
793
794
		// Not all systems support all formats, and Windows fails altogether if unsupported ones are
795
		// used, so let's prevent that. Some substitutions go to the nearest reasonable fallback, some
796
		// turn into static strings, some (i.e. %a, %A, $b, %B, %p) have special handling below.
797
		$strftimeFormatSubstitutions = array(
798
			// Day
799
			'a' => '%a', 'A' => '%A', 'e' => '%d', 'd' => '&#37;d', 'j' => '&#37;j', 'u' => '%w', 'w' => '&#37;w',
800
			// Week
801
			'U' => '&#37;U', 'V' => '%U', 'W' => '%U',
802
			// Month
803
			'b' => '%b', 'B' => '%B', 'h' => '%b', 'm' => '%b',
804
			// Year
805
			'C' => '&#37;C', 'g' => '%y', 'G' => '%Y', 'y' => '&#37;y', 'Y' => '&#37;Y',
806
			// Time
807
			'H' => '&#37;H', 'k' => '%H', 'I' => '%H', 'l' => '%I', 'M' => '&#37;M', 'p' => '%p', 'P' => '%p',
808
			'r' => '%I:%M:%S %p', 'R' => '%H:%M', 'S' => '&#37;S', 'T' => '%H:%M:%S', 'X' => '%T', 'z' => '&#37;z', 'Z' => '&#37;Z',
809
			// Time and Date Stamps
810
			'c' => '%F %T', 'D' => '%m/%d/%y', 'F' => '%Y-%m-%d', 's' => '&#37;s', 'x' => '%F',
811
			// Miscellaneous
812
			'n' => "\n", 't' => "\t", '%' => '&#37;',
813
		);
814
815
		// No need to do this part again if we already did it once
816
		if (is_null($unsupportedFormats))
817
			$unsupportedFormats = (array) cache_get_data('unsupportedtimeformats', 86400);
818
		if (empty($unsupportedFormats))
819
		{
820
			foreach($strftimeFormatSubstitutions as $format => $substitution)
821
			{
822
				$value = @strftime('%' . $format);
823
824
				// Windows will return false for unsupported formats
825
				// Other operating systems return the format string as a literal
826
				if ($value === false || $value === $format)
827
					$unsupportedFormats[] = $format;
828
			}
829
			cache_put_data('unsupportedtimeformats', $unsupportedFormats, 86400);
830
		}
831
832
		// Windows needs extra help if $timeformat contains something completely invalid, e.g. '%Q'
833
		if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')
834
			$timeformat = preg_replace('~%(?!' . implode('|', array_keys($strftimeFormatSubstitutions)) . ')~', '&#37;', $timeformat);
835
836
		// Substitute unsupported formats with supported ones
837
		if (!empty($unsupportedFormats))
838
			while (preg_match('~%(' . implode('|', $unsupportedFormats) . ')~', $timeformat, $matches))
839
				$timeformat = str_replace($matches[0], $strftimeFormatSubstitutions[$matches[1]], $timeformat);
840
841
		// Remember this so we don't need to do it again
842
		$finalizedFormats[$str] = $timeformat;
843
		cache_put_data('timeformatstrings', $finalizedFormats, 86400);
844
	}
845
846
	$str = $finalizedFormats[$str];
847
848
	if (!isset($locale_cache))
849
		$locale_cache = setlocale(LC_TIME, $txt['lang_locale']);
850
851
	if ($locale_cache !== false)
852
	{
853
		// Check if another process changed the locale
854
		if ($process_safe === true && setlocale(LC_TIME, '0') != $locale_cache)
855
			setlocale(LC_TIME, $txt['lang_locale']);
856
857
		if (!isset($non_twelve_hour))
858
			$non_twelve_hour = trim(strftime('%p')) === '';
859 View Code Duplication
		if ($non_twelve_hour && strpos($str, '%p') !== false)
860
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
861
862
		foreach (array('%a', '%A', '%b', '%B') as $token)
863
			if (strpos($str, $token) !== false)
864
				$str = str_replace($token, strftime($token, $time), $str);
865
	}
866
	else
867
	{
868
		// Do-it-yourself time localization.  Fun.
869
		foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label)
870
			if (strpos($str, $token) !== false)
871
				$str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str);
872
873 View Code Duplication
		if (strpos($str, '%p') !== false)
874
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
875
	}
876
877
	// Format the time and then restore any literal percent characters
878
	return str_replace('&#37;', '%', strftime($str, $time));
879
}
880
881
/**
882
 * Removes special entities from strings.  Compatibility...
883
 * Should be used instead of html_entity_decode for PHP version compatibility reasons.
884
 *
885
 * - removes the base entities (&lt;, &quot;, etc.) from text.
886
 * - additionally converts &nbsp; and &#039;.
887
 *
888
 * @param string $string A string
889
 * @return string The string without entities
890
 */
891
function un_htmlspecialchars($string)
892
{
893
	global $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
894
	static $translation = array();
895
896
	// Determine the character set... Default to UTF-8
897
	if (empty($context['character_set']))
898
		$charset = 'UTF-8';
899
	// Use ISO-8859-1 in place of non-supported ISO-8859 charsets...
900
	elseif (strpos($context['character_set'], 'ISO-8859-') !== false && !in_array($context['character_set'], array('ISO-8859-5', 'ISO-8859-15')))
901
		$charset = 'ISO-8859-1';
902
	else
903
		$charset = $context['character_set'];
904
905
	if (empty($translation))
906
		$translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES, $charset)) + array('&#039;' => '\'', '&#39;' => '\'', '&nbsp;' => ' ');
907
908
	return strtr($string, $translation);
909
}
910
911
/**
912
 * Shorten a subject + internationalization concerns.
913
 *
914
 * - shortens a subject so that it is either shorter than length, or that length plus an ellipsis.
915
 * - respects internationalization characters and entities as one character.
916
 * - avoids trailing entities.
917
 * - returns the shortened string.
918
 *
919
 * @param string $subject The subject
920
 * @param int $len How many characters to limit it to
921
 * @return string The shortened subject - either the entire subject (if it's <= $len) or the subject shortened to $len characters with "..." appended
922
 */
923
function shorten_subject($subject, $len)
924
{
925
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
926
927
	// It was already short enough!
928
	if ($smcFunc['strlen']($subject) <= $len)
929
		return $subject;
930
931
	// Shorten it by the length it was too long, and strip off junk from the end.
932
	return $smcFunc['substr']($subject, 0, $len) . '...';
933
}
934
935
/**
936
 * Gets the current time with offset.
937
 *
938
 * - always applies the offset in the time_offset setting.
939
 *
940
 * @param bool $use_user_offset Whether to apply the user's offset as well
941
 * @param int $timestamp A timestamp (null to use current time)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $timestamp not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
942
 * @return int Seconds since the unix epoch, with forum time offset and (optionally) user time offset applied
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
943
 */
944
function forum_time($use_user_offset = true, $timestamp = null)
945
{
946
	global $user_info, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
947
948
	if ($timestamp === null)
949
		$timestamp = time();
950
	elseif ($timestamp == 0)
951
		return 0;
952
953
	return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600;
954
}
955
956
/**
957
 * Calculates all the possible permutations (orders) of array.
958
 * should not be called on huge arrays (bigger than like 10 elements.)
959
 * returns an array containing each permutation.
960
 *
961
 * @deprecated since 2.1
962
 * @param array $array An array
963
 * @return array An array containing each permutation
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array[].

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
964
 */
965
function permute($array)
966
{
967
	$orders = array($array);
968
969
	$n = count($array);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $n. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
970
	$p = range(0, $n);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $p. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
971
	for ($i = 1; $i < $n; null)
972
	{
973
		$p[$i]--;
974
		$j = $i % 2 != 0 ? $p[$i] : 0;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $j. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
975
976
		$temp = $array[$i];
977
		$array[$i] = $array[$j];
978
		$array[$j] = $temp;
979
980
		for ($i = 1; $p[$i] == 0; $i++)
981
			$p[$i] = 1;
982
983
		$orders[] = $array;
984
	}
985
986
	return $orders;
987
}
988
989
/**
990
 * Parse bulletin board code in a string, as well as smileys optionally.
991
 *
992
 * - only parses bbc tags which are not disabled in disabledBBC.
993
 * - handles basic HTML, if enablePostHTML is on.
994
 * - caches the from/to replace regular expressions so as not to reload them every time a string is parsed.
995
 * - only parses smileys if smileys is true.
996
 * - does nothing if the enableBBC setting is off.
997
 * - uses the cache_id as a unique identifier to facilitate any caching it may do.
998
 *  -returns the modified message.
999
 *
1000
 * @param string $message The message
1001
 * @param bool $smileys Whether to parse smileys as well
1002
 * @param string $cache_id The cache ID
1003
 * @param array $parse_tags If set, only parses these tags rather than all of them
1004
 * @return string The parsed message
1005
 */
1006
function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array())
1007
{
1008
	global $smcFunc, $txt, $scripturl, $context, $modSettings, $user_info, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1009
	static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
1010
	static $disabled;
1011
1012
	// Don't waste cycles
1013
	if ($message === '')
1014
		return '';
1015
1016
	// Just in case it wasn't determined yet whether UTF-8 is enabled.
1017
	if (!isset($context['utf8']))
1018
		$context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
1019
1020
	// Clean up any cut/paste issues we may have
1021
	$message = sanitizeMSCutPaste($message);
1022
1023
	// If the load average is too high, don't parse the BBC.
1024
	if (!empty($context['load_average']) && !empty($modSettings['bbc']) && $context['load_average'] >= $modSettings['bbc'])
1025
	{
1026
		$context['disabled_parse_bbc'] = true;
1027
		return $message;
1028
	}
1029
1030
	if ($smileys !== null && ($smileys == '1' || $smileys == '0'))
1031
		$smileys = (bool) $smileys;
1032
1033
	if (empty($modSettings['enableBBC']) && $message !== false)
1034
	{
1035
		if ($smileys === true)
1036
			parsesmileys($message);
1037
1038
		return $message;
1039
	}
1040
1041
	// If we are not doing every tag then we don't cache this run.
1042
	if (!empty($parse_tags) && !empty($bbc_codes))
1043
	{
1044
		$temp_bbc = $bbc_codes;
1045
		$bbc_codes = array();
1046
	}
1047
1048
	// Ensure $modSettings['tld_regex'] contains a valid regex for the autolinker
1049
	if (!empty($modSettings['autoLinkUrls']))
1050
		set_tld_regex();
1051
1052
	// Allow mods access before entering the main parse_bbc loop
1053
	call_integration_hook('integrate_pre_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
1054
1055
	// Sift out the bbc for a performance improvement.
1056
	if (empty($bbc_codes) || $message === false || !empty($parse_tags))
1057
	{
1058
		if (!empty($modSettings['disabledBBC']))
1059
		{
1060
			$disabled = array();
1061
1062
			$temp = explode(',', strtolower($modSettings['disabledBBC']));
1063
1064
			foreach ($temp as $tag)
1065
				$disabled[trim($tag)] = true;
1066
		}
1067
1068
		if (empty($modSettings['enableEmbeddedFlash']))
1069
			$disabled['flash'] = true;
1070
1071
		/* The following bbc are formatted as an array, with keys as follows:
1072
1073
			tag: the tag's name - should be lowercase!
1074
1075
			type: one of...
1076
				- (missing): [tag]parsed content[/tag]
1077
				- unparsed_equals: [tag=xyz]parsed content[/tag]
1078
				- parsed_equals: [tag=parsed data]parsed content[/tag]
1079
				- unparsed_content: [tag]unparsed content[/tag]
1080
				- closed: [tag], [tag/], [tag /]
1081
				- unparsed_commas: [tag=1,2,3]parsed content[/tag]
1082
				- unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
1083
				- unparsed_equals_content: [tag=...]unparsed content[/tag]
1084
1085
			parameters: an optional array of parameters, for the form
1086
			  [tag abc=123]content[/tag].  The array is an associative array
1087
			  where the keys are the parameter names, and the values are an
1088
			  array which may contain the following:
1089
				- match: a regular expression to validate and match the value.
1090
				- quoted: true if the value should be quoted.
1091
				- validate: callback to evaluate on the data, which is $data.
1092
				- value: a string in which to replace $1 with the data.
1093
				  either it or validate may be used, not both.
1094
				- optional: true if the parameter is optional.
1095
1096
			test: a regular expression to test immediately after the tag's
1097
			  '=', ' ' or ']'.  Typically, should have a \] at the end.
1098
			  Optional.
1099
1100
			content: only available for unparsed_content, closed,
1101
			  unparsed_commas_content, and unparsed_equals_content.
1102
			  $1 is replaced with the content of the tag.  Parameters
1103
			  are replaced in the form {param}.  For unparsed_commas_content,
1104
			  $2, $3, ..., $n are replaced.
1105
1106
			before: only when content is not used, to go before any
1107
			  content.  For unparsed_equals, $1 is replaced with the value.
1108
			  For unparsed_commas, $1, $2, ..., $n are replaced.
1109
1110
			after: similar to before in every way, except that it is used
1111
			  when the tag is closed.
1112
1113
			disabled_content: used in place of content when the tag is
1114
			  disabled.  For closed, default is '', otherwise it is '$1' if
1115
			  block_level is false, '<div>$1</div>' elsewise.
1116
1117
			disabled_before: used in place of before when disabled.  Defaults
1118
			  to '<div>' if block_level, '' if not.
1119
1120
			disabled_after: used in place of after when disabled.  Defaults
1121
			  to '</div>' if block_level, '' if not.
1122
1123
			block_level: set to true the tag is a "block level" tag, similar
1124
			  to HTML.  Block level tags cannot be nested inside tags that are
1125
			  not block level, and will not be implicitly closed as easily.
1126
			  One break following a block level tag may also be removed.
1127
1128
			trim: if set, and 'inside' whitespace after the begin tag will be
1129
			  removed.  If set to 'outside', whitespace after the end tag will
1130
			  meet the same fate.
1131
1132
			validate: except when type is missing or 'closed', a callback to
1133
			  validate the data as $data.  Depending on the tag's type, $data
1134
			  may be a string or an array of strings (corresponding to the
1135
			  replacement.)
1136
1137
			quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
1138
			  may be not set, 'optional', or 'required' corresponding to if
1139
			  the content may be quoted.  This allows the parser to read
1140
			  [tag="abc]def[esdf]"] properly.
1141
1142
			require_parents: an array of tag names, or not set.  If set, the
1143
			  enclosing tag *must* be one of the listed tags, or parsing won't
1144
			  occur.
1145
1146
			require_children: similar to require_parents, if set children
1147
			  won't be parsed if they are not in the list.
1148
1149
			disallow_children: similar to, but very different from,
1150
			  require_children, if it is set the listed tags will not be
1151
			  parsed inside the tag.
1152
1153
			parsed_tags_allowed: an array restricting what BBC can be in the
1154
			  parsed_equals parameter, if desired.
1155
		*/
1156
1157
		$codes = array(
1158
			array(
1159
				'tag' => 'abbr',
1160
				'type' => 'unparsed_equals',
1161
				'before' => '<abbr title="$1">',
1162
				'after' => '</abbr>',
1163
				'quoted' => 'optional',
1164
				'disabled_after' => ' ($1)',
1165
			),
1166
			array(
1167
				'tag' => 'anchor',
1168
				'type' => 'unparsed_equals',
1169
				'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]',
1170
				'before' => '<span id="post_$1">',
1171
				'after' => '</span>',
1172
			),
1173
			array(
1174
				'tag' => 'attach',
1175
				'type' => 'unparsed_content',
1176
				'parameters' => array(
1177
					'name' => array('optional' => true),
1178
					'type' => array('optional' => true),
1179
					'alt' => array('optional' => true),
1180
					'title' => array('optional' => true),
1181
					'width' => array('optional' => true, 'match' => '(\d+)'),
1182
					'height' => array('optional' => true, 'match' => '(\d+)'),
1183
				),
1184
				'content' => '$1',
1185
				'validate' => function (&$tag, &$data, $disabled, $params) use ($modSettings, $context, $sourcedir, $txt)
1186
				{
1187
					$returnContext = '';
1188
1189
					// BBC or the entire attachments feature is disabled
1190
					if (empty($modSettings['attachmentEnable']) || !empty($disabled['attach']))
1191
						return $data;
1192
1193
					// Save the attach ID.
1194
					$attachID = $data;
1195
1196
					// Kinda need this.
1197
					require_once($sourcedir . '/Subs-Attachments.php');
1198
1199
					$currentAttachment = parseAttachBBC($attachID);
1200
1201
					// parseAttachBBC will return a string ($txt key) rather than diying with a fatal_error. Up to you to decide what to do.
1202
					if (is_string($currentAttachment))
1203
						return $data = !empty($txt[$currentAttachment]) ? $txt[$currentAttachment] : $currentAttachment;
1204
1205
					if (!empty($currentAttachment['is_image']))
1206
					{
1207
						$alt = ' alt="' . (!empty($params['{alt}']) ? $params['{alt}'] : $currentAttachment['name']) . '"';
1208
						$title = !empty($params['{title}']) ? ' title="' . $params['{title}'] . '"' : '';
1209
1210
						$width = !empty($params['{width}']) ? ' width="' . $params['{width}'] . '"' : '';
1211
						$height = !empty($params['{height}']) ? ' height="' . $params['{height}'] . '"' : '';
1212
1213
						if (empty($width) && empty($height))
1214
						{
1215
							$width = ' width="' . $currentAttachment['width'] . '"';
1216
							$height = ' height="' . $currentAttachment['height'] . '"';
1217
						}
1218
1219
						if ($currentAttachment['thumbnail']['has_thumb'] && empty($params['{width}']) && empty($params['{height}']))
1220
							$returnContext .= '<a href="'. $currentAttachment['href']. ';image" id="link_'. $currentAttachment['id']. '" onclick="'. $currentAttachment['thumbnail']['javascript']. '"><img src="'. $currentAttachment['thumbnail']['href']. '"' . $alt . $title . ' id="thumb_'. $currentAttachment['id']. '" class="atc_img"></a>';
1221
						else
1222
							$returnContext .= '<img src="' . $currentAttachment['href'] . ';image"' . $alt . $title . $width . $height . ' class="bbc_img"/>';
1223
					}
1224
1225
					// No image. Show a link.
1226
					else
1227
						$returnContext .= $currentAttachment['link'];
1228
1229
					// Gotta append what we just did.
1230
					$data = $returnContext;
1231
				},
1232
			),
1233
			array(
1234
				'tag' => 'b',
1235
				'before' => '<b>',
1236
				'after' => '</b>',
1237
			),
1238
			array(
1239
				'tag' => 'center',
1240
				'before' => '<div class="centertext">',
1241
				'after' => '</div>',
1242
				'block_level' => true,
1243
			),
1244
			array(
1245
				'tag' => 'code',
1246
				'type' => 'unparsed_content',
1247
				'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>',
1248
				// @todo Maybe this can be simplified?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1249
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1250
				{
1251
					if (!isset($disabled['code']))
1252
					{
1253
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
1254
1255 View Code Duplication
						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
1256
						{
1257
							// Do PHP code coloring?
1258
							if ($php_parts[$php_i] != '&lt;?php')
1259
								continue;
1260
1261
							$php_string = '';
1262
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1263
							{
1264
								$php_string .= $php_parts[$php_i];
1265
								$php_parts[$php_i++] = '';
1266
							}
1267
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1268
						}
1269
1270
						// Fix the PHP code stuff...
1271
						$data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1272
						$data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);
1273
1274
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1275
						if ($context['browser']['is_opera'])
1276
							$data .= '&nbsp;';
1277
					}
1278
				},
1279
				'block_level' => true,
1280
			),
1281
			array(
1282
				'tag' => 'code',
1283
				'type' => 'unparsed_equals_content',
1284
				'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>',
1285
				// @todo Maybe this can be simplified?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1286
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1287
				{
1288
					if (!isset($disabled['code']))
1289
					{
1290
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);
1291
1292 View Code Duplication
						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
1293
						{
1294
							// Do PHP code coloring?
1295
							if ($php_parts[$php_i] != '&lt;?php')
1296
								continue;
1297
1298
							$php_string = '';
1299
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1300
							{
1301
								$php_string .= $php_parts[$php_i];
1302
								$php_parts[$php_i++] = '';
1303
							}
1304
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1305
						}
1306
1307
						// Fix the PHP code stuff...
1308
						$data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1309
						$data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]);
1310
1311
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1312
						if ($context['browser']['is_opera'])
1313
							$data[0] .= '&nbsp;';
1314
					}
1315
				},
1316
				'block_level' => true,
1317
			),
1318
			array(
1319
				'tag' => 'color',
1320
				'type' => 'unparsed_equals',
1321
				'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]?)\))\]',
1322
				'before' => '<span style="color: $1;" class="bbc_color">',
1323
				'after' => '</span>',
1324
			),
1325
			array(
1326
				'tag' => 'email',
1327
				'type' => 'unparsed_content',
1328
				'content' => '<a href="mailto:$1" class="bbc_email">$1</a>',
1329
				// @todo Should this respect guest_hideContacts?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1330
				'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...
1331
				{
1332
					$data = strtr($data, array('<br>' => ''));
1333
				},
1334
			),
1335
			array(
1336
				'tag' => 'email',
1337
				'type' => 'unparsed_equals',
1338
				'before' => '<a href="mailto:$1" class="bbc_email">',
1339
				'after' => '</a>',
1340
				// @todo Should this respect guest_hideContacts?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1341
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1342
				'disabled_after' => ' ($1)',
1343
			),
1344
			array(
1345
				'tag' => 'flash',
1346
				'type' => 'unparsed_commas_content',
1347
				'test' => '\d+,\d+\]',
1348
				'content' => '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never">',
1349
				'validate' => function (&$tag, &$data, $disabled)
1350
				{
1351
					if (isset($disabled['url']))
1352
						$tag['content'] = '$1';
1353
					$scheme = parse_url($data[0], PHP_URL_SCHEME);
1354
					if (empty($scheme))
1355
						$data[0] = '//' . ltrim($data[0], ':/');
1356
				},
1357
				'disabled_content' => '<a href="$1" target="_blank" rel="noopener">$1</a>',
1358
			),
1359
			array(
1360
				'tag' => 'float',
1361
				'type' => 'unparsed_equals',
1362
				'test' => '(left|right)(\s+max=\d+(?:%|px|em|rem|ex|pt|pc|ch|vw|vh|vmin|vmax|cm|mm|in)?)?\]',
1363
				'before' => '<div $1>',
1364
				'after' => '</div>',
1365
				'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...
1366
				{
1367
					$class = 'class="bbc_float float' . (strpos($data, 'left') === 0 ? 'left' : 'right') . '"';
1368
1369
					if (preg_match('~\bmax=(\d+(?:%|px|em|rem|ex|pt|pc|ch|vw|vh|vmin|vmax|cm|mm|in)?)~', $data, $matches))
1370
						$css = ' style="max-width:' . $matches[1] . (is_numeric($matches[1]) ? 'px' : '') . '"';
1371
					else
1372
						$css = '';
1373
1374
					$data = $class . $css;
1375
				},
1376
				'trim' => 'outside',
1377
				'block_level' => true,
1378
			),
1379
			array(
1380
				'tag' => 'font',
1381
				'type' => 'unparsed_equals',
1382
				'test' => '[A-Za-z0-9_,\-\s]+?\]',
1383
				'before' => '<span style="font-family: $1;" class="bbc_font">',
1384
				'after' => '</span>',
1385
			),
1386
			array(
1387
				'tag' => 'html',
1388
				'type' => 'unparsed_content',
1389
				'content' => '<div>$1</div>',
1390
				'block_level' => true,
1391
				'disabled_content' => '$1',
1392
			),
1393
			array(
1394
				'tag' => 'hr',
1395
				'type' => 'closed',
1396
				'content' => '<hr>',
1397
				'block_level' => true,
1398
			),
1399
			array(
1400
				'tag' => 'i',
1401
				'before' => '<i>',
1402
				'after' => '</i>',
1403
			),
1404
			array(
1405
				'tag' => 'img',
1406
				'type' => 'unparsed_content',
1407
				'parameters' => array(
1408
					'alt' => array('optional' => true),
1409
					'title' => array('optional' => true),
1410
					'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
1411
					'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
1412
				),
1413
				'content' => '<img src="$1" alt="{alt}" title="{title}"{width}{height} class="bbc_img resized">',
1414 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...
1415
				{
1416
					global $image_proxy_enabled, $image_proxy_secret, $boardurl, $user_info;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1417
1418
					$data = strtr($data, array('<br>' => ''));
1419
					$scheme = parse_url($data, PHP_URL_SCHEME);
1420
					if ($image_proxy_enabled)
1421
					{
1422
						if (!empty($user_info['possibly_robot']))
1423
							return;
1424
1425
						if (empty($scheme))
1426
							$data = 'http://' . ltrim($data, ':/');
1427
1428
						if ($scheme != 'https')
1429
							$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1430
					}
1431
					elseif (empty($scheme))
1432
						$data = '//' . ltrim($data, ':/');
1433
				},
1434
				'disabled_content' => '($1)',
1435
			),
1436
			array(
1437
				'tag' => 'img',
1438
				'type' => 'unparsed_content',
1439
				'content' => '<img src="$1" alt="" class="bbc_img">',
1440 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...
1441
				{
1442
					global $image_proxy_enabled, $image_proxy_secret, $boardurl, $user_info;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1443
1444
					$data = strtr($data, array('<br>' => ''));
1445
					$scheme = parse_url($data, PHP_URL_SCHEME);
1446
					if ($image_proxy_enabled)
1447
					{
1448
						if (!empty($user_info['possibly_robot']))
1449
							return;
1450
1451
						if (empty($scheme))
1452
							$data = 'http://' . ltrim($data, ':/');
1453
1454
						if ($scheme != 'https')
1455
							$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1456
					}
1457
					elseif (empty($scheme))
1458
						$data = '//' . ltrim($data, ':/');
1459
				},
1460
				'disabled_content' => '($1)',
1461
			),
1462
			array(
1463
				'tag' => 'iurl',
1464
				'type' => 'unparsed_content',
1465
				'content' => '<a href="$1" class="bbc_link">$1</a>',
1466 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...
1467
				{
1468
					$data = strtr($data, array('<br>' => ''));
1469
					$scheme = parse_url($data, PHP_URL_SCHEME);
1470
					if (empty($scheme))
1471
						$data = '//' . ltrim($data, ':/');
1472
				},
1473
			),
1474
			array(
1475
				'tag' => 'iurl',
1476
				'type' => 'unparsed_equals',
1477
				'quoted' => 'optional',
1478
				'before' => '<a href="$1" class="bbc_link">',
1479
				'after' => '</a>',
1480
				'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...
1481
				{
1482
					if (substr($data, 0, 1) == '#')
1483
						$data = '#post_' . substr($data, 1);
1484
					else
1485
					{
1486
						$scheme = parse_url($data, PHP_URL_SCHEME);
1487
						if (empty($scheme))
1488
							$data = '//' . ltrim($data, ':/');
1489
					}
1490
				},
1491
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1492
				'disabled_after' => ' ($1)',
1493
			),
1494
			array(
1495
				'tag' => 'left',
1496
				'before' => '<div style="text-align: left;">',
1497
				'after' => '</div>',
1498
				'block_level' => true,
1499
			),
1500
			array(
1501
				'tag' => 'li',
1502
				'before' => '<li>',
1503
				'after' => '</li>',
1504
				'trim' => 'outside',
1505
				'require_parents' => array('list'),
1506
				'block_level' => true,
1507
				'disabled_before' => '',
1508
				'disabled_after' => '<br>',
1509
			),
1510
			array(
1511
				'tag' => 'list',
1512
				'before' => '<ul class="bbc_list">',
1513
				'after' => '</ul>',
1514
				'trim' => 'inside',
1515
				'require_children' => array('li', 'list'),
1516
				'block_level' => true,
1517
			),
1518
			array(
1519
				'tag' => 'list',
1520
				'parameters' => array(
1521
					'type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|upper-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)'),
1522
				),
1523
				'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
1524
				'after' => '</ul>',
1525
				'trim' => 'inside',
1526
				'require_children' => array('li'),
1527
				'block_level' => true,
1528
			),
1529
			array(
1530
				'tag' => 'ltr',
1531
				'before' => '<bdo dir="ltr">',
1532
				'after' => '</bdo>',
1533
				'block_level' => true,
1534
			),
1535
			array(
1536
				'tag' => 'me',
1537
				'type' => 'unparsed_equals',
1538
				'before' => '<div class="meaction">* $1 ',
1539
				'after' => '</div>',
1540
				'quoted' => 'optional',
1541
				'block_level' => true,
1542
				'disabled_before' => '/me ',
1543
				'disabled_after' => '<br>',
1544
			),
1545
			array(
1546
				'tag' => 'member',
1547
				'type' => 'unparsed_equals',
1548
				'before' => '<a href="' . $scripturl . '?action=profile;u=$1" class="mention" data-mention="$1">@',
1549
				'after' => '</a>',
1550
			),
1551
			array(
1552
				'tag' => 'nobbc',
1553
				'type' => 'unparsed_content',
1554
				'content' => '$1',
1555
			),
1556
			array(
1557
				'tag' => 'php',
1558
				'type' => 'unparsed_content',
1559
				'content' => '<span class="phpcode">$1</span>',
1560
				'validate' => isset($disabled['php']) ? null : function (&$tag, &$data, $disabled)
1561
				{
1562
					if (!isset($disabled['php']))
1563
					{
1564
						$add_begin = substr(trim($data), 0, 5) != '&lt;?';
1565
						$data = highlight_php_code($add_begin ? '&lt;?php ' . $data . '?&gt;' : $data);
1566
						if ($add_begin)
1567
							$data = preg_replace(array('~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~', '~\?&gt;((?:</(font|span)>)*)$~'), '$1', $data, 2);
1568
					}
1569
				},
1570
				'block_level' => false,
1571
				'disabled_content' => '$1',
1572
			),
1573
			array(
1574
				'tag' => 'pre',
1575
				'before' => '<pre>',
1576
				'after' => '</pre>',
1577
			),
1578
			array(
1579
				'tag' => 'quote',
1580
				'before' => '<blockquote><cite>' . $txt['quote'] . '</cite>',
1581
				'after' => '</blockquote>',
1582
				'trim' => 'both',
1583
				'block_level' => true,
1584
			),
1585
			array(
1586
				'tag' => 'quote',
1587
				'parameters' => array(
1588
					'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
1589
				),
1590
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1591
				'after' => '</blockquote>',
1592
				'trim' => 'both',
1593
				'block_level' => true,
1594
			),
1595
			array(
1596
				'tag' => 'quote',
1597
				'type' => 'parsed_equals',
1598
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': $1</cite>',
1599
				'after' => '</blockquote>',
1600
				'trim' => 'both',
1601
				'quoted' => 'optional',
1602
				// Don't allow everything to be embedded with the author name.
1603
				'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
1604
				'block_level' => true,
1605
			),
1606
			array(
1607
				'tag' => 'quote',
1608
				'parameters' => array(
1609
					'author' => array('match' => '([^<>]{1,192}?)'),
1610
					'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|msg=\d+?|action=profile;u=\d+)'),
1611
					'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
1612
				),
1613
				'before' => '<blockquote><cite><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></cite>',
1614
				'after' => '</blockquote>',
1615
				'trim' => 'both',
1616
				'block_level' => true,
1617
			),
1618
			array(
1619
				'tag' => 'quote',
1620
				'parameters' => array(
1621
					'author' => array('match' => '(.{1,192}?)'),
1622
				),
1623
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1624
				'after' => '</blockquote>',
1625
				'trim' => 'both',
1626
				'block_level' => true,
1627
			),
1628
			array(
1629
				'tag' => 'right',
1630
				'before' => '<div style="text-align: right;">',
1631
				'after' => '</div>',
1632
				'block_level' => true,
1633
			),
1634
			array(
1635
				'tag' => 'rtl',
1636
				'before' => '<bdo dir="rtl">',
1637
				'after' => '</bdo>',
1638
				'block_level' => true,
1639
			),
1640
			array(
1641
				'tag' => 's',
1642
				'before' => '<s>',
1643
				'after' => '</s>',
1644
			),
1645
			array(
1646
				'tag' => 'size',
1647
				'type' => 'unparsed_equals',
1648
				'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]',
1649
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1650
				'after' => '</span>',
1651
			),
1652
			array(
1653
				'tag' => 'size',
1654
				'type' => 'unparsed_equals',
1655
				'test' => '[1-7]\]',
1656
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1657
				'after' => '</span>',
1658
				'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...
1659
				{
1660
					$sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
1661
					$data = $sizes[$data] . 'em';
1662
				},
1663
			),
1664
			array(
1665
				'tag' => 'sub',
1666
				'before' => '<sub>',
1667
				'after' => '</sub>',
1668
			),
1669
			array(
1670
				'tag' => 'sup',
1671
				'before' => '<sup>',
1672
				'after' => '</sup>',
1673
			),
1674
			array(
1675
				'tag' => 'table',
1676
				'before' => '<table class="bbc_table">',
1677
				'after' => '</table>',
1678
				'trim' => 'inside',
1679
				'require_children' => array('tr'),
1680
				'block_level' => true,
1681
			),
1682
			array(
1683
				'tag' => 'td',
1684
				'before' => '<td>',
1685
				'after' => '</td>',
1686
				'require_parents' => array('tr'),
1687
				'trim' => 'outside',
1688
				'block_level' => true,
1689
				'disabled_before' => '',
1690
				'disabled_after' => '',
1691
			),
1692
			array(
1693
				'tag' => 'time',
1694
				'type' => 'unparsed_content',
1695
				'content' => '$1',
1696
				'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...
1697
				{
1698
					if (is_numeric($data))
1699
						$data = timeformat($data);
1700
					else
1701
						$tag['content'] = '[time]$1[/time]';
1702
				},
1703
			),
1704
			array(
1705
				'tag' => 'tr',
1706
				'before' => '<tr>',
1707
				'after' => '</tr>',
1708
				'require_parents' => array('table'),
1709
				'require_children' => array('td'),
1710
				'trim' => 'both',
1711
				'block_level' => true,
1712
				'disabled_before' => '',
1713
				'disabled_after' => '',
1714
			),
1715
			array(
1716
				'tag' => 'u',
1717
				'before' => '<u>',
1718
				'after' => '</u>',
1719
			),
1720
			array(
1721
				'tag' => 'url',
1722
				'type' => 'unparsed_content',
1723
				'content' => '<a href="$1" class="bbc_link" target="_blank" rel="noopener">$1</a>',
1724 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...
1725
				{
1726
					$data = strtr($data, array('<br>' => ''));
1727
					$scheme = parse_url($data, PHP_URL_SCHEME);
1728
					if (empty($scheme))
1729
						$data = '//' . ltrim($data, ':/');
1730
				},
1731
			),
1732
			array(
1733
				'tag' => 'url',
1734
				'type' => 'unparsed_equals',
1735
				'quoted' => 'optional',
1736
				'before' => '<a href="$1" class="bbc_link" target="_blank" rel="noopener">',
1737
				'after' => '</a>',
1738
				'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...
1739
				{
1740
					$scheme = parse_url($data, PHP_URL_SCHEME);
1741
					if (empty($scheme))
1742
						$data = '//' . ltrim($data, ':/');
1743
				},
1744
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1745
				'disabled_after' => ' ($1)',
1746
			),
1747
		);
1748
1749
		// Inside these tags autolink is not recommendable.
1750
		$no_autolink_tags = array(
1751
			'url',
1752
			'iurl',
1753
			'email',
1754
		);
1755
1756
		// Let mods add new BBC without hassle.
1757
		call_integration_hook('integrate_bbc_codes', array(&$codes, &$no_autolink_tags));
1758
1759
		// This is mainly for the bbc manager, so it's easy to add tags above.  Custom BBC should be added above this line.
1760
		if ($message === false)
1761
		{
1762
			if (isset($temp_bbc))
1763
				$bbc_codes = $temp_bbc;
1764
			usort($codes, function ($a, $b) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $a. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $b. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1765
				return strcmp($a['tag'], $b['tag']);
1766
			});
1767
			return $codes;
1768
		}
1769
1770
		// So the parser won't skip them.
1771
		$itemcodes = array(
1772
			'*' => 'disc',
1773
			'@' => 'disc',
1774
			'+' => 'square',
1775
			'x' => 'square',
1776
			'#' => 'square',
1777
			'o' => 'circle',
1778
			'O' => 'circle',
1779
			'0' => 'circle',
1780
		);
1781
		if (!isset($disabled['li']) && !isset($disabled['list']))
1782
		{
1783
			foreach ($itemcodes as $c => $dummy)
1784
				$bbc_codes[$c] = array();
1785
		}
1786
1787
		// Shhhh!
1788
		if (!isset($disabled['color']))
1789
		{
1790
			$codes[] = array(
1791
				'tag' => 'chrissy',
1792
				'before' => '<span style="color: #cc0099;">',
1793
				'after' => ' :-*</span>',
1794
			);
1795
			$codes[] = array(
1796
				'tag' => 'kissy',
1797
				'before' => '<span style="color: #cc0099;">',
1798
				'after' => ' :-*</span>',
1799
			);
1800
		}
1801
1802
		foreach ($codes as $code)
1803
		{
1804
			// Make it easier to process parameters later
1805
			if (!empty($code['parameters']))
1806
				ksort($code['parameters'], SORT_STRING);
1807
1808
			// If we are not doing every tag only do ones we are interested in.
1809
			if (empty($parse_tags) || in_array($code['tag'], $parse_tags))
1810
				$bbc_codes[substr($code['tag'], 0, 1)][] = $code;
1811
		}
1812
		$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...
1813
	}
1814
1815
	// Shall we take the time to cache this?
1816
	if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && isset($message[1000])) || isset($message[2400])) && empty($parse_tags))
1817
	{
1818
		// It's likely this will change if the message is modified.
1819
		$cache_key = 'parse:' . $cache_id . '-' . md5(md5($message) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . $smcFunc['json_encode']($context['browser']) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']);
1820
1821
		if (($temp = cache_get_data($cache_key, 240)) != null)
1822
			return $temp;
1823
1824
		$cache_t = microtime();
1825
	}
1826
1827
	if ($smileys === 'print')
1828
	{
1829
		// [glow], [shadow], and [move] can't really be printed.
1830
		$disabled['glow'] = true;
1831
		$disabled['shadow'] = true;
1832
		$disabled['move'] = true;
1833
1834
		// Colors can't well be displayed... supposed to be black and white.
1835
		$disabled['color'] = true;
1836
		$disabled['black'] = true;
1837
		$disabled['blue'] = true;
1838
		$disabled['white'] = true;
1839
		$disabled['red'] = true;
1840
		$disabled['green'] = true;
1841
		$disabled['me'] = true;
1842
1843
		// Color coding doesn't make sense.
1844
		$disabled['php'] = true;
1845
1846
		// Links are useless on paper... just show the link.
1847
		$disabled['ftp'] = true;
1848
		$disabled['url'] = true;
1849
		$disabled['iurl'] = true;
1850
		$disabled['email'] = true;
1851
		$disabled['flash'] = true;
1852
1853
		// @todo Change maybe?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1854
		if (!isset($_GET['images']))
1855
			$disabled['img'] = true;
1856
1857
		// @todo Interface/setting to add more?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1858
	}
1859
1860
	$open_tags = array();
1861
	$message = strtr($message, array("\n" => '<br>'));
1862
1863
	$alltags = array();
1864
	foreach ($bbc_codes as $section) {
1865
		foreach ($section as $code) {
1866
			$alltags[] = $code['tag'];
1867
		}
1868
	}
1869
	$alltags_regex = '\b' . implode("\b|\b", array_unique($alltags)) . '\b';
1870
1871
	$pos = -1;
1872
	while ($pos !== false)
1873
	{
1874
		$last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
1875
		preg_match('~\[/?(?=' . $alltags_regex . ')~i', $message, $matches, PREG_OFFSET_CAPTURE, $pos + 1);
1876
		$pos = isset($matches[0][1]) ? $matches[0][1] : false;
1877
1878
		// Failsafe.
1879
		if ($pos === false || $last_pos > $pos)
1880
			$pos = strlen($message) + 1;
1881
1882
		// Can't have a one letter smiley, URL, or email! (sorry.)
1883
		if ($last_pos < $pos - 1)
1884
		{
1885
			// Make sure the $last_pos is not negative.
1886
			$last_pos = max($last_pos, 0);
1887
1888
			// Pick a block of data to do some raw fixing on.
1889
			$data = substr($message, $last_pos, $pos - $last_pos);
1890
1891
			// Take care of some HTML!
1892
			if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false)
1893
			{
1894
				$data = preg_replace('~&lt;a\s+href=((?:&quot;)?)((?:https?://|ftps?://|mailto:|tel:)\S+?)\\1&gt;(.*?)&lt;/a&gt;~i', '[url=&quot;$2&quot;]$3[/url]', $data);
1895
1896
				// <br> should be empty.
1897
				$empty_tags = array('br', 'hr');
1898
				foreach ($empty_tags as $tag)
1899
					$data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '<' . $tag . '>', $data);
1900
1901
				// b, u, i, s, pre... basic tags.
1902
				$closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote', 'strong');
1903
				foreach ($closable_tags as $tag)
1904
				{
1905
					$diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
1906
					$data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
1907
1908
					if ($diff > 0)
1909
						$data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
1910
				}
1911
1912
				// Do <img ...> - with security... action= -> action-.
1913
				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);
1914
				if (!empty($matches[0]))
1915
				{
1916
					$replaces = array();
1917
					foreach ($matches[2] as $match => $imgtag)
1918
					{
1919
						$alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
1920
1921
						// Remove action= from the URL - no funny business, now.
1922
						if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0)
1923
							$imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
1924
1925
						// Check if the image is larger than allowed.
1926
						if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height']))
1927
						{
1928
							list ($width, $height) = url_image_size($imgtag);
1929
1930 View Code Duplication
							if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width'])
1931
							{
1932
								$height = (int) (($modSettings['max_image_width'] * $height) / $width);
1933
								$width = $modSettings['max_image_width'];
1934
							}
1935
1936 View Code Duplication
							if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height'])
1937
							{
1938
								$width = (int) (($modSettings['max_image_height'] * $width) / $height);
1939
								$height = $modSettings['max_image_height'];
1940
							}
1941
1942
							// Set the new image tag.
1943
							$replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
1944
						}
1945
						else
1946
							$replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
1947
					}
1948
1949
					$data = strtr($data, $replaces);
1950
				}
1951
			}
1952
1953
			if (!empty($modSettings['autoLinkUrls']))
1954
			{
1955
				// Are we inside tags that should be auto linked?
1956
				$no_autolink_area = false;
1957
				if (!empty($open_tags))
1958
				{
1959
					foreach ($open_tags as $open_tag)
1960
						if (in_array($open_tag['tag'], $no_autolink_tags))
1961
							$no_autolink_area = true;
1962
				}
1963
1964
				// Don't go backwards.
1965
				// @todo Don't think is the real solution....
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1966
				$lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
1967
				if ($pos < $lastAutoPos)
1968
					$no_autolink_area = true;
1969
				$lastAutoPos = $pos;
1970
1971
				if (!$no_autolink_area)
1972
				{
1973
					// Parse any URLs
1974
					if (!isset($disabled['url']) && strpos($data, '[url') === false)
1975
					{
1976
						$url_regex = '
1977
						(?:
1978
							# IRIs with a scheme (or at least an opening "//")
1979
							(?:
1980
								# URI scheme (or lack thereof for schemeless URLs)
1981
								(?:
1982
									# URL scheme and colon
1983
									\b[a-z][\w\-]+:
1984
									| # or
1985
									# A boundary followed by two slashes for schemeless URLs
1986
									(?<=^|\W)(?=//)
1987
								)
1988
1989
								# IRI "authority" chunk
1990
								(?:
1991
									# 2 slashes for IRIs with an "authority"
1992
									//
1993
									# then a domain name
1994
									(?:
1995
										# Either the reserved "localhost" domain name
1996
										localhost
1997
										| # or
1998
										# a run of Unicode domain name characters and a dot
1999
										[\p{L}\p{M}\p{N}\-.:@]+\.
2000
										# and then a TLD valid in the DNS or the reserved "local" TLD
2001
										(?:'. $modSettings['tld_regex'] .'|local)
2002
									)
2003
									# followed by a non-domain character or end of line
2004
									(?=[^\p{L}\p{N}\-.]|$)
2005
2006
									| # Or, if there is no "authority" per se (e.g. mailto: URLs) ...
2007
2008
									# a run of IRI characters
2009
									[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.:@]+[\p{L}\p{M}\p{N}]
2010
									# and then a dot and a closing IRI label
2011
									\.[\p{L}\p{M}\p{N}\-]+
2012
								)
2013
							)
2014
2015
							| # or
2016
2017
							# Naked domains (e.g. "example.com" in "Go to example.com for an example.")
2018
							(?:
2019
								# Preceded by start of line or a non-domain character
2020
								(?<=^|[^\p{L}\p{M}\p{N}\-:@])
2021
2022
								# A run of Unicode domain name characters (excluding [:@])
2023
								[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.]+[\p{L}\p{M}\p{N}]
2024
								# and then a dot and a valid TLD
2025
								\.' . $modSettings['tld_regex'] . '
2026
2027
								# Followed by either:
2028
								(?=
2029
									# end of line or a non-domain character (excluding [.:@])
2030
									$|[^\p{L}\p{N}\-]
2031
									| # or
2032
									# a dot followed by end of line or a non-domain character (excluding [.:@])
2033
									\.(?=$|[^\p{L}\p{N}\-])
2034
								)
2035
							)
2036
						)
2037
2038
						# IRI path, query, and fragment (if present)
2039
						(?:
2040
							# If any of these parts exist, must start with a single /
2041
							/
2042
2043
							# And then optionally:
2044
							(?:
2045
								# One or more of:
2046
								(?:
2047
									# a run of non-space, non-()<>
2048
									[^\s()<>]+
2049
									| # or
2050
									# balanced parens, up to 2 levels
2051
									\(([^\s()<>]+|(\([^\s()<>]+\)))*\)
2052
								)+
2053
2054
								# End with:
2055
								(?:
2056
									# balanced parens, up to 2 levels
2057
									\(([^\s()<>]+|(\([^\s()<>]+\)))*\)
2058
									| # or
2059
									# not a space or one of these punct char
2060
									[^\s`!()\[\]{};:\'".,<>?«»“”‘’/]
2061
									| # or
2062
									# a trailing slash (but not two in a row)
2063
									(?<!/)/
2064
								)
2065
							)?
2066
						)?
2067
						';
2068
2069
						$data = preg_replace_callback('~' . $url_regex . '~xi' . ($context['utf8'] ? 'u' : ''), function ($matches) {
2070
							$url = array_shift($matches);
2071
2072
							$scheme = parse_url($url, PHP_URL_SCHEME);
2073
2074
							if ($scheme == 'mailto')
2075
							{
2076
								$email_address = str_replace('mailto:', '', $url);
2077
								if (!isset($disabled['email']) && filter_var($email_address, FILTER_VALIDATE_EMAIL) !== false)
0 ignored issues
show
Bug introduced by
The variable $disabled seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
2078
									return '[email=' . $email_address . ']' . $url . '[/email]';
2079
								else
2080
									return $url;
2081
							}
2082
2083
							// Are we linking a schemeless URL or naked domain name (e.g. "example.com")?
2084
							if (empty($scheme))
2085
								$fullUrl = '//' . ltrim($url, ':/');
2086
							else
2087
								$fullUrl = $url;
2088
2089
							return '[url=&quot;' . str_replace(array('[', ']'), array('&#91;', '&#93;'), $fullUrl) . '&quot;]' . $url . '[/url]';
2090
						}, $data);
2091
					}
2092
2093
					// Next, emails...  Must be careful not to step on enablePostHTML logic above...
2094
					if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false && stripos($data, 'mailto:') === false)
2095
					{
2096
						$email_regex = '
2097
						# Preceded by a non-domain character or start of line
2098
						(?<=^|[^\p{L}\p{M}\p{N}\-\.])
2099
2100
						# An email address
2101
						[\p{L}\p{M}\p{N}_\-.]{1,80}
2102
						@
2103
						[\p{L}\p{M}\p{N}\-.]+
2104
						\.
2105
						'. $modSettings['tld_regex'] . '
2106
2107
						# Followed by either:
2108
						(?=
2109
							# end of line or a non-domain character (excluding the dot)
2110
							$|[^\p{L}\p{M}\p{N}\-]
2111
							| # or
2112
							# a dot followed by end of line or a non-domain character
2113
							\.(?=$|[^\p{L}\p{M}\p{N}\-])
2114
						)';
2115
2116
						$data = preg_replace('~' . $email_regex . '~xi' . ($context['utf8'] ? 'u' : ''), '[email]$0[/email]', $data);
2117
					}
2118
				}
2119
			}
2120
2121
			$data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
2122
2123
			// If it wasn't changed, no copying or other boring stuff has to happen!
2124
			if ($data != substr($message, $last_pos, $pos - $last_pos))
2125
			{
2126
				$message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
2127
2128
				// Since we changed it, look again in case we added or removed a tag.  But we don't want to skip any.
2129
				$old_pos = strlen($data) + $last_pos;
2130
				$pos = strpos($message, '[', $last_pos);
2131
				$pos = $pos === false ? $old_pos : min($pos, $old_pos);
2132
			}
2133
		}
2134
2135
		// Are we there yet?  Are we there yet?
2136
		if ($pos >= strlen($message) - 1)
2137
			break;
2138
2139
		$tags = strtolower($message[$pos + 1]);
2140
2141
		if ($tags == '/' && !empty($open_tags))
2142
		{
2143
			$pos2 = strpos($message, ']', $pos + 1);
2144
			if ($pos2 == $pos + 2)
2145
				continue;
2146
2147
			$look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
2148
2149
			// A closing tag that doesn't match any open tags? Skip it.
2150
			if (!in_array($look_for, array_map(function($code){return $code['tag'];}, $open_tags)))
2151
				continue;
2152
2153
			$to_close = array();
2154
			$block_level = null;
2155
2156
			do
2157
			{
2158
				$tag = array_pop($open_tags);
2159
				if (!$tag)
2160
					break;
2161
2162
				if (!empty($tag['block_level']))
2163
				{
2164
					// Only find out if we need to.
2165
					if ($block_level === false)
2166
					{
2167
						array_push($open_tags, $tag);
2168
						break;
2169
					}
2170
2171
					// The idea is, if we are LOOKING for a block level tag, we can close them on the way.
2172 View Code Duplication
					if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]]))
2173
					{
2174
						foreach ($bbc_codes[$look_for[0]] as $temp)
2175
							if ($temp['tag'] == $look_for)
2176
							{
2177
								$block_level = !empty($temp['block_level']);
2178
								break;
2179
							}
2180
					}
2181
2182
					if ($block_level !== true)
2183
					{
2184
						$block_level = false;
2185
						array_push($open_tags, $tag);
2186
						break;
2187
					}
2188
				}
2189
2190
				$to_close[] = $tag;
2191
			}
2192
			while ($tag['tag'] != $look_for);
2193
2194
			// Did we just eat through everything and not find it?
2195
			if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)))
2196
			{
2197
				$open_tags = $to_close;
2198
				continue;
2199
			}
2200
			elseif (!empty($to_close) && $tag['tag'] != $look_for)
2201
			{
2202 View Code Duplication
				if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]]))
2203
				{
2204
					foreach ($bbc_codes[$look_for[0]] as $temp)
2205
						if ($temp['tag'] == $look_for)
2206
						{
2207
							$block_level = !empty($temp['block_level']);
2208
							break;
2209
						}
2210
				}
2211
2212
				// We're not looking for a block level tag (or maybe even a tag that exists...)
2213
				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...
2214
				{
2215
					foreach ($to_close as $tag)
2216
						array_push($open_tags, $tag);
2217
					continue;
2218
				}
2219
			}
2220
2221
			foreach ($to_close as $tag)
2222
			{
2223
				$message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
2224
				$pos += strlen($tag['after']) + 2;
2225
				$pos2 = $pos - 1;
2226
2227
				// See the comment at the end of the big loop - just eating whitespace ;).
2228
				$whitespace_regex = '';
2229
				if (!empty($tag['block_level']))
2230
					$whitespace_regex .= '(&nbsp;|\s)*(<br>)?';
2231
				// Trim one line of whitespace after unnested tags, but all of it after nested ones
2232 View Code Duplication
				if (!empty($tag['trim']) && $tag['trim'] != 'inside')
2233
					$whitespace_regex .= empty($tag['require_parents']) ? '(&nbsp;|\s)*' : '(<br>|&nbsp;|\s)*';
2234
2235 View Code Duplication
				if (!empty($whitespace_regex) && preg_match('~' . $whitespace_regex . '~', substr($message, $pos), $matches) != 0)
2236
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2237
			}
2238
2239
			if (!empty($to_close))
2240
			{
2241
				$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...
2242
				$pos--;
2243
			}
2244
2245
			continue;
2246
		}
2247
2248
		// No tags for this character, so just keep going (fastest possible course.)
2249
		if (!isset($bbc_codes[$tags]))
2250
			continue;
2251
2252
		$inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
2253
		$tag = null;
2254
		foreach ($bbc_codes[$tags] as $possible)
2255
		{
2256
			$pt_strlen = strlen($possible['tag']);
2257
2258
			// Not a match?
2259
			if (strtolower(substr($message, $pos + 1, $pt_strlen)) != $possible['tag'])
2260
				continue;
2261
2262
			$next_c = isset($message[$pos + 1 + $pt_strlen]) ? $message[$pos + 1 + $pt_strlen] : '';
2263
			
2264
			// A tag is the last char maybe
2265
			if ($next_c == '')
2266
				break;
2267
2268
			// A test validation?
2269
			if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + $pt_strlen + 1)) === 0)
2270
				continue;
2271
			// Do we want parameters?
2272
			elseif (!empty($possible['parameters']))
2273
			{
2274
				if ($next_c != ' ')
2275
					continue;
2276
			}
2277
			elseif (isset($possible['type']))
2278
			{
2279
				// Do we need an equal sign?
2280
				if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=')
2281
					continue;
2282
				// Maybe we just want a /...
2283
				if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + $pt_strlen, 2) != '/]' && substr($message, $pos + 1 + $pt_strlen, 3) != ' /]')
2284
					continue;
2285
				// An immediate ]?
2286
				if ($possible['type'] == 'unparsed_content' && $next_c != ']')
2287
					continue;
2288
			}
2289
			// No type means 'parsed_content', which demands an immediate ] without parameters!
2290
			elseif ($next_c != ']')
2291
				continue;
2292
2293
			// Check allowed tree?
2294
			if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents'])))
2295
				continue;
2296
			elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children']))
2297
				continue;
2298
			// If this is in the list of disallowed child tags, don't parse it.
2299
			elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children']))
2300
				continue;
2301
2302
			$pos1 = $pos + 1 + $pt_strlen + 1;
2303
2304
			// Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
2305
			if ($possible['tag'] == 'quote')
2306
			{
2307
				// Start with standard
2308
				$quote_alt = false;
2309
				foreach ($open_tags as $open_quote)
2310
				{
2311
					// Every parent quote this quote has flips the styling
2312
					if ($open_quote['tag'] == 'quote')
2313
						$quote_alt = !$quote_alt;
2314
				}
2315
				// Add a class to the quote to style alternating blockquotes
2316
				$possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
2317
			}
2318
2319
			// This is long, but it makes things much easier and cleaner.
2320
			if (!empty($possible['parameters']))
2321
			{
2322
				// Build a regular expression for each parameter for the current tag.
2323
				$preg = array();
2324
				foreach ($possible['parameters'] as $p => $info)
2325
					$preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . '\s*)' . (empty($info['optional']) ? '' : '?');
2326
2327
				// Extract the string that potentially holds our parameters.
2328
				$blob = preg_split('~\[/?(?:' . $alltags_regex . ')~i', substr($message, $pos));
2329
				$blobs = preg_split('~\]~i', $blob[1]);
2330
2331
				$splitters = implode('=|', array_keys($possible['parameters'])) . '=';
2332
2333
				// Progressively append more blobs until we find our parameters or run out of blobs
2334
				$blob_counter = 1;
2335
				while ($blob_counter <= count($blobs))
2336
				{
2337
2338
					$given_param_string = implode(']', array_slice($blobs, 0, $blob_counter++));
2339
2340
					$given_params = preg_split('~\s(?=(' . $splitters . '))~i', $given_param_string);
2341
					sort($given_params, SORT_STRING);
2342
2343
					$match = preg_match('~^' . implode('', $preg) . '$~i', implode(' ', $given_params), $matches) !== 0;
2344
2345
					if ($match)
2346
						$blob_counter = count($blobs) + 1;
2347
				}
2348
2349
				// Didn't match our parameter list, try the next possible.
2350
				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...
2351
					continue;
2352
2353
				$params = array();
2354
				for ($i = 1, $n = count($matches); $i < $n; $i += 2)
2355
				{
2356
					$key = strtok(ltrim($matches[$i]), '=');
2357
					if (isset($possible['parameters'][$key]['value']))
2358
						$params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
2359
					elseif (isset($possible['parameters'][$key]['validate']))
2360
						$params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
2361
					else
2362
						$params['{' . $key . '}'] = $matches[$i + 1];
2363
2364
					// Just to make sure: replace any $ or { so they can't interpolate wrongly.
2365
					$params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
2366
				}
2367
2368
				foreach ($possible['parameters'] as $p => $info)
2369
				{
2370
					if (!isset($params['{' . $p . '}']))
2371
						$params['{' . $p . '}'] = '';
2372
				}
2373
2374
				$tag = $possible;
2375
2376
				// Put the parameters into the string.
2377
				if (isset($tag['before']))
2378
					$tag['before'] = strtr($tag['before'], $params);
2379
				if (isset($tag['after']))
2380
					$tag['after'] = strtr($tag['after'], $params);
2381
				if (isset($tag['content']))
2382
					$tag['content'] = strtr($tag['content'], $params);
2383
2384
				$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...
2385
			}
2386
			else
2387
			{
2388
				$tag = $possible;
2389
				$params = array();
2390
			}
2391
			break;
2392
		}
2393
2394
		// Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
2395
		if ($smileys !== false && $tag === null && isset($itemcodes[$message[$pos + 1]]) && $message[$pos + 2] == ']' && !isset($disabled['list']) && !isset($disabled['li']))
2396
		{
2397
			if ($message[$pos + 1] == '0' && !in_array($message[$pos - 1], array(';', ' ', "\t", "\n", '>')))
2398
				continue;
2399
2400
			$tag = $itemcodes[$message[$pos + 1]];
2401
2402
			// First let's set up the tree: it needs to be in a list, or after an li.
2403
			if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li'))
2404
			{
2405
				$open_tags[] = array(
2406
					'tag' => 'list',
2407
					'after' => '</ul>',
2408
					'block_level' => true,
2409
					'require_children' => array('li'),
2410
					'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2411
				);
2412
				$code = '<ul class="bbc_list">';
2413
			}
2414
			// We're in a list item already: another itemcode?  Close it first.
2415
			elseif ($inside['tag'] == 'li')
2416
			{
2417
				array_pop($open_tags);
2418
				$code = '</li>';
2419
			}
2420
			else
2421
				$code = '';
2422
2423
			// Now we open a new tag.
2424
			$open_tags[] = array(
2425
				'tag' => 'li',
2426
				'after' => '</li>',
2427
				'trim' => 'outside',
2428
				'block_level' => true,
2429
				'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2430
			);
2431
2432
			// First, open the tag...
2433
			$code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>';
2434
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
2435
			$pos += strlen($code) - 1 + 2;
2436
2437
			// Next, find the next break (if any.)  If there's more itemcode after it, keep it going - otherwise close!
2438
			$pos2 = strpos($message, '<br>', $pos);
2439
			$pos3 = strpos($message, '[/', $pos);
2440
			if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false))
2441
			{
2442
				preg_match('~^(<br>|&nbsp;|\s|\[)+~', substr($message, $pos2 + 4), $matches);
2443
				$message = substr($message, 0, $pos2) . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . substr($message, $pos2);
2444
2445
				$open_tags[count($open_tags) - 2]['after'] = '</ul>';
2446
			}
2447
			// Tell the [list] that it needs to close specially.
2448
			else
2449
			{
2450
				// Move the li over, because we're not sure what we'll hit.
2451
				$open_tags[count($open_tags) - 1]['after'] = '';
2452
				$open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
2453
			}
2454
2455
			continue;
2456
		}
2457
2458
		// Implicitly close lists and tables if something other than what's required is in them.  This is needed for itemcode.
2459
		if ($tag === null && $inside !== null && !empty($inside['require_children']))
2460
		{
2461
			array_pop($open_tags);
2462
2463
			$message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
2464
			$pos += strlen($inside['after']) - 1 + 2;
2465
		}
2466
2467
		// No tag?  Keep looking, then.  Silly people using brackets without actual tags.
2468
		if ($tag === null)
2469
			continue;
2470
2471
		// Propagate the list to the child (so wrapping the disallowed tag won't work either.)
2472
		if (isset($inside['disallow_children']))
2473
			$tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
2474
2475
		// Is this tag disabled?
2476
		if (isset($disabled[$tag['tag']]))
2477
		{
2478
			if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content']))
2479
			{
2480
				$tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
2481
				$tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
2482
				$tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
2483
			}
2484
			elseif (isset($tag['disabled_before']) || isset($tag['disabled_after']))
2485
			{
2486
				$tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
2487
				$tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
2488
			}
2489
			else
2490
				$tag['content'] = $tag['disabled_content'];
2491
		}
2492
2493
		// we use this a lot
2494
		$tag_strlen = strlen($tag['tag']);
2495
2496
		// The only special case is 'html', which doesn't need to close things.
2497
		if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level']))
2498
		{
2499
			$n = count($open_tags) - 1;
2500
			while (empty($open_tags[$n]['block_level']) && $n >= 0)
2501
				$n--;
2502
2503
			// Close all the non block level tags so this tag isn't surrounded by them.
2504
			for ($i = count($open_tags) - 1; $i > $n; $i--)
2505
			{
2506
				$message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
2507
				$ot_strlen = strlen($open_tags[$i]['after']);
2508
				$pos += $ot_strlen + 2;
2509
				$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...
2510
2511
				// Trim or eat trailing stuff... see comment at the end of the big loop.
2512
				$whitespace_regex = '';
2513
				if (!empty($tag['block_level']))
2514
					$whitespace_regex .= '(&nbsp;|\s)*(<br>)?';
2515 View Code Duplication
				if (!empty($tag['trim']) && $tag['trim'] != 'inside')
2516
					$whitespace_regex .= empty($tag['require_parents']) ? '(&nbsp;|\s)*' : '(<br>|&nbsp;|\s)*';
2517 View Code Duplication
				if (!empty($whitespace_regex) && preg_match('~' . $whitespace_regex . '~', substr($message, $pos), $matches) != 0)
2518
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2519
2520
				array_pop($open_tags);
2521
			}
2522
		}
2523
2524
		// Can't read past the end of the message
2525
		$pos1 = min(strlen($message), $pos1);
2526
2527
		// No type means 'parsed_content'.
2528
		if (!isset($tag['type']))
2529
		{
2530
			// @todo Check for end tag first, so people can say "I like that [i] tag"?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
2531
			$open_tags[] = $tag;
2532
			$message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
2533
			$pos += strlen($tag['before']) - 1 + 2;
2534
		}
2535
		// Don't parse the content, just skip it.
2536
		elseif ($tag['type'] == 'unparsed_content')
2537
		{
2538
			$pos2 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos1);
2539
			if ($pos2 === false)
2540
				continue;
2541
2542
			$data = substr($message, $pos1, $pos2 - $pos1);
2543
2544
			if (!empty($tag['block_level']) && substr($data, 0, 4) == '<br>')
2545
				$data = substr($data, 4);
2546
2547
			if (isset($tag['validate']))
2548
				$tag['validate']($tag, $data, $disabled, $params);
0 ignored issues
show
Bug introduced by
The variable $params 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...
2549
2550
			$code = strtr($tag['content'], array('$1' => $data));
2551
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + $tag_strlen);
2552
2553
			$pos += strlen($code) - 1 + 2;
2554
			$last_pos = $pos + 1;
2555
2556
		}
2557
		// Don't parse the content, just skip it.
2558
		elseif ($tag['type'] == 'unparsed_equals_content')
2559
		{
2560
			// The value may be quoted for some tags - check.
2561 View Code Duplication
			if (isset($tag['quoted']))
2562
			{
2563
				$quoted = substr($message, $pos1, 6) == '&quot;';
2564
				if ($tag['quoted'] != 'optional' && !$quoted)
2565
					continue;
2566
2567
				if ($quoted)
2568
					$pos1 += 6;
2569
			}
2570
			else
2571
				$quoted = false;
2572
2573
			$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...
2574
			if ($pos2 === false)
2575
				continue;
2576
2577
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2578
			if ($pos3 === false)
2579
				continue;
2580
2581
			$data = array(
2582
				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...
2583
				substr($message, $pos1, $pos2 - $pos1)
2584
			);
2585
2586
			if (!empty($tag['block_level']) && substr($data[0], 0, 4) == '<br>')
2587
				$data[0] = substr($data[0], 4);
2588
2589
			// Validation for my parking, please!
2590
			if (isset($tag['validate']))
2591
				$tag['validate']($tag, $data, $disabled, $params);
2592
2593
			$code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
2594
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2595
			$pos += strlen($code) - 1 + 2;
2596
		}
2597
		// A closed tag, with no content or value.
2598
		elseif ($tag['type'] == 'closed')
2599
		{
2600
			$pos2 = strpos($message, ']', $pos);
2601
			$message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
2602
			$pos += strlen($tag['content']) - 1 + 2;
2603
		}
2604
		// This one is sorta ugly... :/.  Unfortunately, it's needed for flash.
2605
		elseif ($tag['type'] == 'unparsed_commas_content')
2606
		{
2607
			$pos2 = strpos($message, ']', $pos1);
2608
			if ($pos2 === false)
2609
				continue;
2610
2611
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2612
			if ($pos3 === false)
2613
				continue;
2614
2615
			// We want $1 to be the content, and the rest to be csv.
2616
			$data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
2617
			$data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
2618
2619
			if (isset($tag['validate']))
2620
				$tag['validate']($tag, $data, $disabled, $params);
2621
2622
			$code = $tag['content'];
2623 View Code Duplication
			foreach ($data as $k => $d)
2624
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2625
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2626
			$pos += strlen($code) - 1 + 2;
2627
		}
2628
		// This has parsed content, and a csv value which is unparsed.
2629
		elseif ($tag['type'] == 'unparsed_commas')
2630
		{
2631
			$pos2 = strpos($message, ']', $pos1);
2632
			if ($pos2 === false)
2633
				continue;
2634
2635
			$data = explode(',', substr($message, $pos1, $pos2 - $pos1));
2636
2637
			if (isset($tag['validate']))
2638
				$tag['validate']($tag, $data, $disabled, $params);
2639
2640
			// Fix after, for disabled code mainly.
2641 View Code Duplication
			foreach ($data as $k => $d)
2642
				$tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
2643
2644
			$open_tags[] = $tag;
2645
2646
			// Replace them out, $1, $2, $3, $4, etc.
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2647
			$code = $tag['before'];
2648 View Code Duplication
			foreach ($data as $k => $d)
2649
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2650
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
2651
			$pos += strlen($code) - 1 + 2;
2652
		}
2653
		// A tag set to a value, parsed or not.
2654
		elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals')
2655
		{
2656
			// The value may be quoted for some tags - check.
2657 View Code Duplication
			if (isset($tag['quoted']))
2658
			{
2659
				$quoted = substr($message, $pos1, 6) == '&quot;';
2660
				if ($tag['quoted'] != 'optional' && !$quoted)
2661
					continue;
2662
2663
				if ($quoted)
2664
					$pos1 += 6;
2665
			}
2666
			else
2667
				$quoted = false;
2668
2669
			$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...
2670
			if ($pos2 === false)
2671
				continue;
2672
2673
			$data = substr($message, $pos1, $pos2 - $pos1);
2674
2675
			// Validation for my parking, please!
2676
			if (isset($tag['validate']))
2677
				$tag['validate']($tag, $data, $disabled, $params);
2678
2679
			// For parsed content, we must recurse to avoid security problems.
2680
			if ($tag['type'] != 'unparsed_equals')
2681
				$data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
2682
2683
			$tag['after'] = strtr($tag['after'], array('$1' => $data));
2684
2685
			$open_tags[] = $tag;
2686
2687
			$code = strtr($tag['before'], array('$1' => $data));
2688
			$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...
2689
			$pos += strlen($code) - 1 + 2;
2690
		}
2691
2692
		// If this is block level, eat any breaks after it.
2693
		if (!empty($tag['block_level']) && substr($message, $pos + 1, 4) == '<br>')
2694
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 5);
2695
2696
		// Are we trimming outside this tag?
2697
		if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos + 1), $matches) != 0)
2698
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
2699
	}
2700
2701
	// Close any remaining tags.
2702
	while ($tag = array_pop($open_tags))
2703
		$message .= "\n" . $tag['after'] . "\n";
2704
2705
	// Parse the smileys within the parts where it can be done safely.
2706
	if ($smileys === true)
2707
	{
2708
		$message_parts = explode("\n", $message);
2709
		for ($i = 0, $n = count($message_parts); $i < $n; $i += 2)
2710
			parsesmileys($message_parts[$i]);
2711
2712
		$message = implode('', $message_parts);
2713
	}
2714
2715
	// No smileys, just get rid of the markers.
2716
	else
2717
		$message = strtr($message, array("\n" => ''));
2718
2719
	if ($message !== '' && $message[0] === ' ')
2720
		$message = '&nbsp;' . substr($message, 1);
2721
2722
	// Cleanup whitespace.
2723
	$message = strtr($message, array('  ' => ' &nbsp;', "\r" => '', "\n" => '<br>', '<br> ' => '<br>&nbsp;', '&#13;' => "\n"));
2724
2725
	// Allow mods access to what parse_bbc created
2726
	call_integration_hook('integrate_post_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
2727
2728
	// Cache the output if it took some time...
2729
	if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05)
2730
		cache_put_data($cache_key, $message, 240);
2731
2732
	// If this was a force parse revert if needed.
2733
	if (!empty($parse_tags))
2734
	{
2735
		if (empty($temp_bbc))
2736
			$bbc_codes = array();
2737
		else
2738
		{
2739
			$bbc_codes = $temp_bbc;
2740
			unset($temp_bbc);
2741
		}
2742
	}
2743
2744
	return $message;
2745
}
2746
2747
/**
2748
 * Parse smileys in the passed message.
2749
 *
2750
 * The smiley parsing function which makes pretty faces appear :).
2751
 * If custom smiley sets are turned off by smiley_enable, the default set of smileys will be used.
2752
 * These are specifically not parsed in code tags [url=mailto:[email protected]]
2753
 * Caches the smileys from the database or array in memory.
2754
 * Doesn't return anything, but rather modifies message directly.
2755
 *
2756
 * @param string &$message The message to parse smileys in
2757
 */
2758
function parsesmileys(&$message)
2759
{
2760
	global $modSettings, $txt, $user_info, $context, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2761
	static $smileyPregSearch = null, $smileyPregReplacements = array();
2762
2763
	// No smiley set at all?!
2764
	if ($user_info['smiley_set'] == 'none' || trim($message) == '')
2765
		return;
2766
2767
	// If smileyPregSearch hasn't been set, do it now.
2768
	if (empty($smileyPregSearch))
2769
	{
2770
		// Use the default smileys if it is disabled. (better for "portability" of smileys.)
2771
		if (empty($modSettings['smiley_enable']))
2772
		{
2773
			$smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)');
2774
			$smileysto = array('evil.png', 'cheesy.png', 'rolleyes.png', 'angry.png', 'laugh.png', 'smiley.png', 'wink.png', 'grin.png', 'sad.png', 'shocked.png', 'cool.png', 'tongue.png', 'huh.png', 'embarrassed.png', 'lipsrsealed.png', 'kiss.png', 'cry.png', 'undecided.png', 'azn.png', 'afro.png', 'police.png', 'angel.png');
2775
			$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'], '', '', '', '');
2776
		}
2777
		else
2778
		{
2779
			// Load the smileys in reverse order by length so they don't get parsed wrong.
2780
			if (($temp = cache_get_data('parsing_smileys', 480)) == null)
2781
			{
2782
				$result = $smcFunc['db_query']('', '
2783
					SELECT code, filename, description
2784
					FROM {db_prefix}smileys
2785
					ORDER BY LENGTH(code) DESC',
2786
					array(
2787
					)
2788
				);
2789
				$smileysfrom = array();
2790
				$smileysto = array();
2791
				$smileysdescs = array();
2792
				while ($row = $smcFunc['db_fetch_assoc']($result))
2793
				{
2794
					$smileysfrom[] = $row['code'];
2795
					$smileysto[] = $smcFunc['htmlspecialchars']($row['filename']);
2796
					$smileysdescs[] = $row['description'];
2797
				}
2798
				$smcFunc['db_free_result']($result);
2799
2800
				cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480);
2801
			}
2802
			else
2803
				list ($smileysfrom, $smileysto, $smileysdescs) = $temp;
2804
		}
2805
2806
		// The non-breaking-space is a complex thing...
2807
		$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0';
2808
2809
		// This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:[email protected]] doesn't parse the :D smiley)
2810
		$smileyPregReplacements = array();
2811
		$searchParts = array();
2812
		$smileys_path = $smcFunc['htmlspecialchars']($modSettings['smileys_url'] . '/' . $user_info['smiley_set'] . '/');
2813
2814
		for ($i = 0, $n = count($smileysfrom); $i < $n; $i++)
2815
		{
2816
			$specialChars = $smcFunc['htmlspecialchars']($smileysfrom[$i], ENT_QUOTES);
2817
			$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">';
2818
2819
			$smileyPregReplacements[$smileysfrom[$i]] = $smileyCode;
2820
2821
			$searchParts[] = preg_quote($smileysfrom[$i], '~');
2822
			if ($smileysfrom[$i] != $specialChars)
2823
			{
2824
				$smileyPregReplacements[$specialChars] = $smileyCode;
2825
				$searchParts[] = preg_quote($specialChars, '~');
2826
			}
2827
		}
2828
2829
		$smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|(?<![a-zA-Z0-9])\(|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~' . ($context['utf8'] ? 'u' : '');
2830
	}
2831
2832
	// Replace away!
2833
	$message = preg_replace_callback($smileyPregSearch,
2834
		function ($matches) use ($smileyPregReplacements)
2835
		{
2836
			return $smileyPregReplacements[$matches[1]];
2837
		}, $message);
2838
}
2839
2840
/**
2841
 * Highlight any code.
2842
 *
2843
 * Uses PHP's highlight_string() to highlight PHP syntax
2844
 * does special handling to keep the tabs in the code available.
2845
 * used to parse PHP code from inside [code] and [php] tags.
2846
 *
2847
 * @param string $code The code
2848
 * @return string The code with highlighted HTML.
2849
 */
2850
function highlight_php_code($code)
2851
{
2852
	// Remove special characters.
2853
	$code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", '<br>' => "\n", "\t" => 'SMF_TAB();', '&#91;' => '[')));
2854
2855
	$oldlevel = error_reporting(0);
2856
2857
	$buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true));
2858
2859
	error_reporting($oldlevel);
2860
2861
	// Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P.
2862
	$buffer = preg_replace('~SMF_TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer);
2863
2864
	return strtr($buffer, array('\'' => '&#039;', '<code>' => '', '</code>' => ''));
2865
}
2866
2867
/**
2868
 * Make sure the browser doesn't come back and repost the form data.
2869
 * Should be used whenever anything is posted.
2870
 *
2871
 * @param string $setLocation The URL to redirect them to
2872
 * @param bool $refresh Whether to use a meta refresh instead
2873
 * @param bool $permanent Whether to send a 301 Moved Permanently instead of a 302 Moved Temporarily
2874
 */
2875
function redirectexit($setLocation = '', $refresh = false, $permanent = false)
2876
{
2877
	global $scripturl, $context, $modSettings, $db_show_debug, $db_cache;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2878
2879
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
2880
	if (!empty($context['flush_mail']))
2881
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
2882
		AddMailQueue(true);
2883
2884
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
2885
2886
	if ($add)
2887
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
2888
2889
	// Put the session ID in.
2890
	if (defined('SID') && SID != '')
2891
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
2892
	// Keep that debug in their for template debugging!
2893 View Code Duplication
	elseif (isset($_GET['debug']))
2894
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
2895
2896
	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'])))
2897
	{
2898
		if (defined('SID') && SID != '')
2899
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~',
2900
				function ($m) use ($scripturl)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $m. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2901
				{
2902
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html?' . SID. (isset($m[2]) ? "$m[2]" : "");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $m instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Comprehensibility introduced by
The string literal does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
2903
				}, $setLocation);
2904 View Code Duplication
		else
2905
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~',
2906
				function ($m) use ($scripturl)
2907
				{
2908
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? "$m[2]" : "");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $m instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Comprehensibility introduced by
The string literal does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
2909
				}, $setLocation);
2910
	}
2911
2912
	// Maybe integrations want to change where we are heading?
2913
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh, &$permanent));
2914
2915
	// Set the header.
2916
	header('Location: ' . str_replace(' ', '%20', $setLocation), true, $permanent ? 301 : 302);
0 ignored issues
show
Security Response Splitting introduced by
'Location: ' . str_repla...', '%20', $setLocation) can contain request data and is used in response header context(s) leading to a potential security vulnerability.

67 paths for user data to reach this point

  1. Path: Read from $_GET, and $request is assigned in proxy.php on line 98
  1. Read from $_GET, and $request is assigned
    in proxy.php on line 98
  2. $request is passed to redirectexit()
    in proxy.php on line 116
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  2. Path: Read from $_POST, and $_POST['start_date'] is passed through date_parse(), and $d is assigned in Sources/Calendar.php on line 356
  1. Read from $_POST, and $_POST['start_date'] is passed through date_parse(), and $d is assigned
    in Sources/Calendar.php on line 356
  2. $month is assigned
    in Sources/Calendar.php on line 358
  3. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  3. Path: Read from $_POST, and $_POST['start_datetime'] is passed through date_parse(), and $d is assigned in Sources/Calendar.php on line 363
  1. Read from $_POST, and $_POST['start_datetime'] is passed through date_parse(), and $d is assigned
    in Sources/Calendar.php on line 363
  2. $month is assigned
    in Sources/Calendar.php on line 365
  3. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  4. Path: Read from $_POST, and $month is assigned in Sources/Calendar.php on line 372
  1. Read from $_POST, and $month is assigned
    in Sources/Calendar.php on line 372
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  5. Path: Read from $_POST, and $year is assigned in Sources/Calendar.php on line 371
  1. Read from $_POST, and $year is assigned
    in Sources/Calendar.php on line 371
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  6. Path: Read from $_POST, and $day is assigned in Sources/Calendar.php on line 373
  1. Read from $_POST, and $day is assigned
    in Sources/Calendar.php on line 373
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  7. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Display.php on line 1696
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Display.php on line 1696
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  8. Path: Read from $_REQUEST, and !empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Display.php on line 1804
  1. Read from $_REQUEST, and !empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Display.php on line 1804
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  9. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/Load.php on line 806
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/Load.php on line 806
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  10. Path: Read from $_GET, and $_GET is passed through key(), and $k is assigned in Sources/Load.php on line 1880
  1. Read from $_GET, and $_GET is passed through key(), and $k is assigned
    in Sources/Load.php on line 1880
  2. 'wwwRedirect;' . $k . '=' . $v is passed to redirectexit()
    in Sources/Load.php on line 1884
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  11. Path: Read from $_GET, and $_GET is passed through current(), and $v is assigned in Sources/Load.php on line 1881
  1. Read from $_GET, and $_GET is passed through current(), and $v is assigned
    in Sources/Load.php on line 1881
  2. 'wwwRedirect;' . $k . '=' . $v is passed to redirectexit()
    in Sources/Load.php on line 1884
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  12. Path: Read from $_REQUEST, and 'action=admin;area=manageattachments;sa=browse;' . $_REQUEST['type'] . ';sort=' . $_GET['sort'] . (isset($_GET['desc']) ? ';desc' : '') . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageAttachments.php on line 917
  1. Read from $_REQUEST, and 'action=admin;area=manageattachments;sa=browse;' . $_REQUEST['type'] . ';sort=' . $_GET['sort'] . (isset($_GET['desc']) ? ';desc' : '') . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageAttachments.php on line 917
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  13. Path: Read from $_GET, and 'action=admin;area=logs;sa=errorlog' . (isset($_REQUEST['desc']) ? ';desc' : '') . ';start=' . $_GET['start'] . (isset($filter) ? ';filter=' . $_GET['filter'] . ';value=' . $_GET['value'] : '') is passed to redirectexit() in Sources/ManageErrors.php on line 367
  1. Read from $_GET, and 'action=admin;area=logs;sa=errorlog' . (isset($_REQUEST['desc']) ? ';desc' : '') . ';start=' . $_GET['start'] . (isset($filter) ? ';filter=' . $_GET['filter'] . ';value=' . $_GET['value'] : '') is passed to redirectexit()
    in Sources/ManageErrors.php on line 367
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  14. Path: Read from $_GET, and $context is assigned in Sources/ManageLanguages.php on line 792
  1. Read from $_GET, and $context is assigned
    in Sources/ManageLanguages.php on line 792
  2. $context is assigned
    in Sources/ManageLanguages.php on line 797
  3. $context is assigned
    in Sources/ManageLanguages.php on line 845
  4. 'action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/ManageLanguages.php on line 942
  5. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  15. Path: Read from $_REQUEST, and $_REQUEST['tfid'] is passed through explode(), and $file_id is assigned in Sources/ManageLanguages.php on line 793
  1. Read from $_REQUEST, and $_REQUEST['tfid'] is passed through explode(), and $file_id is assigned
    in Sources/ManageLanguages.php on line 793
  2. $current_file is assigned
    in Sources/ManageLanguages.php on line 842
  3. $current_file is passed through sprintf(), and $context is assigned
    in Sources/ManageLanguages.php on line 1006
  4. $context is assigned
    in Sources/ManageLanguages.php on line 1044
  5. 'action=admin;area=languages;sa=editlang;lid=' . $context['lang_id'] is passed to redirectexit()
    in Sources/ManageLanguages.php on line 1171
  6. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  16. Path: Read from $_GET, and $context is assigned in Sources/ManageMaintenance.php on line 1843
  1. Read from $_GET, and $context is assigned
    in Sources/ManageMaintenance.php on line 1843
  2. $context is assigned
    in Sources/ManageMaintenance.php on line 1844
  3. 'action=admin;area=maintain;sa=hooks' . $context['filter_url'] is passed to redirectexit()
    in Sources/ManageMaintenance.php on line 1863
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  17. Path: Read from $_GET, and $context is assigned in Sources/ManageMaintenance.php on line 1844
  1. Read from $_GET, and $context is assigned
    in Sources/ManageMaintenance.php on line 1844
  2. 'action=admin;area=maintain;sa=hooks' . $context['filter_url'] is passed to redirectexit()
    in Sources/ManageMaintenance.php on line 1863
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  18. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $_REQUEST['filter'] . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1061
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $_REQUEST['filter'] . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1061
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  19. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1065
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1065
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  20. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1101
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1101
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  21. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1286
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1286
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  22. Path: Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit() in Sources/ManagePermissions.php on line 464
  1. Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit()
    in Sources/ManagePermissions.php on line 464
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  23. Path: Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit() in Sources/ManagePermissions.php on line 471
  1. Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit()
    in Sources/ManagePermissions.php on line 471
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  24. Path: Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit() in Sources/ManagePermissions.php on line 486
  1. Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit()
    in Sources/ManagePermissions.php on line 486
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  25. Path: Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit() in Sources/ManagePermissions.php on line 493
  1. Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit()
    in Sources/ManagePermissions.php on line 493
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  26. Path: Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit() in Sources/ManagePermissions.php on line 611
  1. Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit()
    in Sources/ManagePermissions.php on line 611
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  27. Path: Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit() in Sources/ManagePermissions.php on line 680
  1. Read from $_REQUEST, and 'action=admin;area=permissions;pid=' . $_REQUEST['pid'] is passed to redirectexit()
    in Sources/ManagePermissions.php on line 680
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  28. Path: Read from $_GET, and 'action=admin;area=permissions;pid=' . $_GET['pid'] is passed to redirectexit() in Sources/ManagePermissions.php on line 965
  1. Read from $_GET, and 'action=admin;area=permissions;pid=' . $_GET['pid'] is passed to redirectexit()
    in Sources/ManagePermissions.php on line 965
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  29. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse' . (isset($_REQUEST['type']) ? ';type=' . $_REQUEST['type'] : '') is passed to redirectexit() in Sources/ManageRegistration.php on line 35
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse' . (isset($_REQUEST['type']) ? ';type=' . $_REQUEST['type'] : '') is passed to redirectexit()
    in Sources/ManageRegistration.php on line 35
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  30. Path: Read from $_GET, and $scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=need_name' is passed to redirectexit() in Sources/ManageSettings.php on line 1740
  1. Read from $_GET, and $scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=need_name' is passed to redirectexit()
    in Sources/ManageSettings.php on line 1740
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  31. Path: Read from $_GET, and $scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=regex_error' is passed to redirectexit() in Sources/ManageSettings.php on line 1744
  1. Read from $_GET, and $scripturl . '?action=admin;area=featuresettings;sa=profileedit;fid=' . $_GET['fid'] . ';msg=regex_error' is passed to redirectexit()
    in Sources/ManageSettings.php on line 1744
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  32. Path: Read from $_REQUEST, and $_REQUEST['topics'] is passed through implode(), and 'action=restoretopic;topics=' . implode(',', $_REQUEST['topics']) . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit() in Sources/MessageIndex.php on line 771
  1. Read from $_REQUEST, and $_REQUEST['topics'] is passed through implode(), and 'action=restoretopic;topics=' . implode(',', $_REQUEST['topics']) . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/MessageIndex.php on line 771
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  33. Path: Read from $_REQUEST, and $redirect_url is assigned in Sources/MessageIndex.php on line 800
  1. Read from $_REQUEST, and $redirect_url is assigned
    in Sources/MessageIndex.php on line 800
  2. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 853
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  34. Path: Read from $_POST, and $redirect_url is assigned in Sources/MessageIndex.php on line 810
  1. Read from $_POST, and $redirect_url is assigned
    in Sources/MessageIndex.php on line 810
  2. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 853
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  35. Path: Read from $_REQUEST, and $_SESSION is assigned in Sources/MessageIndex.php on line 781
  1. Read from $_REQUEST, and $_SESSION is assigned
    in Sources/MessageIndex.php on line 781
  2. $redirect_url is assigned
    in Sources/MessageIndex.php on line 810
  3. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 853
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  36. Path: Read from $_REQUEST, and 'board=' . $board . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Notify.php on line 86
  1. Read from $_REQUEST, and 'board=' . $board . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Notify.php on line 86
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  37. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Notify.php on line 191
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Notify.php on line 191
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  38. Path: Read from $_REQUEST, and $_REQUEST['filename'] is escaped by basename() for file context(s), and $package_name is assigned in Sources/PackageGet.php on line 576
  1. Read from $_REQUEST, and $_REQUEST['filename'] is escaped by basename() for file context(s), and $package_name is assigned
    in Sources/PackageGet.php on line 576
  2. 'action=admin;area=packages;sa=install;package=' . $package_name is passed to redirectexit()
    in Sources/PackageGet.php on line 606
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  39. Path: Read from $_REQUEST, and $_REQUEST['package'] is escaped by basename() for file context(s), and $package_name is assigned in Sources/PackageGet.php on line 578
  1. Read from $_REQUEST, and $_REQUEST['package'] is escaped by basename() for file context(s), and $package_name is assigned
    in Sources/PackageGet.php on line 578
  2. 'action=admin;area=packages;sa=install;package=' . $package_name is passed to redirectexit()
    in Sources/PackageGet.php on line 606
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  40. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2444
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2444
  2. $context is assigned
    in Sources/Packages.php on line 2455
  3. $context is assigned
    in Sources/Packages.php on line 2539
  4. 'action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode($smcFunc['json_encode']($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/Packages.php on line 2683
  5. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  41. Path: Read from $_POST, and $status is assigned in Sources/Packages.php on line 2465
  1. Read from $_POST, and $status is assigned
    in Sources/Packages.php on line 2465
  2. $context is assigned
    in Sources/Packages.php on line 2486
  3. $context is assigned
    in Sources/Packages.php on line 2488
  4. 'action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode($smcFunc['json_encode']($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/Packages.php on line 2499
  5. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  42. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2544
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2544
  2. $context is assigned
    in Sources/Packages.php on line 2546
  3. $context is assigned
    in Sources/Packages.php on line 2547
  4. $context is assigned
    in Sources/Packages.php on line 2549
  5. $context is assigned
    in Sources/Packages.php on line 2594
  6. $context is assigned
    in Sources/Packages.php on line 2678
  7. $context is assigned
    in Sources/Packages.php on line 2679
  8. 'action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode($smcFunc['json_encode']($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/Packages.php on line 2683
  9. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  43. Path: Read from $_REQUEST, and $_REQUEST[$recipientType] is passed through strtr(), and $recipientString is assigned in Sources/PersonalMessage.php on line 2269
  1. Read from $_REQUEST, and $_REQUEST[$recipientType] is passed through strtr(), and $recipientString is assigned
    in Sources/PersonalMessage.php on line 2269
  2. $recipientString is passed through preg_replace(), and preg_replace('~"[^"]+"~', '', $recipientString) is passed through explode(), and explode(',', preg_replace('~"[^"]+"~', '', $recipientString)) is passed through array_merge(), and array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))) is passed through array_unique(), and $namedRecipientList is assigned
    in Sources/PersonalMessage.php on line 2272
  3. $namesNotFound is assigned
    in Sources/PersonalMessage.php on line 2287
  4. $name is assigned
    in Sources/PersonalMessage.php on line 2335
  5. $name is passed through sprintf(), and $context is assigned
    in Sources/PersonalMessage.php on line 2336
  6. $context is assigned
    in Sources/PersonalMessage.php on line 2437
  7. $context['current_label_redirect'] is passed to redirectexit()
    in Sources/PersonalMessage.php on line 2477
  8. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  44. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Poll.php on line 152
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Poll.php on line 152
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  45. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Poll.php on line 221
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Poll.php on line 221
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  46. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Poll.php on line 285
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Poll.php on line 285
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  47. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Poll.php on line 908
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Poll.php on line 908
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  48. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Poll.php on line 1006
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Poll.php on line 1006
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  49. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/Post.php on line 2317
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/Post.php on line 2317
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  50. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/PostModeration.php on line 696
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/PostModeration.php on line 696
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  51. Path: Read from $_GET, and 'action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start'] is passed to redirectexit() in Sources/Profile-View.php on line 543
  1. Read from $_GET, and 'action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start'] is passed to redirectexit()
    in Sources/Profile-View.php on line 543
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  52. Path: Read from $_GET, and 'action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start'] is passed to redirectexit() in Sources/Profile-View.php on line 554
  1. Read from $_GET, and 'action=profile;u=' . $memID . ';area=showposts;start=' . $_GET['start'] is passed to redirectexit()
    in Sources/Profile-View.php on line 554
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  53. Path: Read from $_REQUEST, and 'action=profile;u=' . $_REQUEST['u'] . ';area=showposts;start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/RemoveTopic.php on line 151
  1. Read from $_REQUEST, and 'action=profile;u=' . $_REQUEST['u'] . ';area=showposts;start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/RemoveTopic.php on line 151
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  54. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/RemoveTopic.php on line 155
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/RemoveTopic.php on line 155
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  55. Path: Read from $_GET, and $context is assigned in Sources/ReportedContent.php on line 97
  1. Read from $_GET, and $context is assigned
    in Sources/ReportedContent.php on line 97
  2. $context is assigned
    in Sources/ReportedContent.php on line 100
  3. $context is assigned
    in Sources/ReportedContent.php on line 103
  4. $context is assigned
    in Sources/ReportedContent.php on line 106
  5. $context is assigned
    in Sources/ReportedContent.php on line 109
  6. $scripturl . '?action=moderate;area=reported' . $context['report_type'] is passed to redirectexit()
    in Sources/ReportedContent.php on line 129
  7. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  56. Path: Read from $_POST, and 'topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit() in Sources/ReportToMod.php on line 272
  1. Read from $_POST, and 'topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 272
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  57. Path: Read from $_POST, and 'reportsent;topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit() in Sources/ReportToMod.php on line 344
  1. Read from $_POST, and 'reportsent;topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 344
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  58. Path: Read from $_POST, and 'action=profile;u=' . $_POST['u'] is passed to redirectexit() in Sources/ReportToMod.php on line 396
  1. Read from $_POST, and 'action=profile;u=' . $_POST['u'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 396
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  59. Path: Read from $_POST, and $_POST['u'] is passed to reportUser() in Sources/ReportToMod.php on line 219
  1. Read from $_POST, and $_POST['u'] is passed to reportUser()
    in Sources/ReportToMod.php on line 219
  2. 'reportsent;action=profile;u=' . $id_member is passed to redirectexit()
    in Sources/ReportToMod.php on line 465
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  60. Path: Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and $scripturl . '?action=mlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit() in Sources/Search.php on line 260
  1. Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and $scripturl . '?action=mlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit()
    in Sources/Search.php on line 260
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  61. Path: Read from $_POST, and $topics is assigned in Sources/SplitTopics.php on line 1015
  1. Read from $_POST, and $topics is assigned
    in Sources/SplitTopics.php on line 1015
  2. $topics is passed through min(), and $id_topic is assigned
    in Sources/SplitTopics.php on line 1361
  3. 'action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board is passed to redirectexit()
    in Sources/SplitTopics.php on line 1753
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  62. Path: Read from $_REQUEST, and $_REQUEST['topics'] is passed to MergeExecute() in Sources/MessageIndex.php on line 863
  1. Read from $_REQUEST, and $_REQUEST['topics'] is passed to MergeExecute()
    in Sources/MessageIndex.php on line 863
  2. $topics is passed through min(), and $id_topic is assigned
    in Sources/SplitTopics.php on line 1361
  3. 'action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board is passed to redirectexit()
    in Sources/SplitTopics.php on line 1753
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  63. Path: Read from $_REQUEST, and $userReceiver is assigned in Sources/Subs-Members.php on line 1261
  1. Read from $_REQUEST, and $userReceiver is assigned
    in Sources/Subs-Members.php on line 1261
  2. 'action=profile;u=' . $userReceiver is passed to redirectexit()
    in Sources/Subs-Members.php on line 1299
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  64. Path: Read from $_GET, and 'action=admin;area=theme;sa=list;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit() in Sources/Themes.php on line 755
  1. Read from $_GET, and 'action=admin;area=theme;sa=list;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/Themes.php on line 755
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  65. Path: Read from $_GET, and 'action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . dirname($_REQUEST['filename']) is passed to redirectexit() in Sources/Themes.php on line 1791
  1. Read from $_GET, and 'action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . dirname($_REQUEST['filename']) is passed to redirectexit()
    in Sources/Themes.php on line 1791
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  66. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] . ';moderate' is passed to redirectexit() in Sources/Topic.php on line 94
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] . ';moderate' is passed to redirectexit()
    in Sources/Topic.php on line 94
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  67. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] . ';moderate' is passed to redirectexit() in Sources/Topic.php on line 154
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] . ';moderate' is passed to redirectexit()
    in Sources/Topic.php on line 154
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
2917
2918
	// Debugging.
2919
	if (isset($db_show_debug) && $db_show_debug === true)
2920
		$_SESSION['debug_redirect'] = $db_cache;
2921
2922
	obExit(false);
2923
}
2924
2925
/**
2926
 * Ends execution.  Takes care of template loading and remembering the previous URL.
2927
 * @param bool $header Whether to do the header
0 ignored issues
show
Documentation introduced by
Should the type for parameter $header not be boolean|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2928
 * @param bool $do_footer Whether to do the footer
0 ignored issues
show
Documentation introduced by
Should the type for parameter $do_footer not be boolean|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
2929
 * @param bool $from_index Whether we're coming from the board index
2930
 * @param bool $from_fatal_error Whether we're coming from a fatal error
2931
 */
2932
function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false)
2933
{
2934
	global $context, $settings, $modSettings, $txt, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2935
	static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false;
2936
2937
	// Attempt to prevent a recursive loop.
2938
	++$level;
2939
	if ($level > 1 && !$from_fatal_error && !$has_fatal_error)
2940
		exit;
2941
	if ($from_fatal_error)
2942
		$has_fatal_error = true;
2943
2944
	// Clear out the stat cache.
2945
	trackStats();
2946
2947
	// If we have mail to send, send it.
2948
	if (!empty($context['flush_mail']))
2949
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
2950
		AddMailQueue(true);
2951
2952
	$do_header = $header === null ? !$header_done : $header;
2953
	if ($do_footer === null)
2954
		$do_footer = $do_header;
2955
2956
	// Has the template/header been done yet?
2957
	if ($do_header)
2958
	{
2959
		// Was the page title set last minute? Also update the HTML safe one.
2960
		if (!empty($context['page_title']) && empty($context['page_title_html_safe']))
2961
			$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
2962
2963
		// Start up the session URL fixer.
2964
		ob_start('ob_sessrewrite');
2965
2966
		if (!empty($settings['output_buffers']) && is_string($settings['output_buffers']))
2967
			$buffers = explode(',', $settings['output_buffers']);
2968
		elseif (!empty($settings['output_buffers']))
2969
			$buffers = $settings['output_buffers'];
2970
		else
2971
			$buffers = array();
2972
2973
		if (isset($modSettings['integrate_buffer']))
2974
			$buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers);
2975
2976
		if (!empty($buffers))
2977
			foreach ($buffers as $function)
2978
			{
2979
				$call = call_helper($function, true);
2980
2981
				// Is it valid?
2982
				if (!empty($call))
2983
					ob_start($call);
2984
			}
2985
2986
		// Display the screen in the logical order.
2987
		template_header();
2988
		$header_done = true;
2989
	}
2990
	if ($do_footer)
2991
	{
2992
		loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main');
2993
2994
		// Anything special to put out?
2995
		if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml']))
2996
			echo $context['insert_after_template'];
2997
2998
		// Just so we don't get caught in an endless loop of errors from the footer...
2999
		if (!$footer_done)
3000
		{
3001
			$footer_done = true;
3002
			template_footer();
3003
3004
			// (since this is just debugging... it's okay that it's after </html>.)
3005
			if (!isset($_REQUEST['xml']))
3006
				displayDebug();
3007
		}
3008
	}
3009
3010
	// Remember this URL in case someone doesn't like sending HTTP_REFERER.
3011
	if (strpos($_SERVER['REQUEST_URL'], 'action=dlattach') === false && strpos($_SERVER['REQUEST_URL'], 'action=viewsmfile') === false)
3012
		$_SESSION['old_url'] = $_SERVER['REQUEST_URL'];
3013
3014
	// For session check verification.... don't switch browsers...
3015
	$_SESSION['USER_AGENT'] = empty($_SERVER['HTTP_USER_AGENT']) ? '' : $_SERVER['HTTP_USER_AGENT'];
3016
3017
	// Hand off the output to the portal, etc. we're integrated with.
3018
	call_integration_hook('integrate_exit', array($do_footer));
3019
3020
	// Don't exit if we're coming from index.php; that will pass through normally.
3021
	if (!$from_index)
3022
		exit;
3023
}
3024
3025
/**
3026
 * Get the size of a specified image with better error handling.
3027
 * @todo see if it's better in Subs-Graphics, but one step at the time.
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
3028
 * Uses getimagesize() to determine the size of a file.
3029
 * Attempts to connect to the server first so it won't time out.
3030
 *
3031
 * @param string $url The URL of the image
3032
 * @return array|false The image size as array (width, height), or false on failure
3033
 */
3034
function url_image_size($url)
3035
{
3036
	global $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3037
3038
	// Make sure it is a proper URL.
3039
	$url = str_replace(' ', '%20', $url);
3040
3041
	// Can we pull this from the cache... please please?
3042
	if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null)
3043
		return $temp;
3044
	$t = microtime();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $t. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3045
3046
	// Get the host to pester...
3047
	preg_match('~^\w+://(.+?)/(.*)$~', $url, $match);
3048
3049
	// Can't figure it out, just try the image size.
3050
	if ($url == '' || $url == 'http://' || $url == 'https://')
3051
	{
3052
		return false;
3053
	}
3054
	elseif (!isset($match[1]))
3055
	{
3056
		$size = @getimagesize($url);
3057
	}
3058
	else
3059
	{
3060
		// Try to connect to the server... give it half a second.
3061
		$temp = 0;
3062
		$fp = @fsockopen($match[1], 80, $temp, $temp, 0.5);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3063
3064
		// Successful?  Continue...
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3065
		if ($fp != false)
3066
		{
3067
			// Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.)
3068
			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");
3069
3070
			// Read in the HTTP/1.1 or whatever.
3071
			$test = substr(fgets($fp, 11), -1);
3072
			fclose($fp);
3073
3074
			// See if it returned a 404/403 or something.
3075
			if ($test < 4)
3076
			{
3077
				$size = @getimagesize($url);
3078
3079
				// This probably means allow_url_fopen is off, let's try GD.
3080
				if ($size === false && function_exists('imagecreatefromstring'))
3081
				{
3082
					include_once($sourcedir . '/Subs-Package.php');
3083
3084
					// It's going to hate us for doing this, but another request...
3085
					$image = @imagecreatefromstring(fetch_web_data($url));
3086
					if ($image !== false)
3087
					{
3088
						$size = array(imagesx($image), imagesy($image));
3089
						imagedestroy($image);
3090
					}
3091
				}
3092
			}
3093
		}
3094
	}
3095
3096
	// If we didn't get it, we failed.
3097
	if (!isset($size))
3098
		$size = false;
3099
3100
	// If this took a long time, we may never have to do it again, but then again we might...
3101 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.8)
3102
		cache_put_data('url_image_size-' . md5($url), $size, 240);
3103
3104
	// Didn't work.
3105
	return $size;
3106
}
3107
3108
/**
3109
 * Sets up the basic theme context stuff.
3110
 * @param bool $forceload Whether to load the theme even if it's already loaded
3111
 */
3112
function setupThemeContext($forceload = false)
3113
{
3114
	global $modSettings, $user_info, $scripturl, $context, $settings, $options, $txt, $maintenance;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3115
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3116
	static $loaded = false;
3117
3118
	// Under SSI this function can be called more then once.  That can cause some problems.
3119
	//   So only run the function once unless we are forced to run it again.
3120
	if ($loaded && !$forceload)
3121
		return;
3122
3123
	$loaded = true;
3124
3125
	$context['in_maintenance'] = !empty($maintenance);
3126
	$context['current_time'] = timeformat(time(), false);
3127
	$context['current_action'] = isset($_GET['action']) ? $smcFunc['htmlspecialchars']($_GET['action']) : '';
3128
3129
	// Get some news...
3130
	$context['news_lines'] = array_filter(explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news'])))));
3131
	for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++)
3132
	{
3133
		if (trim($context['news_lines'][$i]) == '')
3134
			continue;
3135
3136
		// Clean it up for presentation ;).
3137
		$context['news_lines'][$i] = parse_bbc(stripslashes(trim($context['news_lines'][$i])), true, 'news' . $i);
3138
	}
3139
	if (!empty($context['news_lines']))
3140
		$context['random_news_line'] = $context['news_lines'][mt_rand(0, count($context['news_lines']) - 1)];
3141
3142
	if (!$user_info['is_guest'])
3143
	{
3144
		$context['user']['messages'] = &$user_info['messages'];
3145
		$context['user']['unread_messages'] = &$user_info['unread_messages'];
3146
		$context['user']['alerts'] = &$user_info['alerts'];
3147
3148
		// Personal message popup...
3149
		if ($user_info['unread_messages'] > (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0))
3150
			$context['user']['popup_messages'] = true;
3151
		else
3152
			$context['user']['popup_messages'] = false;
3153
		$_SESSION['unread_messages'] = $user_info['unread_messages'];
3154
3155
		if (allowedTo('moderate_forum'))
3156
			$context['unapproved_members'] = (!empty($modSettings['registration_method']) && ($modSettings['registration_method'] == 2 || (!empty($modSettings['coppaType']) && $modSettings['coppaType'] == 2))) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0;
3157
3158
		$context['user']['avatar'] = array();
3159
3160
		// Check for gravatar first since we might be forcing them...
3161
		if (($modSettings['gravatarEnabled'] && substr($user_info['avatar']['url'], 0, 11) == 'gravatar://') || !empty($modSettings['gravatarOverride']))
3162
		{
3163
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($user_info['avatar']['url'], 'gravatar://') && strlen($user_info['avatar']['url']) > 11)
3164
				$context['user']['avatar']['href'] = get_gravatar_url($smcFunc['substr']($user_info['avatar']['url'], 11));
3165
			else
3166
				$context['user']['avatar']['href'] = get_gravatar_url($user_info['email']);
3167
		}
3168
		// Uploaded?
3169
		elseif ($user_info['avatar']['url'] == '' && !empty($user_info['avatar']['id_attach']))
3170
			$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';
3171
		// Full URL?
3172
		elseif (strpos($user_info['avatar']['url'], 'http://') === 0 || strpos($user_info['avatar']['url'], 'https://') === 0)
3173
			$context['user']['avatar']['href'] = $user_info['avatar']['url'];
3174
		// Otherwise we assume it's server stored.
3175
		elseif ($user_info['avatar']['url'] != '')
3176
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/' . $smcFunc['htmlspecialchars']($user_info['avatar']['url']);
3177
		// No avatar at all? Fine, we have a big fat default avatar ;)
3178
		else
3179
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/default.png';
3180
3181
		if (!empty($context['user']['avatar']))
3182
			$context['user']['avatar']['image'] = '<img src="' . $context['user']['avatar']['href'] . '" alt="" class="avatar">';
3183
3184
		// Figure out how long they've been logged in.
3185
		$context['user']['total_time_logged_in'] = array(
3186
			'days' => floor($user_info['total_time_logged_in'] / 86400),
3187
			'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600),
3188
			'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60)
3189
		);
3190
	}
3191
	else
3192
	{
3193
		$context['user']['messages'] = 0;
3194
		$context['user']['unread_messages'] = 0;
3195
		$context['user']['avatar'] = array();
3196
		$context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0);
3197
		$context['user']['popup_messages'] = false;
3198
3199
		if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1)
3200
			$txt['welcome_guest'] .= $txt['welcome_guest_activate'];
3201
3202
		// If we've upgraded recently, go easy on the passwords.
3203
		if (!empty($modSettings['disableHashTime']) && ($modSettings['disableHashTime'] == 1 || time() < $modSettings['disableHashTime']))
3204
			$context['disable_login_hashing'] = true;
3205
	}
3206
3207
	// Setup the main menu items.
3208
	setupMenuContext();
3209
3210
	// This is here because old index templates might still use it.
3211
	$context['show_news'] = !empty($settings['enable_news']);
3212
3213
	// This is done to allow theme authors to customize it as they want.
3214
	$context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != 'pm');
3215
3216
	// 2.1+: Add the PM popup here instead. Theme authors can still override it simply by editing/removing the 'fPmPopup' in the array.
3217
	if ($context['show_pm_popup'])
3218
		addInlineJavaScript('
3219
		jQuery(document).ready(function($) {
3220
			new smc_Popup({
3221
				heading: ' . JavaScriptEscape($txt['show_personal_messages_heading']) . ',
3222
				content: ' . JavaScriptEscape(sprintf($txt['show_personal_messages'], $context['user']['unread_messages'], $scripturl . '?action=pm')) . ',
3223
				icon_class: \'generic_icons mail_new\'
3224
			});
3225
		});');
3226
3227
	// Add a generic "Are you sure?" confirmation message.
3228
	addInlineJavaScript('
3229
	var smf_you_sure =' . JavaScriptEscape($txt['quickmod_confirm']) .';');
3230
3231
	// Now add the capping code for avatars.
3232
	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')
3233
		addInlineCss('
3234
img.avatar { max-width: ' . $modSettings['avatar_max_width_external'] . 'px; max-height: ' . $modSettings['avatar_max_height_external'] . 'px; }');
3235
3236
	// This looks weird, but it's because BoardIndex.php references the variable.
3237
	$context['common_stats']['latest_member'] = array(
3238
		'id' => $modSettings['latestMember'],
3239
		'name' => $modSettings['latestRealName'],
3240
		'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'],
3241
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $modSettings['latestRealName'] . '</a>',
3242
	);
3243
	$context['common_stats'] = array(
3244
		'total_posts' => comma_format($modSettings['totalMessages']),
3245
		'total_topics' => comma_format($modSettings['totalTopics']),
3246
		'total_members' => comma_format($modSettings['totalMembers']),
3247
		'latest_member' => $context['common_stats']['latest_member'],
3248
	);
3249
	$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']);
3250
3251
	if (empty($settings['theme_version']))
3252
		addJavaScriptVar('smf_scripturl', $scripturl);
3253
3254
	if (!isset($context['page_title']))
3255
		$context['page_title'] = '';
3256
3257
	// Set some specific vars.
3258
	$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
3259
	$context['meta_keywords'] = !empty($modSettings['meta_keywords']) ? $smcFunc['htmlspecialchars']($modSettings['meta_keywords']) : '';
3260
3261
	// Content related meta tags, including Open Graph
3262
	$context['meta_tags'][] = array('property' => 'og:site_name', 'content' => $context['forum_name']);
3263
	$context['meta_tags'][] = array('property' => 'og:title', 'content' => $context['page_title_html_safe']);
3264
3265
	if (!empty($context['meta_keywords']))
3266
		$context['meta_tags'][] = array('name' => 'keywords', 'content' => $context['meta_keywords']);
3267
3268
	if (!empty($context['canonical_url']))
3269
		$context['meta_tags'][] = array('property' => 'og:url', 'content' => $context['canonical_url']);
3270
3271
	if (!empty($settings['og_image']))
3272
		$context['meta_tags'][] = array('property' => 'og:image', 'content' => $settings['og_image']);
3273
3274
	if (!empty($context['meta_description']))
3275
	{
3276
		$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['meta_description']);
3277
		$context['meta_tags'][] = array('name' => 'description', 'content' => $context['meta_description']);
3278
	}
3279
	else
3280
	{
3281
		$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['page_title_html_safe']);
3282
		$context['meta_tags'][] = array('name' => 'description', 'content' => $context['page_title_html_safe']);
3283
	}
3284
3285
	call_integration_hook('integrate_theme_context');
3286
}
3287
3288
/**
3289
 * Helper function to set the system memory to a needed value
3290
 * - If the needed memory is greater than current, will attempt to get more
3291
 * - if in_use is set to true, will also try to take the current memory usage in to account
3292
 *
3293
 * @param string $needed The amount of memory to request, if needed, like 256M
3294
 * @param bool $in_use Set to true to account for current memory usage of the script
3295
 * @return boolean True if we have at least the needed memory
3296
 */
3297
function setMemoryLimit($needed, $in_use = false)
3298
{
3299
	// everything in bytes
3300
	$memory_current = memoryReturnBytes(ini_get('memory_limit'));
3301
	$memory_needed = memoryReturnBytes($needed);
3302
3303
	// should we account for how much is currently being used?
3304
	if ($in_use)
3305
		$memory_needed += function_exists('memory_get_usage') ? memory_get_usage() : (2 * 1048576);
3306
3307
	// if more is needed, request it
3308
	if ($memory_current < $memory_needed)
3309
	{
3310
		@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...
3311
		$memory_current = memoryReturnBytes(ini_get('memory_limit'));
3312
	}
3313
3314
	$memory_current = max($memory_current, memoryReturnBytes(get_cfg_var('memory_limit')));
3315
3316
	// return success or not
3317
	return (bool) ($memory_current >= $memory_needed);
3318
}
3319
3320
/**
3321
 * Helper function to convert memory string settings to bytes
3322
 *
3323
 * @param string $val The byte string, like 256M or 1G
3324
 * @return integer The string converted to a proper integer in bytes
3325
 */
3326
function memoryReturnBytes($val)
3327
{
3328
	if (is_integer($val))
3329
		return $val;
3330
3331
	// Separate the number from the designator
3332
	$val = trim($val);
3333
	$num = intval(substr($val, 0, strlen($val) - 1));
3334
	$last = strtolower(substr($val, -1));
3335
3336
	// convert to bytes
3337
	switch ($last)
3338
	{
3339
		case 'g':
3340
			$num *= 1024;
3341
		case 'm':
3342
			$num *= 1024;
3343
		case 'k':
3344
			$num *= 1024;
3345
	}
3346
	return $num;
3347
}
3348
3349
/**
3350
 * The header template
3351
 */
3352
function template_header()
3353
{
3354
	global $txt, $modSettings, $context, $user_info, $boarddir, $cachedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3355
3356
	setupThemeContext();
3357
3358
	// Print stuff to prevent caching of pages (except on attachment errors, etc.)
3359
	if (empty($context['no_last_modified']))
3360
	{
3361
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
3362
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3363
3364
		// Are we debugging the template/html content?
3365
		if (!isset($_REQUEST['xml']) && isset($_GET['debug']) && !isBrowser('ie'))
3366
			header('Content-Type: application/xhtml+xml');
3367 View Code Duplication
		elseif (!isset($_REQUEST['xml']))
3368
			header('Content-Type: text/html; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3369
	}
3370
3371
	header('Content-Type: text/' . (isset($_REQUEST['xml']) ? 'xml' : 'html') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3372
3373
	// We need to splice this in after the body layer, or after the main layer for older stuff.
3374
	if ($context['in_maintenance'] && $context['user']['is_admin'])
3375
	{
3376
		$position = array_search('body', $context['template_layers']);
3377
		if ($position === false)
3378
			$position = array_search('main', $context['template_layers']);
3379
3380
		if ($position !== false)
3381
		{
3382
			$before = array_slice($context['template_layers'], 0, $position + 1);
3383
			$after = array_slice($context['template_layers'], $position + 1);
3384
			$context['template_layers'] = array_merge($before, array('maint_warning'), $after);
3385
		}
3386
	}
3387
3388
	$checked_securityFiles = false;
3389
	$showed_banned = false;
3390
	foreach ($context['template_layers'] as $layer)
3391
	{
3392
		loadSubTemplate($layer . '_above', true);
3393
3394
		// May seem contrived, but this is done in case the body and main layer aren't there...
3395
		if (in_array($layer, array('body', 'main')) && allowedTo('admin_forum') && !$user_info['is_guest'] && !$checked_securityFiles)
3396
		{
3397
			$checked_securityFiles = true;
3398
3399
			$securityFiles = array('install.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~');
3400
3401
			// Add your own files.
3402
			call_integration_hook('integrate_security_files', array(&$securityFiles));
3403
3404
			foreach ($securityFiles as $i => $securityFile)
3405
			{
3406
				if (!file_exists($boarddir . '/' . $securityFile))
3407
					unset($securityFiles[$i]);
3408
			}
3409
3410
			// We are already checking so many files...just few more doesn't make any difference! :P
3411
			if (!empty($modSettings['currentAttachmentUploadDir']))
3412
				$path = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
3413
3414
			else
3415
				$path = $modSettings['attachmentUploadDir'];
3416
3417
			secureDirectory($path, true);
3418
			secureDirectory($cachedir);
3419
3420
			// If agreement is enabled, at least the english version shall exists
3421
			if ($modSettings['requireAgreement'])
3422
				$agreement = !file_exists($boarddir . '/agreement.txt');
3423
3424
			if (!empty($securityFiles) || (!empty($modSettings['cache_enable']) && !is_writable($cachedir)) || !empty($agreement))
3425
			{
3426
				echo '
3427
		<div class="errorbox">
3428
			<p class="alert">!!</p>
3429
			<h3>', empty($securityFiles) ? $txt['generic_warning'] : $txt['security_risk'], '</h3>
3430
			<p>';
3431
3432
				foreach ($securityFiles as $securityFile)
3433
				{
3434
					echo '
3435
				', $txt['not_removed'], '<strong>', $securityFile, '</strong>!<br>';
3436
3437
					if ($securityFile == 'Settings.php~' || $securityFile == 'Settings_bak.php~')
3438
						echo '
3439
				', sprintf($txt['not_removed_extra'], $securityFile, substr($securityFile, 0, -1)), '<br>';
3440
				}
3441
3442
				if (!empty($modSettings['cache_enable']) && !is_writable($cachedir))
3443
					echo '
3444
				<strong>', $txt['cache_writable'], '</strong><br>';
3445
3446
				if (!empty($agreement))
3447
					echo '
3448
				<strong>', $txt['agreement_missing'], '</strong><br>';
3449
3450
				echo '
3451
			</p>
3452
		</div>';
3453
			}
3454
		}
3455
		// If the user is banned from posting inform them of it.
3456
		elseif (in_array($layer, array('main', 'body')) && isset($_SESSION['ban']['cannot_post']) && !$showed_banned)
3457
		{
3458
			$showed_banned = true;
3459
			echo '
3460
				<div class="windowbg alert" style="margin: 2ex; padding: 2ex; border: 2px dashed red;">
3461
					', sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']);
3462
3463
			if (!empty($_SESSION['ban']['cannot_post']['reason']))
3464
				echo '
3465
					<div style="padding-left: 4ex; padding-top: 1ex;">', $_SESSION['ban']['cannot_post']['reason'], '</div>';
3466
3467
			if (!empty($_SESSION['ban']['expire_time']))
3468
				echo '
3469
					<div>', sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)), '</div>';
3470
			else
3471
				echo '
3472
					<div>', $txt['your_ban_expires_never'], '</div>';
3473
3474
			echo '
3475
				</div>';
3476
		}
3477
	}
3478
}
3479
3480
/**
3481
 * Show the copyright.
3482
 */
3483
function theme_copyright()
3484
{
3485
	global $forum_copyright, $software_year, $forum_version;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3486
3487
	// Don't display copyright for things like SSI.
3488
	if (!isset($forum_version) || !isset($software_year))
3489
		return;
3490
3491
	// Put in the version...
3492
	printf($forum_copyright, $forum_version, $software_year);
3493
}
3494
3495
/**
3496
 * The template footer
3497
 */
3498
function template_footer()
3499
{
3500
	global $context, $modSettings, $time_start, $db_count;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3501
3502
	// Show the load time?  (only makes sense for the footer.)
3503
	$context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']);
3504
	$context['load_time'] = round(microtime(true) - $time_start, 3);
3505
	$context['load_queries'] = $db_count;
3506
3507
	foreach (array_reverse($context['template_layers']) as $layer)
3508
		loadSubTemplate($layer . '_below', true);
3509
}
3510
3511
/**
3512
 * Output the Javascript files
3513
 * 	- tabbing in this function is to make the HTML source look good proper
3514
 *  - if defered is set function will output all JS (source & inline) set to load at page end
3515
 *
3516
 * @param bool $do_deferred If true will only output the deferred JS (the stuff that goes right before the closing body tag)
3517
 */
3518
function template_javascript($do_deferred = false)
3519
{
3520
	global $context, $modSettings, $settings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3521
3522
	// Use this hook to minify/optimize Javascript files and vars
3523
	call_integration_hook('integrate_pre_javascript_output', array(&$do_deferred));
3524
3525
	$toMinify = array();
3526
	$toMinifyDefer = array();
3527
3528
	// Ouput the declared Javascript variables.
3529
	if (!empty($context['javascript_vars']) && !$do_deferred)
3530
	{
3531
		echo '
3532
	<script>';
3533
3534
		foreach ($context['javascript_vars'] as $key => $value)
3535
		{
3536
			if (empty($value))
3537
			{
3538
				echo '
3539
		var ', $key, ';';
3540
			}
3541
			else
3542
			{
3543
				echo '
3544
		var ', $key, ' = ', $value, ';';
3545
			}
3546
		}
3547
3548
		echo '
3549
	</script>';
3550
	}
3551
3552
	// While we have JavaScript files to place in the template.
3553
	foreach ($context['javascript_files'] as $id => $js_file)
3554
	{
3555
		// Last minute call! allow theme authors to disable single files.
3556
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3557
			continue;
3558
3559
		// By default all files don't get minimized unless the file explicitly says so!
3560
		if (!empty($js_file['options']['minimize']) && !empty($modSettings['minimize_files']))
3561
		{
3562
			if ($do_deferred && !empty($js_file['options']['defer']))
3563
				$toMinifyDefer[] = $js_file;
3564
3565
			elseif (!$do_deferred && empty($js_file['options']['defer']))
3566
				$toMinify[] = $js_file;
3567
3568
			// Grab a random seed.
3569
			if (!isset($minSeed))
3570
				$minSeed = $js_file['options']['seed'];
3571
		}
3572
3573
		elseif ((!$do_deferred && empty($js_file['options']['defer'])) || ($do_deferred && !empty($js_file['options']['defer'])))
3574
			echo '
3575
	<script src="', $js_file['fileUrl'], '"', !empty($js_file['options']['async']) ? ' async="async"' : '', '></script>';
3576
	}
3577
3578
	if ((!$do_deferred && !empty($toMinify)) || ($do_deferred && !empty($toMinifyDefer)))
3579
	{
3580
		$result = custMinify(($do_deferred ? $toMinifyDefer : $toMinify), 'js', $do_deferred);
3581
3582
		// Minify process couldn't work, print each individual files.
3583
		if (!empty($result) && is_array($result))
3584
			foreach ($result as $minFailedFile)
3585
				echo '
3586
	<script src="', $minFailedFile['fileUrl'], '"', !empty($minFailedFile['options']['async']) ? ' async="async"' : '', '></script>';
3587
3588
		else
3589
			echo '
3590
	<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...
3591
	}
3592
3593
	// Inline JavaScript - Actually useful some times!
3594
	if (!empty($context['javascript_inline']))
3595
	{
3596 View Code Duplication
		if (!empty($context['javascript_inline']['defer']) && $do_deferred)
3597
		{
3598
			echo '
3599
<script>';
3600
3601
			foreach ($context['javascript_inline']['defer'] as $js_code)
3602
				echo $js_code;
3603
3604
			echo '
3605
</script>';
3606
		}
3607
3608 View Code Duplication
		if (!empty($context['javascript_inline']['standard']) && !$do_deferred)
3609
		{
3610
			echo '
3611
	<script>';
3612
3613
			foreach ($context['javascript_inline']['standard'] as $js_code)
3614
				echo $js_code;
3615
3616
			echo '
3617
	</script>';
3618
		}
3619
	}
3620
}
3621
3622
/**
3623
 * Output the CSS files
3624
 *
3625
 */
3626
function template_css()
3627
{
3628
	global $context, $db_show_debug, $boardurl, $settings, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3629
3630
	// Use this hook to minify/optimize CSS files
3631
	call_integration_hook('integrate_pre_css_output');
3632
3633
	$toMinify = array();
3634
	$normal = array();
3635
3636
	ksort($context['css_files_order']);
3637
	$context['css_files'] = array_merge(array_flip($context['css_files_order']), $context['css_files']);
3638
3639
	foreach ($context['css_files'] as $id => $file)
3640
	{
3641
		// Last minute call! allow theme authors to disable single files.
3642
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3643
			continue;
3644
3645
		// By default all files don't get minimized unless the file explicitly says so!
3646
		if (!empty($file['options']['minimize']) && !empty($modSettings['minimize_files']))
3647
		{
3648
			$toMinify[] = $file;
3649
3650
			// Grab a random seed.
3651
			if (!isset($minSeed))
3652
				$minSeed = $file['options']['seed'];
3653
		}
3654
3655
		else
3656
			$normal[] = $file['fileUrl'];
3657
	}
3658
3659
	if (!empty($toMinify))
3660
	{
3661
		$result = custMinify($toMinify, 'css');
3662
3663
		// Minify process couldn't work, print each individual files.
3664
		if (!empty($result) && is_array($result))
3665
			foreach ($result as $minFailedFile)
3666
				echo '
3667
	<link rel="stylesheet" href="', $minFailedFile['fileUrl'], '">';
3668
3669
		else
3670
			echo '
3671
	<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...
3672
	}
3673
3674
	// Print the rest after the minified files.
3675
	if (!empty($normal))
3676
		foreach ($normal as $nf)
3677
			echo '
3678
	<link rel="stylesheet" href="', $nf ,'">';
3679
3680
	if ($db_show_debug === true)
3681
	{
3682
		// Try to keep only what's useful.
3683
		$repl = array($boardurl . '/Themes/' => '', $boardurl . '/' => '');
3684
		foreach ($context['css_files'] as $file)
3685
			$context['debug']['sheets'][] = strtr($file['fileName'], $repl);
3686
	}
3687
3688
	if (!empty($context['css_header']))
3689
	{
3690
		echo '
3691
	<style>';
3692
3693
		foreach ($context['css_header'] as $css)
3694
			echo $css .'
3695
	';
3696
3697
		echo'
3698
	</style>';
3699
	}
3700
}
3701
3702
/**
3703
 * Get an array of previously defined files and adds them to our main minified file.
3704
 * Sets a one day cache to avoid re-creating a file on every request.
3705
 *
3706
 * @param array $data The files to minify.
3707
 * @param string $type either css or js.
3708
 * @param bool $do_deferred use for type js to indicate if the minified file will be deferred, IE, put at the closing </body> tag.
3709
 * @return bool|array If an array the minify process failed and the data is returned intact.
3710
 */
3711
function custMinify($data, $type, $do_deferred = false)
3712
{
3713
	global $settings, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3714
3715
	$types = array('css', 'js');
3716
	$type = !empty($type) && in_array($type, $types) ? $type : false;
3717
	$data = !empty($data) ? $data : false;
3718
3719
	if (empty($type) || empty($data))
3720
		return false;
3721
3722
	// Did we already did this?
3723
	$toCache = cache_get_data('minimized_'. $settings['theme_id'] .'_'. $type, 86400);
3724
3725
	// Already done?
3726
	if (!empty($toCache))
3727
		return true;
3728
3729
	// No namespaces, sorry!
3730
	$classType = 'MatthiasMullie\\Minify\\'. strtoupper($type);
3731
3732
	// Temp path.
3733
	$cTempPath = $settings['theme_dir'] .'/'. ($type == 'css' ? 'css' : 'scripts') .'/';
3734
3735
	// What kind of file are we going to create?
3736
	$toCreate = $cTempPath .'minified'. ($do_deferred ? '_deferred' : '') .'.'. $type;
3737
3738
	// File has to exists, if it isn't try to create it.
3739
	if ((!file_exists($toCreate) && @fopen($toCreate, 'w') === false) || !smf_chmod($toCreate))
3740
	{
3741
		loadLanguage('Errors');
3742
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3743
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3744
3745
		// The process failed so roll back to print each individual file.
3746
		return $data;
3747
	}
3748
3749
	$minifier = new $classType();
3750
3751
	foreach ($data as $file)
3752
	{
3753
		$tempFile = str_replace($file['options']['seed'], '', $file['filePath']);
3754
		$toAdd = file_exists($tempFile) ? $tempFile : false;
3755
3756
		// The file couldn't be located so it won't be added, log this error.
3757
		if (empty($toAdd))
3758
		{
3759
			loadLanguage('Errors');
3760
			log_error(sprintf($txt['file_minimize_fail'], $file['fileName']), 'general');
3761
			continue;
3762
		}
3763
3764
		// Add this file to the list.
3765
		$minifier->add($toAdd);
3766
	}
3767
3768
	// Create the file.
3769
	$minifier->minify($toCreate);
3770
	unset($minifier);
3771
	clearstatcache();
3772
3773
	// Minify process failed.
3774
	if (!filesize($toCreate))
3775
	{
3776
		loadLanguage('Errors');
3777
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3778
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3779
3780
		// The process failed so roll back to print each individual file.
3781
		return $data;
3782
	}
3783
3784
	// And create a long lived cache entry.
3785
	cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, $toCreate, 86400);
3786
3787
	return true;
3788
}
3789
3790
/**
3791
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
3792
 * @todo this currently returns the hash if new, and the full filename otherwise.
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
3793
 * Something messy like that.
3794
 * @todo and of course everything relies on this behavior and work around it. :P.
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
3795
 * Converters included.
3796
 *
3797
 * @param string $filename The name of the file
3798
 * @param int $attachment_id The ID of the attachment
3799
 * @param string $dir Which directory it should be in (null to use current one)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $dir not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
3800
 * @param bool $new Whether this is a new attachment
3801
 * @param string $file_hash The file hash
3802
 * @return string The path to the file
0 ignored issues
show
Documentation introduced by
Should the return type not be string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
3803
 */
3804
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
3805
{
3806
	global $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3807
3808
	// Just make up a nice hash...
3809
	if ($new)
3810
		return sha1(md5($filename . time()) . mt_rand());
3811
3812
	// Just make sure that attachment id is only a int
3813
	$attachment_id = (int) $attachment_id;
3814
3815
	// Grab the file hash if it wasn't added.
3816
	// Left this for legacy.
3817
	if ($file_hash === '')
3818
	{
3819
		$request = $smcFunc['db_query']('', '
3820
			SELECT file_hash
3821
			FROM {db_prefix}attachments
3822
			WHERE id_attach = {int:id_attach}',
3823
			array(
3824
				'id_attach' => $attachment_id,
3825
			));
3826
3827
		if ($smcFunc['db_num_rows']($request) === 0)
3828
			return false;
3829
3830
		list ($file_hash) = $smcFunc['db_fetch_row']($request);
3831
		$smcFunc['db_free_result']($request);
3832
	}
3833
3834
	// Still no hash? mmm...
3835
	if (empty($file_hash))
3836
		$file_hash = sha1(md5($filename . time()) . mt_rand());
3837
3838
	// Are we using multiple directories?
3839
	if (is_array($modSettings['attachmentUploadDir']))
3840
		$path = $modSettings['attachmentUploadDir'][$dir];
3841
3842
	else
3843
		$path = $modSettings['attachmentUploadDir'];
3844
3845
	return $path . '/' . $attachment_id . '_' . $file_hash .'.dat';
3846
}
3847
3848
/**
3849
 * Convert a single IP to a ranged IP.
3850
 * internal function used to convert a user-readable format to a format suitable for the database.
3851
 *
3852
 * @param string $fullip The full IP
3853
 * @return array An array of IP parts
3854
 */
3855
function ip2range($fullip)
3856
{
3857
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
3858
	if ($fullip == 'unknown')
3859
		$fullip = '255.255.255.255';
3860
3861
	$ip_parts = explode('-', $fullip);
3862
	$ip_array = array();
3863
3864
	// if ip 22.12.31.21
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3865
	if (count($ip_parts) == 1 && isValidIP($fullip))
3866
	{
3867
		$ip_array['low'] = $fullip;
3868
		$ip_array['high'] = $fullip;
3869
		return $ip_array;
3870
	} // if ip 22.12.* -> 22.12.* - 22.12.*
3871
	elseif (count($ip_parts) == 1)
3872
	{
3873
		$ip_parts[0] = $fullip;
3874
		$ip_parts[1] = $fullip;
3875
	}
3876
3877
	// if ip 22.12.31.21-12.21.31.21
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
3878
	if (count($ip_parts) == 2 && isValidIP($ip_parts[0]) && isValidIP($ip_parts[1]))
3879
	{
3880
		$ip_array['low'] = $ip_parts[0];
3881
		$ip_array['high'] = $ip_parts[1];
3882
		return $ip_array;
3883
	}
3884
	elseif (count($ip_parts) == 2) // if ip 22.22.*-22.22.*
3885
	{
3886
		$valid_low = isValidIP($ip_parts[0]);
3887
		$valid_high = isValidIP($ip_parts[1]);
3888
		$count = 0;
3889
		$mode = (preg_match('/:/',$ip_parts[0]) > 0 ? ':' : '.');
3890
		$max = ($mode == ':' ? 'ffff' : '255');
3891
		$min = 0;
3892 View Code Duplication
		if(!$valid_low)
3893
		{
3894
			$ip_parts[0] = preg_replace('/\*/', '0', $ip_parts[0]);
3895
			$valid_low = isValidIP($ip_parts[0]);
3896
			while (!$valid_low)
3897
			{
3898
				$ip_parts[0] .= $mode . $min;
3899
				$valid_low = isValidIP($ip_parts[0]);
3900
				$count++;
3901
				if ($count > 9) break;
3902
			}
3903
		}
3904
3905
		$count = 0;
3906 View Code Duplication
		if(!$valid_high)
3907
		{
3908
			$ip_parts[1] = preg_replace('/\*/', $max, $ip_parts[1]);
3909
			$valid_high = isValidIP($ip_parts[1]);
3910
			while (!$valid_high)
3911
			{
3912
				$ip_parts[1] .= $mode . $max;
3913
				$valid_high = isValidIP($ip_parts[1]);
3914
				$count++;
3915
				if ($count > 9) break;
3916
			}
3917
		}
3918
3919
		if($valid_high && $valid_low)
3920
		{
3921
			$ip_array['low'] = $ip_parts[0];
3922
			$ip_array['high'] = $ip_parts[1];
3923
		}
3924
3925
	}
3926
3927
	return $ip_array;
3928
}
3929
3930
/**
3931
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
3932
 *
3933
 * @param string $ip The IP to get the hostname from
3934
 * @return string The hostname
3935
 */
3936
function host_from_ip($ip)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ip. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3937
{
3938
	global $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3939
3940
	if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null)
3941
		return $host;
3942
	$t = microtime();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $t. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3943
3944
	// Try the Linux host command, perhaps?
3945
	if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
3946
	{
3947
		if (!isset($modSettings['host_to_dis']))
3948
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
3949
		else
3950
			$test = @shell_exec('host ' . @escapeshellarg($ip));
3951
3952
		// Did host say it didn't find anything?
3953
		if (strpos($test, 'not found') !== false)
3954
			$host = '';
3955
		// Invalid server option?
3956
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
3957
			updateSettings(array('host_to_dis' => 1));
3958
		// Maybe it found something, after all?
3959
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
3960
			$host = $match[1];
3961
	}
3962
3963
	// This is nslookup; usually only Windows, but possibly some Unix?
3964
	if (!isset($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
3965
	{
3966
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
3967
		if (strpos($test, 'Non-existent domain') !== false)
3968
			$host = '';
3969
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
3970
			$host = $match[1];
3971
	}
3972
3973
	// This is the last try :/.
3974
	if (!isset($host) || $host === false)
3975
		$host = @gethostbyaddr($ip);
3976
3977
	// It took a long time, so let's cache it!
3978 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5)
3979
		cache_put_data('hostlookup-' . $ip, $host, 600);
3980
3981
	return $host;
3982
}
3983
3984
/**
3985
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
3986
 *
3987
 * @param string $text The text to split into words
3988
 * @param int $max_chars The maximum number of characters per word
3989
 * @param bool $encrypt Whether to encrypt the results
3990
 * @return array An array of ints or words depending on $encrypt
3991
 */
3992
function text2words($text, $max_chars = 20, $encrypt = false)
3993
{
3994
	global $smcFunc, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3995
3996
	// Step 1: Remove entities/things we don't consider words:
3997
	$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>' => ' ')));
3998
3999
	// Step 2: Entities we left to letters, where applicable, lowercase.
4000
	$words = un_htmlspecialchars($smcFunc['strtolower']($words));
4001
4002
	// Step 3: Ready to split apart and index!
4003
	$words = explode(' ', $words);
4004
4005
	if ($encrypt)
4006
	{
4007
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
4008
		$returned_ints = array();
4009
		foreach ($words as $word)
4010
		{
4011
			if (($word = trim($word, '-_\'')) !== '')
4012
			{
4013
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
4014
				$total = 0;
4015
				for ($i = 0; $i < $max_chars; $i++)
4016
					$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
4017
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
4018
			}
4019
		}
4020
		return array_unique($returned_ints);
4021
	}
4022
	else
4023
	{
4024
		// Trim characters before and after and add slashes for database insertion.
4025
		$returned_words = array();
4026
		foreach ($words as $word)
4027
			if (($word = trim($word, '-_\'')) !== '')
4028
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
4029
4030
		// Filter out all words that occur more than once.
4031
		return array_unique($returned_words);
4032
	}
4033
}
4034
4035
/**
4036
 * Creates an image/text button
4037
 *
4038
 * @param string $name The name of the button (should be a generic_icons class or the name of an image)
4039
 * @param string $alt The alt text
4040
 * @param string $label The $txt string to use as the label
4041
 * @param string $custom Custom text/html to add to the img tag (only when using an actual image)
4042
 * @param boolean $force_use Whether to force use of this when template_create_button is available
4043
 * @return string The HTML to display the button
4044
 */
4045
function create_button($name, $alt, $label = '', $custom = '', $force_use = false)
4046
{
4047
	global $settings, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4048
4049
	// Does the current loaded theme have this and we are not forcing the usage of this function?
4050
	if (function_exists('template_create_button') && !$force_use)
4051
		return template_create_button($name, $alt, $label = '', $custom = '');
4052
4053
	if (!$settings['use_image_buttons'])
4054
		return $txt[$alt];
4055
	elseif (!empty($settings['use_buttons']))
4056
		return '<span class="generic_icons ' . $name . '" alt="' . $txt[$alt] . '"></span>' . ($label != '' ? '&nbsp;<strong>' . $txt[$label] . '</strong>' : '');
4057
	else
4058
		return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . '>';
4059
}
4060
4061
/**
4062
 * Sets up all of the top menu buttons
4063
 * Saves them in the cache if it is available and on
4064
 * Places the results in $context
4065
 *
4066
 */
4067
function setupMenuContext()
4068
{
4069
	global $context, $modSettings, $user_info, $txt, $scripturl, $sourcedir, $settings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4070
4071
	// Set up the menu privileges.
4072
	$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts'));
4073
	$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'));
4074
4075
	$context['allow_memberlist'] = allowedTo('view_mlist');
4076
	$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
4077
	$context['allow_moderation_center'] = $context['user']['can_mod'];
4078
	$context['allow_pm'] = allowedTo('pm_read');
4079
4080
	$cacheTime = $modSettings['lastActive'] * 60;
4081
4082
	// Initial "can you post an event in the calendar" option - but this might have been set in the calendar already.
4083
	if (!isset($context['allow_calendar_event']))
4084
	{
4085
		$context['allow_calendar_event'] = $context['allow_calendar'] && allowedTo('calendar_post');
4086
4087
		// If you don't allow events not linked to posts and you're not an admin, we have more work to do...
4088 View Code Duplication
		if ($context['allow_calendar'] && $context['allow_calendar_event'] && empty($modSettings['cal_allow_unlinked']) && !$user_info['is_admin'])
4089
		{
4090
			$boards_can_post = boardsAllowedTo('post_new');
4091
			$context['allow_calendar_event'] &= !empty($boards_can_post);
4092
		}
4093
	}
4094
4095
	// There is some menu stuff we need to do if we're coming at this from a non-guest perspective.
4096
	if (!$context['user']['is_guest'])
4097
	{
4098
		addInlineJavaScript('
4099
	var user_menus = new smc_PopupMenu();
4100
	user_menus.add("profile", "' . $scripturl . '?action=profile;area=popup");
4101
	user_menus.add("alerts", "' . $scripturl . '?action=profile;area=alerts_popup;u='. $context['user']['id'] .'");', true);
4102
		if ($context['allow_pm'])
4103
			addInlineJavaScript('
4104
	user_menus.add("pm", "' . $scripturl . '?action=pm;sa=popup");', true);
4105
4106
		if (!empty($modSettings['enable_ajax_alerts']))
4107
		{
4108
			require_once($sourcedir . '/Subs-Notify.php');
4109
4110
			$timeout = getNotifyPrefs($context['user']['id'], 'alert_timeout', true);
4111
			$timeout = empty($timeout) ? 10000 : $timeout[$context['user']['id']]['alert_timeout'] * 1000;
4112
4113
			addInlineJavaScript('
4114
	var new_alert_title = "' . $context['forum_name'] . '";
4115
	var alert_timeout = ' . $timeout . ';');
4116
			loadJavaScriptFile('alerts.js', array(), 'smf_alerts');
4117
		}
4118
	}
4119
4120
	// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
4121
	if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
4122
	{
4123
		$buttons = array(
4124
			'home' => array(
4125
				'title' => $txt['home'],
4126
				'href' => $scripturl,
4127
				'show' => true,
4128
				'sub_buttons' => array(
4129
				),
4130
				'is_last' => $context['right_to_left'],
4131
			),
4132
			'search' => array(
4133
				'title' => $txt['search'],
4134
				'href' => $scripturl . '?action=search',
4135
				'show' => $context['allow_search'],
4136
				'sub_buttons' => array(
4137
				),
4138
			),
4139
			'admin' => array(
4140
				'title' => $txt['admin'],
4141
				'href' => $scripturl . '?action=admin',
4142
				'show' => $context['allow_admin'],
4143
				'sub_buttons' => array(
4144
					'featuresettings' => array(
4145
						'title' => $txt['modSettings_title'],
4146
						'href' => $scripturl . '?action=admin;area=featuresettings',
4147
						'show' => allowedTo('admin_forum'),
4148
					),
4149
					'packages' => array(
4150
						'title' => $txt['package'],
4151
						'href' => $scripturl . '?action=admin;area=packages',
4152
						'show' => allowedTo('admin_forum'),
4153
					),
4154
					'errorlog' => array(
4155
						'title' => $txt['errlog'],
4156
						'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
4157
						'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']),
4158
					),
4159
					'permissions' => array(
4160
						'title' => $txt['edit_permissions'],
4161
						'href' => $scripturl . '?action=admin;area=permissions',
4162
						'show' => allowedTo('manage_permissions'),
4163
					),
4164
					'memberapprove' => array(
4165
						'title' => $txt['approve_members_waiting'],
4166
						'href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve',
4167
						'show' => !empty($context['unapproved_members']),
4168
						'is_last' => true,
4169
					),
4170
				),
4171
			),
4172
			'moderate' => array(
4173
				'title' => $txt['moderate'],
4174
				'href' => $scripturl . '?action=moderate',
4175
				'show' => $context['allow_moderation_center'],
4176
				'sub_buttons' => array(
4177
					'modlog' => array(
4178
						'title' => $txt['modlog_view'],
4179
						'href' => $scripturl . '?action=moderate;area=modlog',
4180
						'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4181
					),
4182
					'poststopics' => array(
4183
						'title' => $txt['mc_unapproved_poststopics'],
4184
						'href' => $scripturl . '?action=moderate;area=postmod;sa=posts',
4185
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4186
					),
4187
					'attachments' => array(
4188
						'title' => $txt['mc_unapproved_attachments'],
4189
						'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments',
4190
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4191
					),
4192
					'reports' => array(
4193
						'title' => $txt['mc_reported_posts'],
4194
						'href' => $scripturl . '?action=moderate;area=reportedposts',
4195
						'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4196
					),
4197
					'reported_members' => array(
4198
						'title' => $txt['mc_reported_members'],
4199
						'href' => $scripturl . '?action=moderate;area=reportedmembers',
4200
						'show' => allowedTo('moderate_forum'),
4201
						'is_last' => true,
4202
					)
4203
				),
4204
			),
4205
			'calendar' => array(
4206
				'title' => $txt['calendar'],
4207
				'href' => $scripturl . '?action=calendar',
4208
				'show' => $context['allow_calendar'],
4209
				'sub_buttons' => array(
4210
					'view' => array(
4211
						'title' => $txt['calendar_menu'],
4212
						'href' => $scripturl . '?action=calendar',
4213
						'show' => $context['allow_calendar_event'],
4214
					),
4215
					'post' => array(
4216
						'title' => $txt['calendar_post_event'],
4217
						'href' => $scripturl . '?action=calendar;sa=post',
4218
						'show' => $context['allow_calendar_event'],
4219
						'is_last' => true,
4220
					),
4221
				),
4222
			),
4223
			'mlist' => array(
4224
				'title' => $txt['members_title'],
4225
				'href' => $scripturl . '?action=mlist',
4226
				'show' => $context['allow_memberlist'],
4227
				'sub_buttons' => array(
4228
					'mlist_view' => array(
4229
						'title' => $txt['mlist_menu_view'],
4230
						'href' => $scripturl . '?action=mlist',
4231
						'show' => true,
4232
					),
4233
					'mlist_search' => array(
4234
						'title' => $txt['mlist_search'],
4235
						'href' => $scripturl . '?action=mlist;sa=search',
4236
						'show' => true,
4237
						'is_last' => true,
4238
					),
4239
				),
4240
			),
4241
			'signup' => array(
4242
				'title' => $txt['register'],
4243
				'href' => $scripturl . '?action=signup',
4244
				'show' => $user_info['is_guest'] && $context['can_register'],
4245
				'sub_buttons' => array(
4246
				),
4247
				'is_last' => !$context['right_to_left'],
4248
			),
4249
			'logout' => array(
4250
				'title' => $txt['logout'],
4251
				'href' => $scripturl . '?action=logout;%1$s=%2$s',
4252
				'show' => !$user_info['is_guest'],
4253
				'sub_buttons' => array(
4254
				),
4255
				'is_last' => !$context['right_to_left'],
4256
			),
4257
		);
4258
4259
		// Allow editing menu buttons easily.
4260
		call_integration_hook('integrate_menu_buttons', array(&$buttons));
4261
4262
		// Now we put the buttons in the context so the theme can use them.
4263
		$menu_buttons = array();
4264
		foreach ($buttons as $act => $button)
4265
			if (!empty($button['show']))
4266
			{
4267
				$button['active_button'] = false;
4268
4269
				// This button needs some action.
4270
				if (isset($button['action_hook']))
4271
					$needs_action_hook = true;
4272
4273
				// Make sure the last button truly is the last button.
4274
				if (!empty($button['is_last']))
4275
				{
4276
					if (isset($last_button))
4277
						unset($menu_buttons[$last_button]['is_last']);
4278
					$last_button = $act;
4279
				}
4280
4281
				// Go through the sub buttons if there are any.
4282
				if (!empty($button['sub_buttons']))
4283
					foreach ($button['sub_buttons'] as $key => $subbutton)
4284
					{
4285
						if (empty($subbutton['show']))
4286
							unset($button['sub_buttons'][$key]);
4287
4288
						// 2nd level sub buttons next...
4289
						if (!empty($subbutton['sub_buttons']))
4290
						{
4291
							foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2)
4292
							{
4293
								if (empty($sub_button2['show']))
4294
									unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
4295
							}
4296
						}
4297
					}
4298
4299
				// Does this button have its own icon?
4300
				if (isset($button['icon']) && file_exists($settings['theme_dir'] . '/images/' . $button['icon']))
4301
					$button['icon'] = '<img src="' . $settings['images_url'] . '/' . $button['icon'] . '" alt="">';
4302
				elseif (isset($button['icon']) && file_exists($settings['default_theme_dir'] . '/images/' . $button['icon']))
4303
					$button['icon'] = '<img src="' . $settings['default_images_url'] . '/' . $button['icon'] . '" alt="">';
4304
				elseif (isset($button['icon']))
4305
					$button['icon'] = '<span class="generic_icons ' . $button['icon'] . '"></span>';
4306
				else
4307
					$button['icon'] = '<span class="generic_icons ' . $act . '"></span>';
4308
4309
				$menu_buttons[$act] = $button;
4310
			}
4311
4312
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
4313
			cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
4314
	}
4315
4316
	$context['menu_buttons'] = $menu_buttons;
4317
4318
	// Logging out requires the session id in the url.
4319
	if (isset($context['menu_buttons']['logout']))
4320
		$context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']);
4321
4322
	// Figure out which action we are doing so we can set the active tab.
4323
	// Default to home.
4324
	$current_action = 'home';
4325
4326
	if (isset($context['menu_buttons'][$context['current_action']]))
4327
		$current_action = $context['current_action'];
4328
	elseif ($context['current_action'] == 'search2')
4329
		$current_action = 'search';
4330
	elseif ($context['current_action'] == 'theme')
4331
		$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin';
4332
	elseif ($context['current_action'] == 'register2')
4333
		$current_action = 'register';
4334
	elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder'))
4335
		$current_action = 'login';
4336
	elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center'])
4337
		$current_action = 'moderate';
4338
4339
	// There are certain exceptions to the above where we don't want anything on the menu highlighted.
4340
	if ($context['current_action'] == 'profile' && !empty($context['user']['is_owner']))
4341
	{
4342
		$current_action = !empty($_GET['area']) && $_GET['area'] == 'showalerts' ? 'self_alerts' : 'self_profile';
4343
		$context[$current_action] = true;
4344
	}
4345
	elseif ($context['current_action'] == 'pm')
4346
	{
4347
		$current_action = 'self_pm';
4348
		$context['self_pm'] = true;
4349
	}
4350
4351
	$total_mod_reports = 0;
4352
4353
	if (!empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1' && !empty($context['open_mod_reports']))
4354
	{
4355
		$total_mod_reports = $context['open_mod_reports'];
4356
		$context['menu_buttons']['moderate']['sub_buttons']['reports']['title'] .= ' <span class="amt">' . $context['open_mod_reports'] . '</span>';
4357
	}
4358
4359
	// Show how many errors there are
4360
	if (allowedTo('admin_forum'))
4361
	{
4362
		// Get an error count, if necessary
4363
		if (!isset($context['num_errors']))
4364
		{
4365
			$query = $smcFunc['db_query']('', '
4366
				SELECT COUNT(id_error)
4367
				FROM {db_prefix}log_errors',
4368
				array()
4369
			);
4370
4371
			list($context['num_errors']) = $smcFunc['db_fetch_row']($query);
4372
			$smcFunc['db_free_result']($query);
4373
		}
4374
4375 View Code Duplication
		if (!empty($context['num_errors']))
4376
		{
4377
			$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4378
			$context['menu_buttons']['admin']['sub_buttons']['errorlog']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4379
		}
4380
	}
4381
4382
	// Show number of reported members
4383
	if (!empty($context['open_member_reports']) && allowedTo('moderate_forum'))
4384
	{
4385
		$total_mod_reports += $context['open_member_reports'];
4386
		$context['menu_buttons']['moderate']['sub_buttons']['reported_members']['title'] .= ' <span class="amt">' . $context['open_member_reports'] . '</span>';
4387
	}
4388
4389 View Code Duplication
	if (!empty($context['unapproved_members']))
4390
	{
4391
		$context['menu_buttons']['admin']['sub_buttons']['memberapprove']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4392
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4393
	}
4394
4395
	// Do we have any open reports?
4396
	if ($total_mod_reports > 0)
4397
	{
4398
		$context['menu_buttons']['moderate']['title'] .= ' <span class="amt">' . $total_mod_reports . '</span>';
4399
	}
4400
4401
	// Not all actions are simple.
4402
	if (!empty($needs_action_hook))
4403
		call_integration_hook('integrate_current_action', array(&$current_action));
4404
4405
	if (isset($context['menu_buttons'][$current_action]))
4406
		$context['menu_buttons'][$current_action]['active_button'] = true;
4407
}
4408
4409
/**
4410
 * Generate a random seed and ensure it's stored in settings.
4411
 */
4412
function smf_seed_generator()
4413
{
4414
	updateSettings(array('rand_seed' => microtime() * 1000000));
4415
}
4416
4417
/**
4418
 * Process functions of an integration hook.
4419
 * calls all functions of the given hook.
4420
 * supports static class method calls.
4421
 *
4422
 * @param string $hook The hook name
4423
 * @param array $parameters An array of parameters this hook implements
4424
 * @return array The results of the functions
4425
 */
4426
function call_integration_hook($hook, $parameters = array())
4427
{
4428
	global $modSettings, $settings, $boarddir, $sourcedir, $db_show_debug;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4429
	global $context, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4430
4431
	if ($db_show_debug === true)
4432
		$context['debug']['hooks'][] = $hook;
4433
4434
	// Need to have some control.
4435
	if (!isset($context['instances']))
4436
		$context['instances'] = array();
4437
4438
	$results = array();
4439
	if (empty($modSettings[$hook]))
4440
		return $results;
4441
4442
	$functions = explode(',', $modSettings[$hook]);
4443
	// Loop through each function.
4444
	foreach ($functions as $function)
4445
	{
4446
		// Hook has been marked as "disabled". Skip it!
4447
		if (strpos($function, '!') !== false)
4448
			continue;
4449
4450
		$call = call_helper($function, true);
4451
4452
		// Is it valid?
4453
		if (!empty($call))
4454
			$results[$function] = call_user_func_array($call, $parameters);
4455
4456
		// Whatever it was suppose to call, it failed :(
4457
		elseif (!empty($function))
4458
		{
4459
			loadLanguage('Errors');
4460
4461
			// Get a full path to show on error.
4462
			if (strpos($function, '|') !== false)
4463
			{
4464
				list ($file, $string) = explode('|', $function);
4465
				$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'])));
4466
				log_error(sprintf($txt['hook_fail_call_to'], $string, $absPath), 'general');
4467
			}
4468
4469
			// "Assume" the file resides on $boarddir somewhere...
4470
			else
4471
				log_error(sprintf($txt['hook_fail_call_to'], $function, $boarddir), 'general');
4472
		}
4473
	}
4474
4475
	return $results;
4476
}
4477
4478
/**
4479
 * Add a function for integration hook.
4480
 * does nothing if the function is already added.
4481
 *
4482
 * @param string $hook The complete hook name.
4483
 * @param string $function The function name. Can be a call to a method via Class::method.
4484
 * @param bool $permanent If true, updates the value in settings table.
4485
 * @param string $file The file. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4486
 * @param bool $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4487
 */
4488
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
4489
{
4490
	global $smcFunc, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4491
4492
	// Any objects?
4493
	if ($object)
4494
		$function = $function . '#';
4495
4496
	// Any files  to load?
4497
	if (!empty($file) && is_string($file))
4498
		$function = $file . (!empty($function) ? '|' . $function : '');
4499
4500
	// Get the correct string.
4501
	$integration_call = $function;
4502
4503
	// Is it going to be permanent?
4504
	if ($permanent)
4505
	{
4506
		$request = $smcFunc['db_query']('', '
4507
			SELECT value
4508
			FROM {db_prefix}settings
4509
			WHERE variable = {string:variable}',
4510
			array(
4511
				'variable' => $hook,
4512
			)
4513
		);
4514
		list ($current_functions) = $smcFunc['db_fetch_row']($request);
4515
		$smcFunc['db_free_result']($request);
4516
4517
		if (!empty($current_functions))
4518
		{
4519
			$current_functions = explode(',', $current_functions);
4520
			if (in_array($integration_call, $current_functions))
4521
				return;
4522
4523
			$permanent_functions = array_merge($current_functions, array($integration_call));
4524
		}
4525
		else
4526
			$permanent_functions = array($integration_call);
4527
4528
		updateSettings(array($hook => implode(',', $permanent_functions)));
4529
	}
4530
4531
	// Make current function list usable.
4532
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4533
4534
	// Do nothing, if it's already there.
4535
	if (in_array($integration_call, $functions))
4536
		return;
4537
4538
	$functions[] = $integration_call;
4539
	$modSettings[$hook] = implode(',', $functions);
4540
}
4541
4542
/**
4543
 * Remove an integration hook function.
4544
 * Removes the given function from the given hook.
4545
 * Does nothing if the function is not available.
4546
 *
4547
 * @param string $hook The complete hook name.
4548
 * @param string $function The function name. Can be a call to a method via Class::method.
4549
 * @param boolean $permanent Irrelevant for the function itself but need to declare it to match
4550
 * @param string $file The filename. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4551
 * @param boolean $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4552
 * @see add_integration_function
4553
 */
4554
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...
4555
{
4556
	global $smcFunc, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4557
4558
	// Any objects?
4559
	if ($object)
4560
		$function = $function . '#';
4561
4562
	// Any files  to load?
4563
	if (!empty($file) && is_string($file))
4564
		$function = $file . '|' . $function;
4565
4566
	// Get the correct string.
4567
	$integration_call = $function;
4568
4569
	// Get the permanent functions.
4570
	$request = $smcFunc['db_query']('', '
4571
		SELECT value
4572
		FROM {db_prefix}settings
4573
		WHERE variable = {string:variable}',
4574
		array(
4575
			'variable' => $hook,
4576
		)
4577
	);
4578
	list ($current_functions) = $smcFunc['db_fetch_row']($request);
4579
	$smcFunc['db_free_result']($request);
4580
4581
	if (!empty($current_functions))
4582
	{
4583
		$current_functions = explode(',', $current_functions);
4584
4585
		if (in_array($integration_call, $current_functions))
4586
			updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call)))));
4587
	}
4588
4589
	// Turn the function list into something usable.
4590
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4591
4592
	// You can only remove it if it's available.
4593
	if (!in_array($integration_call, $functions))
4594
		return;
4595
4596
	$functions = array_diff($functions, array($integration_call));
4597
	$modSettings[$hook] = implode(',', $functions);
4598
}
4599
4600
/**
4601
 * Receives a string and tries to figure it out if its a method or a function.
4602
 * If a method is found, it looks for a "#" which indicates SMF should create a new instance of the given class.
4603
 * Checks the string/array for is_callable() and return false/fatal_lang_error is the given value results in a non callable string/array.
4604
 * Prepare and returns a callable depending on the type of method/function found.
4605
 *
4606
 * @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)
4607
 * @param boolean $return If true, the function will not call the function/method but instead will return the formatted string.
4608
 * @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.
4609
 */
4610
function call_helper($string, $return = false)
4611
{
4612
	global $context, $smcFunc, $txt, $db_show_debug;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4613
4614
	// Really?
4615
	if (empty($string))
4616
		return false;
4617
4618
	// An array? should be a "callable" array IE array(object/class, valid_callable).
4619
	// A closure? should be a callable one.
4620
	if (is_array($string) || $string instanceof Closure)
4621
		return $return ? $string : (is_callable($string) ? call_user_func($string) : false);
4622
4623
	// No full objects, sorry! pass a method or a property instead!
4624
	if (is_object($string))
4625
		return false;
4626
4627
	// Stay vitaminized my friends...
4628
	$string = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($string));
4629
4630
	// Is there a file to load?
4631
	$string = load_file($string);
4632
4633
	// Loaded file failed
4634
	if (empty($string))
4635
		return false;
4636
4637
	// Found a method.
4638
	if (strpos($string, '::') !== false)
4639
	{
4640
		list ($class, $method) = explode('::', $string);
4641
4642
		// Check if a new object will be created.
4643
		if (strpos($method, '#') !== false)
4644
		{
4645
			// Need to remove the # thing.
4646
			$method = str_replace('#', '', $method);
4647
4648
			// Don't need to create a new instance for every method.
4649
			if (empty($context['instances'][$class]) || !($context['instances'][$class] instanceof $class))
4650
			{
4651
				$context['instances'][$class] = new $class;
4652
4653
				// Add another one to the list.
4654
				if ($db_show_debug === true)
4655
				{
4656
					if (!isset($context['debug']['instances']))
4657
						$context['debug']['instances'] = array();
4658
4659
					$context['debug']['instances'][$class] = $class;
4660
				}
4661
			}
4662
4663
			$func = array($context['instances'][$class], $method);
4664
		}
4665
4666
		// Right then. This is a call to a static method.
4667
		else
4668
			$func = array($class, $method);
4669
	}
4670
4671
	// Nope! just a plain regular function.
4672
	else
4673
		$func = $string;
4674
4675
	// Right, we got what we need, time to do some checks.
4676
	if (!is_callable($func, false, $callable_name))
4677
	{
4678
		loadLanguage('Errors');
4679
		log_error(sprintf($txt['subAction_fail'], $callable_name), 'general');
4680
4681
		// Gotta tell everybody.
4682
		return false;
4683
	}
4684
4685
	// Everything went better than expected.
4686
	else
4687
	{
4688
		// What are we gonna do about it?
4689
		if ($return)
4690
			return $func;
4691
4692
		// If this is a plain function, avoid the heat of calling call_user_func().
4693
		else
4694
		{
4695
			if (is_array($func))
4696
				call_user_func($func);
4697
4698
			else
4699
				$func();
4700
		}
4701
	}
4702
}
4703
4704
/**
4705
 * Receives a string and tries to figure it out if it contains info to load a file.
4706
 * Checks for a | (pipe) symbol and tries to load a file with the info given.
4707
 * 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.
4708
 *
4709
 * @param string $string The string containing a valid format.
4710
 * @return string|boolean The given string with the pipe and file info removed. Boolean false if the file couldn't be loaded.
4711
 */
4712
function load_file($string)
4713
{
4714
	global $sourcedir, $txt, $boarddir, $settings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4715
4716
	if (empty($string))
4717
		return false;
4718
4719
	if (strpos($string, '|') !== false)
4720
	{
4721
		list ($file, $string) = explode('|', $string);
4722
4723
		// Match the wildcards to their regular vars.
4724
		if (empty($settings['theme_dir']))
4725
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
4726
4727
		else
4728
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
4729
4730
		// Load the file if it can be loaded.
4731
		if (file_exists($absPath))
4732
			require_once($absPath);
4733
4734
		// No? try a fallback to $sourcedir
4735
		else
4736
		{
4737
			$absPath = $sourcedir .'/'. $file;
4738
4739
			if (file_exists($absPath))
4740
				require_once($absPath);
4741
4742
			// Sorry, can't do much for you at this point.
4743
			else
4744
			{
4745
				loadLanguage('Errors');
4746
				log_error(sprintf($txt['hook_fail_loading_file'], $absPath), 'general');
4747
4748
				// File couldn't be loaded.
4749
				return false;
4750
			}
4751
		}
4752
	}
4753
4754
	return $string;
4755
}
4756
4757
/**
4758
 * Prepares an array of "likes" info for the topic specified by $topic
4759
 * @param integer $topic The topic ID to fetch the info from.
4760
 * @return array An array of IDs of messages in the specified topic that the current user likes
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
4761
 */
4762
function prepareLikesContext($topic)
4763
{
4764
	global $user_info, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4765
4766
	// Make sure we have something to work with.
4767
	if (empty($topic))
4768
		return array();
4769
4770
4771
	// We already know the number of likes per message, we just want to know whether the current user liked it or not.
4772
	$user = $user_info['id'];
4773
	$cache_key = 'likes_topic_' . $topic . '_' . $user;
4774
	$ttl = 180;
4775
4776
	if (($temp = cache_get_data($cache_key, $ttl)) === null)
4777
	{
4778
		$temp = array();
4779
		$request = $smcFunc['db_query']('', '
4780
			SELECT content_id
4781
			FROM {db_prefix}user_likes AS l
4782
				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
4783
			WHERE l.id_member = {int:current_user}
4784
				AND l.content_type = {literal:msg}
4785
				AND m.id_topic = {int:topic}',
4786
			array(
4787
				'current_user' => $user,
4788
				'topic' => $topic,
4789
			)
4790
		);
4791
		while ($row = $smcFunc['db_fetch_assoc']($request))
4792
			$temp[] = (int) $row['content_id'];
4793
4794
		cache_put_data($cache_key, $temp, $ttl);
4795
	}
4796
4797
	return $temp;
4798
}
4799
4800
/**
4801
 * Microsoft uses their own character set Code Page 1252 (CP1252), which is a
4802
 * superset of ISO 8859-1, defining several characters between DEC 128 and 159
4803
 * that are not normally displayable.  This converts the popular ones that
4804
 * appear from a cut and paste from windows.
4805
 *
4806
 * @param string $string The string
4807
 * @return string The sanitized string
4808
 */
4809
function sanitizeMSCutPaste($string)
4810
{
4811
	global $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4812
4813
	if (empty($string))
4814
		return $string;
4815
4816
	// UTF-8 occurences of MS special characters
4817
	$findchars_utf8 = array(
4818
		"\xe2\x80\x9a",	// single low-9 quotation mark
4819
		"\xe2\x80\x9e",	// double low-9 quotation mark
4820
		"\xe2\x80\xa6",	// horizontal ellipsis
4821
		"\xe2\x80\x98",	// left single curly quote
4822
		"\xe2\x80\x99",	// right single curly quote
4823
		"\xe2\x80\x9c",	// left double curly quote
4824
		"\xe2\x80\x9d",	// right double curly quote
4825
		"\xe2\x80\x93",	// en dash
4826
		"\xe2\x80\x94",	// em dash
4827
	);
4828
4829
	// windows 1252 / iso equivalents
4830
	$findchars_iso = array(
4831
		chr(130),
4832
		chr(132),
4833
		chr(133),
4834
		chr(145),
4835
		chr(146),
4836
		chr(147),
4837
		chr(148),
4838
		chr(150),
4839
		chr(151),
4840
	);
4841
4842
	// safe replacements
4843
	$replacechars = array(
4844
		',',	// &sbquo;
4845
		',,',	// &bdquo;
4846
		'...',	// &hellip;
4847
		"'",	// &lsquo;
4848
		"'",	// &rsquo;
4849
		'"',	// &ldquo;
4850
		'"',	// &rdquo;
4851
		'-',	// &ndash;
4852
		'--',	// &mdash;
4853
	);
4854
4855
	if ($context['utf8'])
4856
		$string = str_replace($findchars_utf8, $replacechars, $string);
4857
	else
4858
		$string = str_replace($findchars_iso, $replacechars, $string);
4859
4860
	return $string;
4861
}
4862
4863
/**
4864
 * Decode numeric html entities to their ascii or UTF8 equivalent character.
4865
 *
4866
 * Callback function for preg_replace_callback in subs-members
4867
 * Uses capture group 2 in the supplied array
4868
 * Does basic scan to ensure characters are inside a valid range
4869
 *
4870
 * @param array $matches An array of matches (relevant info should be the 3rd item)
4871
 * @return string A fixed string
4872
 */
4873
function replaceEntities__callback($matches)
4874
{
4875
	global $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4876
4877
	if (!isset($matches[2]))
4878
		return '';
4879
4880
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4881
4882
	// remove left to right / right to left overrides
4883
	if ($num === 0x202D || $num === 0x202E)
4884
		return '';
4885
4886
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
4887
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
4888
		return '&#' . $num . ';';
4889
4890
	if (empty($context['utf8']))
4891
	{
4892
		// no control characters
4893
		if ($num < 0x20)
4894
			return '';
4895
		// text is text
4896
		elseif ($num < 0x80)
4897
			return chr($num);
4898
		// all others get html-ised
4899
		else
4900
			return '&#' . $matches[2] . ';';
4901
	}
4902
	else
4903
	{
4904
		// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
4905
		// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
4906
		if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
4907
			return '';
4908
		// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4909
		elseif ($num < 0x80)
4910
			return chr($num);
4911
		// <0x800 (2048)
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4912 View Code Duplication
		elseif ($num < 0x800)
4913
			return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4914
		// < 0x10000 (65536)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4915 View Code Duplication
		elseif ($num < 0x10000)
4916
			return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4917
		// <= 0x10FFFF (1114111)
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4918 View Code Duplication
		else
4919
			return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4920
	}
4921
}
4922
4923
/**
4924
 * Converts html entities to utf8 equivalents
4925
 *
4926
 * Callback function for preg_replace_callback
4927
 * Uses capture group 1 in the supplied array
4928
 * Does basic checks to keep characters inside a viewable range.
4929
 *
4930
 * @param array $matches An array of matches (relevant info should be the 2nd item in the array)
4931
 * @return string The fixed string
4932
 */
4933
function fixchar__callback($matches)
4934
{
4935
	if (!isset($matches[1]))
4936
		return '';
4937
4938
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
4939
4940
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
4941
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
4942
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
4943
		return '';
4944
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4945
	elseif ($num < 0x80)
4946
		return chr($num);
4947
	// <0x800 (2048)
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4948 View Code Duplication
	elseif ($num < 0x800)
4949
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4950
	// < 0x10000 (65536)
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4951 View Code Duplication
	elseif ($num < 0x10000)
4952
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4953
	// <= 0x10FFFF (1114111)
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
4954 View Code Duplication
	else
4955
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4956
}
4957
4958
/**
4959
 * Strips out invalid html entities, replaces others with html style &#123; codes
4960
 *
4961
 * Callback function used of preg_replace_callback in smcFunc $ent_checks, for example
4962
 * strpos, strlen, substr etc
4963
 *
4964
 * @param array $matches An array of matches (relevant info should be the 3rd item in the array)
4965
 * @return string The fixed string
4966
 */
4967
function entity_fix__callback($matches)
4968
{
4969
	if (!isset($matches[2]))
4970
		return '';
4971
4972
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4973
4974
	// we don't allow control characters, characters out of range, byte markers, etc
4975
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
4976
		return '';
4977
	else
4978
		return '&#' . $num . ';';
4979
}
4980
4981
/**
4982
 * Return a Gravatar URL based on
4983
 * - the supplied email address,
4984
 * - the global maximum rating,
4985
 * - the global default fallback,
4986
 * - maximum sizes as set in the admin panel.
4987
 *
4988
 * It is SSL aware, and caches most of the parameters.
4989
 *
4990
 * @param string $email_address The user's email address
4991
 * @return string The gravatar URL
4992
 */
4993
function get_gravatar_url($email_address)
4994
{
4995
	global $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
4996
	static $url_params = null;
4997
4998
	if ($url_params === null)
4999
	{
5000
		$ratings = array('G', 'PG', 'R', 'X');
5001
		$defaults = array('mm', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank');
5002
		$url_params = array();
5003 View Code Duplication
		if (!empty($modSettings['gravatarMaxRating']) && in_array($modSettings['gravatarMaxRating'], $ratings))
5004
			$url_params[] = 'rating=' . $modSettings['gravatarMaxRating'];
5005 View Code Duplication
		if (!empty($modSettings['gravatarDefault']) && in_array($modSettings['gravatarDefault'], $defaults))
5006
			$url_params[] = 'default=' . $modSettings['gravatarDefault'];
5007
		if (!empty($modSettings['avatar_max_width_external']))
5008
			$size_string = (int) $modSettings['avatar_max_width_external'];
5009
		if (!empty($modSettings['avatar_max_height_external']) && !empty($size_string))
5010
			if ((int) $modSettings['avatar_max_height_external'] < $size_string)
5011
				$size_string = $modSettings['avatar_max_height_external'];
5012
5013
		if (!empty($size_string))
5014
			$url_params[] = 's=' . $size_string;
5015
	}
5016
	$http_method = !empty($modSettings['force_ssl']) ? 'https://secure' : 'http://www';
5017
5018
	return $http_method . '.gravatar.com/avatar/' . md5($smcFunc['strtolower']($email_address)) . '?' . implode('&', $url_params);
5019
}
5020
5021
/**
5022
 * Get a list of timezones.
5023
 *
5024
 * @param string $when An optional date or time for which to calculate the timezone offset values. May be a Unix timestamp or any string that strtotime() can understand. Defaults to 'now'.
5025
 * @return array An array of timezone info.
5026
 */
5027
function smf_list_timezones($when = 'now')
5028
{
5029
	global $smcFunc, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
5030
	static $timezones = null, $lastwhen = null;
5031
5032
	// No point doing this over if we already did it once
5033
	if (!empty($timezones) && $when == $lastwhen)
5034
		return $timezones;
5035
	else
5036
		$lastwhen = $when;
5037
5038
	// Parseable datetime string?
5039
	if (is_int($timestamp = strtotime($when)))
5040
		$when = $timestamp;
5041
5042
	// A Unix timestamp?
5043
	elseif (is_numeric($when))
5044
		$when = intval($when);
5045
5046
	// Invalid value? Just get current Unix timestamp.
5047
	else
5048
		$when = time();
5049
5050
	// We'll need these too
5051
	$date_when = date_create('@' . $when);
5052
	$later = (int) date_format(date_add($date_when, date_interval_create_from_date_string('1 year')), 'U');
5053
5054
	// Prefer and give custom descriptions for these time zones
5055
	// If the description is left empty, it will be filled in with the names of matching cities
5056
	$timezone_descriptions = array(
5057
		'America/Adak' => 'Aleutian Islands',
5058
		'Pacific/Marquesas' => 'Marquesas Islands',
5059
		'Pacific/Gambier' => 'Gambier Islands',
5060
		'America/Anchorage' => 'Alaska',
5061
		'Pacific/Pitcairn' => 'Pitcairn Islands',
5062
		'America/Los_Angeles' => 'Pacific Time (USA, Canada)',
5063
		'America/Denver' => 'Mountain Time (USA, Canada)',
5064
		'America/Phoenix' => 'Mountain Time (no DST)',
5065
		'America/Chicago' => 'Central Time (USA, Canada)',
5066
		'America/Belize' => 'Central Time (no DST)',
5067
		'America/New_York' => 'Eastern Time (USA, Canada)',
5068
		'America/Atikokan' => 'Eastern Time (no DST)',
5069
		'America/Halifax' => 'Atlantic Time (Canada)',
5070
		'America/Anguilla' => 'Atlantic Time (no DST)',
5071
		'America/St_Johns' => 'Newfoundland',
5072
		'America/Chihuahua' => 'Chihuahua, Mazatlan',
5073
		'Pacific/Easter' => 'Easter Island',
5074
		'Atlantic/Stanley' => 'Falkland Islands',
5075
		'America/Miquelon' => 'Saint Pierre and Miquelon',
5076
		'America/Argentina/Buenos_Aires' => 'Buenos Aires',
5077
		'America/Sao_Paulo' => 'Brasilia Time',
5078
		'America/Araguaina' => 'Brasilia Time (no DST)',
5079
		'America/Godthab' => 'Greenland',
5080
		'America/Noronha' => 'Fernando de Noronha',
5081
		'Atlantic/Reykjavik' => 'Greenwich Mean Time (no DST)',
5082
		'Europe/London' => '',
5083
		'Europe/Berlin' => 'Central European Time',
5084
		'Europe/Helsinki' => 'Eastern European Time',
5085
		'Africa/Brazzaville' => 'Brazzaville, Lagos, Porto-Novo',
5086
		'Asia/Jerusalem' => 'Jerusalem',
5087
		'Europe/Moscow' => '',
5088
		'Africa/Khartoum' => 'Eastern Africa Time',
5089
		'Asia/Riyadh' => 'Arabia Time',
5090
		'Asia/Kolkata' => 'India, Sri Lanka',
5091
		'Asia/Yekaterinburg' => 'Yekaterinburg, Tyumen',
5092
		'Asia/Dhaka' => 'Astana, Dhaka',
5093
		'Asia/Rangoon' => 'Yangon/Rangoon',
5094
		'Indian/Christmas' => 'Christmas Island',
5095
		'Antarctica/DumontDUrville' => 'Dumont D\'Urville Station',
5096
		'Antarctica/Vostok' => 'Vostok Station',
5097
		'Australia/Lord_Howe' => 'Lord Howe Island',
5098
		'Pacific/Guadalcanal' => 'Solomon Islands',
5099
		'Pacific/Norfolk' => 'Norfolk Island',
5100
		'Pacific/Noumea' => 'New Caledonia',
5101
		'Pacific/Auckland' => 'Auckland, McMurdo Station',
5102
		'Pacific/Kwajalein' => 'Marshall Islands',
5103
		'Pacific/Chatham' => 'Chatham Islands',
5104
	);
5105
5106
	// Should we put time zones from certain countries at the top of the list?
5107
	$priority_countries = !empty($modSettings['timezone_priority_countries']) ? explode(',', $modSettings['timezone_priority_countries']) : array();
5108
	$priority_tzids = array();
5109
	foreach ($priority_countries as $country)
5110
	{
5111
		$country_tzids = @timezone_identifiers_list(DateTimeZone::PER_COUNTRY, strtoupper(trim($country)));
5112
		if (!empty($country_tzids))
5113
			$priority_tzids = array_merge($priority_tzids, $country_tzids);
5114
	}
5115
5116
	// Process the preferred timezones first, then the rest.
5117
	$tzids = array_keys($timezone_descriptions) + array_diff(timezone_identifiers_list(), array_keys($timezone_descriptions));
5118
5119
	// Idea here is to get exactly one representative identifier for each and every unique set of time zone rules.
5120
	foreach ($tzids as $tzid)
5121
	{
5122
		// We don't want UTC right now
5123
		if ($tzid == 'UTC')
5124
			continue;
5125
5126
		$tz = timezone_open($tzid);
5127
5128
		// First, get the set of transition rules for this tzid
5129
		$tzinfo = timezone_transitions_get($tz, $when, $later);
5130
5131
		// Use the entire set of transition rules as the array *key* so we can avoid duplicates
5132
		$tzkey = serialize($tzinfo);
5133
5134
		// Next, get the geographic info for this tzid
5135
		$tzgeo = timezone_location_get($tz);
5136
5137
		// Don't overwrite our preferred tzids
5138
		if (empty($zones[$tzkey]['tzid']))
5139
		{
5140
			$zones[$tzkey]['tzid'] = $tzid;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$zones was never initialized. Although not strictly required by PHP, it is generally a good practice to add $zones = 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...
5141
			$zones[$tzkey]['abbr'] = fix_tz_abbrev($tzid, $tzinfo[0]['abbr']);
0 ignored issues
show
Bug introduced by
The variable $zones 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...
5142
		}
5143
5144
		// A time zone from a prioritized country?
5145
		if (in_array($tzid, $priority_tzids))
5146
			$priority_zones[$tzkey] = true;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$priority_zones was never initialized. Although not strictly required by PHP, it is generally a good practice to add $priority_zones = 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...
5147
5148
		// Keep track of the location and offset for this tzid
5149
		$tzid_parts = explode('/', $tzid);
5150
		$zones[$tzkey]['locations'][] = str_replace(array('St_', '_'), array('St. ', ' '), array_pop($tzid_parts));
5151
		$offsets[$tzkey] = $tzinfo[0]['offset'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$offsets was never initialized. Although not strictly required by PHP, it is generally a good practice to add $offsets = 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...
5152
		$longitudes[$tzkey] = empty($longitudes[$tzkey]) ? $tzgeo['longitude'] : $longitudes[$tzkey];
0 ignored issues
show
Bug introduced by
The variable $longitudes 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...
5153
	}
5154
5155
	// Sort by offset then longitude
5156
	array_multisort($offsets, SORT_ASC, SORT_NUMERIC, $longitudes, SORT_ASC, SORT_NUMERIC, $zones);
5157
5158
	// Build the final array of formatted values
5159
	$priority_timezones = array();
5160
	$timezones = array();
5161
	foreach ($zones as $tzkey => $tzvalue)
5162
	{
5163
		date_timezone_set($date_when, timezone_open($tzvalue['tzid']));
5164
5165
		if (!empty($timezone_descriptions[$tzvalue['tzid']]))
5166
			$desc = $timezone_descriptions[$tzvalue['tzid']];
5167
		else
5168
			$desc = implode(', ', array_unique($tzvalue['locations']));
5169
5170
		if (isset($priority_zones[$tzkey]))
5171
			$priority_timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']';
5172
		else
5173
			$timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']';
5174
	}
5175
5176
	$timezones = array_merge(
5177
		$priority_timezones,
5178
		array('' => '(Forum Default)', 'UTC' => 'UTC - Coordinated Universal Time'),
5179
		$timezones
5180
	);
5181
5182
	return $timezones;
5183
}
5184
5185
/**
5186
 * Reformats certain time zone abbreviations to look better.
5187
 *
5188
 * Some of PHP's time zone abbreviations are just numerical offsets from UTC, e.g. '+04'
5189
 * These look weird and are kind of useless, so we make them look better.
5190
 *
5191
 * @param string $tzid The Olsen time zome identifier for a time zone.
5192
 * @param string $tz_abbrev The abbreviation PHP provided for this time zone.
5193
 * @return string The fixed version of $tz_abbrev.
5194
 */
5195
function fix_tz_abbrev($tzid, $tz_abbrev)
5196
{
5197
	// Is this abbreviation just a numerical offset?
5198
	if (strspn($tz_abbrev, '+-') > 0)
5199
	{
5200
		// To get on this list, a time zone must be historically stable and must not observe daylight saving time
5201
		$missing_tz_abbrs = array(
5202
			'Antarctica/Casey' => 'CAST',
5203
			'Antarctica/Davis' => 'DAVT',
5204
			'Antarctica/DumontDUrville' => 'DDUT',
5205
			'Antarctica/Mawson' => 'MAWT',
5206
			'Antarctica/Rothera' => 'ART',
5207
			'Antarctica/Syowa' => 'SYOT',
5208
			'Antarctica/Vostok' => 'VOST',
5209
			'Asia/Almaty' => 'ALMT',
5210
			'Asia/Aqtau' => 'ORAT',
5211
			'Asia/Aqtobe' => 'AQTT',
5212
			'Asia/Ashgabat' => 'TMT',
5213
			'Asia/Bishkek' => 'KGT',
5214
			'Asia/Colombo' => 'IST',
5215
			'Asia/Dushanbe' => 'TJT',
5216
			'Asia/Oral' => 'ORAT',
5217
			'Asia/Qyzylorda' => 'QYZT',
5218
			'Asia/Samarkand' => 'UZT',
5219
			'Asia/Tashkent' => 'UZT',
5220
			'Asia/Tbilisi' => 'GET',
5221
			'Asia/Yerevan' => 'AMT',
5222
			'Europe/Istanbul' => 'TRT',
5223
			'Europe/Minsk' => 'MSK',
5224
			'Indian/Kerguelen' => 'TFT',
5225
		);
5226
5227
		if (!empty($missing_tz_abbrs[$tzid]))
5228
			$tz_abbrev = $missing_tz_abbrs[$tzid];
5229
		else
5230
		{
5231
			// Russia likes to experiment with time zones often, and names them as offsets from Moscow
5232
			$tz_location = timezone_location_get(timezone_open($tzid));
5233
			if ($tz_location['country_code'] == 'RU')
5234
			{
5235
				$msk_offset = intval($tz_abbrev) - 3;
5236
				$tz_abbrev = 'MSK' . (!empty($msk_offset) ? sprintf('%+0d', $msk_offset) : '');
5237
			}
5238
		}
5239
5240
		// Still no good? We'll just mark it as a UTC offset
5241
		if (strspn($tz_abbrev, '+-') > 0)
5242
		{
5243
			$tz_abbrev = intval($tz_abbrev);
5244
			$tz_abbrev = 'UTC' . (!empty($tz_abbrev) ? sprintf('%+0d', $tz_abbrev) : '');
5245
		}
5246
	}
5247
5248
	return $tz_abbrev;
5249
}
5250
5251
/**
5252
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
5253
 * @return string|false The IP address in binary or false
5254
 */
5255
function inet_ptod($ip_address)
5256
{
5257
	if (!isValidIP($ip_address))
5258
		return $ip_address;
5259
5260
	$bin = inet_pton($ip_address);
5261
	return $bin;
5262
}
5263
5264
/**
5265
 * @param string $bin An IP address in IPv4, IPv6 (Either string (postgresql) or binary (other databases))
5266
 * @return string|false The IP address in presentation format or false on error
5267
 */
5268
function inet_dtop($bin)
5269
{
5270
	if(empty($bin))
5271
		return '';
5272
5273
	global $db_type;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
5274
5275
	if ($db_type == 'postgresql')
5276
		return $bin;
5277
5278
	$ip_address = inet_ntop($bin);
5279
5280
	return $ip_address;
5281
}
5282
5283
/**
5284
 * Safe serialize() and unserialize() replacements
5285
 *
5286
 * @license Public Domain
5287
 *
5288
 * @author anthon (dot) pang (at) gmail (dot) com
5289
 */
5290
5291
/**
5292
 * Safe serialize() replacement. Recursive
5293
 * - output a strict subset of PHP's native serialized representation
5294
 * - does not serialize objects
5295
 *
5296
 * @param mixed $value
5297
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
5298
 */
5299
function _safe_serialize($value)
5300
{
5301
	if(is_null($value))
5302
		return 'N;';
5303
5304
	if(is_bool($value))
5305
		return 'b:'. (int) $value .';';
5306
5307
	if(is_int($value))
5308
		return 'i:'. $value .';';
5309
5310
	if(is_float($value))
5311
		return 'd:'. str_replace(',', '.', $value) .';';
5312
5313
	if(is_string($value))
5314
		return 's:'. strlen($value) .':"'. $value .'";';
5315
5316
	if(is_array($value))
5317
	{
5318
		$out = '';
5319
		foreach($value as $k => $v)
5320
			$out .= _safe_serialize($k) . _safe_serialize($v);
5321
5322
		return 'a:'. count($value) .':{'. $out .'}';
5323
	}
5324
5325
	// safe_serialize cannot serialize resources or objects.
5326
	return false;
5327
}
5328
/**
5329
 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issues.
5330
 *
5331
 * @param mixed $value
5332
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
5333
 */
5334 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...
5335
{
5336
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5337
	if (function_exists('mb_internal_encoding') &&
5338
		(((int) ini_get('mbstring.func_overload')) & 2))
5339
	{
5340
		$mbIntEnc = mb_internal_encoding();
5341
		mb_internal_encoding('ASCII');
5342
	}
5343
5344
	$out = _safe_serialize($value);
5345
5346
	if (isset($mbIntEnc))
5347
		mb_internal_encoding($mbIntEnc);
5348
5349
	return $out;
5350
}
5351
5352
/**
5353
 * Safe unserialize() replacement
5354
 * - accepts a strict subset of PHP's native serialized representation
5355
 * - does not unserialize objects
5356
 *
5357
 * @param string $str
5358
 * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use boolean|integer|double|string|null|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
5359
 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
5360
 */
5361
function _safe_unserialize($str)
5362
{
5363
	// Input  is not a string.
5364
	if(empty($str) || !is_string($str))
5365
		return false;
5366
5367
	$stack = array();
5368
	$expected = array();
5369
5370
	/*
5371
	 * states:
5372
	 *   0 - initial state, expecting a single value or array
5373
	 *   1 - terminal state
5374
	 *   2 - in array, expecting end of array or a key
5375
	 *   3 - in array, expecting value or another array
5376
	 */
5377
	$state = 0;
5378
	while($state != 1)
5379
	{
5380
		$type = isset($str[0]) ? $str[0] : '';
5381
		if($type == '}')
5382
			$str = substr($str, 1);
5383
5384
		else if($type == 'N' && $str[1] == ';')
5385
		{
5386
			$value = null;
5387
			$str = substr($str, 2);
5388
		}
5389
		else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
5390
		{
5391
			$value = $matches[1] == '1' ? true : false;
5392
			$str = substr($str, 4);
5393
		}
5394
		else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
5395
		{
5396
			$value = (int)$matches[1];
5397
			$str = $matches[2];
5398
		}
5399
		else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
5400
		{
5401
			$value = (float)$matches[1];
5402
			$str = $matches[3];
5403
		}
5404
		else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
5405
		{
5406
			$value = substr($matches[2], 0, (int)$matches[1]);
5407
			$str = substr($matches[2], (int)$matches[1] + 2);
5408
		}
5409
		else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches))
5410
		{
5411
			$expectedLength = (int)$matches[1];
5412
			$str = $matches[2];
5413
		}
5414
5415
		// Object or unknown/malformed type.
5416
		else
5417
			return false;
5418
5419
		switch($state)
5420
		{
5421
			case 3: // In array, expecting value or another array.
5422
				if($type == 'a')
5423
				{
5424
					$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...
5425
					$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...
5426
					$list = &$list[$key];
5427
					$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...
5428
					$state = 2;
5429
					break;
5430
				}
5431
				if($type != '}')
5432
				{
5433
					$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...
5434
					$state = 2;
5435
					break;
5436
				}
5437
5438
				// Missing array value.
5439
				return false;
5440
5441
			case 2: // in array, expecting end of array or a key
5442
				if($type == '}')
5443
				{
5444
					// Array size is less than expected.
5445
					if(count($list) < end($expected))
5446
						return false;
5447
5448
					unset($list);
5449
					$list = &$stack[count($stack)-1];
5450
					array_pop($stack);
5451
5452
					// Go to terminal state if we're at the end of the root array.
5453
					array_pop($expected);
5454
5455
					if(count($expected) == 0)
5456
						$state = 1;
5457
5458
					break;
5459
				}
5460
5461
				if($type == 'i' || $type == 's')
5462
				{
5463
					// Array size exceeds expected length.
5464
					if(count($list) >= end($expected))
5465
						return false;
5466
5467
					$key = $value;
5468
					$state = 3;
5469
					break;
5470
				}
5471
5472
				// Illegal array index type.
5473
				return false;
5474
5475
			// Expecting array or value.
5476
			case 0:
5477
				if($type == 'a')
5478
				{
5479
					$data = array();
5480
					$list = &$data;
5481
					$expected[] = $expectedLength;
5482
					$state = 2;
5483
					break;
5484
				}
5485
5486
				if($type != '}')
5487
				{
5488
					$data = $value;
5489
					$state = 1;
5490
					break;
5491
				}
5492
5493
				// Not in array.
5494
				return false;
5495
		}
5496
	}
5497
5498
	// Trailing data in input.
5499
	if(!empty($str))
5500
		return false;
5501
5502
	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...
5503
}
5504
5505
/**
5506
 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
5507
 *
5508
 * @param string $str
5509
 * @return mixed
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use boolean|integer|double|string|null|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
5510
 */
5511 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...
5512
{
5513
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5514
	if (function_exists('mb_internal_encoding') &&
5515
		(((int) ini_get('mbstring.func_overload')) & 0x02))
5516
	{
5517
		$mbIntEnc = mb_internal_encoding();
5518
		mb_internal_encoding('ASCII');
5519
	}
5520
5521
	$out = _safe_unserialize($str);
5522
5523
	if (isset($mbIntEnc))
5524
		mb_internal_encoding($mbIntEnc);
5525
5526
	return $out;
5527
}
5528
5529
/**
5530
 * Tries different modes to make file/dirs writable. Wrapper function for chmod()
5531
5532
 * @param string $file The file/dir full path.
5533
 * @param int $value Not needed, added for legacy reasons.
5534
 * @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.
5535
 */
5536
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...
5537
{
5538
	// No file? no checks!
5539
	if (empty($file))
5540
		return false;
5541
5542
	// Already writable?
5543
	if (is_writable($file))
5544
		return true;
5545
5546
	// Do we have a file or a dir?
5547
	$isDir = is_dir($file);
5548
	$isWritable = false;
5549
5550
	// Set different modes.
5551
	$chmodValues = $isDir ? array(0750, 0755, 0775, 0777) : array(0644, 0664, 0666);
5552
5553
	foreach($chmodValues as $val)
5554
	{
5555
		// If it's writable, break out of the loop.
5556
		if (is_writable($file))
5557
		{
5558
			$isWritable = true;
5559
			break;
5560
		}
5561
5562
		else
5563
			@chmod($file, $val);
0 ignored issues
show
Security File Manipulation introduced by
$file can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

22 paths for user data to reach this point

  1. Path: Read from $_FILES, and $_FILES['package']['name'] is passed through strtolower(), and strtolower($_FILES['package']['name']) is passed through strrchr(), and strrchr(strtolower($_FILES['package']['name']), '.') is passed through substr(), and $extension is assigned in Sources/PackageGet.php on line 656
  1. Read from $_FILES, and $_FILES['package']['name'] is passed through strtolower(), and strtolower($_FILES['package']['name']) is passed through strrchr(), and strrchr(strtolower($_FILES['package']['name']), '.') is passed through substr(), and $extension is assigned
    in Sources/PackageGet.php on line 656
  2. $extension is assigned
    in Sources/PackageGet.php on line 663
  3. $packageName is assigned
    in Sources/PackageGet.php on line 664
  4. $destination is assigned
    in Sources/PackageGet.php on line 667
  5. $destination is passed to smf_chmod()
    in Sources/PackageGet.php on line 674
  2. Path: Read from $_GET, and $packagesdir . '/' . $_GET['package'] is passed to smf_chmod() in Sources/Packages.php on line 1342
  1. Read from $_GET, and $packagesdir . '/' . $_GET['package'] is passed to smf_chmod()
    in Sources/Packages.php on line 1342
  3. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2444
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2444
  2. $context is assigned
    in Sources/Packages.php on line 2455
  3. $path is assigned
    in Sources/Packages.php on line 2509
  4. $path is passed to smf_chmod()
    in Sources/Packages.php on line 2522
  4. Path: Read from $_POST, and $status is assigned in Sources/Packages.php on line 2465
  1. Read from $_POST, and $status is assigned
    in Sources/Packages.php on line 2465
  2. $context is assigned
    in Sources/Packages.php on line 2486
  3. $context is assigned
    in Sources/Packages.php on line 2488
  4. $path is assigned
    in Sources/Packages.php on line 2509
  5. $path is passed to smf_chmod()
    in Sources/Packages.php on line 2522
  5. Path: Read from $_GET, and $context is assigned in Sources/ManageLanguages.php on line 792
  1. Read from $_GET, and $context is assigned
    in Sources/ManageLanguages.php on line 792
  2. $context is assigned
    in Sources/ManageLanguages.php on line 797
  3. $images_dirs is assigned
    in Sources/ManageLanguages.php on line 839
  4. $curPath is assigned
    in Sources/ManageLanguages.php on line 912
  5. $curPath is passed to deltree()
    in Sources/ManageLanguages.php on line 914
  6. $dir . '/' . $entryname is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1794
  6. Path: Read from $_GET, and $packagesdir . '/' . $_GET['package'] is passed to deltree() in Sources/Packages.php on line 1339
  1. Read from $_GET, and $packagesdir . '/' . $_GET['package'] is passed to deltree()
    in Sources/Packages.php on line 1339
  2. $dir . '/' . $entryname is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1794
  7. Path: Read from $_REQUEST, and $_REQUEST['file'] is passed to read_tgz_file() in Sources/Packages.php on line 1285
  1. Read from $_REQUEST, and $_REQUEST['file'] is passed to read_tgz_file()
    in Sources/Packages.php on line 1285
  2. $destination is passed to read_tgz_data()
    in Sources/Subs-Package.php on line 35
  3. $destination is passed to mktree()
    in Sources/Subs-Package.php on line 90
  4. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  8. Path: Read from $_REQUEST, and $_REQUEST['file'] is passed to read_tgz_file() in Sources/Packages.php on line 1309
  1. Read from $_REQUEST, and $_REQUEST['file'] is passed to read_tgz_file()
    in Sources/Packages.php on line 1309
  2. $destination is passed to read_tgz_data()
    in Sources/Subs-Package.php on line 35
  3. $destination is passed to mktree()
    in Sources/Subs-Package.php on line 90
  4. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  9. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2544
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2544
  2. $context is assigned
    in Sources/Packages.php on line 2546
  3. $context is assigned
    in Sources/Packages.php on line 2547
  4. $context is assigned
    in Sources/Packages.php on line 2549
  5. $context is assigned
    in Sources/Packages.php on line 2594
  6. $path is assigned
    in Sources/Packages.php on line 2625
  7. $path . '/' . $entry is passed to package_chmod()
    in Sources/Packages.php on line 2638
  8. $chmod_file is assigned
    in Sources/Subs-Package.php on line 2849
  9. $chmod_file is passed to smf_chmod()
    in Sources/Subs-Package.php on line 2874
  10. Path: Read from $_POST, and $file is assigned in Sources/ManageLanguages.php on line 223
  1. Read from $_POST, and $file is assigned
    in Sources/ManageLanguages.php on line 223
  2. $chmod_files is assigned
    in Sources/ManageLanguages.php on line 229
  3. $chmod_files is passed to create_chmod_control()
    in Sources/ManageLanguages.php on line 234
  4. $file is assigned
    in Sources/Subs-Package.php on line 848
  5. $file is passed to package_chmod()
    in Sources/Subs-Package.php on line 859
  6. $chmod_file is assigned
    in Sources/Subs-Package.php on line 2849
  7. $chmod_file is passed to smf_chmod()
    in Sources/Subs-Package.php on line 2874
  11. Path: Read from $_GET, and $context is assigned in Sources/ManageLanguages.php on line 209
  1. Read from $_GET, and $context is assigned
    in Sources/ManageLanguages.php on line 209
  2. $context is assigned
    in Sources/ManageLanguages.php on line 210
  3. $context is assigned
    in Sources/ManageLanguages.php on line 211
  4. $context is assigned
    in Sources/ManageLanguages.php on line 261
  5. $context is assigned
    in Sources/ManageLanguages.php on line 265
  6. $context['make_writable'] is passed to create_chmod_control()
    in Sources/ManageLanguages.php on line 382
  7. $file is assigned
    in Sources/Subs-Package.php on line 848
  8. $file is passed to package_chmod()
    in Sources/Subs-Package.php on line 859
  9. $chmod_file is assigned
    in Sources/Subs-Package.php on line 2849
  10. $chmod_file is passed to smf_chmod()
    in Sources/Subs-Package.php on line 2874
  12. Path: Read from $_POST, and $_POST['basedirectory_for_attachments'] is passed to automanage_attachments_create_directory() in Sources/ManageAttachments.php on line 222
  1. Read from $_POST, and $_POST['basedirectory_for_attachments'] is passed to automanage_attachments_create_directory()
    in Sources/ManageAttachments.php on line 222
  2. Data is passed through preg_split()
    in vendor/Sources/Subs-Attachments.php on line 260
  3. $tree is assigned
    in Sources/Subs-Attachments.php on line 138
  4. $tree is passed through array_shift(), and $directory is assigned
    in Sources/Subs-Attachments.php on line 146
  5. $directory is passed to smf_chmod()
    in Sources/Subs-Attachments.php on line 164
  13. Path: Read from $_POST, and $path is assigned in Sources/ManageAttachments.php on line 1923
  1. Read from $_POST, and $path is assigned
    in Sources/ManageAttachments.php on line 1923
  2. $path is passed to automanage_attachments_create_directory()
    in Sources/ManageAttachments.php on line 1956
  3. Data is passed through preg_split()
    in vendor/Sources/Subs-Attachments.php on line 260
  4. $tree is assigned
    in Sources/Subs-Attachments.php on line 138
  5. $tree is passed through array_shift(), and $directory is assigned
    in Sources/Subs-Attachments.php on line 146
  6. $directory is passed to smf_chmod()
    in Sources/Subs-Attachments.php on line 164
  14. Path: Read from $_POST, and $_POST['new_base_dir'] is passed to automanage_attachments_create_directory() in Sources/ManageAttachments.php on line 2215
  1. Read from $_POST, and $_POST['new_base_dir'] is passed to automanage_attachments_create_directory()
    in Sources/ManageAttachments.php on line 2215
  2. Data is passed through preg_split()
    in vendor/Sources/Subs-Attachments.php on line 260
  3. $tree is assigned
    in Sources/Subs-Attachments.php on line 138
  4. $tree is passed through array_shift(), and $directory is assigned
    in Sources/Subs-Attachments.php on line 146
  5. $directory is passed to smf_chmod()
    in Sources/Subs-Attachments.php on line 164
  15. Path: Read from $_GET, and $url is assigned in Sources/PackageGet.php on line 230
  1. Read from $_GET, and $url is assigned
    in Sources/PackageGet.php on line 230
  2. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  3. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  4. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  5. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  6. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  7. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  8. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  9. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  10. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  11. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  12. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  16. Path: Read from $_GET, and $url is assigned in Sources/PackageGet.php on line 239
  1. Read from $_GET, and $url is assigned
    in Sources/PackageGet.php on line 239
  2. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  3. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  4. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  5. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  6. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  7. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  8. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  9. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  10. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  11. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  12. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  17. Path: Read from $_GET, and $current_url is assigned in Sources/PackageGet.php on line 358
  1. Read from $_GET, and $current_url is assigned
    in Sources/PackageGet.php on line 358
  2. $package is assigned
    in Sources/PackageGet.php on line 366
  3. $package is assigned
    in Sources/PackageGet.php on line 376
  4. $package is assigned
    in Sources/PackageGet.php on line 377
  5. $package is assigned
    in Sources/PackageGet.php on line 466
  6. $package is assigned
    in Sources/PackageGet.php on line 467
  7. $package is assigned
    in Sources/PackageGet.php on line 468
  8. $package is assigned
    in Sources/PackageGet.php on line 469
  9. $package is assigned
    in Sources/PackageGet.php on line 470
  10. $package is assigned
    in Sources/PackageGet.php on line 473
  11. $context is assigned
    in Sources/PackageGet.php on line 476
  12. $packageSection is assigned
    in Sources/PackageGet.php on line 488
  13. $package is assigned
    in Sources/PackageGet.php on line 490
  14. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  15. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  16. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  17. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  18. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  19. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  20. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  21. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  22. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  23. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  24. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  18. Path: Read from $_GET, and $current_url is assigned in Sources/PackageGet.php on line 360
  1. Read from $_GET, and $current_url is assigned
    in Sources/PackageGet.php on line 360
  2. $package is assigned
    in Sources/PackageGet.php on line 366
  3. $package is assigned
    in Sources/PackageGet.php on line 376
  4. $package is assigned
    in Sources/PackageGet.php on line 377
  5. $package is assigned
    in Sources/PackageGet.php on line 466
  6. $package is assigned
    in Sources/PackageGet.php on line 467
  7. $package is assigned
    in Sources/PackageGet.php on line 468
  8. $package is assigned
    in Sources/PackageGet.php on line 469
  9. $package is assigned
    in Sources/PackageGet.php on line 470
  10. $package is assigned
    in Sources/PackageGet.php on line 473
  11. $context is assigned
    in Sources/PackageGet.php on line 476
  12. $packageSection is assigned
    in Sources/PackageGet.php on line 488
  13. $package is assigned
    in Sources/PackageGet.php on line 490
  14. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  15. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  16. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  17. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  18. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  19. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  20. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  21. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  22. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  23. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  24. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  19. Path: Read from $_GET, and $current_url is assigned in Sources/PackageGet.php on line 383
  1. Read from $_GET, and $current_url is assigned
    in Sources/PackageGet.php on line 383
  2. $package is assigned
    in Sources/PackageGet.php on line 421
  3. $package is assigned
    in Sources/PackageGet.php on line 422
  4. $package is assigned
    in Sources/PackageGet.php on line 461
  5. $package is assigned
    in Sources/PackageGet.php on line 462
  6. $package is assigned
    in Sources/PackageGet.php on line 466
  7. $package is assigned
    in Sources/PackageGet.php on line 467
  8. $package is assigned
    in Sources/PackageGet.php on line 468
  9. $package is assigned
    in Sources/PackageGet.php on line 469
  10. $package is assigned
    in Sources/PackageGet.php on line 470
  11. $package is assigned
    in Sources/PackageGet.php on line 473
  12. $context is assigned
    in Sources/PackageGet.php on line 476
  13. $packageSection is assigned
    in Sources/PackageGet.php on line 488
  14. $package is assigned
    in Sources/PackageGet.php on line 490
  15. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  16. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  17. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  18. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  19. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  20. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  21. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  22. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  23. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  24. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  25. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  20. Path: Read from $_GET, and $current_url is assigned in Sources/PackageGet.php on line 385
  1. Read from $_GET, and $current_url is assigned
    in Sources/PackageGet.php on line 385
  2. $package is assigned
    in Sources/PackageGet.php on line 421
  3. $package is assigned
    in Sources/PackageGet.php on line 422
  4. $package is assigned
    in Sources/PackageGet.php on line 461
  5. $package is assigned
    in Sources/PackageGet.php on line 462
  6. $package is assigned
    in Sources/PackageGet.php on line 466
  7. $package is assigned
    in Sources/PackageGet.php on line 467
  8. $package is assigned
    in Sources/PackageGet.php on line 468
  9. $package is assigned
    in Sources/PackageGet.php on line 469
  10. $package is assigned
    in Sources/PackageGet.php on line 470
  11. $package is assigned
    in Sources/PackageGet.php on line 473
  12. $context is assigned
    in Sources/PackageGet.php on line 476
  13. $packageSection is assigned
    in Sources/PackageGet.php on line 488
  14. $package is assigned
    in Sources/PackageGet.php on line 490
  15. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  16. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  17. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  18. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  19. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  20. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  21. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  22. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  23. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  24. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  25. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  21. Path: Read from $_REQUEST, and $_REQUEST['package'] is passed through preg_replace(), and $context is assigned in Sources/Packages.php on line 104
  1. Read from $_REQUEST, and $_REQUEST['package'] is passed through preg_replace(), and $context is assigned
    in Sources/Packages.php on line 104
  2. $context is assigned
    in Sources/Packages.php on line 107
  3. $context is assigned
    in Sources/Packages.php on line 132
  4. $context is assigned
    in Sources/Packages.php on line 135
  5. $context is assigned
    in Sources/Packages.php on line 141
  6. $context['filename'] is passed to getPackageInfo()
    in Sources/Packages.php on line 193
  7. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  8. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  9. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  10. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  11. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  12. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  13. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  14. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  15. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  16. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  22. Path: Read from $_REQUEST, and $context is assigned in Sources/Packages.php on line 780
  1. Read from $_REQUEST, and $context is assigned
    in Sources/Packages.php on line 780
  2. $context is assigned
    in Sources/Packages.php on line 783
  3. $context is assigned
    in Sources/Packages.php on line 789
  4. $context is assigned
    in Sources/Packages.php on line 792
  5. $context is assigned
    in Sources/Packages.php on line 798
  6. $context is assigned
    in Sources/Packages.php on line 866
  7. $context['filename'] is passed to getPackageInfo()
    in Sources/Packages.php on line 886
  8. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  9. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  10. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  11. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  12. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  13. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  14. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  15. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  16. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  17. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
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...
5564
	}
5565
5566
	return $isWritable;
5567
}
5568
5569
/**
5570
 * Wrapper function for json_decode() with error handling.
5571
5572
 * @param string $json The string to decode.
5573
 * @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.
5574
 * @param bool $logIt To specify if the error will be logged if theres any.
5575
 * @return array Either an empty array or the decoded data as an array.
5576
 */
5577
function smf_json_decode($json, $returnAsArray = false, $logIt = true)
5578
{
5579
	global $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
5580
5581
	// Come on...
5582
	if (empty($json) || !is_string($json))
5583
		return array();
5584
5585
	$returnArray = @json_decode($json, $returnAsArray);
5586
5587
	// PHP 5.3 so no json_last_error_msg()
5588
	switch(json_last_error())
5589
	{
5590
		case JSON_ERROR_NONE:
5591
			$jsonError = false;
5592
			break;
5593
		case JSON_ERROR_DEPTH:
5594
			$jsonError =  'JSON_ERROR_DEPTH';
5595
			break;
5596
		case JSON_ERROR_STATE_MISMATCH:
5597
			$jsonError = 'JSON_ERROR_STATE_MISMATCH';
5598
			break;
5599
		case JSON_ERROR_CTRL_CHAR:
5600
			$jsonError = 'JSON_ERROR_CTRL_CHAR';
5601
			break;
5602
		case JSON_ERROR_SYNTAX:
5603
			$jsonError = 'JSON_ERROR_SYNTAX';
5604
			break;
5605
		case JSON_ERROR_UTF8:
5606
			$jsonError = 'JSON_ERROR_UTF8';
5607
			break;
5608
		default:
5609
			$jsonError = 'unknown';
5610
			break;
5611
	}
5612
5613
	// Something went wrong!
5614
	if (!empty($jsonError) && $logIt)
5615
	{
5616
		// Being a wrapper means we lost our smf_error_handler() privileges :(
5617
		$jsonDebug = debug_backtrace();
5618
		$jsonDebug = $jsonDebug[0];
5619
		loadLanguage('Errors');
5620
5621
		if (!empty($jsonDebug))
5622
			log_error($txt['json_'. $jsonError], 'critical', $jsonDebug['file'], $jsonDebug['line']);
5623
5624
		else
5625
			log_error($txt['json_'. $jsonError], 'critical');
5626
5627
		// Everyone expects an array.
5628
		return array();
5629
	}
5630
5631
	return $returnArray;
5632
}
5633
5634
/**
5635
 * Check the given String if he is a valid IPv4 or IPv6
5636
 * return true or false
5637
 *
5638
 * @param string $IPString
5639
 *
5640
 * @return bool
5641
 */
5642
function isValidIP($IPString)
5643
{
5644
	return filter_var($IPString, FILTER_VALIDATE_IP) !== false;
5645
}
5646
5647
/**
5648
 * Outputs a response.
5649
 * It assumes the data is already a string.
5650
 * @param string $data The data to print
5651
 * @param string $type The content type. Defaults to Json.
5652
 * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be false|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
5653
 */
5654
function smf_serverResponse($data = '', $type = 'Content-Type: application/json')
5655
{
5656
	global $db_show_debug, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
5657
5658
	// Defensive programming anyone?
5659
	if (empty($data))
5660
		return false;
5661
5662
	// Don't need extra stuff...
5663
	$db_show_debug = false;
5664
5665
	// Kill anything else.
5666
	ob_end_clean();
5667
5668
	if (!empty($modSettings['CompressedOutput']))
5669
		@ob_start('ob_gzhandler');
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...
5670
5671
	else
5672
		ob_start();
5673
5674
	// Set the header.
5675
	header($type);
5676
5677
	// Echo!
5678
	echo $data;
5679
5680
	// Done.
5681
	obExit(false);
5682
}
5683
5684
/**
5685
 * Creates an optimized regex to match all known top level domains.
5686
 *
5687
 * The optimized regex is stored in $modSettings['tld_regex'].
5688
 *
5689
 * To update the stored version of the regex to use the latest list of valid TLDs from iana.org, set
5690
 * the $update parameter to true. Updating can take some time, based on network connectivity, so it
5691
 * should normally only be done by calling this function from a background or scheduled task.
5692
 *
5693
 * If $update is not true, but the regex is missing or invalid, the regex will be regenerated from a
5694
 * hard-coded list of TLDs. This regenerated regex will be overwritten on the next scheduled update.
5695
 *
5696
 * @param bool $update If true, fetch and process the latest offical list of TLDs from iana.org.
5697
 */
5698
function set_tld_regex($update = false)
5699
{
5700
	global $sourcedir, $smcFunc, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
5701
	static $done = false;
5702
5703
	// If we don't need to do anything, don't
5704
	if (!$update && $done)
5705
		return;
5706
5707
	// Should we get a new copy of the official list of TLDs?
5708
	if ($update)
5709
	{
5710
		require_once($sourcedir . '/Subs-Package.php');
5711
		$tlds = fetch_web_data('https://data.iana.org/TLD/tlds-alpha-by-domain.txt');
5712
5713
		// If the Internet Assigned Numbers Authority can't be reached, the Internet is gone. We're probably running on a server hidden in a bunker deep underground to protect it from marauding bandits roaming on the surface. We don't want to waste precious electricity on pointlessly repeating background tasks, so we'll wait until the next regularly scheduled update to see if civilization has been restored.
5714
		if ($tlds === false)
5715
			$postapocalypticNightmare = true;
5716
	}
5717
	// If we aren't updating and the regex is valid, we're done
5718
	elseif (!empty($modSettings['tld_regex']) && @preg_match('~' . $modSettings['tld_regex'] . '~', null) !== false)
5719
	{
5720
		$done = true;
5721
		return;
5722
	}
5723
5724
	// If we successfully got an update, process the list into an array
5725
	if (!empty($tlds))
5726
	{
5727
		// Clean $tlds and convert it to an array
5728
		$tlds = array_filter(explode("\n", strtolower($tlds)), function($line) {
5729
			$line = trim($line);
5730
			if (empty($line) || strpos($line, '#') !== false || strpos($line, ' ') !== false)
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return !(empty($line) ||...$line, ' ') !== false);.
Loading history...
5731
				return false;
5732
			else
5733
				return true;
5734
		});
5735
5736
		// Convert Punycode to Unicode
5737
		$tlds = array_map(function ($input) {
5738
			$prefix = 'xn--';
5739
			$safe_char = 0xFFFC;
5740
			$base = 36;
5741
			$tmin = 1;
5742
			$tmax = 26;
5743
			$skew = 38;
5744
			$damp = 700;
5745
			$output_parts = array();
5746
5747
			$input = str_replace(strtoupper($prefix), $prefix, $input);
5748
5749
			$enco_parts = (array) explode('.', $input);
5750
5751
			foreach ($enco_parts as $encoded)
5752
			{
5753
				if (strpos($encoded,$prefix) !== 0 || strlen(trim(str_replace($prefix,'',$encoded))) == 0)
5754
				{
5755
					$output_parts[] = $encoded;
5756
					continue;
5757
				}
5758
5759
				$is_first = true;
5760
				$bias = 72;
5761
				$idx = 0;
5762
				$char = 0x80;
5763
				$decoded = array();
5764
				$output='';
5765
				$delim_pos = strrpos($encoded, '-');
5766
5767
				if ($delim_pos > strlen($prefix))
5768
				{
5769
					for ($k = strlen($prefix); $k < $delim_pos; ++$k)
5770
					{
5771
						$decoded[] = ord($encoded{$k});
5772
					}
5773
				}
5774
5775
				$deco_len = count($decoded);
5776
				$enco_len = strlen($encoded);
5777
5778
				for ($enco_idx = $delim_pos ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len)
5779
				{
5780
					for ($old_idx = $idx, $w = 1, $k = $base; 1 ; $k += $base)
5781
					{
5782
						$cp = ord($encoded{$enco_idx++});
5783
						$digit = ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $base));
5784
						$idx += $digit * $w;
5785
						$t = ($k <= $bias) ? $tmin : (($k >= $bias + $tmax) ? $tmax : ($k - $bias));
5786
5787
						if ($digit < $t)
5788
							break;
5789
5790
						$w = (int) ($w * ($base - $t));
5791
					}
5792
5793
					$delta = $idx - $old_idx;
5794
					$delta = intval($is_first ? ($delta / $damp) : ($delta / 2));
5795
					$delta += intval($delta / ($deco_len + 1));
5796
5797
					for ($k = 0; $delta > (($base - $tmin) * $tmax) / 2; $k += $base)
5798
						$delta = intval($delta / ($base - $tmin));
5799
5800
					$bias = intval($k + ($base - $tmin + 1) * $delta / ($delta + $skew));
5801
					$is_first = false;
5802
					$char += (int) ($idx / ($deco_len + 1));
5803
					$idx %= ($deco_len + 1);
5804
5805
					if ($deco_len > 0)
5806
					{
5807
						for ($i = $deco_len; $i > $idx; $i--)
5808
							$decoded[$i] = $decoded[($i - 1)];
5809
					}
5810
					$decoded[$idx++] = $char;
5811
				}
5812
5813
				foreach ($decoded as $k => $v)
5814
				{
5815
					// 7bit are transferred literally
5816
					if ($v < 128)
5817
						$output .= chr($v);
5818
5819
					// 2 bytes
5820
					elseif ($v < (1 << 11))
5821
						$output .= chr(192+($v >> 6)) . chr(128+($v & 63));
5822
5823
					// 3 bytes
5824
					elseif ($v < (1 << 16))
5825
						$output .= chr(224+($v >> 12)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5826
5827
					// 4 bytes
5828
					elseif ($v < (1 << 21))
5829
						$output .= chr(240+($v >> 18)) . chr(128+(($v >> 12) & 63)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5830
5831
					//  'Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k
5832
					else
5833
						$output .= $safe_char;
5834
				}
5835
5836
				$output_parts[] = $output;
5837
			}
5838
5839
			return implode('.', $output_parts);
5840
		}, $tlds);
5841
	}
5842
	// Otherwise, use the 2012 list of gTLDs and ccTLDs for now and schedule a background update
5843
	else
5844
	{
5845
		$tlds = array('com', 'net', 'org', 'edu', 'gov', 'mil', 'aero', 'asia', 'biz', 'cat',
5846
			'coop', 'info', 'int', 'jobs', 'mobi', 'museum', 'name', 'post', 'pro', 'tel',
5847
			'travel', 'xxx', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq',
5848
			'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
5849
			'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc',
5850
			'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cs', 'cu', 'cv',
5851
			'cx', 'cy', 'cz', 'dd', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh',
5852
			'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge',
5853
			'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw',
5854
			'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq',
5855
			'ir', 'is', 'it', 'ja', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn',
5856
			'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu',
5857
			'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp',
5858
			'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf',
5859
			'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph',
5860
			'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru',
5861
			'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn',
5862
			'so', 'sr', 'ss', 'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th',
5863
			'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug',
5864
			'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye',
5865
			'yt', 'yu', 'za', 'zm', 'zw');
5866
5867
		// Schedule a background update, unless civilization has collapsed and/or we are having connectivity issues.
5868
		$schedule_update = empty($postapocalypticNightmare);
5869
	}
5870
5871
	// Get an optimized regex to match all the TLDs
5872
	$tld_regex = build_regex($tlds);
5873
5874
	// Remember the new regex in $modSettings
5875
	updateSettings(array('tld_regex' => $tld_regex));
5876
5877
	// Schedule a background update if we need one
5878 View Code Duplication
	if (!empty($schedule_update))
5879
	{
5880
		$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
5881
			array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
5882
			array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array()
5883
		);
5884
	}
5885
5886
	// Redundant repetition is redundant
5887
	$done = true;
5888
}
5889
5890
/**
5891
 * Creates optimized regular expressions from an array of strings.
5892
 *
5893
 * An optimized regex built using this function will be much faster than a simple regex built using
5894
 * `implode('|', $strings)` --- anywhere from several times to several orders of magnitude faster.
5895
 *
5896
 * However, the time required to build the optimized regex is approximately equal to the time it
5897
 * takes to execute the simple regex. Therefore, it is only worth calling this function if the
5898
 * resulting regex will be used more than once.
5899
 *
5900
 * Because PHP places an upper limit on the allowed length of a regex, very large arrays of $strings
5901
 * may not fit in a single regex. Normally, the excess strings will simply be dropped. However, if
5902
 * the $returnArray parameter is set to true, this function will build as many regexes as necessary
5903
 * to accomodate everything in $strings and return them in an array. You will need to iterate
5904
 * through all elements of the returned array in order to test all possible matches.
5905
 *
5906
 * @param array $strings An array of strings to make a regex for.
5907
 * @param string $delim An optional delimiter character to pass to preg_quote().
0 ignored issues
show
Documentation introduced by
Should the type for parameter $delim not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
5908
 * @param bool $returnArray If true, returns an array of regexes.
5909
 * @return string|array One or more regular expressions to match any of the input strings.
5910
 */
5911
function build_regex($strings, $delim = null, $returnArray = false)
5912
{
5913
	global $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
5914
5915
	// The mb_* functions are faster than the $smcFunc ones, but may not be available
5916
	if (function_exists('mb_internal_encoding') && function_exists('mb_detect_encoding') && function_exists('mb_strlen') && function_exists('mb_substr'))
5917
	{
5918
		if (($string_encoding = mb_detect_encoding(implode(' ', $strings))) !== false)
5919
		{
5920
			$current_encoding = mb_internal_encoding();
5921
			mb_internal_encoding($string_encoding);
5922
		}
5923
5924
		$strlen = 'mb_strlen';
5925
		$substr = 'mb_substr';
5926
	}
5927
	else
5928
	{
5929
		$strlen = $smcFunc['strlen'];
5930
		$substr = $smcFunc['substr'];
5931
	}
5932
5933
	// This recursive function creates the index array from the strings
5934
	$add_string_to_index = function ($string, $index) use (&$strlen, &$substr, &$add_string_to_index)
5935
	{
5936
		static $depth = 0;
5937
		$depth++;
5938
5939
		$first = $substr($string, 0, 1);
5940
5941
		if (empty($index[$first]))
5942
			$index[$first] = array();
5943
5944
		if ($strlen($string) > 1)
5945
		{
5946
			// Sanity check on recursion
5947
			if ($depth > 99)
5948
				$index[$first][$substr($string, 1)] = '';
5949
5950
			else
5951
				$index[$first] = $add_string_to_index($substr($string, 1), $index[$first]);
5952
		}
5953
		else
5954
			$index[$first][''] = '';
5955
5956
		$depth--;
5957
		return $index;
5958
	};
5959
5960
	// This recursive function turns the index array into a regular expression
5961
	$index_to_regex = function (&$index, $delim) use (&$strlen, &$index_to_regex)
5962
	{
5963
		static $depth = 0;
5964
		$depth++;
5965
5966
		// Absolute max length for a regex is 32768, but we might need wiggle room
5967
		$max_length = 30000;
5968
5969
		$regex = array();
5970
		$length = 0;
5971
5972
		foreach ($index as $key => $value)
5973
		{
5974
			$key_regex = preg_quote($key, $delim);
5975
			$new_key = $key;
5976
5977
			if (empty($value))
5978
				$sub_regex = '';
5979
			else
5980
			{
5981
				$sub_regex = $index_to_regex($value, $delim);
5982
5983
				if (count(array_keys($value)) == 1)
5984
				{
5985
					$new_key_array = explode('(?'.'>', $sub_regex);
5986
					$new_key .= $new_key_array[0];
5987
				}
5988
				else
5989
					$sub_regex = '(?'.'>' . $sub_regex . ')';
5990
			}
5991
5992
			if ($depth > 1)
5993
				$regex[$new_key] = $key_regex . $sub_regex;
5994
			else
5995
			{
5996
				if (($length += strlen($key_regex) + 1) < $max_length || empty($regex))
5997
				{
5998
					$regex[$new_key] = $key_regex . $sub_regex;
5999
					unset($index[$key]);
6000
				}
6001
				else
6002
					break;
6003
			}
6004
		}
6005
6006
		// Sort by key length and then alphabetically
6007
		uksort($regex, function($k1, $k2) use (&$strlen) {
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $k1. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $k2. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
6008
			$l1 = $strlen($k1);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $l1. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
6009
			$l2 = $strlen($k2);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $l2. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
6010
6011
			if ($l1 == $l2)
6012
				return strcmp($k1, $k2) > 0 ? 1 : -1;
6013
			else
6014
				return $l1 > $l2 ? -1 : 1;
6015
		});
6016
6017
		$depth--;
6018
		return implode('|', $regex);
6019
	};
6020
6021
	// Now that the functions are defined, let's do this thing
6022
	$index = array();
6023
	$regex = '';
0 ignored issues
show
Unused Code introduced by
$regex 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...
6024
6025
	foreach ($strings as $string)
6026
		$index = $add_string_to_index($string, $index);
6027
6028
	if ($returnArray === true)
6029
	{
6030
		$regex = array();
6031
		while (!empty($index))
6032
			$regex[] = '(?'.'>' . $index_to_regex($index, $delim) . ')';
6033
	}
6034
	else
6035
		$regex = '(?'.'>' . $index_to_regex($index, $delim) . ')';
6036
6037
	// Restore PHP's internal character encoding to whatever it was originally
6038
	if (!empty($current_encoding))
6039
		mb_internal_encoding($current_encoding);
6040
6041
	return $regex;
6042
}
6043
6044
/**
6045
 * Check if the passed url has an SSL certificate.
6046
 *
6047
 * Returns true if a cert was found & false if not.
6048
 * @param string $url to check, in $boardurl format (no trailing slash).
6049
 */
6050
 function ssl_cert_found($url) {
6051
6052
	// First, strip the subfolder from the passed url, if any
6053
	$parsedurl = parse_url($url);
6054
	$url = 'ssl://' . $parsedurl['host'] . ':443';
6055
6056
	// Next, check the ssl stream context for certificate info
6057
	$result = false;
6058
	$context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, "verify_peer" => true, "allow_self_signed" => true)));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal ssl does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal capture_peer_cert does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal verify_peer does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal allow_self_signed does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
6059
	$stream = @stream_socket_client($url, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
0 ignored issues
show
Security Header Injection introduced by
$url can contain request data and is used in request header context(s) leading to a potential security vulnerability.

2 paths for user data to reach this point

  1. Path: Fetching key HTTP_HOST from $_SERVER, and $host is assigned in other/install.php on line 938
  1. Fetching key HTTP_HOST from $_SERVER, and $host is assigned
    in other/install.php on line 938
  2. $incontext is assigned
    in other/install.php on line 948
  3. $incontext is assigned
    in other/install.php on line 951
  4. $incontext is assigned
    in other/install.php on line 952
  5. $incontext is assigned
    in other/install.php on line 953
  6. $incontext is assigned
    in other/install.php on line 955
  7. $incontext is assigned
    in other/install.php on line 958
  8. $incontext is assigned
    in other/install.php on line 959
  9. $incontext['detected_url'] is passed to ssl_cert_found()
    in other/install.php on line 969
  10. $url is passed through parse_url(), and $parsedurl is assigned
    in Sources/Subs.php on line 6053
  11. $url is assigned
    in Sources/Subs.php on line 6054
  2. Path: Fetching key PHP_SELF from $_SERVER, and $_SERVER['PHP_SELF'] is passed through substr(), and $incontext is assigned in other/install.php on line 948
  1. Fetching key PHP_SELF from $_SERVER, and $_SERVER['PHP_SELF'] is passed through substr(), and $incontext is assigned
    in other/install.php on line 948
  2. $incontext is assigned
    in other/install.php on line 951
  3. $incontext is assigned
    in other/install.php on line 952
  4. $incontext is assigned
    in other/install.php on line 953
  5. $incontext is assigned
    in other/install.php on line 955
  6. $incontext is assigned
    in other/install.php on line 958
  7. $incontext is assigned
    in other/install.php on line 959
  8. $incontext['detected_url'] is passed to ssl_cert_found()
    in other/install.php on line 969
  9. $url is passed through parse_url(), and $parsedurl is assigned
    in Sources/Subs.php on line 6053
  10. $url is assigned
    in Sources/Subs.php on line 6054

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
6060
	if ($stream !== false)
6061
	{
6062
		$params = stream_context_get_params($stream);
6063
		$result = isset($params["options"]["ssl"]["peer_certificate"]) ? true : false;
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal options does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal ssl does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style Comprehensibility introduced by
The string literal peer_certificate does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
6064
	}
6065
    return $result;
6066
}
6067
6068
/**
6069
 * Check if the passed url has a redirect to https:// by querying headers.
6070
 *
6071
 * Returns true if a redirect was found & false if not.
6072
 * Note that when force_ssl = 2, SMF issues its own redirect...  So if this
6073
 * returns true, it may be caused by SMF, not necessarily an .htaccess redirect.
6074
 * @param string $url to check, in $boardurl format (no trailing slash).
6075
 */
6076
function https_redirect_active($url) {
6077
6078
	// Ask for the headers for the passed url, but via http...
6079
	// Need to add the trailing slash, or it puts it there & thinks there's a redirect when there isn't...
6080
	$url = str_ireplace('https://', 'http://', $url) . '/';
6081
	$headers = @get_headers($url);
0 ignored issues
show
Security Header Injection introduced by
$url can contain request data and is used in request header context(s) leading to a potential security vulnerability.

2 paths for user data to reach this point

  1. Path: Fetching key HTTP_HOST from $_SERVER, and $host is assigned in other/install.php on line 938
  1. Fetching key HTTP_HOST from $_SERVER, and $host is assigned
    in other/install.php on line 938
  2. $incontext is assigned
    in other/install.php on line 948
  3. $incontext is assigned
    in other/install.php on line 951
  4. $incontext is assigned
    in other/install.php on line 952
  5. $incontext is assigned
    in other/install.php on line 953
  6. $incontext is assigned
    in other/install.php on line 955
  7. $incontext is assigned
    in other/install.php on line 958
  8. $incontext is assigned
    in other/install.php on line 959
  9. $incontext['detected_url'] is passed to https_redirect_active()
    in other/install.php on line 963
  10. $url is passed through str_ireplace(), and $url is assigned
    in Sources/Subs.php on line 6080
  2. Path: Fetching key PHP_SELF from $_SERVER, and $_SERVER['PHP_SELF'] is passed through substr(), and $incontext is assigned in other/install.php on line 948
  1. Fetching key PHP_SELF from $_SERVER, and $_SERVER['PHP_SELF'] is passed through substr(), and $incontext is assigned
    in other/install.php on line 948
  2. $incontext is assigned
    in other/install.php on line 951
  3. $incontext is assigned
    in other/install.php on line 952
  4. $incontext is assigned
    in other/install.php on line 953
  5. $incontext is assigned
    in other/install.php on line 955
  6. $incontext is assigned
    in other/install.php on line 958
  7. $incontext is assigned
    in other/install.php on line 959
  8. $incontext['detected_url'] is passed to https_redirect_active()
    in other/install.php on line 963
  9. $url is passed through str_ireplace(), and $url is assigned
    in Sources/Subs.php on line 6080

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
6082
	if ($headers === false)
6083
		return false;
6084
6085
	// Now to see if it came back https...
6086
	// First check for a redirect status code in first row (301, 302, 307)
6087
	if (strstr($headers[0], '301') === false && strstr($headers[0], '302') === false && strstr($headers[0], '307') === false)
6088
		return false;
6089
6090
	// Search for the location entry to confirm https
6091
	$result = false;
6092
	foreach ($headers as $header) {
6093
		if (stristr($header, 'Location: https://') !== false) {
6094
			$result = true;
6095
			break;
6096
		}
6097
	}
6098
	return $result;
6099
}
6100
6101
/**
6102
 * Build query_wanna_see_board and query_see_board for a userid
6103
 *
6104
 * Returns array with keys query_wanna_see_board and query_see_board
6105
 * @param int $userid of the user
6106
 */
6107
function build_query_board($userid)
6108
{
6109
	global $user_info, $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
6110
6111
	$query_part = array();
6112
	$groups = array();
0 ignored issues
show
Unused Code introduced by
$groups 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...
6113
	$is_admin = false;
0 ignored issues
show
Unused Code introduced by
$is_admin 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...
6114
	$deny_boards_access = !empty($modSettings['deny_boards_access']) ? $modSettings['deny_boards_access'] : null;
6115
	$mod_cache;
0 ignored issues
show
Bug introduced by
The variable $mod_cache seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
6116
	$ignoreboards;
0 ignored issues
show
Bug introduced by
The variable $ignoreboards seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
6117
6118
	if ($user_info['id'] == $userid)
6119
	{
6120
		$groups = $user_info['groups'];
6121
		$is_admin = $user_info['is_admin'];
6122
		$mod_cache = !empty($user_info['mod_cache']) ? $user_info['mod_cache'] : null;
6123
		$ignoreboards = !empty($user_info['ignoreboards']) ? $user_info['ignoreboards'] : null;
6124
	}
6125
	else
6126
	{
6127
		$request = $smcFunc['db_query']('', '
6128
				SELECT mem.ignore_boards, mem.id_group, mem.additional_groups, mem.id_post_group
6129
				FROM {db_prefix}members AS mem
6130
				WHERE mem.id_member = {int:id_member}
6131
				LIMIT 1',
6132
				array(
6133
					'id_member' => $userid,
6134
				)
6135
			);
6136
6137
		$row = $smcFunc['db_fetch_assoc']($request);
6138
6139 View Code Duplication
		if (empty($row['additional_groups']))
6140
			$groups = array($row['id_group'], $row['id_post_group']);
6141
		else
6142
			$groups = array_merge(
6143
					array($row['id_group'], $row['id_post_group']),
6144
					explode(',', $row['additional_groups'])
6145
			);
6146
6147
		// Because history has proven that it is possible for groups to go bad - clean up in case.
6148
		foreach ($groups as $k => $v)
6149
			$groups[$k] = (int) $v;
6150
6151
		$is_admin = in_array(1, $groups);
6152
6153
		$ignoreboards = !empty($row['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $row['ignore_boards']) : array();
6154
6155
		// What boards are they the moderator of?
6156
		$boards_mod = array();
6157
6158
		$request = $smcFunc['db_query']('', '
6159
			SELECT id_board
6160
			FROM {db_prefix}moderators
6161
			WHERE id_member = {int:current_member}',
6162
			array(
6163
				'current_member' => $userid,
6164
			)
6165
		);
6166
		while ($row = $smcFunc['db_fetch_assoc']($request))
6167
			$boards_mod[] = $row['id_board'];
6168
		$smcFunc['db_free_result']($request);
6169
6170
		// Can any of the groups they're in moderate any of the boards?
6171
		$request = $smcFunc['db_query']('', '
6172
			SELECT id_board
6173
			FROM {db_prefix}moderator_groups
6174
			WHERE id_group IN({array_int:groups})',
6175
			array(
6176
				'groups' => $groups,
6177
			)
6178
		);
6179
		while ($row = $smcFunc['db_fetch_assoc']($request))
6180
			$boards_mod[] = $row['id_board'];
6181
		$smcFunc['db_free_result']($request);
6182
6183
		// Just in case we've got duplicates here...
6184
		$boards_mod = array_unique($boards_mod);
6185
6186
		$mod_cache['mq'] = empty($boards_mod) ? '0=1' : 'b.id_board IN (' . implode(',', $boards_mod) . ')';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$mod_cache was never initialized. Although not strictly required by PHP, it is generally a good practice to add $mod_cache = 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...
6187
	}
6188
6189
	// Just build this here, it makes it easier to change/use - administrators can see all boards.
6190
	if ($is_admin)
6191
		$query_part['query_see_board'] = '1=1';
6192
	// Otherwise just the groups in $user_info['groups'].
6193
	else
6194
		$query_part['query_see_board'] = '((FIND_IN_SET(' . implode(', b.member_groups) != 0 OR FIND_IN_SET(', $groups) . ', b.member_groups) != 0)' . (!empty($deny_boards_access) ? ' AND (FIND_IN_SET(' . implode(', b.deny_member_groups) = 0 AND FIND_IN_SET(', $groups) . ', b.deny_member_groups) = 0)' : '') . (isset($mod_cache) ? ' OR ' . $mod_cache['mq'] : '') . ')';
6195
6196
	// Build the list of boards they WANT to see.
6197
	// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also
6198
6199
	// If they aren't ignoring any boards then they want to see all the boards they can see
6200
	if (empty($ignoreboards))
6201
		$query_part['query_wanna_see_board'] = $query_part['query_see_board'];
6202
	// Ok I guess they don't want to see all the boards
6203
	else
6204
		$query_part['query_wanna_see_board'] = '(' . $query_part['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $ignoreboards) . '))';
6205
6206
	return $query_part;
6207
}
6208
6209
/**
6210
 * Check if the connection is using https.
6211
 *
6212
 * @return boolean true if connection used https
6213
 */
6214
function httpsOn()
6215
{
6216
	$secure = false;
6217
6218 View Code Duplication
	if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
6219
		$secure = true;
6220
	elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on')
6221
		$secure = true;
6222
6223
	return $secure;
6224
}
6225
6226
?>