Completed
Push — release-2.1 ( d6a0ea...665539 )
by Michael
09:46 queued 02:18
created

Subs.php ➔ build_query_board()   F

Complexity

Conditions 16
Paths 400

Size

Total Lines 101
Code Lines 52

Duplication

Lines 7
Ratio 6.93 %

Importance

Changes 0
Metric Value
cc 16
eloc 52
nc 400
nop 1
dl 7
loc 101
rs 3.7109
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to, well, everything.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * 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">$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;
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($scheme))
1423
							$data = 'http://' . ltrim($data, ':/');
1424
1425
						if ($scheme != 'https')
1426
							$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1427
					}
1428
					elseif (empty($scheme))
1429
						$data = '//' . ltrim($data, ':/');
1430
				},
1431
				'disabled_content' => '($1)',
1432
			),
1433
			array(
1434
				'tag' => 'img',
1435
				'type' => 'unparsed_content',
1436
				'content' => '<img src="$1" alt="" class="bbc_img">',
1437 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...
1438
				{
1439
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
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...
1440
1441
					$data = strtr($data, array('<br>' => ''));
1442
					$scheme = parse_url($data, PHP_URL_SCHEME);
1443
					if ($image_proxy_enabled)
1444
					{
1445
						if (empty($scheme))
1446
							$data = 'http://' . ltrim($data, ':/');
1447
1448
						if ($scheme != 'https')
1449
							$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1450
					}
1451
					elseif (empty($scheme))
1452
						$data = '//' . ltrim($data, ':/');
1453
				},
1454
				'disabled_content' => '($1)',
1455
			),
1456
			array(
1457
				'tag' => 'iurl',
1458
				'type' => 'unparsed_content',
1459
				'content' => '<a href="$1" class="bbc_link">$1</a>',
1460 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...
1461
				{
1462
					$data = strtr($data, array('<br>' => ''));
1463
					$scheme = parse_url($data, PHP_URL_SCHEME);
1464
					if (empty($scheme))
1465
						$data = '//' . ltrim($data, ':/');
1466
				},
1467
			),
1468
			array(
1469
				'tag' => 'iurl',
1470
				'type' => 'unparsed_equals',
1471
				'quoted' => 'optional',
1472
				'before' => '<a href="$1" class="bbc_link">',
1473
				'after' => '</a>',
1474
				'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...
1475
				{
1476
					if (substr($data, 0, 1) == '#')
1477
						$data = '#post_' . substr($data, 1);
1478
					else
1479
					{
1480
						$scheme = parse_url($data, PHP_URL_SCHEME);
1481
						if (empty($scheme))
1482
							$data = '//' . ltrim($data, ':/');
1483
					}
1484
				},
1485
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1486
				'disabled_after' => ' ($1)',
1487
			),
1488
			array(
1489
				'tag' => 'left',
1490
				'before' => '<div style="text-align: left;">',
1491
				'after' => '</div>',
1492
				'block_level' => true,
1493
			),
1494
			array(
1495
				'tag' => 'li',
1496
				'before' => '<li>',
1497
				'after' => '</li>',
1498
				'trim' => 'outside',
1499
				'require_parents' => array('list'),
1500
				'block_level' => true,
1501
				'disabled_before' => '',
1502
				'disabled_after' => '<br>',
1503
			),
1504
			array(
1505
				'tag' => 'list',
1506
				'before' => '<ul class="bbc_list">',
1507
				'after' => '</ul>',
1508
				'trim' => 'inside',
1509
				'require_children' => array('li', 'list'),
1510
				'block_level' => true,
1511
			),
1512
			array(
1513
				'tag' => 'list',
1514
				'parameters' => array(
1515
					'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)'),
1516
				),
1517
				'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
1518
				'after' => '</ul>',
1519
				'trim' => 'inside',
1520
				'require_children' => array('li'),
1521
				'block_level' => true,
1522
			),
1523
			array(
1524
				'tag' => 'ltr',
1525
				'before' => '<bdo dir="ltr">',
1526
				'after' => '</bdo>',
1527
				'block_level' => true,
1528
			),
1529
			array(
1530
				'tag' => 'me',
1531
				'type' => 'unparsed_equals',
1532
				'before' => '<div class="meaction">* $1 ',
1533
				'after' => '</div>',
1534
				'quoted' => 'optional',
1535
				'block_level' => true,
1536
				'disabled_before' => '/me ',
1537
				'disabled_after' => '<br>',
1538
			),
1539
			array(
1540
				'tag' => 'member',
1541
				'type' => 'unparsed_equals',
1542
				'before' => '<a href="' . $scripturl . '?action=profile;u=$1" class="mention" data-mention="$1">@',
1543
				'after' => '</a>',
1544
			),
1545
			array(
1546
				'tag' => 'nobbc',
1547
				'type' => 'unparsed_content',
1548
				'content' => '$1',
1549
			),
1550
			array(
1551
				'tag' => 'php',
1552
				'type' => 'unparsed_content',
1553
				'content' => '<span class="phpcode">$1</span>',
1554
				'validate' => isset($disabled['php']) ? null : function (&$tag, &$data, $disabled)
1555
				{
1556
					if (!isset($disabled['php']))
1557
					{
1558
						$add_begin = substr(trim($data), 0, 5) != '&lt;?';
1559
						$data = highlight_php_code($add_begin ? '&lt;?php ' . $data . '?&gt;' : $data);
1560
						if ($add_begin)
1561
							$data = preg_replace(array('~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~', '~\?&gt;((?:</(font|span)>)*)$~'), '$1', $data, 2);
1562
					}
1563
				},
1564
				'block_level' => false,
1565
				'disabled_content' => '$1',
1566
			),
1567
			array(
1568
				'tag' => 'pre',
1569
				'before' => '<pre>',
1570
				'after' => '</pre>',
1571
			),
1572
			array(
1573
				'tag' => 'quote',
1574
				'before' => '<blockquote><cite>' . $txt['quote'] . '</cite>',
1575
				'after' => '</blockquote>',
1576
				'trim' => 'both',
1577
				'block_level' => true,
1578
			),
1579
			array(
1580
				'tag' => 'quote',
1581
				'parameters' => array(
1582
					'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
1583
				),
1584
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1585
				'after' => '</blockquote>',
1586
				'trim' => 'both',
1587
				'block_level' => true,
1588
			),
1589
			array(
1590
				'tag' => 'quote',
1591
				'type' => 'parsed_equals',
1592
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': $1</cite>',
1593
				'after' => '</blockquote>',
1594
				'trim' => 'both',
1595
				'quoted' => 'optional',
1596
				// Don't allow everything to be embedded with the author name.
1597
				'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
1598
				'block_level' => true,
1599
			),
1600
			array(
1601
				'tag' => 'quote',
1602
				'parameters' => array(
1603
					'author' => array('match' => '([^<>]{1,192}?)'),
1604
					'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|msg=\d+?|action=profile;u=\d+)'),
1605
					'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
1606
				),
1607
				'before' => '<blockquote><cite><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></cite>',
1608
				'after' => '</blockquote>',
1609
				'trim' => 'both',
1610
				'block_level' => true,
1611
			),
1612
			array(
1613
				'tag' => 'quote',
1614
				'parameters' => array(
1615
					'author' => array('match' => '(.{1,192}?)'),
1616
				),
1617
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1618
				'after' => '</blockquote>',
1619
				'trim' => 'both',
1620
				'block_level' => true,
1621
			),
1622
			array(
1623
				'tag' => 'right',
1624
				'before' => '<div style="text-align: right;">',
1625
				'after' => '</div>',
1626
				'block_level' => true,
1627
			),
1628
			array(
1629
				'tag' => 'rtl',
1630
				'before' => '<bdo dir="rtl">',
1631
				'after' => '</bdo>',
1632
				'block_level' => true,
1633
			),
1634
			array(
1635
				'tag' => 's',
1636
				'before' => '<s>',
1637
				'after' => '</s>',
1638
			),
1639
			array(
1640
				'tag' => 'size',
1641
				'type' => 'unparsed_equals',
1642
				'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]',
1643
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1644
				'after' => '</span>',
1645
			),
1646
			array(
1647
				'tag' => 'size',
1648
				'type' => 'unparsed_equals',
1649
				'test' => '[1-7]\]',
1650
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1651
				'after' => '</span>',
1652
				'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...
1653
				{
1654
					$sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
1655
					$data = $sizes[$data] . 'em';
1656
				},
1657
			),
1658
			array(
1659
				'tag' => 'sub',
1660
				'before' => '<sub>',
1661
				'after' => '</sub>',
1662
			),
1663
			array(
1664
				'tag' => 'sup',
1665
				'before' => '<sup>',
1666
				'after' => '</sup>',
1667
			),
1668
			array(
1669
				'tag' => 'table',
1670
				'before' => '<table class="bbc_table">',
1671
				'after' => '</table>',
1672
				'trim' => 'inside',
1673
				'require_children' => array('tr'),
1674
				'block_level' => true,
1675
			),
1676
			array(
1677
				'tag' => 'td',
1678
				'before' => '<td>',
1679
				'after' => '</td>',
1680
				'require_parents' => array('tr'),
1681
				'trim' => 'outside',
1682
				'block_level' => true,
1683
				'disabled_before' => '',
1684
				'disabled_after' => '',
1685
			),
1686
			array(
1687
				'tag' => 'time',
1688
				'type' => 'unparsed_content',
1689
				'content' => '$1',
1690
				'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...
1691
				{
1692
					if (is_numeric($data))
1693
						$data = timeformat($data);
1694
					else
1695
						$tag['content'] = '[time]$1[/time]';
1696
				},
1697
			),
1698
			array(
1699
				'tag' => 'tr',
1700
				'before' => '<tr>',
1701
				'after' => '</tr>',
1702
				'require_parents' => array('table'),
1703
				'require_children' => array('td'),
1704
				'trim' => 'both',
1705
				'block_level' => true,
1706
				'disabled_before' => '',
1707
				'disabled_after' => '',
1708
			),
1709
			array(
1710
				'tag' => 'u',
1711
				'before' => '<u>',
1712
				'after' => '</u>',
1713
			),
1714
			array(
1715
				'tag' => 'url',
1716
				'type' => 'unparsed_content',
1717
				'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>',
1718 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...
1719
				{
1720
					$data = strtr($data, array('<br>' => ''));
1721
					$scheme = parse_url($data, PHP_URL_SCHEME);
1722
					if (empty($scheme))
1723
						$data = '//' . ltrim($data, ':/');
1724
				},
1725
			),
1726
			array(
1727
				'tag' => 'url',
1728
				'type' => 'unparsed_equals',
1729
				'quoted' => 'optional',
1730
				'before' => '<a href="$1" class="bbc_link" target="_blank">',
1731
				'after' => '</a>',
1732
				'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...
1733
				{
1734
					$scheme = parse_url($data, PHP_URL_SCHEME);
1735
					if (empty($scheme))
1736
						$data = '//' . ltrim($data, ':/');
1737
				},
1738
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1739
				'disabled_after' => ' ($1)',
1740
			),
1741
		);
1742
1743
		// Inside these tags autolink is not recommendable.
1744
		$no_autolink_tags = array(
1745
			'url',
1746
			'iurl',
1747
			'email',
1748
		);
1749
1750
		// Let mods add new BBC without hassle.
1751
		call_integration_hook('integrate_bbc_codes', array(&$codes, &$no_autolink_tags));
1752
1753
		// This is mainly for the bbc manager, so it's easy to add tags above.  Custom BBC should be added above this line.
1754
		if ($message === false)
1755
		{
1756
			if (isset($temp_bbc))
1757
				$bbc_codes = $temp_bbc;
1758
			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...
1759
				return strcmp($a['tag'], $b['tag']);
1760
			});
1761
			return $codes;
1762
		}
1763
1764
		// So the parser won't skip them.
1765
		$itemcodes = array(
1766
			'*' => 'disc',
1767
			'@' => 'disc',
1768
			'+' => 'square',
1769
			'x' => 'square',
1770
			'#' => 'square',
1771
			'o' => 'circle',
1772
			'O' => 'circle',
1773
			'0' => 'circle',
1774
		);
1775
		if (!isset($disabled['li']) && !isset($disabled['list']))
1776
		{
1777
			foreach ($itemcodes as $c => $dummy)
1778
				$bbc_codes[$c] = array();
1779
		}
1780
1781
		// Shhhh!
1782
		if (!isset($disabled['color']))
1783
		{
1784
			$codes[] = array(
1785
				'tag' => 'chrissy',
1786
				'before' => '<span style="color: #cc0099;">',
1787
				'after' => ' :-*</span>',
1788
			);
1789
			$codes[] = array(
1790
				'tag' => 'kissy',
1791
				'before' => '<span style="color: #cc0099;">',
1792
				'after' => ' :-*</span>',
1793
			);
1794
		}
1795
1796
		foreach ($codes as $code)
1797
		{
1798
			// Make it easier to process parameters later
1799
			if (!empty($code['parameters']))
1800
				ksort($code['parameters'], SORT_STRING);
1801
1802
			// If we are not doing every tag only do ones we are interested in.
1803
			if (empty($parse_tags) || in_array($code['tag'], $parse_tags))
1804
				$bbc_codes[substr($code['tag'], 0, 1)][] = $code;
1805
		}
1806
		$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...
1807
	}
1808
1809
	// Shall we take the time to cache this?
1810
	if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && isset($message[1000])) || isset($message[2400])) && empty($parse_tags))
1811
	{
1812
		// It's likely this will change if the message is modified.
1813
		$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']);
1814
1815
		if (($temp = cache_get_data($cache_key, 240)) != null)
1816
			return $temp;
1817
1818
		$cache_t = microtime();
1819
	}
1820
1821
	if ($smileys === 'print')
1822
	{
1823
		// [glow], [shadow], and [move] can't really be printed.
1824
		$disabled['glow'] = true;
1825
		$disabled['shadow'] = true;
1826
		$disabled['move'] = true;
1827
1828
		// Colors can't well be displayed... supposed to be black and white.
1829
		$disabled['color'] = true;
1830
		$disabled['black'] = true;
1831
		$disabled['blue'] = true;
1832
		$disabled['white'] = true;
1833
		$disabled['red'] = true;
1834
		$disabled['green'] = true;
1835
		$disabled['me'] = true;
1836
1837
		// Color coding doesn't make sense.
1838
		$disabled['php'] = true;
1839
1840
		// Links are useless on paper... just show the link.
1841
		$disabled['ftp'] = true;
1842
		$disabled['url'] = true;
1843
		$disabled['iurl'] = true;
1844
		$disabled['email'] = true;
1845
		$disabled['flash'] = true;
1846
1847
		// @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...
1848
		if (!isset($_GET['images']))
1849
			$disabled['img'] = true;
1850
1851
		// @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...
1852
	}
1853
1854
	$open_tags = array();
1855
	$message = strtr($message, array("\n" => '<br>'));
1856
1857
	$alltags = array();
1858
	foreach ($bbc_codes as $section) {
1859
		foreach ($section as $code) {
1860
			$alltags[] = $code['tag'];
1861
		}
1862
	}
1863
	$alltags_regex = '\b' . implode("\b|\b", array_unique($alltags)) . '\b';
1864
1865
	$pos = -1;
1866
	while ($pos !== false)
1867
	{
1868
		$last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
1869
		preg_match('~\[/?(?=' . $alltags_regex . ')~i', $message, $matches, PREG_OFFSET_CAPTURE, $pos + 1);
1870
		$pos = isset($matches[0][1]) ? $matches[0][1] : false;
1871
1872
		// Failsafe.
1873
		if ($pos === false || $last_pos > $pos)
1874
			$pos = strlen($message) + 1;
1875
1876
		// Can't have a one letter smiley, URL, or email! (sorry.)
1877
		if ($last_pos < $pos - 1)
1878
		{
1879
			// Make sure the $last_pos is not negative.
1880
			$last_pos = max($last_pos, 0);
1881
1882
			// Pick a block of data to do some raw fixing on.
1883
			$data = substr($message, $last_pos, $pos - $last_pos);
1884
1885
			// Take care of some HTML!
1886
			if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false)
1887
			{
1888
				$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);
1889
1890
				// <br> should be empty.
1891
				$empty_tags = array('br', 'hr');
1892
				foreach ($empty_tags as $tag)
1893
					$data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '<' . $tag . '>', $data);
1894
1895
				// b, u, i, s, pre... basic tags.
1896
				$closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote', 'strong');
1897
				foreach ($closable_tags as $tag)
1898
				{
1899
					$diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
1900
					$data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
1901
1902
					if ($diff > 0)
1903
						$data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
1904
				}
1905
1906
				// Do <img ...> - with security... action= -> action-.
1907
				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);
1908
				if (!empty($matches[0]))
1909
				{
1910
					$replaces = array();
1911
					foreach ($matches[2] as $match => $imgtag)
1912
					{
1913
						$alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
1914
1915
						// Remove action= from the URL - no funny business, now.
1916
						if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0)
1917
							$imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
1918
1919
						// Check if the image is larger than allowed.
1920
						if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height']))
1921
						{
1922
							list ($width, $height) = url_image_size($imgtag);
1923
1924 View Code Duplication
							if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width'])
1925
							{
1926
								$height = (int) (($modSettings['max_image_width'] * $height) / $width);
1927
								$width = $modSettings['max_image_width'];
1928
							}
1929
1930 View Code Duplication
							if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height'])
1931
							{
1932
								$width = (int) (($modSettings['max_image_height'] * $width) / $height);
1933
								$height = $modSettings['max_image_height'];
1934
							}
1935
1936
							// Set the new image tag.
1937
							$replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
1938
						}
1939
						else
1940
							$replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
1941
					}
1942
1943
					$data = strtr($data, $replaces);
1944
				}
1945
			}
1946
1947
			if (!empty($modSettings['autoLinkUrls']))
1948
			{
1949
				// Are we inside tags that should be auto linked?
1950
				$no_autolink_area = false;
1951
				if (!empty($open_tags))
1952
				{
1953
					foreach ($open_tags as $open_tag)
1954
						if (in_array($open_tag['tag'], $no_autolink_tags))
1955
							$no_autolink_area = true;
1956
				}
1957
1958
				// Don't go backwards.
1959
				// @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...
1960
				$lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
1961
				if ($pos < $lastAutoPos)
1962
					$no_autolink_area = true;
1963
				$lastAutoPos = $pos;
1964
1965
				if (!$no_autolink_area)
1966
				{
1967
					// Parse any URLs
1968
					if (!isset($disabled['url']) && strpos($data, '[url') === false)
1969
					{
1970
						$url_regex = '
1971
						(?:
1972
							# IRIs with a scheme (or at least an opening "//")
1973
							(?:
1974
								# URI scheme (or lack thereof for schemeless URLs)
1975
								(?:
1976
									# URL scheme and colon
1977
									\b[a-z][\w\-]+:
1978
									| # or
1979
									# A boundary followed by two slashes for schemeless URLs
1980
									(?<=^|\W)(?=//)
1981
								)
1982
1983
								# IRI "authority" chunk
1984
								(?:
1985
									# 2 slashes for IRIs with an "authority"
1986
									//
1987
									# then a domain name
1988
									(?:
1989
										# Either the reserved "localhost" domain name
1990
										localhost
1991
										| # or
1992
										# a run of Unicode domain name characters and a dot
1993
										[\p{L}\p{M}\p{N}\-.:@]+\.
1994
										# and then a TLD valid in the DNS or the reserved "local" TLD
1995
										(?:'. $modSettings['tld_regex'] .'|local)
1996
									)
1997
									# followed by a non-domain character or end of line
1998
									(?=[^\p{L}\p{N}\-.]|$)
1999
2000
									| # Or, if there is no "authority" per se (e.g. mailto: URLs) ...
2001
2002
									# a run of IRI characters
2003
									[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.:@]+[\p{L}\p{M}\p{N}]
2004
									# and then a dot and a closing IRI label
2005
									\.[\p{L}\p{M}\p{N}\-]+
2006
								)
2007
							)
2008
2009
							| # or
2010
2011
							# Naked domains (e.g. "example.com" in "Go to example.com for an example.")
2012
							(?:
2013
								# Preceded by start of line or a non-domain character
2014
								(?<=^|[^\p{L}\p{M}\p{N}\-:@])
2015
2016
								# A run of Unicode domain name characters (excluding [:@])
2017
								[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.]+[\p{L}\p{M}\p{N}]
2018
								# and then a dot and a valid TLD
2019
								\.' . $modSettings['tld_regex'] . '
2020
2021
								# Followed by either:
2022
								(?=
2023
									# end of line or a non-domain character (excluding [.:@])
2024
									$|[^\p{L}\p{N}\-]
2025
									| # or
2026
									# a dot followed by end of line or a non-domain character (excluding [.:@])
2027
									\.(?=$|[^\p{L}\p{N}\-])
2028
								)
2029
							)
2030
						)
2031
2032
						# IRI path, query, and fragment (if present)
2033
						(?:
2034
							# If any of these parts exist, must start with a single /
2035
							/
2036
2037
							# And then optionally:
2038
							(?:
2039
								# One or more of:
2040
								(?:
2041
									# a run of non-space, non-()<>
2042
									[^\s()<>]+
2043
									| # or
2044
									# balanced parens, up to 2 levels
2045
									\(([^\s()<>]+|(\([^\s()<>]+\)))*\)
2046
								)+
2047
2048
								# End with:
2049
								(?:
2050
									# balanced parens, up to 2 levels
2051
									\(([^\s()<>]+|(\([^\s()<>]+\)))*\)
2052
									| # or
2053
									# not a space or one of these punct char
2054
									[^\s`!()\[\]{};:\'".,<>?«»“”‘’/]
2055
									| # or
2056
									# a trailing slash (but not two in a row)
2057
									(?<!/)/
2058
								)
2059
							)?
2060
						)?
2061
						';
2062
2063
						$data = preg_replace_callback('~' . $url_regex . '~xi' . ($context['utf8'] ? 'u' : ''), function ($matches) {
2064
							$url = array_shift($matches);
2065
2066
							$scheme = parse_url($url, PHP_URL_SCHEME);
2067
2068
							if ($scheme == 'mailto')
2069
							{
2070
								$email_address = str_replace('mailto:', '', $url);
2071
								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...
2072
									return '[email=' . $email_address . ']' . $url . '[/email]';
2073
								else
2074
									return $url;
2075
							}
2076
2077
							// Are we linking a schemeless URL or naked domain name (e.g. "example.com")?
2078
							if (empty($scheme))
2079
								$fullUrl = '//' . ltrim($url, ':/');
2080
							else
2081
								$fullUrl = $url;
2082
2083
							return '[url=&quot;' . str_replace(array('[', ']'), array('&#91;', '&#93;'), $fullUrl) . '&quot;]' . $url . '[/url]';
2084
						}, $data);
2085
					}
2086
2087
					// Next, emails...  Must be careful not to step on enablePostHTML logic above...
2088
					if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false && stripos($data, 'mailto:') === false)
2089
					{
2090
						$email_regex = '
2091
						# Preceded by a non-domain character or start of line
2092
						(?<=^|[^\p{L}\p{M}\p{N}\-\.])
2093
2094
						# An email address
2095
						[\p{L}\p{M}\p{N}_\-.]{1,80}
2096
						@
2097
						[\p{L}\p{M}\p{N}\-.]+
2098
						\.
2099
						'. $modSettings['tld_regex'] . '
2100
2101
						# Followed by either:
2102
						(?=
2103
							# end of line or a non-domain character (excluding the dot)
2104
							$|[^\p{L}\p{M}\p{N}\-]
2105
							| # or
2106
							# a dot followed by end of line or a non-domain character
2107
							\.(?=$|[^\p{L}\p{M}\p{N}\-])
2108
						)';
2109
2110
						$data = preg_replace('~' . $email_regex . '~xi' . ($context['utf8'] ? 'u' : ''), '[email]$0[/email]', $data);
2111
					}
2112
				}
2113
			}
2114
2115
			$data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
2116
2117
			// If it wasn't changed, no copying or other boring stuff has to happen!
2118
			if ($data != substr($message, $last_pos, $pos - $last_pos))
2119
			{
2120
				$message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
2121
2122
				// Since we changed it, look again in case we added or removed a tag.  But we don't want to skip any.
2123
				$old_pos = strlen($data) + $last_pos;
2124
				$pos = strpos($message, '[', $last_pos);
2125
				$pos = $pos === false ? $old_pos : min($pos, $old_pos);
2126
			}
2127
		}
2128
2129
		// Are we there yet?  Are we there yet?
2130
		if ($pos >= strlen($message) - 1)
2131
			break;
2132
2133
		$tags = strtolower($message[$pos + 1]);
2134
2135
		if ($tags == '/' && !empty($open_tags))
2136
		{
2137
			$pos2 = strpos($message, ']', $pos + 1);
2138
			if ($pos2 == $pos + 2)
2139
				continue;
2140
2141
			$look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
2142
2143
			$to_close = array();
2144
			$block_level = null;
2145
2146
			do
2147
			{
2148
				$tag = array_pop($open_tags);
2149
				if (!$tag)
2150
					break;
2151
2152
				if (!empty($tag['block_level']))
2153
				{
2154
					// Only find out if we need to.
2155
					if ($block_level === false)
2156
					{
2157
						array_push($open_tags, $tag);
2158
						break;
2159
					}
2160
2161
					// The idea is, if we are LOOKING for a block level tag, we can close them on the way.
2162 View Code Duplication
					if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]]))
2163
					{
2164
						foreach ($bbc_codes[$look_for[0]] as $temp)
2165
							if ($temp['tag'] == $look_for)
2166
							{
2167
								$block_level = !empty($temp['block_level']);
2168
								break;
2169
							}
2170
					}
2171
2172
					if ($block_level !== true)
2173
					{
2174
						$block_level = false;
2175
						array_push($open_tags, $tag);
2176
						break;
2177
					}
2178
				}
2179
2180
				$to_close[] = $tag;
2181
			}
2182
			while ($tag['tag'] != $look_for);
2183
2184
			// Did we just eat through everything and not find it?
2185
			if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)))
2186
			{
2187
				$open_tags = $to_close;
2188
				continue;
2189
			}
2190
			elseif (!empty($to_close) && $tag['tag'] != $look_for)
2191
			{
2192 View Code Duplication
				if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]]))
2193
				{
2194
					foreach ($bbc_codes[$look_for[0]] as $temp)
2195
						if ($temp['tag'] == $look_for)
2196
						{
2197
							$block_level = !empty($temp['block_level']);
2198
							break;
2199
						}
2200
				}
2201
2202
				// We're not looking for a block level tag (or maybe even a tag that exists...)
2203
				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...
2204
				{
2205
					foreach ($to_close as $tag)
2206
						array_push($open_tags, $tag);
2207
					continue;
2208
				}
2209
			}
2210
2211
			foreach ($to_close as $tag)
2212
			{
2213
				$message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
2214
				$pos += strlen($tag['after']) + 2;
2215
				$pos2 = $pos - 1;
2216
2217
				// See the comment at the end of the big loop - just eating whitespace ;).
2218
				$whitespace_regex = '';
2219
				if (!empty($tag['block_level']))
2220
					$whitespace_regex .= '(&nbsp;|\s)*(<br>)?';
2221
				// Trim one line of whitespace after unnested tags, but all of it after nested ones
2222 View Code Duplication
				if (!empty($tag['trim']) && $tag['trim'] != 'inside')
2223
					$whitespace_regex .= empty($tag['require_parents']) ? '(&nbsp;|\s)*' : '(<br>|&nbsp;|\s)*';
2224
2225 View Code Duplication
				if (!empty($whitespace_regex) && preg_match('~' . $whitespace_regex . '~', substr($message, $pos), $matches) != 0)
2226
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2227
			}
2228
2229
			if (!empty($to_close))
2230
			{
2231
				$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...
2232
				$pos--;
2233
			}
2234
2235
			continue;
2236
		}
2237
2238
		// No tags for this character, so just keep going (fastest possible course.)
2239
		if (!isset($bbc_codes[$tags]))
2240
			continue;
2241
2242
		$inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
2243
		$tag = null;
2244
		foreach ($bbc_codes[$tags] as $possible)
2245
		{
2246
			$pt_strlen = strlen($possible['tag']);
2247
2248
			// Not a match?
2249
			if (strtolower(substr($message, $pos + 1, $pt_strlen)) != $possible['tag'])
2250
				continue;
2251
2252
			$next_c = $message[$pos + 1 + $pt_strlen];
2253
2254
			// A test validation?
2255
			if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + $pt_strlen + 1)) === 0)
2256
				continue;
2257
			// Do we want parameters?
2258
			elseif (!empty($possible['parameters']))
2259
			{
2260
				if ($next_c != ' ')
2261
					continue;
2262
			}
2263
			elseif (isset($possible['type']))
2264
			{
2265
				// Do we need an equal sign?
2266
				if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=')
2267
					continue;
2268
				// Maybe we just want a /...
2269
				if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + $pt_strlen, 2) != '/]' && substr($message, $pos + 1 + $pt_strlen, 3) != ' /]')
2270
					continue;
2271
				// An immediate ]?
2272
				if ($possible['type'] == 'unparsed_content' && $next_c != ']')
2273
					continue;
2274
			}
2275
			// No type means 'parsed_content', which demands an immediate ] without parameters!
2276
			elseif ($next_c != ']')
2277
				continue;
2278
2279
			// Check allowed tree?
2280
			if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents'])))
2281
				continue;
2282
			elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children']))
2283
				continue;
2284
			// If this is in the list of disallowed child tags, don't parse it.
2285
			elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children']))
2286
				continue;
2287
2288
			$pos1 = $pos + 1 + $pt_strlen + 1;
2289
2290
			// Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
2291
			if ($possible['tag'] == 'quote')
2292
			{
2293
				// Start with standard
2294
				$quote_alt = false;
2295
				foreach ($open_tags as $open_quote)
2296
				{
2297
					// Every parent quote this quote has flips the styling
2298
					if ($open_quote['tag'] == 'quote')
2299
						$quote_alt = !$quote_alt;
2300
				}
2301
				// Add a class to the quote to style alternating blockquotes
2302
				$possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
2303
			}
2304
2305
			// This is long, but it makes things much easier and cleaner.
2306
			if (!empty($possible['parameters']))
2307
			{
2308
				// Build a regular expression for each parameter for the current tag.
2309
				$preg = array();
2310
				foreach ($possible['parameters'] as $p => $info)
2311
					$preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . '\s*)' . (empty($info['optional']) ? '' : '?');
2312
2313
				// Extract the string that potentially holds our parameters.
2314
				$blob = preg_split('~\[/?(?:' . $alltags_regex . ')~i', substr($message, $pos));
2315
				$blobs = preg_split('~\]~i', $blob[1]);
2316
2317
				$splitters = implode('=|', array_keys($possible['parameters'])) . '=';
2318
2319
				// Progressively append more blobs until we find our parameters or run out of blobs
2320
				$blob_counter = 1;
2321
				while ($blob_counter <= count($blobs))
2322
				{
2323
2324
					$given_param_string = implode(']', array_slice($blobs, 0, $blob_counter++));
2325
2326
					$given_params = preg_split('~\s(?=(' . $splitters . '))~i', $given_param_string);
2327
					sort($given_params, SORT_STRING);
2328
2329
					$match = preg_match('~^' . implode('', $preg) . '$~i', implode(' ', $given_params), $matches) !== 0;
2330
2331
					if ($match)
2332
						$blob_counter = count($blobs) + 1;
2333
				}
2334
2335
				// Didn't match our parameter list, try the next possible.
2336
				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...
2337
					continue;
2338
2339
				$params = array();
2340
				for ($i = 1, $n = count($matches); $i < $n; $i += 2)
2341
				{
2342
					$key = strtok(ltrim($matches[$i]), '=');
2343
					if (isset($possible['parameters'][$key]['value']))
2344
						$params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
2345
					elseif (isset($possible['parameters'][$key]['validate']))
2346
						$params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
2347
					else
2348
						$params['{' . $key . '}'] = $matches[$i + 1];
2349
2350
					// Just to make sure: replace any $ or { so they can't interpolate wrongly.
2351
					$params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
2352
				}
2353
2354
				foreach ($possible['parameters'] as $p => $info)
2355
				{
2356
					if (!isset($params['{' . $p . '}']))
2357
						$params['{' . $p . '}'] = '';
2358
				}
2359
2360
				$tag = $possible;
2361
2362
				// Put the parameters into the string.
2363
				if (isset($tag['before']))
2364
					$tag['before'] = strtr($tag['before'], $params);
2365
				if (isset($tag['after']))
2366
					$tag['after'] = strtr($tag['after'], $params);
2367
				if (isset($tag['content']))
2368
					$tag['content'] = strtr($tag['content'], $params);
2369
2370
				$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...
2371
			}
2372
			else
2373
			{
2374
				$tag = $possible;
2375
				$params = array();
2376
			}
2377
			break;
2378
		}
2379
2380
		// Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
2381
		if ($smileys !== false && $tag === null && isset($itemcodes[$message[$pos + 1]]) && $message[$pos + 2] == ']' && !isset($disabled['list']) && !isset($disabled['li']))
2382
		{
2383
			if ($message[$pos + 1] == '0' && !in_array($message[$pos - 1], array(';', ' ', "\t", "\n", '>')))
2384
				continue;
2385
2386
			$tag = $itemcodes[$message[$pos + 1]];
2387
2388
			// First let's set up the tree: it needs to be in a list, or after an li.
2389
			if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li'))
2390
			{
2391
				$open_tags[] = array(
2392
					'tag' => 'list',
2393
					'after' => '</ul>',
2394
					'block_level' => true,
2395
					'require_children' => array('li'),
2396
					'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2397
				);
2398
				$code = '<ul class="bbc_list">';
2399
			}
2400
			// We're in a list item already: another itemcode?  Close it first.
2401
			elseif ($inside['tag'] == 'li')
2402
			{
2403
				array_pop($open_tags);
2404
				$code = '</li>';
2405
			}
2406
			else
2407
				$code = '';
2408
2409
			// Now we open a new tag.
2410
			$open_tags[] = array(
2411
				'tag' => 'li',
2412
				'after' => '</li>',
2413
				'trim' => 'outside',
2414
				'block_level' => true,
2415
				'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2416
			);
2417
2418
			// First, open the tag...
2419
			$code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>';
2420
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
2421
			$pos += strlen($code) - 1 + 2;
2422
2423
			// Next, find the next break (if any.)  If there's more itemcode after it, keep it going - otherwise close!
2424
			$pos2 = strpos($message, '<br>', $pos);
2425
			$pos3 = strpos($message, '[/', $pos);
2426
			if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false))
2427
			{
2428
				preg_match('~^(<br>|&nbsp;|\s|\[)+~', substr($message, $pos2 + 4), $matches);
2429
				$message = substr($message, 0, $pos2) . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . substr($message, $pos2);
2430
2431
				$open_tags[count($open_tags) - 2]['after'] = '</ul>';
2432
			}
2433
			// Tell the [list] that it needs to close specially.
2434
			else
2435
			{
2436
				// Move the li over, because we're not sure what we'll hit.
2437
				$open_tags[count($open_tags) - 1]['after'] = '';
2438
				$open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
2439
			}
2440
2441
			continue;
2442
		}
2443
2444
		// Implicitly close lists and tables if something other than what's required is in them.  This is needed for itemcode.
2445
		if ($tag === null && $inside !== null && !empty($inside['require_children']))
2446
		{
2447
			array_pop($open_tags);
2448
2449
			$message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
2450
			$pos += strlen($inside['after']) - 1 + 2;
2451
		}
2452
2453
		// No tag?  Keep looking, then.  Silly people using brackets without actual tags.
2454
		if ($tag === null)
2455
			continue;
2456
2457
		// Propagate the list to the child (so wrapping the disallowed tag won't work either.)
2458
		if (isset($inside['disallow_children']))
2459
			$tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
2460
2461
		// Is this tag disabled?
2462
		if (isset($disabled[$tag['tag']]))
2463
		{
2464
			if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content']))
2465
			{
2466
				$tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
2467
				$tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
2468
				$tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
2469
			}
2470
			elseif (isset($tag['disabled_before']) || isset($tag['disabled_after']))
2471
			{
2472
				$tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
2473
				$tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
2474
			}
2475
			else
2476
				$tag['content'] = $tag['disabled_content'];
2477
		}
2478
2479
		// we use this a lot
2480
		$tag_strlen = strlen($tag['tag']);
2481
2482
		// The only special case is 'html', which doesn't need to close things.
2483
		if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level']))
2484
		{
2485
			$n = count($open_tags) - 1;
2486
			while (empty($open_tags[$n]['block_level']) && $n >= 0)
2487
				$n--;
2488
2489
			// Close all the non block level tags so this tag isn't surrounded by them.
2490
			for ($i = count($open_tags) - 1; $i > $n; $i--)
2491
			{
2492
				$message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
2493
				$ot_strlen = strlen($open_tags[$i]['after']);
2494
				$pos += $ot_strlen + 2;
2495
				$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...
2496
2497
				// Trim or eat trailing stuff... see comment at the end of the big loop.
2498
				$whitespace_regex = '';
2499
				if (!empty($tag['block_level']))
2500
					$whitespace_regex .= '(&nbsp;|\s)*(<br>)?';
2501 View Code Duplication
				if (!empty($tag['trim']) && $tag['trim'] != 'inside')
2502
					$whitespace_regex .= empty($tag['require_parents']) ? '(&nbsp;|\s)*' : '(<br>|&nbsp;|\s)*';
2503 View Code Duplication
				if (!empty($whitespace_regex) && preg_match('~' . $whitespace_regex . '~', substr($message, $pos), $matches) != 0)
2504
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2505
2506
				array_pop($open_tags);
2507
			}
2508
		}
2509
2510
		// Can't read past the end of the message
2511
		$pos1 = min(strlen($message), $pos1);
2512
2513
		// No type means 'parsed_content'.
2514
		if (!isset($tag['type']))
2515
		{
2516
			// @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...
2517
			$open_tags[] = $tag;
2518
			$message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
2519
			$pos += strlen($tag['before']) - 1 + 2;
2520
		}
2521
		// Don't parse the content, just skip it.
2522
		elseif ($tag['type'] == 'unparsed_content')
2523
		{
2524
			$pos2 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos1);
2525
			if ($pos2 === false)
2526
				continue;
2527
2528
			$data = substr($message, $pos1, $pos2 - $pos1);
2529
2530
			if (!empty($tag['block_level']) && substr($data, 0, 4) == '<br>')
2531
				$data = substr($data, 4);
2532
2533
			if (isset($tag['validate']))
2534
				$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...
2535
2536
			$code = strtr($tag['content'], array('$1' => $data));
2537
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + $tag_strlen);
2538
2539
			$pos += strlen($code) - 1 + 2;
2540
			$last_pos = $pos + 1;
2541
2542
		}
2543
		// Don't parse the content, just skip it.
2544
		elseif ($tag['type'] == 'unparsed_equals_content')
2545
		{
2546
			// The value may be quoted for some tags - check.
2547 View Code Duplication
			if (isset($tag['quoted']))
2548
			{
2549
				$quoted = substr($message, $pos1, 6) == '&quot;';
2550
				if ($tag['quoted'] != 'optional' && !$quoted)
2551
					continue;
2552
2553
				if ($quoted)
2554
					$pos1 += 6;
2555
			}
2556
			else
2557
				$quoted = false;
2558
2559
			$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...
2560
			if ($pos2 === false)
2561
				continue;
2562
2563
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2564
			if ($pos3 === false)
2565
				continue;
2566
2567
			$data = array(
2568
				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...
2569
				substr($message, $pos1, $pos2 - $pos1)
2570
			);
2571
2572
			if (!empty($tag['block_level']) && substr($data[0], 0, 4) == '<br>')
2573
				$data[0] = substr($data[0], 4);
2574
2575
			// Validation for my parking, please!
2576
			if (isset($tag['validate']))
2577
				$tag['validate']($tag, $data, $disabled, $params);
2578
2579
			$code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
2580
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2581
			$pos += strlen($code) - 1 + 2;
2582
		}
2583
		// A closed tag, with no content or value.
2584
		elseif ($tag['type'] == 'closed')
2585
		{
2586
			$pos2 = strpos($message, ']', $pos);
2587
			$message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
2588
			$pos += strlen($tag['content']) - 1 + 2;
2589
		}
2590
		// This one is sorta ugly... :/.  Unfortunately, it's needed for flash.
2591
		elseif ($tag['type'] == 'unparsed_commas_content')
2592
		{
2593
			$pos2 = strpos($message, ']', $pos1);
2594
			if ($pos2 === false)
2595
				continue;
2596
2597
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2598
			if ($pos3 === false)
2599
				continue;
2600
2601
			// We want $1 to be the content, and the rest to be csv.
2602
			$data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
2603
			$data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
2604
2605
			if (isset($tag['validate']))
2606
				$tag['validate']($tag, $data, $disabled, $params);
2607
2608
			$code = $tag['content'];
2609 View Code Duplication
			foreach ($data as $k => $d)
2610
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2611
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2612
			$pos += strlen($code) - 1 + 2;
2613
		}
2614
		// This has parsed content, and a csv value which is unparsed.
2615
		elseif ($tag['type'] == 'unparsed_commas')
2616
		{
2617
			$pos2 = strpos($message, ']', $pos1);
2618
			if ($pos2 === false)
2619
				continue;
2620
2621
			$data = explode(',', substr($message, $pos1, $pos2 - $pos1));
2622
2623
			if (isset($tag['validate']))
2624
				$tag['validate']($tag, $data, $disabled, $params);
2625
2626
			// Fix after, for disabled code mainly.
2627 View Code Duplication
			foreach ($data as $k => $d)
2628
				$tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
2629
2630
			$open_tags[] = $tag;
2631
2632
			// 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...
2633
			$code = $tag['before'];
2634 View Code Duplication
			foreach ($data as $k => $d)
2635
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2636
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
2637
			$pos += strlen($code) - 1 + 2;
2638
		}
2639
		// A tag set to a value, parsed or not.
2640
		elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals')
2641
		{
2642
			// The value may be quoted for some tags - check.
2643 View Code Duplication
			if (isset($tag['quoted']))
2644
			{
2645
				$quoted = substr($message, $pos1, 6) == '&quot;';
2646
				if ($tag['quoted'] != 'optional' && !$quoted)
2647
					continue;
2648
2649
				if ($quoted)
2650
					$pos1 += 6;
2651
			}
2652
			else
2653
				$quoted = false;
2654
2655
			$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...
2656
			if ($pos2 === false)
2657
				continue;
2658
2659
			$data = substr($message, $pos1, $pos2 - $pos1);
2660
2661
			// Validation for my parking, please!
2662
			if (isset($tag['validate']))
2663
				$tag['validate']($tag, $data, $disabled, $params);
2664
2665
			// For parsed content, we must recurse to avoid security problems.
2666
			if ($tag['type'] != 'unparsed_equals')
2667
				$data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
2668
2669
			$tag['after'] = strtr($tag['after'], array('$1' => $data));
2670
2671
			$open_tags[] = $tag;
2672
2673
			$code = strtr($tag['before'], array('$1' => $data));
2674
			$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...
2675
			$pos += strlen($code) - 1 + 2;
2676
		}
2677
2678
		// If this is block level, eat any breaks after it.
2679
		if (!empty($tag['block_level']) && substr($message, $pos + 1, 4) == '<br>')
2680
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 5);
2681
2682
		// Are we trimming outside this tag?
2683
		if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos + 1), $matches) != 0)
2684
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
2685
	}
2686
2687
	// Close any remaining tags.
2688
	while ($tag = array_pop($open_tags))
2689
		$message .= "\n" . $tag['after'] . "\n";
2690
2691
	// Parse the smileys within the parts where it can be done safely.
2692
	if ($smileys === true)
2693
	{
2694
		$message_parts = explode("\n", $message);
2695
		for ($i = 0, $n = count($message_parts); $i < $n; $i += 2)
2696
			parsesmileys($message_parts[$i]);
2697
2698
		$message = implode('', $message_parts);
2699
	}
2700
2701
	// No smileys, just get rid of the markers.
2702
	else
2703
		$message = strtr($message, array("\n" => ''));
2704
2705
	if ($message !== '' && $message[0] === ' ')
2706
		$message = '&nbsp;' . substr($message, 1);
2707
2708
	// Cleanup whitespace.
2709
	$message = strtr($message, array('  ' => ' &nbsp;', "\r" => '', "\n" => '<br>', '<br> ' => '<br>&nbsp;', '&#13;' => "\n"));
2710
2711
	// Allow mods access to what parse_bbc created
2712
	call_integration_hook('integrate_post_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
2713
2714
	// Cache the output if it took some time...
2715
	if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05)
2716
		cache_put_data($cache_key, $message, 240);
2717
2718
	// If this was a force parse revert if needed.
2719
	if (!empty($parse_tags))
2720
	{
2721
		if (empty($temp_bbc))
2722
			$bbc_codes = array();
2723
		else
2724
		{
2725
			$bbc_codes = $temp_bbc;
2726
			unset($temp_bbc);
2727
		}
2728
	}
2729
2730
	return $message;
2731
}
2732
2733
/**
2734
 * Parse smileys in the passed message.
2735
 *
2736
 * The smiley parsing function which makes pretty faces appear :).
2737
 * If custom smiley sets are turned off by smiley_enable, the default set of smileys will be used.
2738
 * These are specifically not parsed in code tags [url=mailto:[email protected]]
2739
 * Caches the smileys from the database or array in memory.
2740
 * Doesn't return anything, but rather modifies message directly.
2741
 *
2742
 * @param string &$message The message to parse smileys in
2743
 */
2744
function parsesmileys(&$message)
2745
{
2746
	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...
2747
	static $smileyPregSearch = null, $smileyPregReplacements = array();
2748
2749
	// No smiley set at all?!
2750
	if ($user_info['smiley_set'] == 'none' || trim($message) == '')
2751
		return;
2752
2753
	// If smileyPregSearch hasn't been set, do it now.
2754
	if (empty($smileyPregSearch))
2755
	{
2756
		// Use the default smileys if it is disabled. (better for "portability" of smileys.)
2757
		if (empty($modSettings['smiley_enable']))
2758
		{
2759
			$smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)');
2760
			$smileysto = array('evil.gif', 'cheesy.gif', 'rolleyes.gif', 'angry.gif', 'laugh.gif', 'smiley.gif', 'wink.gif', 'grin.gif', 'sad.gif', 'shocked.gif', 'cool.gif', 'tongue.gif', 'huh.gif', 'embarrassed.gif', 'lipsrsealed.gif', 'kiss.gif', 'cry.gif', 'undecided.gif', 'azn.gif', 'afro.gif', 'police.gif', 'angel.gif');
2761
			$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'], '', '', '', '');
2762
		}
2763
		else
2764
		{
2765
			// Load the smileys in reverse order by length so they don't get parsed wrong.
2766
			if (($temp = cache_get_data('parsing_smileys', 480)) == null)
2767
			{
2768
				$result = $smcFunc['db_query']('', '
2769
					SELECT code, filename, description
2770
					FROM {db_prefix}smileys
2771
					ORDER BY LENGTH(code) DESC',
2772
					array(
2773
					)
2774
				);
2775
				$smileysfrom = array();
2776
				$smileysto = array();
2777
				$smileysdescs = array();
2778
				while ($row = $smcFunc['db_fetch_assoc']($result))
2779
				{
2780
					$smileysfrom[] = $row['code'];
2781
					$smileysto[] = $smcFunc['htmlspecialchars']($row['filename']);
2782
					$smileysdescs[] = $row['description'];
2783
				}
2784
				$smcFunc['db_free_result']($result);
2785
2786
				cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480);
2787
			}
2788
			else
2789
				list ($smileysfrom, $smileysto, $smileysdescs) = $temp;
2790
		}
2791
2792
		// The non-breaking-space is a complex thing...
2793
		$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0';
2794
2795
		// This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:[email protected]] doesn't parse the :D smiley)
2796
		$smileyPregReplacements = array();
2797
		$searchParts = array();
2798
		$smileys_path = $smcFunc['htmlspecialchars']($modSettings['smileys_url'] . '/' . $user_info['smiley_set'] . '/');
2799
2800
		for ($i = 0, $n = count($smileysfrom); $i < $n; $i++)
2801
		{
2802
			$specialChars = $smcFunc['htmlspecialchars']($smileysfrom[$i], ENT_QUOTES);
2803
			$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">';
2804
2805
			$smileyPregReplacements[$smileysfrom[$i]] = $smileyCode;
2806
2807
			$searchParts[] = preg_quote($smileysfrom[$i], '~');
2808
			if ($smileysfrom[$i] != $specialChars)
2809
			{
2810
				$smileyPregReplacements[$specialChars] = $smileyCode;
2811
				$searchParts[] = preg_quote($specialChars, '~');
2812
			}
2813
		}
2814
2815
		$smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|(?<![a-zA-Z0-9])\(|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~' . ($context['utf8'] ? 'u' : '');
2816
	}
2817
2818
	// Replace away!
2819
	$message = preg_replace_callback($smileyPregSearch,
2820
		function ($matches) use ($smileyPregReplacements)
2821
		{
2822
			return $smileyPregReplacements[$matches[1]];
2823
		}, $message);
2824
}
2825
2826
/**
2827
 * Highlight any code.
2828
 *
2829
 * Uses PHP's highlight_string() to highlight PHP syntax
2830
 * does special handling to keep the tabs in the code available.
2831
 * used to parse PHP code from inside [code] and [php] tags.
2832
 *
2833
 * @param string $code The code
2834
 * @return string The code with highlighted HTML.
2835
 */
2836
function highlight_php_code($code)
2837
{
2838
	// Remove special characters.
2839
	$code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", '<br>' => "\n", "\t" => 'SMF_TAB();', '&#91;' => '[')));
2840
2841
	$oldlevel = error_reporting(0);
2842
2843
	$buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true));
2844
2845
	error_reporting($oldlevel);
2846
2847
	// Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P.
2848
	$buffer = preg_replace('~SMF_TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer);
2849
2850
	return strtr($buffer, array('\'' => '&#039;', '<code>' => '', '</code>' => ''));
2851
}
2852
2853
/**
2854
 * Make sure the browser doesn't come back and repost the form data.
2855
 * Should be used whenever anything is posted.
2856
 *
2857
 * @param string $setLocation The URL to redirect them to
2858
 * @param bool $refresh Whether to use a meta refresh instead
2859
 * @param bool $permanent Whether to send a 301 Moved Permanently instead of a 302 Moved Temporarily
2860
 */
2861
function redirectexit($setLocation = '', $refresh = false, $permanent = false)
2862
{
2863
	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...
2864
2865
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
2866
	if (!empty($context['flush_mail']))
2867
		// @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...
2868
		AddMailQueue(true);
2869
2870
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
2871
2872
	if ($add)
2873
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
2874
2875
	// Put the session ID in.
2876
	if (defined('SID') && SID != '')
2877
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
2878
	// Keep that debug in their for template debugging!
2879 View Code Duplication
	elseif (isset($_GET['debug']))
2880
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
2881
2882
	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'])))
2883
	{
2884
		if (defined('SID') && SID != '')
2885
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~',
2886
				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...
2887
				{
2888
					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...
2889
				}, $setLocation);
2890 View Code Duplication
		else
2891
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~',
2892
				function ($m) use ($scripturl)
2893
				{
2894
					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...
2895
				}, $setLocation);
2896
	}
2897
2898
	// Maybe integrations want to change where we are heading?
2899
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh, &$permanent));
2900
2901
	// Set the header.
2902
	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 $_GET, and $request is assigned in proxy.php on line 98
  1. Read from $_GET, and $request is assigned
    in proxy.php on line 98
  2. $request is passed to redirectexit()
    in proxy.php on line 116
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  2. Path: Read from $_POST, and $_POST['start_date'] is passed through date_parse(), and $d is assigned in Sources/Calendar.php on line 356
  1. Read from $_POST, and $_POST['start_date'] is passed through date_parse(), and $d is assigned
    in Sources/Calendar.php on line 356
  2. $month is assigned
    in Sources/Calendar.php on line 358
  3. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  3. Path: Read from $_POST, and $_POST['start_datetime'] is passed through date_parse(), and $d is assigned in Sources/Calendar.php on line 363
  1. Read from $_POST, and $_POST['start_datetime'] is passed through date_parse(), and $d is assigned
    in Sources/Calendar.php on line 363
  2. $month is assigned
    in Sources/Calendar.php on line 365
  3. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  4. Path: Read from $_POST, and $month is assigned in Sources/Calendar.php on line 372
  1. Read from $_POST, and $month is assigned
    in Sources/Calendar.php on line 372
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  5. Path: Read from $_POST, and $year is assigned in Sources/Calendar.php on line 371
  1. Read from $_POST, and $year is assigned
    in Sources/Calendar.php on line 371
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  6. Path: Read from $_POST, and $day is assigned in Sources/Calendar.php on line 373
  1. Read from $_POST, and $day is assigned
    in Sources/Calendar.php on line 373
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  7. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Display.php on line 1909
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Display.php on line 1909
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  8. Path: Read from $_REQUEST, and !empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Display.php on line 2017
  1. Read from $_REQUEST, and !empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Display.php on line 2017
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  9. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/Load.php on line 809
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/Load.php on line 809
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  10. Path: Read from $_GET, and $_GET is passed through each(), and $k is assigned in Sources/Load.php on line 1852
  1. Read from $_GET, and $_GET is passed through each(), and $k is assigned
    in Sources/Load.php on line 1852
  2. 'wwwRedirect;' . $k . '=' . $v is passed to redirectexit()
    in Sources/Load.php on line 1855
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 1031
  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 1031
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 1035
  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 1035
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 1071
  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 1071
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 1256
  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 1256
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 784
  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 784
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  32. Path: Read from $_REQUEST, and $redirect_url is assigned in Sources/MessageIndex.php on line 813
  1. Read from $_REQUEST, and $redirect_url is assigned
    in Sources/MessageIndex.php on line 813
  2. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 866
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  33. Path: Read from $_POST, and $redirect_url is assigned in Sources/MessageIndex.php on line 823
  1. Read from $_POST, and $redirect_url is assigned
    in Sources/MessageIndex.php on line 823
  2. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 866
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  34. Path: Read from $_REQUEST, and $_SESSION is assigned in Sources/MessageIndex.php on line 794
  1. Read from $_REQUEST, and $_SESSION is assigned
    in Sources/MessageIndex.php on line 794
  2. $redirect_url is assigned
    in Sources/MessageIndex.php on line 823
  3. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 866
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  42. Path: Read from $_REQUEST, and $_REQUEST[$recipientType] is passed through strtr(), and $recipientString is assigned in Sources/PersonalMessage.php on line 2291
  1. Read from $_REQUEST, and $_REQUEST[$recipientType] is passed through strtr(), and $recipientString is assigned
    in Sources/PersonalMessage.php on line 2291
  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 2294
  3. $namesNotFound is assigned
    in Sources/PersonalMessage.php on line 2309
  4. $name is assigned
    in Sources/PersonalMessage.php on line 2357
  5. $name is passed through sprintf(), and $context is assigned
    in Sources/PersonalMessage.php on line 2358
  6. $context is assigned
    in Sources/PersonalMessage.php on line 2459
  7. $context['current_label_redirect'] is passed to redirectexit()
    in Sources/PersonalMessage.php on line 2499
  8. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  48. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/Post.php on line 2259
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/Post.php on line 2259
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  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 2902
  55. Path: Read from $_POST, and 'topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit() in Sources/ReportToMod.php on line 274
  1. Read from $_POST, and 'topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 274
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 346
  1. Read from $_POST, and 'reportsent;topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 346
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  57. Path: Read from $_POST, and 'action=profile;u=' . $_POST['u'] is passed to redirectexit() in Sources/ReportToMod.php on line 398
  1. Read from $_POST, and 'action=profile;u=' . $_POST['u'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 398
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  58. Path: Read from $_POST, and $_POST['u'] is passed to reportUser() in Sources/ReportToMod.php on line 221
  1. Read from $_POST, and $_POST['u'] is passed to reportUser()
    in Sources/ReportToMod.php on line 221
  2. 'reportsent;action=profile;u=' . $id_member is passed to redirectexit()
    in Sources/ReportToMod.php on line 467
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 2902
  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 2902
  61. Path: Read from $_REQUEST, and $_REQUEST['topics'] is passed to MergeExecute() in Sources/MessageIndex.php on line 876
  1. Read from $_REQUEST, and $_REQUEST['topics'] is passed to MergeExecute()
    in Sources/MessageIndex.php on line 876
  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 2902
  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 2902
  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 749
  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 749
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 1785
  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 1785
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2902
  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 2902
  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 2902

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...
2903
2904
	// Debugging.
2905
	if (isset($db_show_debug) && $db_show_debug === true)
2906
		$_SESSION['debug_redirect'] = $db_cache;
2907
2908
	obExit(false);
2909
}
2910
2911
/**
2912
 * Ends execution.  Takes care of template loading and remembering the previous URL.
2913
 * @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...
2914
 * @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...
2915
 * @param bool $from_index Whether we're coming from the board index
2916
 * @param bool $from_fatal_error Whether we're coming from a fatal error
2917
 */
2918
function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false)
2919
{
2920
	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...
2921
	static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false;
2922
2923
	// Attempt to prevent a recursive loop.
2924
	++$level;
2925
	if ($level > 1 && !$from_fatal_error && !$has_fatal_error)
2926
		exit;
2927
	if ($from_fatal_error)
2928
		$has_fatal_error = true;
2929
2930
	// Clear out the stat cache.
2931
	trackStats();
2932
2933
	// If we have mail to send, send it.
2934
	if (!empty($context['flush_mail']))
2935
		// @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...
2936
		AddMailQueue(true);
2937
2938
	$do_header = $header === null ? !$header_done : $header;
2939
	if ($do_footer === null)
2940
		$do_footer = $do_header;
2941
2942
	// Has the template/header been done yet?
2943
	if ($do_header)
2944
	{
2945
		// Was the page title set last minute? Also update the HTML safe one.
2946
		if (!empty($context['page_title']) && empty($context['page_title_html_safe']))
2947
			$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
2948
2949
		// Start up the session URL fixer.
2950
		ob_start('ob_sessrewrite');
2951
2952
		if (!empty($settings['output_buffers']) && is_string($settings['output_buffers']))
2953
			$buffers = explode(',', $settings['output_buffers']);
2954
		elseif (!empty($settings['output_buffers']))
2955
			$buffers = $settings['output_buffers'];
2956
		else
2957
			$buffers = array();
2958
2959
		if (isset($modSettings['integrate_buffer']))
2960
			$buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers);
2961
2962
		if (!empty($buffers))
2963
			foreach ($buffers as $function)
2964
			{
2965
				$call = call_helper($function, true);
2966
2967
				// Is it valid?
2968
				if (!empty($call))
2969
					ob_start($call);
2970
			}
2971
2972
		// Display the screen in the logical order.
2973
		template_header();
2974
		$header_done = true;
2975
	}
2976
	if ($do_footer)
2977
	{
2978
		loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main');
2979
2980
		// Anything special to put out?
2981
		if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml']))
2982
			echo $context['insert_after_template'];
2983
2984
		// Just so we don't get caught in an endless loop of errors from the footer...
2985
		if (!$footer_done)
2986
		{
2987
			$footer_done = true;
2988
			template_footer();
2989
2990
			// (since this is just debugging... it's okay that it's after </html>.)
2991
			if (!isset($_REQUEST['xml']))
2992
				displayDebug();
2993
		}
2994
	}
2995
2996
	// Remember this URL in case someone doesn't like sending HTTP_REFERER.
2997
	if (strpos($_SERVER['REQUEST_URL'], 'action=dlattach') === false && strpos($_SERVER['REQUEST_URL'], 'action=viewsmfile') === false)
2998
		$_SESSION['old_url'] = $_SERVER['REQUEST_URL'];
2999
3000
	// For session check verification.... don't switch browsers...
3001
	$_SESSION['USER_AGENT'] = empty($_SERVER['HTTP_USER_AGENT']) ? '' : $_SERVER['HTTP_USER_AGENT'];
3002
3003
	// Hand off the output to the portal, etc. we're integrated with.
3004
	call_integration_hook('integrate_exit', array($do_footer));
3005
3006
	// Don't exit if we're coming from index.php; that will pass through normally.
3007
	if (!$from_index)
3008
		exit;
3009
}
3010
3011
/**
3012
 * Get the size of a specified image with better error handling.
3013
 * @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...
3014
 * Uses getimagesize() to determine the size of a file.
3015
 * Attempts to connect to the server first so it won't time out.
3016
 *
3017
 * @param string $url The URL of the image
3018
 * @return array|false The image size as array (width, height), or false on failure
3019
 */
3020
function url_image_size($url)
3021
{
3022
	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...
3023
3024
	// Make sure it is a proper URL.
3025
	$url = str_replace(' ', '%20', $url);
3026
3027
	// Can we pull this from the cache... please please?
3028
	if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null)
3029
		return $temp;
3030
	$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...
3031
3032
	// Get the host to pester...
3033
	preg_match('~^\w+://(.+?)/(.*)$~', $url, $match);
3034
3035
	// Can't figure it out, just try the image size.
3036
	if ($url == '' || $url == 'http://' || $url == 'https://')
3037
	{
3038
		return false;
3039
	}
3040
	elseif (!isset($match[1]))
3041
	{
3042
		$size = @getimagesize($url);
3043
	}
3044
	else
3045
	{
3046
		// Try to connect to the server... give it half a second.
3047
		$temp = 0;
3048
		$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...
3049
3050
		// 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...
3051
		if ($fp != false)
3052
		{
3053
			// Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.)
3054
			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");
3055
3056
			// Read in the HTTP/1.1 or whatever.
3057
			$test = substr(fgets($fp, 11), -1);
3058
			fclose($fp);
3059
3060
			// See if it returned a 404/403 or something.
3061
			if ($test < 4)
3062
			{
3063
				$size = @getimagesize($url);
3064
3065
				// This probably means allow_url_fopen is off, let's try GD.
3066
				if ($size === false && function_exists('imagecreatefromstring'))
3067
				{
3068
					include_once($sourcedir . '/Subs-Package.php');
3069
3070
					// It's going to hate us for doing this, but another request...
3071
					$image = @imagecreatefromstring(fetch_web_data($url));
3072
					if ($image !== false)
3073
					{
3074
						$size = array(imagesx($image), imagesy($image));
3075
						imagedestroy($image);
3076
					}
3077
				}
3078
			}
3079
		}
3080
	}
3081
3082
	// If we didn't get it, we failed.
3083
	if (!isset($size))
3084
		$size = false;
3085
3086
	// If this took a long time, we may never have to do it again, but then again we might...
3087 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.8)
3088
		cache_put_data('url_image_size-' . md5($url), $size, 240);
3089
3090
	// Didn't work.
3091
	return $size;
3092
}
3093
3094
/**
3095
 * Sets up the basic theme context stuff.
3096
 * @param bool $forceload Whether to load the theme even if it's already loaded
3097
 */
3098
function setupThemeContext($forceload = false)
3099
{
3100
	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...
3101
	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...
3102
	static $loaded = false;
3103
3104
	// Under SSI this function can be called more then once.  That can cause some problems.
3105
	//   So only run the function once unless we are forced to run it again.
3106
	if ($loaded && !$forceload)
3107
		return;
3108
3109
	$loaded = true;
3110
3111
	$context['in_maintenance'] = !empty($maintenance);
3112
	$context['current_time'] = timeformat(time(), false);
3113
	$context['current_action'] = isset($_GET['action']) ? $smcFunc['htmlspecialchars']($_GET['action']) : '';
3114
3115
	// Get some news...
3116
	$context['news_lines'] = array_filter(explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news'])))));
3117
	for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++)
3118
	{
3119
		if (trim($context['news_lines'][$i]) == '')
3120
			continue;
3121
3122
		// Clean it up for presentation ;).
3123
		$context['news_lines'][$i] = parse_bbc(stripslashes(trim($context['news_lines'][$i])), true, 'news' . $i);
3124
	}
3125
	if (!empty($context['news_lines']))
3126
		$context['random_news_line'] = $context['news_lines'][mt_rand(0, count($context['news_lines']) - 1)];
3127
3128
	if (!$user_info['is_guest'])
3129
	{
3130
		$context['user']['messages'] = &$user_info['messages'];
3131
		$context['user']['unread_messages'] = &$user_info['unread_messages'];
3132
		$context['user']['alerts'] = &$user_info['alerts'];
3133
3134
		// Personal message popup...
3135
		if ($user_info['unread_messages'] > (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0))
3136
			$context['user']['popup_messages'] = true;
3137
		else
3138
			$context['user']['popup_messages'] = false;
3139
		$_SESSION['unread_messages'] = $user_info['unread_messages'];
3140
3141
		if (allowedTo('moderate_forum'))
3142
			$context['unapproved_members'] = (!empty($modSettings['registration_method']) && ($modSettings['registration_method'] == 2 || (!empty($modSettings['coppaType']) && $modSettings['coppaType'] == 2))) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0;
3143
3144
		$context['user']['avatar'] = array();
3145
3146
		// Check for gravatar first since we might be forcing them...
3147
		if (($modSettings['gravatarEnabled'] && substr($user_info['avatar']['url'], 0, 11) == 'gravatar://') || !empty($modSettings['gravatarOverride']))
3148
		{
3149
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($user_info['avatar']['url'], 'gravatar://') && strlen($user_info['avatar']['url']) > 11)
3150
				$context['user']['avatar']['href'] = get_gravatar_url($smcFunc['substr']($user_info['avatar']['url'], 11));
3151
			else
3152
				$context['user']['avatar']['href'] = get_gravatar_url($user_info['email']);
3153
		}
3154
		// Uploaded?
3155
		elseif ($user_info['avatar']['url'] == '' && !empty($user_info['avatar']['id_attach']))
3156
			$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';
3157
		// Full URL?
3158
		elseif (strpos($user_info['avatar']['url'], 'http://') === 0 || strpos($user_info['avatar']['url'], 'https://') === 0)
3159
			$context['user']['avatar']['href'] = $user_info['avatar']['url'];
3160
		// Otherwise we assume it's server stored.
3161
		elseif ($user_info['avatar']['url'] != '')
3162
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/' . $smcFunc['htmlspecialchars']($user_info['avatar']['url']);
3163
		// No avatar at all? Fine, we have a big fat default avatar ;)
3164
		else
3165
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/default.png';
3166
3167
		if (!empty($context['user']['avatar']))
3168
			$context['user']['avatar']['image'] = '<img src="' . $context['user']['avatar']['href'] . '" alt="" class="avatar">';
3169
3170
		// Figure out how long they've been logged in.
3171
		$context['user']['total_time_logged_in'] = array(
3172
			'days' => floor($user_info['total_time_logged_in'] / 86400),
3173
			'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600),
3174
			'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60)
3175
		);
3176
	}
3177
	else
3178
	{
3179
		$context['user']['messages'] = 0;
3180
		$context['user']['unread_messages'] = 0;
3181
		$context['user']['avatar'] = array();
3182
		$context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0);
3183
		$context['user']['popup_messages'] = false;
3184
3185
		if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1)
3186
			$txt['welcome_guest'] .= $txt['welcome_guest_activate'];
3187
3188
		// If we've upgraded recently, go easy on the passwords.
3189
		if (!empty($modSettings['disableHashTime']) && ($modSettings['disableHashTime'] == 1 || time() < $modSettings['disableHashTime']))
3190
			$context['disable_login_hashing'] = true;
3191
	}
3192
3193
	// Setup the main menu items.
3194
	setupMenuContext();
3195
3196
	// This is here because old index templates might still use it.
3197
	$context['show_news'] = !empty($settings['enable_news']);
3198
3199
	// This is done to allow theme authors to customize it as they want.
3200
	$context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != 'pm');
3201
3202
	// 2.1+: Add the PM popup here instead. Theme authors can still override it simply by editing/removing the 'fPmPopup' in the array.
3203
	if ($context['show_pm_popup'])
3204
		addInlineJavaScript('
3205
		jQuery(document).ready(function($) {
3206
			new smc_Popup({
3207
				heading: ' . JavaScriptEscape($txt['show_personal_messages_heading']) . ',
3208
				content: ' . JavaScriptEscape(sprintf($txt['show_personal_messages'], $context['user']['unread_messages'], $scripturl . '?action=pm')) . ',
3209
				icon_class: \'generic_icons mail_new\'
3210
			});
3211
		});');
3212
3213
	// Add a generic "Are you sure?" confirmation message.
3214
	addInlineJavaScript('
3215
	var smf_you_sure =' . JavaScriptEscape($txt['quickmod_confirm']) .';');
3216
3217
	// Now add the capping code for avatars.
3218
	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')
3219
		addInlineCss('
3220
img.avatar { max-width: ' . $modSettings['avatar_max_width_external'] . 'px; max-height: ' . $modSettings['avatar_max_height_external'] . 'px; }');
3221
3222
	// This looks weird, but it's because BoardIndex.php references the variable.
3223
	$context['common_stats']['latest_member'] = array(
3224
		'id' => $modSettings['latestMember'],
3225
		'name' => $modSettings['latestRealName'],
3226
		'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'],
3227
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $modSettings['latestRealName'] . '</a>',
3228
	);
3229
	$context['common_stats'] = array(
3230
		'total_posts' => comma_format($modSettings['totalMessages']),
3231
		'total_topics' => comma_format($modSettings['totalTopics']),
3232
		'total_members' => comma_format($modSettings['totalMembers']),
3233
		'latest_member' => $context['common_stats']['latest_member'],
3234
	);
3235
	$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']);
3236
3237
	if (empty($settings['theme_version']))
3238
		addJavaScriptVar('smf_scripturl', $scripturl);
3239
3240
	if (!isset($context['page_title']))
3241
		$context['page_title'] = '';
3242
3243
	// Set some specific vars.
3244
	$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
3245
	$context['meta_keywords'] = !empty($modSettings['meta_keywords']) ? $smcFunc['htmlspecialchars']($modSettings['meta_keywords']) : '';
3246
3247
	// Content related meta tags, including Open Graph
3248
	$context['meta_tags'][] = array('property' => 'og:site_name', 'content' => $context['forum_name']);
3249
	$context['meta_tags'][] = array('property' => 'og:title', 'content' => $context['page_title_html_safe']);
3250
3251
	if (!empty($context['meta_keywords']))
3252
		$context['meta_tags'][] = array('name' => 'keywords', 'content' => $context['meta_keywords']);
3253
3254
	if (!empty($context['canonical_url']))
3255
		$context['meta_tags'][] = array('property' => 'og:url', 'content' => $context['canonical_url']);
3256
3257
	if (!empty($settings['og_image']))
3258
		$context['meta_tags'][] = array('property' => 'og:image', 'content' => $settings['og_image']);
3259
3260
	if (!empty($context['meta_description']))
3261
	{
3262
		$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['meta_description']);
3263
		$context['meta_tags'][] = array('name' => 'description', 'content' => $context['meta_description']);
3264
	}
3265
	else
3266
	{
3267
		$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['page_title_html_safe']);
3268
		$context['meta_tags'][] = array('name' => 'description', 'content' => $context['page_title_html_safe']);
3269
	}
3270
3271
	call_integration_hook('integrate_theme_context');
3272
}
3273
3274
/**
3275
 * Helper function to set the system memory to a needed value
3276
 * - If the needed memory is greater than current, will attempt to get more
3277
 * - if in_use is set to true, will also try to take the current memory usage in to account
3278
 *
3279
 * @param string $needed The amount of memory to request, if needed, like 256M
3280
 * @param bool $in_use Set to true to account for current memory usage of the script
3281
 * @return boolean True if we have at least the needed memory
3282
 */
3283
function setMemoryLimit($needed, $in_use = false)
3284
{
3285
	// everything in bytes
3286
	$memory_current = memoryReturnBytes(ini_get('memory_limit'));
3287
	$memory_needed = memoryReturnBytes($needed);
3288
3289
	// should we account for how much is currently being used?
3290
	if ($in_use)
3291
		$memory_needed += function_exists('memory_get_usage') ? memory_get_usage() : (2 * 1048576);
3292
3293
	// if more is needed, request it
3294
	if ($memory_current < $memory_needed)
3295
	{
3296
		@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...
3297
		$memory_current = memoryReturnBytes(ini_get('memory_limit'));
3298
	}
3299
3300
	$memory_current = max($memory_current, memoryReturnBytes(get_cfg_var('memory_limit')));
3301
3302
	// return success or not
3303
	return (bool) ($memory_current >= $memory_needed);
3304
}
3305
3306
/**
3307
 * Helper function to convert memory string settings to bytes
3308
 *
3309
 * @param string $val The byte string, like 256M or 1G
3310
 * @return integer The string converted to a proper integer in bytes
3311
 */
3312
function memoryReturnBytes($val)
3313
{
3314
	if (is_integer($val))
3315
		return $val;
3316
3317
	// Separate the number from the designator
3318
	$val = trim($val);
3319
	$num = intval(substr($val, 0, strlen($val) - 1));
3320
	$last = strtolower(substr($val, -1));
3321
3322
	// convert to bytes
3323
	switch ($last)
3324
	{
3325
		case 'g':
3326
			$num *= 1024;
3327
		case 'm':
3328
			$num *= 1024;
3329
		case 'k':
3330
			$num *= 1024;
3331
	}
3332
	return $num;
3333
}
3334
3335
/**
3336
 * The header template
3337
 */
3338
function template_header()
3339
{
3340
	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...
3341
3342
	setupThemeContext();
3343
3344
	// Print stuff to prevent caching of pages (except on attachment errors, etc.)
3345
	if (empty($context['no_last_modified']))
3346
	{
3347
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
3348
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3349
3350
		// Are we debugging the template/html content?
3351
		if (!isset($_REQUEST['xml']) && isset($_GET['debug']) && !isBrowser('ie'))
3352
			header('Content-Type: application/xhtml+xml');
3353 View Code Duplication
		elseif (!isset($_REQUEST['xml']))
3354
			header('Content-Type: text/html; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3355
	}
3356
3357
	header('Content-Type: text/' . (isset($_REQUEST['xml']) ? 'xml' : 'html') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3358
3359
	// We need to splice this in after the body layer, or after the main layer for older stuff.
3360
	if ($context['in_maintenance'] && $context['user']['is_admin'])
3361
	{
3362
		$position = array_search('body', $context['template_layers']);
3363
		if ($position === false)
3364
			$position = array_search('main', $context['template_layers']);
3365
3366
		if ($position !== false)
3367
		{
3368
			$before = array_slice($context['template_layers'], 0, $position + 1);
3369
			$after = array_slice($context['template_layers'], $position + 1);
3370
			$context['template_layers'] = array_merge($before, array('maint_warning'), $after);
3371
		}
3372
	}
3373
3374
	$checked_securityFiles = false;
3375
	$showed_banned = false;
3376
	foreach ($context['template_layers'] as $layer)
3377
	{
3378
		loadSubTemplate($layer . '_above', true);
3379
3380
		// May seem contrived, but this is done in case the body and main layer aren't there...
3381
		if (in_array($layer, array('body', 'main')) && allowedTo('admin_forum') && !$user_info['is_guest'] && !$checked_securityFiles)
3382
		{
3383
			$checked_securityFiles = true;
3384
3385
			$securityFiles = array('install.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~');
3386
3387
			// Add your own files.
3388
			call_integration_hook('integrate_security_files', array(&$securityFiles));
3389
3390
			foreach ($securityFiles as $i => $securityFile)
3391
			{
3392
				if (!file_exists($boarddir . '/' . $securityFile))
3393
					unset($securityFiles[$i]);
3394
			}
3395
3396
			// We are already checking so many files...just few more doesn't make any difference! :P
3397
			if (!empty($modSettings['currentAttachmentUploadDir']))
3398
				$path = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
3399
3400
			else
3401
				$path = $modSettings['attachmentUploadDir'];
3402
3403
			secureDirectory($path, true);
3404
			secureDirectory($cachedir);
3405
3406
			// If agreement is enabled, at least the english version shall exists
3407
			if ($modSettings['requireAgreement'])
3408
				$agreement = !file_exists($boarddir . '/agreement.txt');
3409
3410
			if (!empty($securityFiles) || (!empty($modSettings['cache_enable']) && !is_writable($cachedir)) || !empty($agreement))
3411
			{
3412
				echo '
3413
		<div class="errorbox">
3414
			<p class="alert">!!</p>
3415
			<h3>', empty($securityFiles) ? $txt['generic_warning'] : $txt['security_risk'], '</h3>
3416
			<p>';
3417
3418
				foreach ($securityFiles as $securityFile)
3419
				{
3420
					echo '
3421
				', $txt['not_removed'], '<strong>', $securityFile, '</strong>!<br>';
3422
3423
					if ($securityFile == 'Settings.php~' || $securityFile == 'Settings_bak.php~')
3424
						echo '
3425
				', sprintf($txt['not_removed_extra'], $securityFile, substr($securityFile, 0, -1)), '<br>';
3426
				}
3427
3428
				if (!empty($modSettings['cache_enable']) && !is_writable($cachedir))
3429
					echo '
3430
				<strong>', $txt['cache_writable'], '</strong><br>';
3431
3432
				if (!empty($agreement))
3433
					echo '
3434
				<strong>', $txt['agreement_missing'], '</strong><br>';
3435
3436
				echo '
3437
			</p>
3438
		</div>';
3439
			}
3440
		}
3441
		// If the user is banned from posting inform them of it.
3442
		elseif (in_array($layer, array('main', 'body')) && isset($_SESSION['ban']['cannot_post']) && !$showed_banned)
3443
		{
3444
			$showed_banned = true;
3445
			echo '
3446
				<div class="windowbg alert" style="margin: 2ex; padding: 2ex; border: 2px dashed red;">
3447
					', sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']);
3448
3449
			if (!empty($_SESSION['ban']['cannot_post']['reason']))
3450
				echo '
3451
					<div style="padding-left: 4ex; padding-top: 1ex;">', $_SESSION['ban']['cannot_post']['reason'], '</div>';
3452
3453
			if (!empty($_SESSION['ban']['expire_time']))
3454
				echo '
3455
					<div>', sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)), '</div>';
3456
			else
3457
				echo '
3458
					<div>', $txt['your_ban_expires_never'], '</div>';
3459
3460
			echo '
3461
				</div>';
3462
		}
3463
	}
3464
}
3465
3466
/**
3467
 * Show the copyright.
3468
 */
3469
function theme_copyright()
3470
{
3471
	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...
3472
3473
	// Don't display copyright for things like SSI.
3474
	if (!isset($forum_version) || !isset($software_year))
3475
		return;
3476
3477
	// Put in the version...
3478
	printf($forum_copyright, $forum_version, $software_year);
3479
}
3480
3481
/**
3482
 * The template footer
3483
 */
3484
function template_footer()
3485
{
3486
	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...
3487
3488
	// Show the load time?  (only makes sense for the footer.)
3489
	$context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']);
3490
	$context['load_time'] = round(microtime(true) - $time_start, 3);
3491
	$context['load_queries'] = $db_count;
3492
3493
	foreach (array_reverse($context['template_layers']) as $layer)
3494
		loadSubTemplate($layer . '_below', true);
3495
}
3496
3497
/**
3498
 * Output the Javascript files
3499
 * 	- tabbing in this function is to make the HTML source look good proper
3500
 *  - if defered is set function will output all JS (source & inline) set to load at page end
3501
 *
3502
 * @param bool $do_deferred If true will only output the deferred JS (the stuff that goes right before the closing body tag)
3503
 */
3504
function template_javascript($do_deferred = false)
3505
{
3506
	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...
3507
3508
	// Use this hook to minify/optimize Javascript files and vars
3509
	call_integration_hook('integrate_pre_javascript_output', array(&$do_deferred));
3510
3511
	$toMinify = array();
3512
	$toMinifyDefer = array();
3513
3514
	// Ouput the declared Javascript variables.
3515
	if (!empty($context['javascript_vars']) && !$do_deferred)
3516
	{
3517
		echo '
3518
	<script>';
3519
3520
		foreach ($context['javascript_vars'] as $key => $value)
3521
		{
3522
			if (empty($value))
3523
			{
3524
				echo '
3525
		var ', $key, ';';
3526
			}
3527
			else
3528
			{
3529
				echo '
3530
		var ', $key, ' = ', $value, ';';
3531
			}
3532
		}
3533
3534
		echo '
3535
	</script>';
3536
	}
3537
3538
	// While we have JavaScript files to place in the template.
3539
	foreach ($context['javascript_files'] as $id => $js_file)
3540
	{
3541
		// Last minute call! allow theme authors to disable single files.
3542
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3543
			continue;
3544
3545
		// By default all files don't get minimized unless the file explicitly says so!
3546
		if (!empty($js_file['options']['minimize']) && !empty($modSettings['minimize_files']))
3547
		{
3548
			if ($do_deferred && !empty($js_file['options']['defer']))
3549
				$toMinifyDefer[] = $js_file;
3550
3551
			elseif (!$do_deferred && empty($js_file['options']['defer']))
3552
				$toMinify[] = $js_file;
3553
3554
			// Grab a random seed.
3555
			if (!isset($minSeed))
3556
				$minSeed = $js_file['options']['seed'];
3557
		}
3558
3559
		elseif ((!$do_deferred && empty($js_file['options']['defer'])) || ($do_deferred && !empty($js_file['options']['defer'])))
3560
			echo '
3561
	<script src="', $js_file['fileUrl'], '"', !empty($js_file['options']['async']) ? ' async="async"' : '', '></script>';
3562
	}
3563
3564
	if ((!$do_deferred && !empty($toMinify)) || ($do_deferred && !empty($toMinifyDefer)))
3565
	{
3566
		$result = custMinify(($do_deferred ? $toMinifyDefer : $toMinify), 'js', $do_deferred);
3567
3568
		// Minify process couldn't work, print each individual files.
3569
		if (!empty($result) && is_array($result))
3570
			foreach ($result as $minFailedFile)
3571
				echo '
3572
	<script src="', $minFailedFile['fileUrl'], '"', !empty($minFailedFile['options']['async']) ? ' async="async"' : '', '></script>';
3573
3574
		else
3575
			echo '
3576
	<script src="', $settings['theme_url'] ,'/scripts/minified', ($do_deferred ? '_deferred' : '') ,'.js', $minSeed ,'"></script>';
0 ignored issues
show
Bug introduced by
The variable $minSeed does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3577
	}
3578
3579
	// Inline JavaScript - Actually useful some times!
3580
	if (!empty($context['javascript_inline']))
3581
	{
3582 View Code Duplication
		if (!empty($context['javascript_inline']['defer']) && $do_deferred)
3583
		{
3584
			echo '
3585
<script>';
3586
3587
			foreach ($context['javascript_inline']['defer'] as $js_code)
3588
				echo $js_code;
3589
3590
			echo '
3591
</script>';
3592
		}
3593
3594 View Code Duplication
		if (!empty($context['javascript_inline']['standard']) && !$do_deferred)
3595
		{
3596
			echo '
3597
	<script>';
3598
3599
			foreach ($context['javascript_inline']['standard'] as $js_code)
3600
				echo $js_code;
3601
3602
			echo '
3603
	</script>';
3604
		}
3605
	}
3606
}
3607
3608
/**
3609
 * Output the CSS files
3610
 *
3611
 */
3612
function template_css()
3613
{
3614
	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...
3615
3616
	// Use this hook to minify/optimize CSS files
3617
	call_integration_hook('integrate_pre_css_output');
3618
3619
	$toMinify = array();
3620
	$normal = array();
3621
3622
	foreach ($context['css_files'] as $id => $file)
3623
	{
3624
		// Last minute call! allow theme authors to disable single files.
3625
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3626
			continue;
3627
3628
		// By default all files don't get minimized unless the file explicitly says so!
3629
		if (!empty($file['options']['minimize']) && !empty($modSettings['minimize_files']))
3630
		{
3631
			$toMinify[] = $file;
3632
3633
			// Grab a random seed.
3634
			if (!isset($minSeed))
3635
				$minSeed = $file['options']['seed'];
3636
		}
3637
3638
		else
3639
			$normal[] = $file['fileUrl'];
3640
	}
3641
3642
	if (!empty($toMinify))
3643
	{
3644
		$result = custMinify($toMinify, 'css');
3645
3646
		// Minify process couldn't work, print each individual files.
3647
		if (!empty($result) && is_array($result))
3648
			foreach ($result as $minFailedFile)
3649
				echo '
3650
	<link rel="stylesheet" href="', $minFailedFile['fileUrl'], '">';
3651
3652
		else
3653
			echo '
3654
	<link rel="stylesheet" href="', $settings['theme_url'] ,'/css/minified.css', $minSeed ,'">';
0 ignored issues
show
Bug introduced by
The variable $minSeed does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3655
	}
3656
3657
	// Print the rest after the minified files.
3658
	if (!empty($normal))
3659
		foreach ($normal as $nf)
3660
			echo '
3661
	<link rel="stylesheet" href="', $nf ,'">';
3662
3663
	if ($db_show_debug === true)
3664
	{
3665
		// Try to keep only what's useful.
3666
		$repl = array($boardurl . '/Themes/' => '', $boardurl . '/' => '');
3667
		foreach ($context['css_files'] as $file)
3668
			$context['debug']['sheets'][] = strtr($file['fileName'], $repl);
3669
	}
3670
3671
	if (!empty($context['css_header']))
3672
	{
3673
		echo '
3674
	<style>';
3675
3676
		foreach ($context['css_header'] as $css)
3677
			echo $css .'
3678
	';
3679
3680
		echo'
3681
	</style>';
3682
	}
3683
}
3684
3685
/**
3686
 * Get an array of previously defined files and adds them to our main minified file.
3687
 * Sets a one day cache to avoid re-creating a file on every request.
3688
 *
3689
 * @param array $data The files to minify.
3690
 * @param string $type either css or js.
3691
 * @param bool $do_deferred use for type js to indicate if the minified file will be deferred, IE, put at the closing </body> tag.
3692
 * @return bool|array If an array the minify process failed and the data is returned intact.
3693
 */
3694
function custMinify($data, $type, $do_deferred = false)
3695
{
3696
	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...
3697
3698
	$types = array('css', 'js');
3699
	$type = !empty($type) && in_array($type, $types) ? $type : false;
3700
	$data = !empty($data) ? $data : false;
3701
3702
	if (empty($type) || empty($data))
3703
		return false;
3704
3705
	// Did we already did this?
3706
	$toCache = cache_get_data('minimized_'. $settings['theme_id'] .'_'. $type, 86400);
3707
3708
	// Already done?
3709
	if (!empty($toCache))
3710
		return true;
3711
3712
	// No namespaces, sorry!
3713
	$classType = 'MatthiasMullie\\Minify\\'. strtoupper($type);
3714
3715
	// Temp path.
3716
	$cTempPath = $settings['theme_dir'] .'/'. ($type == 'css' ? 'css' : 'scripts') .'/';
3717
3718
	// What kind of file are we going to create?
3719
	$toCreate = $cTempPath .'minified'. ($do_deferred ? '_deferred' : '') .'.'. $type;
3720
3721
	// File has to exists, if it isn't try to create it.
3722
	if ((!file_exists($toCreate) && @fopen($toCreate, 'w') === false) || !smf_chmod($toCreate))
3723
	{
3724
		loadLanguage('Errors');
3725
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3726
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3727
3728
		// The process failed so roll back to print each individual file.
3729
		return $data;
3730
	}
3731
3732
	$minifier = new $classType();
3733
3734
	foreach ($data as $file)
3735
	{
3736
		$tempFile = str_replace($file['options']['seed'], '', $file['filePath']);
3737
		$toAdd = file_exists($tempFile) ? $tempFile : false;
3738
3739
		// The file couldn't be located so it won't be added, log this error.
3740
		if (empty($toAdd))
3741
		{
3742
			loadLanguage('Errors');
3743
			log_error(sprintf($txt['file_minimize_fail'], $file['fileName']), 'general');
3744
			continue;
3745
		}
3746
3747
		// Add this file to the list.
3748
		$minifier->add($toAdd);
3749
	}
3750
3751
	// Create the file.
3752
	$minifier->minify($toCreate);
3753
	unset($minifier);
3754
	clearstatcache();
3755
3756
	// Minify process failed.
3757
	if (!filesize($toCreate))
3758
	{
3759
		loadLanguage('Errors');
3760
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3761
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3762
3763
		// The process failed so roll back to print each individual file.
3764
		return $data;
3765
	}
3766
3767
	// And create a long lived cache entry.
3768
	cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, $toCreate, 86400);
3769
3770
	return true;
3771
}
3772
3773
/**
3774
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
3775
 * @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...
3776
 * Something messy like that.
3777
 * @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...
3778
 * Converters included.
3779
 *
3780
 * @param string $filename The name of the file
3781
 * @param int $attachment_id The ID of the attachment
3782
 * @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...
3783
 * @param bool $new Whether this is a new attachment
3784
 * @param string $file_hash The file hash
3785
 * @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...
3786
 */
3787
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
3788
{
3789
	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...
3790
3791
	// Just make up a nice hash...
3792
	if ($new)
3793
		return sha1(md5($filename . time()) . mt_rand());
3794
3795
	// Just make sure that attachment id is only a int
3796
	$attachment_id = (int) $attachment_id;
3797
3798
	// Grab the file hash if it wasn't added.
3799
	// Left this for legacy.
3800
	if ($file_hash === '')
3801
	{
3802
		$request = $smcFunc['db_query']('', '
3803
			SELECT file_hash
3804
			FROM {db_prefix}attachments
3805
			WHERE id_attach = {int:id_attach}',
3806
			array(
3807
				'id_attach' => $attachment_id,
3808
			));
3809
3810
		if ($smcFunc['db_num_rows']($request) === 0)
3811
			return false;
3812
3813
		list ($file_hash) = $smcFunc['db_fetch_row']($request);
3814
		$smcFunc['db_free_result']($request);
3815
	}
3816
3817
	// Still no hash? mmm...
3818
	if (empty($file_hash))
3819
		$file_hash = sha1(md5($filename . time()) . mt_rand());
3820
3821
	// Are we using multiple directories?
3822
	if (is_array($modSettings['attachmentUploadDir']))
3823
		$path = $modSettings['attachmentUploadDir'][$dir];
3824
3825
	else
3826
		$path = $modSettings['attachmentUploadDir'];
3827
3828
	return $path . '/' . $attachment_id . '_' . $file_hash .'.dat';
3829
}
3830
3831
/**
3832
 * Convert a single IP to a ranged IP.
3833
 * internal function used to convert a user-readable format to a format suitable for the database.
3834
 *
3835
 * @param string $fullip The full IP
3836
 * @return array An array of IP parts
3837
 */
3838
function ip2range($fullip)
3839
{
3840
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
3841
	if ($fullip == 'unknown')
3842
		$fullip = '255.255.255.255';
3843
3844
	$ip_parts = explode('-', $fullip);
3845
	$ip_array = array();
3846
3847
	// 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...
3848
	if (count($ip_parts) == 1 && isValidIP($fullip))
3849
	{
3850
		$ip_array['low'] = $fullip;
3851
		$ip_array['high'] = $fullip;
3852
		return $ip_array;
3853
	} // if ip 22.12.* -> 22.12.* - 22.12.*
3854
	elseif (count($ip_parts) == 1)
3855
	{
3856
		$ip_parts[0] = $fullip;
3857
		$ip_parts[1] = $fullip;
3858
	}
3859
3860
	// 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...
3861
	if (count($ip_parts) == 2 && isValidIP($ip_parts[0]) && isValidIP($ip_parts[1]))
3862
	{
3863
		$ip_array['low'] = $ip_parts[0];
3864
		$ip_array['high'] = $ip_parts[1];
3865
		return $ip_array;
3866
	}
3867
	elseif (count($ip_parts) == 2) // if ip 22.22.*-22.22.*
3868
	{
3869
		$valid_low = isValidIP($ip_parts[0]);
3870
		$valid_high = isValidIP($ip_parts[1]);
3871
		$count = 0;
3872
		$mode = (preg_match('/:/',$ip_parts[0]) > 0 ? ':' : '.');
3873
		$max = ($mode == ':' ? 'ffff' : '255');
3874
		$min = 0;
3875 View Code Duplication
		if(!$valid_low)
3876
		{
3877
			$ip_parts[0] = preg_replace('/\*/', '0', $ip_parts[0]);
3878
			$valid_low = isValidIP($ip_parts[0]);
3879
			while (!$valid_low)
3880
			{
3881
				$ip_parts[0] .= $mode . $min;
3882
				$valid_low = isValidIP($ip_parts[0]);
3883
				$count++;
3884
				if ($count > 9) break;
3885
			}
3886
		}
3887
3888
		$count = 0;
3889 View Code Duplication
		if(!$valid_high)
3890
		{
3891
			$ip_parts[1] = preg_replace('/\*/', $max, $ip_parts[1]);
3892
			$valid_high = isValidIP($ip_parts[1]);
3893
			while (!$valid_high)
3894
			{
3895
				$ip_parts[1] .= $mode . $max;
3896
				$valid_high = isValidIP($ip_parts[1]);
3897
				$count++;
3898
				if ($count > 9) break;
3899
			}
3900
		}
3901
3902
		if($valid_high && $valid_low)
3903
		{
3904
			$ip_array['low'] = $ip_parts[0];
3905
			$ip_array['high'] = $ip_parts[1];
3906
		}
3907
3908
	}
3909
3910
	return $ip_array;
3911
}
3912
3913
/**
3914
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
3915
 *
3916
 * @param string $ip The IP to get the hostname from
3917
 * @return string The hostname
3918
 */
3919
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...
3920
{
3921
	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...
3922
3923
	if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null)
3924
		return $host;
3925
	$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...
3926
3927
	// Try the Linux host command, perhaps?
3928
	if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
3929
	{
3930
		if (!isset($modSettings['host_to_dis']))
3931
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
3932
		else
3933
			$test = @shell_exec('host ' . @escapeshellarg($ip));
3934
3935
		// Did host say it didn't find anything?
3936
		if (strpos($test, 'not found') !== false)
3937
			$host = '';
3938
		// Invalid server option?
3939
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
3940
			updateSettings(array('host_to_dis' => 1));
3941
		// Maybe it found something, after all?
3942
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
3943
			$host = $match[1];
3944
	}
3945
3946
	// This is nslookup; usually only Windows, but possibly some Unix?
3947
	if (!isset($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
3948
	{
3949
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
3950
		if (strpos($test, 'Non-existent domain') !== false)
3951
			$host = '';
3952
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
3953
			$host = $match[1];
3954
	}
3955
3956
	// This is the last try :/.
3957
	if (!isset($host) || $host === false)
3958
		$host = @gethostbyaddr($ip);
3959
3960
	// It took a long time, so let's cache it!
3961 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5)
3962
		cache_put_data('hostlookup-' . $ip, $host, 600);
3963
3964
	return $host;
3965
}
3966
3967
/**
3968
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
3969
 *
3970
 * @param string $text The text to split into words
3971
 * @param int $max_chars The maximum number of characters per word
3972
 * @param bool $encrypt Whether to encrypt the results
3973
 * @return array An array of ints or words depending on $encrypt
3974
 */
3975
function text2words($text, $max_chars = 20, $encrypt = false)
3976
{
3977
	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...
3978
3979
	// Step 1: Remove entities/things we don't consider words:
3980
	$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>' => ' ')));
3981
3982
	// Step 2: Entities we left to letters, where applicable, lowercase.
3983
	$words = un_htmlspecialchars($smcFunc['strtolower']($words));
3984
3985
	// Step 3: Ready to split apart and index!
3986
	$words = explode(' ', $words);
3987
3988
	if ($encrypt)
3989
	{
3990
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
3991
		$returned_ints = array();
3992
		foreach ($words as $word)
3993
		{
3994
			if (($word = trim($word, '-_\'')) !== '')
3995
			{
3996
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
3997
				$total = 0;
3998
				for ($i = 0; $i < $max_chars; $i++)
3999
					$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
4000
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
4001
			}
4002
		}
4003
		return array_unique($returned_ints);
4004
	}
4005
	else
4006
	{
4007
		// Trim characters before and after and add slashes for database insertion.
4008
		$returned_words = array();
4009
		foreach ($words as $word)
4010
			if (($word = trim($word, '-_\'')) !== '')
4011
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
4012
4013
		// Filter out all words that occur more than once.
4014
		return array_unique($returned_words);
4015
	}
4016
}
4017
4018
/**
4019
 * Creates an image/text button
4020
 *
4021
 * @param string $name The name of the button (should be a generic_icons class or the name of an image)
4022
 * @param string $alt The alt text
4023
 * @param string $label The $txt string to use as the label
4024
 * @param string $custom Custom text/html to add to the img tag (only when using an actual image)
4025
 * @param boolean $force_use Whether to force use of this when template_create_button is available
4026
 * @return string The HTML to display the button
4027
 */
4028
function create_button($name, $alt, $label = '', $custom = '', $force_use = false)
4029
{
4030
	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...
4031
4032
	// Does the current loaded theme have this and we are not forcing the usage of this function?
4033
	if (function_exists('template_create_button') && !$force_use)
4034
		return template_create_button($name, $alt, $label = '', $custom = '');
4035
4036
	if (!$settings['use_image_buttons'])
4037
		return $txt[$alt];
4038
	elseif (!empty($settings['use_buttons']))
4039
		return '<span class="generic_icons ' . $name . '" alt="' . $txt[$alt] . '"></span>' . ($label != '' ? '&nbsp;<strong>' . $txt[$label] . '</strong>' : '');
4040
	else
4041
		return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . '>';
4042
}
4043
4044
/**
4045
 * Sets up all of the top menu buttons
4046
 * Saves them in the cache if it is available and on
4047
 * Places the results in $context
4048
 *
4049
 */
4050
function setupMenuContext()
4051
{
4052
	global $context, $modSettings, $user_info, $txt, $scripturl, $sourcedir, $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...
4053
4054
	// Set up the menu privileges.
4055
	$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts'));
4056
	$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'));
4057
4058
	$context['allow_memberlist'] = allowedTo('view_mlist');
4059
	$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
4060
	$context['allow_moderation_center'] = $context['user']['can_mod'];
4061
	$context['allow_pm'] = allowedTo('pm_read');
4062
4063
	$cacheTime = $modSettings['lastActive'] * 60;
4064
4065
	// Initial "can you post an event in the calendar" option - but this might have been set in the calendar already.
4066
	if (!isset($context['allow_calendar_event']))
4067
	{
4068
		$context['allow_calendar_event'] = $context['allow_calendar'] && allowedTo('calendar_post');
4069
4070
		// If you don't allow events not linked to posts and you're not an admin, we have more work to do...
4071 View Code Duplication
		if ($context['allow_calendar'] && $context['allow_calendar_event'] && empty($modSettings['cal_allow_unlinked']) && !$user_info['is_admin'])
4072
		{
4073
			$boards_can_post = boardsAllowedTo('post_new');
4074
			$context['allow_calendar_event'] &= !empty($boards_can_post);
4075
		}
4076
	}
4077
4078
	// There is some menu stuff we need to do if we're coming at this from a non-guest perspective.
4079
	if (!$context['user']['is_guest'])
4080
	{
4081
		addInlineJavaScript('
4082
	var user_menus = new smc_PopupMenu();
4083
	user_menus.add("profile", "' . $scripturl . '?action=profile;area=popup");
4084
	user_menus.add("alerts", "' . $scripturl . '?action=profile;area=alerts_popup;u='. $context['user']['id'] .'");', true);
4085
		if ($context['allow_pm'])
4086
			addInlineJavaScript('
4087
	user_menus.add("pm", "' . $scripturl . '?action=pm;sa=popup");', true);
4088
4089
		if (!empty($modSettings['enable_ajax_alerts']))
4090
		{
4091
			require_once($sourcedir . '/Subs-Notify.php');
4092
4093
			$timeout = getNotifyPrefs($context['user']['id'], 'alert_timeout', true);
4094
			$timeout = empty($timeout) ? 10000 : $timeout[$context['user']['id']]['alert_timeout'] * 1000;
4095
4096
			addInlineJavaScript('
4097
	var new_alert_title = "' . $context['forum_name'] . '";
4098
	var alert_timeout = ' . $timeout . ';');
4099
			loadJavaScriptFile('alerts.js', array(), 'smf_alerts');
4100
		}
4101
	}
4102
4103
	// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
4104
	if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
4105
	{
4106
		$buttons = array(
4107
			'home' => array(
4108
				'title' => $txt['home'],
4109
				'href' => $scripturl,
4110
				'show' => true,
4111
				'sub_buttons' => array(
4112
				),
4113
				'is_last' => $context['right_to_left'],
4114
			),
4115
			'search' => array(
4116
				'title' => $txt['search'],
4117
				'href' => $scripturl . '?action=search',
4118
				'show' => $context['allow_search'],
4119
				'sub_buttons' => array(
4120
				),
4121
			),
4122
			'admin' => array(
4123
				'title' => $txt['admin'],
4124
				'href' => $scripturl . '?action=admin',
4125
				'show' => $context['allow_admin'],
4126
				'sub_buttons' => array(
4127
					'featuresettings' => array(
4128
						'title' => $txt['modSettings_title'],
4129
						'href' => $scripturl . '?action=admin;area=featuresettings',
4130
						'show' => allowedTo('admin_forum'),
4131
					),
4132
					'packages' => array(
4133
						'title' => $txt['package'],
4134
						'href' => $scripturl . '?action=admin;area=packages',
4135
						'show' => allowedTo('admin_forum'),
4136
					),
4137
					'errorlog' => array(
4138
						'title' => $txt['errlog'],
4139
						'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
4140
						'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']),
4141
					),
4142
					'permissions' => array(
4143
						'title' => $txt['edit_permissions'],
4144
						'href' => $scripturl . '?action=admin;area=permissions',
4145
						'show' => allowedTo('manage_permissions'),
4146
					),
4147
					'memberapprove' => array(
4148
						'title' => $txt['approve_members_waiting'],
4149
						'href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve',
4150
						'show' => !empty($context['unapproved_members']),
4151
						'is_last' => true,
4152
					),
4153
				),
4154
			),
4155
			'moderate' => array(
4156
				'title' => $txt['moderate'],
4157
				'href' => $scripturl . '?action=moderate',
4158
				'show' => $context['allow_moderation_center'],
4159
				'sub_buttons' => array(
4160
					'modlog' => array(
4161
						'title' => $txt['modlog_view'],
4162
						'href' => $scripturl . '?action=moderate;area=modlog',
4163
						'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4164
					),
4165
					'poststopics' => array(
4166
						'title' => $txt['mc_unapproved_poststopics'],
4167
						'href' => $scripturl . '?action=moderate;area=postmod;sa=posts',
4168
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4169
					),
4170
					'attachments' => array(
4171
						'title' => $txt['mc_unapproved_attachments'],
4172
						'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments',
4173
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4174
					),
4175
					'reports' => array(
4176
						'title' => $txt['mc_reported_posts'],
4177
						'href' => $scripturl . '?action=moderate;area=reportedposts',
4178
						'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4179
					),
4180
					'reported_members' => array(
4181
						'title' => $txt['mc_reported_members'],
4182
						'href' => $scripturl . '?action=moderate;area=reportedmembers',
4183
						'show' => allowedTo('moderate_forum'),
4184
						'is_last' => true,
4185
					)
4186
				),
4187
			),
4188
			'calendar' => array(
4189
				'title' => $txt['calendar'],
4190
				'href' => $scripturl . '?action=calendar',
4191
				'show' => $context['allow_calendar'],
4192
				'sub_buttons' => array(
4193
					'view' => array(
4194
						'title' => $txt['calendar_menu'],
4195
						'href' => $scripturl . '?action=calendar',
4196
						'show' => $context['allow_calendar_event'],
4197
					),
4198
					'post' => array(
4199
						'title' => $txt['calendar_post_event'],
4200
						'href' => $scripturl . '?action=calendar;sa=post',
4201
						'show' => $context['allow_calendar_event'],
4202
						'is_last' => true,
4203
					),
4204
				),
4205
			),
4206
			'mlist' => array(
4207
				'title' => $txt['members_title'],
4208
				'href' => $scripturl . '?action=mlist',
4209
				'show' => $context['allow_memberlist'],
4210
				'sub_buttons' => array(
4211
					'mlist_view' => array(
4212
						'title' => $txt['mlist_menu_view'],
4213
						'href' => $scripturl . '?action=mlist',
4214
						'show' => true,
4215
					),
4216
					'mlist_search' => array(
4217
						'title' => $txt['mlist_search'],
4218
						'href' => $scripturl . '?action=mlist;sa=search',
4219
						'show' => true,
4220
						'is_last' => true,
4221
					),
4222
				),
4223
			),
4224
			'signup' => array(
4225
				'title' => $txt['register'],
4226
				'href' => $scripturl . '?action=signup',
4227
				'show' => $user_info['is_guest'] && $context['can_register'],
4228
				'sub_buttons' => array(
4229
				),
4230
				'is_last' => !$context['right_to_left'],
4231
			),
4232
			'logout' => array(
4233
				'title' => $txt['logout'],
4234
				'href' => $scripturl . '?action=logout;%1$s=%2$s',
4235
				'show' => !$user_info['is_guest'],
4236
				'sub_buttons' => array(
4237
				),
4238
				'is_last' => !$context['right_to_left'],
4239
			),
4240
		);
4241
4242
		// Allow editing menu buttons easily.
4243
		call_integration_hook('integrate_menu_buttons', array(&$buttons));
4244
4245
		// Now we put the buttons in the context so the theme can use them.
4246
		$menu_buttons = array();
4247
		foreach ($buttons as $act => $button)
4248
			if (!empty($button['show']))
4249
			{
4250
				$button['active_button'] = false;
4251
4252
				// This button needs some action.
4253
				if (isset($button['action_hook']))
4254
					$needs_action_hook = true;
4255
4256
				// Make sure the last button truly is the last button.
4257
				if (!empty($button['is_last']))
4258
				{
4259
					if (isset($last_button))
4260
						unset($menu_buttons[$last_button]['is_last']);
4261
					$last_button = $act;
4262
				}
4263
4264
				// Go through the sub buttons if there are any.
4265
				if (!empty($button['sub_buttons']))
4266
					foreach ($button['sub_buttons'] as $key => $subbutton)
4267
					{
4268
						if (empty($subbutton['show']))
4269
							unset($button['sub_buttons'][$key]);
4270
4271
						// 2nd level sub buttons next...
4272
						if (!empty($subbutton['sub_buttons']))
4273
						{
4274
							foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2)
4275
							{
4276
								if (empty($sub_button2['show']))
4277
									unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
4278
							}
4279
						}
4280
					}
4281
4282
				// Does this button have its own icon?
4283
				if (isset($button['icon']) && file_exists($settings['theme_dir'] . '/images/' . $button['icon']))
4284
					$button['icon'] = '<img src="' . $settings['images_url'] . '/' . $button['icon'] . '" alt="">';
4285
				elseif (isset($button['icon']) && file_exists($settings['default_theme_dir'] . '/images/' . $button['icon']))
4286
					$button['icon'] = '<img src="' . $settings['default_images_url'] . '/' . $button['icon'] . '" alt="">';
4287
				elseif (isset($button['icon']))
4288
					$button['icon'] = '<span class="generic_icons ' . $button['icon'] . '"></span>';
4289
				else
4290
					$button['icon'] = '<span class="generic_icons ' . $act . '"></span>';
4291
4292
				$menu_buttons[$act] = $button;
4293
			}
4294
4295
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
4296
			cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
4297
	}
4298
4299
	$context['menu_buttons'] = $menu_buttons;
4300
4301
	// Logging out requires the session id in the url.
4302
	if (isset($context['menu_buttons']['logout']))
4303
		$context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']);
4304
4305
	// Figure out which action we are doing so we can set the active tab.
4306
	// Default to home.
4307
	$current_action = 'home';
4308
4309
	if (isset($context['menu_buttons'][$context['current_action']]))
4310
		$current_action = $context['current_action'];
4311
	elseif ($context['current_action'] == 'search2')
4312
		$current_action = 'search';
4313
	elseif ($context['current_action'] == 'theme')
4314
		$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin';
4315
	elseif ($context['current_action'] == 'register2')
4316
		$current_action = 'register';
4317
	elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder'))
4318
		$current_action = 'login';
4319
	elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center'])
4320
		$current_action = 'moderate';
4321
4322
	// There are certain exceptions to the above where we don't want anything on the menu highlighted.
4323
	if ($context['current_action'] == 'profile' && !empty($context['user']['is_owner']))
4324
	{
4325
		$current_action = !empty($_GET['area']) && $_GET['area'] == 'showalerts' ? 'self_alerts' : 'self_profile';
4326
		$context[$current_action] = true;
4327
	}
4328
	elseif ($context['current_action'] == 'pm')
4329
	{
4330
		$current_action = 'self_pm';
4331
		$context['self_pm'] = true;
4332
	}
4333
4334
	$total_mod_reports = 0;
4335
4336
	if (!empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1' && !empty($context['open_mod_reports']))
4337
	{
4338
		$total_mod_reports = $context['open_mod_reports'];
4339
		$context['menu_buttons']['moderate']['sub_buttons']['reports']['title'] .= ' <span class="amt">' . $context['open_mod_reports'] . '</span>';
4340
	}
4341
4342
	// Show how many errors there are
4343 View Code Duplication
	if (!empty($context['num_errors']) && allowedTo('admin_forum'))
4344
	{
4345
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4346
		$context['menu_buttons']['admin']['sub_buttons']['errorlog']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4347
	}
4348
4349
	// Show number of reported members
4350
	if (!empty($context['open_member_reports']) && allowedTo('moderate_forum'))
4351
	{
4352
		$total_mod_reports += $context['open_member_reports'];
4353
		$context['menu_buttons']['moderate']['sub_buttons']['reported_members']['title'] .= ' <span class="amt">' . $context['open_member_reports'] . '</span>';
4354
	}
4355
4356 View Code Duplication
	if (!empty($context['unapproved_members']))
4357
	{
4358
		$context['menu_buttons']['admin']['sub_buttons']['memberapprove']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4359
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4360
	}
4361
4362
	// Do we have any open reports?
4363
	if ($total_mod_reports > 0)
4364
	{
4365
		$context['menu_buttons']['moderate']['title'] .= ' <span class="amt">' . $total_mod_reports . '</span>';
4366
	}
4367
4368
	// Not all actions are simple.
4369
	if (!empty($needs_action_hook))
4370
		call_integration_hook('integrate_current_action', array(&$current_action));
4371
4372
	if (isset($context['menu_buttons'][$current_action]))
4373
		$context['menu_buttons'][$current_action]['active_button'] = true;
4374
}
4375
4376
/**
4377
 * Generate a random seed and ensure it's stored in settings.
4378
 */
4379
function smf_seed_generator()
4380
{
4381
	updateSettings(array('rand_seed' => microtime() * 1000000));
4382
}
4383
4384
/**
4385
 * Process functions of an integration hook.
4386
 * calls all functions of the given hook.
4387
 * supports static class method calls.
4388
 *
4389
 * @param string $hook The hook name
4390
 * @param array $parameters An array of parameters this hook implements
4391
 * @return array The results of the functions
4392
 */
4393
function call_integration_hook($hook, $parameters = array())
4394
{
4395
	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...
4396
	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...
4397
4398
	if ($db_show_debug === true)
4399
		$context['debug']['hooks'][] = $hook;
4400
4401
	// Need to have some control.
4402
	if (!isset($context['instances']))
4403
		$context['instances'] = array();
4404
4405
	$results = array();
4406
	if (empty($modSettings[$hook]))
4407
		return $results;
4408
4409
	$functions = explode(',', $modSettings[$hook]);
4410
	// Loop through each function.
4411
	foreach ($functions as $function)
4412
	{
4413
		// Hook has been marked as "disabled". Skip it!
4414
		if (strpos($function, '!') !== false)
4415
			continue;
4416
4417
		$call = call_helper($function, true);
4418
4419
		// Is it valid?
4420
		if (!empty($call))
4421
			$results[$function] = call_user_func_array($call, $parameters);
4422
4423
		// Whatever it was suppose to call, it failed :(
4424
		elseif (!empty($function))
4425
		{
4426
			loadLanguage('Errors');
4427
4428
			// Get a full path to show on error.
4429
			if (strpos($function, '|') !== false)
4430
			{
4431
				list ($file, $string) = explode('|', $function);
4432
				$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'])));
4433
				log_error(sprintf($txt['hook_fail_call_to'], $string, $absPath), 'general');
4434
			}
4435
4436
			// "Assume" the file resides on $boarddir somewhere...
4437
			else
4438
				log_error(sprintf($txt['hook_fail_call_to'], $function, $boarddir), 'general');
4439
		}
4440
	}
4441
4442
	return $results;
4443
}
4444
4445
/**
4446
 * Add a function for integration hook.
4447
 * does nothing if the function is already added.
4448
 *
4449
 * @param string $hook The complete hook name.
4450
 * @param string $function The function name. Can be a call to a method via Class::method.
4451
 * @param bool $permanent If true, updates the value in settings table.
4452
 * @param string $file The file. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4453
 * @param bool $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4454
 */
4455
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
4456
{
4457
	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...
4458
4459
	// Any objects?
4460
	if ($object)
4461
		$function = $function . '#';
4462
4463
	// Any files  to load?
4464
	if (!empty($file) && is_string($file))
4465
		$function = $file . (!empty($function) ? '|' . $function : '');
4466
4467
	// Get the correct string.
4468
	$integration_call = $function;
4469
4470
	// Is it going to be permanent?
4471
	if ($permanent)
4472
	{
4473
		$request = $smcFunc['db_query']('', '
4474
			SELECT value
4475
			FROM {db_prefix}settings
4476
			WHERE variable = {string:variable}',
4477
			array(
4478
				'variable' => $hook,
4479
			)
4480
		);
4481
		list ($current_functions) = $smcFunc['db_fetch_row']($request);
4482
		$smcFunc['db_free_result']($request);
4483
4484
		if (!empty($current_functions))
4485
		{
4486
			$current_functions = explode(',', $current_functions);
4487
			if (in_array($integration_call, $current_functions))
4488
				return;
4489
4490
			$permanent_functions = array_merge($current_functions, array($integration_call));
4491
		}
4492
		else
4493
			$permanent_functions = array($integration_call);
4494
4495
		updateSettings(array($hook => implode(',', $permanent_functions)));
4496
	}
4497
4498
	// Make current function list usable.
4499
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4500
4501
	// Do nothing, if it's already there.
4502
	if (in_array($integration_call, $functions))
4503
		return;
4504
4505
	$functions[] = $integration_call;
4506
	$modSettings[$hook] = implode(',', $functions);
4507
}
4508
4509
/**
4510
 * Remove an integration hook function.
4511
 * Removes the given function from the given hook.
4512
 * Does nothing if the function is not available.
4513
 *
4514
 * @param string $hook The complete hook name.
4515
 * @param string $function The function name. Can be a call to a method via Class::method.
4516
 * @param boolean $permanent Irrelevant for the function itself but need to declare it to match
4517
 * @param string $file The filename. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4518
 * @param boolean $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4519
 * @see add_integration_function
4520
 */
4521
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...
4522
{
4523
	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...
4524
4525
	// Any objects?
4526
	if ($object)
4527
		$function = $function . '#';
4528
4529
	// Any files  to load?
4530
	if (!empty($file) && is_string($file))
4531
		$function = $file . '|' . $function;
4532
4533
	// Get the correct string.
4534
	$integration_call = $function;
4535
4536
	// Get the permanent functions.
4537
	$request = $smcFunc['db_query']('', '
4538
		SELECT value
4539
		FROM {db_prefix}settings
4540
		WHERE variable = {string:variable}',
4541
		array(
4542
			'variable' => $hook,
4543
		)
4544
	);
4545
	list ($current_functions) = $smcFunc['db_fetch_row']($request);
4546
	$smcFunc['db_free_result']($request);
4547
4548
	if (!empty($current_functions))
4549
	{
4550
		$current_functions = explode(',', $current_functions);
4551
4552
		if (in_array($integration_call, $current_functions))
4553
			updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call)))));
4554
	}
4555
4556
	// Turn the function list into something usable.
4557
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4558
4559
	// You can only remove it if it's available.
4560
	if (!in_array($integration_call, $functions))
4561
		return;
4562
4563
	$functions = array_diff($functions, array($integration_call));
4564
	$modSettings[$hook] = implode(',', $functions);
4565
}
4566
4567
/**
4568
 * Receives a string and tries to figure it out if its a method or a function.
4569
 * If a method is found, it looks for a "#" which indicates SMF should create a new instance of the given class.
4570
 * Checks the string/array for is_callable() and return false/fatal_lang_error is the given value results in a non callable string/array.
4571
 * Prepare and returns a callable depending on the type of method/function found.
4572
 *
4573
 * @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)
4574
 * @param boolean $return If true, the function will not call the function/method but instead will return the formatted string.
4575
 * @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.
4576
 */
4577
function call_helper($string, $return = false)
4578
{
4579
	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...
4580
4581
	// Really?
4582
	if (empty($string))
4583
		return false;
4584
4585
	// An array? should be a "callable" array IE array(object/class, valid_callable).
4586
	// A closure? should be a callable one.
4587
	if (is_array($string) || $string instanceof Closure)
4588
		return $return ? $string : (is_callable($string) ? call_user_func($string) : false);
4589
4590
	// No full objects, sorry! pass a method or a property instead!
4591
	if (is_object($string))
4592
		return false;
4593
4594
	// Stay vitaminized my friends...
4595
	$string = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($string));
4596
4597
	// Is there a file to load?
4598
	$string = load_file($string);
4599
4600
	// Loaded file failed
4601
	if (empty($string))
4602
		return false;
4603
4604
	// Found a method.
4605
	if (strpos($string, '::') !== false)
4606
	{
4607
		list ($class, $method) = explode('::', $string);
4608
4609
		// Check if a new object will be created.
4610
		if (strpos($method, '#') !== false)
4611
		{
4612
			// Need to remove the # thing.
4613
			$method = str_replace('#', '', $method);
4614
4615
			// Don't need to create a new instance for every method.
4616
			if (empty($context['instances'][$class]) || !($context['instances'][$class] instanceof $class))
4617
			{
4618
				$context['instances'][$class] = new $class;
4619
4620
				// Add another one to the list.
4621
				if ($db_show_debug === true)
4622
				{
4623
					if (!isset($context['debug']['instances']))
4624
						$context['debug']['instances'] = array();
4625
4626
					$context['debug']['instances'][$class] = $class;
4627
				}
4628
			}
4629
4630
			$func = array($context['instances'][$class], $method);
4631
		}
4632
4633
		// Right then. This is a call to a static method.
4634
		else
4635
			$func = array($class, $method);
4636
	}
4637
4638
	// Nope! just a plain regular function.
4639
	else
4640
		$func = $string;
4641
4642
	// Right, we got what we need, time to do some checks.
4643
	if (!is_callable($func, false, $callable_name))
4644
	{
4645
		loadLanguage('Errors');
4646
		log_error(sprintf($txt['subAction_fail'], $callable_name), 'general');
4647
4648
		// Gotta tell everybody.
4649
		return false;
4650
	}
4651
4652
	// Everything went better than expected.
4653
	else
4654
	{
4655
		// What are we gonna do about it?
4656
		if ($return)
4657
			return $func;
4658
4659
		// If this is a plain function, avoid the heat of calling call_user_func().
4660
		else
4661
		{
4662
			if (is_array($func))
4663
				call_user_func($func);
4664
4665
			else
4666
				$func();
4667
		}
4668
	}
4669
}
4670
4671
/**
4672
 * Receives a string and tries to figure it out if it contains info to load a file.
4673
 * Checks for a | (pipe) symbol and tries to load a file with the info given.
4674
 * 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.
4675
 *
4676
 * @param string $string The string containing a valid format.
4677
 * @return string|boolean The given string with the pipe and file info removed. Boolean false if the file couldn't be loaded.
4678
 */
4679
function load_file($string)
4680
{
4681
	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...
4682
4683
	if (empty($string))
4684
		return false;
4685
4686
	if (strpos($string, '|') !== false)
4687
	{
4688
		list ($file, $string) = explode('|', $string);
4689
4690
		// Match the wildcards to their regular vars.
4691
		if (empty($settings['theme_dir']))
4692
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
4693
4694
		else
4695
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
4696
4697
		// Load the file if it can be loaded.
4698
		if (file_exists($absPath))
4699
			require_once($absPath);
4700
4701
		// No? try a fallback to $sourcedir
4702
		else
4703
		{
4704
			$absPath = $sourcedir .'/'. $file;
4705
4706
			if (file_exists($absPath))
4707
				require_once($absPath);
4708
4709
			// Sorry, can't do much for you at this point.
4710
			else
4711
			{
4712
				loadLanguage('Errors');
4713
				log_error(sprintf($txt['hook_fail_loading_file'], $absPath), 'general');
4714
4715
				// File couldn't be loaded.
4716
				return false;
4717
			}
4718
		}
4719
	}
4720
4721
	return $string;
4722
}
4723
4724
/**
4725
 * Prepares an array of "likes" info for the topic specified by $topic
4726
 * @param integer $topic The topic ID to fetch the info from.
4727
 * @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...
4728
 */
4729
function prepareLikesContext($topic)
4730
{
4731
	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...
4732
4733
	// Make sure we have something to work with.
4734
	if (empty($topic))
4735
		return array();
4736
4737
4738
	// We already know the number of likes per message, we just want to know whether the current user liked it or not.
4739
	$user = $user_info['id'];
4740
	$cache_key = 'likes_topic_' . $topic . '_' . $user;
4741
	$ttl = 180;
4742
4743
	if (($temp = cache_get_data($cache_key, $ttl)) === null)
4744
	{
4745
		$temp = array();
4746
		$request = $smcFunc['db_query']('', '
4747
			SELECT content_id
4748
			FROM {db_prefix}user_likes AS l
4749
				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
4750
			WHERE l.id_member = {int:current_user}
4751
				AND l.content_type = {literal:msg}
4752
				AND m.id_topic = {int:topic}',
4753
			array(
4754
				'current_user' => $user,
4755
				'topic' => $topic,
4756
			)
4757
		);
4758
		while ($row = $smcFunc['db_fetch_assoc']($request))
4759
			$temp[] = (int) $row['content_id'];
4760
4761
		cache_put_data($cache_key, $temp, $ttl);
4762
	}
4763
4764
	return $temp;
4765
}
4766
4767
/**
4768
 * Microsoft uses their own character set Code Page 1252 (CP1252), which is a
4769
 * superset of ISO 8859-1, defining several characters between DEC 128 and 159
4770
 * that are not normally displayable.  This converts the popular ones that
4771
 * appear from a cut and paste from windows.
4772
 *
4773
 * @param string $string The string
4774
 * @return string The sanitized string
4775
 */
4776
function sanitizeMSCutPaste($string)
4777
{
4778
	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...
4779
4780
	if (empty($string))
4781
		return $string;
4782
4783
	// UTF-8 occurences of MS special characters
4784
	$findchars_utf8 = array(
4785
		"\xe2\x80\x9a",	// single low-9 quotation mark
4786
		"\xe2\x80\x9e",	// double low-9 quotation mark
4787
		"\xe2\x80\xa6",	// horizontal ellipsis
4788
		"\xe2\x80\x98",	// left single curly quote
4789
		"\xe2\x80\x99",	// right single curly quote
4790
		"\xe2\x80\x9c",	// left double curly quote
4791
		"\xe2\x80\x9d",	// right double curly quote
4792
		"\xe2\x80\x93",	// en dash
4793
		"\xe2\x80\x94",	// em dash
4794
	);
4795
4796
	// windows 1252 / iso equivalents
4797
	$findchars_iso = array(
4798
		chr(130),
4799
		chr(132),
4800
		chr(133),
4801
		chr(145),
4802
		chr(146),
4803
		chr(147),
4804
		chr(148),
4805
		chr(150),
4806
		chr(151),
4807
	);
4808
4809
	// safe replacements
4810
	$replacechars = array(
4811
		',',	// &sbquo;
4812
		',,',	// &bdquo;
4813
		'...',	// &hellip;
4814
		"'",	// &lsquo;
4815
		"'",	// &rsquo;
4816
		'"',	// &ldquo;
4817
		'"',	// &rdquo;
4818
		'-',	// &ndash;
4819
		'--',	// &mdash;
4820
	);
4821
4822
	if ($context['utf8'])
4823
		$string = str_replace($findchars_utf8, $replacechars, $string);
4824
	else
4825
		$string = str_replace($findchars_iso, $replacechars, $string);
4826
4827
	return $string;
4828
}
4829
4830
/**
4831
 * Decode numeric html entities to their ascii or UTF8 equivalent character.
4832
 *
4833
 * Callback function for preg_replace_callback in subs-members
4834
 * Uses capture group 2 in the supplied array
4835
 * Does basic scan to ensure characters are inside a valid range
4836
 *
4837
 * @param array $matches An array of matches (relevant info should be the 3rd item)
4838
 * @return string A fixed string
4839
 */
4840
function replaceEntities__callback($matches)
4841
{
4842
	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...
4843
4844
	if (!isset($matches[2]))
4845
		return '';
4846
4847
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4848
4849
	// remove left to right / right to left overrides
4850
	if ($num === 0x202D || $num === 0x202E)
4851
		return '';
4852
4853
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
4854
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
4855
		return '&#' . $num . ';';
4856
4857
	if (empty($context['utf8']))
4858
	{
4859
		// no control characters
4860
		if ($num < 0x20)
4861
			return '';
4862
		// text is text
4863
		elseif ($num < 0x80)
4864
			return chr($num);
4865
		// all others get html-ised
4866
		else
4867
			return '&#' . $matches[2] . ';';
4868
	}
4869
	else
4870
	{
4871
		// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
4872
		// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
4873
		if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
4874
			return '';
4875
		// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4876
		elseif ($num < 0x80)
4877
			return chr($num);
4878
		// <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...
4879 View Code Duplication
		elseif ($num < 0x800)
4880
			return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4881
		// < 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...
4882 View Code Duplication
		elseif ($num < 0x10000)
4883
			return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4884
		// <= 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...
4885 View Code Duplication
		else
4886
			return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4887
	}
4888
}
4889
4890
/**
4891
 * Converts html entities to utf8 equivalents
4892
 *
4893
 * Callback function for preg_replace_callback
4894
 * Uses capture group 1 in the supplied array
4895
 * Does basic checks to keep characters inside a viewable range.
4896
 *
4897
 * @param array $matches An array of matches (relevant info should be the 2nd item in the array)
4898
 * @return string The fixed string
4899
 */
4900
function fixchar__callback($matches)
4901
{
4902
	if (!isset($matches[1]))
4903
		return '';
4904
4905
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
4906
4907
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
4908
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
4909
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
4910
		return '';
4911
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4912
	elseif ($num < 0x80)
4913
		return chr($num);
4914
	// <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...
4915 View Code Duplication
	elseif ($num < 0x800)
4916
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4917
	// < 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...
4918 View Code Duplication
	elseif ($num < 0x10000)
4919
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4920
	// <= 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...
4921 View Code Duplication
	else
4922
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4923
}
4924
4925
/**
4926
 * Strips out invalid html entities, replaces others with html style &#123; codes
4927
 *
4928
 * Callback function used of preg_replace_callback in smcFunc $ent_checks, for example
4929
 * strpos, strlen, substr etc
4930
 *
4931
 * @param array $matches An array of matches (relevant info should be the 3rd item in the array)
4932
 * @return string The fixed string
4933
 */
4934
function entity_fix__callback($matches)
4935
{
4936
	if (!isset($matches[2]))
4937
		return '';
4938
4939
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4940
4941
	// we don't allow control characters, characters out of range, byte markers, etc
4942
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
4943
		return '';
4944
	else
4945
		return '&#' . $num . ';';
4946
}
4947
4948
/**
4949
 * Return a Gravatar URL based on
4950
 * - the supplied email address,
4951
 * - the global maximum rating,
4952
 * - the global default fallback,
4953
 * - maximum sizes as set in the admin panel.
4954
 *
4955
 * It is SSL aware, and caches most of the parameters.
4956
 *
4957
 * @param string $email_address The user's email address
4958
 * @return string The gravatar URL
4959
 */
4960
function get_gravatar_url($email_address)
4961
{
4962
	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...
4963
	static $url_params = null;
4964
4965
	if ($url_params === null)
4966
	{
4967
		$ratings = array('G', 'PG', 'R', 'X');
4968
		$defaults = array('mm', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank');
4969
		$url_params = array();
4970 View Code Duplication
		if (!empty($modSettings['gravatarMaxRating']) && in_array($modSettings['gravatarMaxRating'], $ratings))
4971
			$url_params[] = 'rating=' . $modSettings['gravatarMaxRating'];
4972 View Code Duplication
		if (!empty($modSettings['gravatarDefault']) && in_array($modSettings['gravatarDefault'], $defaults))
4973
			$url_params[] = 'default=' . $modSettings['gravatarDefault'];
4974
		if (!empty($modSettings['avatar_max_width_external']))
4975
			$size_string = (int) $modSettings['avatar_max_width_external'];
4976
		if (!empty($modSettings['avatar_max_height_external']) && !empty($size_string))
4977
			if ((int) $modSettings['avatar_max_height_external'] < $size_string)
4978
				$size_string = $modSettings['avatar_max_height_external'];
4979
4980
		if (!empty($size_string))
4981
			$url_params[] = 's=' . $size_string;
4982
	}
4983
	$http_method = !empty($modSettings['force_ssl']) && $modSettings['force_ssl'] == 2 ? 'https://secure' : 'http://www';
4984
4985
	return $http_method . '.gravatar.com/avatar/' . md5($smcFunc['strtolower']($email_address)) . '?' . implode('&', $url_params);
4986
}
4987
4988
/**
4989
 * Get a list of timezones.
4990
 *
4991
 * @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'.
4992
 * @return array An array of timezone info.
4993
 */
4994
function smf_list_timezones($when = 'now')
4995
{
4996
	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...
4997
	static $timezones = null, $lastwhen = null;
4998
4999
	// No point doing this over if we already did it once
5000
	if (!empty($timezones) && $when == $lastwhen)
5001
		return $timezones;
5002
	else
5003
		$lastwhen = $when;
5004
5005
	// Parseable datetime string?
5006
	if (is_int($timestamp = strtotime($when)))
5007
		$when = $timestamp;
5008
5009
	// A Unix timestamp?
5010
	elseif (is_numeric($when))
5011
		$when = intval($when);
5012
5013
	// Invalid value? Just get current Unix timestamp.
5014
	else
5015
		$when = time();
5016
5017
	// We'll need these too
5018
	$date_when = date_create('@' . $when);
5019
	$later = (int) date_format(date_add($date_when, date_interval_create_from_date_string('1 year')), 'U');
5020
5021
	// Prefer and give custom descriptions for these time zones
5022
	// If the description is left empty, it will be filled in with the names of matching cities
5023
	$timezone_descriptions = array(
5024
		'America/Adak' => 'Aleutian Islands',
5025
		'Pacific/Marquesas' => 'Marquesas Islands',
5026
		'Pacific/Gambier' => 'Gambier Islands',
5027
		'America/Anchorage' => 'Alaska',
5028
		'Pacific/Pitcairn' => 'Pitcairn Islands',
5029
		'America/Los_Angeles' => 'Pacific Time (USA, Canada)',
5030
		'America/Denver' => 'Mountain Time (USA, Canada)',
5031
		'America/Phoenix' => 'Mountain Time (no DST)',
5032
		'America/Chicago' => 'Central Time (USA, Canada)',
5033
		'America/Belize' => 'Central Time (no DST)',
5034
		'America/New_York' => 'Eastern Time (USA, Canada)',
5035
		'America/Atikokan' => 'Eastern Time (no DST)',
5036
		'America/Halifax' => 'Atlantic Time (Canada)',
5037
		'America/Anguilla' => 'Atlantic Time (no DST)',
5038
		'America/St_Johns' => 'Newfoundland',
5039
		'America/Chihuahua' => 'Chihuahua, Mazatlan',
5040
		'Pacific/Easter' => 'Easter Island',
5041
		'Atlantic/Stanley' => 'Falkland Islands',
5042
		'America/Miquelon' => 'Saint Pierre and Miquelon',
5043
		'America/Argentina/Buenos_Aires' => 'Buenos Aires',
5044
		'America/Sao_Paulo' => 'Brasilia Time',
5045
		'America/Araguaina' => 'Brasilia Time (no DST)',
5046
		'America/Godthab' => 'Greenland',
5047
		'America/Noronha' => 'Fernando de Noronha',
5048
		'Atlantic/Reykjavik' => 'Greenwich Mean Time (no DST)',
5049
		'Europe/London' => '',
5050
		'Europe/Berlin' => 'Central European Time',
5051
		'Europe/Helsinki' => 'Eastern European Time',
5052
		'Africa/Brazzaville' => 'Brazzaville, Lagos, Porto-Novo',
5053
		'Asia/Jerusalem' => 'Jerusalem',
5054
		'Europe/Moscow' => '',
5055
		'Africa/Khartoum' => 'Eastern Africa Time',
5056
		'Asia/Riyadh' => 'Arabia Time',
5057
		'Asia/Kolkata' => 'India, Sri Lanka',
5058
		'Asia/Yekaterinburg' => 'Yekaterinburg, Tyumen',
5059
		'Asia/Dhaka' => 'Astana, Dhaka',
5060
		'Asia/Rangoon' => 'Yangon/Rangoon',
5061
		'Indian/Christmas' => 'Christmas Island',
5062
		'Antarctica/DumontDUrville' => 'Dumont D\'Urville Station',
5063
		'Antarctica/Vostok' => 'Vostok Station',
5064
		'Australia/Lord_Howe' => 'Lord Howe Island',
5065
		'Pacific/Guadalcanal' => 'Solomon Islands',
5066
		'Pacific/Norfolk' => 'Norfolk Island',
5067
		'Pacific/Noumea' => 'New Caledonia',
5068
		'Pacific/Auckland' => 'Auckland, McMurdo Station',
5069
		'Pacific/Kwajalein' => 'Marshall Islands',
5070
		'Pacific/Chatham' => 'Chatham Islands',
5071
	);
5072
5073
	// Should we put time zones from certain countries at the top of the list?
5074
	$priority_countries = !empty($modSettings['timezone_priority_countries']) ? explode(',', $modSettings['timezone_priority_countries']) : array();
5075
	$priority_tzids = array();
5076
	foreach ($priority_countries as $country)
5077
	{
5078
		$country_tzids = @timezone_identifiers_list(DateTimeZone::PER_COUNTRY, strtoupper(trim($country)));
5079
		if (!empty($country_tzids))
5080
			$priority_tzids = array_merge($priority_tzids, $country_tzids);
5081
	}
5082
5083
	// Process the preferred timezones first, then the rest.
5084
	$tzids = array_keys($timezone_descriptions) + array_diff(timezone_identifiers_list(), array_keys($timezone_descriptions));
5085
5086
	// Idea here is to get exactly one representative identifier for each and every unique set of time zone rules.
5087
	foreach ($tzids as $tzid)
5088
	{
5089
		// We don't want UTC right now
5090
		if ($tzid == 'UTC')
5091
			continue;
5092
5093
		$tz = timezone_open($tzid);
5094
5095
		// First, get the set of transition rules for this tzid
5096
		$tzinfo = timezone_transitions_get($tz, $when, $later);
5097
5098
		// Use the entire set of transition rules as the array *key* so we can avoid duplicates
5099
		$tzkey = serialize($tzinfo);
5100
5101
		// Next, get the geographic info for this tzid
5102
		$tzgeo = timezone_location_get($tz);
5103
5104
		// Don't overwrite our preferred tzids
5105
		if (empty($zones[$tzkey]['tzid']))
5106
		{
5107
			$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...
5108
			$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...
5109
		}
5110
5111
		// A time zone from a prioritized country?
5112
		if (in_array($tzid, $priority_tzids))
5113
			$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...
5114
5115
		// Keep track of the location and offset for this tzid
5116
		$tzid_parts = explode('/', $tzid);
5117
		$zones[$tzkey]['locations'][] = str_replace(array('St_', '_'), array('St. ', ' '), array_pop($tzid_parts));
5118
		$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...
5119
		$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...
5120
	}
5121
5122
	// Sort by offset then longitude
5123
	array_multisort($offsets, SORT_ASC, SORT_NUMERIC, $longitudes, SORT_ASC, SORT_NUMERIC, $zones);
5124
5125
	// Build the final array of formatted values
5126
	$priority_timezones = array();
5127
	$timezones = array();
5128
	foreach ($zones as $tzkey => $tzvalue)
5129
	{
5130
		date_timezone_set($date_when, timezone_open($tzvalue['tzid']));
5131
5132
		if (!empty($timezone_descriptions[$tzvalue['tzid']]))
5133
			$desc = $timezone_descriptions[$tzvalue['tzid']];
5134
		else
5135
			$desc = implode(', ', array_unique($tzvalue['locations']));
5136
5137
		if (isset($priority_zones[$tzkey]))
5138
			$priority_timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']';
5139
		else
5140
			$timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']';
5141
	}
5142
5143
	$timezones = array_merge(
5144
		$priority_timezones,
5145
		array('' => '(Forum Default)', 'UTC' => 'UTC - Coordinated Universal Time'),
5146
		$timezones
5147
	);
5148
5149
	return $timezones;
5150
}
5151
5152
/**
5153
 * Reformats certain time zone abbreviations to look better.
5154
 *
5155
 * Some of PHP's time zone abbreviations are just numerical offsets from UTC, e.g. '+04'
5156
 * These look weird and are kind of useless, so we make them look better.
5157
 *
5158
 * @param string $tzid The Olsen time zome identifier for a time zone.
5159
 * @param string $tz_abbrev The abbreviation PHP provided for this time zone.
5160
 * @return string The fixed version of $tz_abbrev.
5161
 */
5162
function fix_tz_abbrev($tzid, $tz_abbrev)
5163
{
5164
	// Is this abbreviation just a numerical offset?
5165
	if (strspn($tz_abbrev, '+-') > 0)
5166
	{
5167
		// To get on this list, a time zone must be historically stable and must not observe daylight saving time
5168
		$missing_tz_abbrs = array(
5169
			'Antarctica/Casey' => 'CAST',
5170
			'Antarctica/Davis' => 'DAVT',
5171
			'Antarctica/DumontDUrville' => 'DDUT',
5172
			'Antarctica/Mawson' => 'MAWT',
5173
			'Antarctica/Rothera' => 'ART',
5174
			'Antarctica/Syowa' => 'SYOT',
5175
			'Antarctica/Vostok' => 'VOST',
5176
			'Asia/Almaty' => 'ALMT',
5177
			'Asia/Aqtau' => 'ORAT',
5178
			'Asia/Aqtobe' => 'AQTT',
5179
			'Asia/Ashgabat' => 'TMT',
5180
			'Asia/Bishkek' => 'KGT',
5181
			'Asia/Colombo' => 'IST',
5182
			'Asia/Dushanbe' => 'TJT',
5183
			'Asia/Oral' => 'ORAT',
5184
			'Asia/Qyzylorda' => 'QYZT',
5185
			'Asia/Samarkand' => 'UZT',
5186
			'Asia/Tashkent' => 'UZT',
5187
			'Asia/Tbilisi' => 'GET',
5188
			'Asia/Yerevan' => 'AMT',
5189
			'Europe/Istanbul' => 'TRT',
5190
			'Europe/Minsk' => 'MSK',
5191
			'Indian/Kerguelen' => 'TFT',
5192
		);
5193
5194
		if (!empty($missing_tz_abbrs[$tzid]))
5195
			$tz_abbrev = $missing_tz_abbrs[$tzid];
5196
		else
5197
		{
5198
			// Russia likes to experiment with time zones often, and names them as offsets from Moscow
5199
			$tz_location = timezone_location_get(timezone_open($tzid));
5200
			if ($tz_location['country_code'] == 'RU')
5201
			{
5202
				$msk_offset = intval($tz_abbrev) - 3;
5203
				$tz_abbrev = 'MSK' . (!empty($msk_offset) ? sprintf('%+0d', $msk_offset) : '');
5204
			}
5205
		}
5206
5207
		// Still no good? We'll just mark it as a UTC offset
5208
		if (strspn($tz_abbrev, '+-') > 0)
5209
		{
5210
			$tz_abbrev = intval($tz_abbrev);
5211
			$tz_abbrev = 'UTC' . (!empty($tz_abbrev) ? sprintf('%+0d', $tz_abbrev) : '');
5212
		}
5213
	}
5214
5215
	return $tz_abbrev;
5216
}
5217
5218
/**
5219
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
5220
 * @return string|false The IP address in binary or false
5221
 */
5222
function inet_ptod($ip_address)
5223
{
5224
	if (!isValidIP($ip_address))
5225
		return $ip_address;
5226
5227
	$bin = inet_pton($ip_address);
5228
	return $bin;
5229
}
5230
5231
/**
5232
 * @param string $bin An IP address in IPv4, IPv6 (Either string (postgresql) or binary (other databases))
5233
 * @return string|false The IP address in presentation format or false on error
5234
 */
5235
function inet_dtop($bin)
5236
{
5237
	if(empty($bin))
5238
		return '';
5239
5240
	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...
5241
5242
	if ($db_type == 'postgresql')
5243
		return $bin;
5244
5245
	$ip_address = inet_ntop($bin);
5246
5247
	return $ip_address;
5248
}
5249
5250
/**
5251
 * Safe serialize() and unserialize() replacements
5252
 *
5253
 * @license Public Domain
5254
 *
5255
 * @author anthon (dot) pang (at) gmail (dot) com
5256
 */
5257
5258
/**
5259
 * Safe serialize() replacement. Recursive
5260
 * - output a strict subset of PHP's native serialized representation
5261
 * - does not serialize objects
5262
 *
5263
 * @param mixed $value
5264
 * @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...
5265
 */
5266
function _safe_serialize($value)
5267
{
5268
	if(is_null($value))
5269
		return 'N;';
5270
5271
	if(is_bool($value))
5272
		return 'b:'. (int) $value .';';
5273
5274
	if(is_int($value))
5275
		return 'i:'. $value .';';
5276
5277
	if(is_float($value))
5278
		return 'd:'. str_replace(',', '.', $value) .';';
5279
5280
	if(is_string($value))
5281
		return 's:'. strlen($value) .':"'. $value .'";';
5282
5283
	if(is_array($value))
5284
	{
5285
		$out = '';
5286
		foreach($value as $k => $v)
5287
			$out .= _safe_serialize($k) . _safe_serialize($v);
5288
5289
		return 'a:'. count($value) .':{'. $out .'}';
5290
	}
5291
5292
	// safe_serialize cannot serialize resources or objects.
5293
	return false;
5294
}
5295
/**
5296
 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issues.
5297
 *
5298
 * @param mixed $value
5299
 * @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...
5300
 */
5301 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...
5302
{
5303
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5304
	if (function_exists('mb_internal_encoding') &&
5305
		(((int) ini_get('mbstring.func_overload')) & 2))
5306
	{
5307
		$mbIntEnc = mb_internal_encoding();
5308
		mb_internal_encoding('ASCII');
5309
	}
5310
5311
	$out = _safe_serialize($value);
5312
5313
	if (isset($mbIntEnc))
5314
		mb_internal_encoding($mbIntEnc);
5315
5316
	return $out;
5317
}
5318
5319
/**
5320
 * Safe unserialize() replacement
5321
 * - accepts a strict subset of PHP's native serialized representation
5322
 * - does not unserialize objects
5323
 *
5324
 * @param string $str
5325
 * @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...
5326
 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
5327
 */
5328
function _safe_unserialize($str)
5329
{
5330
	// Input  is not a string.
5331
	if(empty($str) || !is_string($str))
5332
		return false;
5333
5334
	$stack = array();
5335
	$expected = array();
5336
5337
	/*
5338
	 * states:
5339
	 *   0 - initial state, expecting a single value or array
5340
	 *   1 - terminal state
5341
	 *   2 - in array, expecting end of array or a key
5342
	 *   3 - in array, expecting value or another array
5343
	 */
5344
	$state = 0;
5345
	while($state != 1)
5346
	{
5347
		$type = isset($str[0]) ? $str[0] : '';
5348
		if($type == '}')
5349
			$str = substr($str, 1);
5350
5351
		else if($type == 'N' && $str[1] == ';')
5352
		{
5353
			$value = null;
5354
			$str = substr($str, 2);
5355
		}
5356
		else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
5357
		{
5358
			$value = $matches[1] == '1' ? true : false;
5359
			$str = substr($str, 4);
5360
		}
5361
		else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
5362
		{
5363
			$value = (int)$matches[1];
5364
			$str = $matches[2];
5365
		}
5366
		else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
5367
		{
5368
			$value = (float)$matches[1];
5369
			$str = $matches[3];
5370
		}
5371
		else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
5372
		{
5373
			$value = substr($matches[2], 0, (int)$matches[1]);
5374
			$str = substr($matches[2], (int)$matches[1] + 2);
5375
		}
5376
		else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches))
5377
		{
5378
			$expectedLength = (int)$matches[1];
5379
			$str = $matches[2];
5380
		}
5381
5382
		// Object or unknown/malformed type.
5383
		else
5384
			return false;
5385
5386
		switch($state)
5387
		{
5388
			case 3: // In array, expecting value or another array.
5389
				if($type == 'a')
5390
				{
5391
					$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...
5392
					$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...
5393
					$list = &$list[$key];
5394
					$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...
5395
					$state = 2;
5396
					break;
5397
				}
5398
				if($type != '}')
5399
				{
5400
					$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...
5401
					$state = 2;
5402
					break;
5403
				}
5404
5405
				// Missing array value.
5406
				return false;
5407
5408
			case 2: // in array, expecting end of array or a key
5409
				if($type == '}')
5410
				{
5411
					// Array size is less than expected.
5412
					if(count($list) < end($expected))
5413
						return false;
5414
5415
					unset($list);
5416
					$list = &$stack[count($stack)-1];
5417
					array_pop($stack);
5418
5419
					// Go to terminal state if we're at the end of the root array.
5420
					array_pop($expected);
5421
5422
					if(count($expected) == 0)
5423
						$state = 1;
5424
5425
					break;
5426
				}
5427
5428
				if($type == 'i' || $type == 's')
5429
				{
5430
					// Array size exceeds expected length.
5431
					if(count($list) >= end($expected))
5432
						return false;
5433
5434
					$key = $value;
5435
					$state = 3;
5436
					break;
5437
				}
5438
5439
				// Illegal array index type.
5440
				return false;
5441
5442
			// Expecting array or value.
5443
			case 0:
5444
				if($type == 'a')
5445
				{
5446
					$data = array();
5447
					$list = &$data;
5448
					$expected[] = $expectedLength;
5449
					$state = 2;
5450
					break;
5451
				}
5452
5453
				if($type != '}')
5454
				{
5455
					$data = $value;
5456
					$state = 1;
5457
					break;
5458
				}
5459
5460
				// Not in array.
5461
				return false;
5462
		}
5463
	}
5464
5465
	// Trailing data in input.
5466
	if(!empty($str))
5467
		return false;
5468
5469
	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...
5470
}
5471
5472
/**
5473
 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
5474
 *
5475
 * @param string $str
5476
 * @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...
5477
 */
5478 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...
5479
{
5480
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5481
	if (function_exists('mb_internal_encoding') &&
5482
		(((int) ini_get('mbstring.func_overload')) & 0x02))
5483
	{
5484
		$mbIntEnc = mb_internal_encoding();
5485
		mb_internal_encoding('ASCII');
5486
	}
5487
5488
	$out = _safe_unserialize($str);
5489
5490
	if (isset($mbIntEnc))
5491
		mb_internal_encoding($mbIntEnc);
5492
5493
	return $out;
5494
}
5495
5496
/**
5497
 * Tries different modes to make file/dirs writable. Wrapper function for chmod()
5498
5499
 * @param string $file The file/dir full path.
5500
 * @param int $value Not needed, added for legacy reasons.
5501
 * @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.
5502
 */
5503
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...
5504
{
5505
	// No file? no checks!
5506
	if (empty($file))
5507
		return false;
5508
5509
	// Already writable?
5510
	if (is_writable($file))
5511
		return true;
5512
5513
	// Do we have a file or a dir?
5514
	$isDir = is_dir($file);
5515
	$isWritable = false;
5516
5517
	// Set different modes.
5518
	$chmodValues = $isDir ? array(0750, 0755, 0775, 0777) : array(0644, 0664, 0666);
5519
5520
	foreach($chmodValues as $val)
5521
	{
5522
		// If it's writable, break out of the loop.
5523
		if (is_writable($file))
5524
		{
5525
			$isWritable = true;
5526
			break;
5527
		}
5528
5529
		else
5530
			@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 1788
  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 1788
  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 1834
  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 1834
  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 2843
  9. $chmod_file is passed to smf_chmod()
    in Sources/Subs-Package.php on line 2868
  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 842
  5. $file is passed to package_chmod()
    in Sources/Subs-Package.php on line 853
  6. $chmod_file is assigned
    in Sources/Subs-Package.php on line 2843
  7. $chmod_file is passed to smf_chmod()
    in Sources/Subs-Package.php on line 2868
  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 842
  8. $file is passed to package_chmod()
    in Sources/Subs-Package.php on line 853
  9. $chmod_file is assigned
    in Sources/Subs-Package.php on line 2843
  10. $chmod_file is passed to smf_chmod()
    in Sources/Subs-Package.php on line 2868
  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 542
  4. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 560
  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 1326
  9. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1735
  10. $this_action is assigned
    in Sources/Subs-Package.php on line 1326
  11. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1334
  12. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1834
  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 542
  4. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 560
  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 1326
  9. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1735
  10. $this_action is assigned
    in Sources/Subs-Package.php on line 1326
  11. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1334
  12. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1834
  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 542
  16. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 560
  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 1326
  21. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1735
  22. $this_action is assigned
    in Sources/Subs-Package.php on line 1326
  23. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1334
  24. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1834
  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 542
  16. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 560
  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 1326
  21. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1735
  22. $this_action is assigned
    in Sources/Subs-Package.php on line 1326
  23. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1334
  24. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1834
  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 542
  17. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 560
  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 1326
  22. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1735
  23. $this_action is assigned
    in Sources/Subs-Package.php on line 1326
  24. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1334
  25. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1834
  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 542
  17. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 560
  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 1326
  22. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1735
  23. $this_action is assigned
    in Sources/Subs-Package.php on line 1326
  24. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1334
  25. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1834
  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 542
  8. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 560
  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 1326
  13. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1735
  14. $this_action is assigned
    in Sources/Subs-Package.php on line 1326
  15. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1334
  16. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1834
  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 542
  9. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 560
  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 1326
  14. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1735
  15. $this_action is assigned
    in Sources/Subs-Package.php on line 1326
  16. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1334
  17. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1834

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...
5531
	}
5532
5533
	return $isWritable;
5534
}
5535
5536
/**
5537
 * Wrapper function for json_decode() with error handling.
5538
5539
 * @param string $json The string to decode.
5540
 * @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.
5541
 * @param bool $logIt To specify if the error will be logged if theres any.
5542
 * @return array Either an empty array or the decoded data as an array.
5543
 */
5544
function smf_json_decode($json, $returnAsArray = false, $logIt = true)
5545
{
5546
	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...
5547
5548
	// Come on...
5549
	if (empty($json) || !is_string($json))
5550
		return array();
5551
5552
	$returnArray = @json_decode($json, $returnAsArray);
5553
5554
	// PHP 5.3 so no json_last_error_msg()
5555
	switch(json_last_error())
5556
	{
5557
		case JSON_ERROR_NONE:
5558
			$jsonError = false;
5559
			break;
5560
		case JSON_ERROR_DEPTH:
5561
			$jsonError =  'JSON_ERROR_DEPTH';
5562
			break;
5563
		case JSON_ERROR_STATE_MISMATCH:
5564
			$jsonError = 'JSON_ERROR_STATE_MISMATCH';
5565
			break;
5566
		case JSON_ERROR_CTRL_CHAR:
5567
			$jsonError = 'JSON_ERROR_CTRL_CHAR';
5568
			break;
5569
		case JSON_ERROR_SYNTAX:
5570
			$jsonError = 'JSON_ERROR_SYNTAX';
5571
			break;
5572
		case JSON_ERROR_UTF8:
5573
			$jsonError = 'JSON_ERROR_UTF8';
5574
			break;
5575
		default:
5576
			$jsonError = 'unknown';
5577
			break;
5578
	}
5579
5580
	// Something went wrong!
5581
	if (!empty($jsonError) && $logIt)
5582
	{
5583
		// Being a wrapper means we lost our smf_error_handler() privileges :(
5584
		$jsonDebug = debug_backtrace();
5585
		$jsonDebug = $jsonDebug[0];
5586
		loadLanguage('Errors');
5587
5588
		if (!empty($jsonDebug))
5589
			log_error($txt['json_'. $jsonError], 'critical', $jsonDebug['file'], $jsonDebug['line']);
5590
5591
		else
5592
			log_error($txt['json_'. $jsonError], 'critical');
5593
5594
		// Everyone expects an array.
5595
		return array();
5596
	}
5597
5598
	return $returnArray;
5599
}
5600
5601
/**
5602
 * Check the given String if he is a valid IPv4 or IPv6
5603
 * return true or false
5604
 *
5605
 * @param string $IPString
5606
 *
5607
 * @return bool
5608
 */
5609
function isValidIP($IPString)
5610
{
5611
	return filter_var($IPString, FILTER_VALIDATE_IP) !== false;
5612
}
5613
5614
/**
5615
 * Outputs a response.
5616
 * It assumes the data is already a string.
5617
 * @param string $data The data to print
5618
 * @param string $type The content type. Defaults to Json.
5619
 * @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...
5620
 */
5621
function smf_serverResponse($data = '', $type = 'Content-Type: application/json')
5622
{
5623
	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...
5624
5625
	// Defensive programming anyone?
5626
	if (empty($data))
5627
		return false;
5628
5629
	// Don't need extra stuff...
5630
	$db_show_debug = false;
5631
5632
	// Kill anything else.
5633
	ob_end_clean();
5634
5635
	if (!empty($modSettings['CompressedOutput']))
5636
		@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...
5637
5638
	else
5639
		ob_start();
5640
5641
	// Set the header.
5642
	header($type);
5643
5644
	// Echo!
5645
	echo $data;
5646
5647
	// Done.
5648
	obExit(false);
5649
}
5650
5651
/**
5652
 * Creates an optimized regex to match all known top level domains.
5653
 *
5654
 * The optimized regex is stored in $modSettings['tld_regex'].
5655
 *
5656
 * To update the stored version of the regex to use the latest list of valid TLDs from iana.org, set
5657
 * the $update parameter to true. Updating can take some time, based on network connectivity, so it
5658
 * should normally only be done by calling this function from a background or scheduled task.
5659
 *
5660
 * If $update is not true, but the regex is missing or invalid, the regex will be regenerated from a
5661
 * hard-coded list of TLDs. This regenerated regex will be overwritten on the next scheduled update.
5662
 *
5663
 * @param bool $update If true, fetch and process the latest offical list of TLDs from iana.org.
5664
 */
5665
function set_tld_regex($update = false)
5666
{
5667
	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...
5668
	static $done = false;
5669
5670
	// If we don't need to do anything, don't
5671
	if (!$update && $done)
5672
		return;
5673
5674
	// Should we get a new copy of the official list of TLDs?
5675
	if ($update)
5676
	{
5677
		require_once($sourcedir . '/Subs-Package.php');
5678
		$tlds = fetch_web_data('https://data.iana.org/TLD/tlds-alpha-by-domain.txt');
5679
5680
		// 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.
5681
		if ($tlds === false)
5682
			$postapocalypticNightmare = true;
5683
	}
5684
	// If we aren't updating and the regex is valid, we're done
5685
	elseif (!empty($modSettings['tld_regex']) && @preg_match('~' . $modSettings['tld_regex'] . '~', null) !== false)
5686
	{
5687
		$done = true;
5688
		return;
5689
	}
5690
5691
	// If we successfully got an update, process the list into an array
5692
	if (!empty($tlds))
5693
	{
5694
		// Clean $tlds and convert it to an array
5695
		$tlds = array_filter(explode("\n", strtolower($tlds)), function($line) {
5696
			$line = trim($line);
5697
			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...
5698
				return false;
5699
			else
5700
				return true;
5701
		});
5702
5703
		// Convert Punycode to Unicode
5704
		$tlds = array_map(function ($input) {
5705
			$prefix = 'xn--';
5706
			$safe_char = 0xFFFC;
5707
			$base = 36;
5708
			$tmin = 1;
5709
			$tmax = 26;
5710
			$skew = 38;
5711
			$damp = 700;
5712
			$output_parts = array();
5713
5714
			$input = str_replace(strtoupper($prefix), $prefix, $input);
5715
5716
			$enco_parts = (array) explode('.', $input);
5717
5718
			foreach ($enco_parts as $encoded)
5719
			{
5720
				if (strpos($encoded,$prefix) !== 0 || strlen(trim(str_replace($prefix,'',$encoded))) == 0)
5721
				{
5722
					$output_parts[] = $encoded;
5723
					continue;
5724
				}
5725
5726
				$is_first = true;
5727
				$bias = 72;
5728
				$idx = 0;
5729
				$char = 0x80;
5730
				$decoded = array();
5731
				$output='';
5732
				$delim_pos = strrpos($encoded, '-');
5733
5734
				if ($delim_pos > strlen($prefix))
5735
				{
5736
					for ($k = strlen($prefix); $k < $delim_pos; ++$k)
5737
					{
5738
						$decoded[] = ord($encoded{$k});
5739
					}
5740
				}
5741
5742
				$deco_len = count($decoded);
5743
				$enco_len = strlen($encoded);
5744
5745
				for ($enco_idx = $delim_pos ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len)
5746
				{
5747
					for ($old_idx = $idx, $w = 1, $k = $base; 1 ; $k += $base)
5748
					{
5749
						$cp = ord($encoded{$enco_idx++});
5750
						$digit = ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $base));
5751
						$idx += $digit * $w;
5752
						$t = ($k <= $bias) ? $tmin : (($k >= $bias + $tmax) ? $tmax : ($k - $bias));
5753
5754
						if ($digit < $t)
5755
							break;
5756
5757
						$w = (int) ($w * ($base - $t));
5758
					}
5759
5760
					$delta = $idx - $old_idx;
5761
					$delta = intval($is_first ? ($delta / $damp) : ($delta / 2));
5762
					$delta += intval($delta / ($deco_len + 1));
5763
5764
					for ($k = 0; $delta > (($base - $tmin) * $tmax) / 2; $k += $base)
5765
						$delta = intval($delta / ($base - $tmin));
5766
5767
					$bias = intval($k + ($base - $tmin + 1) * $delta / ($delta + $skew));
5768
					$is_first = false;
5769
					$char += (int) ($idx / ($deco_len + 1));
5770
					$idx %= ($deco_len + 1);
5771
5772
					if ($deco_len > 0)
5773
					{
5774
						for ($i = $deco_len; $i > $idx; $i--)
5775
							$decoded[$i] = $decoded[($i - 1)];
5776
					}
5777
					$decoded[$idx++] = $char;
5778
				}
5779
5780
				foreach ($decoded as $k => $v)
5781
				{
5782
					// 7bit are transferred literally
5783
					if ($v < 128)
5784
						$output .= chr($v);
5785
5786
					// 2 bytes
5787
					elseif ($v < (1 << 11))
5788
						$output .= chr(192+($v >> 6)) . chr(128+($v & 63));
5789
5790
					// 3 bytes
5791
					elseif ($v < (1 << 16))
5792
						$output .= chr(224+($v >> 12)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5793
5794
					// 4 bytes
5795
					elseif ($v < (1 << 21))
5796
						$output .= chr(240+($v >> 18)) . chr(128+(($v >> 12) & 63)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5797
5798
					//  'Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k
5799
					else
5800
						$output .= $safe_char;
5801
				}
5802
5803
				$output_parts[] = $output;
5804
			}
5805
5806
			return implode('.', $output_parts);
5807
		}, $tlds);
5808
	}
5809
	// Otherwise, use the 2012 list of gTLDs and ccTLDs for now and schedule a background update
5810
	else
5811
	{
5812
		$tlds = array('com', 'net', 'org', 'edu', 'gov', 'mil', 'aero', 'asia', 'biz', 'cat',
5813
			'coop', 'info', 'int', 'jobs', 'mobi', 'museum', 'name', 'post', 'pro', 'tel',
5814
			'travel', 'xxx', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq',
5815
			'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
5816
			'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc',
5817
			'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cs', 'cu', 'cv',
5818
			'cx', 'cy', 'cz', 'dd', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh',
5819
			'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge',
5820
			'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw',
5821
			'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq',
5822
			'ir', 'is', 'it', 'ja', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn',
5823
			'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu',
5824
			'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp',
5825
			'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf',
5826
			'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph',
5827
			'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru',
5828
			'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn',
5829
			'so', 'sr', 'ss', 'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th',
5830
			'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug',
5831
			'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye',
5832
			'yt', 'yu', 'za', 'zm', 'zw');
5833
5834
		// Schedule a background update, unless civilization has collapsed and/or we are having connectivity issues.
5835
		$schedule_update = empty($postapocalypticNightmare);
5836
	}
5837
5838
	// Get an optimized regex to match all the TLDs
5839
	$tld_regex = build_regex($tlds);
5840
5841
	// Remember the new regex in $modSettings
5842
	updateSettings(array('tld_regex' => $tld_regex));
5843
5844
	// Schedule a background update if we need one
5845 View Code Duplication
	if (!empty($schedule_update))
5846
	{
5847
		$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
5848
			array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
5849
			array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array()
5850
		);
5851
	}
5852
5853
	// Redundant repetition is redundant
5854
	$done = true;
5855
}
5856
5857
/**
5858
 * Creates optimized regular expressions from an array of strings.
5859
 *
5860
 * An optimized regex built using this function will be much faster than a simple regex built using
5861
 * `implode('|', $strings)` --- anywhere from several times to several orders of magnitude faster.
5862
 *
5863
 * However, the time required to build the optimized regex is approximately equal to the time it
5864
 * takes to execute the simple regex. Therefore, it is only worth calling this function if the
5865
 * resulting regex will be used more than once.
5866
 *
5867
 * Because PHP places an upper limit on the allowed length of a regex, very large arrays of $strings
5868
 * may not fit in a single regex. Normally, the excess strings will simply be dropped. However, if
5869
 * the $returnArray parameter is set to true, this function will build as many regexes as necessary
5870
 * to accomodate everything in $strings and return them in an array. You will need to iterate
5871
 * through all elements of the returned array in order to test all possible matches.
5872
 *
5873
 * @param array $strings An array of strings to make a regex for.
5874
 * @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...
5875
 * @param bool $returnArray If true, returns an array of regexes.
5876
 * @return string|array One or more regular expressions to match any of the input strings.
5877
 */
5878
function build_regex($strings, $delim = null, $returnArray = false)
5879
{
5880
	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...
5881
5882
	// The mb_* functions are faster than the $smcFunc ones, but may not be available
5883
	if (function_exists('mb_internal_encoding') && function_exists('mb_detect_encoding') && function_exists('mb_strlen') && function_exists('mb_substr'))
5884
	{
5885
		if (($string_encoding = mb_detect_encoding(implode(' ', $strings))) !== false)
5886
		{
5887
			$current_encoding = mb_internal_encoding();
5888
			mb_internal_encoding($string_encoding);
5889
		}
5890
5891
		$strlen = 'mb_strlen';
5892
		$substr = 'mb_substr';
5893
	}
5894
	else
5895
	{
5896
		$strlen = $smcFunc['strlen'];
5897
		$substr = $smcFunc['substr'];
5898
	}
5899
5900
	// This recursive function creates the index array from the strings
5901
	$add_string_to_index = function ($string, $index) use (&$strlen, &$substr, &$add_string_to_index)
5902
	{
5903
		static $depth = 0;
5904
		$depth++;
5905
5906
		$first = $substr($string, 0, 1);
5907
5908
		if (empty($index[$first]))
5909
			$index[$first] = array();
5910
5911
		if ($strlen($string) > 1)
5912
		{
5913
			// Sanity check on recursion
5914
			if ($depth > 99)
5915
				$index[$first][$substr($string, 1)] = '';
5916
5917
			else
5918
				$index[$first] = $add_string_to_index($substr($string, 1), $index[$first]);
5919
		}
5920
		else
5921
			$index[$first][''] = '';
5922
5923
		$depth--;
5924
		return $index;
5925
	};
5926
5927
	// This recursive function turns the index array into a regular expression
5928
	$index_to_regex = function (&$index, $delim) use (&$strlen, &$index_to_regex)
5929
	{
5930
		static $depth = 0;
5931
		$depth++;
5932
5933
		// Absolute max length for a regex is 32768, but we might need wiggle room
5934
		$max_length = 30000;
5935
5936
		$regex = array();
5937
		$length = 0;
5938
5939
		foreach ($index as $key => $value)
5940
		{
5941
			$key_regex = preg_quote($key, $delim);
5942
			$new_key = $key;
5943
5944
			if (empty($value))
5945
				$sub_regex = '';
5946
			else
5947
			{
5948
				$sub_regex = $index_to_regex($value, $delim);
5949
5950
				if (count(array_keys($value)) == 1)
5951
				{
5952
					$new_key_array = explode('(?'.'>', $sub_regex);
5953
					$new_key .= $new_key_array[0];
5954
				}
5955
				else
5956
					$sub_regex = '(?'.'>' . $sub_regex . ')';
5957
			}
5958
5959
			if ($depth > 1)
5960
				$regex[$new_key] = $key_regex . $sub_regex;
5961
			else
5962
			{
5963
				if (($length += strlen($key_regex) + 1) < $max_length || empty($regex))
5964
				{
5965
					$regex[$new_key] = $key_regex . $sub_regex;
5966
					unset($index[$key]);
5967
				}
5968
				else
5969
					break;
5970
			}
5971
		}
5972
5973
		// Sort by key length and then alphabetically
5974
		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...
5975
			$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...
5976
			$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...
5977
5978
			if ($l1 == $l2)
5979
				return strcmp($k1, $k2) > 0 ? 1 : -1;
5980
			else
5981
				return $l1 > $l2 ? -1 : 1;
5982
		});
5983
5984
		$depth--;
5985
		return implode('|', $regex);
5986
	};
5987
5988
	// Now that the functions are defined, let's do this thing
5989
	$index = array();
5990
	$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...
5991
5992
	foreach ($strings as $string)
5993
		$index = $add_string_to_index($string, $index);
5994
5995
	if ($returnArray === true)
5996
	{
5997
		$regex = array();
5998
		while (!empty($index))
5999
			$regex[] = '(?'.'>' . $index_to_regex($index, $delim) . ')';
6000
	}
6001
	else
6002
		$regex = '(?'.'>' . $index_to_regex($index, $delim) . ')';
6003
6004
	// Restore PHP's internal character encoding to whatever it was originally
6005
	if (!empty($current_encoding))
6006
		mb_internal_encoding($current_encoding);
6007
6008
	return $regex;
6009
}
6010
6011
/**
6012
 * Check if the passed url has an SSL certificate.
6013
 *
6014
 * Returns true if a cert was found & false if not.
6015
 * @param string $url to check, in $boardurl format (no trailing slash).
6016
 */
6017
 function ssl_cert_found($url) {
6018
6019
	// Ask for the headers for the passed url, but via https...
6020
	$url = str_ireplace('http://', 'https://', $url) . '/';
6021
6022
	$result = false;
6023
	$stream = stream_context_create (array("ssl" => array("capture_peer_cert" => 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...
6024
	$read = @fopen($url, "rb", false, $stream);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal rb 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...
6025
	if ($read !== false) {
6026
		$cont = stream_context_get_params($read);
6027
		$result = isset($cont["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...
6028
	}
6029
    return $result;
6030
}
6031
6032
/**
6033
 * Check if the passed url has a redirect to https:// by querying headers.
6034
 *
6035
 * Returns true if a redirect was found & false if not.
6036
 * Note that when force_ssl = 2, SMF issues its own redirect...  So if this
6037
 * returns true, it may be caused by SMF, not necessarily an .htaccess redirect.
6038
 * @param string $url to check, in $boardurl format (no trailing slash).
6039
 */
6040
function https_redirect_active($url) {
6041
6042
	// Ask for the headers for the passed url, but via http...
6043
	// Need to add the trailing slash, or it puts it there & thinks there's a redirect when there isn't...
6044
	$url = str_ireplace('https://', 'http://', $url) . '/';
6045
	$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 928
  1. Fetching key HTTP_HOST from $_SERVER, and $host is assigned
    in other/install.php on line 928
  2. $incontext is assigned
    in other/install.php on line 931
  3. $incontext is assigned
    in other/install.php on line 934
  4. $incontext is assigned
    in other/install.php on line 935
  5. $incontext is assigned
    in other/install.php on line 936
  6. $incontext is assigned
    in other/install.php on line 938
  7. $incontext is assigned
    in other/install.php on line 941
  8. $incontext is assigned
    in other/install.php on line 942
  9. $incontext['detected_url'] is passed to https_redirect_active()
    in other/install.php on line 946
  10. $url is passed through str_ireplace(), and $url is assigned
    in Sources/Subs.php on line 6044
  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 931
  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 931
  2. $incontext is assigned
    in other/install.php on line 934
  3. $incontext is assigned
    in other/install.php on line 935
  4. $incontext is assigned
    in other/install.php on line 936
  5. $incontext is assigned
    in other/install.php on line 938
  6. $incontext is assigned
    in other/install.php on line 941
  7. $incontext is assigned
    in other/install.php on line 942
  8. $incontext['detected_url'] is passed to https_redirect_active()
    in other/install.php on line 946
  9. $url is passed through str_ireplace(), and $url is assigned
    in Sources/Subs.php on line 6044

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...
6046
	if ($headers === false)
6047
		return false;
6048
6049
	// Now to see if it came back https...
6050
	// First check for a redirect status code in first row (301, 302, 307)
6051
	if (strstr($headers[0], '301') === false && strstr($headers[0], '302') === false && strstr($headers[0], '307') === false)
6052
		return false;
6053
6054
	// Search for the location entry to confirm https
6055
	$result = false;
6056
	foreach ($headers as $header) {
6057
		if (stristr($header, 'Location: https://') !== false) {
6058
			$result = true;
6059
			break;
6060
		}
6061
	}
6062
	return $result;
6063
}
6064
6065
/**
6066
 * Build query_wanna_see_board and query_see_board for a userid
6067
 * 
6068
 * Returns array with keys query_wanna_see_board and query_see_board
6069
 * @param int $userid of the user
6070
 */
6071
function build_query_board($userid)
6072
{
6073
	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...
6074
6075
	$query_part = array();
6076
	$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...
6077
	$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...
6078
	$deny_boards_access = !empty($modSettings['deny_boards_access']) ? $modSettings['deny_boards_access'] : null;
6079
	$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...
6080
	$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...
6081
6082
	if ($user_info['id'] == $userid)
6083
	{
6084
		$groups = $user_info['groups'];
6085
		$is_admin = $user_info['is_admin'];
6086
		$mod_cache = !empty($user_info['mod_cache']) ? $user_info['mod_cache'] : null;
6087
		$ignoreboards = !empty($user_info['ignoreboards']) ? $user_info['ignoreboards'] : null;
6088
	}
6089
	else
6090
	{
6091
		$request = $smcFunc['db_query']('', '
6092
				SELECT mem.ignore_boards, mem.id_group, mem.additional_groups, mem.id_post_group
6093
				FROM {db_prefix}members AS mem
6094
				WHERE mem.id_member = {int:id_member}
6095
				LIMIT 1',
6096
				array(
6097
					'id_member' => $userid,
6098
				)
6099
			);
6100
6101
		$row = $smcFunc['db_fetch_assoc']($request);
6102
6103 View Code Duplication
		if (empty($row['additional_groups']))
6104
			$groups = array($row['id_group'], $user_settings['id_post_group']);
0 ignored issues
show
Bug introduced by
The variable $user_settings does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
6105
		else
6106
			$groups = array_merge(
6107
					array($row['id_group'], $user_settings['id_post_group']),
6108
					explode(',', $row['additional_groups'])
6109
			);
6110
6111
		// Because history has proven that it is possible for groups to go bad - clean up in case.
6112
		foreach ($groups as $k => $v)
6113
			$groups[$k] = (int) $v;
6114
6115
		$is_admin = in_array(1, $groups);
6116
6117
		$ignoreboards = !empty($row['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $row['ignore_boards']) : array();
6118
6119
		// What boards are they the moderator of?
6120
		$boards_mod = array();
6121
6122
		$request = $smcFunc['db_query']('', '
6123
			SELECT id_board
6124
			FROM {db_prefix}moderators
6125
			WHERE id_member = {int:current_member}',
6126
			array(
6127
				'current_member' => $userid,
6128
			)
6129
		);
6130
		while ($row = $smcFunc['db_fetch_assoc']($request))
6131
			$boards_mod[] = $row['id_board'];
6132
		$smcFunc['db_free_result']($request);
6133
6134
		// Can any of the groups they're in moderate any of the boards?
6135
		$request = $smcFunc['db_query']('', '
6136
			SELECT id_board
6137
			FROM {db_prefix}moderator_groups
6138
			WHERE id_group IN({array_int:groups})',
6139
			array(
6140
				'groups' => $groups,
6141
			)
6142
		);
6143
		while ($row = $smcFunc['db_fetch_assoc']($request))
6144
			$boards_mod[] = $row['id_board'];
6145
		$smcFunc['db_free_result']($request);
6146
6147
		// Just in case we've got duplicates here...
6148
		$boards_mod = array_unique($boards_mod);
6149
6150
		$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...
6151
	}
6152
	
6153
	// Just build this here, it makes it easier to change/use - administrators can see all boards.
6154
	if ($is_admin)
6155
		$query_part['query_see_board'] = '1=1';
6156
	// Otherwise just the groups in $user_info['groups'].
6157
	else
6158
		$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'] : '') . ')';
6159
6160
	// Build the list of boards they WANT to see.
6161
	// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also
6162
6163
	// If they aren't ignoring any boards then they want to see all the boards they can see
6164
	if (empty($ignoreboards))
6165
		$query_part['query_wanna_see_board'] = $query_part['query_see_board'];
6166
	// Ok I guess they don't want to see all the boards
6167
	else
6168
		$query_part['query_wanna_see_board'] = '(' . $query_part['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $ignoreboards) . '))';
6169
6170
	return $query_part;
6171
}
6172
6173
?>