Completed
Push — release-2.1 ( 335073...11228b )
by Colin
12:28
created

Subs.php ➔ ssl_cert_found()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to, well, everything.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Update some basic statistics.
21
 *
22
 * 'member' statistic updates the latest member, the total member
23
 *  count, and the number of unapproved members.
24
 * 'member' also only counts approved members when approval is on, but
25
 *  is much more efficient with it off.
26
 *
27
 * 'message' changes the total number of messages, and the
28
 *  highest message id by id_msg - which can be parameters 1 and 2,
29
 *  respectively.
30
 *
31
 * 'topic' updates the total number of topics, or if parameter1 is true
32
 *  simply increments them.
33
 *
34
 * 'subject' updates the log_search_subjects in the event of a topic being
35
 *  moved, removed or split.  parameter1 is the topicid, parameter2 is the new subject
36
 *
37
 * 'postgroups' case updates those members who match condition's
38
 *  post-based membergroups in the database (restricted by parameter1).
39
 *
40
 * @param string $type Stat type - can be 'member', 'message', 'topic', 'subject' or 'postgroups'
41
 * @param mixed $parameter1 A parameter for updating the stats
42
 * @param mixed $parameter2 A 2nd parameter for updating the stats
43
 */
44
function updateStats($type, $parameter1 = null, $parameter2 = null)
45
{
46
	global $modSettings, $smcFunc;
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;
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 ($type == 'int' && ($val === '+' || $val === '-'))
383
		{
384
			$val = $var . ' ' . $val . ' 1';
385
			$type = 'raw';
386
		}
387
388
		// Ensure posts, instant_messages, and unread_messages don't overflow or underflow.
389
		if (in_array($var, array('posts', 'instant_messages', 'unread_messages')))
390
		{
391
			if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match))
392
			{
393
				if ($match[1] != '+ ')
394
					$val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END';
395
				$type = 'raw';
396
			}
397
		}
398
399
		$setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},';
400
		$parameters['p_' . $var] = $val;
401
	}
402
403
	$smcFunc['db_query']('', '
404
		UPDATE {db_prefix}members
405
		SET' . substr($setString, 0, -1) . '
406
		WHERE ' . $condition,
407
		$parameters
408
	);
409
410
	updateStats('postgroups', $members, array_keys($data));
411
412
	// Clear any caching?
413
	if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members))
414
	{
415
		if (!is_array($members))
416
			$members = array($members);
417
418
		foreach ($members as $member)
419
		{
420
			if ($modSettings['cache_enable'] >= 3)
421
			{
422
				cache_put_data('member_data-profile-' . $member, null, 120);
423
				cache_put_data('member_data-normal-' . $member, null, 120);
424
				cache_put_data('member_data-minimal-' . $member, null, 120);
425
			}
426
			cache_put_data('user_settings-' . $member, null, 60);
427
		}
428
	}
429
}
430
431
/**
432
 * Updates the settings table as well as $modSettings... only does one at a time if $update is true.
433
 *
434
 * - updates both the settings table and $modSettings array.
435
 * - all of changeArray's indexes and values are assumed to have escaped apostrophes (')!
436
 * - if a variable is already set to what you want to change it to, that
437
 *   variable will be skipped over; it would be unnecessary to reset.
438
 * - When use_update is true, UPDATEs will be used instead of REPLACE.
439
 * - when use_update is true, the value can be true or false to increment
440
 *  or decrement it, respectively.
441
 *
442
 * @param array $changeArray An array of info about what we're changing in 'setting' => 'value' format
443
 * @param bool $update Whether to use an UPDATE query instead of a REPLACE query
444
 */
445
function updateSettings($changeArray, $update = false)
446
{
447
	global $modSettings, $smcFunc;
448
449
	if (empty($changeArray) || !is_array($changeArray))
450
		return;
451
452
	$toRemove = array();
453
454
	// Go check if there is any setting to be removed.
455
	foreach ($changeArray as $k => $v)
456
		if ($v === null)
457
		{
458
			// Found some, remove them from the original array and add them to ours.
459
			unset($changeArray[$k]);
460
			$toRemove[] = $k;
461
		}
462
463
	// Proceed with the deletion.
464
	if (!empty($toRemove))
465
		$smcFunc['db_query']('', '
466
			DELETE FROM {db_prefix}settings
467
			WHERE variable IN ({array_string:remove})',
468
			array(
469
				'remove' => $toRemove,
470
			)
471
		);
472
473
	// In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs.
474
	if ($update)
475
	{
476
		foreach ($changeArray as $variable => $value)
477
		{
478
			$smcFunc['db_query']('', '
479
				UPDATE {db_prefix}settings
480
				SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value}
481
				WHERE variable = {string:variable}',
482
				array(
483
					'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value),
484
					'variable' => $variable,
485
				)
486
			);
487
			$modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value);
488
		}
489
490
		// Clean out the cache and make sure the cobwebs are gone too.
491
		cache_put_data('modSettings', null, 90);
492
493
		return;
494
	}
495
496
	$replaceArray = array();
497
	foreach ($changeArray as $variable => $value)
498
	{
499
		// Don't bother if it's already like that ;).
500
		if (isset($modSettings[$variable]) && $modSettings[$variable] == $value)
501
			continue;
502
		// If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it.
503
		elseif (!isset($modSettings[$variable]) && empty($value))
504
			continue;
505
506
		$replaceArray[] = array($variable, $value);
507
508
		$modSettings[$variable] = $value;
509
	}
510
511
	if (empty($replaceArray))
512
		return;
513
514
	$smcFunc['db_insert']('replace',
515
		'{db_prefix}settings',
516
		array('variable' => 'string-255', 'value' => 'string-65534'),
517
		$replaceArray,
518
		array('variable')
519
	);
520
521
	// Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
522
	cache_put_data('modSettings', null, 90);
523
}
524
525
/**
526
 * Constructs a page list.
527
 *
528
 * - builds the page list, e.g. 1 ... 6 7 [8] 9 10 ... 15.
529
 * - flexible_start causes it to use "url.page" instead of "url;start=page".
530
 * - very importantly, cleans up the start value passed, and forces it to
531
 *   be a multiple of num_per_page.
532
 * - checks that start is not more than max_value.
533
 * - base_url should be the URL without any start parameter on it.
534
 * - uses the compactTopicPagesEnable and compactTopicPagesContiguous
535
 *   settings to decide how to display the menu.
536
 *
537
 * an example is available near the function definition.
538
 * $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true);
539
 *
540
 * @param string $base_url The basic URL to be used for each link.
541
 * @param int &$start The start position, by reference. If this is not a multiple of the number of items per page, it is sanitized to be so and the value will persist upon the function's return.
542
 * @param int $max_value The total number of items you are paginating for.
543
 * @param int $num_per_page The number of items to be displayed on a given page. $start will be forced to be a multiple of this value.
544
 * @param bool $flexible_start Whether a ;start=x component should be introduced into the URL automatically (see above)
545
 * @param bool $show_prevnext Whether the Previous and Next links should be shown (should be on only when navigating the list)
546
 *
547
 * @return string The complete HTML of the page index that was requested, formatted by the template.
548
 */
549
function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false, $show_prevnext = true)
550
{
551
	global $modSettings, $context, $smcFunc, $settings, $txt;
552
553
	// Save whether $start was less than 0 or not.
554
	$start = (int) $start;
555
	$start_invalid = $start < 0;
556
557
	// Make sure $start is a proper variable - not less than 0.
558
	if ($start_invalid)
559
		$start = 0;
560
	// Not greater than the upper bound.
561
	elseif ($start >= $max_value)
562
		$start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page)));
563
	// And it has to be a multiple of $num_per_page!
564
	else
565
		$start = max(0, (int) $start - ((int) $start % (int) $num_per_page));
566
567
	$context['current_page'] = $start / $num_per_page;
568
569
	// Define some default page index settings if we don't already have it...
570
	if (!isset($settings['page_index']))
571
	{
572
		// This defines the formatting for the page indexes used throughout the forum.
573
		$settings['page_index'] = array(
574
			'extra_before' => '<span class="pages">' . $txt['pages'] . '</span>',
575
			'previous_page' => '<span class="generic_icons previous_page"></span>',
576
			'current_page' => '<span class="current_page">%1$d</span> ',
577
			'page' => '<a class="navPages" href="{URL}">%2$s</a> ',
578
			'expand_pages' => '<span class="expand_pages" onclick="expandPages(this, {LINK}, {FIRST_PAGE}, {LAST_PAGE}, {PER_PAGE});"> ... </span>',
579
			'next_page' => '<span class="generic_icons next_page"></span>',
580
			'extra_after' => '',
581
		);
582
	}
583
584
	$base_link = strtr($settings['page_index']['page'], array('{URL}' => $flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d'));
585
	$pageindex = $settings['page_index']['extra_before'];
586
587
	// Compact pages is off or on?
588
	if (empty($modSettings['compactTopicPagesEnable']))
589
	{
590
		// Show the left arrow.
591
		$pageindex .= $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']);
592
593
		// Show all the pages.
594
		$display_page = 1;
595
		for ($counter = 0; $counter < $max_value; $counter += $num_per_page)
596
			$pageindex .= $start == $counter && !$start_invalid ? sprintf($settings['page_index']['current_page'], $display_page++) : sprintf($base_link, $counter, $display_page++);
597
598
		// Show the right arrow.
599
		$display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page);
600
		if ($start != $counter - $max_value && !$start_invalid)
601
			$pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, $settings['page_index']['next_page']);
602
	}
603
	else
604
	{
605
		// If they didn't enter an odd value, pretend they did.
606
		$PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2;
607
608
		// Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page)
609
		if (!empty($start) && $show_prevnext)
610
			$pageindex .= sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']);
611
		else
612
			$pageindex .= '';
613
614
		// Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15)
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...
615
		if ($start > $num_per_page * $PageContiguous)
616
			$pageindex .= sprintf($base_link, 0, '1');
617
618
		// Show the ... after the first page.  (prev page 1 >...< 6 7 [8] 9 10 ... 15 next page)
619
		if ($start > $num_per_page * ($PageContiguous + 1))
620
			$pageindex .= strtr($settings['page_index']['expand_pages'], array(
621
				'{LINK}' => JavaScriptEscape($smcFunc['htmlspecialchars']($base_link)),
622
				'{FIRST_PAGE}' => $num_per_page,
623
				'{LAST_PAGE}' => $start - $num_per_page * $PageContiguous,
624
				'{PER_PAGE}' => $num_per_page,
625
			));
626
627
		// Show the pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
628
		for ($nCont = $PageContiguous; $nCont >= 1; $nCont--)
629 View Code Duplication
			if ($start >= $num_per_page * $nCont)
630
			{
631
				$tmpStart = $start - $num_per_page * $nCont;
632
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
633
			}
634
635
		// Show the current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
636
		if (!$start_invalid)
637
			$pageindex .= sprintf($settings['page_index']['current_page'], $start / $num_per_page + 1);
638
		else
639
			$pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1);
640
641
		// Show the pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
642
		$tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page;
643
		for ($nCont = 1; $nCont <= $PageContiguous; $nCont++)
644 View Code Duplication
			if ($start + $num_per_page * $nCont <= $tmpMaxPages)
645
			{
646
				$tmpStart = $start + $num_per_page * $nCont;
647
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
648
			}
649
650
		// Show the '...' part near the end. (prev page 1 ... 6 7 [8] 9 10 >...< 15 next page)
651
		if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages)
652
			$pageindex .= strtr($settings['page_index']['expand_pages'], array(
653
				'{LINK}' => JavaScriptEscape($smcFunc['htmlspecialchars']($base_link)),
654
				'{FIRST_PAGE}' => $start + $num_per_page * ($PageContiguous + 1),
655
				'{LAST_PAGE}' => $tmpMaxPages,
656
				'{PER_PAGE}' => $num_per_page,
657
			));
658
659
		// Show the last number in the list. (prev page 1 ... 6 7 [8] 9 10 ... >15<  next page)
660
		if ($start + $num_per_page * $PageContiguous < $tmpMaxPages)
661
			$pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1);
662
663
		// Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
664
		if ($start != $tmpMaxPages && $show_prevnext)
665
			$pageindex .= sprintf($base_link, $start + $num_per_page, $settings['page_index']['next_page']);
666
	}
667
	$pageindex .= $settings['page_index']['extra_after'];
668
669
	return $pageindex;
670
}
671
672
/**
673
 * - Formats a number.
674
 * - uses the format of number_format to decide how to format the number.
675
 *   for example, it might display "1 234,50".
676
 * - caches the formatting data from the setting for optimization.
677
 *
678
 * @param float $number A number
679
 * @param bool|int $override_decimal_count If set, will use the specified number of decimal places. Otherwise it's automatically determined
680
 * @return string A formatted number
681
 */
682
function comma_format($number, $override_decimal_count = false)
683
{
684
	global $txt;
685
	static $thousands_separator = null, $decimal_separator = null, $decimal_count = null;
686
687
	// Cache these values...
688
	if ($decimal_separator === null)
689
	{
690
		// Not set for whatever reason?
691
		if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1)
692
			return $number;
693
694
		// Cache these each load...
695
		$thousands_separator = $matches[1];
696
		$decimal_separator = $matches[2];
697
		$decimal_count = strlen($matches[3]);
698
	}
699
700
	// Format the string with our friend, number_format.
701
	return number_format($number, (float) $number === $number ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator);
702
}
703
704
/**
705
 * Format a time to make it look purdy.
706
 *
707
 * - returns a pretty formatted version of time based on the user's format in $user_info['time_format'].
708
 * - applies all necessary time offsets to the timestamp, unless offset_type is set.
709
 * - if todayMod is set and show_today was not not specified or true, an
710
 *   alternate format string is used to show the date with something to show it is "today" or "yesterday".
711
 * - performs localization (more than just strftime would do alone.)
712
 *
713
 * @param int $log_time A timestamp
714
 * @param bool $show_today Whether to show "Today"/"Yesterday" or just a date
715
 * @param bool|string $offset_type If false, uses both user time offset and forum offset. If 'forum', uses only the forum offset. Otherwise no offset is applied.
716
 * @param bool $process_safe Activate setlocale check for changes at runtime. Slower, but safer.
717
 * @return string A formatted timestamp
718
 */
719
function timeformat($log_time, $show_today = true, $offset_type = false, $process_safe = false)
720
{
721
	global $context, $user_info, $txt, $modSettings;
722
	static $non_twelve_hour, $locale_cache;
723
	static $unsupportedFormats, $finalizedFormats;
724
725
	// Offset the time.
726
	if (!$offset_type)
727
		$time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600;
728
	// Just the forum offset?
729
	elseif ($offset_type == 'forum')
730
		$time = $log_time + $modSettings['time_offset'] * 3600;
731
	else
732
		$time = $log_time;
733
734
	// We can't have a negative date (on Windows, at least.)
735
	if ($log_time < 0)
736
		$log_time = 0;
737
738
	// Today and Yesterday?
739
	if ($modSettings['todayMod'] >= 1 && $show_today === true)
740
	{
741
		// Get the current time.
742
		$nowtime = forum_time();
743
744
		$then = @getdate($time);
745
		$now = @getdate($nowtime);
746
747
		// Try to make something of a time format string...
748
		$s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S';
749
		if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false)
750
		{
751
			$h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l';
752
			$today_fmt = $h . ':%M' . $s . ' %p';
753
		}
754
		else
755
			$today_fmt = '%H:%M' . $s;
756
757
		// Same day of the year, same year.... Today!
758
		if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
759
			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...
760
761
		// 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...
762
		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))
763
			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...
764
	}
765
766
	$str = !is_bool($show_today) ? $show_today : $user_info['time_format'];
767
768
	// Use the cached formats if available
769
	if (is_null($finalizedFormats))
770
		$finalizedFormats = (array) cache_get_data('timeformatstrings', 86400);
771
772
	// Make a supported version for this format if we don't already have one
773
	if (empty($finalizedFormats[$str]))
774
	{
775
		$timeformat = $str;
776
777
		// Not all systems support all formats, and Windows fails altogether if unsupported ones are
778
		// used, so let's prevent that. Some substitutions go to the nearest reasonable fallback, some
779
		// turn into static strings, some (i.e. %a, %A, $b, %B, %p) have special handling below.
780
		$strftimeFormatSubstitutions = array(
781
			// Day
782
			'a' => '%a', 'A' => '%A', 'e' => '%d', 'd' => '&#37;d', 'j' => '&#37;j', 'u' => '%w', 'w' => '&#37;w',
783
			// Week
784
			'U' => '&#37;U', 'V' => '%U', 'W' => '%U',
785
			// Month
786
			'b' => '%b', 'B' => '%B', 'h' => '%b', 'm' => '%b',
787
			// Year
788
			'C' => '&#37;C', 'g' => '%y', 'G' => '%Y', 'y' => '&#37;y', 'Y' => '&#37;Y',
789
			// Time
790
			'H' => '&#37;H', 'k' => '%H', 'I' => '%H', 'l' => '%I', 'M' => '&#37;M', 'p' => '%p', 'P' => '%p',
791
			'r' => '%I:%M:%S %p', 'R' => '%H:%M', 'S' => '&#37;S', 'T' => '%H:%M:%S', 'X' => '%T', 'z' => '&#37;z', 'Z' => '&#37;Z',
792
			// Time and Date Stamps
793
			'c' => '%F %T', 'D' => '%m/%d/%y', 'F' => '%Y-%m-%d', 's' => '&#37;s', 'x' => '%F',
794
			// Miscellaneous
795
			'n' => "\n", 't' => "\t", '%' => '&#37;',
796
		);
797
798
		// No need to do this part again if we already did it once
799
		if (is_null($unsupportedFormats))
800
			$unsupportedFormats = (array) cache_get_data('unsupportedtimeformats', 86400);
801
		if (empty($unsupportedFormats))
802
		{
803
			foreach($strftimeFormatSubstitutions as $format => $substitution)
804
			{
805
				$value = @strftime('%' . $format);
806
807
				// Windows will return false for unsupported formats
808
				// Other operating systems return the format string as a literal
809
				if ($value === false || $value === $format)
810
					$unsupportedFormats[] = $format;
811
			}
812
			cache_put_data('unsupportedtimeformats', $unsupportedFormats, 86400);
813
		}
814
815
		// Windows needs extra help if $timeformat contains something completely invalid, e.g. '%Q'
816
		if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN')
817
			$timeformat = preg_replace('~%(?!' . implode('|', array_keys($strftimeFormatSubstitutions)) . ')~', '&#37;', $timeformat);
818
819
		// Substitute unsupported formats with supported ones
820
		if (!empty($unsupportedFormats))
821
			while (preg_match('~%(' . implode('|', $unsupportedFormats) . ')~', $timeformat, $matches))
822
				$timeformat = str_replace($matches[0], $strftimeFormatSubstitutions[$matches[1]], $timeformat);
823
824
		// Remember this so we don't need to do it again
825
		$finalizedFormats[$str] = $timeformat;
826
		cache_put_data('timeformatstrings', $finalizedFormats, 86400);
827
	}
828
829
	$str = $finalizedFormats[$str];
830
831
	if (!isset($locale_cache))
832
		$locale_cache = setlocale(LC_TIME, $txt['lang_locale']);
833
834
	if ($locale_cache !== false)
835
	{
836
		// Check if another process changed the locale
837
		if ($process_safe === true && setlocale(LC_TIME, '0') != $locale_cache)
838
			setlocale(LC_TIME, $txt['lang_locale']);
839
840
		if (!isset($non_twelve_hour))
841
			$non_twelve_hour = trim(strftime('%p')) === '';
842 View Code Duplication
		if ($non_twelve_hour && strpos($str, '%p') !== false)
843
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
844
845
		foreach (array('%a', '%A', '%b', '%B') as $token)
846
			if (strpos($str, $token) !== false)
847
				$str = str_replace($token, strftime($token, $time), $str);
848
	}
849
	else
850
	{
851
		// Do-it-yourself time localization.  Fun.
852
		foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label)
853
			if (strpos($str, $token) !== false)
854
				$str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str);
855
856 View Code Duplication
		if (strpos($str, '%p') !== false)
857
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
858
	}
859
860
	// Format the time and then restore any literal percent characters
861
	return str_replace('&#37;', '%', strftime($str, $time));
862
}
863
864
/**
865
 * Removes special entities from strings.  Compatibility...
866
 * Should be used instead of html_entity_decode for PHP version compatibility reasons.
867
 *
868
 * - removes the base entities (&lt;, &quot;, etc.) from text.
869
 * - additionally converts &nbsp; and &#039;.
870
 *
871
 * @param string $string A string
872
 * @return string The string without entities
873
 */
874
function un_htmlspecialchars($string)
875
{
876
	global $context;
877
	static $translation = array();
878
879
	// Determine the character set... Default to UTF-8
880
	if (empty($context['character_set']))
881
		$charset = 'UTF-8';
882
	// Use ISO-8859-1 in place of non-supported ISO-8859 charsets...
883
	elseif (strpos($context['character_set'], 'ISO-8859-') !== false && !in_array($context['character_set'], array('ISO-8859-5', 'ISO-8859-15')))
884
		$charset = 'ISO-8859-1';
885
	else
886
		$charset = $context['character_set'];
887
888
	if (empty($translation))
889
		$translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES, $charset)) + array('&#039;' => '\'', '&#39;' => '\'', '&nbsp;' => ' ');
890
891
	return strtr($string, $translation);
892
}
893
894
/**
895
 * Shorten a subject + internationalization concerns.
896
 *
897
 * - shortens a subject so that it is either shorter than length, or that length plus an ellipsis.
898
 * - respects internationalization characters and entities as one character.
899
 * - avoids trailing entities.
900
 * - returns the shortened string.
901
 *
902
 * @param string $subject The subject
903
 * @param int $len How many characters to limit it to
904
 * @return string The shortened subject - either the entire subject (if it's <= $len) or the subject shortened to $len characters with "..." appended
905
 */
906
function shorten_subject($subject, $len)
907
{
908
	global $smcFunc;
909
910
	// It was already short enough!
911
	if ($smcFunc['strlen']($subject) <= $len)
912
		return $subject;
913
914
	// Shorten it by the length it was too long, and strip off junk from the end.
915
	return $smcFunc['substr']($subject, 0, $len) . '...';
916
}
917
918
/**
919
 * Gets the current time with offset.
920
 *
921
 * - always applies the offset in the time_offset setting.
922
 *
923
 * @param bool $use_user_offset Whether to apply the user's offset as well
924
 * @param int $timestamp A timestamp (null to use current time)
925
 * @return int Seconds since the unix epoch, with forum time offset and (optionally) user time offset applied
926
 */
927
function forum_time($use_user_offset = true, $timestamp = null)
928
{
929
	global $user_info, $modSettings;
930
931
	if ($timestamp === null)
932
		$timestamp = time();
933
	elseif ($timestamp == 0)
934
		return 0;
935
936
	return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600;
937
}
938
939
/**
940
 * Calculates all the possible permutations (orders) of array.
941
 * should not be called on huge arrays (bigger than like 10 elements.)
942
 * returns an array containing each permutation.
943
 *
944
 * @deprecated since 2.1
945
 * @param array $array An array
946
 * @return array An array containing each permutation
947
 */
948
function permute($array)
949
{
950
	$orders = array($array);
951
952
	$n = count($array);
953
	$p = range(0, $n);
954
	for ($i = 1; $i < $n; null)
955
	{
956
		$p[$i]--;
957
		$j = $i % 2 != 0 ? $p[$i] : 0;
958
959
		$temp = $array[$i];
960
		$array[$i] = $array[$j];
961
		$array[$j] = $temp;
962
963
		for ($i = 1; $p[$i] == 0; $i++)
964
			$p[$i] = 1;
965
966
		$orders[] = $array;
967
	}
968
969
	return $orders;
970
}
971
972
/**
973
 * Parse bulletin board code in a string, as well as smileys optionally.
974
 *
975
 * - only parses bbc tags which are not disabled in disabledBBC.
976
 * - handles basic HTML, if enablePostHTML is on.
977
 * - caches the from/to replace regular expressions so as not to reload them every time a string is parsed.
978
 * - only parses smileys if smileys is true.
979
 * - does nothing if the enableBBC setting is off.
980
 * - uses the cache_id as a unique identifier to facilitate any caching it may do.
981
 *  -returns the modified message.
982
 *
983
 * @param string $message The message
984
 * @param bool $smileys Whether to parse smileys as well
985
 * @param string $cache_id The cache ID
986
 * @param array $parse_tags If set, only parses these tags rather than all of them
987
 * @return string The parsed message
988
 */
989
function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array())
990
{
991
	global $smcFunc, $txt, $scripturl, $context, $modSettings, $user_info, $sourcedir;
992
	static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
993
	static $disabled;
994
995
	// Don't waste cycles
996
	if ($message === '')
997
		return '';
998
999
	// Just in case it wasn't determined yet whether UTF-8 is enabled.
1000
	if (!isset($context['utf8']))
1001
		$context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
1002
1003
	// Clean up any cut/paste issues we may have
1004
	$message = sanitizeMSCutPaste($message);
1005
1006
	// If the load average is too high, don't parse the BBC.
1007
	if (!empty($context['load_average']) && !empty($modSettings['bbc']) && $context['load_average'] >= $modSettings['bbc'])
1008
	{
1009
		$context['disabled_parse_bbc'] = true;
1010
		return $message;
1011
	}
1012
1013
	if ($smileys !== null && ($smileys == '1' || $smileys == '0'))
1014
		$smileys = (bool) $smileys;
1015
1016
	if (empty($modSettings['enableBBC']) && $message !== false)
1017
	{
1018
		if ($smileys === true)
1019
			parsesmileys($message);
1020
1021
		return $message;
1022
	}
1023
1024
	// If we are not doing every tag then we don't cache this run.
1025
	if (!empty($parse_tags) && !empty($bbc_codes))
1026
	{
1027
		$temp_bbc = $bbc_codes;
1028
		$bbc_codes = array();
1029
	}
1030
1031
	// Ensure $modSettings['tld_regex'] contains a valid regex for the autolinker
1032
	if (!empty($modSettings['autoLinkUrls']))
1033
		set_tld_regex();
1034
1035
	// Allow mods access before entering the main parse_bbc loop
1036
	call_integration_hook('integrate_pre_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
1037
1038
	// Sift out the bbc for a performance improvement.
1039
	if (empty($bbc_codes) || $message === false || !empty($parse_tags))
1040
	{
1041
		if (!empty($modSettings['disabledBBC']))
1042
		{
1043
			$disabled = array();
1044
1045
			$temp = explode(',', strtolower($modSettings['disabledBBC']));
1046
1047
			foreach ($temp as $tag)
1048
				$disabled[trim($tag)] = true;
1049
		}
1050
1051
		if (empty($modSettings['enableEmbeddedFlash']))
1052
			$disabled['flash'] = true;
1053
1054
		/* The following bbc are formatted as an array, with keys as follows:
1055
1056
			tag: the tag's name - should be lowercase!
1057
1058
			type: one of...
1059
				- (missing): [tag]parsed content[/tag]
1060
				- unparsed_equals: [tag=xyz]parsed content[/tag]
1061
				- parsed_equals: [tag=parsed data]parsed content[/tag]
1062
				- unparsed_content: [tag]unparsed content[/tag]
1063
				- closed: [tag], [tag/], [tag /]
1064
				- unparsed_commas: [tag=1,2,3]parsed content[/tag]
1065
				- unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
1066
				- unparsed_equals_content: [tag=...]unparsed content[/tag]
1067
1068
			parameters: an optional array of parameters, for the form
1069
			  [tag abc=123]content[/tag].  The array is an associative array
1070
			  where the keys are the parameter names, and the values are an
1071
			  array which may contain the following:
1072
				- match: a regular expression to validate and match the value.
1073
				- quoted: true if the value should be quoted.
1074
				- validate: callback to evaluate on the data, which is $data.
1075
				- value: a string in which to replace $1 with the data.
1076
				  either it or validate may be used, not both.
1077
				- optional: true if the parameter is optional.
1078
1079
			test: a regular expression to test immediately after the tag's
1080
			  '=', ' ' or ']'.  Typically, should have a \] at the end.
1081
			  Optional.
1082
1083
			content: only available for unparsed_content, closed,
1084
			  unparsed_commas_content, and unparsed_equals_content.
1085
			  $1 is replaced with the content of the tag.  Parameters
1086
			  are replaced in the form {param}.  For unparsed_commas_content,
1087
			  $2, $3, ..., $n are replaced.
1088
1089
			before: only when content is not used, to go before any
1090
			  content.  For unparsed_equals, $1 is replaced with the value.
1091
			  For unparsed_commas, $1, $2, ..., $n are replaced.
1092
1093
			after: similar to before in every way, except that it is used
1094
			  when the tag is closed.
1095
1096
			disabled_content: used in place of content when the tag is
1097
			  disabled.  For closed, default is '', otherwise it is '$1' if
1098
			  block_level is false, '<div>$1</div>' elsewise.
1099
1100
			disabled_before: used in place of before when disabled.  Defaults
1101
			  to '<div>' if block_level, '' if not.
1102
1103
			disabled_after: used in place of after when disabled.  Defaults
1104
			  to '</div>' if block_level, '' if not.
1105
1106
			block_level: set to true the tag is a "block level" tag, similar
1107
			  to HTML.  Block level tags cannot be nested inside tags that are
1108
			  not block level, and will not be implicitly closed as easily.
1109
			  One break following a block level tag may also be removed.
1110
1111
			trim: if set, and 'inside' whitespace after the begin tag will be
1112
			  removed.  If set to 'outside', whitespace after the end tag will
1113
			  meet the same fate.
1114
1115
			validate: except when type is missing or 'closed', a callback to
1116
			  validate the data as $data.  Depending on the tag's type, $data
1117
			  may be a string or an array of strings (corresponding to the
1118
			  replacement.)
1119
1120
			quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
1121
			  may be not set, 'optional', or 'required' corresponding to if
1122
			  the content may be quoted.  This allows the parser to read
1123
			  [tag="abc]def[esdf]"] properly.
1124
1125
			require_parents: an array of tag names, or not set.  If set, the
1126
			  enclosing tag *must* be one of the listed tags, or parsing won't
1127
			  occur.
1128
1129
			require_children: similar to require_parents, if set children
1130
			  won't be parsed if they are not in the list.
1131
1132
			disallow_children: similar to, but very different from,
1133
			  require_children, if it is set the listed tags will not be
1134
			  parsed inside the tag.
1135
1136
			parsed_tags_allowed: an array restricting what BBC can be in the
1137
			  parsed_equals parameter, if desired.
1138
		*/
1139
1140
		$codes = array(
1141
			array(
1142
				'tag' => 'abbr',
1143
				'type' => 'unparsed_equals',
1144
				'before' => '<abbr title="$1">',
1145
				'after' => '</abbr>',
1146
				'quoted' => 'optional',
1147
				'disabled_after' => ' ($1)',
1148
			),
1149
			array(
1150
				'tag' => 'anchor',
1151
				'type' => 'unparsed_equals',
1152
				'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]',
1153
				'before' => '<span id="post_$1">',
1154
				'after' => '</span>',
1155
			),
1156
			array(
1157
				'tag' => 'attach',
1158
				'type' => 'unparsed_content',
1159
				'parameters' => array(
1160
					'name' => array('optional' => true),
1161
					'type' => array('optional' => true),
1162
					'alt' => array('optional' => true),
1163
					'title' => array('optional' => true),
1164
					'width' => array('optional' => true, 'match' => '(\d+)'),
1165
					'height' => array('optional' => true, 'match' => '(\d+)'),
1166
				),
1167
				'content' => '$1',
1168
				'validate' => function (&$tag, &$data, $disabled, $params) use ($modSettings, $context, $sourcedir, $txt)
1169
				{
1170
					$returnContext = '';
1171
1172
					// BBC or the entire attachments feature is disabled
1173
					if (empty($modSettings['attachmentEnable']) || !empty($disabled['attach']))
1174
						return $data;
1175
1176
					// Save the attach ID.
1177
					$attachID = $data;
1178
1179
					// Kinda need this.
1180
					require_once($sourcedir . '/Subs-Attachments.php');
1181
1182
					$currentAttachment = parseAttachBBC($attachID);
1183
1184
					// parseAttachBBC will return a string ($txt key) rather than diying with a fatal_error. Up to you to decide what to do.
1185
					if (is_string($currentAttachment))
1186
						return $data = !empty($txt[$currentAttachment]) ? $txt[$currentAttachment] : $currentAttachment;
1187
1188
					if (!empty($currentAttachment['is_image']))
1189
					{
1190
						$alt = ' alt="' . (!empty($params['{alt}']) ? $params['{alt}'] : $currentAttachment['name']) . '"';
1191
						$title = !empty($params['{title}']) ? ' title="' . $params['{title}'] . '"' : '';
1192
1193
						$width = !empty($params['{width}']) ? ' width="' . $params['{width}'] . '"' : '';
1194
						$height = !empty($params['{height}']) ? ' height="' . $params['{height}'] . '"' : '';
1195
1196
						if (empty($width) && empty($height))
1197
						{
1198
							$width = ' width="' . $currentAttachment['width'] . '"';
1199
							$height = ' height="' . $currentAttachment['height'] . '"';
1200
						}
1201
1202
						if ($currentAttachment['thumbnail']['has_thumb'] && empty($params['{width}']) && empty($params['{height}']))
1203
							$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>';
1204
						else
1205
							$returnContext .= '<img src="' . $currentAttachment['href'] . ';image"' . $alt . $title . $width . $height . ' class="bbc_img"/>';
1206
					}
1207
1208
					// No image. Show a link.
1209
					else
1210
						$returnContext .= $currentAttachment['link'];
1211
1212
					// Gotta append what we just did.
1213
					$data = $returnContext;
1214
				},
1215
			),
1216
			array(
1217
				'tag' => 'b',
1218
				'before' => '<b>',
1219
				'after' => '</b>',
1220
			),
1221
			array(
1222
				'tag' => 'center',
1223
				'before' => '<div class="centertext">',
1224
				'after' => '</div>',
1225
				'block_level' => true,
1226
			),
1227
			array(
1228
				'tag' => 'code',
1229
				'type' => 'unparsed_content',
1230
				'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>',
1231
				// @todo Maybe this can be simplified?
1232
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1233
				{
1234
					if (!isset($disabled['code']))
1235
					{
1236
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
1237
1238 View Code Duplication
						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
1239
						{
1240
							// Do PHP code coloring?
1241
							if ($php_parts[$php_i] != '&lt;?php')
1242
								continue;
1243
1244
							$php_string = '';
1245
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1246
							{
1247
								$php_string .= $php_parts[$php_i];
1248
								$php_parts[$php_i++] = '';
1249
							}
1250
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1251
						}
1252
1253
						// Fix the PHP code stuff...
1254
						$data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1255
						$data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);
1256
1257
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1258
						if ($context['browser']['is_opera'])
1259
							$data .= '&nbsp;';
1260
					}
1261
				},
1262
				'block_level' => true,
1263
			),
1264
			array(
1265
				'tag' => 'code',
1266
				'type' => 'unparsed_equals_content',
1267
				'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>',
1268
				// @todo Maybe this can be simplified?
1269
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1270
				{
1271
					if (!isset($disabled['code']))
1272
					{
1273
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);
1274
1275 View Code Duplication
						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
1276
						{
1277
							// Do PHP code coloring?
1278
							if ($php_parts[$php_i] != '&lt;?php')
1279
								continue;
1280
1281
							$php_string = '';
1282
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1283
							{
1284
								$php_string .= $php_parts[$php_i];
1285
								$php_parts[$php_i++] = '';
1286
							}
1287
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1288
						}
1289
1290
						// Fix the PHP code stuff...
1291
						$data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1292
						$data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]);
1293
1294
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1295
						if ($context['browser']['is_opera'])
1296
							$data[0] .= '&nbsp;';
1297
					}
1298
				},
1299
				'block_level' => true,
1300
			),
1301
			array(
1302
				'tag' => 'color',
1303
				'type' => 'unparsed_equals',
1304
				'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]?)\))\]',
1305
				'before' => '<span style="color: $1;" class="bbc_color">',
1306
				'after' => '</span>',
1307
			),
1308
			array(
1309
				'tag' => 'email',
1310
				'type' => 'unparsed_content',
1311
				'content' => '<a href="mailto:$1" class="bbc_email">$1</a>',
1312
				// @todo Should this respect guest_hideContacts?
1313
				'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...
1314
				{
1315
					$data = strtr($data, array('<br>' => ''));
1316
				},
1317
			),
1318
			array(
1319
				'tag' => 'email',
1320
				'type' => 'unparsed_equals',
1321
				'before' => '<a href="mailto:$1" class="bbc_email">',
1322
				'after' => '</a>',
1323
				// @todo Should this respect guest_hideContacts?
1324
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1325
				'disabled_after' => ' ($1)',
1326
			),
1327
			array(
1328
				'tag' => 'flash',
1329
				'type' => 'unparsed_commas_content',
1330
				'test' => '\d+,\d+\]',
1331
				'content' => '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never">',
1332
				'validate' => function (&$tag, &$data, $disabled)
1333
				{
1334
					if (isset($disabled['url']))
1335
						$tag['content'] = '$1';
1336
					$scheme = parse_url($data[0], PHP_URL_SCHEME);
1337
					if (empty($scheme))
1338
						$data[0] = '//' . ltrim($data[0], ':/');
1339
				},
1340
				'disabled_content' => '<a href="$1" target="_blank" class="new_win">$1</a>',
1341
			),
1342
			array(
1343
				'tag' => 'float',
1344
				'type' => 'unparsed_equals',
1345
				'test' => '(left|right)(\s+max=\d+(?:%|px|em|rem|ex|pt|pc|ch|vw|vh|vmin|vmax|cm|mm|in)?)?\]',
1346
				'before' => '<div $1>',
1347
				'after' => '</div>',
1348
				'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...
1349
				{
1350
					$class = 'class="bbc_float float' . (strpos($data, 'left') === 0 ? 'left' : 'right') . '"';
1351
1352
					if (preg_match('~\bmax=(\d+(?:%|px|em|rem|ex|pt|pc|ch|vw|vh|vmin|vmax|cm|mm|in)?)~', $data, $matches))
1353
						$css = ' style="max-width:' . $matches[1] . (is_numeric($matches[1]) ? 'px' : '') . '"';
1354
					else
1355
						$css = '';
1356
1357
					$data = $class . $css;
1358
				},
1359
				'trim' => 'outside',
1360
				'block_level' => true,
1361
			),
1362
			array(
1363
				'tag' => 'font',
1364
				'type' => 'unparsed_equals',
1365
				'test' => '[A-Za-z0-9_,\-\s]+?\]',
1366
				'before' => '<span style="font-family: $1;" class="bbc_font">',
1367
				'after' => '</span>',
1368
			),
1369
			array(
1370
				'tag' => 'html',
1371
				'type' => 'unparsed_content',
1372
				'content' => '<div>$1</div>',
1373
				'block_level' => true,
1374
				'disabled_content' => '$1',
1375
			),
1376
			array(
1377
				'tag' => 'hr',
1378
				'type' => 'closed',
1379
				'content' => '<hr>',
1380
				'block_level' => true,
1381
			),
1382
			array(
1383
				'tag' => 'i',
1384
				'before' => '<i>',
1385
				'after' => '</i>',
1386
			),
1387
			array(
1388
				'tag' => 'img',
1389
				'type' => 'unparsed_content',
1390
				'parameters' => array(
1391
					'alt' => array('optional' => true),
1392
					'title' => array('optional' => true),
1393
					'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
1394
					'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
1395
				),
1396
				'content' => '<img src="$1" alt="{alt}" title="{title}"{width}{height} class="bbc_img resized">',
1397 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...
1398
				{
1399
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1400
1401
					$data = strtr($data, array('<br>' => ''));
1402
					$scheme = parse_url($data, PHP_URL_SCHEME);
1403
					if ($image_proxy_enabled)
1404
					{
1405
						if (empty($scheme))
1406
							$data = 'http://' . ltrim($data, ':/');
1407
1408
						if ($scheme != 'https')
1409
							$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1410
					}
1411
					elseif (empty($scheme))
1412
						$data = '//' . ltrim($data, ':/');
1413
				},
1414
				'disabled_content' => '($1)',
1415
			),
1416
			array(
1417
				'tag' => 'img',
1418
				'type' => 'unparsed_content',
1419
				'content' => '<img src="$1" alt="" class="bbc_img">',
1420 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...
1421
				{
1422
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1423
1424
					$data = strtr($data, array('<br>' => ''));
1425
					$scheme = parse_url($data, PHP_URL_SCHEME);
1426
					if ($image_proxy_enabled)
1427
					{
1428
						if (empty($scheme))
1429
							$data = 'http://' . ltrim($data, ':/');
1430
1431
						if ($scheme != 'https')
1432
							$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1433
					}
1434
					elseif (empty($scheme))
1435
						$data = '//' . ltrim($data, ':/');
1436
				},
1437
				'disabled_content' => '($1)',
1438
			),
1439
			array(
1440
				'tag' => 'iurl',
1441
				'type' => 'unparsed_content',
1442
				'content' => '<a href="$1" class="bbc_link">$1</a>',
1443 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...
1444
				{
1445
					$data = strtr($data, array('<br>' => ''));
1446
					$scheme = parse_url($data, PHP_URL_SCHEME);
1447
					if (empty($scheme))
1448
						$data = '//' . ltrim($data, ':/');
1449
				},
1450
			),
1451
			array(
1452
				'tag' => 'iurl',
1453
				'type' => 'unparsed_equals',
1454
				'quoted' => 'optional',
1455
				'before' => '<a href="$1" class="bbc_link">',
1456
				'after' => '</a>',
1457
				'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...
1458
				{
1459
					if (substr($data, 0, 1) == '#')
1460
						$data = '#post_' . substr($data, 1);
1461
					else
1462
					{
1463
						$scheme = parse_url($data, PHP_URL_SCHEME);
1464
						if (empty($scheme))
1465
							$data = '//' . ltrim($data, ':/');
1466
					}
1467
				},
1468
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1469
				'disabled_after' => ' ($1)',
1470
			),
1471
			array(
1472
				'tag' => 'left',
1473
				'before' => '<div style="text-align: left;">',
1474
				'after' => '</div>',
1475
				'block_level' => true,
1476
			),
1477
			array(
1478
				'tag' => 'li',
1479
				'before' => '<li>',
1480
				'after' => '</li>',
1481
				'trim' => 'outside',
1482
				'require_parents' => array('list'),
1483
				'block_level' => true,
1484
				'disabled_before' => '',
1485
				'disabled_after' => '<br>',
1486
			),
1487
			array(
1488
				'tag' => 'list',
1489
				'before' => '<ul class="bbc_list">',
1490
				'after' => '</ul>',
1491
				'trim' => 'inside',
1492
				'require_children' => array('li', 'list'),
1493
				'block_level' => true,
1494
			),
1495
			array(
1496
				'tag' => 'list',
1497
				'parameters' => array(
1498
					'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)'),
1499
				),
1500
				'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
1501
				'after' => '</ul>',
1502
				'trim' => 'inside',
1503
				'require_children' => array('li'),
1504
				'block_level' => true,
1505
			),
1506
			array(
1507
				'tag' => 'ltr',
1508
				'before' => '<bdo dir="ltr">',
1509
				'after' => '</bdo>',
1510
				'block_level' => true,
1511
			),
1512
			array(
1513
				'tag' => 'me',
1514
				'type' => 'unparsed_equals',
1515
				'before' => '<div class="meaction">* $1 ',
1516
				'after' => '</div>',
1517
				'quoted' => 'optional',
1518
				'block_level' => true,
1519
				'disabled_before' => '/me ',
1520
				'disabled_after' => '<br>',
1521
			),
1522
			array(
1523
				'tag' => 'member',
1524
				'type' => 'unparsed_equals',
1525
				'before' => '<a href="' . $scripturl . '?action=profile;u=$1" class="mention" data-mention="$1">@',
1526
				'after' => '</a>',
1527
			),
1528
			array(
1529
				'tag' => 'nobbc',
1530
				'type' => 'unparsed_content',
1531
				'content' => '$1',
1532
			),
1533
			array(
1534
				'tag' => 'php',
1535
				'type' => 'unparsed_content',
1536
				'content' => '<span class="phpcode">$1</span>',
1537
				'validate' => isset($disabled['php']) ? null : function (&$tag, &$data, $disabled)
1538
				{
1539
					if (!isset($disabled['php']))
1540
					{
1541
						$add_begin = substr(trim($data), 0, 5) != '&lt;?';
1542
						$data = highlight_php_code($add_begin ? '&lt;?php ' . $data . '?&gt;' : $data);
1543
						if ($add_begin)
1544
							$data = preg_replace(array('~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~', '~\?&gt;((?:</(font|span)>)*)$~'), '$1', $data, 2);
1545
					}
1546
				},
1547
				'block_level' => false,
1548
				'disabled_content' => '$1',
1549
			),
1550
			array(
1551
				'tag' => 'pre',
1552
				'before' => '<pre>',
1553
				'after' => '</pre>',
1554
			),
1555
			array(
1556
				'tag' => 'quote',
1557
				'before' => '<blockquote><cite>' . $txt['quote'] . '</cite>',
1558
				'after' => '</blockquote>',
1559
				'trim' => 'both',
1560
				'block_level' => true,
1561
			),
1562
			array(
1563
				'tag' => 'quote',
1564
				'parameters' => array(
1565
					'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
1566
				),
1567
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1568
				'after' => '</blockquote>',
1569
				'trim' => 'both',
1570
				'block_level' => true,
1571
			),
1572
			array(
1573
				'tag' => 'quote',
1574
				'type' => 'parsed_equals',
1575
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': $1</cite>',
1576
				'after' => '</blockquote>',
1577
				'trim' => 'both',
1578
				'quoted' => 'optional',
1579
				// Don't allow everything to be embedded with the author name.
1580
				'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
1581
				'block_level' => true,
1582
			),
1583
			array(
1584
				'tag' => 'quote',
1585
				'parameters' => array(
1586
					'author' => array('match' => '([^<>]{1,192}?)'),
1587
					'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|msg=\d+?|action=profile;u=\d+)'),
1588
					'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
1589
				),
1590
				'before' => '<blockquote><cite><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></cite>',
1591
				'after' => '</blockquote>',
1592
				'trim' => 'both',
1593
				'block_level' => true,
1594
			),
1595
			array(
1596
				'tag' => 'quote',
1597
				'parameters' => array(
1598
					'author' => array('match' => '(.{1,192}?)'),
1599
				),
1600
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1601
				'after' => '</blockquote>',
1602
				'trim' => 'both',
1603
				'block_level' => true,
1604
			),
1605
			array(
1606
				'tag' => 'right',
1607
				'before' => '<div style="text-align: right;">',
1608
				'after' => '</div>',
1609
				'block_level' => true,
1610
			),
1611
			array(
1612
				'tag' => 'rtl',
1613
				'before' => '<bdo dir="rtl">',
1614
				'after' => '</bdo>',
1615
				'block_level' => true,
1616
			),
1617
			array(
1618
				'tag' => 's',
1619
				'before' => '<s>',
1620
				'after' => '</s>',
1621
			),
1622
			array(
1623
				'tag' => 'size',
1624
				'type' => 'unparsed_equals',
1625
				'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]',
1626
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1627
				'after' => '</span>',
1628
			),
1629
			array(
1630
				'tag' => 'size',
1631
				'type' => 'unparsed_equals',
1632
				'test' => '[1-7]\]',
1633
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1634
				'after' => '</span>',
1635
				'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...
1636
				{
1637
					$sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
1638
					$data = $sizes[$data] . 'em';
1639
				},
1640
			),
1641
			array(
1642
				'tag' => 'sub',
1643
				'before' => '<sub>',
1644
				'after' => '</sub>',
1645
			),
1646
			array(
1647
				'tag' => 'sup',
1648
				'before' => '<sup>',
1649
				'after' => '</sup>',
1650
			),
1651
			array(
1652
				'tag' => 'table',
1653
				'before' => '<table class="bbc_table">',
1654
				'after' => '</table>',
1655
				'trim' => 'inside',
1656
				'require_children' => array('tr'),
1657
				'block_level' => true,
1658
			),
1659
			array(
1660
				'tag' => 'td',
1661
				'before' => '<td>',
1662
				'after' => '</td>',
1663
				'require_parents' => array('tr'),
1664
				'trim' => 'outside',
1665
				'block_level' => true,
1666
				'disabled_before' => '',
1667
				'disabled_after' => '',
1668
			),
1669
			array(
1670
				'tag' => 'time',
1671
				'type' => 'unparsed_content',
1672
				'content' => '$1',
1673
				'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...
1674
				{
1675
					if (is_numeric($data))
1676
						$data = timeformat($data);
1677
					else
1678
						$tag['content'] = '[time]$1[/time]';
1679
				},
1680
			),
1681
			array(
1682
				'tag' => 'tr',
1683
				'before' => '<tr>',
1684
				'after' => '</tr>',
1685
				'require_parents' => array('table'),
1686
				'require_children' => array('td'),
1687
				'trim' => 'both',
1688
				'block_level' => true,
1689
				'disabled_before' => '',
1690
				'disabled_after' => '',
1691
			),
1692
			array(
1693
				'tag' => 'u',
1694
				'before' => '<u>',
1695
				'after' => '</u>',
1696
			),
1697
			array(
1698
				'tag' => 'url',
1699
				'type' => 'unparsed_content',
1700
				'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>',
1701 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...
1702
				{
1703
					$data = strtr($data, array('<br>' => ''));
1704
					$scheme = parse_url($data, PHP_URL_SCHEME);
1705
					if (empty($scheme))
1706
						$data = '//' . ltrim($data, ':/');
1707
				},
1708
			),
1709
			array(
1710
				'tag' => 'url',
1711
				'type' => 'unparsed_equals',
1712
				'quoted' => 'optional',
1713
				'before' => '<a href="$1" class="bbc_link" target="_blank">',
1714
				'after' => '</a>',
1715
				'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...
1716
				{
1717
					$scheme = parse_url($data, PHP_URL_SCHEME);
1718
					if (empty($scheme))
1719
						$data = '//' . ltrim($data, ':/');
1720
				},
1721
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1722
				'disabled_after' => ' ($1)',
1723
			),
1724
		);
1725
1726
		// Inside these tags autolink is not recommendable.
1727
		$no_autolink_tags = array(
1728
			'url',
1729
			'iurl',
1730
			'email',
1731
		);
1732
1733
		// Let mods add new BBC without hassle.
1734
		call_integration_hook('integrate_bbc_codes', array(&$codes, &$no_autolink_tags));
1735
1736
		// This is mainly for the bbc manager, so it's easy to add tags above.  Custom BBC should be added above this line.
1737
		if ($message === false)
1738
		{
1739
			if (isset($temp_bbc))
1740
				$bbc_codes = $temp_bbc;
1741
			usort($codes, function ($a, $b) {
1742
				return strcmp($a['tag'], $b['tag']);
1743
			});
1744
			return $codes;
1745
		}
1746
1747
		// So the parser won't skip them.
1748
		$itemcodes = array(
1749
			'*' => 'disc',
1750
			'@' => 'disc',
1751
			'+' => 'square',
1752
			'x' => 'square',
1753
			'#' => 'square',
1754
			'o' => 'circle',
1755
			'O' => 'circle',
1756
			'0' => 'circle',
1757
		);
1758
		if (!isset($disabled['li']) && !isset($disabled['list']))
1759
		{
1760
			foreach ($itemcodes as $c => $dummy)
1761
				$bbc_codes[$c] = array();
1762
		}
1763
1764
		// Shhhh!
1765
		if (!isset($disabled['color']))
1766
		{
1767
			$codes[] = array(
1768
				'tag' => 'chrissy',
1769
				'before' => '<span style="color: #cc0099;">',
1770
				'after' => ' :-*</span>',
1771
			);
1772
			$codes[] = array(
1773
				'tag' => 'kissy',
1774
				'before' => '<span style="color: #cc0099;">',
1775
				'after' => ' :-*</span>',
1776
			);
1777
		}
1778
1779
		foreach ($codes as $code)
1780
		{
1781
			// Make it easier to process parameters later
1782
			if (!empty($code['parameters']))
1783
				ksort($code['parameters'], SORT_STRING);
1784
1785
			// If we are not doing every tag only do ones we are interested in.
1786
			if (empty($parse_tags) || in_array($code['tag'], $parse_tags))
1787
				$bbc_codes[substr($code['tag'], 0, 1)][] = $code;
1788
		}
1789
		$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...
1790
	}
1791
1792
	// Shall we take the time to cache this?
1793
	if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && isset($message[1000])) || isset($message[2400])) && empty($parse_tags))
1794
	{
1795
		// It's likely this will change if the message is modified.
1796
		$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']);
1797
1798
		if (($temp = cache_get_data($cache_key, 240)) != null)
1799
			return $temp;
1800
1801
		$cache_t = microtime();
1802
	}
1803
1804
	if ($smileys === 'print')
1805
	{
1806
		// [glow], [shadow], and [move] can't really be printed.
1807
		$disabled['glow'] = true;
1808
		$disabled['shadow'] = true;
1809
		$disabled['move'] = true;
1810
1811
		// Colors can't well be displayed... supposed to be black and white.
1812
		$disabled['color'] = true;
1813
		$disabled['black'] = true;
1814
		$disabled['blue'] = true;
1815
		$disabled['white'] = true;
1816
		$disabled['red'] = true;
1817
		$disabled['green'] = true;
1818
		$disabled['me'] = true;
1819
1820
		// Color coding doesn't make sense.
1821
		$disabled['php'] = true;
1822
1823
		// Links are useless on paper... just show the link.
1824
		$disabled['ftp'] = true;
1825
		$disabled['url'] = true;
1826
		$disabled['iurl'] = true;
1827
		$disabled['email'] = true;
1828
		$disabled['flash'] = true;
1829
1830
		// @todo Change maybe?
1831
		if (!isset($_GET['images']))
1832
			$disabled['img'] = true;
1833
1834
		// @todo Interface/setting to add more?
1835
	}
1836
1837
	$open_tags = array();
1838
	$message = strtr($message, array("\n" => '<br>'));
1839
1840
	$alltags = array();
1841
	foreach ($bbc_codes as $section) {
1842
		foreach ($section as $code) {
1843
			$alltags[] = $code['tag'];
1844
		}
1845
	}
1846
	$alltags_regex = '\b' . implode("\b|\b", array_unique($alltags)) . '\b';
1847
1848
	$pos = -1;
1849
	while ($pos !== false)
1850
	{
1851
		$last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
1852
		preg_match('~\[/?(?=' . $alltags_regex . ')~i', $message, $matches, PREG_OFFSET_CAPTURE, $pos + 1);
1853
		$pos = isset($matches[0][1]) ? $matches[0][1] : false;
1854
1855
		// Failsafe.
1856
		if ($pos === false || $last_pos > $pos)
1857
			$pos = strlen($message) + 1;
1858
1859
		// Can't have a one letter smiley, URL, or email! (sorry.)
1860
		if ($last_pos < $pos - 1)
1861
		{
1862
			// Make sure the $last_pos is not negative.
1863
			$last_pos = max($last_pos, 0);
1864
1865
			// Pick a block of data to do some raw fixing on.
1866
			$data = substr($message, $last_pos, $pos - $last_pos);
1867
1868
			// Take care of some HTML!
1869
			if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false)
1870
			{
1871
				$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);
1872
1873
				// <br> should be empty.
1874
				$empty_tags = array('br', 'hr');
1875
				foreach ($empty_tags as $tag)
1876
					$data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '<' . $tag . '>', $data);
1877
1878
				// b, u, i, s, pre... basic tags.
1879
				$closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote', 'strong');
1880
				foreach ($closable_tags as $tag)
1881
				{
1882
					$diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
1883
					$data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
1884
1885
					if ($diff > 0)
1886
						$data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
1887
				}
1888
1889
				// Do <img ...> - with security... action= -> action-.
1890
				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);
1891
				if (!empty($matches[0]))
1892
				{
1893
					$replaces = array();
1894
					foreach ($matches[2] as $match => $imgtag)
1895
					{
1896
						$alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
1897
1898
						// Remove action= from the URL - no funny business, now.
1899
						if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0)
1900
							$imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
1901
1902
						// Check if the image is larger than allowed.
1903
						if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height']))
1904
						{
1905
							list ($width, $height) = url_image_size($imgtag);
1906
1907 View Code Duplication
							if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width'])
1908
							{
1909
								$height = (int) (($modSettings['max_image_width'] * $height) / $width);
1910
								$width = $modSettings['max_image_width'];
1911
							}
1912
1913 View Code Duplication
							if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height'])
1914
							{
1915
								$width = (int) (($modSettings['max_image_height'] * $width) / $height);
1916
								$height = $modSettings['max_image_height'];
1917
							}
1918
1919
							// Set the new image tag.
1920
							$replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
1921
						}
1922
						else
1923
							$replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
1924
					}
1925
1926
					$data = strtr($data, $replaces);
1927
				}
1928
			}
1929
1930
			if (!empty($modSettings['autoLinkUrls']))
1931
			{
1932
				// Are we inside tags that should be auto linked?
1933
				$no_autolink_area = false;
1934
				if (!empty($open_tags))
1935
				{
1936
					foreach ($open_tags as $open_tag)
1937
						if (in_array($open_tag['tag'], $no_autolink_tags))
1938
							$no_autolink_area = true;
1939
				}
1940
1941
				// Don't go backwards.
1942
				// @todo Don't think is the real solution....
1943
				$lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
1944
				if ($pos < $lastAutoPos)
1945
					$no_autolink_area = true;
1946
				$lastAutoPos = $pos;
1947
1948
				if (!$no_autolink_area)
1949
				{
1950
					// Parse any URLs
1951
					if (!isset($disabled['url']) && strpos($data, '[url') === false)
1952
					{
1953
						$url_regex = '
1954
						(?:
1955
							# IRIs with a scheme (or at least an opening "//")
1956
							(?:
1957
								# URI scheme (or lack thereof for schemeless URLs)
1958
								(?:
1959
									# URL scheme and colon
1960
									\b[a-z][\w\-]+:
1961
									| # or
1962
									# A boundary followed by two slashes for schemeless URLs
1963
									(?<=^|\W)(?=//)
1964
								)
1965
1966
								# IRI "authority" chunk
1967
								(?:
1968
									# 2 slashes for IRIs with an "authority"
1969
									//
1970
									# then a domain name
1971
									(?:
1972
										# Either the reserved "localhost" domain name
1973
										localhost
1974
										| # or
1975
										# a run of Unicode domain name characters and a dot
1976
										[\p{L}\p{M}\p{N}\-.:@]+\.
1977
										# and then a TLD valid in the DNS or the reserved "local" TLD
1978
										(?:'. $modSettings['tld_regex'] .'|local)
1979
									)
1980
									# followed by a non-domain character or end of line
1981
									(?=[^\p{L}\p{N}\-.]|$)
1982
1983
									| # Or, if there is no "authority" per se (e.g. mailto: URLs) ...
1984
1985
									# a run of IRI characters
1986
									[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.:@]+[\p{L}\p{M}\p{N}]
1987
									# and then a dot and a closing IRI label
1988
									\.[\p{L}\p{M}\p{N}\-]+
1989
								)
1990
							)
1991
1992
							| # or
1993
1994
							# Naked domains (e.g. "example.com" in "Go to example.com for an example.")
1995
							(?:
1996
								# Preceded by start of line or a non-domain character
1997
								(?<=^|[^\p{L}\p{M}\p{N}\-:@])
1998
1999
								# A run of Unicode domain name characters (excluding [:@])
2000
								[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.]+[\p{L}\p{M}\p{N}]
2001
								# and then a dot and a valid TLD
2002
								\.' . $modSettings['tld_regex'] . '
2003
2004
								# Followed by either:
2005
								(?=
2006
									# end of line or a non-domain character (excluding [.:@])
2007
									$|[^\p{L}\p{N}\-]
2008
									| # or
2009
									# a dot followed by end of line or a non-domain character (excluding [.:@])
2010
									\.(?=$|[^\p{L}\p{N}\-])
2011
								)
2012
							)
2013
						)
2014
2015
						# IRI path, query, and fragment (if present)
2016
						(?:
2017
							# If any of these parts exist, must start with a single /
2018
							/
2019
2020
							# And then optionally:
2021
							(?:
2022
								# One or more of:
2023
								(?:
2024
									# a run of non-space, non-()<>
2025
									[^\s()<>]+
2026
									| # or
2027
									# balanced parens, up to 2 levels
2028
									\(([^\s()<>]+|(\([^\s()<>]+\)))*\)
2029
								)+
2030
2031
								# End with:
2032
								(?:
2033
									# balanced parens, up to 2 levels
2034
									\(([^\s()<>]+|(\([^\s()<>]+\)))*\)
2035
									| # or
2036
									# not a space or one of these punct char
2037
									[^\s`!()\[\]{};:\'".,<>?«»“”‘’/]
2038
									| # or
2039
									# a trailing slash (but not two in a row)
2040
									(?<!/)/
2041
								)
2042
							)?
2043
						)?
2044
						';
2045
2046
						$data = preg_replace_callback('~' . $url_regex . '~xi' . ($context['utf8'] ? 'u' : ''), function ($matches) {
2047
							$url = array_shift($matches);
2048
2049
							$scheme = parse_url($url, PHP_URL_SCHEME);
2050
2051
							if ($scheme == 'mailto')
2052
							{
2053
								$email_address = str_replace('mailto:', '', $url);
2054
								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...
2055
									return '[email=' . $email_address . ']' . $url . '[/email]';
2056
								else
2057
									return $url;
2058
							}
2059
2060
							// Are we linking a schemeless URL or naked domain name (e.g. "example.com")?
2061
							if (empty($scheme))
2062
								$fullUrl = '//' . ltrim($url, ':/');
2063
							else
2064
								$fullUrl = $url;
2065
2066
							return '[url=&quot;' . str_replace(array('[', ']'), array('&#91;', '&#93;'), $fullUrl) . '&quot;]' . $url . '[/url]';
2067
						}, $data);
2068
					}
2069
2070
					// Next, emails...  Must be careful not to step on enablePostHTML logic above...
2071
					if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false && stripos($data, 'mailto:') === false)
2072
					{
2073
						$email_regex = '
2074
						# Preceded by a non-domain character or start of line
2075
						(?<=^|[^\p{L}\p{M}\p{N}\-\.])
2076
2077
						# An email address
2078
						[\p{L}\p{M}\p{N}_\-.]{1,80}
2079
						@
2080
						[\p{L}\p{M}\p{N}\-.]+
2081
						\.
2082
						'. $modSettings['tld_regex'] . '
2083
2084
						# Followed by either:
2085
						(?=
2086
							# end of line or a non-domain character (excluding the dot)
2087
							$|[^\p{L}\p{M}\p{N}\-]
2088
							| # or
2089
							# a dot followed by end of line or a non-domain character
2090
							\.(?=$|[^\p{L}\p{M}\p{N}\-])
2091
						)';
2092
2093
						$data = preg_replace('~' . $email_regex . '~xi' . ($context['utf8'] ? 'u' : ''), '[email]$0[/email]', $data);
2094
					}
2095
				}
2096
			}
2097
2098
			$data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
2099
2100
			// If it wasn't changed, no copying or other boring stuff has to happen!
2101
			if ($data != substr($message, $last_pos, $pos - $last_pos))
2102
			{
2103
				$message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
2104
2105
				// Since we changed it, look again in case we added or removed a tag.  But we don't want to skip any.
2106
				$old_pos = strlen($data) + $last_pos;
2107
				$pos = strpos($message, '[', $last_pos);
2108
				$pos = $pos === false ? $old_pos : min($pos, $old_pos);
2109
			}
2110
		}
2111
2112
		// Are we there yet?  Are we there yet?
2113
		if ($pos >= strlen($message) - 1)
2114
			break;
2115
2116
		$tags = strtolower($message[$pos + 1]);
2117
2118
		if ($tags == '/' && !empty($open_tags))
2119
		{
2120
			$pos2 = strpos($message, ']', $pos + 1);
2121
			if ($pos2 == $pos + 2)
2122
				continue;
2123
2124
			$look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
2125
2126
			$to_close = array();
2127
			$block_level = null;
2128
2129
			do
2130
			{
2131
				$tag = array_pop($open_tags);
2132
				if (!$tag)
2133
					break;
2134
2135
				if (!empty($tag['block_level']))
2136
				{
2137
					// Only find out if we need to.
2138
					if ($block_level === false)
2139
					{
2140
						array_push($open_tags, $tag);
2141
						break;
2142
					}
2143
2144
					// The idea is, if we are LOOKING for a block level tag, we can close them on the way.
2145 View Code Duplication
					if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]]))
2146
					{
2147
						foreach ($bbc_codes[$look_for[0]] as $temp)
2148
							if ($temp['tag'] == $look_for)
2149
							{
2150
								$block_level = !empty($temp['block_level']);
2151
								break;
2152
							}
2153
					}
2154
2155
					if ($block_level !== true)
2156
					{
2157
						$block_level = false;
2158
						array_push($open_tags, $tag);
2159
						break;
2160
					}
2161
				}
2162
2163
				$to_close[] = $tag;
2164
			}
2165
			while ($tag['tag'] != $look_for);
2166
2167
			// Did we just eat through everything and not find it?
2168
			if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)))
2169
			{
2170
				$open_tags = $to_close;
2171
				continue;
2172
			}
2173
			elseif (!empty($to_close) && $tag['tag'] != $look_for)
2174
			{
2175 View Code Duplication
				if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]]))
2176
				{
2177
					foreach ($bbc_codes[$look_for[0]] as $temp)
2178
						if ($temp['tag'] == $look_for)
2179
						{
2180
							$block_level = !empty($temp['block_level']);
2181
							break;
2182
						}
2183
				}
2184
2185
				// We're not looking for a block level tag (or maybe even a tag that exists...)
2186
				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...
2187
				{
2188
					foreach ($to_close as $tag)
2189
						array_push($open_tags, $tag);
2190
					continue;
2191
				}
2192
			}
2193
2194
			foreach ($to_close as $tag)
2195
			{
2196
				$message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
2197
				$pos += strlen($tag['after']) + 2;
2198
				$pos2 = $pos - 1;
2199
2200
				// See the comment at the end of the big loop - just eating whitespace ;).
2201
				$whitespace_regex = '';
2202
				if (!empty($tag['block_level']))
2203
					$whitespace_regex .= '(&nbsp;|\s)*(<br>)?';
2204
				// Trim one line of whitespace after unnested tags, but all of it after nested ones
2205 View Code Duplication
				if (!empty($tag['trim']) && $tag['trim'] != 'inside')
2206
					$whitespace_regex .= empty($tag['require_parents']) ? '(&nbsp;|\s)*' : '(<br>|&nbsp;|\s)*';
2207
2208 View Code Duplication
				if (!empty($whitespace_regex) && preg_match('~' . $whitespace_regex . '~', substr($message, $pos), $matches) != 0)
2209
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2210
			}
2211
2212
			if (!empty($to_close))
2213
			{
2214
				$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...
2215
				$pos--;
2216
			}
2217
2218
			continue;
2219
		}
2220
2221
		// No tags for this character, so just keep going (fastest possible course.)
2222
		if (!isset($bbc_codes[$tags]))
2223
			continue;
2224
2225
		$inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
2226
		$tag = null;
2227
		foreach ($bbc_codes[$tags] as $possible)
2228
		{
2229
			$pt_strlen = strlen($possible['tag']);
2230
2231
			// Not a match?
2232
			if (strtolower(substr($message, $pos + 1, $pt_strlen)) != $possible['tag'])
2233
				continue;
2234
2235
			$next_c = $message[$pos + 1 + $pt_strlen];
2236
2237
			// A test validation?
2238
			if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + $pt_strlen + 1)) === 0)
2239
				continue;
2240
			// Do we want parameters?
2241
			elseif (!empty($possible['parameters']))
2242
			{
2243
				if ($next_c != ' ')
2244
					continue;
2245
			}
2246
			elseif (isset($possible['type']))
2247
			{
2248
				// Do we need an equal sign?
2249
				if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=')
2250
					continue;
2251
				// Maybe we just want a /...
2252
				if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + $pt_strlen, 2) != '/]' && substr($message, $pos + 1 + $pt_strlen, 3) != ' /]')
2253
					continue;
2254
				// An immediate ]?
2255
				if ($possible['type'] == 'unparsed_content' && $next_c != ']')
2256
					continue;
2257
			}
2258
			// No type means 'parsed_content', which demands an immediate ] without parameters!
2259
			elseif ($next_c != ']')
2260
				continue;
2261
2262
			// Check allowed tree?
2263
			if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents'])))
2264
				continue;
2265
			elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children']))
2266
				continue;
2267
			// If this is in the list of disallowed child tags, don't parse it.
2268
			elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children']))
2269
				continue;
2270
2271
			$pos1 = $pos + 1 + $pt_strlen + 1;
2272
2273
			// Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
2274
			if ($possible['tag'] == 'quote')
2275
			{
2276
				// Start with standard
2277
				$quote_alt = false;
2278
				foreach ($open_tags as $open_quote)
2279
				{
2280
					// Every parent quote this quote has flips the styling
2281
					if ($open_quote['tag'] == 'quote')
2282
						$quote_alt = !$quote_alt;
2283
				}
2284
				// Add a class to the quote to style alternating blockquotes
2285
				$possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
2286
			}
2287
2288
			// This is long, but it makes things much easier and cleaner.
2289
			if (!empty($possible['parameters']))
2290
			{
2291
				// Build a regular expression for each parameter for the current tag.
2292
				$preg = array();
2293
				foreach ($possible['parameters'] as $p => $info)
2294
					$preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . '\s*)' . (empty($info['optional']) ? '' : '?');
2295
2296
				// Extract the string that potentially holds our parameters.
2297
				$blob = preg_split('~\[/?(?:' . $alltags_regex . ')~i', substr($message, $pos));
2298
				$blobs = preg_split('~\]~i', $blob[1]);
2299
2300
				$splitters = implode('=|', array_keys($possible['parameters'])) . '=';
2301
2302
				// Progressively append more blobs until we find our parameters or run out of blobs
2303
				$blob_counter = 1;
2304
				while ($blob_counter <= count($blobs))
2305
				{
2306
2307
					$given_param_string = implode(']', array_slice($blobs, 0, $blob_counter++));
2308
2309
					$given_params = preg_split('~\s(?=(' . $splitters . '))~i', $given_param_string);
2310
					sort($given_params, SORT_STRING);
2311
2312
					$match = preg_match('~^' . implode('', $preg) . '$~i', implode(' ', $given_params), $matches) !== 0;
2313
2314
					if ($match)
2315
						$blob_counter = count($blobs) + 1;
2316
				}
2317
2318
				// Didn't match our parameter list, try the next possible.
2319
				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...
2320
					continue;
2321
2322
				$params = array();
2323
				for ($i = 1, $n = count($matches); $i < $n; $i += 2)
2324
				{
2325
					$key = strtok(ltrim($matches[$i]), '=');
2326
					if (isset($possible['parameters'][$key]['value']))
2327
						$params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
2328
					elseif (isset($possible['parameters'][$key]['validate']))
2329
						$params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
2330
					else
2331
						$params['{' . $key . '}'] = $matches[$i + 1];
2332
2333
					// Just to make sure: replace any $ or { so they can't interpolate wrongly.
2334
					$params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
2335
				}
2336
2337
				foreach ($possible['parameters'] as $p => $info)
2338
				{
2339
					if (!isset($params['{' . $p . '}']))
2340
						$params['{' . $p . '}'] = '';
2341
				}
2342
2343
				$tag = $possible;
2344
2345
				// Put the parameters into the string.
2346
				if (isset($tag['before']))
2347
					$tag['before'] = strtr($tag['before'], $params);
2348
				if (isset($tag['after']))
2349
					$tag['after'] = strtr($tag['after'], $params);
2350
				if (isset($tag['content']))
2351
					$tag['content'] = strtr($tag['content'], $params);
2352
2353
				$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...
2354
			}
2355
			else
2356
			{
2357
				$tag = $possible;
2358
				$params = array();
2359
			}
2360
			break;
2361
		}
2362
2363
		// Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
2364
		if ($smileys !== false && $tag === null && isset($itemcodes[$message[$pos + 1]]) && $message[$pos + 2] == ']' && !isset($disabled['list']) && !isset($disabled['li']))
2365
		{
2366
			if ($message[$pos + 1] == '0' && !in_array($message[$pos - 1], array(';', ' ', "\t", "\n", '>')))
2367
				continue;
2368
2369
			$tag = $itemcodes[$message[$pos + 1]];
2370
2371
			// First let's set up the tree: it needs to be in a list, or after an li.
2372
			if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li'))
2373
			{
2374
				$open_tags[] = array(
2375
					'tag' => 'list',
2376
					'after' => '</ul>',
2377
					'block_level' => true,
2378
					'require_children' => array('li'),
2379
					'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2380
				);
2381
				$code = '<ul class="bbc_list">';
2382
			}
2383
			// We're in a list item already: another itemcode?  Close it first.
2384
			elseif ($inside['tag'] == 'li')
2385
			{
2386
				array_pop($open_tags);
2387
				$code = '</li>';
2388
			}
2389
			else
2390
				$code = '';
2391
2392
			// Now we open a new tag.
2393
			$open_tags[] = array(
2394
				'tag' => 'li',
2395
				'after' => '</li>',
2396
				'trim' => 'outside',
2397
				'block_level' => true,
2398
				'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2399
			);
2400
2401
			// First, open the tag...
2402
			$code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>';
2403
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
2404
			$pos += strlen($code) - 1 + 2;
2405
2406
			// Next, find the next break (if any.)  If there's more itemcode after it, keep it going - otherwise close!
2407
			$pos2 = strpos($message, '<br>', $pos);
2408
			$pos3 = strpos($message, '[/', $pos);
2409
			if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false))
2410
			{
2411
				preg_match('~^(<br>|&nbsp;|\s|\[)+~', substr($message, $pos2 + 4), $matches);
2412
				$message = substr($message, 0, $pos2) . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . substr($message, $pos2);
2413
2414
				$open_tags[count($open_tags) - 2]['after'] = '</ul>';
2415
			}
2416
			// Tell the [list] that it needs to close specially.
2417
			else
2418
			{
2419
				// Move the li over, because we're not sure what we'll hit.
2420
				$open_tags[count($open_tags) - 1]['after'] = '';
2421
				$open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
2422
			}
2423
2424
			continue;
2425
		}
2426
2427
		// Implicitly close lists and tables if something other than what's required is in them.  This is needed for itemcode.
2428
		if ($tag === null && $inside !== null && !empty($inside['require_children']))
2429
		{
2430
			array_pop($open_tags);
2431
2432
			$message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
2433
			$pos += strlen($inside['after']) - 1 + 2;
2434
		}
2435
2436
		// No tag?  Keep looking, then.  Silly people using brackets without actual tags.
2437
		if ($tag === null)
2438
			continue;
2439
2440
		// Propagate the list to the child (so wrapping the disallowed tag won't work either.)
2441
		if (isset($inside['disallow_children']))
2442
			$tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
2443
2444
		// Is this tag disabled?
2445
		if (isset($disabled[$tag['tag']]))
2446
		{
2447
			if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content']))
2448
			{
2449
				$tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
2450
				$tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
2451
				$tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
2452
			}
2453
			elseif (isset($tag['disabled_before']) || isset($tag['disabled_after']))
2454
			{
2455
				$tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
2456
				$tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
2457
			}
2458
			else
2459
				$tag['content'] = $tag['disabled_content'];
2460
		}
2461
2462
		// we use this a lot
2463
		$tag_strlen = strlen($tag['tag']);
2464
2465
		// The only special case is 'html', which doesn't need to close things.
2466
		if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level']))
2467
		{
2468
			$n = count($open_tags) - 1;
2469
			while (empty($open_tags[$n]['block_level']) && $n >= 0)
2470
				$n--;
2471
2472
			// Close all the non block level tags so this tag isn't surrounded by them.
2473
			for ($i = count($open_tags) - 1; $i > $n; $i--)
2474
			{
2475
				$message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
2476
				$ot_strlen = strlen($open_tags[$i]['after']);
2477
				$pos += $ot_strlen + 2;
2478
				$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...
2479
2480
				// Trim or eat trailing stuff... see comment at the end of the big loop.
2481
				$whitespace_regex = '';
2482
				if (!empty($tag['block_level']))
2483
					$whitespace_regex .= '(&nbsp;|\s)*(<br>)?';
2484 View Code Duplication
				if (!empty($tag['trim']) && $tag['trim'] != 'inside')
2485
					$whitespace_regex .= empty($tag['require_parents']) ? '(&nbsp;|\s)*' : '(<br>|&nbsp;|\s)*';
2486 View Code Duplication
				if (!empty($whitespace_regex) && preg_match('~' . $whitespace_regex . '~', substr($message, $pos), $matches) != 0)
2487
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2488
2489
				array_pop($open_tags);
2490
			}
2491
		}
2492
2493
		// No type means 'parsed_content'.
2494
		if (!isset($tag['type']))
2495
		{
2496
			// @todo Check for end tag first, so people can say "I like that [i] tag"?
2497
			$open_tags[] = $tag;
2498
			$message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
2499
			$pos += strlen($tag['before']) - 1 + 2;
2500
		}
2501
		// Don't parse the content, just skip it.
2502
		elseif ($tag['type'] == 'unparsed_content')
2503
		{
2504
			$pos2 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos1);
2505
			if ($pos2 === false)
2506
				continue;
2507
2508
			$data = substr($message, $pos1, $pos2 - $pos1);
2509
2510
			if (!empty($tag['block_level']) && substr($data, 0, 4) == '<br>')
2511
				$data = substr($data, 4);
2512
2513
			if (isset($tag['validate']))
2514
				$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...
2515
2516
			$code = strtr($tag['content'], array('$1' => $data));
2517
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + $tag_strlen);
2518
2519
			$pos += strlen($code) - 1 + 2;
2520
			$last_pos = $pos + 1;
2521
2522
		}
2523
		// Don't parse the content, just skip it.
2524
		elseif ($tag['type'] == 'unparsed_equals_content')
2525
		{
2526
			// The value may be quoted for some tags - check.
2527 View Code Duplication
			if (isset($tag['quoted']))
2528
			{
2529
				$quoted = substr($message, $pos1, 6) == '&quot;';
2530
				if ($tag['quoted'] != 'optional' && !$quoted)
2531
					continue;
2532
2533
				if ($quoted)
2534
					$pos1 += 6;
2535
			}
2536
			else
2537
				$quoted = false;
2538
2539
			$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...
2540
			if ($pos2 === false)
2541
				continue;
2542
2543
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2544
			if ($pos3 === false)
2545
				continue;
2546
2547
			$data = array(
2548
				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...
2549
				substr($message, $pos1, $pos2 - $pos1)
2550
			);
2551
2552
			if (!empty($tag['block_level']) && substr($data[0], 0, 4) == '<br>')
2553
				$data[0] = substr($data[0], 4);
2554
2555
			// Validation for my parking, please!
2556
			if (isset($tag['validate']))
2557
				$tag['validate']($tag, $data, $disabled, $params);
2558
2559
			$code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
2560
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2561
			$pos += strlen($code) - 1 + 2;
2562
		}
2563
		// A closed tag, with no content or value.
2564
		elseif ($tag['type'] == 'closed')
2565
		{
2566
			$pos2 = strpos($message, ']', $pos);
2567
			$message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
2568
			$pos += strlen($tag['content']) - 1 + 2;
2569
		}
2570
		// This one is sorta ugly... :/.  Unfortunately, it's needed for flash.
2571
		elseif ($tag['type'] == 'unparsed_commas_content')
2572
		{
2573
			$pos2 = strpos($message, ']', $pos1);
2574
			if ($pos2 === false)
2575
				continue;
2576
2577
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2578
			if ($pos3 === false)
2579
				continue;
2580
2581
			// We want $1 to be the content, and the rest to be csv.
2582
			$data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
2583
			$data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
2584
2585
			if (isset($tag['validate']))
2586
				$tag['validate']($tag, $data, $disabled, $params);
2587
2588
			$code = $tag['content'];
2589 View Code Duplication
			foreach ($data as $k => $d)
2590
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2591
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2592
			$pos += strlen($code) - 1 + 2;
2593
		}
2594
		// This has parsed content, and a csv value which is unparsed.
2595
		elseif ($tag['type'] == 'unparsed_commas')
2596
		{
2597
			$pos2 = strpos($message, ']', $pos1);
2598
			if ($pos2 === false)
2599
				continue;
2600
2601
			$data = explode(',', substr($message, $pos1, $pos2 - $pos1));
2602
2603
			if (isset($tag['validate']))
2604
				$tag['validate']($tag, $data, $disabled, $params);
2605
2606
			// Fix after, for disabled code mainly.
2607 View Code Duplication
			foreach ($data as $k => $d)
2608
				$tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
2609
2610
			$open_tags[] = $tag;
2611
2612
			// 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...
2613
			$code = $tag['before'];
2614 View Code Duplication
			foreach ($data as $k => $d)
2615
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2616
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
2617
			$pos += strlen($code) - 1 + 2;
2618
		}
2619
		// A tag set to a value, parsed or not.
2620
		elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals')
2621
		{
2622
			// The value may be quoted for some tags - check.
2623 View Code Duplication
			if (isset($tag['quoted']))
2624
			{
2625
				$quoted = substr($message, $pos1, 6) == '&quot;';
2626
				if ($tag['quoted'] != 'optional' && !$quoted)
2627
					continue;
2628
2629
				if ($quoted)
2630
					$pos1 += 6;
2631
			}
2632
			else
2633
				$quoted = false;
2634
2635
			$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...
2636
			if ($pos2 === false)
2637
				continue;
2638
2639
			$data = substr($message, $pos1, $pos2 - $pos1);
2640
2641
			// Validation for my parking, please!
2642
			if (isset($tag['validate']))
2643
				$tag['validate']($tag, $data, $disabled, $params);
2644
2645
			// For parsed content, we must recurse to avoid security problems.
2646
			if ($tag['type'] != 'unparsed_equals')
2647
				$data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
2648
2649
			$tag['after'] = strtr($tag['after'], array('$1' => $data));
2650
2651
			$open_tags[] = $tag;
2652
2653
			$code = strtr($tag['before'], array('$1' => $data));
2654
			$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...
2655
			$pos += strlen($code) - 1 + 2;
2656
		}
2657
2658
		// If this is block level, eat any breaks after it.
2659
		if (!empty($tag['block_level']) && substr($message, $pos + 1, 4) == '<br>')
2660
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 5);
2661
2662
		// Are we trimming outside this tag?
2663
		if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos + 1), $matches) != 0)
2664
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
2665
	}
2666
2667
	// Close any remaining tags.
2668
	while ($tag = array_pop($open_tags))
2669
		$message .= "\n" . $tag['after'] . "\n";
2670
2671
	// Parse the smileys within the parts where it can be done safely.
2672
	if ($smileys === true)
2673
	{
2674
		$message_parts = explode("\n", $message);
2675
		for ($i = 0, $n = count($message_parts); $i < $n; $i += 2)
2676
			parsesmileys($message_parts[$i]);
2677
2678
		$message = implode('', $message_parts);
2679
	}
2680
2681
	// No smileys, just get rid of the markers.
2682
	else
2683
		$message = strtr($message, array("\n" => ''));
2684
2685
	if ($message !== '' && $message[0] === ' ')
2686
		$message = '&nbsp;' . substr($message, 1);
2687
2688
	// Cleanup whitespace.
2689
	$message = strtr($message, array('  ' => ' &nbsp;', "\r" => '', "\n" => '<br>', '<br> ' => '<br>&nbsp;', '&#13;' => "\n"));
2690
2691
	// Allow mods access to what parse_bbc created
2692
	call_integration_hook('integrate_post_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
2693
2694
	// Cache the output if it took some time...
2695
	if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05)
2696
		cache_put_data($cache_key, $message, 240);
2697
2698
	// If this was a force parse revert if needed.
2699
	if (!empty($parse_tags))
2700
	{
2701
		if (empty($temp_bbc))
2702
			$bbc_codes = array();
2703
		else
2704
		{
2705
			$bbc_codes = $temp_bbc;
2706
			unset($temp_bbc);
2707
		}
2708
	}
2709
2710
	return $message;
2711
}
2712
2713
/**
2714
 * Parse smileys in the passed message.
2715
 *
2716
 * The smiley parsing function which makes pretty faces appear :).
2717
 * If custom smiley sets are turned off by smiley_enable, the default set of smileys will be used.
2718
 * These are specifically not parsed in code tags [url=mailto:[email protected]]
2719
 * Caches the smileys from the database or array in memory.
2720
 * Doesn't return anything, but rather modifies message directly.
2721
 *
2722
 * @param string &$message The message to parse smileys in
2723
 */
2724
function parsesmileys(&$message)
2725
{
2726
	global $modSettings, $txt, $user_info, $context, $smcFunc;
2727
	static $smileyPregSearch = null, $smileyPregReplacements = array();
2728
2729
	// No smiley set at all?!
2730
	if ($user_info['smiley_set'] == 'none' || trim($message) == '')
2731
		return;
2732
2733
	// If smileyPregSearch hasn't been set, do it now.
2734
	if (empty($smileyPregSearch))
2735
	{
2736
		// Use the default smileys if it is disabled. (better for "portability" of smileys.)
2737
		if (empty($modSettings['smiley_enable']))
2738
		{
2739
			$smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)');
2740
			$smileysto = array('evil.gif', 'cheesy.gif', 'rolleyes.gif', 'angry.gif', 'laugh.gif', 'smiley.gif', 'wink.gif', 'grin.gif', 'sad.gif', 'shocked.gif', 'cool.gif', 'tongue.gif', 'huh.gif', 'embarrassed.gif', 'lipsrsealed.gif', 'kiss.gif', 'cry.gif', 'undecided.gif', 'azn.gif', 'afro.gif', 'police.gif', 'angel.gif');
2741
			$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'], '', '', '', '');
2742
		}
2743
		else
2744
		{
2745
			// Load the smileys in reverse order by length so they don't get parsed wrong.
2746
			if (($temp = cache_get_data('parsing_smileys', 480)) == null)
2747
			{
2748
				$result = $smcFunc['db_query']('', '
2749
					SELECT code, filename, description
2750
					FROM {db_prefix}smileys
2751
					ORDER BY LENGTH(code) DESC',
2752
					array(
2753
					)
2754
				);
2755
				$smileysfrom = array();
2756
				$smileysto = array();
2757
				$smileysdescs = array();
2758
				while ($row = $smcFunc['db_fetch_assoc']($result))
2759
				{
2760
					$smileysfrom[] = $row['code'];
2761
					$smileysto[] = $smcFunc['htmlspecialchars']($row['filename']);
2762
					$smileysdescs[] = $row['description'];
2763
				}
2764
				$smcFunc['db_free_result']($result);
2765
2766
				cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480);
2767
			}
2768
			else
2769
				list ($smileysfrom, $smileysto, $smileysdescs) = $temp;
2770
		}
2771
2772
		// The non-breaking-space is a complex thing...
2773
		$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0';
2774
2775
		// This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:[email protected]] doesn't parse the :D smiley)
2776
		$smileyPregReplacements = array();
2777
		$searchParts = array();
2778
		$smileys_path = $smcFunc['htmlspecialchars']($modSettings['smileys_url'] . '/' . $user_info['smiley_set'] . '/');
2779
2780
		for ($i = 0, $n = count($smileysfrom); $i < $n; $i++)
2781
		{
2782
			$specialChars = $smcFunc['htmlspecialchars']($smileysfrom[$i], ENT_QUOTES);
2783
			$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">';
2784
2785
			$smileyPregReplacements[$smileysfrom[$i]] = $smileyCode;
2786
2787
			$searchParts[] = preg_quote($smileysfrom[$i], '~');
2788
			if ($smileysfrom[$i] != $specialChars)
2789
			{
2790
				$smileyPregReplacements[$specialChars] = $smileyCode;
2791
				$searchParts[] = preg_quote($specialChars, '~');
2792
			}
2793
		}
2794
2795
		$smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|(?<![a-zA-Z0-9])\(|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~' . ($context['utf8'] ? 'u' : '');
2796
	}
2797
2798
	// Replace away!
2799
	$message = preg_replace_callback($smileyPregSearch,
2800
		function ($matches) use ($smileyPregReplacements)
2801
		{
2802
			return $smileyPregReplacements[$matches[1]];
2803
		}, $message);
2804
}
2805
2806
/**
2807
 * Highlight any code.
2808
 *
2809
 * Uses PHP's highlight_string() to highlight PHP syntax
2810
 * does special handling to keep the tabs in the code available.
2811
 * used to parse PHP code from inside [code] and [php] tags.
2812
 *
2813
 * @param string $code The code
2814
 * @return string The code with highlighted HTML.
2815
 */
2816
function highlight_php_code($code)
2817
{
2818
	// Remove special characters.
2819
	$code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", '<br>' => "\n", "\t" => 'SMF_TAB();', '&#91;' => '[')));
2820
2821
	$oldlevel = error_reporting(0);
2822
2823
	$buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true));
2824
2825
	error_reporting($oldlevel);
2826
2827
	// Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P.
2828
	$buffer = preg_replace('~SMF_TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer);
2829
2830
	return strtr($buffer, array('\'' => '&#039;', '<code>' => '', '</code>' => ''));
2831
}
2832
2833
/**
2834
 * Make sure the browser doesn't come back and repost the form data.
2835
 * Should be used whenever anything is posted.
2836
 *
2837
 * @param string $setLocation The URL to redirect them to
2838
 * @param bool $refresh Whether to use a meta refresh instead
2839
 * @param bool $permanent Whether to send a 301 Moved Permanently instead of a 302 Moved Temporarily
2840
 */
2841
function redirectexit($setLocation = '', $refresh = false, $permanent = false)
2842
{
2843
	global $scripturl, $context, $modSettings, $db_show_debug, $db_cache;
2844
2845
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
2846
	if (!empty($context['flush_mail']))
2847
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
2848
		AddMailQueue(true);
2849
2850
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
2851
2852
	if ($add)
2853
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
2854
2855
	// Put the session ID in.
2856
	if (defined('SID') && SID != '')
2857
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
2858
	// Keep that debug in their for template debugging!
2859 View Code Duplication
	elseif (isset($_GET['debug']))
2860
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
2861
2862
	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'])))
2863
	{
2864
		if (defined('SID') && SID != '')
2865
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~',
2866
				function ($m) use ($scripturl)
2867
				{
2868
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html?' . SID. (isset($m[2]) ? "$m[2]" : "");
2869
				}, $setLocation);
2870 View Code Duplication
		else
2871
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~',
2872
				function ($m) use ($scripturl)
2873
				{
2874
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? "$m[2]" : "");
2875
				}, $setLocation);
2876
	}
2877
2878
	// Maybe integrations want to change where we are heading?
2879
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh, &$permanent));
2880
2881
	// Set the header.
2882
	header('Location: ' . str_replace(' ', '%20', $setLocation), true, $permanent ? 301 : 302);
0 ignored issues
show
Security Response Splitting introduced by
'Location: ' . str_repla...', '%20', $setLocation) can contain request data and is used in response header context(s) leading to a potential security vulnerability.

66 paths for user data to reach this point

  1. Path: Read from $_GET, and $request is assigned in proxy.php on line 98
  1. Read from $_GET, and $request is assigned
    in proxy.php on line 98
  2. $request is passed to redirectexit()
    in proxy.php on line 116
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  2. Path: Read from $_POST, and $_POST['start_date'] is passed through date_parse(), and $d is assigned in Sources/Calendar.php on line 356
  1. Read from $_POST, and $_POST['start_date'] is passed through date_parse(), and $d is assigned
    in Sources/Calendar.php on line 356
  2. $month is assigned
    in Sources/Calendar.php on line 358
  3. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  3. Path: Read from $_POST, and $_POST['start_datetime'] is passed through date_parse(), and $d is assigned in Sources/Calendar.php on line 363
  1. Read from $_POST, and $_POST['start_datetime'] is passed through date_parse(), and $d is assigned
    in Sources/Calendar.php on line 363
  2. $month is assigned
    in Sources/Calendar.php on line 365
  3. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  4. Path: Read from $_POST, and $month is assigned in Sources/Calendar.php on line 372
  1. Read from $_POST, and $month is assigned
    in Sources/Calendar.php on line 372
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  5. Path: Read from $_POST, and $year is assigned in Sources/Calendar.php on line 371
  1. Read from $_POST, and $year is assigned
    in Sources/Calendar.php on line 371
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  6. Path: Read from $_POST, and $day is assigned in Sources/Calendar.php on line 373
  1. Read from $_POST, and $day is assigned
    in Sources/Calendar.php on line 373
  2. $scripturl . '?action=calendar;month=' . $month . ';year=' . $year . ';day=' . $day is passed to redirectexit()
    in Sources/Calendar.php on line 375
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  7. Path: Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Display.php on line 1910
  1. Read from $_REQUEST, and 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Display.php on line 1910
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  8. Path: Read from $_REQUEST, and !empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit() in Sources/Display.php on line 2018
  1. Read from $_REQUEST, and !empty($topicGone) ? 'board=' . $board : 'topic=' . $topic . '.' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/Display.php on line 2018
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  9. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/Load.php on line 817
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/Load.php on line 817
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  10. Path: Read from $_GET, and $_GET is passed through each(), and $k is assigned in Sources/Load.php on line 1851
  1. Read from $_GET, and $_GET is passed through each(), and $k is assigned
    in Sources/Load.php on line 1851
  2. 'wwwRedirect;' . $k . '=' . $v is passed to redirectexit()
    in Sources/Load.php on line 1854
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 2882
  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 2882
  13. Path: Read from $_GET, and $context is assigned in Sources/ManageLanguages.php on line 792
  1. Read from $_GET, and $context is assigned
    in Sources/ManageLanguages.php on line 792
  2. $context is assigned
    in Sources/ManageLanguages.php on line 797
  3. $context is assigned
    in Sources/ManageLanguages.php on line 845
  4. 'action=admin;area=languages;sa=edit;' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/ManageLanguages.php on line 942
  5. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  14. Path: Read from $_REQUEST, and $_REQUEST['tfid'] is passed through explode(), and $file_id is assigned in Sources/ManageLanguages.php on line 793
  1. Read from $_REQUEST, and $_REQUEST['tfid'] is passed through explode(), and $file_id is assigned
    in Sources/ManageLanguages.php on line 793
  2. $current_file is assigned
    in Sources/ManageLanguages.php on line 842
  3. $current_file is passed through sprintf(), and $context is assigned
    in Sources/ManageLanguages.php on line 1006
  4. $context is assigned
    in Sources/ManageLanguages.php on line 1044
  5. 'action=admin;area=languages;sa=editlang;lid=' . $context['lang_id'] is passed to redirectexit()
    in Sources/ManageLanguages.php on line 1171
  6. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 2882
  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 2882
  17. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $_REQUEST['filter'] . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1031
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $_REQUEST['filter'] . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1031
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  18. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1035
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1035
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  19. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1071
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1071
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  20. Path: Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit() in Sources/ManageMembers.php on line 1256
  1. Read from $_REQUEST, and 'action=admin;area=viewmembers;sa=browse;type=' . $_REQUEST['type'] . ';sort=' . $_REQUEST['sort'] . ';filter=' . $current_filter . ';start=' . $_REQUEST['start'] is passed to redirectexit()
    in Sources/ManageMembers.php on line 1256
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 2882
  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 2882
  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 2882
  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 2882
  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 2882
  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 2882
  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 2882
  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 2882
  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 1741
  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 1741
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 1745
  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 1745
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 772
  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 772
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  32. Path: Read from $_REQUEST, and $redirect_url is assigned in Sources/MessageIndex.php on line 801
  1. Read from $_REQUEST, and $redirect_url is assigned
    in Sources/MessageIndex.php on line 801
  2. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 854
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  33. Path: Read from $_POST, and $redirect_url is assigned in Sources/MessageIndex.php on line 811
  1. Read from $_POST, and $redirect_url is assigned
    in Sources/MessageIndex.php on line 811
  2. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 854
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  34. Path: Read from $_REQUEST, and $_SESSION is assigned in Sources/MessageIndex.php on line 782
  1. Read from $_REQUEST, and $_SESSION is assigned
    in Sources/MessageIndex.php on line 782
  2. $redirect_url is assigned
    in Sources/MessageIndex.php on line 811
  3. $redirect_url is passed to redirectexit()
    in Sources/MessageIndex.php on line 854
  4. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 2882
  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 2882
  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 2882
  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 2882
  39. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2444
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2444
  2. $context is assigned
    in Sources/Packages.php on line 2455
  3. $context is assigned
    in Sources/Packages.php on line 2539
  4. 'action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode($smcFunc['json_encode']($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/Packages.php on line 2683
  5. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  40. Path: Read from $_POST, and $status is assigned in Sources/Packages.php on line 2465
  1. Read from $_POST, and $status is assigned
    in Sources/Packages.php on line 2465
  2. $context is assigned
    in Sources/Packages.php on line 2486
  3. $context is assigned
    in Sources/Packages.php on line 2488
  4. 'action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode($smcFunc['json_encode']($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/Packages.php on line 2499
  5. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  41. Path: Read from $_POST, and $context is assigned in Sources/Packages.php on line 2544
  1. Read from $_POST, and $context is assigned
    in Sources/Packages.php on line 2544
  2. $context is assigned
    in Sources/Packages.php on line 2546
  3. $context is assigned
    in Sources/Packages.php on line 2547
  4. $context is assigned
    in Sources/Packages.php on line 2549
  5. $context is assigned
    in Sources/Packages.php on line 2594
  6. $context is assigned
    in Sources/Packages.php on line 2678
  7. $context is assigned
    in Sources/Packages.php on line 2679
  8. 'action=admin;area=packages;sa=perms' . (!empty($context['back_look_data']) ? ';back_look=' . base64_encode($smcFunc['json_encode']($context['back_look_data'])) : '') . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/Packages.php on line 2683
  9. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  42. Path: Read from $_REQUEST, and $_REQUEST[$recipientType] is passed through strtr(), and $recipientString is assigned in Sources/PersonalMessage.php on line 2291
  1. Read from $_REQUEST, and $_REQUEST[$recipientType] is passed through strtr(), and $recipientString is assigned
    in Sources/PersonalMessage.php on line 2291
  2. $recipientString is passed through preg_replace(), and preg_replace('~"[^"]+"~', '', $recipientString) is passed through explode(), and explode(',', preg_replace('~"[^"]+"~', '', $recipientString)) is passed through array_merge(), and array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString))) is passed through array_unique(), and $namedRecipientList is assigned
    in Sources/PersonalMessage.php on line 2294
  3. $namesNotFound is assigned
    in Sources/PersonalMessage.php on line 2309
  4. $name is assigned
    in Sources/PersonalMessage.php on line 2357
  5. $name is passed through sprintf(), and $context is assigned
    in Sources/PersonalMessage.php on line 2358
  6. $context is assigned
    in Sources/PersonalMessage.php on line 2459
  7. $context['current_label_redirect'] is passed to redirectexit()
    in Sources/PersonalMessage.php on line 2499
  8. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 2882
  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 2882
  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 2882
  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 2882
  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 2882
  48. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in Sources/Post.php on line 2259
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in Sources/Post.php on line 2259
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 2882
  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 518
  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 518
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 529
  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 529
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 2882
  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 2882
  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 2882
  55. Path: Read from $_POST, and 'topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit() in Sources/ReportToMod.php on line 274
  1. Read from $_POST, and 'topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 274
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  56. Path: Read from $_POST, and 'reportsent;topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit() in Sources/ReportToMod.php on line 346
  1. Read from $_POST, and 'reportsent;topic=' . $topic . '.msg' . $_POST['msg'] . '#msg' . $_POST['msg'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 346
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  57. Path: Read from $_POST, and 'action=profile;u=' . $_POST['u'] is passed to redirectexit() in Sources/ReportToMod.php on line 398
  1. Read from $_POST, and 'action=profile;u=' . $_POST['u'] is passed to redirectexit()
    in Sources/ReportToMod.php on line 398
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  58. Path: Read from $_POST, and $_POST['u'] is passed to reportUser() in Sources/ReportToMod.php on line 221
  1. Read from $_POST, and $_POST['u'] is passed to reportUser()
    in Sources/ReportToMod.php on line 221
  2. 'reportsent;action=profile;u=' . $id_member is passed to redirectexit()
    in Sources/ReportToMod.php on line 467
  3. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 2882
  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 2882
  61. Path: Read from $_REQUEST, and $_REQUEST['topics'] is passed to MergeExecute() in Sources/MessageIndex.php on line 864
  1. Read from $_REQUEST, and $_REQUEST['topics'] is passed to MergeExecute()
    in Sources/MessageIndex.php on line 864
  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 2882
  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 2882
  63. Path: Read from $_GET, and 'action=admin;area=theme;sa=list;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit() in Sources/Themes.php on line 749
  1. Read from $_GET, and 'action=admin;area=theme;sa=list;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] is passed to redirectexit()
    in Sources/Themes.php on line 749
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  64. Path: Read from $_GET, and 'action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . dirname($_REQUEST['filename']) is passed to redirectexit() in Sources/Themes.php on line 1785
  1. Read from $_GET, and 'action=admin;area=theme;th=' . $_GET['th'] . ';' . $context['session_var'] . '=' . $context['session_id'] . ';sa=edit;directory=' . dirname($_REQUEST['filename']) is passed to redirectexit()
    in Sources/Themes.php on line 1785
  2. $setLocation is passed through str_replace()
    in Sources/Subs.php on line 2882
  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 2882
  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 2882

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

22 paths for user data to reach this point

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

General Strategies to prevent injection

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

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

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

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

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

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

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

2 paths for user data to reach this point

  1. Path: Fetching key HTTP_HOST from $_SERVER, and $host is assigned in other/install.php on line 928
  1. Fetching key HTTP_HOST from $_SERVER, and $host is assigned
    in other/install.php on line 928
  2. $incontext is assigned
    in other/install.php on line 931
  3. $incontext is assigned
    in other/install.php on line 934
  4. $incontext is assigned
    in other/install.php on line 935
  5. $incontext is assigned
    in other/install.php on line 936
  6. $incontext is assigned
    in other/install.php on line 938
  7. $incontext is assigned
    in other/install.php on line 941
  8. $incontext is assigned
    in other/install.php on line 942
  9. $incontext['detected_url'] is passed to https_redirect_active()
    in other/install.php on line 946
  10. $url is passed through str_ireplace(), and $url is assigned
    in Sources/Subs.php on line 6022
  2. Path: Fetching key PHP_SELF from $_SERVER, and $_SERVER['PHP_SELF'] is passed through substr(), and $incontext is assigned in other/install.php on line 931
  1. Fetching key PHP_SELF from $_SERVER, and $_SERVER['PHP_SELF'] is passed through substr(), and $incontext is assigned
    in other/install.php on line 931
  2. $incontext is assigned
    in other/install.php on line 934
  3. $incontext is assigned
    in other/install.php on line 935
  4. $incontext is assigned
    in other/install.php on line 936
  5. $incontext is assigned
    in other/install.php on line 938
  6. $incontext is assigned
    in other/install.php on line 941
  7. $incontext is assigned
    in other/install.php on line 942
  8. $incontext['detected_url'] is passed to https_redirect_active()
    in other/install.php on line 946
  9. $url is passed through str_ireplace(), and $url is assigned
    in Sources/Subs.php on line 6022

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...
6024
	if ($headers === false)
6025
		return false;
6026
6027
	// Now to see if it came back https...   
6028
	// First check for a redirect status code in first row (301, 302, 307)
6029
	if (strstr($headers[0], '301') === false && strstr($headers[0], '302') === false && strstr($headers[0], '307') === false)
6030
		return false;
6031
	
6032
	// Search for the location entry to confirm https
6033
	$result = false;
6034
	foreach ($headers as $header) {
6035
		if (stristr($header, 'Location: https://') !== false) {
6036
			$result = true;
6037
			break;
6038
		}
6039
	}
6040
	return $result;		
6041
}
6042
6043
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...