Completed
Push — release-2.1 ( 594510...67de6a )
by Colin
07:41
created

Subs.php ➔ set_tld_regex()   C

Complexity

Conditions 12
Paths 14

Size

Total Lines 93
Code Lines 52

Duplication

Lines 7
Ratio 7.53 %

Importance

Changes 0
Metric Value
cc 12
eloc 52
nc 14
nop 1
dl 7
loc 93
rs 5.034
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to, well, everything.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2018 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Update some basic statistics.
21
 *
22
 * 'member' statistic updates the latest member, the total member
23
 *  count, and the number of unapproved members.
24
 * 'member' also only counts approved members when approval is on, but
25
 *  is much more efficient with it off.
26
 *
27
 * 'message' changes the total number of messages, and the
28
 *  highest message id by id_msg - which can be parameters 1 and 2,
29
 *  respectively.
30
 *
31
 * 'topic' updates the total number of topics, or if parameter1 is true
32
 *  simply increments them.
33
 *
34
 * 'subject' updates the log_search_subjects in the event of a topic being
35
 *  moved, removed or split.  parameter1 is the topicid, parameter2 is the new subject
36
 *
37
 * 'postgroups' case updates those members who match condition's
38
 *  post-based membergroups in the database (restricted by parameter1).
39
 *
40
 * @param string $type Stat type - can be 'member', 'message', 'topic', 'subject' or 'postgroups'
41
 * @param mixed $parameter1 A parameter for updating the stats
42
 * @param mixed $parameter2 A 2nd parameter for updating the stats
43
 */
44
function updateStats($type, $parameter1 = null, $parameter2 = null)
45
{
46
	global $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
47
48
	switch ($type)
49
	{
50
		case 'member':
51
			$changes = array(
52
				'memberlist_updated' => time(),
53
			);
54
55
			// #1 latest member ID, #2 the real name for a new registration.
56
			if (is_numeric($parameter1))
57
			{
58
				$changes['latestMember'] = $parameter1;
59
				$changes['latestRealName'] = $parameter2;
60
61
				updateSettings(array('totalMembers' => true), true);
62
			}
63
64
			// We need to calculate the totals.
65
			else
66
			{
67
				// Update the latest activated member (highest id_member) and count.
68
				$result = $smcFunc['db_query']('', '
69
				SELECT COUNT(*), MAX(id_member)
70
				FROM {db_prefix}members
71
				WHERE is_activated = {int:is_activated}',
72
					array(
73
						'is_activated' => 1,
74
					)
75
				);
76
				list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result);
77
				$smcFunc['db_free_result']($result);
78
79
				// Get the latest activated member's display name.
80
				$result = $smcFunc['db_query']('', '
81
				SELECT real_name
82
				FROM {db_prefix}members
83
				WHERE id_member = {int:id_member}
84
				LIMIT 1',
85
					array(
86
						'id_member' => (int) $changes['latestMember'],
87
					)
88
				);
89
				list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result);
90
				$smcFunc['db_free_result']($result);
91
92
				if (!empty($modSettings['registration_method']))
93
				{
94
					// Are we using registration approval?
95
					if ($modSettings['registration_method'] == 2 || !empty($modSettings['approveAccountDeletion']))
96
					{
97
						// Update the amount of members awaiting approval
98
						$result = $smcFunc['db_query']('', '
99
						SELECT COUNT(*)
100
						FROM {db_prefix}members
101
						WHERE is_activated IN ({array_int:activation_status})',
102
							array(
103
								'activation_status' => array(3, 4),
104
							)
105
						);
106
						list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result);
107
						$smcFunc['db_free_result']($result);
108
					}
109
110
					// What about unapproved COPPA registrations?
111 View Code Duplication
					if (!empty($modSettings['coppaType']) && $modSettings['coppaType'] != 1)
112
					{
113
						$result = $smcFunc['db_query']('', '
114
						SELECT COUNT(*)
115
						FROM {db_prefix}members
116
						WHERE is_activated = {int:coppa_approval}',
117
							array(
118
								'coppa_approval' => 5,
119
							)
120
						);
121
						list ($coppa_approvals) = $smcFunc['db_fetch_row']($result);
122
						$smcFunc['db_free_result']($result);
123
124
						// Add this to the number of unapproved members
125
						if (!empty($changes['unapprovedMembers']))
126
							$changes['unapprovedMembers'] += $coppa_approvals;
127
						else
128
							$changes['unapprovedMembers'] = $coppa_approvals;
129
					}
130
				}
131
			}
132
			updateSettings($changes);
133
			break;
134
135
		case 'message':
136
			if ($parameter1 === true && $parameter2 !== null)
137
				updateSettings(array('totalMessages' => true, 'maxMsgID' => $parameter2), true);
138
			else
139
			{
140
				// SUM and MAX on a smaller table is better for InnoDB tables.
141
				$result = $smcFunc['db_query']('', '
142
				SELECT SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id
143
				FROM {db_prefix}boards
144
				WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
145
					AND id_board != {int:recycle_board}' : ''),
146
					array(
147
						'recycle_board' => isset($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
148
						'blank_redirect' => '',
149
					)
150
				);
151
				$row = $smcFunc['db_fetch_assoc']($result);
152
				$smcFunc['db_free_result']($result);
153
154
				updateSettings(array(
155
					'totalMessages' => $row['total_messages'] === null ? 0 : $row['total_messages'],
156
					'maxMsgID' => $row['max_msg_id'] === null ? 0 : $row['max_msg_id']
157
				));
158
			}
159
			break;
160
161
		case 'subject':
162
			// Remove the previous subject (if any).
163
			$smcFunc['db_query']('', '
164
			DELETE FROM {db_prefix}log_search_subjects
165
			WHERE id_topic = {int:id_topic}',
166
				array(
167
					'id_topic' => (int) $parameter1,
168
				)
169
			);
170
171
			// Insert the new subject.
172
			if ($parameter2 !== null)
173
			{
174
				$parameter1 = (int) $parameter1;
175
				$parameter2 = text2words($parameter2);
176
177
				$inserts = array();
178
				foreach ($parameter2 as $word)
179
					$inserts[] = array($word, $parameter1);
180
181 View Code Duplication
				if (!empty($inserts))
182
					$smcFunc['db_insert']('ignore',
183
						'{db_prefix}log_search_subjects',
184
						array('word' => 'string', 'id_topic' => 'int'),
185
						$inserts,
186
						array('word', 'id_topic')
187
					);
188
			}
189
			break;
190
191
		case 'topic':
192
			if ($parameter1 === true)
193
				updateSettings(array('totalTopics' => true), true);
194
			else
195
			{
196
				// Get the number of topics - a SUM is better for InnoDB tables.
197
				// We also ignore the recycle bin here because there will probably be a bunch of one-post topics there.
198
				$result = $smcFunc['db_query']('', '
199
				SELECT SUM(num_topics + unapproved_topics) AS total_topics
200
				FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
201
				WHERE id_board != {int:recycle_board}' : ''),
202
					array(
203
						'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
204
					)
205
				);
206
				$row = $smcFunc['db_fetch_assoc']($result);
207
				$smcFunc['db_free_result']($result);
208
209
				updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics']));
210
			}
211
			break;
212
213
		case 'postgroups':
214
			// Parameter two is the updated columns: we should check to see if we base groups off any of these.
215
			if ($parameter2 !== null && !in_array('posts', $parameter2))
216
				return;
217
218
			$postgroups = cache_get_data('updateStats:postgroups', 360);
219
			if ($postgroups == null || $parameter1 == null)
220
			{
221
				// Fetch the postgroups!
222
				$request = $smcFunc['db_query']('', '
223
				SELECT id_group, min_posts
224
				FROM {db_prefix}membergroups
225
				WHERE min_posts != {int:min_posts}',
226
					array(
227
						'min_posts' => -1,
228
					)
229
				);
230
				$postgroups = array();
231
				while ($row = $smcFunc['db_fetch_assoc']($request))
232
					$postgroups[$row['id_group']] = $row['min_posts'];
233
				$smcFunc['db_free_result']($request);
234
235
				// Sort them this way because if it's done with MySQL it causes a filesort :(.
236
				arsort($postgroups);
237
238
				cache_put_data('updateStats:postgroups', $postgroups, 360);
239
			}
240
241
			// Oh great, they've screwed their post groups.
242
			if (empty($postgroups))
243
				return;
244
245
			// Set all membergroups from most posts to least posts.
246
			$conditions = '';
247
			$lastMin = 0;
248
			foreach ($postgroups as $id => $min_posts)
0 ignored issues
show
Bug introduced by
The expression $postgroups of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
249
			{
250
				$conditions .= '
251
					WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id;
252
				$lastMin = $min_posts;
253
			}
254
255
			// A big fat CASE WHEN... END is faster than a zillion UPDATE's ;).
256
			$smcFunc['db_query']('', '
257
			UPDATE {db_prefix}members
258
			SET id_post_group = CASE ' . $conditions . '
259
					ELSE 0
260
				END' . ($parameter1 != null ? '
261
			WHERE ' . (is_array($parameter1) ? 'id_member IN ({array_int:members})' : 'id_member = {int:members}') : ''),
262
				array(
263
					'members' => $parameter1,
264
				)
265
			);
266
			break;
267
268
		default:
269
			trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE);
270
	}
271
}
272
273
/**
274
 * Updates the columns in the members table.
275
 * Assumes the data has been htmlspecialchar'd.
276
 * this function should be used whenever member data needs to be
277
 * updated in place of an UPDATE query.
278
 *
279
 * id_member is either an int or an array of ints to be updated.
280
 *
281
 * data is an associative array of the columns to be updated and their respective values.
282
 * any string values updated should be quoted and slashed.
283
 *
284
 * the value of any column can be '+' or '-', which mean 'increment'
285
 * and decrement, respectively.
286
 *
287
 * if the member's post number is updated, updates their post groups.
288
 *
289
 * @param mixed $members An array of member IDs, null to update this for all members or the ID of a single member
290
 * @param array $data The info to update for the members
291
 */
292
function updateMemberData($members, $data)
293
{
294
	global $modSettings, $user_info, $smcFunc, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
295
296
	$parameters = array();
297
	if (is_array($members))
298
	{
299
		$condition = 'id_member IN ({array_int:members})';
300
		$parameters['members'] = $members;
301
	}
302
	elseif ($members === null)
303
		$condition = '1=1';
304
	else
305
	{
306
		$condition = 'id_member = {int:member}';
307
		$parameters['member'] = $members;
308
	}
309
310
	// Everything is assumed to be a string unless it's in the below.
311
	$knownInts = array(
312
		'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages',
313
		'new_pm', 'pm_prefs', 'gender', 'show_online', 'pm_receive_from', 'alerts',
314
		'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning',
315
	);
316
	$knownFloats = array(
317
		'time_offset',
318
	);
319
320
	if (!empty($modSettings['integrate_change_member_data']))
321
	{
322
		// Only a few member variables are really interesting for integration.
323
		$integration_vars = array(
324
			'member_name',
325
			'real_name',
326
			'email_address',
327
			'id_group',
328
			'gender',
329
			'birthdate',
330
			'website_title',
331
			'website_url',
332
			'location',
333
			'time_format',
334
			'time_offset',
335
			'avatar',
336
			'lngfile',
337
		);
338
		$vars_to_integrate = array_intersect($integration_vars, array_keys($data));
339
340
		// Only proceed if there are any variables left to call the integration function.
341
		if (count($vars_to_integrate) != 0)
342
		{
343
			// Fetch a list of member_names if necessary
344
			if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members)))
345
				$member_names = array($user_info['username']);
346 View Code Duplication
			else
347
			{
348
				$member_names = array();
349
				$request = $smcFunc['db_query']('', '
350
					SELECT member_name
351
					FROM {db_prefix}members
352
					WHERE ' . $condition,
353
					$parameters
354
				);
355
				while ($row = $smcFunc['db_fetch_assoc']($request))
356
					$member_names[] = $row['member_name'];
357
				$smcFunc['db_free_result']($request);
358
			}
359
360
			if (!empty($member_names))
361
				foreach ($vars_to_integrate as $var)
362
					call_integration_hook('integrate_change_member_data', array($member_names, $var, &$data[$var], &$knownInts, &$knownFloats));
363
		}
364
	}
365
366
	$setString = '';
367
	foreach ($data as $var => $val)
368
	{
369
		$type = 'string';
370
		if (in_array($var, $knownInts))
371
			$type = 'int';
372
		elseif (in_array($var, $knownFloats))
373
			$type = 'float';
374
		elseif ($var == 'birthdate')
375
			$type = 'date';
376
		elseif ($var == 'member_ip')
377
			$type = 'inet';
378
		elseif ($var == 'member_ip2')
379
			$type = 'inet';
380
381
		// Doing an increment?
382
		if ($var == 'alerts' && ($val === '+' || $val === '-'))
383
		{
384
			include_once($sourcedir . '/Profile-View.php');
385
			if (is_array($members))
386
			{
387
				$val = 'CASE ';
388
				foreach ($members as $k => $v)
389
					$val .= 'WHEN id_member = ' . $v . ' THEN '. count(fetch_alerts($v, false, 0, array(), false)) . ' ';
390
				$val = $val . ' END';
391
				$type = 'raw';
392
			}
393
			else
394
			{
395
				$blub = fetch_alerts($members, false, 0, array(), false);
396
				$val = count($blub);
397
			}
398
		}
399
		else if ($type == 'int' && ($val === '+' || $val === '-'))
400
		{
401
			$val = $var . ' ' . $val . ' 1';
402
			$type = 'raw';
403
		}
404
405
		// Ensure posts, instant_messages, and unread_messages don't overflow or underflow.
406
		if (in_array($var, array('posts', 'instant_messages', 'unread_messages')))
407
		{
408
			if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match))
409
			{
410
				if ($match[1] != '+ ')
411
					$val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END';
412
				$type = 'raw';
413
			}
414
		}
415
416
		$setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},';
417
		$parameters['p_' . $var] = $val;
418
	}
419
420
	$smcFunc['db_query']('', '
421
		UPDATE {db_prefix}members
422
		SET' . substr($setString, 0, -1) . '
423
		WHERE ' . $condition,
424
		$parameters
425
	);
426
427
	updateStats('postgroups', $members, array_keys($data));
428
429
	// Clear any caching?
430
	if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members))
431
	{
432
		if (!is_array($members))
433
			$members = array($members);
434
435
		foreach ($members as $member)
436
		{
437
			if ($modSettings['cache_enable'] >= 3)
438
			{
439
				cache_put_data('member_data-profile-' . $member, null, 120);
440
				cache_put_data('member_data-normal-' . $member, null, 120);
441
				cache_put_data('member_data-minimal-' . $member, null, 120);
442
			}
443
			cache_put_data('user_settings-' . $member, null, 60);
444
		}
445
	}
446
}
447
448
/**
449
 * Updates the settings table as well as $modSettings... only does one at a time if $update is true.
450
 *
451
 * - updates both the settings table and $modSettings array.
452
 * - all of changeArray's indexes and values are assumed to have escaped apostrophes (')!
453
 * - if a variable is already set to what you want to change it to, that
454
 *   variable will be skipped over; it would be unnecessary to reset.
455
 * - When use_update is true, UPDATEs will be used instead of REPLACE.
456
 * - when use_update is true, the value can be true or false to increment
457
 *  or decrement it, respectively.
458
 *
459
 * @param array $changeArray An array of info about what we're changing in 'setting' => 'value' format
460
 * @param bool $update Whether to use an UPDATE query instead of a REPLACE query
461
 */
462
function updateSettings($changeArray, $update = false)
463
{
464
	global $modSettings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

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

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

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

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

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

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

Loading history...
769
			$today_fmt = $h . ':%M' . $s . ' %p';
770
		}
771
		else
772
			$today_fmt = '%H:%M' . $s;
773
774
		// Same day of the year, same year.... Today!
775
		if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
776
			return $txt['today'] . timeformat($log_time, $today_fmt, $offset_type);
0 ignored issues
show
Documentation introduced by
$today_fmt is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
777
778
		// Day-of-year is one less and same year, or it's the first of the year and that's the last of the year...
779
		if ($modSettings['todayMod'] == '2' && (($then['yday'] == $now['yday'] - 1 && $then['year'] == $now['year']) || ($now['yday'] == 0 && $then['year'] == $now['year'] - 1) && $then['mon'] == 12 && $then['mday'] == 31))
780
			return $txt['yesterday'] . timeformat($log_time, $today_fmt, $offset_type);
0 ignored issues
show
Documentation introduced by
$today_fmt is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

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

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

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

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

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

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

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

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

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

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

Loading history...
1331
				{
1332
					$data = strtr($data, array('<br>' => ''));
1333
				},
1334
			),
1335
			array(
1336
				'tag' => 'email',
1337
				'type' => 'unparsed_equals',
1338
				'before' => '<a href="mailto:$1" class="bbc_email">',
1339
				'after' => '</a>',
1340
				// @todo Should this respect guest_hideContacts?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
1341
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1342
				'disabled_after' => ' ($1)',
1343
			),
1344
			array(
1345
				'tag' => 'flash',
1346
				'type' => 'unparsed_commas_content',
1347
				'test' => '\d+,\d+\]',
1348
				'content' => '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never">',
1349
				'validate' => function (&$tag, &$data, $disabled)
1350
				{
1351
					if (isset($disabled['url']))
1352
						$tag['content'] = '$1';
1353
					$scheme = parse_url($data[0], PHP_URL_SCHEME);
1354
					if (empty($scheme))
1355
						$data[0] = '//' . ltrim($data[0], ':/');
1356
				},
1357
				'disabled_content' => '<a href="$1" target="_blank" rel="noopener">$1</a>',
1358
			),
1359
			array(
1360
				'tag' => 'float',
1361
				'type' => 'unparsed_equals',
1362
				'test' => '(left|right)(\s+max=\d+(?:%|px|em|rem|ex|pt|pc|ch|vw|vh|vmin|vmax|cm|mm|in)?)?\]',
1363
				'before' => '<div $1>',
1364
				'after' => '</div>',
1365
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

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

Loading history...
1366
				{
1367
					$class = 'class="bbc_float float' . (strpos($data, 'left') === 0 ? 'left' : 'right') . '"';
1368
1369
					if (preg_match('~\bmax=(\d+(?:%|px|em|rem|ex|pt|pc|ch|vw|vh|vmin|vmax|cm|mm|in)?)~', $data, $matches))
1370
						$css = ' style="max-width:' . $matches[1] . (is_numeric($matches[1]) ? 'px' : '') . '"';
1371
					else
1372
						$css = '';
1373
1374
					$data = $class . $css;
1375
				},
1376
				'trim' => 'outside',
1377
				'block_level' => true,
1378
			),
1379
			array(
1380
				'tag' => 'font',
1381
				'type' => 'unparsed_equals',
1382
				'test' => '[A-Za-z0-9_,\-\s]+?\]',
1383
				'before' => '<span style="font-family: $1;" class="bbc_font">',
1384
				'after' => '</span>',
1385
			),
1386
			array(
1387
				'tag' => 'html',
1388
				'type' => 'unparsed_content',
1389
				'content' => '<div>$1</div>',
1390
				'block_level' => true,
1391
				'disabled_content' => '$1',
1392
			),
1393
			array(
1394
				'tag' => 'hr',
1395
				'type' => 'closed',
1396
				'content' => '<hr>',
1397
				'block_level' => true,
1398
			),
1399
			array(
1400
				'tag' => 'i',
1401
				'before' => '<i>',
1402
				'after' => '</i>',
1403
			),
1404
			array(
1405
				'tag' => 'img',
1406
				'type' => 'unparsed_content',
1407
				'parameters' => array(
1408
					'alt' => array('optional' => true),
1409
					'title' => array('optional' => true),
1410
					'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
1411
					'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
1412
				),
1413
				'content' => '<img src="$1" alt="{alt}" title="{title}"{width}{height} class="bbc_img resized">',
1414 View Code Duplication
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
1417
1418
					$data = strtr($data, array('<br>' => ''));
1419
					$scheme = parse_url($data, PHP_URL_SCHEME);
1420
					if ($image_proxy_enabled)
1421
					{
1422
						if (!empty($user_info['possibly_robot']))
1423
							return;
1424
1425
						if (empty($scheme))
1426
							$data = 'http://' . ltrim($data, ':/');
1427
1428
						if ($scheme != 'https')
1429
							$data = get_proxied_url($data);
1430
					}
1431
					elseif (empty($scheme))
1432
						$data = '//' . ltrim($data, ':/');
1433
				},
1434
				'disabled_content' => '($1)',
1435
			),
1436
			array(
1437
				'tag' => 'img',
1438
				'type' => 'unparsed_content',
1439
				'content' => '<img src="$1" alt="" class="bbc_img">',
1440 View Code Duplication
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
1443
1444
					$data = strtr($data, array('<br>' => ''));
1445
					$scheme = parse_url($data, PHP_URL_SCHEME);
1446
					if ($image_proxy_enabled)
1447
					{
1448
						if (!empty($user_info['possibly_robot']))
1449
							return;
1450
1451
						if (empty($scheme))
1452
							$data = 'http://' . ltrim($data, ':/');
1453
1454
						if ($scheme != 'https')
1455
							$data = get_proxied_url($data);
1456
					}
1457
					elseif (empty($scheme))
1458
						$data = '//' . ltrim($data, ':/');
1459
				},
1460
				'disabled_content' => '($1)',
1461
			),
1462
			array(
1463
				'tag' => 'iurl',
1464
				'type' => 'unparsed_content',
1465
				'content' => '<a href="$1" class="bbc_link">$1</a>',
1466 View Code Duplication
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

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

Loading history...
1467
				{
1468
					$data = strtr($data, array('<br>' => ''));
1469
					$scheme = parse_url($data, PHP_URL_SCHEME);
1470
					if (empty($scheme))
1471
						$data = '//' . ltrim($data, ':/');
1472
				},
1473
			),
1474
			array(
1475
				'tag' => 'iurl',
1476
				'type' => 'unparsed_equals',
1477
				'quoted' => 'optional',
1478
				'before' => '<a href="$1" class="bbc_link">',
1479
				'after' => '</a>',
1480
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
2878
2879
	// Only use the proxy if enabled and necessary
2880
	if (empty($image_proxy_enabled) || parse_url($url, PHP_URL_SCHEME) === 'https')
2881
		return $url;
2882
2883
	// We don't need to proxy our own resources
2884
	if (strpos(strtr($url, array('http://' => 'https://')), strtr($boardurl, array('http://' => 'https://'))) === 0)
2885
		return strtr($url, array('http://' => 'https://'));
2886
2887
	// By default, use SMF's own image proxy script
2888
	$proxied_url = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($url) . '&hash=' . md5($url . $image_proxy_secret);
2889
2890
	// Allow mods to easily implement an alternative proxy
2891
	// MOD AUTHORS: To add settings UI for your proxy, use the integrate_general_settings hook.
2892
	call_integration_hook('integrate_proxy', array($url, &$proxied_url));
2893
2894
	return $proxied_url;
2895
}
2896
2897
/**
2898
 * Make sure the browser doesn't come back and repost the form data.
2899
 * Should be used whenever anything is posted.
2900
 *
2901
 * @param string $setLocation The URL to redirect them to
2902
 * @param bool $refresh Whether to use a meta refresh instead
2903
 * @param bool $permanent Whether to send a 301 Moved Permanently instead of a 302 Moved Temporarily
2904
 */
2905
function redirectexit($setLocation = '', $refresh = false, $permanent = false)
2906
{
2907
	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...
2908
2909
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
2910
	if (!empty($context['flush_mail']))
2911
		// @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...
2912
		AddMailQueue(true);
2913
2914
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
2915
2916
	if ($add)
2917
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
2918
2919
	// Put the session ID in.
2920
	if (defined('SID') && SID != '')
2921
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
2922
	// Keep that debug in their for template debugging!
2923 View Code Duplication
	elseif (isset($_GET['debug']))
2924
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
2925
2926
	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'])))
2927
	{
2928
		if (defined('SID') && SID != '')
2929
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~',
2930
				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...
2931
				{
2932
					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...
2933
				}, $setLocation);
2934 View Code Duplication
		else
2935
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~',
2936
				function ($m) use ($scripturl)
2937
				{
2938
					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...
2939
				}, $setLocation);
2940
	}
2941
2942
	// Maybe integrations want to change where we are heading?
2943
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh, &$permanent));
2944
2945
	// Set the header.
2946
	header('location: ' . str_replace(' ', '%20', $setLocation), true, $permanent ? 301 : 302);
0 ignored issues
show
Security Response Splitting introduced by
'location: ' . str_repla...', '%20', $setLocation) can contain request data and is used in response header context(s) leading to a potential security vulnerability.

66 paths for user data to reach this point

  1. Path: Read from $_POST, and $_POST['start_date'] is passed through date_parse(), and $d is assigned in Sources/Calendar.php on line 356
  1. Read from $_POST, and $_POST['start_date'] is passed through date_parse(), and $d is assigned
    in Sources/Calendar.php on line 356
  2. $month is assigned
    in Sources/Calendar.php on line 358
  3. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  2. Path: Read from $_POST, and $_POST['start_datetime'] is passed through date_parse(), and $d is assigned in Sources/Calendar.php on line 363
  1. Read from $_POST, and $_POST['start_datetime'] is passed through date_parse(), and $d is assigned
    in Sources/Calendar.php on line 363
  2. $month is assigned
    in Sources/Calendar.php on line 365
  3. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  3. Path: Read from $_POST, and $month is assigned in Sources/Calendar.php on line 372
  1. Read from $_POST, and $month is assigned
    in Sources/Calendar.php on line 372
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  4. Path: Read from $_POST, and $year is assigned in Sources/Calendar.php on line 371
  1. Read from $_POST, and $year is assigned
    in Sources/Calendar.php on line 371
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  5. Path: Read from $_POST, and $day is assigned in Sources/Calendar.php on line 373
  1. Read from $_POST, and $day is assigned
    in Sources/Calendar.php on line 373
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  6. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Display.php on line 1696
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Display.php on line 1696
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  7. Path: Read from $_REQUEST, and !empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Display.php on line 1804
  1. Read from $_REQUEST, and !empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Display.php on line 1804
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  8. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/Load.php on line 808
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/Load.php on line 808
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  9. Path: Read from $_GET, and $_GET is passed through key(), and $k is assigned in Sources/Load.php on line 1882
  1. Read from $_GET, and $_GET is passed through key(), and $k is assigned
    in Sources/Load.php on line 1882
  2. 'wwwRedirect;' . $k . '=' . $v is passed to redirectexit()
    in Sources/Load.php on line 1886
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  10. Path: Read from $_GET, and $_GET is passed through current(), and $v is assigned in Sources/Load.php on line 1883
  1. Read from $_GET, and $_GET is passed through current(), and $v is assigned
    in Sources/Load.php on line 1883
  2. 'wwwRedirect;' . $k . '=' . $v is passed to redirectexit()
    in Sources/Load.php on line 1886
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 2946
  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 2946
  13. Path: Read from $_GET, and $context is assigned in Sources/ManageLanguages.php on line 789
  1. Read from $_GET, and $context is assigned
    in Sources/ManageLanguages.php on line 789
  2. $context is assigned
    in Sources/ManageLanguages.php on line 794
  3. $context is assigned
    in Sources/ManageLanguages.php on line 842
  4. 'action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/ManageLanguages.php on line 939
  5. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  14. Path: Read from $_REQUEST, and $_REQUEST['tfid'] is passed through explode(), and $file_id is assigned in Sources/ManageLanguages.php on line 790
  1. Read from $_REQUEST, and $_REQUEST['tfid'] is passed through explode(), and $file_id is assigned
    in Sources/ManageLanguages.php on line 790
  2. $current_file is assigned
    in Sources/ManageLanguages.php on line 839
  3. $current_file is passed through sprintf(), and $context is assigned
    in Sources/ManageLanguages.php on line 1003
  4. $context is assigned
    in Sources/ManageLanguages.php on line 1041
  5. 'action=admin;area=languages;sa=editlang;lid=' . $context['lang_id'] is passed to redirectexit()
    in Sources/ManageLanguages.php on line 1168
  6. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 2946
  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 2946
  17. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $_REQUEST['filter'] . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1061
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $_REQUEST['filter'] . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1061
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  18. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1065
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1065
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  19. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1101
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1101
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  20. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1286
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1286
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 2946
  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 2946
  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 2946
  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 2946
  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 2946
  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 2946
  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 2946
  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 2946
  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 1744
  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 1744
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 1748
  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 1748
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  31. Path: Read from $_REQUEST, and $_REQUEST['topics'] is passed through implode(), and 'action=restoretopic;topics=' . implode(',', $_REQUEST['topics']) . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit() in Sources/MessageIndex.php on line 771
  1. Read from $_REQUEST, and $_REQUEST['topics'] is passed through implode(), and 'action=restoretopic;topics=' . implode(',', $_REQUEST['topics']) . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/MessageIndex.php on line 771
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  32. Path: Read from $_REQUEST, and $redirect_url is assigned in Sources/MessageIndex.php on line 800
  1. Read from $_REQUEST, and $redirect_url is assigned
    in Sources/MessageIndex.php on line 800
  2. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 853
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  33. Path: Read from $_POST, and $redirect_url is assigned in Sources/MessageIndex.php on line 810
  1. Read from $_POST, and $redirect_url is assigned
    in Sources/MessageIndex.php on line 810
  2. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 853
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  34. Path: Read from $_REQUEST, and $_SESSION is assigned in Sources/MessageIndex.php on line 781
  1. Read from $_REQUEST, and $_SESSION is assigned
    in Sources/MessageIndex.php on line 781
  2. $redirect_url is assigned
    in Sources/MessageIndex.php on line 810
  3. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 853
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 2946
  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 2946
  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 2946
  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 2946
  39. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2445
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2445
  2. $context is assigned
    in Sources/Packages.php on line 2456
  3. $context is assigned
    in Sources/Packages.php on line 2540
  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 2684
  5. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  40. Path: Read from $_POST, and $status is assigned in Sources/Packages.php on line 2466
  1. Read from $_POST, and $status is assigned
    in Sources/Packages.php on line 2466
  2. $context is assigned
    in Sources/Packages.php on line 2487
  3. $context is assigned
    in Sources/Packages.php on line 2489
  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 2500
  5. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  41. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2545
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2545
  2. $context is assigned
    in Sources/Packages.php on line 2547
  3. $context is assigned
    in Sources/Packages.php on line 2548
  4. $context is assigned
    in Sources/Packages.php on line 2550
  5. $context is assigned
    in Sources/Packages.php on line 2595
  6. $context is assigned
    in Sources/Packages.php on line 2679
  7. $context is assigned
    in Sources/Packages.php on line 2680
  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 2684
  9. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  42. Path: Read from $_REQUEST, and $_REQUEST[$recipientType] is passed through strtr(), and $recipientString is assigned in Sources/PersonalMessage.php on line 2269
  1. Read from $_REQUEST, and $_REQUEST[$recipientType] is passed through strtr(), and $recipientString is assigned
    in Sources/PersonalMessage.php on line 2269
  2. $recipientString is passed through preg_replace(), and preg_replace('~"[^"]+"~', '', $recipientString) is passed through explode(), and explode(',', preg_replace('~"[^"]+"~', '', $recipientString)) is passed through array_merge(), and array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))) is passed through array_unique(), and $namedRecipientList is assigned
    in Sources/PersonalMessage.php on line 2272
  3. $namesNotFound is assigned
    in Sources/PersonalMessage.php on line 2287
  4. $name is assigned
    in Sources/PersonalMessage.php on line 2335
  5. $name is passed through sprintf(), and $context is assigned
    in Sources/PersonalMessage.php on line 2336
  6. $context is assigned
    in Sources/PersonalMessage.php on line 2437
  7. $context['current_label_redirect'] is passed to redirectexit()
    in Sources/PersonalMessage.php on line 2477
  8. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 2946
  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 2946
  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 2946
  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 2946
  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 2946
  48. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/Post.php on line 2323
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/Post.php on line 2323
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 2946
  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 2946
  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 2946
  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 2946
  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 2946
  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 2946
  55. Path: Read from $_POST, and 'topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit() in Sources/ReportToMod.php on line 272
  1. Read from $_POST, and 'topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 272
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  56. Path: Read from $_POST, and 'reportsent;topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit() in Sources/ReportToMod.php on line 344
  1. Read from $_POST, and 'reportsent;topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 344
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  57. Path: Read from $_POST, and 'action=profile;u=' . $_POST['u'] is passed to redirectexit() in Sources/ReportToMod.php on line 396
  1. Read from $_POST, and 'action=profile;u=' . $_POST['u'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 396
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  58. Path: Read from $_POST, and $_POST['u'] is passed to reportUser() in Sources/ReportToMod.php on line 219
  1. Read from $_POST, and $_POST['u'] is passed to reportUser()
    in Sources/ReportToMod.php on line 219
  2. 'reportsent;action=profile;u=' . $id_member is passed to redirectexit()
    in Sources/ReportToMod.php on line 465
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 2946
  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 2946
  61. Path: Read from $_REQUEST, and $_REQUEST['topics'] is passed to MergeExecute() in Sources/MessageIndex.php on line 863
  1. Read from $_REQUEST, and $_REQUEST['topics'] is passed to MergeExecute()
    in Sources/MessageIndex.php on line 863
  2. $topics is passed through min(), and $id_topic is assigned
    in Sources/SplitTopics.php on line 1361
  3. 'action=mergetopics;sa=done;to=' . $id_topic . ';targetboard=' . $target_board is passed to redirectexit()
    in Sources/SplitTopics.php on line 1753
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 2946
  63. Path: Read from $_GET, and 'action=admin;area=theme;sa=list;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit() in Sources/Themes.php on line 755
  1. Read from $_GET, and 'action=admin;area=theme;sa=list;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/Themes.php on line 755
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  64. Path: Read from $_GET, and 'action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . dirname($_REQUEST['filename']) is passed to redirectexit() in Sources/Themes.php on line 1791
  1. Read from $_GET, and 'action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . dirname($_REQUEST['filename']) is passed to redirectexit()
    in Sources/Themes.php on line 1791
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2946
  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 2946
  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 2946

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

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
3767
3768
	// Did we already do this?
3769
	list($toCache, $async, $defer) = array_pad((array) cache_get_data('minimized_' . $settings['theme_id'] . '_' . $type . '_' . $hash, 86400), 3, null);
3770
3771
	// Already done?
3772
	if (!empty($toCache))
3773
		return array('smf_minified' => array(
3774
			'fileUrl' => $settings['theme_url'] . '/' . ($type == 'css' ? 'css' : 'scripts') . '/' . basename($toCache),
3775
			'filePath' => $toCache,
3776
			'fileName' => basename($toCache),
3777
			'options' => array('async' => !empty($async), 'defer' => !empty($defer)),
3778
		));
3779
3780
3781
	// No namespaces, sorry!
3782
	$classType = 'MatthiasMullie\\Minify\\'. strtoupper($type);
3783
3784
	// Temp path.
3785
	$cTempPath = $settings['theme_dir'] . '/' . ($type == 'css' ? 'css' : 'scripts') . '/';
3786
3787
	// What kind of file are we going to create?
3788
	$toCreate = $cTempPath . 'minified_' . $hash . '.' . $type;
3789
3790
	// File has to exist. If it doesn't, try to create it.
3791
	if ((!file_exists($toCreate) && @fopen($toCreate, 'w') === false) || !smf_chmod($toCreate))
3792
	{
3793
		loadLanguage('Errors');
3794
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3795
		cache_put_data('minimized_' . $settings['theme_id'] . '_' . $type . '_' . $hash, null);
3796
3797
		// The process failed, so roll back to print each individual file.
3798
		return $data;
3799
	}
3800
3801
	$minifier = new $classType();
3802
3803
	$async = $type === 'js';
3804
	$defer = $type === 'js';
3805
3806
	foreach ($data as $id => $file)
3807
	{
3808
		if (empty($file['filePath']))
3809
			$toAdd = false;
3810
		else
3811
		{
3812
			$seed = isset($file['options']['seed']) ? $file['options']['seed'] : '';
3813
			$tempFile = str_replace($seed, '', $file['filePath']);
3814
			$toAdd = file_exists($tempFile) ? $tempFile : false;
3815
		}
3816
3817
		// A minified script should only be loaded asynchronously if all its components wanted to be.
3818
		if (empty($file['options']['async']))
3819
			$async = false;
3820
3821
		// A minified script should only be deferred if all its components wanted to be.
3822
		if (empty($file['options']['defer']))
3823
			$defer = false;
3824
3825
		// The file couldn't be located so it won't be added. Log this error.
3826
		if (empty($toAdd))
3827
		{
3828
			loadLanguage('Errors');
3829
			log_error(sprintf($txt['file_minimize_fail'], !empty($file['fileName']) ? $file['fileName'] : $id), 'general');
3830
			continue;
3831
		}
3832
3833
		// Add this file to the list.
3834
		$minifier->add($toAdd);
3835
	}
3836
3837
	// Create the file.
3838
	$minifier->minify($toCreate);
3839
	unset($minifier);
3840
	clearstatcache();
3841
3842
	// Minify process failed.
3843
	if (!filesize($toCreate))
3844
	{
3845
		loadLanguage('Errors');
3846
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3847
		cache_put_data('minimized_' . $settings['theme_id'] . '_' . $type . '_' . $hash, null);
3848
3849
		// The process failed so roll back to print each individual file.
3850
		return $data;
3851
	}
3852
3853
	// And create a one day cache entry.
3854
	cache_put_data('minimized_' . $settings['theme_id'] . '_' . $type . '_' . $hash, array($toCache, $async, $defer), 86400);
3855
3856
	return array('smf_minified' => array(
3857
		'fileUrl' => $settings['theme_url'] . '/' . ($type == 'css' ? 'css' : 'scripts') . '/' . basename($toCreate),
3858
		'filePath' => $toCreate,
3859
		'fileName' => basename($toCreate),
3860
		'options' => array('async' => $async, 'defer' => $defer),
3861
	));
3862
}
3863
3864
/**
3865
 * Clears out old minimized CSS and JavaScript files
3866
 */
3867
function deleteAllMinified()
3868
{
3869
	global $smcFunc, $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...
3870
3871
	$not_deleted = array();
3872
3873
	// Kinda sucks that we need to do another query to get all the theme dirs, but c'est la vie.
3874
	$request = $smcFunc['db_query']('', '
3875
		SELECT id_theme AS id, value AS dir
3876
		FROM {db_prefix}themes
3877
		WHERE variable = {string:var}',
3878
		array(
3879
			'var' => 'theme_dir',
3880
		)
3881
	);
3882
	while ($theme = $smcFunc['db_fetch_assoc']($request))
3883
	{
3884
		foreach (array('css', 'js') as $type)
3885
		{
3886
			foreach (glob(rtrim($theme['dir'], '/') . '/' . ($type == 'css' ? 'css' : 'scripts') . '/minified*.' . $type) as $filename)
3887
			{
3888
				// Remove the cache entry
3889
				if (preg_match('~([a-zA-Z0-9]+)\.' . $type . '$~', $filename, $matches))
3890
					cache_put_data('minimized_' . $theme['id'] . '_' . $type . '_' . $matches[1], null);
3891
3892
				// Try to delete the file. Add it to our error list if it fails.
3893
				if (!@unlink($filename))
3894
					$not_deleted[] = $filename;
3895
			}
3896
		}
3897
	}
3898
3899
	// If any of the files could not be deleted, log an error about it.
3900
	if (!empty($not_deleted))
3901
	{
3902
		loadLanguage('Errors');
3903
		log_error(sprintf($txt['unlink_minimized_fail'], implode('<br>', $not_deleted)), 'general');
3904
	}
3905
}
3906
3907
/**
3908
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
3909
 * @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...
3910
 * Something messy like that.
3911
 * @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...
3912
 * Converters included.
3913
 *
3914
 * @param string $filename The name of the file
3915
 * @param int $attachment_id The ID of the attachment
3916
 * @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...
3917
 * @param bool $new Whether this is a new attachment
3918
 * @param string $file_hash The file hash
3919
 * @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...
3920
 */
3921
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
3922
{
3923
	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...
3924
3925
	// Just make up a nice hash...
3926
	if ($new)
3927
		return sha1(md5($filename . time()) . mt_rand());
3928
3929
	// Just make sure that attachment id is only a int
3930
	$attachment_id = (int) $attachment_id;
3931
3932
	// Grab the file hash if it wasn't added.
3933
	// Left this for legacy.
3934
	if ($file_hash === '')
3935
	{
3936
		$request = $smcFunc['db_query']('', '
3937
			SELECT file_hash
3938
			FROM {db_prefix}attachments
3939
			WHERE id_attach = {int:id_attach}',
3940
			array(
3941
				'id_attach' => $attachment_id,
3942
			));
3943
3944
		if ($smcFunc['db_num_rows']($request) === 0)
3945
			return false;
3946
3947
		list ($file_hash) = $smcFunc['db_fetch_row']($request);
3948
		$smcFunc['db_free_result']($request);
3949
	}
3950
3951
	// Still no hash? mmm...
3952
	if (empty($file_hash))
3953
		$file_hash = sha1(md5($filename . time()) . mt_rand());
3954
3955
	// Are we using multiple directories?
3956
	if (is_array($modSettings['attachmentUploadDir']))
3957
		$path = $modSettings['attachmentUploadDir'][$dir];
3958
3959
	else
3960
		$path = $modSettings['attachmentUploadDir'];
3961
3962
	return $path . '/' . $attachment_id . '_' . $file_hash .'.dat';
3963
}
3964
3965
/**
3966
 * Convert a single IP to a ranged IP.
3967
 * internal function used to convert a user-readable format to a format suitable for the database.
3968
 *
3969
 * @param string $fullip The full IP
3970
 * @return array An array of IP parts
3971
 */
3972
function ip2range($fullip)
3973
{
3974
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
3975
	if ($fullip == 'unknown')
3976
		$fullip = '255.255.255.255';
3977
3978
	$ip_parts = explode('-', $fullip);
3979
	$ip_array = array();
3980
3981
	// 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...
3982
	if (count($ip_parts) == 1 && isValidIP($fullip))
3983
	{
3984
		$ip_array['low'] = $fullip;
3985
		$ip_array['high'] = $fullip;
3986
		return $ip_array;
3987
	} // if ip 22.12.* -> 22.12.* - 22.12.*
3988
	elseif (count($ip_parts) == 1)
3989
	{
3990
		$ip_parts[0] = $fullip;
3991
		$ip_parts[1] = $fullip;
3992
	}
3993
3994
	// 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...
3995
	if (count($ip_parts) == 2 && isValidIP($ip_parts[0]) && isValidIP($ip_parts[1]))
3996
	{
3997
		$ip_array['low'] = $ip_parts[0];
3998
		$ip_array['high'] = $ip_parts[1];
3999
		return $ip_array;
4000
	}
4001
	elseif (count($ip_parts) == 2) // if ip 22.22.*-22.22.*
4002
	{
4003
		$valid_low = isValidIP($ip_parts[0]);
4004
		$valid_high = isValidIP($ip_parts[1]);
4005
		$count = 0;
4006
		$mode = (preg_match('/:/',$ip_parts[0]) > 0 ? ':' : '.');
4007
		$max = ($mode == ':' ? 'ffff' : '255');
4008
		$min = 0;
4009 View Code Duplication
		if(!$valid_low)
4010
		{
4011
			$ip_parts[0] = preg_replace('/\*/', '0', $ip_parts[0]);
4012
			$valid_low = isValidIP($ip_parts[0]);
4013
			while (!$valid_low)
4014
			{
4015
				$ip_parts[0] .= $mode . $min;
4016
				$valid_low = isValidIP($ip_parts[0]);
4017
				$count++;
4018
				if ($count > 9) break;
4019
			}
4020
		}
4021
4022
		$count = 0;
4023 View Code Duplication
		if(!$valid_high)
4024
		{
4025
			$ip_parts[1] = preg_replace('/\*/', $max, $ip_parts[1]);
4026
			$valid_high = isValidIP($ip_parts[1]);
4027
			while (!$valid_high)
4028
			{
4029
				$ip_parts[1] .= $mode . $max;
4030
				$valid_high = isValidIP($ip_parts[1]);
4031
				$count++;
4032
				if ($count > 9) break;
4033
			}
4034
		}
4035
4036
		if($valid_high && $valid_low)
4037
		{
4038
			$ip_array['low'] = $ip_parts[0];
4039
			$ip_array['high'] = $ip_parts[1];
4040
		}
4041
4042
	}
4043
4044
	return $ip_array;
4045
}
4046
4047
/**
4048
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
4049
 *
4050
 * @param string $ip The IP to get the hostname from
4051
 * @return string The hostname
4052
 */
4053
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...
4054
{
4055
	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...
4056
4057
	if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null)
4058
		return $host;
4059
	$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...
4060
4061
	// Try the Linux host command, perhaps?
4062
	if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
4063
	{
4064
		if (!isset($modSettings['host_to_dis']))
4065
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
4066
		else
4067
			$test = @shell_exec('host ' . @escapeshellarg($ip));
4068
4069
		// Did host say it didn't find anything?
4070
		if (strpos($test, 'not found') !== false)
4071
			$host = '';
4072
		// Invalid server option?
4073
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
4074
			updateSettings(array('host_to_dis' => 1));
4075
		// Maybe it found something, after all?
4076
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
4077
			$host = $match[1];
4078
	}
4079
4080
	// This is nslookup; usually only Windows, but possibly some Unix?
4081
	if (!isset($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
4082
	{
4083
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
4084
		if (strpos($test, 'Non-existent domain') !== false)
4085
			$host = '';
4086
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
4087
			$host = $match[1];
4088
	}
4089
4090
	// This is the last try :/.
4091
	if (!isset($host) || $host === false)
4092
		$host = @gethostbyaddr($ip);
4093
4094
	// It took a long time, so let's cache it!
4095 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5)
4096
		cache_put_data('hostlookup-' . $ip, $host, 600);
4097
4098
	return $host;
4099
}
4100
4101
/**
4102
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
4103
 *
4104
 * @param string $text The text to split into words
4105
 * @param int $max_chars The maximum number of characters per word
4106
 * @param bool $encrypt Whether to encrypt the results
4107
 * @return array An array of ints or words depending on $encrypt
4108
 */
4109
function text2words($text, $max_chars = 20, $encrypt = false)
4110
{
4111
	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...
4112
4113
	// Step 1: Remove entities/things we don't consider words:
4114
	$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>' => ' ')));
4115
4116
	// Step 2: Entities we left to letters, where applicable, lowercase.
4117
	$words = un_htmlspecialchars($smcFunc['strtolower']($words));
4118
4119
	// Step 3: Ready to split apart and index!
4120
	$words = explode(' ', $words);
4121
4122
	if ($encrypt)
4123
	{
4124
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
4125
		$returned_ints = array();
4126
		foreach ($words as $word)
4127
		{
4128
			if (($word = trim($word, '-_\'')) !== '')
4129
			{
4130
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
4131
				$total = 0;
4132
				for ($i = 0; $i < $max_chars; $i++)
4133
					$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
4134
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
4135
			}
4136
		}
4137
		return array_unique($returned_ints);
4138
	}
4139
	else
4140
	{
4141
		// Trim characters before and after and add slashes for database insertion.
4142
		$returned_words = array();
4143
		foreach ($words as $word)
4144
			if (($word = trim($word, '-_\'')) !== '')
4145
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
4146
4147
		// Filter out all words that occur more than once.
4148
		return array_unique($returned_words);
4149
	}
4150
}
4151
4152
/**
4153
 * Creates an image/text button
4154
 *
4155
 * @param string $name The name of the button (should be a generic_icons class or the name of an image)
4156
 * @param string $alt The alt text
4157
 * @param string $label The $txt string to use as the label
4158
 * @param string $custom Custom text/html to add to the img tag (only when using an actual image)
4159
 * @param boolean $force_use Whether to force use of this when template_create_button is available
4160
 * @return string The HTML to display the button
4161
 */
4162
function create_button($name, $alt, $label = '', $custom = '', $force_use = false)
4163
{
4164
	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...
4165
4166
	// Does the current loaded theme have this and we are not forcing the usage of this function?
4167
	if (function_exists('template_create_button') && !$force_use)
4168
		return template_create_button($name, $alt, $label = '', $custom = '');
4169
4170
	if (!$settings['use_image_buttons'])
4171
		return $txt[$alt];
4172
	elseif (!empty($settings['use_buttons']))
4173
		return '<span class="generic_icons ' . $name . '" alt="' . $txt[$alt] . '"></span>' . ($label != '' ? '&nbsp;<strong>' . $txt[$label] . '</strong>' : '');
4174
	else
4175
		return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . '>';
4176
}
4177
4178
/**
4179
 * Sets up all of the top menu buttons
4180
 * Saves them in the cache if it is available and on
4181
 * Places the results in $context
4182
 *
4183
 */
4184
function setupMenuContext()
4185
{
4186
	global $context, $modSettings, $user_info, $txt, $scripturl, $sourcedir, $settings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
4187
4188
	// Set up the menu privileges.
4189
	$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts'));
4190
	$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'));
4191
4192
	$context['allow_memberlist'] = allowedTo('view_mlist');
4193
	$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
4194
	$context['allow_moderation_center'] = $context['user']['can_mod'];
4195
	$context['allow_pm'] = allowedTo('pm_read');
4196
4197
	$cacheTime = $modSettings['lastActive'] * 60;
4198
4199
	// Initial "can you post an event in the calendar" option - but this might have been set in the calendar already.
4200
	if (!isset($context['allow_calendar_event']))
4201
	{
4202
		$context['allow_calendar_event'] = $context['allow_calendar'] && allowedTo('calendar_post');
4203
4204
		// If you don't allow events not linked to posts and you're not an admin, we have more work to do...
4205 View Code Duplication
		if ($context['allow_calendar'] && $context['allow_calendar_event'] && empty($modSettings['cal_allow_unlinked']) && !$user_info['is_admin'])
4206
		{
4207
			$boards_can_post = boardsAllowedTo('post_new');
4208
			$context['allow_calendar_event'] &= !empty($boards_can_post);
4209
		}
4210
	}
4211
4212
	// There is some menu stuff we need to do if we're coming at this from a non-guest perspective.
4213
	if (!$context['user']['is_guest'])
4214
	{
4215
		addInlineJavaScript('
4216
	var user_menus = new smc_PopupMenu();
4217
	user_menus.add("profile", "' . $scripturl . '?action=profile;area=popup");
4218
	user_menus.add("alerts", "' . $scripturl . '?action=profile;area=alerts_popup;u='. $context['user']['id'] .'");', true);
4219
		if ($context['allow_pm'])
4220
			addInlineJavaScript('
4221
	user_menus.add("pm", "' . $scripturl . '?action=pm;sa=popup");', true);
4222
4223
		if (!empty($modSettings['enable_ajax_alerts']))
4224
		{
4225
			require_once($sourcedir . '/Subs-Notify.php');
4226
4227
			$timeout = getNotifyPrefs($context['user']['id'], 'alert_timeout', true);
4228
			$timeout = empty($timeout) ? 10000 : $timeout[$context['user']['id']]['alert_timeout'] * 1000;
4229
4230
			addInlineJavaScript('
4231
	var new_alert_title = "' . $context['forum_name'] . '";
4232
	var alert_timeout = ' . $timeout . ';');
4233
			loadJavaScriptFile('alerts.js', array('minimize' => true), 'smf_alerts');
4234
		}
4235
	}
4236
4237
	// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
4238
	if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
4239
	{
4240
		$buttons = array(
4241
			'home' => array(
4242
				'title' => $txt['home'],
4243
				'href' => $scripturl,
4244
				'show' => true,
4245
				'sub_buttons' => array(
4246
				),
4247
				'is_last' => $context['right_to_left'],
4248
			),
4249
			'search' => array(
4250
				'title' => $txt['search'],
4251
				'href' => $scripturl . '?action=search',
4252
				'show' => $context['allow_search'],
4253
				'sub_buttons' => array(
4254
				),
4255
			),
4256
			'admin' => array(
4257
				'title' => $txt['admin'],
4258
				'href' => $scripturl . '?action=admin',
4259
				'show' => $context['allow_admin'],
4260
				'sub_buttons' => array(
4261
					'featuresettings' => array(
4262
						'title' => $txt['modSettings_title'],
4263
						'href' => $scripturl . '?action=admin;area=featuresettings',
4264
						'show' => allowedTo('admin_forum'),
4265
					),
4266
					'packages' => array(
4267
						'title' => $txt['package'],
4268
						'href' => $scripturl . '?action=admin;area=packages',
4269
						'show' => allowedTo('admin_forum'),
4270
					),
4271
					'errorlog' => array(
4272
						'title' => $txt['errlog'],
4273
						'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
4274
						'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']),
4275
					),
4276
					'permissions' => array(
4277
						'title' => $txt['edit_permissions'],
4278
						'href' => $scripturl . '?action=admin;area=permissions',
4279
						'show' => allowedTo('manage_permissions'),
4280
					),
4281
					'memberapprove' => array(
4282
						'title' => $txt['approve_members_waiting'],
4283
						'href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve',
4284
						'show' => !empty($context['unapproved_members']),
4285
						'is_last' => true,
4286
					),
4287
				),
4288
			),
4289
			'moderate' => array(
4290
				'title' => $txt['moderate'],
4291
				'href' => $scripturl . '?action=moderate',
4292
				'show' => $context['allow_moderation_center'],
4293
				'sub_buttons' => array(
4294
					'modlog' => array(
4295
						'title' => $txt['modlog_view'],
4296
						'href' => $scripturl . '?action=moderate;area=modlog',
4297
						'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4298
					),
4299
					'poststopics' => array(
4300
						'title' => $txt['mc_unapproved_poststopics'],
4301
						'href' => $scripturl . '?action=moderate;area=postmod;sa=posts',
4302
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4303
					),
4304
					'attachments' => array(
4305
						'title' => $txt['mc_unapproved_attachments'],
4306
						'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments',
4307
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4308
					),
4309
					'reports' => array(
4310
						'title' => $txt['mc_reported_posts'],
4311
						'href' => $scripturl . '?action=moderate;area=reportedposts',
4312
						'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4313
					),
4314
					'reported_members' => array(
4315
						'title' => $txt['mc_reported_members'],
4316
						'href' => $scripturl . '?action=moderate;area=reportedmembers',
4317
						'show' => allowedTo('moderate_forum'),
4318
						'is_last' => true,
4319
					)
4320
				),
4321
			),
4322
			'calendar' => array(
4323
				'title' => $txt['calendar'],
4324
				'href' => $scripturl . '?action=calendar',
4325
				'show' => $context['allow_calendar'],
4326
				'sub_buttons' => array(
4327
					'view' => array(
4328
						'title' => $txt['calendar_menu'],
4329
						'href' => $scripturl . '?action=calendar',
4330
						'show' => $context['allow_calendar_event'],
4331
					),
4332
					'post' => array(
4333
						'title' => $txt['calendar_post_event'],
4334
						'href' => $scripturl . '?action=calendar;sa=post',
4335
						'show' => $context['allow_calendar_event'],
4336
						'is_last' => true,
4337
					),
4338
				),
4339
			),
4340
			'mlist' => array(
4341
				'title' => $txt['members_title'],
4342
				'href' => $scripturl . '?action=mlist',
4343
				'show' => $context['allow_memberlist'],
4344
				'sub_buttons' => array(
4345
					'mlist_view' => array(
4346
						'title' => $txt['mlist_menu_view'],
4347
						'href' => $scripturl . '?action=mlist',
4348
						'show' => true,
4349
					),
4350
					'mlist_search' => array(
4351
						'title' => $txt['mlist_search'],
4352
						'href' => $scripturl . '?action=mlist;sa=search',
4353
						'show' => true,
4354
						'is_last' => true,
4355
					),
4356
				),
4357
			),
4358
			'signup' => array(
4359
				'title' => $txt['register'],
4360
				'href' => $scripturl . '?action=signup',
4361
				'show' => $user_info['is_guest'] && $context['can_register'],
4362
				'sub_buttons' => array(
4363
				),
4364
				'is_last' => !$context['right_to_left'],
4365
			),
4366
			'logout' => array(
4367
				'title' => $txt['logout'],
4368
				'href' => $scripturl . '?action=logout;%1$s=%2$s',
4369
				'show' => !$user_info['is_guest'],
4370
				'sub_buttons' => array(
4371
				),
4372
				'is_last' => !$context['right_to_left'],
4373
			),
4374
		);
4375
4376
		// Allow editing menu buttons easily.
4377
		call_integration_hook('integrate_menu_buttons', array(&$buttons));
4378
4379
		// Now we put the buttons in the context so the theme can use them.
4380
		$menu_buttons = array();
4381
		foreach ($buttons as $act => $button)
4382
			if (!empty($button['show']))
4383
			{
4384
				$button['active_button'] = false;
4385
4386
				// This button needs some action.
4387
				if (isset($button['action_hook']))
4388
					$needs_action_hook = true;
4389
4390
				// Make sure the last button truly is the last button.
4391
				if (!empty($button['is_last']))
4392
				{
4393
					if (isset($last_button))
4394
						unset($menu_buttons[$last_button]['is_last']);
4395
					$last_button = $act;
4396
				}
4397
4398
				// Go through the sub buttons if there are any.
4399
				if (!empty($button['sub_buttons']))
4400
					foreach ($button['sub_buttons'] as $key => $subbutton)
4401
					{
4402
						if (empty($subbutton['show']))
4403
							unset($button['sub_buttons'][$key]);
4404
4405
						// 2nd level sub buttons next...
4406
						if (!empty($subbutton['sub_buttons']))
4407
						{
4408
							foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2)
4409
							{
4410
								if (empty($sub_button2['show']))
4411
									unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
4412
							}
4413
						}
4414
					}
4415
4416
				// Does this button have its own icon?
4417
				if (isset($button['icon']) && file_exists($settings['theme_dir'] . '/images/' . $button['icon']))
4418
					$button['icon'] = '<img src="' . $settings['images_url'] . '/' . $button['icon'] . '" alt="">';
4419
				elseif (isset($button['icon']) && file_exists($settings['default_theme_dir'] . '/images/' . $button['icon']))
4420
					$button['icon'] = '<img src="' . $settings['default_images_url'] . '/' . $button['icon'] . '" alt="">';
4421
				elseif (isset($button['icon']))
4422
					$button['icon'] = '<span class="generic_icons ' . $button['icon'] . '"></span>';
4423
				else
4424
					$button['icon'] = '<span class="generic_icons ' . $act . '"></span>';
4425
4426
				$menu_buttons[$act] = $button;
4427
			}
4428
4429
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
4430
			cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
4431
	}
4432
4433
	$context['menu_buttons'] = $menu_buttons;
4434
4435
	// Logging out requires the session id in the url.
4436
	if (isset($context['menu_buttons']['logout']))
4437
		$context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']);
4438
4439
	// Figure out which action we are doing so we can set the active tab.
4440
	// Default to home.
4441
	$current_action = 'home';
4442
4443
	if (isset($context['menu_buttons'][$context['current_action']]))
4444
		$current_action = $context['current_action'];
4445
	elseif ($context['current_action'] == 'search2')
4446
		$current_action = 'search';
4447
	elseif ($context['current_action'] == 'theme')
4448
		$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin';
4449
	elseif ($context['current_action'] == 'register2')
4450
		$current_action = 'register';
4451
	elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder'))
4452
		$current_action = 'login';
4453
	elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center'])
4454
		$current_action = 'moderate';
4455
4456
	// There are certain exceptions to the above where we don't want anything on the menu highlighted.
4457
	if ($context['current_action'] == 'profile' && !empty($context['user']['is_owner']))
4458
	{
4459
		$current_action = !empty($_GET['area']) && $_GET['area'] == 'showalerts' ? 'self_alerts' : 'self_profile';
4460
		$context[$current_action] = true;
4461
	}
4462
	elseif ($context['current_action'] == 'pm')
4463
	{
4464
		$current_action = 'self_pm';
4465
		$context['self_pm'] = true;
4466
	}
4467
4468
	$total_mod_reports = 0;
4469
4470
	if (!empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1' && !empty($context['open_mod_reports']) && !empty($context['menu_buttons']['moderate']))
4471
	{
4472
		$total_mod_reports = $context['open_mod_reports'];
4473
		$context['menu_buttons']['moderate']['sub_buttons']['reports']['title'] .= ' <span class="amt">' . $context['open_mod_reports'] . '</span>';
4474
	}
4475
4476
	// Show how many errors there are
4477
	if (!empty($context['menu_buttons']['admin']))
4478
	{
4479
		// Get an error count, if necessary
4480
		if (!isset($context['num_errors']))
4481
		{
4482
			$query = $smcFunc['db_query']('', '
4483
				SELECT COUNT(id_error)
4484
				FROM {db_prefix}log_errors',
4485
				array()
4486
			);
4487
4488
			list($context['num_errors']) = $smcFunc['db_fetch_row']($query);
4489
			$smcFunc['db_free_result']($query);
4490
		}
4491
4492
		if (!empty($context['num_errors']))
4493
		{
4494
			$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4495
			$context['menu_buttons']['admin']['sub_buttons']['errorlog']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4496
		}
4497
	}
4498
4499
	// Show number of reported members
4500
	if (!empty($context['open_member_reports']) && !empty($context['menu_buttons']['moderate']))
4501
	{
4502
		$total_mod_reports += $context['open_member_reports'];
4503
		$context['menu_buttons']['moderate']['sub_buttons']['reported_members']['title'] .= ' <span class="amt">' . $context['open_member_reports'] . '</span>';
4504
	}
4505
4506
	if (!empty($context['unapproved_members']) && !empty($context['menu_buttons']['admin']))
4507
	{
4508
		$context['menu_buttons']['admin']['sub_buttons']['memberapprove']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4509
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4510
	}
4511
4512
	// Do we have any open reports?
4513
	if ($total_mod_reports > 0 && !empty($context['menu_buttons']['moderate']))
4514
	{
4515
		$context['menu_buttons']['moderate']['title'] .= ' <span class="amt">' . $total_mod_reports . '</span>';
4516
	}
4517
4518
	// Not all actions are simple.
4519
	if (!empty($needs_action_hook))
4520
		call_integration_hook('integrate_current_action', array(&$current_action));
4521
4522
	if (isset($context['menu_buttons'][$current_action]))
4523
		$context['menu_buttons'][$current_action]['active_button'] = true;
4524
}
4525
4526
/**
4527
 * Generate a random seed and ensure it's stored in settings.
4528
 */
4529
function smf_seed_generator()
4530
{
4531
	updateSettings(array('rand_seed' => microtime(true)));
4532
}
4533
4534
/**
4535
 * Process functions of an integration hook.
4536
 * calls all functions of the given hook.
4537
 * supports static class method calls.
4538
 *
4539
 * @param string $hook The hook name
4540
 * @param array $parameters An array of parameters this hook implements
4541
 * @return array The results of the functions
4542
 */
4543
function call_integration_hook($hook, $parameters = array())
4544
{
4545
	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...
4546
	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...
4547
4548
	if ($db_show_debug === true)
4549
		$context['debug']['hooks'][] = $hook;
4550
4551
	// Need to have some control.
4552
	if (!isset($context['instances']))
4553
		$context['instances'] = array();
4554
4555
	$results = array();
4556
	if (empty($modSettings[$hook]))
4557
		return $results;
4558
4559
	$functions = explode(',', $modSettings[$hook]);
4560
	// Loop through each function.
4561
	foreach ($functions as $function)
4562
	{
4563
		// Hook has been marked as "disabled". Skip it!
4564
		if (strpos($function, '!') !== false)
4565
			continue;
4566
4567
		$call = call_helper($function, true);
4568
4569
		// Is it valid?
4570
		if (!empty($call))
4571
			$results[$function] = call_user_func_array($call, $parameters);
4572
4573
		// Whatever it was suppose to call, it failed :(
4574
		elseif (!empty($function))
4575
		{
4576
			loadLanguage('Errors');
4577
4578
			// Get a full path to show on error.
4579
			if (strpos($function, '|') !== false)
4580
			{
4581
				list ($file, $string) = explode('|', $function);
4582
				$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'])));
4583
				log_error(sprintf($txt['hook_fail_call_to'], $string, $absPath), 'general');
4584
			}
4585
4586
			// "Assume" the file resides on $boarddir somewhere...
4587
			else
4588
				log_error(sprintf($txt['hook_fail_call_to'], $function, $boarddir), 'general');
4589
		}
4590
	}
4591
4592
	return $results;
4593
}
4594
4595
/**
4596
 * Add a function for integration hook.
4597
 * does nothing if the function is already added.
4598
 *
4599
 * @param string $hook The complete hook name.
4600
 * @param string $function The function name. Can be a call to a method via Class::method.
4601
 * @param bool $permanent If true, updates the value in settings table.
4602
 * @param string $file The file. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4603
 * @param bool $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4604
 */
4605
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
4606
{
4607
	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...
4608
4609
	// Any objects?
4610
	if ($object)
4611
		$function = $function . '#';
4612
4613
	// Any files  to load?
4614
	if (!empty($file) && is_string($file))
4615
		$function = $file . (!empty($function) ? '|' . $function : '');
4616
4617
	// Get the correct string.
4618
	$integration_call = $function;
4619
4620
	// Is it going to be permanent?
4621
	if ($permanent)
4622
	{
4623
		$request = $smcFunc['db_query']('', '
4624
			SELECT value
4625
			FROM {db_prefix}settings
4626
			WHERE variable = {string:variable}',
4627
			array(
4628
				'variable' => $hook,
4629
			)
4630
		);
4631
		list ($current_functions) = $smcFunc['db_fetch_row']($request);
4632
		$smcFunc['db_free_result']($request);
4633
4634
		if (!empty($current_functions))
4635
		{
4636
			$current_functions = explode(',', $current_functions);
4637
			if (in_array($integration_call, $current_functions))
4638
				return;
4639
4640
			$permanent_functions = array_merge($current_functions, array($integration_call));
4641
		}
4642
		else
4643
			$permanent_functions = array($integration_call);
4644
4645
		updateSettings(array($hook => implode(',', $permanent_functions)));
4646
	}
4647
4648
	// Make current function list usable.
4649
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4650
4651
	// Do nothing, if it's already there.
4652
	if (in_array($integration_call, $functions))
4653
		return;
4654
4655
	$functions[] = $integration_call;
4656
	$modSettings[$hook] = implode(',', $functions);
4657
}
4658
4659
/**
4660
 * Remove an integration hook function.
4661
 * Removes the given function from the given hook.
4662
 * Does nothing if the function is not available.
4663
 *
4664
 * @param string $hook The complete hook name.
4665
 * @param string $function The function name. Can be a call to a method via Class::method.
4666
 * @param boolean $permanent Irrelevant for the function itself but need to declare it to match
4667
 * @param string $file The filename. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4668
 * @param boolean $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4669
 * @see add_integration_function
4670
 */
4671
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...
4672
{
4673
	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...
4674
4675
	// Any objects?
4676
	if ($object)
4677
		$function = $function . '#';
4678
4679
	// Any files  to load?
4680
	if (!empty($file) && is_string($file))
4681
		$function = $file . '|' . $function;
4682
4683
	// Get the correct string.
4684
	$integration_call = $function;
4685
4686
	// Get the permanent functions.
4687
	$request = $smcFunc['db_query']('', '
4688
		SELECT value
4689
		FROM {db_prefix}settings
4690
		WHERE variable = {string:variable}',
4691
		array(
4692
			'variable' => $hook,
4693
		)
4694
	);
4695
	list ($current_functions) = $smcFunc['db_fetch_row']($request);
4696
	$smcFunc['db_free_result']($request);
4697
4698
	if (!empty($current_functions))
4699
	{
4700
		$current_functions = explode(',', $current_functions);
4701
4702
		if (in_array($integration_call, $current_functions))
4703
			updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call)))));
4704
	}
4705
4706
	// Turn the function list into something usable.
4707
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4708
4709
	// You can only remove it if it's available.
4710
	if (!in_array($integration_call, $functions))
4711
		return;
4712
4713
	$functions = array_diff($functions, array($integration_call));
4714
	$modSettings[$hook] = implode(',', $functions);
4715
}
4716
4717
/**
4718
 * Receives a string and tries to figure it out if its a method or a function.
4719
 * If a method is found, it looks for a "#" which indicates SMF should create a new instance of the given class.
4720
 * Checks the string/array for is_callable() and return false/fatal_lang_error is the given value results in a non callable string/array.
4721
 * Prepare and returns a callable depending on the type of method/function found.
4722
 *
4723
 * @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)
4724
 * @param boolean $return If true, the function will not call the function/method but instead will return the formatted string.
4725
 * @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.
4726
 */
4727
function call_helper($string, $return = false)
4728
{
4729
	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...
4730
4731
	// Really?
4732
	if (empty($string))
4733
		return false;
4734
4735
	// An array? should be a "callable" array IE array(object/class, valid_callable).
4736
	// A closure? should be a callable one.
4737
	if (is_array($string) || $string instanceof Closure)
4738
		return $return ? $string : (is_callable($string) ? call_user_func($string) : false);
4739
4740
	// No full objects, sorry! pass a method or a property instead!
4741
	if (is_object($string))
4742
		return false;
4743
4744
	// Stay vitaminized my friends...
4745
	$string = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($string));
4746
4747
	// Is there a file to load?
4748
	$string = load_file($string);
4749
4750
	// Loaded file failed
4751
	if (empty($string))
4752
		return false;
4753
4754
	// Found a method.
4755
	if (strpos($string, '::') !== false)
4756
	{
4757
		list ($class, $method) = explode('::', $string);
4758
4759
		// Check if a new object will be created.
4760
		if (strpos($method, '#') !== false)
4761
		{
4762
			// Need to remove the # thing.
4763
			$method = str_replace('#', '', $method);
4764
4765
			// Don't need to create a new instance for every method.
4766
			if (empty($context['instances'][$class]) || !($context['instances'][$class] instanceof $class))
4767
			{
4768
				$context['instances'][$class] = new $class;
4769
4770
				// Add another one to the list.
4771
				if ($db_show_debug === true)
4772
				{
4773
					if (!isset($context['debug']['instances']))
4774
						$context['debug']['instances'] = array();
4775
4776
					$context['debug']['instances'][$class] = $class;
4777
				}
4778
			}
4779
4780
			$func = array($context['instances'][$class], $method);
4781
		}
4782
4783
		// Right then. This is a call to a static method.
4784
		else
4785
			$func = array($class, $method);
4786
	}
4787
4788
	// Nope! just a plain regular function.
4789
	else
4790
		$func = $string;
4791
4792
	// Right, we got what we need, time to do some checks.
4793
	if (!is_callable($func, false, $callable_name))
4794
	{
4795
		loadLanguage('Errors');
4796
		log_error(sprintf($txt['subAction_fail'], $callable_name), 'general');
4797
4798
		// Gotta tell everybody.
4799
		return false;
4800
	}
4801
4802
	// Everything went better than expected.
4803
	else
4804
	{
4805
		// What are we gonna do about it?
4806
		if ($return)
4807
			return $func;
4808
4809
		// If this is a plain function, avoid the heat of calling call_user_func().
4810
		else
4811
		{
4812
			if (is_array($func))
4813
				call_user_func($func);
4814
4815
			else
4816
				$func();
4817
		}
4818
	}
4819
}
4820
4821
/**
4822
 * Receives a string and tries to figure it out if it contains info to load a file.
4823
 * Checks for a | (pipe) symbol and tries to load a file with the info given.
4824
 * 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.
4825
 *
4826
 * @param string $string The string containing a valid format.
4827
 * @return string|boolean The given string with the pipe and file info removed. Boolean false if the file couldn't be loaded.
4828
 */
4829
function load_file($string)
4830
{
4831
	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...
4832
4833
	if (empty($string))
4834
		return false;
4835
4836
	if (strpos($string, '|') !== false)
4837
	{
4838
		list ($file, $string) = explode('|', $string);
4839
4840
		// Match the wildcards to their regular vars.
4841
		if (empty($settings['theme_dir']))
4842
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
4843
4844
		else
4845
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
4846
4847
		// Load the file if it can be loaded.
4848
		if (file_exists($absPath))
4849
			require_once($absPath);
4850
4851
		// No? try a fallback to $sourcedir
4852
		else
4853
		{
4854
			$absPath = $sourcedir .'/'. $file;
4855
4856
			if (file_exists($absPath))
4857
				require_once($absPath);
4858
4859
			// Sorry, can't do much for you at this point.
4860
			else
4861
			{
4862
				loadLanguage('Errors');
4863
				log_error(sprintf($txt['hook_fail_loading_file'], $absPath), 'general');
4864
4865
				// File couldn't be loaded.
4866
				return false;
4867
			}
4868
		}
4869
	}
4870
4871
	return $string;
4872
}
4873
4874
/**
4875
 * Get the contents of a URL, irrespective of allow_url_fopen.
4876
 *
4877
 * - reads the contents of an http or ftp address and returns the page in a string
4878
 * - will accept up to 3 page redirections (redirectio_level in the function call is private)
4879
 * - if post_data is supplied, the value and length is posted to the given url as form data
4880
 * - URL must be supplied in lowercase
4881
 *
4882
 * @param string $url The URL
4883
 * @param string $post_data The data to post to the given URL
4884
 * @param bool $keep_alive Whether to send keepalive info
4885
 * @param int $redirection_level How many levels of redirection
4886
 * @return string|false The fetched data or false on failure
4887
 */
4888
function fetch_web_data($url, $post_data = '', $keep_alive = false, $redirection_level = 0)
4889
{
4890
	global $webmaster_email, $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...
4891
	static $keep_alive_dom = null, $keep_alive_fp = null;
4892
4893
	preg_match('~^(http|ftp)(s)?://([^/:]+)(:(\d+))?(.+)$~', $url, $match);
4894
4895
	// No scheme? No data for you!
4896
	if (empty($match[1]))
4897
		return false;
4898
4899
	// An FTP url. We should try connecting and RETRieving it...
4900
	elseif ($match[1] == 'ftp')
4901
	{
4902
		// Include the file containing the ftp_connection class.
4903
		require_once($sourcedir . '/Class-Package.php');
4904
4905
		// Establish a connection and attempt to enable passive mode.
4906
		$ftp = new ftp_connection(($match[2] ? 'ssl://' : '') . $match[3], empty($match[5]) ? 21 : $match[5], 'anonymous', $webmaster_email);
4907
		if ($ftp->error !== false || !$ftp->passive())
4908
			return false;
4909
4910
		// I want that one *points*!
4911
		fwrite($ftp->connection, 'RETR ' . $match[6] . "\r\n");
4912
4913
		// Since passive mode worked (or we would have returned already!) open the connection.
4914
		$fp = @fsockopen($ftp->pasv['ip'], $ftp->pasv['port'], $err, $err, 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...
4915
		if (!$fp)
4916
			return false;
4917
4918
		// The server should now say something in acknowledgement.
4919
		$ftp->check_response(150);
4920
4921
		$data = '';
4922
		while (!feof($fp))
4923
			$data .= fread($fp, 4096);
4924
		fclose($fp);
4925
4926
		// All done, right?  Good.
4927
		$ftp->check_response(226);
4928
		$ftp->close();
4929
	}
4930
4931
	// This is more likely; a standard HTTP URL.
4932
	elseif (isset($match[1]) && $match[1] == 'http')
4933
	{
4934
		// First try to use fsockopen, because it is fastest.
4935
		if ($keep_alive && $match[3] == $keep_alive_dom)
4936
			$fp = $keep_alive_fp;
4937
		if (empty($fp))
4938
		{
4939
			// Open the socket on the port we want...
4940
			$fp = @fsockopen(($match[2] ? 'ssl://' : '') . $match[3], empty($match[5]) ? ($match[2] ? 443 : 80) : $match[5], $err, $err, 5);
4941
		}
4942
		if (!empty($fp))
4943
		{
4944
			if ($keep_alive)
4945
			{
4946
				$keep_alive_dom = $match[3];
4947
				$keep_alive_fp = $fp;
4948
			}
4949
4950
			// I want this, from there, and I'm not going to be bothering you for more (probably.)
4951
			if (empty($post_data))
4952
			{
4953
				fwrite($fp, 'GET ' . ($match[6] !== '/' ? str_replace(' ', '%20', $match[6]) : '') . ' HTTP/1.0' . "\r\n");
4954
				fwrite($fp, 'Host: ' . $match[3] . (empty($match[5]) ? ($match[2] ? ':443' : '') : ':' . $match[5]) . "\r\n");
4955
				fwrite($fp, 'user-agent: PHP/SMF' . "\r\n");
4956
				if ($keep_alive)
4957
					fwrite($fp, 'connection: Keep-Alive' . "\r\n\r\n");
4958
				else
4959
					fwrite($fp, 'connection: close' . "\r\n\r\n");
4960
			}
4961
			else
4962
			{
4963
				fwrite($fp, 'POST ' . ($match[6] !== '/' ? $match[6] : '') . ' HTTP/1.0' . "\r\n");
4964
				fwrite($fp, 'Host: ' . $match[3] . (empty($match[5]) ? ($match[2] ? ':443' : '') : ':' . $match[5]) . "\r\n");
4965
				fwrite($fp, 'user-agent: PHP/SMF' . "\r\n");
4966
				if ($keep_alive)
4967
					fwrite($fp, 'connection: Keep-Alive' . "\r\n");
4968
				else
4969
					fwrite($fp, 'connection: close' . "\r\n");
4970
				fwrite($fp, 'content-type: application/x-www-form-urlencoded' . "\r\n");
4971
				fwrite($fp, 'content-length: ' . strlen($post_data) . "\r\n\r\n");
4972
				fwrite($fp, $post_data);
4973
			}
4974
4975
			$response = fgets($fp, 768);
4976
4977
			// Redirect in case this location is permanently or temporarily moved.
4978
			if ($redirection_level < 3 && preg_match('~^HTTP/\S+\s+30[127]~i', $response) === 1)
4979
			{
4980
				$header = '';
4981
				$location = '';
4982
				while (!feof($fp) && trim($header = fgets($fp, 4096)) != '')
4983
					if (strpos($header, 'location:') !== false)
4984
						$location = trim(substr($header, strpos($header, ':') + 1));
4985
4986
				if (empty($location))
4987
					return false;
4988
				else
4989
				{
4990
					if (!$keep_alive)
4991
						fclose($fp);
4992
					return fetch_web_data($location, $post_data, $keep_alive, $redirection_level + 1);
4993
				}
4994
			}
4995
4996
			// Make sure we get a 200 OK.
4997
			elseif (preg_match('~^HTTP/\S+\s+20[01]~i', $response) === 0)
4998
				return false;
4999
5000
			// Skip the headers...
5001
			while (!feof($fp) && trim($header = fgets($fp, 4096)) != '')
5002
			{
5003
				if (preg_match('~content-length:\s*(\d+)~i', $header, $match) != 0)
5004
					$content_length = $match[1];
5005
				elseif (preg_match('~connection:\s*close~i', $header) != 0)
5006
				{
5007
					$keep_alive_dom = null;
5008
					$keep_alive = false;
5009
				}
5010
5011
				continue;
5012
			}
5013
5014
			$data = '';
5015
			if (isset($content_length))
5016
			{
5017
				while (!feof($fp) && strlen($data) < $content_length)
5018
					$data .= fread($fp, $content_length - strlen($data));
5019
			}
5020
			else
5021
			{
5022
				while (!feof($fp))
5023
					$data .= fread($fp, 4096);
5024
			}
5025
5026
			if (!$keep_alive)
5027
				fclose($fp);
5028
		}
5029
5030
		// If using fsockopen didn't work, try to use cURL if available.
5031
		elseif (function_exists('curl_init'))
5032
		{
5033
			// Include the file containing the curl_fetch_web_data class.
5034
			require_once($sourcedir . '/Class-CurlFetchWeb.php');
5035
5036
			$fetch_data = new curl_fetch_web_data();
5037
			$fetch_data->get_url_data($url, $post_data);
0 ignored issues
show
Documentation introduced by
$post_data is of type string, but the function expects a array.

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...
5038
5039
			// no errors and a 200 result, then we have a good dataset, well we at least have data. ;)
5040
			if ($fetch_data->result('code') == 200 && !$fetch_data->result('error'))
5041
				$data = $fetch_data->result('body');
5042
			else
5043
				return false;
5044
		}
5045
5046
		// Neither fsockopen nor curl are available. Well, phooey.
5047
		else
5048
			return false;
5049
	}
5050
	else
5051
	{
5052
		// Umm, this shouldn't happen?
5053
		trigger_error('fetch_web_data(): Bad URL', E_USER_NOTICE);
5054
		$data = false;
5055
	}
5056
5057
	return $data;
5058
}
5059
5060
/**
5061
 * Prepares an array of "likes" info for the topic specified by $topic
5062
 * @param integer $topic The topic ID to fetch the info from.
5063
 * @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...
5064
 */
5065
function prepareLikesContext($topic)
5066
{
5067
	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...
5068
5069
	// Make sure we have something to work with.
5070
	if (empty($topic))
5071
		return array();
5072
5073
5074
	// We already know the number of likes per message, we just want to know whether the current user liked it or not.
5075
	$user = $user_info['id'];
5076
	$cache_key = 'likes_topic_' . $topic . '_' . $user;
5077
	$ttl = 180;
5078
5079
	if (($temp = cache_get_data($cache_key, $ttl)) === null)
5080
	{
5081
		$temp = array();
5082
		$request = $smcFunc['db_query']('', '
5083
			SELECT content_id
5084
			FROM {db_prefix}user_likes AS l
5085
				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
5086
			WHERE l.id_member = {int:current_user}
5087
				AND l.content_type = {literal:msg}
5088
				AND m.id_topic = {int:topic}',
5089
			array(
5090
				'current_user' => $user,
5091
				'topic' => $topic,
5092
			)
5093
		);
5094
		while ($row = $smcFunc['db_fetch_assoc']($request))
5095
			$temp[] = (int) $row['content_id'];
5096
5097
		cache_put_data($cache_key, $temp, $ttl);
5098
	}
5099
5100
	return $temp;
5101
}
5102
5103
/**
5104
 * Microsoft uses their own character set Code Page 1252 (CP1252), which is a
5105
 * superset of ISO 8859-1, defining several characters between DEC 128 and 159
5106
 * that are not normally displayable.  This converts the popular ones that
5107
 * appear from a cut and paste from windows.
5108
 *
5109
 * @param string $string The string
5110
 * @return string The sanitized string
5111
 */
5112
function sanitizeMSCutPaste($string)
5113
{
5114
	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...
5115
5116
	if (empty($string))
5117
		return $string;
5118
5119
	// UTF-8 occurences of MS special characters
5120
	$findchars_utf8 = array(
5121
		"\xe2\x80\x9a",	// single low-9 quotation mark
5122
		"\xe2\x80\x9e",	// double low-9 quotation mark
5123
		"\xe2\x80\xa6",	// horizontal ellipsis
5124
		"\xe2\x80\x98",	// left single curly quote
5125
		"\xe2\x80\x99",	// right single curly quote
5126
		"\xe2\x80\x9c",	// left double curly quote
5127
		"\xe2\x80\x9d",	// right double curly quote
5128
		"\xe2\x80\x93",	// en dash
5129
		"\xe2\x80\x94",	// em dash
5130
	);
5131
5132
	// windows 1252 / iso equivalents
5133
	$findchars_iso = array(
5134
		chr(130),
5135
		chr(132),
5136
		chr(133),
5137
		chr(145),
5138
		chr(146),
5139
		chr(147),
5140
		chr(148),
5141
		chr(150),
5142
		chr(151),
5143
	);
5144
5145
	// safe replacements
5146
	$replacechars = array(
5147
		',',	// &sbquo;
5148
		',,',	// &bdquo;
5149
		'...',	// &hellip;
5150
		"'",	// &lsquo;
5151
		"'",	// &rsquo;
5152
		'"',	// &ldquo;
5153
		'"',	// &rdquo;
5154
		'-',	// &ndash;
5155
		'--',	// &mdash;
5156
	);
5157
5158
	if ($context['utf8'])
5159
		$string = str_replace($findchars_utf8, $replacechars, $string);
5160
	else
5161
		$string = str_replace($findchars_iso, $replacechars, $string);
5162
5163
	return $string;
5164
}
5165
5166
/**
5167
 * Decode numeric html entities to their ascii or UTF8 equivalent character.
5168
 *
5169
 * Callback function for preg_replace_callback in subs-members
5170
 * Uses capture group 2 in the supplied array
5171
 * Does basic scan to ensure characters are inside a valid range
5172
 *
5173
 * @param array $matches An array of matches (relevant info should be the 3rd item)
5174
 * @return string A fixed string
5175
 */
5176
function replaceEntities__callback($matches)
5177
{
5178
	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...
5179
5180
	if (!isset($matches[2]))
5181
		return '';
5182
5183
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
5184
5185
	// remove left to right / right to left overrides
5186
	if ($num === 0x202D || $num === 0x202E)
5187
		return '';
5188
5189
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
5190
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
5191
		return '&#' . $num . ';';
5192
5193
	if (empty($context['utf8']))
5194
	{
5195
		// no control characters
5196
		if ($num < 0x20)
5197
			return '';
5198
		// text is text
5199
		elseif ($num < 0x80)
5200
			return chr($num);
5201
		// all others get html-ised
5202
		else
5203
			return '&#' . $matches[2] . ';';
5204
	}
5205
	else
5206
	{
5207
		// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
5208
		// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
5209
		if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
5210
			return '';
5211
		// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
5212
		elseif ($num < 0x80)
5213
			return chr($num);
5214
		// <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...
5215
		elseif ($num < 0x800)
5216
			return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
5217
		// < 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...
5218
		elseif ($num < 0x10000)
5219
			return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
5220
		// <= 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...
5221 View Code Duplication
		else
5222
			return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
5223
	}
5224
}
5225
5226
/**
5227
 * Converts html entities to utf8 equivalents
5228
 *
5229
 * Callback function for preg_replace_callback
5230
 * Uses capture group 1 in the supplied array
5231
 * Does basic checks to keep characters inside a viewable range.
5232
 *
5233
 * @param array $matches An array of matches (relevant info should be the 2nd item in the array)
5234
 * @return string The fixed string
5235
 */
5236
function fixchar__callback($matches)
5237
{
5238
	if (!isset($matches[1]))
5239
		return '';
5240
5241
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
5242
5243
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
5244
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
5245
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
5246
		return '';
5247
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
5248
	elseif ($num < 0x80)
5249
		return chr($num);
5250
	// <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...
5251
	elseif ($num < 0x800)
5252
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
5253
	// < 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...
5254
	elseif ($num < 0x10000)
5255
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
5256
	// <= 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...
5257 View Code Duplication
	else
5258
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
5259
}
5260
5261
/**
5262
 * Strips out invalid html entities, replaces others with html style &#123; codes
5263
 *
5264
 * Callback function used of preg_replace_callback in smcFunc $ent_checks, for example
5265
 * strpos, strlen, substr etc
5266
 *
5267
 * @param array $matches An array of matches (relevant info should be the 3rd item in the array)
5268
 * @return string The fixed string
5269
 */
5270
function entity_fix__callback($matches)
5271
{
5272
	if (!isset($matches[2]))
5273
		return '';
5274
5275
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
5276
5277
	// we don't allow control characters, characters out of range, byte markers, etc
5278
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
5279
		return '';
5280
	else
5281
		return '&#' . $num . ';';
5282
}
5283
5284
/**
5285
 * Return a Gravatar URL based on
5286
 * - the supplied email address,
5287
 * - the global maximum rating,
5288
 * - the global default fallback,
5289
 * - maximum sizes as set in the admin panel.
5290
 *
5291
 * It is SSL aware, and caches most of the parameters.
5292
 *
5293
 * @param string $email_address The user's email address
5294
 * @return string The gravatar URL
5295
 */
5296
function get_gravatar_url($email_address)
5297
{
5298
	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...
5299
	static $url_params = null;
5300
5301
	if ($url_params === null)
5302
	{
5303
		$ratings = array('G', 'PG', 'R', 'X');
5304
		$defaults = array('mm', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank');
5305
		$url_params = array();
5306 View Code Duplication
		if (!empty($modSettings['gravatarMaxRating']) && in_array($modSettings['gravatarMaxRating'], $ratings))
5307
			$url_params[] = 'rating=' . $modSettings['gravatarMaxRating'];
5308 View Code Duplication
		if (!empty($modSettings['gravatarDefault']) && in_array($modSettings['gravatarDefault'], $defaults))
5309
			$url_params[] = 'default=' . $modSettings['gravatarDefault'];
5310
		if (!empty($modSettings['avatar_max_width_external']))
5311
			$size_string = (int) $modSettings['avatar_max_width_external'];
5312
		if (!empty($modSettings['avatar_max_height_external']) && !empty($size_string))
5313
			if ((int) $modSettings['avatar_max_height_external'] < $size_string)
5314
				$size_string = $modSettings['avatar_max_height_external'];
5315
5316
		if (!empty($size_string))
5317
			$url_params[] = 's=' . $size_string;
5318
	}
5319
	$http_method = !empty($modSettings['force_ssl']) ? 'https://secure' : 'http://www';
5320
5321
	return $http_method . '.gravatar.com/avatar/' . md5($smcFunc['strtolower']($email_address)) . '?' . implode('&', $url_params);
5322
}
5323
5324
/**
5325
 * Get a list of timezones.
5326
 *
5327
 * @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'.
5328
 * @return array An array of timezone info.
5329
 */
5330
function smf_list_timezones($when = 'now')
5331
{
5332
	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...
5333
	static $timezones = null, $lastwhen = null;
5334
5335
	// No point doing this over if we already did it once
5336
	if (!empty($timezones) && $when == $lastwhen)
5337
		return $timezones;
5338
	else
5339
		$lastwhen = $when;
5340
5341
	// Parseable datetime string?
5342
	if (is_int($timestamp = strtotime($when)))
5343
		$when = $timestamp;
5344
5345
	// A Unix timestamp?
5346
	elseif (is_numeric($when))
5347
		$when = intval($when);
5348
5349
	// Invalid value? Just get current Unix timestamp.
5350
	else
5351
		$when = time();
5352
5353
	// We'll need these too
5354
	$date_when = date_create('@' . $when);
5355
	$later = (int) date_format(date_add($date_when, date_interval_create_from_date_string('1 year')), 'U');
5356
5357
	// Prefer and give custom descriptions for these time zones
5358
	// If the description is left empty, it will be filled in with the names of matching cities
5359
	$timezone_descriptions = array(
5360
		'America/Adak' => 'Aleutian Islands',
5361
		'Pacific/Marquesas' => 'Marquesas Islands',
5362
		'Pacific/Gambier' => 'Gambier Islands',
5363
		'America/Anchorage' => 'Alaska',
5364
		'Pacific/Pitcairn' => 'Pitcairn Islands',
5365
		'America/Los_Angeles' => 'Pacific Time (USA, Canada)',
5366
		'America/Denver' => 'Mountain Time (USA, Canada)',
5367
		'America/Phoenix' => 'Mountain Time (no DST)',
5368
		'America/Chicago' => 'Central Time (USA, Canada)',
5369
		'America/Belize' => 'Central Time (no DST)',
5370
		'America/New_York' => 'Eastern Time (USA, Canada)',
5371
		'America/Atikokan' => 'Eastern Time (no DST)',
5372
		'America/Halifax' => 'Atlantic Time (Canada)',
5373
		'America/Anguilla' => 'Atlantic Time (no DST)',
5374
		'America/St_Johns' => 'Newfoundland',
5375
		'America/Chihuahua' => 'Chihuahua, Mazatlan',
5376
		'Pacific/Easter' => 'Easter Island',
5377
		'Atlantic/Stanley' => 'Falkland Islands',
5378
		'America/Miquelon' => 'Saint Pierre and Miquelon',
5379
		'America/Argentina/Buenos_Aires' => 'Buenos Aires',
5380
		'America/Sao_Paulo' => 'Brasilia Time',
5381
		'America/Araguaina' => 'Brasilia Time (no DST)',
5382
		'America/Godthab' => 'Greenland',
5383
		'America/Noronha' => 'Fernando de Noronha',
5384
		'Atlantic/Reykjavik' => 'Greenwich Mean Time (no DST)',
5385
		'Europe/London' => '',
5386
		'Europe/Berlin' => 'Central European Time',
5387
		'Europe/Helsinki' => 'Eastern European Time',
5388
		'Africa/Brazzaville' => 'Brazzaville, Lagos, Porto-Novo',
5389
		'Asia/Jerusalem' => 'Jerusalem',
5390
		'Europe/Moscow' => '',
5391
		'Africa/Khartoum' => 'Eastern Africa Time',
5392
		'Asia/Riyadh' => 'Arabia Time',
5393
		'Asia/Kolkata' => 'India, Sri Lanka',
5394
		'Asia/Yekaterinburg' => 'Yekaterinburg, Tyumen',
5395
		'Asia/Dhaka' => 'Astana, Dhaka',
5396
		'Asia/Rangoon' => 'Yangon/Rangoon',
5397
		'Indian/Christmas' => 'Christmas Island',
5398
		'Antarctica/DumontDUrville' => 'Dumont D\'Urville Station',
5399
		'Antarctica/Vostok' => 'Vostok Station',
5400
		'Australia/Lord_Howe' => 'Lord Howe Island',
5401
		'Pacific/Guadalcanal' => 'Solomon Islands',
5402
		'Pacific/Norfolk' => 'Norfolk Island',
5403
		'Pacific/Noumea' => 'New Caledonia',
5404
		'Pacific/Auckland' => 'Auckland, McMurdo Station',
5405
		'Pacific/Kwajalein' => 'Marshall Islands',
5406
		'Pacific/Chatham' => 'Chatham Islands',
5407
	);
5408
5409
	// Should we put time zones from certain countries at the top of the list?
5410
	$priority_countries = !empty($modSettings['timezone_priority_countries']) ? explode(',', $modSettings['timezone_priority_countries']) : array();
5411
	$priority_tzids = array();
5412
	foreach ($priority_countries as $country)
5413
	{
5414
		$country_tzids = @timezone_identifiers_list(DateTimeZone::PER_COUNTRY, strtoupper(trim($country)));
5415
		if (!empty($country_tzids))
5416
			$priority_tzids = array_merge($priority_tzids, $country_tzids);
5417
	}
5418
5419
	// Process the preferred timezones first, then the rest.
5420
	$tzids = array_keys($timezone_descriptions) + array_diff(timezone_identifiers_list(), array_keys($timezone_descriptions));
5421
5422
	// Idea here is to get exactly one representative identifier for each and every unique set of time zone rules.
5423
	foreach ($tzids as $tzid)
5424
	{
5425
		// We don't want UTC right now
5426
		if ($tzid == 'UTC')
5427
			continue;
5428
5429
		$tz = timezone_open($tzid);
5430
5431
		// First, get the set of transition rules for this tzid
5432
		$tzinfo = timezone_transitions_get($tz, $when, $later);
5433
5434
		// Use the entire set of transition rules as the array *key* so we can avoid duplicates
5435
		$tzkey = serialize($tzinfo);
5436
5437
		// Next, get the geographic info for this tzid
5438
		$tzgeo = timezone_location_get($tz);
5439
5440
		// Don't overwrite our preferred tzids
5441
		if (empty($zones[$tzkey]['tzid']))
5442
		{
5443
			$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...
5444
			$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...
5445
		}
5446
5447
		// A time zone from a prioritized country?
5448
		if (in_array($tzid, $priority_tzids))
5449
			$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...
5450
5451
		// Keep track of the location and offset for this tzid
5452
		$tzid_parts = explode('/', $tzid);
5453
		$zones[$tzkey]['locations'][] = str_replace(array('St_', '_'), array('St. ', ' '), array_pop($tzid_parts));
5454
		$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...
5455
		$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...
5456
	}
5457
5458
	// Sort by offset then longitude
5459
	array_multisort($offsets, SORT_ASC, SORT_NUMERIC, $longitudes, SORT_ASC, SORT_NUMERIC, $zones);
5460
5461
	// Build the final array of formatted values
5462
	$priority_timezones = array();
5463
	$timezones = array();
5464
	foreach ($zones as $tzkey => $tzvalue)
5465
	{
5466
		date_timezone_set($date_when, timezone_open($tzvalue['tzid']));
5467
5468
		if (!empty($timezone_descriptions[$tzvalue['tzid']]))
5469
			$desc = $timezone_descriptions[$tzvalue['tzid']];
5470
		else
5471
			$desc = implode(', ', array_unique($tzvalue['locations']));
5472
5473
		if (isset($priority_zones[$tzkey]))
5474
			$priority_timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']';
5475
		else
5476
			$timezones[$tzvalue['tzid']] = $tzvalue['abbr'] . ' - ' . $desc . ' [UTC' . date_format($date_when, 'P') . ']';
5477
	}
5478
5479
	$timezones = array_merge(
5480
		$priority_timezones,
5481
		array('' => '(Forum Default)', 'UTC' => 'UTC - Coordinated Universal Time'),
5482
		$timezones
5483
	);
5484
5485
	return $timezones;
5486
}
5487
5488
/**
5489
 * Reformats certain time zone abbreviations to look better.
5490
 *
5491
 * Some of PHP's time zone abbreviations are just numerical offsets from UTC, e.g. '+04'
5492
 * These look weird and are kind of useless, so we make them look better.
5493
 *
5494
 * @param string $tzid The Olsen time zome identifier for a time zone.
5495
 * @param string $tz_abbrev The abbreviation PHP provided for this time zone.
5496
 * @return string The fixed version of $tz_abbrev.
5497
 */
5498
function fix_tz_abbrev($tzid, $tz_abbrev)
5499
{
5500
	// Is this abbreviation just a numerical offset?
5501
	if (strspn($tz_abbrev, '+-') > 0)
5502
	{
5503
		// To get on this list, a time zone must be historically stable and must not observe daylight saving time
5504
		$missing_tz_abbrs = array(
5505
			'Antarctica/Casey' => 'CAST',
5506
			'Antarctica/Davis' => 'DAVT',
5507
			'Antarctica/DumontDUrville' => 'DDUT',
5508
			'Antarctica/Mawson' => 'MAWT',
5509
			'Antarctica/Rothera' => 'ART',
5510
			'Antarctica/Syowa' => 'SYOT',
5511
			'Antarctica/Vostok' => 'VOST',
5512
			'Asia/Almaty' => 'ALMT',
5513
			'Asia/Aqtau' => 'ORAT',
5514
			'Asia/Aqtobe' => 'AQTT',
5515
			'Asia/Ashgabat' => 'TMT',
5516
			'Asia/Bishkek' => 'KGT',
5517
			'Asia/Colombo' => 'IST',
5518
			'Asia/Dushanbe' => 'TJT',
5519
			'Asia/Oral' => 'ORAT',
5520
			'Asia/Qyzylorda' => 'QYZT',
5521
			'Asia/Samarkand' => 'UZT',
5522
			'Asia/Tashkent' => 'UZT',
5523
			'Asia/Tbilisi' => 'GET',
5524
			'Asia/Yerevan' => 'AMT',
5525
			'Europe/Istanbul' => 'TRT',
5526
			'Europe/Minsk' => 'MSK',
5527
			'Indian/Kerguelen' => 'TFT',
5528
		);
5529
5530
		if (!empty($missing_tz_abbrs[$tzid]))
5531
			$tz_abbrev = $missing_tz_abbrs[$tzid];
5532
		else
5533
		{
5534
			// Russia likes to experiment with time zones often, and names them as offsets from Moscow
5535
			$tz_location = timezone_location_get(timezone_open($tzid));
5536
			if ($tz_location['country_code'] == 'RU')
5537
			{
5538
				$msk_offset = intval($tz_abbrev) - 3;
5539
				$tz_abbrev = 'MSK' . (!empty($msk_offset) ? sprintf('%+0d', $msk_offset) : '');
5540
			}
5541
		}
5542
5543
		// Still no good? We'll just mark it as a UTC offset
5544
		if (strspn($tz_abbrev, '+-') > 0)
5545
		{
5546
			$tz_abbrev = intval($tz_abbrev);
5547
			$tz_abbrev = 'UTC' . (!empty($tz_abbrev) ? sprintf('%+0d', $tz_abbrev) : '');
5548
		}
5549
	}
5550
5551
	return $tz_abbrev;
5552
}
5553
5554
/**
5555
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
5556
 * @return string|false The IP address in binary or false
5557
 */
5558
function inet_ptod($ip_address)
5559
{
5560
	if (!isValidIP($ip_address))
5561
		return $ip_address;
5562
5563
	$bin = inet_pton($ip_address);
5564
	return $bin;
5565
}
5566
5567
/**
5568
 * @param string $bin An IP address in IPv4, IPv6 (Either string (postgresql) or binary (other databases))
5569
 * @return string|false The IP address in presentation format or false on error
5570
 */
5571
function inet_dtop($bin)
5572
{
5573
	if(empty($bin))
5574
		return '';
5575
5576
	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...
5577
5578
	if ($db_type == 'postgresql')
5579
		return $bin;
5580
5581
	$ip_address = inet_ntop($bin);
5582
5583
	return $ip_address;
5584
}
5585
5586
/**
5587
 * Safe serialize() and unserialize() replacements
5588
 *
5589
 * @license Public Domain
5590
 *
5591
 * @author anthon (dot) pang (at) gmail (dot) com
5592
 */
5593
5594
/**
5595
 * Safe serialize() replacement. Recursive
5596
 * - output a strict subset of PHP's native serialized representation
5597
 * - does not serialize objects
5598
 *
5599
 * @param mixed $value
5600
 * @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...
5601
 */
5602
function _safe_serialize($value)
5603
{
5604
	if(is_null($value))
5605
		return 'N;';
5606
5607
	if(is_bool($value))
5608
		return 'b:'. (int) $value .';';
5609
5610
	if(is_int($value))
5611
		return 'i:'. $value .';';
5612
5613
	if(is_float($value))
5614
		return 'd:'. str_replace(',', '.', $value) .';';
5615
5616
	if(is_string($value))
5617
		return 's:'. strlen($value) .':"'. $value .'";';
5618
5619
	if(is_array($value))
5620
	{
5621
		$out = '';
5622
		foreach($value as $k => $v)
5623
			$out .= _safe_serialize($k) . _safe_serialize($v);
5624
5625
		return 'a:'. count($value) .':{'. $out .'}';
5626
	}
5627
5628
	// safe_serialize cannot serialize resources or objects.
5629
	return false;
5630
}
5631
/**
5632
 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issues.
5633
 *
5634
 * @param mixed $value
5635
 * @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...
5636
 */
5637 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...
5638
{
5639
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5640
	if (function_exists('mb_internal_encoding') &&
5641
		(((int) ini_get('mbstring.func_overload')) & 2))
5642
	{
5643
		$mbIntEnc = mb_internal_encoding();
5644
		mb_internal_encoding('ASCII');
5645
	}
5646
5647
	$out = _safe_serialize($value);
5648
5649
	if (isset($mbIntEnc))
5650
		mb_internal_encoding($mbIntEnc);
5651
5652
	return $out;
5653
}
5654
5655
/**
5656
 * Safe unserialize() replacement
5657
 * - accepts a strict subset of PHP's native serialized representation
5658
 * - does not unserialize objects
5659
 *
5660
 * @param string $str
5661
 * @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...
5662
 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
5663
 */
5664
function _safe_unserialize($str)
5665
{
5666
	// Input  is not a string.
5667
	if(empty($str) || !is_string($str))
5668
		return false;
5669
5670
	$stack = array();
5671
	$expected = array();
5672
5673
	/*
5674
	 * states:
5675
	 *   0 - initial state, expecting a single value or array
5676
	 *   1 - terminal state
5677
	 *   2 - in array, expecting end of array or a key
5678
	 *   3 - in array, expecting value or another array
5679
	 */
5680
	$state = 0;
5681
	while($state != 1)
5682
	{
5683
		$type = isset($str[0]) ? $str[0] : '';
5684
		if($type == '}')
5685
			$str = substr($str, 1);
5686
5687
		else if($type == 'N' && $str[1] == ';')
5688
		{
5689
			$value = null;
5690
			$str = substr($str, 2);
5691
		}
5692
		else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
5693
		{
5694
			$value = $matches[1] == '1' ? true : false;
5695
			$str = substr($str, 4);
5696
		}
5697
		else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
5698
		{
5699
			$value = (int)$matches[1];
5700
			$str = $matches[2];
5701
		}
5702
		else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
5703
		{
5704
			$value = (float)$matches[1];
5705
			$str = $matches[3];
5706
		}
5707
		else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
5708
		{
5709
			$value = substr($matches[2], 0, (int)$matches[1]);
5710
			$str = substr($matches[2], (int)$matches[1] + 2);
5711
		}
5712
		else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches))
5713
		{
5714
			$expectedLength = (int)$matches[1];
5715
			$str = $matches[2];
5716
		}
5717
5718
		// Object or unknown/malformed type.
5719
		else
5720
			return false;
5721
5722
		switch($state)
5723
		{
5724
			case 3: // In array, expecting value or another array.
5725
				if($type == 'a')
5726
				{
5727
					$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...
5728
					$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...
5729
					$list = &$list[$key];
5730
					$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...
5731
					$state = 2;
5732
					break;
5733
				}
5734
				if($type != '}')
5735
				{
5736
					$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...
5737
					$state = 2;
5738
					break;
5739
				}
5740
5741
				// Missing array value.
5742
				return false;
5743
5744
			case 2: // in array, expecting end of array or a key
5745
				if($type == '}')
5746
				{
5747
					// Array size is less than expected.
5748
					if(count($list) < end($expected))
5749
						return false;
5750
5751
					unset($list);
5752
					$list = &$stack[count($stack)-1];
5753
					array_pop($stack);
5754
5755
					// Go to terminal state if we're at the end of the root array.
5756
					array_pop($expected);
5757
5758
					if(count($expected) == 0)
5759
						$state = 1;
5760
5761
					break;
5762
				}
5763
5764
				if($type == 'i' || $type == 's')
5765
				{
5766
					// Array size exceeds expected length.
5767
					if(count($list) >= end($expected))
5768
						return false;
5769
5770
					$key = $value;
5771
					$state = 3;
5772
					break;
5773
				}
5774
5775
				// Illegal array index type.
5776
				return false;
5777
5778
			// Expecting array or value.
5779
			case 0:
5780
				if($type == 'a')
5781
				{
5782
					$data = array();
5783
					$list = &$data;
5784
					$expected[] = $expectedLength;
5785
					$state = 2;
5786
					break;
5787
				}
5788
5789
				if($type != '}')
5790
				{
5791
					$data = $value;
5792
					$state = 1;
5793
					break;
5794
				}
5795
5796
				// Not in array.
5797
				return false;
5798
		}
5799
	}
5800
5801
	// Trailing data in input.
5802
	if(!empty($str))
5803
		return false;
5804
5805
	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...
5806
}
5807
5808
/**
5809
 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
5810
 *
5811
 * @param string $str
5812
 * @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...
5813
 */
5814 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...
5815
{
5816
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5817
	if (function_exists('mb_internal_encoding') &&
5818
		(((int) ini_get('mbstring.func_overload')) & 0x02))
5819
	{
5820
		$mbIntEnc = mb_internal_encoding();
5821
		mb_internal_encoding('ASCII');
5822
	}
5823
5824
	$out = _safe_unserialize($str);
5825
5826
	if (isset($mbIntEnc))
5827
		mb_internal_encoding($mbIntEnc);
5828
5829
	return $out;
5830
}
5831
5832
/**
5833
 * Tries different modes to make file/dirs writable. Wrapper function for chmod()
5834
5835
 * @param string $file The file/dir full path.
5836
 * @param int $value Not needed, added for legacy reasons.
5837
 * @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.
5838
 */
5839
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...
5840
{
5841
	// No file? no checks!
5842
	if (empty($file))
5843
		return false;
5844
5845
	// Already writable?
5846
	if (is_writable($file))
5847
		return true;
5848
5849
	// Do we have a file or a dir?
5850
	$isDir = is_dir($file);
5851
	$isWritable = false;
5852
5853
	// Set different modes.
5854
	$chmodValues = $isDir ? array(0750, 0755, 0775, 0777) : array(0644, 0664, 0666);
5855
5856
	foreach($chmodValues as $val)
5857
	{
5858
		// If it's writable, break out of the loop.
5859
		if (is_writable($file))
5860
		{
5861
			$isWritable = true;
5862
			break;
5863
		}
5864
5865
		else
5866
			@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 1343
  1. Read from $_GET, and $packagesdir . '/' . $_GET['package'] is passed to smf_chmod()
    in Sources/Packages.php on line 1343
  3. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2445
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2445
  2. $context is assigned
    in Sources/Packages.php on line 2456
  3. $path is assigned
    in Sources/Packages.php on line 2510
  4. $path is passed to smf_chmod()
    in Sources/Packages.php on line 2523
  4. Path: Read from $_POST, and $status is assigned in Sources/Packages.php on line 2466
  1. Read from $_POST, and $status is assigned
    in Sources/Packages.php on line 2466
  2. $context is assigned
    in Sources/Packages.php on line 2487
  3. $context is assigned
    in Sources/Packages.php on line 2489
  4. $path is assigned
    in Sources/Packages.php on line 2510
  5. $path is passed to smf_chmod()
    in Sources/Packages.php on line 2523
  5. Path: Read from $_GET, and $context is assigned in Sources/ManageLanguages.php on line 789
  1. Read from $_GET, and $context is assigned
    in Sources/ManageLanguages.php on line 789
  2. $context is assigned
    in Sources/ManageLanguages.php on line 794
  3. $images_dirs is assigned
    in Sources/ManageLanguages.php on line 836
  4. $curPath is assigned
    in Sources/ManageLanguages.php on line 909
  5. $curPath is passed to deltree()
    in Sources/ManageLanguages.php on line 911
  6. $dir . '/' . $entryname is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1794
  6. Path: Read from $_GET, and $packagesdir . '/' . $_GET['package'] is passed to deltree() in Sources/Packages.php on line 1340
  1. Read from $_GET, and $packagesdir . '/' . $_GET['package'] is passed to deltree()
    in Sources/Packages.php on line 1340
  2. $dir . '/' . $entryname is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1794
  7. Path: Read from $_REQUEST, and $_REQUEST['file'] is passed to read_tgz_file() in Sources/Packages.php on line 1286
  1. Read from $_REQUEST, and $_REQUEST['file'] is passed to read_tgz_file()
    in Sources/Packages.php on line 1286
  2. $destination is passed to read_tgz_data()
    in Sources/Subs-Package.php on line 35
  3. $destination is passed to mktree()
    in Sources/Subs-Package.php on line 90
  4. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  8. Path: Read from $_REQUEST, and $_REQUEST['file'] is passed to read_tgz_file() in Sources/Packages.php on line 1310
  1. Read from $_REQUEST, and $_REQUEST['file'] is passed to read_tgz_file()
    in Sources/Packages.php on line 1310
  2. $destination is passed to read_tgz_data()
    in Sources/Subs-Package.php on line 35
  3. $destination is passed to mktree()
    in Sources/Subs-Package.php on line 90
  4. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  9. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2545
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2545
  2. $context is assigned
    in Sources/Packages.php on line 2547
  3. $context is assigned
    in Sources/Packages.php on line 2548
  4. $context is assigned
    in Sources/Packages.php on line 2550
  5. $context is assigned
    in Sources/Packages.php on line 2595
  6. $path is assigned
    in Sources/Packages.php on line 2626
  7. $path . '/' . $entry is passed to package_chmod()
    in Sources/Packages.php on line 2639
  8. $chmod_file is assigned
    in Sources/Subs-Package.php on line 2849
  9. $chmod_file is passed to smf_chmod()
    in Sources/Subs-Package.php on line 2874
  10. Path: Read from $_POST, and $file is assigned in Sources/ManageLanguages.php on line 220
  1. Read from $_POST, and $file is assigned
    in Sources/ManageLanguages.php on line 220
  2. $chmod_files is assigned
    in Sources/ManageLanguages.php on line 226
  3. $chmod_files is passed to create_chmod_control()
    in Sources/ManageLanguages.php on line 231
  4. $file is assigned
    in Sources/Subs-Package.php on line 848
  5. $file is passed to package_chmod()
    in Sources/Subs-Package.php on line 859
  6. $chmod_file is assigned
    in Sources/Subs-Package.php on line 2849
  7. $chmod_file is passed to smf_chmod()
    in Sources/Subs-Package.php on line 2874
  11. Path: Read from $_GET, and $context is assigned in Sources/ManageLanguages.php on line 206
  1. Read from $_GET, and $context is assigned
    in Sources/ManageLanguages.php on line 206
  2. $context is assigned
    in Sources/ManageLanguages.php on line 207
  3. $context is assigned
    in Sources/ManageLanguages.php on line 208
  4. $context is assigned
    in Sources/ManageLanguages.php on line 258
  5. $context is assigned
    in Sources/ManageLanguages.php on line 262
  6. $context['make_writable'] is passed to create_chmod_control()
    in Sources/ManageLanguages.php on line 379
  7. $file is assigned
    in Sources/Subs-Package.php on line 848
  8. $file is passed to package_chmod()
    in Sources/Subs-Package.php on line 859
  9. $chmod_file is assigned
    in Sources/Subs-Package.php on line 2849
  10. $chmod_file is passed to smf_chmod()
    in Sources/Subs-Package.php on line 2874
  12. Path: Read from $_POST, and $_POST['basedirectory_for_attachments'] is passed to automanage_attachments_create_directory() in Sources/ManageAttachments.php on line 222
  1. Read from $_POST, and $_POST['basedirectory_for_attachments'] is passed to automanage_attachments_create_directory()
    in Sources/ManageAttachments.php on line 222
  2. Data is passed through preg_split()
    in vendor/Sources/Subs-Attachments.php on line 260
  3. $tree is assigned
    in Sources/Subs-Attachments.php on line 138
  4. $tree is passed through array_shift(), and $directory is assigned
    in Sources/Subs-Attachments.php on line 146
  5. $directory is passed to smf_chmod()
    in Sources/Subs-Attachments.php on line 164
  13. Path: Read from $_POST, and $path is assigned in Sources/ManageAttachments.php on line 1923
  1. Read from $_POST, and $path is assigned
    in Sources/ManageAttachments.php on line 1923
  2. $path is passed to automanage_attachments_create_directory()
    in Sources/ManageAttachments.php on line 1956
  3. Data is passed through preg_split()
    in vendor/Sources/Subs-Attachments.php on line 260
  4. $tree is assigned
    in Sources/Subs-Attachments.php on line 138
  5. $tree is passed through array_shift(), and $directory is assigned
    in Sources/Subs-Attachments.php on line 146
  6. $directory is passed to smf_chmod()
    in Sources/Subs-Attachments.php on line 164
  14. Path: Read from $_POST, and $_POST['new_base_dir'] is passed to automanage_attachments_create_directory() in Sources/ManageAttachments.php on line 2215
  1. Read from $_POST, and $_POST['new_base_dir'] is passed to automanage_attachments_create_directory()
    in Sources/ManageAttachments.php on line 2215
  2. Data is passed through preg_split()
    in vendor/Sources/Subs-Attachments.php on line 260
  3. $tree is assigned
    in Sources/Subs-Attachments.php on line 138
  4. $tree is passed through array_shift(), and $directory is assigned
    in Sources/Subs-Attachments.php on line 146
  5. $directory is passed to smf_chmod()
    in Sources/Subs-Attachments.php on line 164
  15. Path: Read from $_GET, and $url is assigned in Sources/PackageGet.php on line 230
  1. Read from $_GET, and $url is assigned
    in Sources/PackageGet.php on line 230
  2. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  3. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  4. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  5. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  6. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  7. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  8. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  9. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  10. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  11. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  12. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  16. Path: Read from $_GET, and $url is assigned in Sources/PackageGet.php on line 239
  1. Read from $_GET, and $url is assigned
    in Sources/PackageGet.php on line 239
  2. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  3. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  4. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  5. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  6. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  7. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  8. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  9. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  10. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  11. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  12. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  17. Path: Read from $_GET, and $current_url is assigned in Sources/PackageGet.php on line 358
  1. Read from $_GET, and $current_url is assigned
    in Sources/PackageGet.php on line 358
  2. $package is assigned
    in Sources/PackageGet.php on line 366
  3. $package is assigned
    in Sources/PackageGet.php on line 376
  4. $package is assigned
    in Sources/PackageGet.php on line 377
  5. $package is assigned
    in Sources/PackageGet.php on line 466
  6. $package is assigned
    in Sources/PackageGet.php on line 467
  7. $package is assigned
    in Sources/PackageGet.php on line 468
  8. $package is assigned
    in Sources/PackageGet.php on line 469
  9. $package is assigned
    in Sources/PackageGet.php on line 470
  10. $package is assigned
    in Sources/PackageGet.php on line 473
  11. $context is assigned
    in Sources/PackageGet.php on line 476
  12. $packageSection is assigned
    in Sources/PackageGet.php on line 488
  13. $package is assigned
    in Sources/PackageGet.php on line 490
  14. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  15. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  16. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  17. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  18. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  19. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  20. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  21. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  22. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  23. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  24. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  18. Path: Read from $_GET, and $current_url is assigned in Sources/PackageGet.php on line 360
  1. Read from $_GET, and $current_url is assigned
    in Sources/PackageGet.php on line 360
  2. $package is assigned
    in Sources/PackageGet.php on line 366
  3. $package is assigned
    in Sources/PackageGet.php on line 376
  4. $package is assigned
    in Sources/PackageGet.php on line 377
  5. $package is assigned
    in Sources/PackageGet.php on line 466
  6. $package is assigned
    in Sources/PackageGet.php on line 467
  7. $package is assigned
    in Sources/PackageGet.php on line 468
  8. $package is assigned
    in Sources/PackageGet.php on line 469
  9. $package is assigned
    in Sources/PackageGet.php on line 470
  10. $package is assigned
    in Sources/PackageGet.php on line 473
  11. $context is assigned
    in Sources/PackageGet.php on line 476
  12. $packageSection is assigned
    in Sources/PackageGet.php on line 488
  13. $package is assigned
    in Sources/PackageGet.php on line 490
  14. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  15. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  16. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  17. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  18. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  19. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  20. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  21. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  22. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  23. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  24. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  19. Path: Read from $_GET, and $current_url is assigned in Sources/PackageGet.php on line 383
  1. Read from $_GET, and $current_url is assigned
    in Sources/PackageGet.php on line 383
  2. $package is assigned
    in Sources/PackageGet.php on line 421
  3. $package is assigned
    in Sources/PackageGet.php on line 422
  4. $package is assigned
    in Sources/PackageGet.php on line 461
  5. $package is assigned
    in Sources/PackageGet.php on line 462
  6. $package is assigned
    in Sources/PackageGet.php on line 466
  7. $package is assigned
    in Sources/PackageGet.php on line 467
  8. $package is assigned
    in Sources/PackageGet.php on line 468
  9. $package is assigned
    in Sources/PackageGet.php on line 469
  10. $package is assigned
    in Sources/PackageGet.php on line 470
  11. $package is assigned
    in Sources/PackageGet.php on line 473
  12. $context is assigned
    in Sources/PackageGet.php on line 476
  13. $packageSection is assigned
    in Sources/PackageGet.php on line 488
  14. $package is assigned
    in Sources/PackageGet.php on line 490
  15. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  16. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  17. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  18. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  19. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  20. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  21. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  22. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  23. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  24. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  25. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  20. Path: Read from $_GET, and $current_url is assigned in Sources/PackageGet.php on line 385
  1. Read from $_GET, and $current_url is assigned
    in Sources/PackageGet.php on line 385
  2. $package is assigned
    in Sources/PackageGet.php on line 421
  3. $package is assigned
    in Sources/PackageGet.php on line 422
  4. $package is assigned
    in Sources/PackageGet.php on line 461
  5. $package is assigned
    in Sources/PackageGet.php on line 462
  6. $package is assigned
    in Sources/PackageGet.php on line 466
  7. $package is assigned
    in Sources/PackageGet.php on line 467
  8. $package is assigned
    in Sources/PackageGet.php on line 468
  9. $package is assigned
    in Sources/PackageGet.php on line 469
  10. $package is assigned
    in Sources/PackageGet.php on line 470
  11. $package is assigned
    in Sources/PackageGet.php on line 473
  12. $context is assigned
    in Sources/PackageGet.php on line 476
  13. $packageSection is assigned
    in Sources/PackageGet.php on line 488
  14. $package is assigned
    in Sources/PackageGet.php on line 490
  15. $url . '/' . $package['filename'] is passed to getPackageInfo()
    in Sources/PackageGet.php on line 497
  16. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  17. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  18. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  19. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  20. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  21. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  22. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  23. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  24. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  25. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  21. Path: Read from $_REQUEST, and $_REQUEST['package'] is passed through preg_replace(), and $context is assigned in Sources/Packages.php on line 104
  1. Read from $_REQUEST, and $_REQUEST['package'] is passed through preg_replace(), and $context is assigned
    in Sources/Packages.php on line 104
  2. $context is assigned
    in Sources/Packages.php on line 107
  3. $context is assigned
    in Sources/Packages.php on line 132
  4. $context is assigned
    in Sources/Packages.php on line 135
  5. $context is assigned
    in Sources/Packages.php on line 141
  6. $context['filename'] is passed to getPackageInfo()
    in Sources/Packages.php on line 193
  7. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  8. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  9. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  10. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  11. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  12. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  13. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  14. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  15. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  16. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840
  22. Path: Read from $_REQUEST, and $context is assigned in Sources/Packages.php on line 780
  1. Read from $_REQUEST, and $context is assigned
    in Sources/Packages.php on line 780
  2. $context is assigned
    in Sources/Packages.php on line 783
  3. $context is assigned
    in Sources/Packages.php on line 789
  4. $context is assigned
    in Sources/Packages.php on line 792
  5. $context is assigned
    in Sources/Packages.php on line 798
  6. $context is assigned
    in Sources/Packages.php on line 866
  7. $context['filename'] is passed to getPackageInfo()
    in Sources/Packages.php on line 886
  8. $packagesdir . '/' . $gzfilename . '/package-info.xml' is passed through file_get_contents(), and $packageInfo is assigned
    in Sources/Subs-Package.php on line 548
  9. $packageInfo is passed to xmlArray::__construct()
    in Sources/Subs-Package.php on line 566
  10. xmlArray::$array is assigned
    in Sources/Class-Package.php on line 63
  11. Tainted property xmlArray::$array is read, and $array is assigned
    in Sources/Class-Package.php on line 144
  12. xmlArray::path() returns tainted data, and $array is assigned
    in Sources/Class-Package.php on line 102
  13. xmlArray::fetch() returns tainted data
    in Sources/Subs-Package.php on line 1332
  14. Data is passed through strtr()
    in vendor/Sources/Subs-Package.php on line 1741
  15. $this_action is assigned
    in Sources/Subs-Package.php on line 1332
  16. $this_action['destination'] is passed to mktree()
    in Sources/Subs-Package.php on line 1340
  17. $strPath is passed to smf_chmod()
    in Sources/Subs-Package.php on line 1840

General Strategies to prevent injection

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

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

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

$sanitized = (integer) $tainted;
Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
5867
	}
5868
5869
	return $isWritable;
5870
}
5871
5872
/**
5873
 * Wrapper function for json_decode() with error handling.
5874
5875
 * @param string $json The string to decode.
5876
 * @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.
5877
 * @param bool $logIt To specify if the error will be logged if theres any.
5878
 * @return array Either an empty array or the decoded data as an array.
5879
 */
5880
function smf_json_decode($json, $returnAsArray = false, $logIt = true)
5881
{
5882
	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...
5883
5884
	// Come on...
5885
	if (empty($json) || !is_string($json))
5886
		return array();
5887
5888
	$returnArray = @json_decode($json, $returnAsArray);
5889
5890
	// PHP 5.3 so no json_last_error_msg()
5891
	switch(json_last_error())
5892
	{
5893
		case JSON_ERROR_NONE:
5894
			$jsonError = false;
5895
			break;
5896
		case JSON_ERROR_DEPTH:
5897
			$jsonError =  'JSON_ERROR_DEPTH';
5898
			break;
5899
		case JSON_ERROR_STATE_MISMATCH:
5900
			$jsonError = 'JSON_ERROR_STATE_MISMATCH';
5901
			break;
5902
		case JSON_ERROR_CTRL_CHAR:
5903
			$jsonError = 'JSON_ERROR_CTRL_CHAR';
5904
			break;
5905
		case JSON_ERROR_SYNTAX:
5906
			$jsonError = 'JSON_ERROR_SYNTAX';
5907
			break;
5908
		case JSON_ERROR_UTF8:
5909
			$jsonError = 'JSON_ERROR_UTF8';
5910
			break;
5911
		default:
5912
			$jsonError = 'unknown';
5913
			break;
5914
	}
5915
5916
	// Something went wrong!
5917
	if (!empty($jsonError) && $logIt)
5918
	{
5919
		// Being a wrapper means we lost our smf_error_handler() privileges :(
5920
		$jsonDebug = debug_backtrace();
5921
		$jsonDebug = $jsonDebug[0];
5922
		loadLanguage('Errors');
5923
5924
		if (!empty($jsonDebug))
5925
			log_error($txt['json_'. $jsonError], 'critical', $jsonDebug['file'], $jsonDebug['line']);
5926
5927
		else
5928
			log_error($txt['json_'. $jsonError], 'critical');
5929
5930
		// Everyone expects an array.
5931
		return array();
5932
	}
5933
5934
	return $returnArray;
5935
}
5936
5937
/**
5938
 * Check the given String if he is a valid IPv4 or IPv6
5939
 * return true or false
5940
 *
5941
 * @param string $IPString
5942
 *
5943
 * @return bool
5944
 */
5945
function isValidIP($IPString)
5946
{
5947
	return filter_var($IPString, FILTER_VALIDATE_IP) !== false;
5948
}
5949
5950
/**
5951
 * Outputs a response.
5952
 * It assumes the data is already a string.
5953
 * @param string $data The data to print
5954
 * @param string $type The content type. Defaults to Json.
5955
 * @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...
5956
 */
5957
function smf_serverResponse($data = '', $type = 'content-type: application/json')
5958
{
5959
	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...
5960
5961
	// Defensive programming anyone?
5962
	if (empty($data))
5963
		return false;
5964
5965
	// Don't need extra stuff...
5966
	$db_show_debug = false;
5967
5968
	// Kill anything else.
5969
	ob_end_clean();
5970
5971
	if (!empty($modSettings['CompressedOutput']))
5972
		@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...
5973
5974
	else
5975
		ob_start();
5976
5977
	// Set the header.
5978
	header($type);
5979
5980
	// Echo!
5981
	echo $data;
5982
5983
	// Done.
5984
	obExit(false);
5985
}
5986
5987
/**
5988
 * Creates an optimized regex to match all known top level domains.
5989
 *
5990
 * The optimized regex is stored in $modSettings['tld_regex'].
5991
 *
5992
 * To update the stored version of the regex to use the latest list of valid TLDs from iana.org, set
5993
 * the $update parameter to true. Updating can take some time, based on network connectivity, so it
5994
 * should normally only be done by calling this function from a background or scheduled task.
5995
 *
5996
 * If $update is not true, but the regex is missing or invalid, the regex will be regenerated from a
5997
 * hard-coded list of TLDs. This regenerated regex will be overwritten on the next scheduled update.
5998
 *
5999
 * @param bool $update If true, fetch and process the latest offical list of TLDs from iana.org.
6000
 */
6001
function set_tld_regex($update = false)
6002
{
6003
	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...
6004
	static $done = false;
6005
6006
	// If we don't need to do anything, don't
6007
	if (!$update && $done)
6008
		return;
6009
6010
	// Should we get a new copy of the official list of TLDs?
6011
	if ($update)
6012
	{
6013
		$tlds = fetch_web_data('https://data.iana.org/TLD/tlds-alpha-by-domain.txt');
6014
6015
		// If the Internet Assigned Numbers Authority can't be reached, the Internet is gone.
6016
		// We're probably running on a server hidden in a bunker deep underground to protect it from
6017
		// marauding bandits roaming on the surface. We don't want to waste precious electricity on
6018
		// pointlessly repeating background tasks, so we'll wait until the next regularly scheduled
6019
		// update to see if civilization has been restored.
6020
		if ($tlds === false)
6021
			$postapocalypticNightmare = true;
6022
	}
6023
	// If we aren't updating and the regex is valid, we're done
6024
	elseif (!empty($modSettings['tld_regex']) && @preg_match('~' . $modSettings['tld_regex'] . '~', null) !== false)
6025
	{
6026
		$done = true;
6027
		return;
6028
	}
6029
6030
	// If we successfully got an update, process the list into an array
6031
	if (!empty($tlds))
6032
	{
6033
		// Clean $tlds and convert it to an array
6034
		$tlds = array_filter(explode("\n", strtolower($tlds)), function($line) {
6035
			$line = trim($line);
6036
			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...
6037
				return false;
6038
			else
6039
				return true;
6040
		});
6041
6042
		// Convert Punycode to Unicode
6043
		require_once($sourcedir . '/punycode/Punycode.php');
6044
		$Punycode = new TrueBV\Punycode();
6045
		$tlds = array_map(function ($input) use ($Punycode) { return $Punycode->decode($input); }, $tlds);
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
6046
	}
6047
	// Otherwise, use the 2012 list of gTLDs and ccTLDs for now and schedule a background update
6048
	else
6049
	{
6050
		$tlds = array('com', 'net', 'org', 'edu', 'gov', 'mil', 'aero', 'asia', 'biz', 'cat',
6051
			'coop', 'info', 'int', 'jobs', 'mobi', 'museum', 'name', 'post', 'pro', 'tel',
6052
			'travel', 'xxx', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq',
6053
			'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
6054
			'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc',
6055
			'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cs', 'cu', 'cv',
6056
			'cx', 'cy', 'cz', 'dd', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh',
6057
			'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge',
6058
			'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw',
6059
			'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq',
6060
			'ir', 'is', 'it', 'ja', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn',
6061
			'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu',
6062
			'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp',
6063
			'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf',
6064
			'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph',
6065
			'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru',
6066
			'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn',
6067
			'so', 'sr', 'ss', 'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th',
6068
			'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug',
6069
			'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye',
6070
			'yt', 'yu', 'za', 'zm', 'zw');
6071
6072
		// Schedule a background update, unless civilization has collapsed and/or we are having connectivity issues.
6073
		$schedule_update = empty($postapocalypticNightmare);
6074
	}
6075
6076
	// Get an optimized regex to match all the TLDs
6077
	$tld_regex = build_regex($tlds);
6078
6079
	// Remember the new regex in $modSettings
6080
	updateSettings(array('tld_regex' => $tld_regex));
6081
6082
	// Schedule a background update if we need one
6083 View Code Duplication
	if (!empty($schedule_update))
6084
	{
6085
		$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
6086
			array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
6087
			array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array()
6088
		);
6089
	}
6090
6091
	// Redundant repetition is redundant
6092
	$done = true;
6093
}
6094
6095
/**
6096
 * Creates optimized regular expressions from an array of strings.
6097
 *
6098
 * An optimized regex built using this function will be much faster than a simple regex built using
6099
 * `implode('|', $strings)` --- anywhere from several times to several orders of magnitude faster.
6100
 *
6101
 * However, the time required to build the optimized regex is approximately equal to the time it
6102
 * takes to execute the simple regex. Therefore, it is only worth calling this function if the
6103
 * resulting regex will be used more than once.
6104
 *
6105
 * Because PHP places an upper limit on the allowed length of a regex, very large arrays of $strings
6106
 * may not fit in a single regex. Normally, the excess strings will simply be dropped. However, if
6107
 * the $returnArray parameter is set to true, this function will build as many regexes as necessary
6108
 * to accomodate everything in $strings and return them in an array. You will need to iterate
6109
 * through all elements of the returned array in order to test all possible matches.
6110
 *
6111
 * @param array $strings An array of strings to make a regex for.
6112
 * @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...
6113
 * @param bool $returnArray If true, returns an array of regexes.
6114
 * @return string|array One or more regular expressions to match any of the input strings.
6115
 */
6116
function build_regex($strings, $delim = null, $returnArray = false)
6117
{
6118
	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...
6119
6120
	// The mb_* functions are faster than the $smcFunc ones, but may not be available
6121
	if (function_exists('mb_internal_encoding') && function_exists('mb_detect_encoding') && function_exists('mb_strlen') && function_exists('mb_substr'))
6122
	{
6123
		if (($string_encoding = mb_detect_encoding(implode(' ', $strings))) !== false)
6124
		{
6125
			$current_encoding = mb_internal_encoding();
6126
			mb_internal_encoding($string_encoding);
6127
		}
6128
6129
		$strlen = 'mb_strlen';
6130
		$substr = 'mb_substr';
6131
	}
6132
	else
6133
	{
6134
		$strlen = $smcFunc['strlen'];
6135
		$substr = $smcFunc['substr'];
6136
	}
6137
6138
	// This recursive function creates the index array from the strings
6139
	$add_string_to_index = function ($string, $index) use (&$strlen, &$substr, &$add_string_to_index)
6140
	{
6141
		static $depth = 0;
6142
		$depth++;
6143
6144
		$first = $substr($string, 0, 1);
6145
6146
		if (empty($index[$first]))
6147
			$index[$first] = array();
6148
6149
		if ($strlen($string) > 1)
6150
		{
6151
			// Sanity check on recursion
6152
			if ($depth > 99)
6153
				$index[$first][$substr($string, 1)] = '';
6154
6155
			else
6156
				$index[$first] = $add_string_to_index($substr($string, 1), $index[$first]);
6157
		}
6158
		else
6159
			$index[$first][''] = '';
6160
6161
		$depth--;
6162
		return $index;
6163
	};
6164
6165
	// This recursive function turns the index array into a regular expression
6166
	$index_to_regex = function (&$index, $delim) use (&$strlen, &$index_to_regex)
6167
	{
6168
		static $depth = 0;
6169
		$depth++;
6170
6171
		// Absolute max length for a regex is 32768, but we might need wiggle room
6172
		$max_length = 30000;
6173
6174
		$regex = array();
6175
		$length = 0;
6176
6177
		foreach ($index as $key => $value)
6178
		{
6179
			$key_regex = preg_quote($key, $delim);
6180
			$new_key = $key;
6181
6182
			if (empty($value))
6183
				$sub_regex = '';
6184
			else
6185
			{
6186
				$sub_regex = $index_to_regex($value, $delim);
6187
6188
				if (count(array_keys($value)) == 1)
6189
				{
6190
					$new_key_array = explode('(?'.'>', $sub_regex);
6191
					$new_key .= $new_key_array[0];
6192
				}
6193
				else
6194
					$sub_regex = '(?'.'>' . $sub_regex . ')';
6195
			}
6196
6197
			if ($depth > 1)
6198
				$regex[$new_key] = $key_regex . $sub_regex;
6199
			else
6200
			{
6201
				if (($length += strlen($key_regex) + 1) < $max_length || empty($regex))
6202
				{
6203
					$regex[$new_key] = $key_regex . $sub_regex;
6204
					unset($index[$key]);
6205
				}
6206
				else
6207
					break;
6208
			}
6209
		}
6210
6211
		// Sort by key length and then alphabetically
6212
		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...
6213
			$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...
6214
			$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...
6215
6216
			if ($l1 == $l2)
6217
				return strcmp($k1, $k2) > 0 ? 1 : -1;
6218
			else
6219
				return $l1 > $l2 ? -1 : 1;
6220
		});
6221
6222
		$depth--;
6223
		return implode('|', $regex);
6224
	};
6225
6226
	// Now that the functions are defined, let's do this thing
6227
	$index = array();
6228
	$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...
6229
6230
	foreach ($strings as $string)
6231
		$index = $add_string_to_index($string, $index);
6232
6233
	if ($returnArray === true)
6234
	{
6235
		$regex = array();
6236
		while (!empty($index))
6237
			$regex[] = '(?'.'>' . $index_to_regex($index, $delim) . ')';
6238
	}
6239
	else
6240
		$regex = '(?'.'>' . $index_to_regex($index, $delim) . ')';
6241
6242
	// Restore PHP's internal character encoding to whatever it was originally
6243
	if (!empty($current_encoding))
6244
		mb_internal_encoding($current_encoding);
6245
6246
	return $regex;
6247
}
6248
6249
/**
6250
 * Check if the passed url has an SSL certificate.
6251
 *
6252
 * Returns true if a cert was found & false if not.
6253
 * @param string $url to check, in $boardurl format (no trailing slash).
6254
 */
6255
 function ssl_cert_found($url) {
6256
6257
	// First, strip the subfolder from the passed url, if any
6258
	$parsedurl = parse_url($url);
6259
	$url = 'ssl://' . $parsedurl['host'] . ':443';
6260
6261
	// Next, check the ssl stream context for certificate info
6262
	$result = false;
6263
	$context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, "verify_peer" => true, "allow_self_signed" => true)));
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal ssl does not require double quotes, as per coding-style, please use single quotes.

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

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

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

<?php

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

print $doubleQuoted;

will print an indented: Single is Value

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

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

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

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

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

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

<?php

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

print $doubleQuoted;

will print an indented: Single is Value

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

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

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

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

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

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

<?php

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

print $doubleQuoted;

will print an indented: Single is Value

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

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

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

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

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

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

<?php

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

print $doubleQuoted;

will print an indented: Single is Value

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

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

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

2 paths for user data to reach this point

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

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...
6265
	if ($stream !== false)
6266
	{
6267
		$params = stream_context_get_params($stream);
6268
		$result = isset($params["options"]["ssl"]["peer_certificate"]) ? true : false;
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal options does not require double quotes, as per coding-style, please use single quotes.

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

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

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

<?php

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

print $doubleQuoted;

will print an indented: Single is Value

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

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

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

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

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

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

<?php

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

print $doubleQuoted;

will print an indented: Single is Value

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

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

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

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

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

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

<?php

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

print $doubleQuoted;

will print an indented: Single is Value

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

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

Loading history...
6269
	}
6270
    return $result;
6271
}
6272
6273
/**
6274
 * Check if the passed url has a redirect to https:// by querying headers.
6275
 *
6276
 * Returns true if a redirect was found & false if not.
6277
 * Note that when force_ssl = 2, SMF issues its own redirect...  So if this
6278
 * returns true, it may be caused by SMF, not necessarily an .htaccess redirect.
6279
 * @param string $url to check, in $boardurl format (no trailing slash).
6280
 */
6281
function https_redirect_active($url) {
6282
6283
	// Ask for the headers for the passed url, but via http...
6284
	// Need to add the trailing slash, or it puts it there & thinks there's a redirect when there isn't...
6285
	$url = str_ireplace('https://', 'http://', $url) . '/';
6286
	$headers = @get_headers($url);
0 ignored issues
show
Security Header Injection introduced by
$url can contain request data and is used in request header context(s) leading to a potential security vulnerability.

2 paths for user data to reach this point

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

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...
6287
	if ($headers === false)
6288
		return false;
6289
6290
	// Now to see if it came back https...
6291
	// First check for a redirect status code in first row (301, 302, 307)
6292
	if (strstr($headers[0], '301') === false && strstr($headers[0], '302') === false && strstr($headers[0], '307') === false)
6293
		return false;
6294
6295
	// Search for the location entry to confirm https
6296
	$result = false;
6297
	foreach ($headers as $header) {
6298
		if (stristr($header, 'Location: https://') !== false) {
6299
			$result = true;
6300
			break;
6301
		}
6302
	}
6303
	return $result;
6304
}
6305
6306
/**
6307
 * Build query_wanna_see_board and query_see_board for a userid
6308
 *
6309
 * Returns array with keys query_wanna_see_board and query_see_board
6310
 * @param int $userid of the user
6311
 */
6312
function build_query_board($userid)
6313
{
6314
	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...
6315
6316
	$query_part = array();
6317
	$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...
6318
	$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...
6319
	$deny_boards_access = !empty($modSettings['deny_boards_access']) ? $modSettings['deny_boards_access'] : null;
6320
	$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...
6321
	$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...
6322
6323
	if ($user_info['id'] == $userid)
6324
	{
6325
		$groups = $user_info['groups'];
6326
		$is_admin = $user_info['is_admin'];
6327
		$mod_cache = !empty($user_info['mod_cache']) ? $user_info['mod_cache'] : null;
6328
		$ignoreboards = !empty($user_info['ignoreboards']) ? $user_info['ignoreboards'] : null;
6329
	}
6330
	else
6331
	{
6332
		$request = $smcFunc['db_query']('', '
6333
				SELECT mem.ignore_boards, mem.id_group, mem.additional_groups, mem.id_post_group
6334
				FROM {db_prefix}members AS mem
6335
				WHERE mem.id_member = {int:id_member}
6336
				LIMIT 1',
6337
				array(
6338
					'id_member' => $userid,
6339
				)
6340
			);
6341
6342
		$row = $smcFunc['db_fetch_assoc']($request);
6343
6344 View Code Duplication
		if (empty($row['additional_groups']))
6345
			$groups = array($row['id_group'], $row['id_post_group']);
6346
		else
6347
			$groups = array_merge(
6348
					array($row['id_group'], $row['id_post_group']),
6349
					explode(',', $row['additional_groups'])
6350
			);
6351
6352
		// Because history has proven that it is possible for groups to go bad - clean up in case.
6353
		foreach ($groups as $k => $v)
6354
			$groups[$k] = (int) $v;
6355
6356
		$is_admin = in_array(1, $groups);
6357
6358
		$ignoreboards = !empty($row['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $row['ignore_boards']) : array();
6359
6360
		// What boards are they the moderator of?
6361
		$boards_mod = array();
6362
6363
		$request = $smcFunc['db_query']('', '
6364
			SELECT id_board
6365
			FROM {db_prefix}moderators
6366
			WHERE id_member = {int:current_member}',
6367
			array(
6368
				'current_member' => $userid,
6369
			)
6370
		);
6371
		while ($row = $smcFunc['db_fetch_assoc']($request))
6372
			$boards_mod[] = $row['id_board'];
6373
		$smcFunc['db_free_result']($request);
6374
6375
		// Can any of the groups they're in moderate any of the boards?
6376
		$request = $smcFunc['db_query']('', '
6377
			SELECT id_board
6378
			FROM {db_prefix}moderator_groups
6379
			WHERE id_group IN({array_int:groups})',
6380
			array(
6381
				'groups' => $groups,
6382
			)
6383
		);
6384
		while ($row = $smcFunc['db_fetch_assoc']($request))
6385
			$boards_mod[] = $row['id_board'];
6386
		$smcFunc['db_free_result']($request);
6387
6388
		// Just in case we've got duplicates here...
6389
		$boards_mod = array_unique($boards_mod);
6390
6391
		$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...
6392
	}
6393
6394
	// Just build this here, it makes it easier to change/use - administrators can see all boards.
6395
	if ($is_admin)
6396
		$query_part['query_see_board'] = '1=1';
6397
	// Otherwise just the groups in $user_info['groups'].
6398
	else
6399
		$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'] : '') . ')';
6400
6401
	// Build the list of boards they WANT to see.
6402
	// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also
6403
6404
	// If they aren't ignoring any boards then they want to see all the boards they can see
6405
	if (empty($ignoreboards))
6406
		$query_part['query_wanna_see_board'] = $query_part['query_see_board'];
6407
	// Ok I guess they don't want to see all the boards
6408
	else
6409
		$query_part['query_wanna_see_board'] = '(' . $query_part['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $ignoreboards) . '))';
6410
6411
	return $query_part;
6412
}
6413
6414
/**
6415
 * Check if the connection is using https.
6416
 *
6417
 * @return boolean true if connection used https
6418
 */
6419
function httpsOn()
6420
{
6421
	$secure = false;
6422
6423 View Code Duplication
	if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
6424
		$secure = true;
6425
	elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on')
6426
		$secure = true;
6427
6428
	return $secure;
6429
}
6430
6431
/**
6432
 * A wrapper for `filter_var($url, FILTER_VALIDATE_URL)` that can handle URLs
6433
 * with international characters (a.k.a. IRIs)
6434
 *
6435
 * @param string $iri The IRI to test.
6436
 * @param int $flags Optional flags to pass to filter_var()
0 ignored issues
show
Documentation introduced by
Should the type for parameter $flags 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...
6437
 * @return string|bool Either the original IRI, or false if the IRI was invalid.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string|false.

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...
6438
 */
6439
function validate_iri($iri, $flags = null)
6440
{
6441
	$url = iri_to_url($iri);
6442
6443
	if (filter_var($url, FILTER_VALIDATE_URL, $flags) !== false)
6444
		return $iri;
6445
	else
6446
		return false;
6447
}
6448
6449
/**
6450
 * A wrapper for `filter_var($url, FILTER_SANITIZE_URL)` that can handle URLs
6451
 * with international characters (a.k.a. IRIs)
6452
 *
6453
 * Note: The returned value will still be an IRI, not a URL. To convert to URL,
6454
 * feed the result of this function to iri_to_url()
6455
 *
6456
 * @param string $iri The IRI to sanitize.
6457
 * @return string|bool The sanitized version of the IRI
6458
 */
6459
function sanitize_iri($iri)
6460
{
6461
	// Encode any non-ASCII characters (but not space or control characters of any sort)
6462
	$iri = preg_replace_callback('~[^\x00-\x7F\pZ\pC]~u', function ($matches) {
6463
		return rawurlencode($matches[0]);
6464
	}, $iri);
6465
6466
	// Perform normal sanitization
6467
	$iri = filter_var($iri, FILTER_SANITIZE_URL);
6468
6469
	// Decode the non-ASCII characters
6470
	$iri = rawurldecode($iri);
6471
6472
	return $iri;
6473
}
6474
6475
/**
6476
 * Converts a URL with international characters (an IRI) into a pure ASCII URL
6477
 *
6478
 * Uses Punycode to encode any non-ASCII characters in the domain name, and uses
6479
 * standard URL encoding on the rest.
6480
 *
6481
 * @param string $iri A IRI that may or may not contain non-ASCII characters.
6482
 * @return string|bool The URL version of the IRI.
6483
 */
6484
function iri_to_url($iri)
6485
{
6486
	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...
6487
6488
	$host = parse_url((strpos($iri, '://') === false ? 'http://' : '') . ltrim($iri, ':/'), PHP_URL_HOST);
6489
6490
	if (empty($host))
6491
		return $iri;
6492
6493
	// Convert the domain using the Punycode algorithm
6494
	require_once($sourcedir . '/punycode/Punycode.php');
6495
	$Punycode = new TrueBV\Punycode();
6496
	$encoded_host = $Punycode->encode($host);
6497
	$pos = strpos($iri, $host);
6498
	$iri = substr_replace($iri, $encoded_host, $pos, strlen($host));
6499
6500
	// Encode any disallowed characters in the rest of the URL
6501
	$unescaped = array(
6502
		'%21'=>'!', '%23'=>'#', '%24'=>'$', '%26'=>'&',
6503
		'%27'=>"'", '%28'=>'(', '%29'=>')', '%2A'=>'*',
6504
		'%2B'=>'+', '%2C'=>',',	'%2F'=>'/', '%3A'=>':',
6505
		'%3B'=>';', '%3D'=>'=', '%3F'=>'?', '%40'=>'@',
6506
	);
6507
	$iri = strtr(rawurlencode($iri), $unescaped);
6508
6509
	return $iri;
6510
}
6511
6512
/**
6513
 * Decodes a URL containing encoded international characters to UTF-8
6514
 *
6515
 * Decodes any Punycode encoded characters in the domain name, then uses
6516
 * standard URL decoding on the rest.
6517
 *
6518
 * @param string $url The pure ASCII version of a URL.
6519
 * @return string|bool The UTF-8 version of the URL.
6520
 */
6521
function url_to_iri($url)
6522
{
6523
	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...
6524
6525
	$host = parse_url((strpos($url, '://') === false ? 'http://' : '') . ltrim($url, ':/'), PHP_URL_HOST);
6526
6527
	if (empty($host))
6528
		return $url;
6529
6530
	// Decode the domain from Punycode
6531
	require_once($sourcedir . '/punycode/Punycode.php');
6532
	$Punycode = new TrueBV\Punycode();
6533
	$decoded_host = $Punycode->decode($host);
6534
	$pos = strpos($url, $host);
6535
	$url = substr_replace($url, $decoded_host, $pos, strlen($host));
6536
6537
	// Decode the rest of the URL
6538
	$url = rawurldecode($url);
6539
6540
	return $url;
6541
}
6542
6543
?>