Completed
Push — release-2.1 ( 62bce6...b11117 )
by Colin
08:13
created

Subs.php ➔ setMemoryLimit()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
nc 6
nop 2
dl 0
loc 22
rs 8.9197
c 0
b 0
f 0
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.

66 paths for user data to reach this point

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/Load.php on line 808
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/Load.php on line 808
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  9. Path: Read from $_GET, and $_GET is passed through key(), and $k is assigned in Sources/Load.php on line 1882
  1. Read from $_GET, and $_GET is passed through key(), and $k is assigned
    in Sources/Load.php on line 1882
  2. 'wwwRedirect;' . $k . '=' . $v is passed to redirectexit()
    in Sources/Load.php on line 1886
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  10. Path: Read from $_GET, and $_GET is passed through current(), and $v is assigned in Sources/Load.php on line 1883
  1. Read from $_GET, and $_GET is passed through current(), and $v is assigned
    in Sources/Load.php on line 1883
  2. 'wwwRedirect;' . $k . '=' . $v is passed to redirectexit()
    in Sources/Load.php on line 1886
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2916
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  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 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
  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 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  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 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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 View Code Duplication
			if (!isset($minSeed) && isset($js_file['options']['seed']))
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
		$minSuccessful = array_keys($result) === array('smf_minified');
3583
3584
		foreach ($result as $minFile)
0 ignored issues
show
Bug introduced by
The expression $result of type boolean|array 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...
3585
			echo '
3586
	<script src="', $minFile['fileUrl'], $minSuccessful && isset($minSeed) ? $minSeed : '', '"', !empty($minFile['options']['async']) ? ' async="async"' : '', '></script>';
3587
3588
	}
3589
3590
	// Inline JavaScript - Actually useful some times!
3591
	if (!empty($context['javascript_inline']))
3592
	{
3593 View Code Duplication
		if (!empty($context['javascript_inline']['defer']) && $do_deferred)
3594
		{
3595
			echo '
3596
<script>';
3597
3598
			foreach ($context['javascript_inline']['defer'] as $js_code)
3599
				echo $js_code;
3600
3601
			echo '
3602
</script>';
3603
		}
3604
3605 View Code Duplication
		if (!empty($context['javascript_inline']['standard']) && !$do_deferred)
3606
		{
3607
			echo '
3608
	<script>';
3609
3610
			foreach ($context['javascript_inline']['standard'] as $js_code)
3611
				echo $js_code;
3612
3613
			echo '
3614
	</script>';
3615
		}
3616
	}
3617
}
3618
3619
/**
3620
 * Output the CSS files
3621
 *
3622
 */
3623
function template_css()
3624
{
3625
	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...
3626
3627
	// Use this hook to minify/optimize CSS files
3628
	call_integration_hook('integrate_pre_css_output');
3629
3630
	$toMinify = array();
3631
	$normal = array();
3632
3633
	ksort($context['css_files_order']);
3634
	$context['css_files'] = array_merge(array_flip($context['css_files_order']), $context['css_files']);
3635
3636
	foreach ($context['css_files'] as $id => $file)
3637
	{
3638
		// Last minute call! allow theme authors to disable single files.
3639
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3640
			continue;
3641
3642
		// Files are minimized unless they explicitly opt out.
3643
		if (!isset($file['options']['minimize']))
3644
			$file['options']['minimize'] = true;
3645
3646
		if (!empty($file['options']['minimize']) && !empty($modSettings['minimize_files']))
3647
		{
3648
			$toMinify[] = $file;
3649
3650
			// Grab a random seed.
3651 View Code Duplication
			if (!isset($minSeed) && isset($file['options']['seed']))
3652
				$minSeed = $file['options']['seed'];
3653
		}
3654
		else
3655
			$normal[] = $file['fileUrl'];
3656
	}
3657
3658
	if (!empty($toMinify))
3659
	{
3660
		$result = custMinify($toMinify, 'css');
3661
3662
		$minSuccessful = array_keys($result) === array('smf_minified');
3663
3664
		foreach ($result as $minFile)
0 ignored issues
show
Bug introduced by
The expression $result of type boolean|array 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...
3665
			echo '
3666
	<link rel="stylesheet" href="', $minFile['fileUrl'], $minSuccessful && isset($minSeed) ? $minSeed : '', '">';
3667
	}
3668
3669
	// Print the rest after the minified files.
3670
	if (!empty($normal))
3671
		foreach ($normal as $nf)
3672
			echo '
3673
	<link rel="stylesheet" href="', $nf ,'">';
3674
3675
	if ($db_show_debug === true)
3676
	{
3677
		// Try to keep only what's useful.
3678
		$repl = array($boardurl . '/Themes/' => '', $boardurl . '/' => '');
3679
		foreach ($context['css_files'] as $file)
3680
			$context['debug']['sheets'][] = strtr($file['fileName'], $repl);
3681
	}
3682
3683
	if (!empty($context['css_header']))
3684
	{
3685
		echo '
3686
	<style>';
3687
3688
		foreach ($context['css_header'] as $css)
3689
			echo $css .'
3690
	';
3691
3692
		echo'
3693
	</style>';
3694
	}
3695
}
3696
3697
/**
3698
 * Get an array of previously defined files and adds them to our main minified file.
3699
 * Sets a one day cache to avoid re-creating a file on every request.
3700
 *
3701
 * @param array $data The files to minify.
3702
 * @param string $type either css or js.
3703
 * @param bool $do_deferred use for type js to indicate if the minified file will be deferred, IE, put at the closing </body> tag.
3704
 * @return bool|array If an array the minify process failed and the data is returned intact.
3705
 */
3706
function custMinify($data, $type, $do_deferred = false)
3707
{
3708
	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...
3709
3710
	$types = array('css', 'js');
3711
	$type = !empty($type) && in_array($type, $types) ? $type : false;
3712
	$data = !empty($data) ? $data : false;
3713
3714
	if (empty($type) || empty($data))
3715
		return false;
3716
3717
	// Different pages include different files, so we use a hash to label the different combinations
3718
	$hash = md5(implode(' ', array_keys($data)));
3719
3720
	// Did we already did this?
3721
	$toCache = cache_get_data('minimized_' . $settings['theme_id'] . '_' . $type . '_' . $hash, 86400);
3722
3723
	// Already done?
3724
	if (!empty($toCache))
3725
		return true;
3726
3727
	// No namespaces, sorry!
3728
	$classType = 'MatthiasMullie\\Minify\\'. strtoupper($type);
3729
3730
	// Temp path.
3731
	$cTempPath = $settings['theme_dir'] . '/' . ($type == 'css' ? 'css' : 'scripts') . '/';
3732
3733
	// What kind of file are we going to create?
3734
	$toCreate = $cTempPath . 'minified' . ($do_deferred ? '_deferred' : '') . '_' . $hash . '.' . $type;
3735
3736
	// File has to exist. If it doesn't, try to create it.
3737
	if ((!file_exists($toCreate) && @fopen($toCreate, 'w') === false) || !smf_chmod($toCreate))
3738
	{
3739
		loadLanguage('Errors');
3740
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3741
		cache_put_data('minimized_' . $settings['theme_id'] . '_' . $type . '_' . $hash, null);
3742
3743
		// The process failed, so roll back to print each individual file.
3744
		return $data;
3745
	}
3746
3747
	$minifier = new $classType();
3748
3749
	$async = $type === 'js';
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
		// A minified script should only be loaded asynchronously if all its components wanted to be.
3757
		if (empty($file['options']['async']))
3758
			$async = false;
3759
3760
		// The file couldn't be located so it won't be added. Log this error.
3761
		if (empty($toAdd))
3762
		{
3763
			loadLanguage('Errors');
3764
			log_error(sprintf($txt['file_minimize_fail'], $file['fileName']), 'general');
3765
			continue;
3766
		}
3767
3768
		// Add this file to the list.
3769
		$minifier->add($toAdd);
3770
	}
3771
3772
	// Create the file.
3773
	$minifier->minify($toCreate);
3774
	unset($minifier);
3775
	clearstatcache();
3776
3777
	// Minify process failed.
3778
	if (!filesize($toCreate))
3779
	{
3780
		loadLanguage('Errors');
3781
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3782
		cache_put_data('minimized_' . $settings['theme_id'] . '_' . $type . '_' . $hash, null);
3783
3784
		// The process failed so roll back to print each individual file.
3785
		return $data;
3786
	}
3787
3788
	// And create a long lived cache entry.
3789
	cache_put_data('minimized_' . $settings['theme_id'] . '_' . $type . '_' . $hash, $toCreate, 86400);
3790
3791
	return array('smf_minified' => array(
3792
		'fileUrl' => $settings['theme_url'] . '/' . ($type == 'css' ? 'css' : 'scripts') . '/' . basename($toCreate),
3793
		'filePath' => $toCreate,
3794
		'fileName' => basename($toCreate),
3795
		'options' => array('async' => $async),
3796
	));
3797
}
3798
3799
/**
3800
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
3801
 * @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...
3802
 * Something messy like that.
3803
 * @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...
3804
 * Converters included.
3805
 *
3806
 * @param string $filename The name of the file
3807
 * @param int $attachment_id The ID of the attachment
3808
 * @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...
3809
 * @param bool $new Whether this is a new attachment
3810
 * @param string $file_hash The file hash
3811
 * @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...
3812
 */
3813
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
3814
{
3815
	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...
3816
3817
	// Just make up a nice hash...
3818
	if ($new)
3819
		return sha1(md5($filename . time()) . mt_rand());
3820
3821
	// Just make sure that attachment id is only a int
3822
	$attachment_id = (int) $attachment_id;
3823
3824
	// Grab the file hash if it wasn't added.
3825
	// Left this for legacy.
3826
	if ($file_hash === '')
3827
	{
3828
		$request = $smcFunc['db_query']('', '
3829
			SELECT file_hash
3830
			FROM {db_prefix}attachments
3831
			WHERE id_attach = {int:id_attach}',
3832
			array(
3833
				'id_attach' => $attachment_id,
3834
			));
3835
3836
		if ($smcFunc['db_num_rows']($request) === 0)
3837
			return false;
3838
3839
		list ($file_hash) = $smcFunc['db_fetch_row']($request);
3840
		$smcFunc['db_free_result']($request);
3841
	}
3842
3843
	// Still no hash? mmm...
3844
	if (empty($file_hash))
3845
		$file_hash = sha1(md5($filename . time()) . mt_rand());
3846
3847
	// Are we using multiple directories?
3848
	if (is_array($modSettings['attachmentUploadDir']))
3849
		$path = $modSettings['attachmentUploadDir'][$dir];
3850
3851
	else
3852
		$path = $modSettings['attachmentUploadDir'];
3853
3854
	return $path . '/' . $attachment_id . '_' . $file_hash .'.dat';
3855
}
3856
3857
/**
3858
 * Convert a single IP to a ranged IP.
3859
 * internal function used to convert a user-readable format to a format suitable for the database.
3860
 *
3861
 * @param string $fullip The full IP
3862
 * @return array An array of IP parts
3863
 */
3864
function ip2range($fullip)
3865
{
3866
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
3867
	if ($fullip == 'unknown')
3868
		$fullip = '255.255.255.255';
3869
3870
	$ip_parts = explode('-', $fullip);
3871
	$ip_array = array();
3872
3873
	// 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...
3874
	if (count($ip_parts) == 1 && isValidIP($fullip))
3875
	{
3876
		$ip_array['low'] = $fullip;
3877
		$ip_array['high'] = $fullip;
3878
		return $ip_array;
3879
	} // if ip 22.12.* -> 22.12.* - 22.12.*
3880
	elseif (count($ip_parts) == 1)
3881
	{
3882
		$ip_parts[0] = $fullip;
3883
		$ip_parts[1] = $fullip;
3884
	}
3885
3886
	// 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...
3887
	if (count($ip_parts) == 2 && isValidIP($ip_parts[0]) && isValidIP($ip_parts[1]))
3888
	{
3889
		$ip_array['low'] = $ip_parts[0];
3890
		$ip_array['high'] = $ip_parts[1];
3891
		return $ip_array;
3892
	}
3893
	elseif (count($ip_parts) == 2) // if ip 22.22.*-22.22.*
3894
	{
3895
		$valid_low = isValidIP($ip_parts[0]);
3896
		$valid_high = isValidIP($ip_parts[1]);
3897
		$count = 0;
3898
		$mode = (preg_match('/:/',$ip_parts[0]) > 0 ? ':' : '.');
3899
		$max = ($mode == ':' ? 'ffff' : '255');
3900
		$min = 0;
3901 View Code Duplication
		if(!$valid_low)
3902
		{
3903
			$ip_parts[0] = preg_replace('/\*/', '0', $ip_parts[0]);
3904
			$valid_low = isValidIP($ip_parts[0]);
3905
			while (!$valid_low)
3906
			{
3907
				$ip_parts[0] .= $mode . $min;
3908
				$valid_low = isValidIP($ip_parts[0]);
3909
				$count++;
3910
				if ($count > 9) break;
3911
			}
3912
		}
3913
3914
		$count = 0;
3915 View Code Duplication
		if(!$valid_high)
3916
		{
3917
			$ip_parts[1] = preg_replace('/\*/', $max, $ip_parts[1]);
3918
			$valid_high = isValidIP($ip_parts[1]);
3919
			while (!$valid_high)
3920
			{
3921
				$ip_parts[1] .= $mode . $max;
3922
				$valid_high = isValidIP($ip_parts[1]);
3923
				$count++;
3924
				if ($count > 9) break;
3925
			}
3926
		}
3927
3928
		if($valid_high && $valid_low)
3929
		{
3930
			$ip_array['low'] = $ip_parts[0];
3931
			$ip_array['high'] = $ip_parts[1];
3932
		}
3933
3934
	}
3935
3936
	return $ip_array;
3937
}
3938
3939
/**
3940
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
3941
 *
3942
 * @param string $ip The IP to get the hostname from
3943
 * @return string The hostname
3944
 */
3945
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...
3946
{
3947
	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...
3948
3949
	if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null)
3950
		return $host;
3951
	$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...
3952
3953
	// Try the Linux host command, perhaps?
3954
	if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
3955
	{
3956
		if (!isset($modSettings['host_to_dis']))
3957
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
3958
		else
3959
			$test = @shell_exec('host ' . @escapeshellarg($ip));
3960
3961
		// Did host say it didn't find anything?
3962
		if (strpos($test, 'not found') !== false)
3963
			$host = '';
3964
		// Invalid server option?
3965
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
3966
			updateSettings(array('host_to_dis' => 1));
3967
		// Maybe it found something, after all?
3968
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
3969
			$host = $match[1];
3970
	}
3971
3972
	// This is nslookup; usually only Windows, but possibly some Unix?
3973
	if (!isset($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
3974
	{
3975
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
3976
		if (strpos($test, 'Non-existent domain') !== false)
3977
			$host = '';
3978
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
3979
			$host = $match[1];
3980
	}
3981
3982
	// This is the last try :/.
3983
	if (!isset($host) || $host === false)
3984
		$host = @gethostbyaddr($ip);
3985
3986
	// It took a long time, so let's cache it!
3987 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5)
3988
		cache_put_data('hostlookup-' . $ip, $host, 600);
3989
3990
	return $host;
3991
}
3992
3993
/**
3994
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
3995
 *
3996
 * @param string $text The text to split into words
3997
 * @param int $max_chars The maximum number of characters per word
3998
 * @param bool $encrypt Whether to encrypt the results
3999
 * @return array An array of ints or words depending on $encrypt
4000
 */
4001
function text2words($text, $max_chars = 20, $encrypt = false)
4002
{
4003
	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...
4004
4005
	// Step 1: Remove entities/things we don't consider words:
4006
	$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>' => ' ')));
4007
4008
	// Step 2: Entities we left to letters, where applicable, lowercase.
4009
	$words = un_htmlspecialchars($smcFunc['strtolower']($words));
4010
4011
	// Step 3: Ready to split apart and index!
4012
	$words = explode(' ', $words);
4013
4014
	if ($encrypt)
4015
	{
4016
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
4017
		$returned_ints = array();
4018
		foreach ($words as $word)
4019
		{
4020
			if (($word = trim($word, '-_\'')) !== '')
4021
			{
4022
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
4023
				$total = 0;
4024
				for ($i = 0; $i < $max_chars; $i++)
4025
					$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
4026
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
4027
			}
4028
		}
4029
		return array_unique($returned_ints);
4030
	}
4031
	else
4032
	{
4033
		// Trim characters before and after and add slashes for database insertion.
4034
		$returned_words = array();
4035
		foreach ($words as $word)
4036
			if (($word = trim($word, '-_\'')) !== '')
4037
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
4038
4039
		// Filter out all words that occur more than once.
4040
		return array_unique($returned_words);
4041
	}
4042
}
4043
4044
/**
4045
 * Creates an image/text button
4046
 *
4047
 * @param string $name The name of the button (should be a generic_icons class or the name of an image)
4048
 * @param string $alt The alt text
4049
 * @param string $label The $txt string to use as the label
4050
 * @param string $custom Custom text/html to add to the img tag (only when using an actual image)
4051
 * @param boolean $force_use Whether to force use of this when template_create_button is available
4052
 * @return string The HTML to display the button
4053
 */
4054
function create_button($name, $alt, $label = '', $custom = '', $force_use = false)
4055
{
4056
	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...
4057
4058
	// Does the current loaded theme have this and we are not forcing the usage of this function?
4059
	if (function_exists('template_create_button') && !$force_use)
4060
		return template_create_button($name, $alt, $label = '', $custom = '');
4061
4062
	if (!$settings['use_image_buttons'])
4063
		return $txt[$alt];
4064
	elseif (!empty($settings['use_buttons']))
4065
		return '<span class="generic_icons ' . $name . '" alt="' . $txt[$alt] . '"></span>' . ($label != '' ? '&nbsp;<strong>' . $txt[$label] . '</strong>' : '');
4066
	else
4067
		return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . '>';
4068
}
4069
4070
/**
4071
 * Sets up all of the top menu buttons
4072
 * Saves them in the cache if it is available and on
4073
 * Places the results in $context
4074
 *
4075
 */
4076
function setupMenuContext()
4077
{
4078
	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...
4079
4080
	// Set up the menu privileges.
4081
	$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts'));
4082
	$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'));
4083
4084
	$context['allow_memberlist'] = allowedTo('view_mlist');
4085
	$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
4086
	$context['allow_moderation_center'] = $context['user']['can_mod'];
4087
	$context['allow_pm'] = allowedTo('pm_read');
4088
4089
	$cacheTime = $modSettings['lastActive'] * 60;
4090
4091
	// Initial "can you post an event in the calendar" option - but this might have been set in the calendar already.
4092
	if (!isset($context['allow_calendar_event']))
4093
	{
4094
		$context['allow_calendar_event'] = $context['allow_calendar'] && allowedTo('calendar_post');
4095
4096
		// If you don't allow events not linked to posts and you're not an admin, we have more work to do...
4097 View Code Duplication
		if ($context['allow_calendar'] && $context['allow_calendar_event'] && empty($modSettings['cal_allow_unlinked']) && !$user_info['is_admin'])
4098
		{
4099
			$boards_can_post = boardsAllowedTo('post_new');
4100
			$context['allow_calendar_event'] &= !empty($boards_can_post);
4101
		}
4102
	}
4103
4104
	// There is some menu stuff we need to do if we're coming at this from a non-guest perspective.
4105
	if (!$context['user']['is_guest'])
4106
	{
4107
		addInlineJavaScript('
4108
	var user_menus = new smc_PopupMenu();
4109
	user_menus.add("profile", "' . $scripturl . '?action=profile;area=popup");
4110
	user_menus.add("alerts", "' . $scripturl . '?action=profile;area=alerts_popup;u='. $context['user']['id'] .'");', true);
4111
		if ($context['allow_pm'])
4112
			addInlineJavaScript('
4113
	user_menus.add("pm", "' . $scripturl . '?action=pm;sa=popup");', true);
4114
4115
		if (!empty($modSettings['enable_ajax_alerts']))
4116
		{
4117
			require_once($sourcedir . '/Subs-Notify.php');
4118
4119
			$timeout = getNotifyPrefs($context['user']['id'], 'alert_timeout', true);
4120
			$timeout = empty($timeout) ? 10000 : $timeout[$context['user']['id']]['alert_timeout'] * 1000;
4121
4122
			addInlineJavaScript('
4123
	var new_alert_title = "' . $context['forum_name'] . '";
4124
	var alert_timeout = ' . $timeout . ';');
4125
			loadJavaScriptFile('alerts.js', array(), 'smf_alerts');
4126
		}
4127
	}
4128
4129
	// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
4130
	if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
4131
	{
4132
		$buttons = array(
4133
			'home' => array(
4134
				'title' => $txt['home'],
4135
				'href' => $scripturl,
4136
				'show' => true,
4137
				'sub_buttons' => array(
4138
				),
4139
				'is_last' => $context['right_to_left'],
4140
			),
4141
			'search' => array(
4142
				'title' => $txt['search'],
4143
				'href' => $scripturl . '?action=search',
4144
				'show' => $context['allow_search'],
4145
				'sub_buttons' => array(
4146
				),
4147
			),
4148
			'admin' => array(
4149
				'title' => $txt['admin'],
4150
				'href' => $scripturl . '?action=admin',
4151
				'show' => $context['allow_admin'],
4152
				'sub_buttons' => array(
4153
					'featuresettings' => array(
4154
						'title' => $txt['modSettings_title'],
4155
						'href' => $scripturl . '?action=admin;area=featuresettings',
4156
						'show' => allowedTo('admin_forum'),
4157
					),
4158
					'packages' => array(
4159
						'title' => $txt['package'],
4160
						'href' => $scripturl . '?action=admin;area=packages',
4161
						'show' => allowedTo('admin_forum'),
4162
					),
4163
					'errorlog' => array(
4164
						'title' => $txt['errlog'],
4165
						'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
4166
						'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']),
4167
					),
4168
					'permissions' => array(
4169
						'title' => $txt['edit_permissions'],
4170
						'href' => $scripturl . '?action=admin;area=permissions',
4171
						'show' => allowedTo('manage_permissions'),
4172
					),
4173
					'memberapprove' => array(
4174
						'title' => $txt['approve_members_waiting'],
4175
						'href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve',
4176
						'show' => !empty($context['unapproved_members']),
4177
						'is_last' => true,
4178
					),
4179
				),
4180
			),
4181
			'moderate' => array(
4182
				'title' => $txt['moderate'],
4183
				'href' => $scripturl . '?action=moderate',
4184
				'show' => $context['allow_moderation_center'],
4185
				'sub_buttons' => array(
4186
					'modlog' => array(
4187
						'title' => $txt['modlog_view'],
4188
						'href' => $scripturl . '?action=moderate;area=modlog',
4189
						'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4190
					),
4191
					'poststopics' => array(
4192
						'title' => $txt['mc_unapproved_poststopics'],
4193
						'href' => $scripturl . '?action=moderate;area=postmod;sa=posts',
4194
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4195
					),
4196
					'attachments' => array(
4197
						'title' => $txt['mc_unapproved_attachments'],
4198
						'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments',
4199
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4200
					),
4201
					'reports' => array(
4202
						'title' => $txt['mc_reported_posts'],
4203
						'href' => $scripturl . '?action=moderate;area=reportedposts',
4204
						'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4205
					),
4206
					'reported_members' => array(
4207
						'title' => $txt['mc_reported_members'],
4208
						'href' => $scripturl . '?action=moderate;area=reportedmembers',
4209
						'show' => allowedTo('moderate_forum'),
4210
						'is_last' => true,
4211
					)
4212
				),
4213
			),
4214
			'calendar' => array(
4215
				'title' => $txt['calendar'],
4216
				'href' => $scripturl . '?action=calendar',
4217
				'show' => $context['allow_calendar'],
4218
				'sub_buttons' => array(
4219
					'view' => array(
4220
						'title' => $txt['calendar_menu'],
4221
						'href' => $scripturl . '?action=calendar',
4222
						'show' => $context['allow_calendar_event'],
4223
					),
4224
					'post' => array(
4225
						'title' => $txt['calendar_post_event'],
4226
						'href' => $scripturl . '?action=calendar;sa=post',
4227
						'show' => $context['allow_calendar_event'],
4228
						'is_last' => true,
4229
					),
4230
				),
4231
			),
4232
			'mlist' => array(
4233
				'title' => $txt['members_title'],
4234
				'href' => $scripturl . '?action=mlist',
4235
				'show' => $context['allow_memberlist'],
4236
				'sub_buttons' => array(
4237
					'mlist_view' => array(
4238
						'title' => $txt['mlist_menu_view'],
4239
						'href' => $scripturl . '?action=mlist',
4240
						'show' => true,
4241
					),
4242
					'mlist_search' => array(
4243
						'title' => $txt['mlist_search'],
4244
						'href' => $scripturl . '?action=mlist;sa=search',
4245
						'show' => true,
4246
						'is_last' => true,
4247
					),
4248
				),
4249
			),
4250
			'signup' => array(
4251
				'title' => $txt['register'],
4252
				'href' => $scripturl . '?action=signup',
4253
				'show' => $user_info['is_guest'] && $context['can_register'],
4254
				'sub_buttons' => array(
4255
				),
4256
				'is_last' => !$context['right_to_left'],
4257
			),
4258
			'logout' => array(
4259
				'title' => $txt['logout'],
4260
				'href' => $scripturl . '?action=logout;%1$s=%2$s',
4261
				'show' => !$user_info['is_guest'],
4262
				'sub_buttons' => array(
4263
				),
4264
				'is_last' => !$context['right_to_left'],
4265
			),
4266
		);
4267
4268
		// Allow editing menu buttons easily.
4269
		call_integration_hook('integrate_menu_buttons', array(&$buttons));
4270
4271
		// Now we put the buttons in the context so the theme can use them.
4272
		$menu_buttons = array();
4273
		foreach ($buttons as $act => $button)
4274
			if (!empty($button['show']))
4275
			{
4276
				$button['active_button'] = false;
4277
4278
				// This button needs some action.
4279
				if (isset($button['action_hook']))
4280
					$needs_action_hook = true;
4281
4282
				// Make sure the last button truly is the last button.
4283
				if (!empty($button['is_last']))
4284
				{
4285
					if (isset($last_button))
4286
						unset($menu_buttons[$last_button]['is_last']);
4287
					$last_button = $act;
4288
				}
4289
4290
				// Go through the sub buttons if there are any.
4291
				if (!empty($button['sub_buttons']))
4292
					foreach ($button['sub_buttons'] as $key => $subbutton)
4293
					{
4294
						if (empty($subbutton['show']))
4295
							unset($button['sub_buttons'][$key]);
4296
4297
						// 2nd level sub buttons next...
4298
						if (!empty($subbutton['sub_buttons']))
4299
						{
4300
							foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2)
4301
							{
4302
								if (empty($sub_button2['show']))
4303
									unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
4304
							}
4305
						}
4306
					}
4307
4308
				// Does this button have its own icon?
4309
				if (isset($button['icon']) && file_exists($settings['theme_dir'] . '/images/' . $button['icon']))
4310
					$button['icon'] = '<img src="' . $settings['images_url'] . '/' . $button['icon'] . '" alt="">';
4311
				elseif (isset($button['icon']) && file_exists($settings['default_theme_dir'] . '/images/' . $button['icon']))
4312
					$button['icon'] = '<img src="' . $settings['default_images_url'] . '/' . $button['icon'] . '" alt="">';
4313
				elseif (isset($button['icon']))
4314
					$button['icon'] = '<span class="generic_icons ' . $button['icon'] . '"></span>';
4315
				else
4316
					$button['icon'] = '<span class="generic_icons ' . $act . '"></span>';
4317
4318
				$menu_buttons[$act] = $button;
4319
			}
4320
4321
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
4322
			cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
4323
	}
4324
4325
	$context['menu_buttons'] = $menu_buttons;
4326
4327
	// Logging out requires the session id in the url.
4328
	if (isset($context['menu_buttons']['logout']))
4329
		$context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']);
4330
4331
	// Figure out which action we are doing so we can set the active tab.
4332
	// Default to home.
4333
	$current_action = 'home';
4334
4335
	if (isset($context['menu_buttons'][$context['current_action']]))
4336
		$current_action = $context['current_action'];
4337
	elseif ($context['current_action'] == 'search2')
4338
		$current_action = 'search';
4339
	elseif ($context['current_action'] == 'theme')
4340
		$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin';
4341
	elseif ($context['current_action'] == 'register2')
4342
		$current_action = 'register';
4343
	elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder'))
4344
		$current_action = 'login';
4345
	elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center'])
4346
		$current_action = 'moderate';
4347
4348
	// There are certain exceptions to the above where we don't want anything on the menu highlighted.
4349
	if ($context['current_action'] == 'profile' && !empty($context['user']['is_owner']))
4350
	{
4351
		$current_action = !empty($_GET['area']) && $_GET['area'] == 'showalerts' ? 'self_alerts' : 'self_profile';
4352
		$context[$current_action] = true;
4353
	}
4354
	elseif ($context['current_action'] == 'pm')
4355
	{
4356
		$current_action = 'self_pm';
4357
		$context['self_pm'] = true;
4358
	}
4359
4360
	$total_mod_reports = 0;
4361
4362
	if (!empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1' && !empty($context['open_mod_reports']))
4363
	{
4364
		$total_mod_reports = $context['open_mod_reports'];
4365
		$context['menu_buttons']['moderate']['sub_buttons']['reports']['title'] .= ' <span class="amt">' . $context['open_mod_reports'] . '</span>';
4366
	}
4367
4368
	// Show how many errors there are
4369
	if (allowedTo('admin_forum'))
4370
	{
4371
		// Get an error count, if necessary
4372
		if (!isset($context['num_errors']))
4373
		{
4374
			$query = $smcFunc['db_query']('', '
4375
				SELECT COUNT(id_error)
4376
				FROM {db_prefix}log_errors',
4377
				array()
4378
			);
4379
4380
			list($context['num_errors']) = $smcFunc['db_fetch_row']($query);
4381
			$smcFunc['db_free_result']($query);
4382
		}
4383
4384 View Code Duplication
		if (!empty($context['num_errors']))
4385
		{
4386
			$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4387
			$context['menu_buttons']['admin']['sub_buttons']['errorlog']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4388
		}
4389
	}
4390
4391
	// Show number of reported members
4392
	if (!empty($context['open_member_reports']) && allowedTo('moderate_forum'))
4393
	{
4394
		$total_mod_reports += $context['open_member_reports'];
4395
		$context['menu_buttons']['moderate']['sub_buttons']['reported_members']['title'] .= ' <span class="amt">' . $context['open_member_reports'] . '</span>';
4396
	}
4397
4398 View Code Duplication
	if (!empty($context['unapproved_members']))
4399
	{
4400
		$context['menu_buttons']['admin']['sub_buttons']['memberapprove']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4401
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4402
	}
4403
4404
	// Do we have any open reports?
4405
	if ($total_mod_reports > 0)
4406
	{
4407
		$context['menu_buttons']['moderate']['title'] .= ' <span class="amt">' . $total_mod_reports . '</span>';
4408
	}
4409
4410
	// Not all actions are simple.
4411
	if (!empty($needs_action_hook))
4412
		call_integration_hook('integrate_current_action', array(&$current_action));
4413
4414
	if (isset($context['menu_buttons'][$current_action]))
4415
		$context['menu_buttons'][$current_action]['active_button'] = true;
4416
}
4417
4418
/**
4419
 * Generate a random seed and ensure it's stored in settings.
4420
 */
4421
function smf_seed_generator()
4422
{
4423
	updateSettings(array('rand_seed' => microtime() * 1000000));
4424
}
4425
4426
/**
4427
 * Process functions of an integration hook.
4428
 * calls all functions of the given hook.
4429
 * supports static class method calls.
4430
 *
4431
 * @param string $hook The hook name
4432
 * @param array $parameters An array of parameters this hook implements
4433
 * @return array The results of the functions
4434
 */
4435
function call_integration_hook($hook, $parameters = array())
4436
{
4437
	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...
4438
	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...
4439
4440
	if ($db_show_debug === true)
4441
		$context['debug']['hooks'][] = $hook;
4442
4443
	// Need to have some control.
4444
	if (!isset($context['instances']))
4445
		$context['instances'] = array();
4446
4447
	$results = array();
4448
	if (empty($modSettings[$hook]))
4449
		return $results;
4450
4451
	$functions = explode(',', $modSettings[$hook]);
4452
	// Loop through each function.
4453
	foreach ($functions as $function)
4454
	{
4455
		// Hook has been marked as "disabled". Skip it!
4456
		if (strpos($function, '!') !== false)
4457
			continue;
4458
4459
		$call = call_helper($function, true);
4460
4461
		// Is it valid?
4462
		if (!empty($call))
4463
			$results[$function] = call_user_func_array($call, $parameters);
4464
4465
		// Whatever it was suppose to call, it failed :(
4466
		elseif (!empty($function))
4467
		{
4468
			loadLanguage('Errors');
4469
4470
			// Get a full path to show on error.
4471
			if (strpos($function, '|') !== false)
4472
			{
4473
				list ($file, $string) = explode('|', $function);
4474
				$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'])));
4475
				log_error(sprintf($txt['hook_fail_call_to'], $string, $absPath), 'general');
4476
			}
4477
4478
			// "Assume" the file resides on $boarddir somewhere...
4479
			else
4480
				log_error(sprintf($txt['hook_fail_call_to'], $function, $boarddir), 'general');
4481
		}
4482
	}
4483
4484
	return $results;
4485
}
4486
4487
/**
4488
 * Add a function for integration hook.
4489
 * does nothing if the function is already added.
4490
 *
4491
 * @param string $hook The complete hook name.
4492
 * @param string $function The function name. Can be a call to a method via Class::method.
4493
 * @param bool $permanent If true, updates the value in settings table.
4494
 * @param string $file The file. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4495
 * @param bool $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4496
 */
4497
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
4498
{
4499
	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...
4500
4501
	// Any objects?
4502
	if ($object)
4503
		$function = $function . '#';
4504
4505
	// Any files  to load?
4506
	if (!empty($file) && is_string($file))
4507
		$function = $file . (!empty($function) ? '|' . $function : '');
4508
4509
	// Get the correct string.
4510
	$integration_call = $function;
4511
4512
	// Is it going to be permanent?
4513
	if ($permanent)
4514
	{
4515
		$request = $smcFunc['db_query']('', '
4516
			SELECT value
4517
			FROM {db_prefix}settings
4518
			WHERE variable = {string:variable}',
4519
			array(
4520
				'variable' => $hook,
4521
			)
4522
		);
4523
		list ($current_functions) = $smcFunc['db_fetch_row']($request);
4524
		$smcFunc['db_free_result']($request);
4525
4526
		if (!empty($current_functions))
4527
		{
4528
			$current_functions = explode(',', $current_functions);
4529
			if (in_array($integration_call, $current_functions))
4530
				return;
4531
4532
			$permanent_functions = array_merge($current_functions, array($integration_call));
4533
		}
4534
		else
4535
			$permanent_functions = array($integration_call);
4536
4537
		updateSettings(array($hook => implode(',', $permanent_functions)));
4538
	}
4539
4540
	// Make current function list usable.
4541
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4542
4543
	// Do nothing, if it's already there.
4544
	if (in_array($integration_call, $functions))
4545
		return;
4546
4547
	$functions[] = $integration_call;
4548
	$modSettings[$hook] = implode(',', $functions);
4549
}
4550
4551
/**
4552
 * Remove an integration hook function.
4553
 * Removes the given function from the given hook.
4554
 * Does nothing if the function is not available.
4555
 *
4556
 * @param string $hook The complete hook name.
4557
 * @param string $function The function name. Can be a call to a method via Class::method.
4558
 * @param boolean $permanent Irrelevant for the function itself but need to declare it to match
4559
 * @param string $file The filename. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4560
 * @param boolean $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4561
 * @see add_integration_function
4562
 */
4563
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...
4564
{
4565
	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...
4566
4567
	// Any objects?
4568
	if ($object)
4569
		$function = $function . '#';
4570
4571
	// Any files  to load?
4572
	if (!empty($file) && is_string($file))
4573
		$function = $file . '|' . $function;
4574
4575
	// Get the correct string.
4576
	$integration_call = $function;
4577
4578
	// Get the permanent functions.
4579
	$request = $smcFunc['db_query']('', '
4580
		SELECT value
4581
		FROM {db_prefix}settings
4582
		WHERE variable = {string:variable}',
4583
		array(
4584
			'variable' => $hook,
4585
		)
4586
	);
4587
	list ($current_functions) = $smcFunc['db_fetch_row']($request);
4588
	$smcFunc['db_free_result']($request);
4589
4590
	if (!empty($current_functions))
4591
	{
4592
		$current_functions = explode(',', $current_functions);
4593
4594
		if (in_array($integration_call, $current_functions))
4595
			updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call)))));
4596
	}
4597
4598
	// Turn the function list into something usable.
4599
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4600
4601
	// You can only remove it if it's available.
4602
	if (!in_array($integration_call, $functions))
4603
		return;
4604
4605
	$functions = array_diff($functions, array($integration_call));
4606
	$modSettings[$hook] = implode(',', $functions);
4607
}
4608
4609
/**
4610
 * Receives a string and tries to figure it out if its a method or a function.
4611
 * If a method is found, it looks for a "#" which indicates SMF should create a new instance of the given class.
4612
 * Checks the string/array for is_callable() and return false/fatal_lang_error is the given value results in a non callable string/array.
4613
 * Prepare and returns a callable depending on the type of method/function found.
4614
 *
4615
 * @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)
4616
 * @param boolean $return If true, the function will not call the function/method but instead will return the formatted string.
4617
 * @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.
4618
 */
4619
function call_helper($string, $return = false)
4620
{
4621
	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...
4622
4623
	// Really?
4624
	if (empty($string))
4625
		return false;
4626
4627
	// An array? should be a "callable" array IE array(object/class, valid_callable).
4628
	// A closure? should be a callable one.
4629
	if (is_array($string) || $string instanceof Closure)
4630
		return $return ? $string : (is_callable($string) ? call_user_func($string) : false);
4631
4632
	// No full objects, sorry! pass a method or a property instead!
4633
	if (is_object($string))
4634
		return false;
4635
4636
	// Stay vitaminized my friends...
4637
	$string = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($string));
4638
4639
	// Is there a file to load?
4640
	$string = load_file($string);
4641
4642
	// Loaded file failed
4643
	if (empty($string))
4644
		return false;
4645
4646
	// Found a method.
4647
	if (strpos($string, '::') !== false)
4648
	{
4649
		list ($class, $method) = explode('::', $string);
4650
4651
		// Check if a new object will be created.
4652
		if (strpos($method, '#') !== false)
4653
		{
4654
			// Need to remove the # thing.
4655
			$method = str_replace('#', '', $method);
4656
4657
			// Don't need to create a new instance for every method.
4658
			if (empty($context['instances'][$class]) || !($context['instances'][$class] instanceof $class))
4659
			{
4660
				$context['instances'][$class] = new $class;
4661
4662
				// Add another one to the list.
4663
				if ($db_show_debug === true)
4664
				{
4665
					if (!isset($context['debug']['instances']))
4666
						$context['debug']['instances'] = array();
4667
4668
					$context['debug']['instances'][$class] = $class;
4669
				}
4670
			}
4671
4672
			$func = array($context['instances'][$class], $method);
4673
		}
4674
4675
		// Right then. This is a call to a static method.
4676
		else
4677
			$func = array($class, $method);
4678
	}
4679
4680
	// Nope! just a plain regular function.
4681
	else
4682
		$func = $string;
4683
4684
	// Right, we got what we need, time to do some checks.
4685
	if (!is_callable($func, false, $callable_name))
4686
	{
4687
		loadLanguage('Errors');
4688
		log_error(sprintf($txt['subAction_fail'], $callable_name), 'general');
4689
4690
		// Gotta tell everybody.
4691
		return false;
4692
	}
4693
4694
	// Everything went better than expected.
4695
	else
4696
	{
4697
		// What are we gonna do about it?
4698
		if ($return)
4699
			return $func;
4700
4701
		// If this is a plain function, avoid the heat of calling call_user_func().
4702
		else
4703
		{
4704
			if (is_array($func))
4705
				call_user_func($func);
4706
4707
			else
4708
				$func();
4709
		}
4710
	}
4711
}
4712
4713
/**
4714
 * Receives a string and tries to figure it out if it contains info to load a file.
4715
 * Checks for a | (pipe) symbol and tries to load a file with the info given.
4716
 * 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.
4717
 *
4718
 * @param string $string The string containing a valid format.
4719
 * @return string|boolean The given string with the pipe and file info removed. Boolean false if the file couldn't be loaded.
4720
 */
4721
function load_file($string)
4722
{
4723
	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...
4724
4725
	if (empty($string))
4726
		return false;
4727
4728
	if (strpos($string, '|') !== false)
4729
	{
4730
		list ($file, $string) = explode('|', $string);
4731
4732
		// Match the wildcards to their regular vars.
4733
		if (empty($settings['theme_dir']))
4734
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
4735
4736
		else
4737
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
4738
4739
		// Load the file if it can be loaded.
4740
		if (file_exists($absPath))
4741
			require_once($absPath);
4742
4743
		// No? try a fallback to $sourcedir
4744
		else
4745
		{
4746
			$absPath = $sourcedir .'/'. $file;
4747
4748
			if (file_exists($absPath))
4749
				require_once($absPath);
4750
4751
			// Sorry, can't do much for you at this point.
4752
			else
4753
			{
4754
				loadLanguage('Errors');
4755
				log_error(sprintf($txt['hook_fail_loading_file'], $absPath), 'general');
4756
4757
				// File couldn't be loaded.
4758
				return false;
4759
			}
4760
		}
4761
	}
4762
4763
	return $string;
4764
}
4765
4766
/**
4767
 * Prepares an array of "likes" info for the topic specified by $topic
4768
 * @param integer $topic The topic ID to fetch the info from.
4769
 * @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...
4770
 */
4771
function prepareLikesContext($topic)
4772
{
4773
	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...
4774
4775
	// Make sure we have something to work with.
4776
	if (empty($topic))
4777
		return array();
4778
4779
4780
	// We already know the number of likes per message, we just want to know whether the current user liked it or not.
4781
	$user = $user_info['id'];
4782
	$cache_key = 'likes_topic_' . $topic . '_' . $user;
4783
	$ttl = 180;
4784
4785
	if (($temp = cache_get_data($cache_key, $ttl)) === null)
4786
	{
4787
		$temp = array();
4788
		$request = $smcFunc['db_query']('', '
4789
			SELECT content_id
4790
			FROM {db_prefix}user_likes AS l
4791
				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
4792
			WHERE l.id_member = {int:current_user}
4793
				AND l.content_type = {literal:msg}
4794
				AND m.id_topic = {int:topic}',
4795
			array(
4796
				'current_user' => $user,
4797
				'topic' => $topic,
4798
			)
4799
		);
4800
		while ($row = $smcFunc['db_fetch_assoc']($request))
4801
			$temp[] = (int) $row['content_id'];
4802
4803
		cache_put_data($cache_key, $temp, $ttl);
4804
	}
4805
4806
	return $temp;
4807
}
4808
4809
/**
4810
 * Microsoft uses their own character set Code Page 1252 (CP1252), which is a
4811
 * superset of ISO 8859-1, defining several characters between DEC 128 and 159
4812
 * that are not normally displayable.  This converts the popular ones that
4813
 * appear from a cut and paste from windows.
4814
 *
4815
 * @param string $string The string
4816
 * @return string The sanitized string
4817
 */
4818
function sanitizeMSCutPaste($string)
4819
{
4820
	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...
4821
4822
	if (empty($string))
4823
		return $string;
4824
4825
	// UTF-8 occurences of MS special characters
4826
	$findchars_utf8 = array(
4827
		"\xe2\x80\x9a",	// single low-9 quotation mark
4828
		"\xe2\x80\x9e",	// double low-9 quotation mark
4829
		"\xe2\x80\xa6",	// horizontal ellipsis
4830
		"\xe2\x80\x98",	// left single curly quote
4831
		"\xe2\x80\x99",	// right single curly quote
4832
		"\xe2\x80\x9c",	// left double curly quote
4833
		"\xe2\x80\x9d",	// right double curly quote
4834
		"\xe2\x80\x93",	// en dash
4835
		"\xe2\x80\x94",	// em dash
4836
	);
4837
4838
	// windows 1252 / iso equivalents
4839
	$findchars_iso = array(
4840
		chr(130),
4841
		chr(132),
4842
		chr(133),
4843
		chr(145),
4844
		chr(146),
4845
		chr(147),
4846
		chr(148),
4847
		chr(150),
4848
		chr(151),
4849
	);
4850
4851
	// safe replacements
4852
	$replacechars = array(
4853
		',',	// &sbquo;
4854
		',,',	// &bdquo;
4855
		'...',	// &hellip;
4856
		"'",	// &lsquo;
4857
		"'",	// &rsquo;
4858
		'"',	// &ldquo;
4859
		'"',	// &rdquo;
4860
		'-',	// &ndash;
4861
		'--',	// &mdash;
4862
	);
4863
4864
	if ($context['utf8'])
4865
		$string = str_replace($findchars_utf8, $replacechars, $string);
4866
	else
4867
		$string = str_replace($findchars_iso, $replacechars, $string);
4868
4869
	return $string;
4870
}
4871
4872
/**
4873
 * Decode numeric html entities to their ascii or UTF8 equivalent character.
4874
 *
4875
 * Callback function for preg_replace_callback in subs-members
4876
 * Uses capture group 2 in the supplied array
4877
 * Does basic scan to ensure characters are inside a valid range
4878
 *
4879
 * @param array $matches An array of matches (relevant info should be the 3rd item)
4880
 * @return string A fixed string
4881
 */
4882
function replaceEntities__callback($matches)
4883
{
4884
	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...
4885
4886
	if (!isset($matches[2]))
4887
		return '';
4888
4889
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4890
4891
	// remove left to right / right to left overrides
4892
	if ($num === 0x202D || $num === 0x202E)
4893
		return '';
4894
4895
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
4896
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
4897
		return '&#' . $num . ';';
4898
4899
	if (empty($context['utf8']))
4900
	{
4901
		// no control characters
4902
		if ($num < 0x20)
4903
			return '';
4904
		// text is text
4905
		elseif ($num < 0x80)
4906
			return chr($num);
4907
		// all others get html-ised
4908
		else
4909
			return '&#' . $matches[2] . ';';
4910
	}
4911
	else
4912
	{
4913
		// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
4914
		// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
4915
		if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
4916
			return '';
4917
		// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4918
		elseif ($num < 0x80)
4919
			return chr($num);
4920
		// <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...
4921 View Code Duplication
		elseif ($num < 0x800)
4922
			return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4923
		// < 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...
4924 View Code Duplication
		elseif ($num < 0x10000)
4925
			return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4926
		// <= 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...
4927 View Code Duplication
		else
4928
			return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4929
	}
4930
}
4931
4932
/**
4933
 * Converts html entities to utf8 equivalents
4934
 *
4935
 * Callback function for preg_replace_callback
4936
 * Uses capture group 1 in the supplied array
4937
 * Does basic checks to keep characters inside a viewable range.
4938
 *
4939
 * @param array $matches An array of matches (relevant info should be the 2nd item in the array)
4940
 * @return string The fixed string
4941
 */
4942
function fixchar__callback($matches)
4943
{
4944
	if (!isset($matches[1]))
4945
		return '';
4946
4947
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
4948
4949
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
4950
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
4951
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
4952
		return '';
4953
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4954
	elseif ($num < 0x80)
4955
		return chr($num);
4956
	// <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...
4957 View Code Duplication
	elseif ($num < 0x800)
4958
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4959
	// < 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...
4960 View Code Duplication
	elseif ($num < 0x10000)
4961
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4962
	// <= 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...
4963 View Code Duplication
	else
4964
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4965
}
4966
4967
/**
4968
 * Strips out invalid html entities, replaces others with html style &#123; codes
4969
 *
4970
 * Callback function used of preg_replace_callback in smcFunc $ent_checks, for example
4971
 * strpos, strlen, substr etc
4972
 *
4973
 * @param array $matches An array of matches (relevant info should be the 3rd item in the array)
4974
 * @return string The fixed string
4975
 */
4976
function entity_fix__callback($matches)
4977
{
4978
	if (!isset($matches[2]))
4979
		return '';
4980
4981
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4982
4983
	// we don't allow control characters, characters out of range, byte markers, etc
4984
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
4985
		return '';
4986
	else
4987
		return '&#' . $num . ';';
4988
}
4989
4990
/**
4991
 * Return a Gravatar URL based on
4992
 * - the supplied email address,
4993
 * - the global maximum rating,
4994
 * - the global default fallback,
4995
 * - maximum sizes as set in the admin panel.
4996
 *
4997
 * It is SSL aware, and caches most of the parameters.
4998
 *
4999
 * @param string $email_address The user's email address
5000
 * @return string The gravatar URL
5001
 */
5002
function get_gravatar_url($email_address)
5003
{
5004
	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...
5005
	static $url_params = null;
5006
5007
	if ($url_params === null)
5008
	{
5009
		$ratings = array('G', 'PG', 'R', 'X');
5010
		$defaults = array('mm', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank');
5011
		$url_params = array();
5012 View Code Duplication
		if (!empty($modSettings['gravatarMaxRating']) && in_array($modSettings['gravatarMaxRating'], $ratings))
5013
			$url_params[] = 'rating=' . $modSettings['gravatarMaxRating'];
5014 View Code Duplication
		if (!empty($modSettings['gravatarDefault']) && in_array($modSettings['gravatarDefault'], $defaults))
5015
			$url_params[] = 'default=' . $modSettings['gravatarDefault'];
5016
		if (!empty($modSettings['avatar_max_width_external']))
5017
			$size_string = (int) $modSettings['avatar_max_width_external'];
5018
		if (!empty($modSettings['avatar_max_height_external']) && !empty($size_string))
5019
			if ((int) $modSettings['avatar_max_height_external'] < $size_string)
5020
				$size_string = $modSettings['avatar_max_height_external'];
5021
5022
		if (!empty($size_string))
5023
			$url_params[] = 's=' . $size_string;
5024
	}
5025
	$http_method = !empty($modSettings['force_ssl']) ? 'https://secure' : 'http://www';
5026
5027
	return $http_method . '.gravatar.com/avatar/' . md5($smcFunc['strtolower']($email_address)) . '?' . implode('&', $url_params);
5028
}
5029
5030
/**
5031
 * Get a list of timezones.
5032
 *
5033
 * @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'.
5034
 * @return array An array of timezone info.
5035
 */
5036
function smf_list_timezones($when = 'now')
5037
{
5038
	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...
5039
	static $timezones = null, $lastwhen = null;
5040
5041
	// No point doing this over if we already did it once
5042
	if (!empty($timezones) && $when == $lastwhen)
5043
		return $timezones;
5044
	else
5045
		$lastwhen = $when;
5046
5047
	// Parseable datetime string?
5048
	if (is_int($timestamp = strtotime($when)))
5049
		$when = $timestamp;
5050
5051
	// A Unix timestamp?
5052
	elseif (is_numeric($when))
5053
		$when = intval($when);
5054
5055
	// Invalid value? Just get current Unix timestamp.
5056
	else
5057
		$when = time();
5058
5059
	// We'll need these too
5060
	$date_when = date_create('@' . $when);
5061
	$later = (int) date_format(date_add($date_when, date_interval_create_from_date_string('1 year')), 'U');
5062
5063
	// Prefer and give custom descriptions for these time zones
5064
	// If the description is left empty, it will be filled in with the names of matching cities
5065
	$timezone_descriptions = array(
5066
		'America/Adak' => 'Aleutian Islands',
5067
		'Pacific/Marquesas' => 'Marquesas Islands',
5068
		'Pacific/Gambier' => 'Gambier Islands',
5069
		'America/Anchorage' => 'Alaska',
5070
		'Pacific/Pitcairn' => 'Pitcairn Islands',
5071
		'America/Los_Angeles' => 'Pacific Time (USA, Canada)',
5072
		'America/Denver' => 'Mountain Time (USA, Canada)',
5073
		'America/Phoenix' => 'Mountain Time (no DST)',
5074
		'America/Chicago' => 'Central Time (USA, Canada)',
5075
		'America/Belize' => 'Central Time (no DST)',
5076
		'America/New_York' => 'Eastern Time (USA, Canada)',
5077
		'America/Atikokan' => 'Eastern Time (no DST)',
5078
		'America/Halifax' => 'Atlantic Time (Canada)',
5079
		'America/Anguilla' => 'Atlantic Time (no DST)',
5080
		'America/St_Johns' => 'Newfoundland',
5081
		'America/Chihuahua' => 'Chihuahua, Mazatlan',
5082
		'Pacific/Easter' => 'Easter Island',
5083
		'Atlantic/Stanley' => 'Falkland Islands',
5084
		'America/Miquelon' => 'Saint Pierre and Miquelon',
5085
		'America/Argentina/Buenos_Aires' => 'Buenos Aires',
5086
		'America/Sao_Paulo' => 'Brasilia Time',
5087
		'America/Araguaina' => 'Brasilia Time (no DST)',
5088
		'America/Godthab' => 'Greenland',
5089
		'America/Noronha' => 'Fernando de Noronha',
5090
		'Atlantic/Reykjavik' => 'Greenwich Mean Time (no DST)',
5091
		'Europe/London' => '',
5092
		'Europe/Berlin' => 'Central European Time',
5093
		'Europe/Helsinki' => 'Eastern European Time',
5094
		'Africa/Brazzaville' => 'Brazzaville, Lagos, Porto-Novo',
5095
		'Asia/Jerusalem' => 'Jerusalem',
5096
		'Europe/Moscow' => '',
5097
		'Africa/Khartoum' => 'Eastern Africa Time',
5098
		'Asia/Riyadh' => 'Arabia Time',
5099
		'Asia/Kolkata' => 'India, Sri Lanka',
5100
		'Asia/Yekaterinburg' => 'Yekaterinburg, Tyumen',
5101
		'Asia/Dhaka' => 'Astana, Dhaka',
5102
		'Asia/Rangoon' => 'Yangon/Rangoon',
5103
		'Indian/Christmas' => 'Christmas Island',
5104
		'Antarctica/DumontDUrville' => 'Dumont D\'Urville Station',
5105
		'Antarctica/Vostok' => 'Vostok Station',
5106
		'Australia/Lord_Howe' => 'Lord Howe Island',
5107
		'Pacific/Guadalcanal' => 'Solomon Islands',
5108
		'Pacific/Norfolk' => 'Norfolk Island',
5109
		'Pacific/Noumea' => 'New Caledonia',
5110
		'Pacific/Auckland' => 'Auckland, McMurdo Station',
5111
		'Pacific/Kwajalein' => 'Marshall Islands',
5112
		'Pacific/Chatham' => 'Chatham Islands',
5113
	);
5114
5115
	// Should we put time zones from certain countries at the top of the list?
5116
	$priority_countries = !empty($modSettings['timezone_priority_countries']) ? explode(',', $modSettings['timezone_priority_countries']) : array();
5117
	$priority_tzids = array();
5118
	foreach ($priority_countries as $country)
5119
	{
5120
		$country_tzids = @timezone_identifiers_list(DateTimeZone::PER_COUNTRY, strtoupper(trim($country)));
5121
		if (!empty($country_tzids))
5122
			$priority_tzids = array_merge($priority_tzids, $country_tzids);
5123
	}
5124
5125
	// Process the preferred timezones first, then the rest.
5126
	$tzids = array_keys($timezone_descriptions) + array_diff(timezone_identifiers_list(), array_keys($timezone_descriptions));
5127
5128
	// Idea here is to get exactly one representative identifier for each and every unique set of time zone rules.
5129
	foreach ($tzids as $tzid)
5130
	{
5131
		// We don't want UTC right now
5132
		if ($tzid == 'UTC')
5133
			continue;
5134
5135
		$tz = timezone_open($tzid);
5136
5137
		// First, get the set of transition rules for this tzid
5138
		$tzinfo = timezone_transitions_get($tz, $when, $later);
5139
5140
		// Use the entire set of transition rules as the array *key* so we can avoid duplicates
5141
		$tzkey = serialize($tzinfo);
5142
5143
		// Next, get the geographic info for this tzid
5144
		$tzgeo = timezone_location_get($tz);
5145
5146
		// Don't overwrite our preferred tzids
5147
		if (empty($zones[$tzkey]['tzid']))
5148
		{
5149
			$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...
5150
			$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...
5151
		}
5152
5153
		// A time zone from a prioritized country?
5154
		if (in_array($tzid, $priority_tzids))
5155
			$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...
5156
5157
		// Keep track of the location and offset for this tzid
5158
		$tzid_parts = explode('/', $tzid);
5159
		$zones[$tzkey]['locations'][] = str_replace(array('St_', '_'), array('St. ', ' '), array_pop($tzid_parts));
5160
		$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...
5161
		$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...
5162
	}
5163
5164
	// Sort by offset then longitude
5165
	array_multisort($offsets, SORT_ASC, SORT_NUMERIC, $longitudes, SORT_ASC, SORT_NUMERIC, $zones);
5166
5167
	// Build the final array of formatted values
5168
	$priority_timezones = array();
5169
	$timezones = array();
5170
	foreach ($zones as $tzkey => $tzvalue)
5171
	{
5172
		date_timezone_set($date_when, timezone_open($tzvalue['tzid']));
5173
5174
		if (!empty($timezone_descriptions[$tzvalue['tzid']]))
5175
			$desc = $timezone_descriptions[$tzvalue['tzid']];
5176
		else
5177
			$desc = implode(', ', array_unique($tzvalue['locations']));
5178
5179
		if (isset($priority_zones[$tzkey]))
5180
			$priority_timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']';
5181
		else
5182
			$timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']';
5183
	}
5184
5185
	$timezones = array_merge(
5186
		$priority_timezones,
5187
		array('' => '(Forum Default)', 'UTC' => 'UTC - Coordinated Universal Time'),
5188
		$timezones
5189
	);
5190
5191
	return $timezones;
5192
}
5193
5194
/**
5195
 * Reformats certain time zone abbreviations to look better.
5196
 *
5197
 * Some of PHP's time zone abbreviations are just numerical offsets from UTC, e.g. '+04'
5198
 * These look weird and are kind of useless, so we make them look better.
5199
 *
5200
 * @param string $tzid The Olsen time zome identifier for a time zone.
5201
 * @param string $tz_abbrev The abbreviation PHP provided for this time zone.
5202
 * @return string The fixed version of $tz_abbrev.
5203
 */
5204
function fix_tz_abbrev($tzid, $tz_abbrev)
5205
{
5206
	// Is this abbreviation just a numerical offset?
5207
	if (strspn($tz_abbrev, '+-') > 0)
5208
	{
5209
		// To get on this list, a time zone must be historically stable and must not observe daylight saving time
5210
		$missing_tz_abbrs = array(
5211
			'Antarctica/Casey' => 'CAST',
5212
			'Antarctica/Davis' => 'DAVT',
5213
			'Antarctica/DumontDUrville' => 'DDUT',
5214
			'Antarctica/Mawson' => 'MAWT',
5215
			'Antarctica/Rothera' => 'ART',
5216
			'Antarctica/Syowa' => 'SYOT',
5217
			'Antarctica/Vostok' => 'VOST',
5218
			'Asia/Almaty' => 'ALMT',
5219
			'Asia/Aqtau' => 'ORAT',
5220
			'Asia/Aqtobe' => 'AQTT',
5221
			'Asia/Ashgabat' => 'TMT',
5222
			'Asia/Bishkek' => 'KGT',
5223
			'Asia/Colombo' => 'IST',
5224
			'Asia/Dushanbe' => 'TJT',
5225
			'Asia/Oral' => 'ORAT',
5226
			'Asia/Qyzylorda' => 'QYZT',
5227
			'Asia/Samarkand' => 'UZT',
5228
			'Asia/Tashkent' => 'UZT',
5229
			'Asia/Tbilisi' => 'GET',
5230
			'Asia/Yerevan' => 'AMT',
5231
			'Europe/Istanbul' => 'TRT',
5232
			'Europe/Minsk' => 'MSK',
5233
			'Indian/Kerguelen' => 'TFT',
5234
		);
5235
5236
		if (!empty($missing_tz_abbrs[$tzid]))
5237
			$tz_abbrev = $missing_tz_abbrs[$tzid];
5238
		else
5239
		{
5240
			// Russia likes to experiment with time zones often, and names them as offsets from Moscow
5241
			$tz_location = timezone_location_get(timezone_open($tzid));
5242
			if ($tz_location['country_code'] == 'RU')
5243
			{
5244
				$msk_offset = intval($tz_abbrev) - 3;
5245
				$tz_abbrev = 'MSK' . (!empty($msk_offset) ? sprintf('%+0d', $msk_offset) : '');
5246
			}
5247
		}
5248
5249
		// Still no good? We'll just mark it as a UTC offset
5250
		if (strspn($tz_abbrev, '+-') > 0)
5251
		{
5252
			$tz_abbrev = intval($tz_abbrev);
5253
			$tz_abbrev = 'UTC' . (!empty($tz_abbrev) ? sprintf('%+0d', $tz_abbrev) : '');
5254
		}
5255
	}
5256
5257
	return $tz_abbrev;
5258
}
5259
5260
/**
5261
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
5262
 * @return string|false The IP address in binary or false
5263
 */
5264
function inet_ptod($ip_address)
5265
{
5266
	if (!isValidIP($ip_address))
5267
		return $ip_address;
5268
5269
	$bin = inet_pton($ip_address);
5270
	return $bin;
5271
}
5272
5273
/**
5274
 * @param string $bin An IP address in IPv4, IPv6 (Either string (postgresql) or binary (other databases))
5275
 * @return string|false The IP address in presentation format or false on error
5276
 */
5277
function inet_dtop($bin)
5278
{
5279
	if(empty($bin))
5280
		return '';
5281
5282
	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...
5283
5284
	if ($db_type == 'postgresql')
5285
		return $bin;
5286
5287
	$ip_address = inet_ntop($bin);
5288
5289
	return $ip_address;
5290
}
5291
5292
/**
5293
 * Safe serialize() and unserialize() replacements
5294
 *
5295
 * @license Public Domain
5296
 *
5297
 * @author anthon (dot) pang (at) gmail (dot) com
5298
 */
5299
5300
/**
5301
 * Safe serialize() replacement. Recursive
5302
 * - output a strict subset of PHP's native serialized representation
5303
 * - does not serialize objects
5304
 *
5305
 * @param mixed $value
5306
 * @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...
5307
 */
5308
function _safe_serialize($value)
5309
{
5310
	if(is_null($value))
5311
		return 'N;';
5312
5313
	if(is_bool($value))
5314
		return 'b:'. (int) $value .';';
5315
5316
	if(is_int($value))
5317
		return 'i:'. $value .';';
5318
5319
	if(is_float($value))
5320
		return 'd:'. str_replace(',', '.', $value) .';';
5321
5322
	if(is_string($value))
5323
		return 's:'. strlen($value) .':"'. $value .'";';
5324
5325
	if(is_array($value))
5326
	{
5327
		$out = '';
5328
		foreach($value as $k => $v)
5329
			$out .= _safe_serialize($k) . _safe_serialize($v);
5330
5331
		return 'a:'. count($value) .':{'. $out .'}';
5332
	}
5333
5334
	// safe_serialize cannot serialize resources or objects.
5335
	return false;
5336
}
5337
/**
5338
 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issues.
5339
 *
5340
 * @param mixed $value
5341
 * @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...
5342
 */
5343 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...
5344
{
5345
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5346
	if (function_exists('mb_internal_encoding') &&
5347
		(((int) ini_get('mbstring.func_overload')) & 2))
5348
	{
5349
		$mbIntEnc = mb_internal_encoding();
5350
		mb_internal_encoding('ASCII');
5351
	}
5352
5353
	$out = _safe_serialize($value);
5354
5355
	if (isset($mbIntEnc))
5356
		mb_internal_encoding($mbIntEnc);
5357
5358
	return $out;
5359
}
5360
5361
/**
5362
 * Safe unserialize() replacement
5363
 * - accepts a strict subset of PHP's native serialized representation
5364
 * - does not unserialize objects
5365
 *
5366
 * @param string $str
5367
 * @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...
5368
 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
5369
 */
5370
function _safe_unserialize($str)
5371
{
5372
	// Input  is not a string.
5373
	if(empty($str) || !is_string($str))
5374
		return false;
5375
5376
	$stack = array();
5377
	$expected = array();
5378
5379
	/*
5380
	 * states:
5381
	 *   0 - initial state, expecting a single value or array
5382
	 *   1 - terminal state
5383
	 *   2 - in array, expecting end of array or a key
5384
	 *   3 - in array, expecting value or another array
5385
	 */
5386
	$state = 0;
5387
	while($state != 1)
5388
	{
5389
		$type = isset($str[0]) ? $str[0] : '';
5390
		if($type == '}')
5391
			$str = substr($str, 1);
5392
5393
		else if($type == 'N' && $str[1] == ';')
5394
		{
5395
			$value = null;
5396
			$str = substr($str, 2);
5397
		}
5398
		else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
5399
		{
5400
			$value = $matches[1] == '1' ? true : false;
5401
			$str = substr($str, 4);
5402
		}
5403
		else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
5404
		{
5405
			$value = (int)$matches[1];
5406
			$str = $matches[2];
5407
		}
5408
		else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
5409
		{
5410
			$value = (float)$matches[1];
5411
			$str = $matches[3];
5412
		}
5413
		else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
5414
		{
5415
			$value = substr($matches[2], 0, (int)$matches[1]);
5416
			$str = substr($matches[2], (int)$matches[1] + 2);
5417
		}
5418
		else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches))
5419
		{
5420
			$expectedLength = (int)$matches[1];
5421
			$str = $matches[2];
5422
		}
5423
5424
		// Object or unknown/malformed type.
5425
		else
5426
			return false;
5427
5428
		switch($state)
5429
		{
5430
			case 3: // In array, expecting value or another array.
5431
				if($type == 'a')
5432
				{
5433
					$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...
5434
					$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...
5435
					$list = &$list[$key];
5436
					$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...
5437
					$state = 2;
5438
					break;
5439
				}
5440
				if($type != '}')
5441
				{
5442
					$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...
5443
					$state = 2;
5444
					break;
5445
				}
5446
5447
				// Missing array value.
5448
				return false;
5449
5450
			case 2: // in array, expecting end of array or a key
5451
				if($type == '}')
5452
				{
5453
					// Array size is less than expected.
5454
					if(count($list) < end($expected))
5455
						return false;
5456
5457
					unset($list);
5458
					$list = &$stack[count($stack)-1];
5459
					array_pop($stack);
5460
5461
					// Go to terminal state if we're at the end of the root array.
5462
					array_pop($expected);
5463
5464
					if(count($expected) == 0)
5465
						$state = 1;
5466
5467
					break;
5468
				}
5469
5470
				if($type == 'i' || $type == 's')
5471
				{
5472
					// Array size exceeds expected length.
5473
					if(count($list) >= end($expected))
5474
						return false;
5475
5476
					$key = $value;
5477
					$state = 3;
5478
					break;
5479
				}
5480
5481
				// Illegal array index type.
5482
				return false;
5483
5484
			// Expecting array or value.
5485
			case 0:
5486
				if($type == 'a')
5487
				{
5488
					$data = array();
5489
					$list = &$data;
5490
					$expected[] = $expectedLength;
5491
					$state = 2;
5492
					break;
5493
				}
5494
5495
				if($type != '}')
5496
				{
5497
					$data = $value;
5498
					$state = 1;
5499
					break;
5500
				}
5501
5502
				// Not in array.
5503
				return false;
5504
		}
5505
	}
5506
5507
	// Trailing data in input.
5508
	if(!empty($str))
5509
		return false;
5510
5511
	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...
5512
}
5513
5514
/**
5515
 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
5516
 *
5517
 * @param string $str
5518
 * @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...
5519
 */
5520 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...
5521
{
5522
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5523
	if (function_exists('mb_internal_encoding') &&
5524
		(((int) ini_get('mbstring.func_overload')) & 0x02))
5525
	{
5526
		$mbIntEnc = mb_internal_encoding();
5527
		mb_internal_encoding('ASCII');
5528
	}
5529
5530
	$out = _safe_unserialize($str);
5531
5532
	if (isset($mbIntEnc))
5533
		mb_internal_encoding($mbIntEnc);
5534
5535
	return $out;
5536
}
5537
5538
/**
5539
 * Tries different modes to make file/dirs writable. Wrapper function for chmod()
5540
5541
 * @param string $file The file/dir full path.
5542
 * @param int $value Not needed, added for legacy reasons.
5543
 * @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.
5544
 */
5545
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...
5546
{
5547
	// No file? no checks!
5548
	if (empty($file))
5549
		return false;
5550
5551
	// Already writable?
5552
	if (is_writable($file))
5553
		return true;
5554
5555
	// Do we have a file or a dir?
5556
	$isDir = is_dir($file);
5557
	$isWritable = false;
5558
5559
	// Set different modes.
5560
	$chmodValues = $isDir ? array(0750, 0755, 0775, 0777) : array(0644, 0664, 0666);
5561
5562
	foreach($chmodValues as $val)
5563
	{
5564
		// If it's writable, break out of the loop.
5565
		if (is_writable($file))
5566
		{
5567
			$isWritable = true;
5568
			break;
5569
		}
5570
5571
		else
5572
			@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...
5573
	}
5574
5575
	return $isWritable;
5576
}
5577
5578
/**
5579
 * Wrapper function for json_decode() with error handling.
5580
5581
 * @param string $json The string to decode.
5582
 * @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.
5583
 * @param bool $logIt To specify if the error will be logged if theres any.
5584
 * @return array Either an empty array or the decoded data as an array.
5585
 */
5586
function smf_json_decode($json, $returnAsArray = false, $logIt = true)
5587
{
5588
	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...
5589
5590
	// Come on...
5591
	if (empty($json) || !is_string($json))
5592
		return array();
5593
5594
	$returnArray = @json_decode($json, $returnAsArray);
5595
5596
	// PHP 5.3 so no json_last_error_msg()
5597
	switch(json_last_error())
5598
	{
5599
		case JSON_ERROR_NONE:
5600
			$jsonError = false;
5601
			break;
5602
		case JSON_ERROR_DEPTH:
5603
			$jsonError =  'JSON_ERROR_DEPTH';
5604
			break;
5605
		case JSON_ERROR_STATE_MISMATCH:
5606
			$jsonError = 'JSON_ERROR_STATE_MISMATCH';
5607
			break;
5608
		case JSON_ERROR_CTRL_CHAR:
5609
			$jsonError = 'JSON_ERROR_CTRL_CHAR';
5610
			break;
5611
		case JSON_ERROR_SYNTAX:
5612
			$jsonError = 'JSON_ERROR_SYNTAX';
5613
			break;
5614
		case JSON_ERROR_UTF8:
5615
			$jsonError = 'JSON_ERROR_UTF8';
5616
			break;
5617
		default:
5618
			$jsonError = 'unknown';
5619
			break;
5620
	}
5621
5622
	// Something went wrong!
5623
	if (!empty($jsonError) && $logIt)
5624
	{
5625
		// Being a wrapper means we lost our smf_error_handler() privileges :(
5626
		$jsonDebug = debug_backtrace();
5627
		$jsonDebug = $jsonDebug[0];
5628
		loadLanguage('Errors');
5629
5630
		if (!empty($jsonDebug))
5631
			log_error($txt['json_'. $jsonError], 'critical', $jsonDebug['file'], $jsonDebug['line']);
5632
5633
		else
5634
			log_error($txt['json_'. $jsonError], 'critical');
5635
5636
		// Everyone expects an array.
5637
		return array();
5638
	}
5639
5640
	return $returnArray;
5641
}
5642
5643
/**
5644
 * Check the given String if he is a valid IPv4 or IPv6
5645
 * return true or false
5646
 *
5647
 * @param string $IPString
5648
 *
5649
 * @return bool
5650
 */
5651
function isValidIP($IPString)
5652
{
5653
	return filter_var($IPString, FILTER_VALIDATE_IP) !== false;
5654
}
5655
5656
/**
5657
 * Outputs a response.
5658
 * It assumes the data is already a string.
5659
 * @param string $data The data to print
5660
 * @param string $type The content type. Defaults to Json.
5661
 * @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...
5662
 */
5663
function smf_serverResponse($data = '', $type = 'content-type: application/json')
5664
{
5665
	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...
5666
5667
	// Defensive programming anyone?
5668
	if (empty($data))
5669
		return false;
5670
5671
	// Don't need extra stuff...
5672
	$db_show_debug = false;
5673
5674
	// Kill anything else.
5675
	ob_end_clean();
5676
5677
	if (!empty($modSettings['CompressedOutput']))
5678
		@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...
5679
5680
	else
5681
		ob_start();
5682
5683
	// Set the header.
5684
	header($type);
5685
5686
	// Echo!
5687
	echo $data;
5688
5689
	// Done.
5690
	obExit(false);
5691
}
5692
5693
/**
5694
 * Creates an optimized regex to match all known top level domains.
5695
 *
5696
 * The optimized regex is stored in $modSettings['tld_regex'].
5697
 *
5698
 * To update the stored version of the regex to use the latest list of valid TLDs from iana.org, set
5699
 * the $update parameter to true. Updating can take some time, based on network connectivity, so it
5700
 * should normally only be done by calling this function from a background or scheduled task.
5701
 *
5702
 * If $update is not true, but the regex is missing or invalid, the regex will be regenerated from a
5703
 * hard-coded list of TLDs. This regenerated regex will be overwritten on the next scheduled update.
5704
 *
5705
 * @param bool $update If true, fetch and process the latest offical list of TLDs from iana.org.
5706
 */
5707
function set_tld_regex($update = false)
5708
{
5709
	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...
5710
	static $done = false;
5711
5712
	// If we don't need to do anything, don't
5713
	if (!$update && $done)
5714
		return;
5715
5716
	// Should we get a new copy of the official list of TLDs?
5717
	if ($update)
5718
	{
5719
		require_once($sourcedir . '/Subs-Package.php');
5720
		$tlds = fetch_web_data('https://data.iana.org/TLD/tlds-alpha-by-domain.txt');
5721
5722
		// 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.
5723
		if ($tlds === false)
5724
			$postapocalypticNightmare = true;
5725
	}
5726
	// If we aren't updating and the regex is valid, we're done
5727
	elseif (!empty($modSettings['tld_regex']) && @preg_match('~' . $modSettings['tld_regex'] . '~', null) !== false)
5728
	{
5729
		$done = true;
5730
		return;
5731
	}
5732
5733
	// If we successfully got an update, process the list into an array
5734
	if (!empty($tlds))
5735
	{
5736
		// Clean $tlds and convert it to an array
5737
		$tlds = array_filter(explode("\n", strtolower($tlds)), function($line) {
5738
			$line = trim($line);
5739
			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...
5740
				return false;
5741
			else
5742
				return true;
5743
		});
5744
5745
		// Convert Punycode to Unicode
5746
		$tlds = array_map(function ($input) {
5747
			$prefix = 'xn--';
5748
			$safe_char = 0xFFFC;
5749
			$base = 36;
5750
			$tmin = 1;
5751
			$tmax = 26;
5752
			$skew = 38;
5753
			$damp = 700;
5754
			$output_parts = array();
5755
5756
			$input = str_replace(strtoupper($prefix), $prefix, $input);
5757
5758
			$enco_parts = (array) explode('.', $input);
5759
5760
			foreach ($enco_parts as $encoded)
5761
			{
5762
				if (strpos($encoded,$prefix) !== 0 || strlen(trim(str_replace($prefix,'',$encoded))) == 0)
5763
				{
5764
					$output_parts[] = $encoded;
5765
					continue;
5766
				}
5767
5768
				$is_first = true;
5769
				$bias = 72;
5770
				$idx = 0;
5771
				$char = 0x80;
5772
				$decoded = array();
5773
				$output='';
5774
				$delim_pos = strrpos($encoded, '-');
5775
5776
				if ($delim_pos > strlen($prefix))
5777
				{
5778
					for ($k = strlen($prefix); $k < $delim_pos; ++$k)
5779
					{
5780
						$decoded[] = ord($encoded{$k});
5781
					}
5782
				}
5783
5784
				$deco_len = count($decoded);
5785
				$enco_len = strlen($encoded);
5786
5787
				for ($enco_idx = $delim_pos ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len)
5788
				{
5789
					for ($old_idx = $idx, $w = 1, $k = $base; 1 ; $k += $base)
5790
					{
5791
						$cp = ord($encoded{$enco_idx++});
5792
						$digit = ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $base));
5793
						$idx += $digit * $w;
5794
						$t = ($k <= $bias) ? $tmin : (($k >= $bias + $tmax) ? $tmax : ($k - $bias));
5795
5796
						if ($digit < $t)
5797
							break;
5798
5799
						$w = (int) ($w * ($base - $t));
5800
					}
5801
5802
					$delta = $idx - $old_idx;
5803
					$delta = intval($is_first ? ($delta / $damp) : ($delta / 2));
5804
					$delta += intval($delta / ($deco_len + 1));
5805
5806
					for ($k = 0; $delta > (($base - $tmin) * $tmax) / 2; $k += $base)
5807
						$delta = intval($delta / ($base - $tmin));
5808
5809
					$bias = intval($k + ($base - $tmin + 1) * $delta / ($delta + $skew));
5810
					$is_first = false;
5811
					$char += (int) ($idx / ($deco_len + 1));
5812
					$idx %= ($deco_len + 1);
5813
5814
					if ($deco_len > 0)
5815
					{
5816
						for ($i = $deco_len; $i > $idx; $i--)
5817
							$decoded[$i] = $decoded[($i - 1)];
5818
					}
5819
					$decoded[$idx++] = $char;
5820
				}
5821
5822
				foreach ($decoded as $k => $v)
5823
				{
5824
					// 7bit are transferred literally
5825
					if ($v < 128)
5826
						$output .= chr($v);
5827
5828
					// 2 bytes
5829
					elseif ($v < (1 << 11))
5830
						$output .= chr(192+($v >> 6)) . chr(128+($v & 63));
5831
5832
					// 3 bytes
5833
					elseif ($v < (1 << 16))
5834
						$output .= chr(224+($v >> 12)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5835
5836
					// 4 bytes
5837
					elseif ($v < (1 << 21))
5838
						$output .= chr(240+($v >> 18)) . chr(128+(($v >> 12) & 63)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5839
5840
					//  'Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k
5841
					else
5842
						$output .= $safe_char;
5843
				}
5844
5845
				$output_parts[] = $output;
5846
			}
5847
5848
			return implode('.', $output_parts);
5849
		}, $tlds);
5850
	}
5851
	// Otherwise, use the 2012 list of gTLDs and ccTLDs for now and schedule a background update
5852
	else
5853
	{
5854
		$tlds = array('com', 'net', 'org', 'edu', 'gov', 'mil', 'aero', 'asia', 'biz', 'cat',
5855
			'coop', 'info', 'int', 'jobs', 'mobi', 'museum', 'name', 'post', 'pro', 'tel',
5856
			'travel', 'xxx', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq',
5857
			'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
5858
			'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc',
5859
			'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cs', 'cu', 'cv',
5860
			'cx', 'cy', 'cz', 'dd', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh',
5861
			'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge',
5862
			'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw',
5863
			'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq',
5864
			'ir', 'is', 'it', 'ja', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn',
5865
			'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu',
5866
			'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp',
5867
			'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf',
5868
			'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph',
5869
			'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru',
5870
			'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn',
5871
			'so', 'sr', 'ss', 'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th',
5872
			'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug',
5873
			'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye',
5874
			'yt', 'yu', 'za', 'zm', 'zw');
5875
5876
		// Schedule a background update, unless civilization has collapsed and/or we are having connectivity issues.
5877
		$schedule_update = empty($postapocalypticNightmare);
5878
	}
5879
5880
	// Get an optimized regex to match all the TLDs
5881
	$tld_regex = build_regex($tlds);
5882
5883
	// Remember the new regex in $modSettings
5884
	updateSettings(array('tld_regex' => $tld_regex));
5885
5886
	// Schedule a background update if we need one
5887 View Code Duplication
	if (!empty($schedule_update))
5888
	{
5889
		$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
5890
			array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
5891
			array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array()
5892
		);
5893
	}
5894
5895
	// Redundant repetition is redundant
5896
	$done = true;
5897
}
5898
5899
/**
5900
 * Creates optimized regular expressions from an array of strings.
5901
 *
5902
 * An optimized regex built using this function will be much faster than a simple regex built using
5903
 * `implode('|', $strings)` --- anywhere from several times to several orders of magnitude faster.
5904
 *
5905
 * However, the time required to build the optimized regex is approximately equal to the time it
5906
 * takes to execute the simple regex. Therefore, it is only worth calling this function if the
5907
 * resulting regex will be used more than once.
5908
 *
5909
 * Because PHP places an upper limit on the allowed length of a regex, very large arrays of $strings
5910
 * may not fit in a single regex. Normally, the excess strings will simply be dropped. However, if
5911
 * the $returnArray parameter is set to true, this function will build as many regexes as necessary
5912
 * to accomodate everything in $strings and return them in an array. You will need to iterate
5913
 * through all elements of the returned array in order to test all possible matches.
5914
 *
5915
 * @param array $strings An array of strings to make a regex for.
5916
 * @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...
5917
 * @param bool $returnArray If true, returns an array of regexes.
5918
 * @return string|array One or more regular expressions to match any of the input strings.
5919
 */
5920
function build_regex($strings, $delim = null, $returnArray = false)
5921
{
5922
	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...
5923
5924
	// The mb_* functions are faster than the $smcFunc ones, but may not be available
5925
	if (function_exists('mb_internal_encoding') && function_exists('mb_detect_encoding') && function_exists('mb_strlen') && function_exists('mb_substr'))
5926
	{
5927
		if (($string_encoding = mb_detect_encoding(implode(' ', $strings))) !== false)
5928
		{
5929
			$current_encoding = mb_internal_encoding();
5930
			mb_internal_encoding($string_encoding);
5931
		}
5932
5933
		$strlen = 'mb_strlen';
5934
		$substr = 'mb_substr';
5935
	}
5936
	else
5937
	{
5938
		$strlen = $smcFunc['strlen'];
5939
		$substr = $smcFunc['substr'];
5940
	}
5941
5942
	// This recursive function creates the index array from the strings
5943
	$add_string_to_index = function ($string, $index) use (&$strlen, &$substr, &$add_string_to_index)
5944
	{
5945
		static $depth = 0;
5946
		$depth++;
5947
5948
		$first = $substr($string, 0, 1);
5949
5950
		if (empty($index[$first]))
5951
			$index[$first] = array();
5952
5953
		if ($strlen($string) > 1)
5954
		{
5955
			// Sanity check on recursion
5956
			if ($depth > 99)
5957
				$index[$first][$substr($string, 1)] = '';
5958
5959
			else
5960
				$index[$first] = $add_string_to_index($substr($string, 1), $index[$first]);
5961
		}
5962
		else
5963
			$index[$first][''] = '';
5964
5965
		$depth--;
5966
		return $index;
5967
	};
5968
5969
	// This recursive function turns the index array into a regular expression
5970
	$index_to_regex = function (&$index, $delim) use (&$strlen, &$index_to_regex)
5971
	{
5972
		static $depth = 0;
5973
		$depth++;
5974
5975
		// Absolute max length for a regex is 32768, but we might need wiggle room
5976
		$max_length = 30000;
5977
5978
		$regex = array();
5979
		$length = 0;
5980
5981
		foreach ($index as $key => $value)
5982
		{
5983
			$key_regex = preg_quote($key, $delim);
5984
			$new_key = $key;
5985
5986
			if (empty($value))
5987
				$sub_regex = '';
5988
			else
5989
			{
5990
				$sub_regex = $index_to_regex($value, $delim);
5991
5992
				if (count(array_keys($value)) == 1)
5993
				{
5994
					$new_key_array = explode('(?'.'>', $sub_regex);
5995
					$new_key .= $new_key_array[0];
5996
				}
5997
				else
5998
					$sub_regex = '(?'.'>' . $sub_regex . ')';
5999
			}
6000
6001
			if ($depth > 1)
6002
				$regex[$new_key] = $key_regex . $sub_regex;
6003
			else
6004
			{
6005
				if (($length += strlen($key_regex) + 1) < $max_length || empty($regex))
6006
				{
6007
					$regex[$new_key] = $key_regex . $sub_regex;
6008
					unset($index[$key]);
6009
				}
6010
				else
6011
					break;
6012
			}
6013
		}
6014
6015
		// Sort by key length and then alphabetically
6016
		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...
6017
			$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...
6018
			$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...
6019
6020
			if ($l1 == $l2)
6021
				return strcmp($k1, $k2) > 0 ? 1 : -1;
6022
			else
6023
				return $l1 > $l2 ? -1 : 1;
6024
		});
6025
6026
		$depth--;
6027
		return implode('|', $regex);
6028
	};
6029
6030
	// Now that the functions are defined, let's do this thing
6031
	$index = array();
6032
	$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...
6033
6034
	foreach ($strings as $string)
6035
		$index = $add_string_to_index($string, $index);
6036
6037
	if ($returnArray === true)
6038
	{
6039
		$regex = array();
6040
		while (!empty($index))
6041
			$regex[] = '(?'.'>' . $index_to_regex($index, $delim) . ')';
6042
	}
6043
	else
6044
		$regex = '(?'.'>' . $index_to_regex($index, $delim) . ')';
6045
6046
	// Restore PHP's internal character encoding to whatever it was originally
6047
	if (!empty($current_encoding))
6048
		mb_internal_encoding($current_encoding);
6049
6050
	return $regex;
6051
}
6052
6053
/**
6054
 * Check if the passed url has an SSL certificate.
6055
 *
6056
 * Returns true if a cert was found & false if not.
6057
 * @param string $url to check, in $boardurl format (no trailing slash).
6058
 */
6059
 function ssl_cert_found($url) {
6060
6061
	// First, strip the subfolder from the passed url, if any
6062
	$parsedurl = parse_url($url);
6063
	$url = 'ssl://' . $parsedurl['host'] . ':443';
6064
6065
	// Next, check the ssl stream context for certificate info
6066
	$result = false;
6067
	$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...
6068
	$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 6062
  11. $url is assigned
    in Sources/Subs.php on line 6063
  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 6062
  10. $url is assigned
    in Sources/Subs.php on line 6063

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...
6069
	if ($stream !== false)
6070
	{
6071
		$params = stream_context_get_params($stream);
6072
		$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...
6073
	}
6074
    return $result;
6075
}
6076
6077
/**
6078
 * Check if the passed url has a redirect to https:// by querying headers.
6079
 *
6080
 * Returns true if a redirect was found & false if not.
6081
 * Note that when force_ssl = 2, SMF issues its own redirect...  So if this
6082
 * returns true, it may be caused by SMF, not necessarily an .htaccess redirect.
6083
 * @param string $url to check, in $boardurl format (no trailing slash).
6084
 */
6085
function https_redirect_active($url) {
6086
6087
	// Ask for the headers for the passed url, but via http...
6088
	// Need to add the trailing slash, or it puts it there & thinks there's a redirect when there isn't...
6089
	$url = str_ireplace('https://', 'http://', $url) . '/';
6090
	$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 6089
  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 6089

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...
6091
	if ($headers === false)
6092
		return false;
6093
6094
	// Now to see if it came back https...
6095
	// First check for a redirect status code in first row (301, 302, 307)
6096
	if (strstr($headers[0], '301') === false && strstr($headers[0], '302') === false && strstr($headers[0], '307') === false)
6097
		return false;
6098
6099
	// Search for the location entry to confirm https
6100
	$result = false;
6101
	foreach ($headers as $header) {
6102
		if (stristr($header, 'Location: https://') !== false) {
6103
			$result = true;
6104
			break;
6105
		}
6106
	}
6107
	return $result;
6108
}
6109
6110
/**
6111
 * Build query_wanna_see_board and query_see_board for a userid
6112
 *
6113
 * Returns array with keys query_wanna_see_board and query_see_board
6114
 * @param int $userid of the user
6115
 */
6116
function build_query_board($userid)
6117
{
6118
	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...
6119
6120
	$query_part = array();
6121
	$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...
6122
	$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...
6123
	$deny_boards_access = !empty($modSettings['deny_boards_access']) ? $modSettings['deny_boards_access'] : null;
6124
	$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...
6125
	$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...
6126
6127
	if ($user_info['id'] == $userid)
6128
	{
6129
		$groups = $user_info['groups'];
6130
		$is_admin = $user_info['is_admin'];
6131
		$mod_cache = !empty($user_info['mod_cache']) ? $user_info['mod_cache'] : null;
6132
		$ignoreboards = !empty($user_info['ignoreboards']) ? $user_info['ignoreboards'] : null;
6133
	}
6134
	else
6135
	{
6136
		$request = $smcFunc['db_query']('', '
6137
				SELECT mem.ignore_boards, mem.id_group, mem.additional_groups, mem.id_post_group
6138
				FROM {db_prefix}members AS mem
6139
				WHERE mem.id_member = {int:id_member}
6140
				LIMIT 1',
6141
				array(
6142
					'id_member' => $userid,
6143
				)
6144
			);
6145
6146
		$row = $smcFunc['db_fetch_assoc']($request);
6147
6148 View Code Duplication
		if (empty($row['additional_groups']))
6149
			$groups = array($row['id_group'], $row['id_post_group']);
6150
		else
6151
			$groups = array_merge(
6152
					array($row['id_group'], $row['id_post_group']),
6153
					explode(',', $row['additional_groups'])
6154
			);
6155
6156
		// Because history has proven that it is possible for groups to go bad - clean up in case.
6157
		foreach ($groups as $k => $v)
6158
			$groups[$k] = (int) $v;
6159
6160
		$is_admin = in_array(1, $groups);
6161
6162
		$ignoreboards = !empty($row['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $row['ignore_boards']) : array();
6163
6164
		// What boards are they the moderator of?
6165
		$boards_mod = array();
6166
6167
		$request = $smcFunc['db_query']('', '
6168
			SELECT id_board
6169
			FROM {db_prefix}moderators
6170
			WHERE id_member = {int:current_member}',
6171
			array(
6172
				'current_member' => $userid,
6173
			)
6174
		);
6175
		while ($row = $smcFunc['db_fetch_assoc']($request))
6176
			$boards_mod[] = $row['id_board'];
6177
		$smcFunc['db_free_result']($request);
6178
6179
		// Can any of the groups they're in moderate any of the boards?
6180
		$request = $smcFunc['db_query']('', '
6181
			SELECT id_board
6182
			FROM {db_prefix}moderator_groups
6183
			WHERE id_group IN({array_int:groups})',
6184
			array(
6185
				'groups' => $groups,
6186
			)
6187
		);
6188
		while ($row = $smcFunc['db_fetch_assoc']($request))
6189
			$boards_mod[] = $row['id_board'];
6190
		$smcFunc['db_free_result']($request);
6191
6192
		// Just in case we've got duplicates here...
6193
		$boards_mod = array_unique($boards_mod);
6194
6195
		$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...
6196
	}
6197
6198
	// Just build this here, it makes it easier to change/use - administrators can see all boards.
6199
	if ($is_admin)
6200
		$query_part['query_see_board'] = '1=1';
6201
	// Otherwise just the groups in $user_info['groups'].
6202
	else
6203
		$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'] : '') . ')';
6204
6205
	// Build the list of boards they WANT to see.
6206
	// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also
6207
6208
	// If they aren't ignoring any boards then they want to see all the boards they can see
6209
	if (empty($ignoreboards))
6210
		$query_part['query_wanna_see_board'] = $query_part['query_see_board'];
6211
	// Ok I guess they don't want to see all the boards
6212
	else
6213
		$query_part['query_wanna_see_board'] = '(' . $query_part['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $ignoreboards) . '))';
6214
6215
	return $query_part;
6216
}
6217
6218
/**
6219
 * Check if the connection is using https.
6220
 *
6221
 * @return boolean true if connection used https
6222
 */
6223
function httpsOn()
6224
{
6225
	$secure = false;
6226
6227 View Code Duplication
	if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
6228
		$secure = true;
6229
	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')
6230
		$secure = true;
6231
6232
	return $secure;
6233
}
6234
6235
?>