Completed
Push — release-2.1 ( 64d581...84b0cc )
by Rick
06:44 queued 10s
created

Subs.php ➔ set_tld_regex()   D

Complexity

Conditions 34
Paths 10

Size

Total Lines 189
Code Lines 113

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 34
eloc 113
nc 10
nop 1
dl 0
loc 189
rs 4.3215
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to, well, everything.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2016 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 3
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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)
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
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
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
 * @return string A formatted timestamp
717
 */
718
function timeformat($log_time, $show_today = true, $offset_type = false)
719
{
720
	global $context, $user_info, $txt, $modSettings;
721
	static $non_twelve_hour;
722
723
	// Offset the time.
724
	if (!$offset_type)
725
		$time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600;
726
	// Just the forum offset?
727
	elseif ($offset_type == 'forum')
728
		$time = $log_time + $modSettings['time_offset'] * 3600;
729
	else
730
		$time = $log_time;
731
732
	// We can't have a negative date (on Windows, at least.)
733
	if ($log_time < 0)
734
		$log_time = 0;
735
736
	// Today and Yesterday?
737
	if ($modSettings['todayMod'] >= 1 && $show_today === true)
738
	{
739
		// Get the current time.
740
		$nowtime = forum_time();
741
742
		$then = @getdate($time);
743
		$now = @getdate($nowtime);
744
745
		// Try to make something of a time format string...
746
		$s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S';
747
		if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false)
748
		{
749
			$h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l';
750
			$today_fmt = $h . ':%M' . $s . ' %p';
751
		}
752
		else
753
			$today_fmt = '%H:%M' . $s;
754
755
		// Same day of the year, same year.... Today!
756
		if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
757
			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...
758
759
		// 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...
760
		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))
761
			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...
762
	}
763
764
	$str = !is_bool($show_today) ? $show_today : $user_info['time_format'];
765
766
	if (setlocale(LC_TIME, $txt['lang_locale']))
767
	{
768
		if (!isset($non_twelve_hour))
769
			$non_twelve_hour = trim(strftime('%p')) === '';
770 View Code Duplication
		if ($non_twelve_hour && strpos($str, '%p') !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
771
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
772
773
		foreach (array('%a', '%A', '%b', '%B') as $token)
774
			if (strpos($str, $token) !== false)
775
				$str = str_replace($token, strftime($token, $time), $str);
776
	}
777
	else
778
	{
779
		// Do-it-yourself time localization.  Fun.
780
		foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label)
781
			if (strpos($str, $token) !== false)
782
				$str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str);
783
784 View Code Duplication
		if (strpos($str, '%p') !== false)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
785
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
786
	}
787
788
	// Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that.
789
	if ($context['server']['is_windows'] && strpos($str, '%e') !== false)
790
		$str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str);
791
792
	// Format any other characters..
793
	return strftime($str, $time);
794
}
795
796
/**
797
 * Removes special entities from strings.  Compatibility...
798
 * Should be used instead of html_entity_decode for PHP version compatibility reasons.
799
 *
800
 * - removes the base entities (&lt;, &quot;, etc.) from text.
801
 * - additionally converts &nbsp; and &#039;.
802
 *
803
 * @param string $string A string
804
 * @return string The string without entities
805
 */
806
function un_htmlspecialchars($string)
0 ignored issues
show
Best Practice introduced by
The function un_htmlspecialchars() has been defined more than once; this definition is ignored, only the first definition in other/upgrade.php (L149-152) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
807
{
808
	global $context;
809
	static $translation = array();
810
811
	// Determine the character set... Default to UTF-8
812
	if (empty($context['character_set']))
813
		$charset = 'UTF-8';
814
	// Use ISO-8859-1 in place of non-supported ISO-8859 charsets...
815
	elseif (strpos($context['character_set'], 'ISO-8859-') !== false && !in_array($context['character_set'], array('ISO-8859-5', 'ISO-8859-15')))
816
		$charset = 'ISO-8859-1';
817
	else
818
		$charset = $context['character_set'];
819
820
	if (empty($translation))
821
		$translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES, $charset)) + array('&#039;' => '\'', '&#39;' => '\'', '&nbsp;' => ' ');
822
823
	return strtr($string, $translation);
824
}
825
826
/**
827
 * Shorten a subject + internationalization concerns.
828
 *
829
 * - shortens a subject so that it is either shorter than length, or that length plus an ellipsis.
830
 * - respects internationalization characters and entities as one character.
831
 * - avoids trailing entities.
832
 * - returns the shortened string.
833
 *
834
 * @param string $subject The subject
835
 * @param int $len How many characters to limit it to
836
 * @return string The shortened subject - either the entire subject (if it's <= $len) or the subject shortened to $len characters with "..." appended
837
 */
838
function shorten_subject($subject, $len)
839
{
840
	global $smcFunc;
841
842
	// It was already short enough!
843
	if ($smcFunc['strlen']($subject) <= $len)
844
		return $subject;
845
846
	// Shorten it by the length it was too long, and strip off junk from the end.
847
	return $smcFunc['substr']($subject, 0, $len) . '...';
848
}
849
850
/**
851
 * Gets the current time with offset.
852
 *
853
 * - always applies the offset in the time_offset setting.
854
 *
855
 * @param bool $use_user_offset Whether to apply the user's offset as well
856
 * @param int $timestamp A timestamp (null to use current time)
857
 * @return int Seconds since the unix epoch, with forum time offset and (optionally) user time offset applied
858
 */
859
function forum_time($use_user_offset = true, $timestamp = null)
860
{
861
	global $user_info, $modSettings;
862
863
	if ($timestamp === null)
864
		$timestamp = time();
865
	elseif ($timestamp == 0)
866
		return 0;
867
868
	return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600;
869
}
870
871
/**
872
 * Calculates all the possible permutations (orders) of array.
873
 * should not be called on huge arrays (bigger than like 10 elements.)
874
 * returns an array containing each permutation.
875
 *
876
 * @deprecated since 2.1
877
 * @param array $array An array
878
 * @return array An array containing each permutation
879
 */
880
function permute($array)
881
{
882
	$orders = array($array);
883
884
	$n = count($array);
885
	$p = range(0, $n);
886
	for ($i = 1; $i < $n; null)
887
	{
888
		$p[$i]--;
889
		$j = $i % 2 != 0 ? $p[$i] : 0;
890
891
		$temp = $array[$i];
892
		$array[$i] = $array[$j];
893
		$array[$j] = $temp;
894
895
		for ($i = 1; $p[$i] == 0; $i++)
896
			$p[$i] = 1;
897
898
		$orders[] = $array;
899
	}
900
901
	return $orders;
902
}
903
904
/**
905
 * Parse bulletin board code in a string, as well as smileys optionally.
906
 *
907
 * - only parses bbc tags which are not disabled in disabledBBC.
908
 * - handles basic HTML, if enablePostHTML is on.
909
 * - caches the from/to replace regular expressions so as not to reload them every time a string is parsed.
910
 * - only parses smileys if smileys is true.
911
 * - does nothing if the enableBBC setting is off.
912
 * - uses the cache_id as a unique identifier to facilitate any caching it may do.
913
 *  -returns the modified message.
914
 *
915
 * @param string $message The message
916
 * @param bool $smileys Whether to parse smileys as well
917
 * @param string $cache_id The cache ID
918
 * @param array $parse_tags If set, only parses these tags rather than all of them
919
 * @return string The parsed message
920
 */
921
function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array())
922
{
923
	global $txt, $scripturl, $context, $modSettings, $user_info, $sourcedir;
924
	static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
925
	static $disabled;
926
927
	// Don't waste cycles
928
	if ($message === '')
929
		return '';
930
931
	// Just in case it wasn't determined yet whether UTF-8 is enabled.
932
	if (!isset($context['utf8']))
933
		$context['utf8'] = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
934
935
	// Clean up any cut/paste issues we may have
936
	$message = sanitizeMSCutPaste($message);
937
938
	// If the load average is too high, don't parse the BBC.
939
	if (!empty($context['load_average']) && !empty($modSettings['bbc']) && $context['load_average'] >= $modSettings['bbc'])
940
	{
941
		$context['disabled_parse_bbc'] = true;
942
		return $message;
943
	}
944
945
	if ($smileys !== null && ($smileys == '1' || $smileys == '0'))
946
		$smileys = (bool) $smileys;
947
948
	if (empty($modSettings['enableBBC']) && $message !== false)
949
	{
950
		if ($smileys === true)
951
			parsesmileys($message);
952
953
		return $message;
954
	}
955
956
	// If we are not doing every tag then we don't cache this run.
957
	if (!empty($parse_tags) && !empty($bbc_codes))
958
	{
959
		$temp_bbc = $bbc_codes;
960
		$bbc_codes = array();
961
	}
962
963
	// Ensure $modSettings['tld_regex'] contains a valid regex for the autolinker
964
	if (!empty($modSettings['autoLinkUrls']))
965
		set_tld_regex();
966
967
	// Allow mods access before entering the main parse_bbc loop
968
	call_integration_hook('integrate_pre_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
969
970
	// Sift out the bbc for a performance improvement.
971
	if (empty($bbc_codes) || $message === false || !empty($parse_tags))
972
	{
973
		if (!empty($modSettings['disabledBBC']))
974
		{
975
			$disabled = array();
976
977
			$temp = explode(',', strtolower($modSettings['disabledBBC']));
978
979
			foreach ($temp as $tag)
980
				$disabled[trim($tag)] = true;
981
		}
982
983
		if (empty($modSettings['enableEmbeddedFlash']))
984
			$disabled['flash'] = true;
985
986
		/* The following bbc are formatted as an array, with keys as follows:
987
988
			tag: the tag's name - should be lowercase!
989
990
			type: one of...
991
				- (missing): [tag]parsed content[/tag]
992
				- unparsed_equals: [tag=xyz]parsed content[/tag]
993
				- parsed_equals: [tag=parsed data]parsed content[/tag]
994
				- unparsed_content: [tag]unparsed content[/tag]
995
				- closed: [tag], [tag/], [tag /]
996
				- unparsed_commas: [tag=1,2,3]parsed content[/tag]
997
				- unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
998
				- unparsed_equals_content: [tag=...]unparsed content[/tag]
999
1000
			parameters: an optional array of parameters, for the form
1001
			  [tag abc=123]content[/tag].  The array is an associative array
1002
			  where the keys are the parameter names, and the values are an
1003
			  array which may contain the following:
1004
				- match: a regular expression to validate and match the value.
1005
				- quoted: true if the value should be quoted.
1006
				- validate: callback to evaluate on the data, which is $data.
1007
				- value: a string in which to replace $1 with the data.
1008
				  either it or validate may be used, not both.
1009
				- optional: true if the parameter is optional.
1010
1011
			test: a regular expression to test immediately after the tag's
1012
			  '=', ' ' or ']'.  Typically, should have a \] at the end.
1013
			  Optional.
1014
1015
			content: only available for unparsed_content, closed,
1016
			  unparsed_commas_content, and unparsed_equals_content.
1017
			  $1 is replaced with the content of the tag.  Parameters
1018
			  are replaced in the form {param}.  For unparsed_commas_content,
1019
			  $2, $3, ..., $n are replaced.
1020
1021
			before: only when content is not used, to go before any
1022
			  content.  For unparsed_equals, $1 is replaced with the value.
1023
			  For unparsed_commas, $1, $2, ..., $n are replaced.
1024
1025
			after: similar to before in every way, except that it is used
1026
			  when the tag is closed.
1027
1028
			disabled_content: used in place of content when the tag is
1029
			  disabled.  For closed, default is '', otherwise it is '$1' if
1030
			  block_level is false, '<div>$1</div>' elsewise.
1031
1032
			disabled_before: used in place of before when disabled.  Defaults
1033
			  to '<div>' if block_level, '' if not.
1034
1035
			disabled_after: used in place of after when disabled.  Defaults
1036
			  to '</div>' if block_level, '' if not.
1037
1038
			block_level: set to true the tag is a "block level" tag, similar
1039
			  to HTML.  Block level tags cannot be nested inside tags that are
1040
			  not block level, and will not be implicitly closed as easily.
1041
			  One break following a block level tag may also be removed.
1042
1043
			trim: if set, and 'inside' whitespace after the begin tag will be
1044
			  removed.  If set to 'outside', whitespace after the end tag will
1045
			  meet the same fate.
1046
1047
			validate: except when type is missing or 'closed', a callback to
1048
			  validate the data as $data.  Depending on the tag's type, $data
1049
			  may be a string or an array of strings (corresponding to the
1050
			  replacement.)
1051
1052
			quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
1053
			  may be not set, 'optional', or 'required' corresponding to if
1054
			  the content may be quoted.  This allows the parser to read
1055
			  [tag="abc]def[esdf]"] properly.
1056
1057
			require_parents: an array of tag names, or not set.  If set, the
1058
			  enclosing tag *must* be one of the listed tags, or parsing won't
1059
			  occur.
1060
1061
			require_children: similar to require_parents, if set children
1062
			  won't be parsed if they are not in the list.
1063
1064
			disallow_children: similar to, but very different from,
1065
			  require_children, if it is set the listed tags will not be
1066
			  parsed inside the tag.
1067
1068
			parsed_tags_allowed: an array restricting what BBC can be in the
1069
			  parsed_equals parameter, if desired.
1070
		*/
1071
1072
		$codes = array(
1073
			array(
1074
				'tag' => 'abbr',
1075
				'type' => 'unparsed_equals',
1076
				'before' => '<abbr title="$1">',
1077
				'after' => '</abbr>',
1078
				'quoted' => 'optional',
1079
				'disabled_after' => ' ($1)',
1080
			),
1081
			array(
1082
				'tag' => 'anchor',
1083
				'type' => 'unparsed_equals',
1084
				'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]',
1085
				'before' => '<span id="post_$1">',
1086
				'after' => '</span>',
1087
			),
1088
			array(
1089
				'tag' => 'attach',
1090
				'type' => 'unparsed_content',
1091
				'parameters' => array(
1092
					'name' => array('optional' => true),
1093
					'type' => array('optional' => true),
1094
					'alt' => array('optional' => true),
1095
					'title' => array('optional' => true),
1096
					'width' => array('optional' => true, 'match' => '(\d+)'),
1097
					'height' => array('optional' => true, 'match' => '(\d+)'),
1098
				),
1099
				'content' => '$1',
1100
				'validate' => function (&$tag, &$data, $disabled, $params) use ($modSettings, $context, $sourcedir, $txt)
1101
				{
1102
					$returnContext = '';
1103
1104
					// BBC or the entire attachments feature is disabled
1105
					if (empty($modSettings['attachmentEnable']) || !empty($disabled['attach']))
1106
						return $data;
1107
1108
					// Save the attach ID.
1109
					$attachID = $data;
1110
1111
					// Kinda need this.
1112
					require_once($sourcedir . '/Subs-Attachments.php');
1113
1114
					$currentAttachment = parseAttachBBC($attachID);
1115
1116
					// parseAttachBBC will return a string ($txt key) rather than diying with a fatal_error. Up to you to decide what to do.
1117
					if (is_string($currentAttachment))
1118
						return $data = !empty($txt[$currentAttachment]) ? $txt[$currentAttachment] : $currentAttachment;
1119
1120
					if (!empty($currentAttachment['is_image']))
1121
					{
1122
						$alt = !empty($params['{alt}']) ? ' alt="' . $params['{alt}'] . '"' : ' alt="' . $currentAttachment['name'] . '"';
0 ignored issues
show
Unused Code introduced by
$alt 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...
1123
						$title = !empty($params['{title}']) ? ' title="' . $params['{alt}'] . '"' : '';
0 ignored issues
show
Unused Code introduced by
$title 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...
1124
1125
						$width = !empty($params['{width}']) ? ' width="' . $params['{width}'] . '"' : '';
1126
						$height = !empty($params['{height}']) ? ' height="' . $params['{height}'] . '"' : '';
1127
1128
						if (empty($width) && empty($height))
1129
						{
1130
							$width = ' width="' . $currentAttachment['width'] . '"';
1131
							$height = ' height="' . $currentAttachment['height'] . '"';
1132
						}
1133
1134
						if ($currentAttachment['thumbnail']['has_thumb'] && empty($params['{width}']) && empty($params['{height}']))
1135
							$returnContext .= '<a href="'. $currentAttachment['href']. ';image" id="link_'. $currentAttachment['id']. '" onclick="'. $currentAttachment['thumbnail']['javascript']. '"><img src="'. $currentAttachment['thumbnail']['href']. '" alt="' . $currentAttachment['name'] . '" id="thumb_'. $currentAttachment['id']. '"></a>';
1136
						else
1137
							$returnContext .= '<img src="' . $currentAttachment['href'] . ';image" alt="' . $currentAttachment['name'] . '"' . $width . $height . '/>';
1138
					}
1139
1140
					// No image. Show a link.
1141
					else
1142
						$returnContext .= $currentAttachment['link'];
1143
1144
					// Gotta append what we just did.
1145
					$data = $returnContext;
1146
				},
1147
			),
1148
			array(
1149
				'tag' => 'b',
1150
				'before' => '<b>',
1151
				'after' => '</b>',
1152
			),
1153
			array(
1154
				'tag' => 'center',
1155
				'before' => '<div class="centertext">',
1156
				'after' => '</div>',
1157
				'block_level' => true,
1158
			),
1159
			array(
1160
				'tag' => 'code',
1161
				'type' => 'unparsed_content',
1162
				'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>',
1163
				// @todo Maybe this can be simplified?
1164
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1165
				{
1166
					if (!isset($disabled['code']))
1167
					{
1168
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
1169
1170 View Code Duplication
						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1171
						{
1172
							// Do PHP code coloring?
1173
							if ($php_parts[$php_i] != '&lt;?php')
1174
								continue;
1175
1176
							$php_string = '';
1177
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1178
							{
1179
								$php_string .= $php_parts[$php_i];
1180
								$php_parts[$php_i++] = '';
1181
							}
1182
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1183
						}
1184
1185
						// Fix the PHP code stuff...
1186
						$data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1187
						$data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);
1188
1189
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1190
						if ($context['browser']['is_opera'])
1191
							$data .= '&nbsp;';
1192
					}
1193
				},
1194
				'block_level' => true,
1195
			),
1196
			array(
1197
				'tag' => 'code',
1198
				'type' => 'unparsed_equals_content',
1199
				'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>',
1200
				// @todo Maybe this can be simplified?
1201
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1202
				{
1203
					if (!isset($disabled['code']))
1204
					{
1205
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);
1206
1207 View Code Duplication
						for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1208
						{
1209
							// Do PHP code coloring?
1210
							if ($php_parts[$php_i] != '&lt;?php')
1211
								continue;
1212
1213
							$php_string = '';
1214
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1215
							{
1216
								$php_string .= $php_parts[$php_i];
1217
								$php_parts[$php_i++] = '';
1218
							}
1219
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1220
						}
1221
1222
						// Fix the PHP code stuff...
1223
						$data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1224
						$data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]);
1225
1226
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1227
						if ($context['browser']['is_opera'])
1228
							$data[0] .= '&nbsp;';
1229
					}
1230
				},
1231
				'block_level' => true,
1232
			),
1233
			array(
1234
				'tag' => 'color',
1235
				'type' => 'unparsed_equals',
1236
				'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]?)\))\]',
1237
				'before' => '<span style="color: $1;" class="bbc_color">',
1238
				'after' => '</span>',
1239
			),
1240
			array(
1241
				'tag' => 'email',
1242
				'type' => 'unparsed_content',
1243
				'content' => '<a href="mailto:$1" class="bbc_email">$1</a>',
1244
				// @todo Should this respect guest_hideContacts?
1245
				'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...
1246
				{
1247
					$data = strtr($data, array('<br>' => ''));
1248
				},
1249
			),
1250
			array(
1251
				'tag' => 'email',
1252
				'type' => 'unparsed_equals',
1253
				'before' => '<a href="mailto:$1" class="bbc_email">',
1254
				'after' => '</a>',
1255
				// @todo Should this respect guest_hideContacts?
1256
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1257
				'disabled_after' => ' ($1)',
1258
			),
1259
			array(
1260
				'tag' => 'flash',
1261
				'type' => 'unparsed_commas_content',
1262
				'test' => '\d+,\d+\]',
1263
				'content' => '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never">',
1264
				'validate' => function (&$tag, &$data, $disabled)
1265
				{
1266
					if (isset($disabled['url']))
1267
						$tag['content'] = '$1';
1268
					$scheme = parse_url($data[0], PHP_URL_SCHEME);
1269
					if (empty($scheme))
1270
						$data[0] = '//' . ltrim($data[0], ':/');
1271
				},
1272
				'disabled_content' => '<a href="$1" target="_blank" class="new_win">$1</a>',
1273
			),
1274
			array(
1275
				'tag' => 'font',
1276
				'type' => 'unparsed_equals',
1277
				'test' => '[A-Za-z0-9_,\-\s]+?\]',
1278
				'before' => '<span style="font-family: $1;" class="bbc_font">',
1279
				'after' => '</span>',
1280
			),
1281
			array(
1282
				'tag' => 'html',
1283
				'type' => 'unparsed_content',
1284
				'content' => '$1',
1285
				'block_level' => true,
1286
				'disabled_content' => '$1',
1287
			),
1288
			array(
1289
				'tag' => 'hr',
1290
				'type' => 'closed',
1291
				'content' => '<hr>',
1292
				'block_level' => true,
1293
			),
1294
			array(
1295
				'tag' => 'i',
1296
				'before' => '<i>',
1297
				'after' => '</i>',
1298
			),
1299
			array(
1300
				'tag' => 'img',
1301
				'type' => 'unparsed_content',
1302
				'parameters' => array(
1303
					'alt' => array('optional' => true),
1304
					'title' => array('optional' => true),
1305
					'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
1306
					'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
1307
				),
1308
				'content' => '<img src="$1" alt="{alt}" title="{title}"{width}{height} class="bbc_img resized">',
1309 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...
Duplication introduced by
This code seems to be duplicated across 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...
1310
				{
1311
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1312
1313
					$data = strtr($data, array('<br>' => ''));
1314
					$scheme = parse_url($data, PHP_URL_SCHEME);
1315
					if ($image_proxy_enabled)
1316
					{
1317
						if (empty($scheme))
1318
							$data = 'http://' . ltrim($data, ':/');
1319
1320
						if ($scheme != 'https')
1321
							$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1322
					}
1323
					elseif (empty($scheme))
1324
						$data = '//' . ltrim($data, ':/');
1325
				},
1326
				'disabled_content' => '($1)',
1327
			),
1328
			array(
1329
				'tag' => 'img',
1330
				'type' => 'unparsed_content',
1331
				'content' => '<img src="$1" alt="" class="bbc_img">',
1332 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...
Duplication introduced by
This code seems to be duplicated across 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...
1333
				{
1334
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1335
1336
					$data = strtr($data, array('<br>' => ''));
1337
					$scheme = parse_url($data, PHP_URL_SCHEME);
1338
					if ($image_proxy_enabled)
1339
					{
1340
						if (empty($scheme))
1341
							$data = 'http://' . ltrim($data, ':/');
1342
1343
						if ($scheme != 'https')
1344
							$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1345
					}
1346
					elseif (empty($scheme))
1347
						$data = '//' . ltrim($data, ':/');
1348
				},
1349
				'disabled_content' => '($1)',
1350
			),
1351
			array(
1352
				'tag' => 'iurl',
1353
				'type' => 'unparsed_content',
1354
				'content' => '<a href="$1" class="bbc_link">$1</a>',
1355 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...
Duplication introduced by
This code seems to be duplicated across 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...
1356
				{
1357
					$data = strtr($data, array('<br>' => ''));
1358
					$scheme = parse_url($data, PHP_URL_SCHEME);
1359
					if (empty($scheme))
1360
						$data = '//' . ltrim($data, ':/');
1361
				},
1362
			),
1363
			array(
1364
				'tag' => 'iurl',
1365
				'type' => 'unparsed_equals',
1366
				'quoted' => 'optional',
1367
				'before' => '<a href="$1" class="bbc_link">',
1368
				'after' => '</a>',
1369
				'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...
1370
				{
1371
					if (substr($data, 0, 1) == '#')
1372
						$data = '#post_' . substr($data, 1);
1373
					else
1374
					{
1375
						$scheme = parse_url($data, PHP_URL_SCHEME);
1376
						if (empty($scheme))
1377
							$data = '//' . ltrim($data, ':/');
1378
					}
1379
				},
1380
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1381
				'disabled_after' => ' ($1)',
1382
			),
1383
			array(
1384
				'tag' => 'left',
1385
				'before' => '<div style="text-align: left;">',
1386
				'after' => '</div>',
1387
				'block_level' => true,
1388
			),
1389
			array(
1390
				'tag' => 'li',
1391
				'before' => '<li>',
1392
				'after' => '</li>',
1393
				'trim' => 'outside',
1394
				'require_parents' => array('list'),
1395
				'block_level' => true,
1396
				'disabled_before' => '',
1397
				'disabled_after' => '<br>',
1398
			),
1399
			array(
1400
				'tag' => 'list',
1401
				'before' => '<ul class="bbc_list">',
1402
				'after' => '</ul>',
1403
				'trim' => 'inside',
1404
				'require_children' => array('li', 'list'),
1405
				'block_level' => true,
1406
			),
1407
			array(
1408
				'tag' => 'list',
1409
				'parameters' => array(
1410
					'type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)'),
1411
				),
1412
				'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
1413
				'after' => '</ul>',
1414
				'trim' => 'inside',
1415
				'require_children' => array('li'),
1416
				'block_level' => true,
1417
			),
1418
			array(
1419
				'tag' => 'ltr',
1420
				'before' => '<bdo dir="ltr">',
1421
				'after' => '</bdo>',
1422
				'block_level' => true,
1423
			),
1424
			array(
1425
				'tag' => 'me',
1426
				'type' => 'unparsed_equals',
1427
				'before' => '<div class="meaction">* $1 ',
1428
				'after' => '</div>',
1429
				'quoted' => 'optional',
1430
				'block_level' => true,
1431
				'disabled_before' => '/me ',
1432
				'disabled_after' => '<br>',
1433
			),
1434
			array(
1435
				'tag' => 'member',
1436
				'type' => 'unparsed_equals',
1437
				'before' => '<a href="' . $scripturl . '?action=profile;u=$1" class="mention" data-mention="$1">@',
1438
				'after' => '</a>',
1439
			),
1440
			array(
1441
				'tag' => 'nobbc',
1442
				'type' => 'unparsed_content',
1443
				'content' => '$1',
1444
			),
1445
			array(
1446
				'tag' => 'php',
1447
				'type' => 'unparsed_content',
1448
				'content' => '<span class="phpcode">$1</span>',
1449
				'validate' => isset($disabled['php']) ? null : function (&$tag, &$data, $disabled)
1450
				{
1451
					if (!isset($disabled['php']))
1452
					{
1453
						$add_begin = substr(trim($data), 0, 5) != '&lt;?';
1454
						$data = highlight_php_code($add_begin ? '&lt;?php ' . $data . '?&gt;' : $data);
1455
						if ($add_begin)
1456
							$data = preg_replace(array('~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~', '~\?&gt;((?:</(font|span)>)*)$~'), '$1', $data, 2);
1457
					}
1458
				},
1459
				'block_level' => false,
1460
				'disabled_content' => '$1',
1461
			),
1462
			array(
1463
				'tag' => 'pre',
1464
				'before' => '<pre>',
1465
				'after' => '</pre>',
1466
			),
1467
			array(
1468
				'tag' => 'quote',
1469
				'before' => '<blockquote><cite>' . $txt['quote'] . '</cite>',
1470
				'after' => '</blockquote>',
1471
				'trim' => 'both',
1472
				'block_level' => true,
1473
			),
1474
			array(
1475
				'tag' => 'quote',
1476
				'parameters' => array(
1477
					'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
1478
				),
1479
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1480
				'after' => '</blockquote>',
1481
				'trim' => 'both',
1482
				'block_level' => true,
1483
			),
1484
			array(
1485
				'tag' => 'quote',
1486
				'type' => 'parsed_equals',
1487
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': $1</cite>',
1488
				'after' => '</blockquote>',
1489
				'trim' => 'both',
1490
				'quoted' => 'optional',
1491
				// Don't allow everything to be embedded with the author name.
1492
				'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
1493
				'block_level' => true,
1494
			),
1495
			array(
1496
				'tag' => 'quote',
1497
				'parameters' => array(
1498
					'author' => array('match' => '([^<>]{1,192}?)'),
1499
					'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|msg=\d+?|action=profile;u=\d+)'),
1500
					'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
1501
				),
1502
				'before' => '<blockquote><cite><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></cite>',
1503
				'after' => '</blockquote>',
1504
				'trim' => 'both',
1505
				'block_level' => true,
1506
			),
1507
			array(
1508
				'tag' => 'quote',
1509
				'parameters' => array(
1510
					'author' => array('match' => '(.{1,192}?)'),
1511
				),
1512
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1513
				'after' => '</blockquote>',
1514
				'trim' => 'both',
1515
				'block_level' => true,
1516
			),
1517
			array(
1518
				'tag' => 'right',
1519
				'before' => '<div style="text-align: right;">',
1520
				'after' => '</div>',
1521
				'block_level' => true,
1522
			),
1523
			array(
1524
				'tag' => 'rtl',
1525
				'before' => '<bdo dir="rtl">',
1526
				'after' => '</bdo>',
1527
				'block_level' => true,
1528
			),
1529
			array(
1530
				'tag' => 's',
1531
				'before' => '<s>',
1532
				'after' => '</s>',
1533
			),
1534
			array(
1535
				'tag' => 'size',
1536
				'type' => 'unparsed_equals',
1537
				'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]',
1538
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1539
				'after' => '</span>',
1540
			),
1541
			array(
1542
				'tag' => 'size',
1543
				'type' => 'unparsed_equals',
1544
				'test' => '[1-7]\]',
1545
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1546
				'after' => '</span>',
1547
				'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...
1548
				{
1549
					$sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
1550
					$data = $sizes[$data] . 'em';
1551
				},
1552
			),
1553
			array(
1554
				'tag' => 'sub',
1555
				'before' => '<sub>',
1556
				'after' => '</sub>',
1557
			),
1558
			array(
1559
				'tag' => 'sup',
1560
				'before' => '<sup>',
1561
				'after' => '</sup>',
1562
			),
1563
			array(
1564
				'tag' => 'table',
1565
				'before' => '<table class="bbc_table">',
1566
				'after' => '</table>',
1567
				'trim' => 'inside',
1568
				'require_children' => array('tr'),
1569
				'block_level' => true,
1570
			),
1571
			array(
1572
				'tag' => 'td',
1573
				'before' => '<td>',
1574
				'after' => '</td>',
1575
				'require_parents' => array('tr'),
1576
				'trim' => 'outside',
1577
				'block_level' => true,
1578
				'disabled_before' => '',
1579
				'disabled_after' => '',
1580
			),
1581
			array(
1582
				'tag' => 'time',
1583
				'type' => 'unparsed_content',
1584
				'content' => '$1',
1585
				'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...
1586
				{
1587
					if (is_numeric($data))
1588
						$data = timeformat($data);
1589
					else
1590
						$tag['content'] = '[time]$1[/time]';
1591
				},
1592
			),
1593
			array(
1594
				'tag' => 'tr',
1595
				'before' => '<tr>',
1596
				'after' => '</tr>',
1597
				'require_parents' => array('table'),
1598
				'require_children' => array('td'),
1599
				'trim' => 'both',
1600
				'block_level' => true,
1601
				'disabled_before' => '',
1602
				'disabled_after' => '',
1603
			),
1604
			array(
1605
				'tag' => 'u',
1606
				'before' => '<u>',
1607
				'after' => '</u>',
1608
			),
1609
			array(
1610
				'tag' => 'url',
1611
				'type' => 'unparsed_content',
1612
				'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>',
1613 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...
Duplication introduced by
This code seems to be duplicated across 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...
1614
				{
1615
					$data = strtr($data, array('<br>' => ''));
1616
					$scheme = parse_url($data, PHP_URL_SCHEME);
1617
					if (empty($scheme))
1618
						$data = '//' . ltrim($data, ':/');
1619
				},
1620
			),
1621
			array(
1622
				'tag' => 'url',
1623
				'type' => 'unparsed_equals',
1624
				'quoted' => 'optional',
1625
				'before' => '<a href="$1" class="bbc_link" target="_blank">',
1626
				'after' => '</a>',
1627
				'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...
1628
				{
1629
					$scheme = parse_url($data, PHP_URL_SCHEME);
1630
					if (empty($scheme))
1631
						$data = '//' . ltrim($data, ':/');
1632
				},
1633
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1634
				'disabled_after' => ' ($1)',
1635
			),
1636
		);
1637
1638
		// Inside these tags autolink is not recommendable.
1639
		$no_autolink_tags = array(
1640
			'url',
1641
			'iurl',
1642
			'email',
1643
		);
1644
1645
		// Let mods add new BBC without hassle.
1646
		call_integration_hook('integrate_bbc_codes', array(&$codes, &$no_autolink_tags));
1647
1648
		// This is mainly for the bbc manager, so it's easy to add tags above.  Custom BBC should be added above this line.
1649
		if ($message === false)
1650
		{
1651
			if (isset($temp_bbc))
1652
				$bbc_codes = $temp_bbc;
1653
			usort($codes, function ($a, $b) {
1654
				return strcmp($a['tag'], $b['tag']);
1655
			});
1656
			return $codes;
1657
		}
1658
1659
		// So the parser won't skip them.
1660
		$itemcodes = array(
1661
			'*' => 'disc',
1662
			'@' => 'disc',
1663
			'+' => 'square',
1664
			'x' => 'square',
1665
			'#' => 'square',
1666
			'o' => 'circle',
1667
			'O' => 'circle',
1668
			'0' => 'circle',
1669
		);
1670
		if (!isset($disabled['li']) && !isset($disabled['list']))
1671
		{
1672
			foreach ($itemcodes as $c => $dummy)
1673
				$bbc_codes[$c] = array();
1674
		}
1675
1676
		// Shhhh!
1677
		if (!isset($disabled['color']))
1678
		{
1679
			$codes[] = array(
1680
				'tag' => 'chrissy',
1681
				'before' => '<span style="color: #cc0099;">',
1682
				'after' => ' :-*</span>',
1683
			);
1684
			$codes[] = array(
1685
				'tag' => 'kissy',
1686
				'before' => '<span style="color: #cc0099;">',
1687
				'after' => ' :-*</span>',
1688
			);
1689
		}
1690
1691
		foreach ($codes as $code)
1692
		{
1693
			// Make it easier to process parameters later
1694
			if (!empty($code['parameters']))
1695
				ksort($code['parameters'], SORT_STRING);
1696
1697
			// If we are not doing every tag only do ones we are interested in.
1698
			if (empty($parse_tags) || in_array($code['tag'], $parse_tags))
1699
				$bbc_codes[substr($code['tag'], 0, 1)][] = $code;
1700
		}
1701
		$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...
1702
	}
1703
1704
	// Shall we take the time to cache this?
1705
	if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && isset($message[1000])) || isset($message[2400])) && empty($parse_tags))
1706
	{
1707
		// It's likely this will change if the message is modified.
1708
		$cache_key = 'parse:' . $cache_id . '-' . md5(md5($message) . '-' . $smileys . (empty($disabled) ? '' : implode(',', array_keys($disabled))) . json_encode($context['browser']) . $txt['lang_locale'] . $user_info['time_offset'] . $user_info['time_format']);
1709
1710
		if (($temp = cache_get_data($cache_key, 240)) != null)
1711
			return $temp;
1712
1713
		$cache_t = microtime();
1714
	}
1715
1716
	if ($smileys === 'print')
1717
	{
1718
		// [glow], [shadow], and [move] can't really be printed.
1719
		$disabled['glow'] = true;
1720
		$disabled['shadow'] = true;
1721
		$disabled['move'] = true;
1722
1723
		// Colors can't well be displayed... supposed to be black and white.
1724
		$disabled['color'] = true;
1725
		$disabled['black'] = true;
1726
		$disabled['blue'] = true;
1727
		$disabled['white'] = true;
1728
		$disabled['red'] = true;
1729
		$disabled['green'] = true;
1730
		$disabled['me'] = true;
1731
1732
		// Color coding doesn't make sense.
1733
		$disabled['php'] = true;
1734
1735
		// Links are useless on paper... just show the link.
1736
		$disabled['ftp'] = true;
1737
		$disabled['url'] = true;
1738
		$disabled['iurl'] = true;
1739
		$disabled['email'] = true;
1740
		$disabled['flash'] = true;
1741
1742
		// @todo Change maybe?
1743
		if (!isset($_GET['images']))
1744
			$disabled['img'] = true;
1745
1746
		// @todo Interface/setting to add more?
1747
	}
1748
1749
	$open_tags = array();
1750
	$message = strtr($message, array("\n" => '<br>'));
1751
1752
	foreach ($bbc_codes as $section) {
1753
		foreach ($section as $code) {
1754
			$alltags[] = $code['tag'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$alltags was never initialized. Although not strictly required by PHP, it is generally a good practice to add $alltags = 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...
1755
		}
1756
	}
1757
	$alltags_regex = '\b' . implode("\b|\b", array_unique($alltags)) . '\b';
0 ignored issues
show
Bug introduced by
The variable $alltags 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...
1758
1759
	// The non-breaking-space looks a bit different each time.
1760
	$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0';
0 ignored issues
show
Unused Code introduced by
$non_breaking_space 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...
1761
1762
	$pos = -1;
1763
	while ($pos !== false)
1764
	{
1765
		$last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
1766
		preg_match('~\[/?(?=' . $alltags_regex . ')~', $message, $matches, PREG_OFFSET_CAPTURE, $pos + 1);
1767
		$pos = isset($matches[0][1]) ? $matches[0][1] : false;
1768
1769
		// Failsafe.
1770
		if ($pos === false || $last_pos > $pos)
1771
			$pos = strlen($message) + 1;
1772
1773
		// Can't have a one letter smiley, URL, or email! (sorry.)
1774
		if ($last_pos < $pos - 1)
1775
		{
1776
			// Make sure the $last_pos is not negative.
1777
			$last_pos = max($last_pos, 0);
1778
1779
			// Pick a block of data to do some raw fixing on.
1780
			$data = substr($message, $last_pos, $pos - $last_pos);
1781
1782
			// Take care of some HTML!
1783
			if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false)
1784
			{
1785
				$data = preg_replace('~&lt;a\s+href=((?:&quot;)?)((?:https?://|ftps?://|mailto:)\S+?)\\1&gt;~i', '[url=&quot;$2&quot;]', $data);
1786
				$data = preg_replace('~&lt;/a&gt;~i', '[/url]', $data);
1787
1788
				// <br> should be empty.
1789
				$empty_tags = array('br', 'hr');
1790
				foreach ($empty_tags as $tag)
1791
					$data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '[' . $tag . ' /]', $data);
1792
1793
				// b, u, i, s, pre... basic tags.
1794
				$closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote');
1795
				foreach ($closable_tags as $tag)
1796
				{
1797
					$diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
1798
					$data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
1799
1800
					if ($diff > 0)
1801
						$data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
1802
				}
1803
1804
				// Do <img ...> - with security... action= -> action-.
1805
				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);
1806
				if (!empty($matches[0]))
1807
				{
1808
					$replaces = array();
1809
					foreach ($matches[2] as $match => $imgtag)
1810
					{
1811
						$alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
1812
1813
						// Remove action= from the URL - no funny business, now.
1814
						if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0)
1815
							$imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
1816
1817
						// Check if the image is larger than allowed.
1818
						if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height']))
1819
						{
1820
							list ($width, $height) = url_image_size($imgtag);
1821
1822 View Code Duplication
							if (!empty($modSettings['max_image_width']) && $width > $modSettings['max_image_width'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1823
							{
1824
								$height = (int) (($modSettings['max_image_width'] * $height) / $width);
1825
								$width = $modSettings['max_image_width'];
1826
							}
1827
1828 View Code Duplication
							if (!empty($modSettings['max_image_height']) && $height > $modSettings['max_image_height'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1829
							{
1830
								$width = (int) (($modSettings['max_image_height'] * $width) / $height);
1831
								$height = $modSettings['max_image_height'];
1832
							}
1833
1834
							// Set the new image tag.
1835
							$replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
1836
						}
1837
						else
1838
							$replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
1839
					}
1840
1841
					$data = strtr($data, $replaces);
1842
				}
1843
			}
1844
1845
			if (!empty($modSettings['autoLinkUrls']))
1846
			{
1847
				// Are we inside tags that should be auto linked?
1848
				$no_autolink_area = false;
1849
				if (!empty($open_tags))
1850
				{
1851
					foreach ($open_tags as $open_tag)
1852
						if (in_array($open_tag['tag'], $no_autolink_tags))
1853
							$no_autolink_area = true;
1854
				}
1855
1856
				// Don't go backwards.
1857
				// @todo Don't think is the real solution....
1858
				$lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
1859
				if ($pos < $lastAutoPos)
1860
					$no_autolink_area = true;
1861
				$lastAutoPos = $pos;
1862
1863
				if (!$no_autolink_area)
1864
				{
1865
					// Parse any URLs
1866
					if (!isset($disabled['url']) && strpos($data, '[url') === false)
1867
					{
1868
						$url_regex = '
1869
						(?:
1870
							# IRIs with a scheme (or at least an opening "//")
1871
							(?:
1872
								# URI scheme (or lack thereof for schemeless URLs)
1873
								(?:
1874
									# URL scheme and colon
1875
									\b[a-z][\w\-]+:
1876
									| # or
1877
									# A boundary followed by two slashes for schemeless URLs
1878
									(?<=^|\W)(?=//)
1879
								)
1880
1881
								# IRI "authority" chunk
1882
								(?:
1883
									# 2 slashes for IRIs with an "authority"
1884
									//
1885
									# then a domain name
1886
									(?:
1887
										# Either the reserved "localhost" domain name
1888
										localhost
1889
										| # or
1890
										# a run of Unicode domain name characters and a dot
1891
										[\p{L}\p{M}\p{N}\-.:@]+\.
1892
										# and then a TLD valid in the DNS or the reserved "local" TLD
1893
										(?:'. $modSettings['tld_regex'] .'|local)
1894
									)
1895
									# followed by a non-domain character or end of line
1896
									(?=[^\p{L}\p{N}\-.]|$)
1897
1898
									| # Or, if there is no "authority" per se (e.g. mailto: URLs) ...
1899
1900
									# a run of IRI characters
1901
									[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.:@]+[\p{L}\p{M}\p{N}]
1902
									# and then a dot and a closing IRI label
1903
									\.[\p{L}\p{M}\p{N}\-]+
1904
								)
1905
							)
1906
1907
							| # or
1908
1909
							# Naked domains (e.g. "example.com" in "Go to example.com for an example.")
1910
							(?:
1911
								# Preceded by start of line or a non-domain character
1912
								(?<=^|[^\p{L}\p{M}\p{N}\-:@])
1913
1914
								# A run of Unicode domain name characters (excluding [:@])
1915
								[\p{L}\p{N}][\p{L}\p{M}\p{N}\-.]+[\p{L}\p{M}\p{N}]
1916
								# and then a dot and a valid TLD
1917
								\.' . $modSettings['tld_regex'] . '
1918
1919
								# Followed by either:
1920
								(?=
1921
									# end of line or a non-domain character (excluding [.:@])
1922
									$|[^\p{L}\p{N}\-]
1923
									| # or
1924
									# a dot followed by end of line or a non-domain character (excluding [.:@])
1925
									\.(?=$|[^\p{L}\p{N}\-])
1926
								)
1927
							)
1928
						)
1929
1930
						# IRI path, query, and fragment (if present)
1931
						(?:
1932
							# If any of these parts exist, must start with a single /
1933
							/
1934
1935
							# And then optionally:
1936
							(?:
1937
								# One or more of:
1938
								(?:
1939
									# a run of non-space, non-()<>
1940
									[^\s()<>]+
1941
									| # or
1942
									# balanced parens, up to 2 levels
1943
									\(([^\s()<>]+|(\([^\s()<>]+\)))*\)
1944
								)+
1945
1946
								# End with:
1947
								(?:
1948
									# balanced parens, up to 2 levels
1949
									\(([^\s()<>]+|(\([^\s()<>]+\)))*\)
1950
									| # or
1951
									# not a space or one of these punct char
1952
									[^\s`!()\[\]{};:\'".,<>?«»“”‘’/]
1953
									| # or
1954
									# a trailing slash (but not two in a row)
1955
									(?<!/)/
1956
								)
1957
							)?
1958
						)?
1959
						';
1960
1961
						$data = preg_replace_callback('~' . $url_regex . '~xi' . ($context['utf8'] ? 'u' : ''), function ($matches) {
1962
							$url = array_shift($matches);
1963
1964
							$scheme = parse_url($url, PHP_URL_SCHEME);
1965
1966
							if ($scheme == 'mailto')
1967
							{
1968
								$email_address = str_replace('mailto:', '', $url);
1969
								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...
1970
									return '[email=' . $email_address . ']' . $url . '[/email]';
1971
								else
1972
									return $url;
1973
							}
1974
1975
							// Are we linking a schemeless URL or naked domain name (e.g. "example.com")?
1976
							if (empty($scheme))
1977
								$fullUrl = '//' . ltrim($url, ':/');
1978
							else
1979
								$fullUrl = $url;
1980
1981
							return '[url=&quot;' . str_replace(array('[', ']'), array('&#91;', '&#93;'), $fullUrl) . '&quot;]' . $url . '[/url]';
1982
						}, $data);
1983
					}
1984
1985
					// Next, emails...
1986
					if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false)
1987
					{
1988
						$email_regex = '
1989
						# Preceded by a non-domain character or start of line
1990
						(?<=^|[^\p{L}\p{M}\p{N}\-\.])
1991
1992
						# An email address
1993
						[\p{L}\p{M}\p{N}_\-.]{1,80}
1994
						@
1995
						[\p{L}\p{M}\p{N}\-.]+
1996
						\.
1997
						'. $modSettings['tld_regex'] . '
1998
1999
						# Followed by either:
2000
						(?=
2001
							# end of line or a non-domain character (excluding the dot)
2002
							$|[^\p{L}\p{M}\p{N}\-]
2003
							| # or
2004
							# a dot followed by end of line or a non-domain character
2005
							\.(?=$|[^\p{L}\p{M}\p{N}\-])
2006
						)';
2007
2008
						$data = preg_replace('~' . $email_regex . '~xi' . ($context['utf8'] ? 'u' : ''), '[email]$0[/email]', $data);
2009
					}
2010
				}
2011
			}
2012
2013
			$data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
2014
2015
			// If it wasn't changed, no copying or other boring stuff has to happen!
2016
			if ($data != substr($message, $last_pos, $pos - $last_pos))
2017
			{
2018
				$message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
2019
2020
				// Since we changed it, look again in case we added or removed a tag.  But we don't want to skip any.
2021
				$old_pos = strlen($data) + $last_pos;
2022
				$pos = strpos($message, '[', $last_pos);
2023
				$pos = $pos === false ? $old_pos : min($pos, $old_pos);
2024
			}
2025
		}
2026
2027
		// Are we there yet?  Are we there yet?
2028
		if ($pos >= strlen($message) - 1)
2029
			break;
2030
2031
		$tags = strtolower($message[$pos + 1]);
2032
2033
		if ($tags == '/' && !empty($open_tags))
2034
		{
2035
			$pos2 = strpos($message, ']', $pos + 1);
2036
			if ($pos2 == $pos + 2)
2037
				continue;
2038
2039
			$look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
2040
2041
			$to_close = array();
2042
			$block_level = null;
2043
2044
			do
2045
			{
2046
				$tag = array_pop($open_tags);
2047
				if (!$tag)
2048
					break;
2049
2050
				if (!empty($tag['block_level']))
2051
				{
2052
					// Only find out if we need to.
2053
					if ($block_level === false)
2054
					{
2055
						array_push($open_tags, $tag);
2056
						break;
2057
					}
2058
2059
					// The idea is, if we are LOOKING for a block level tag, we can close them on the way.
2060 View Code Duplication
					if (strlen($look_for) > 0 && isset($bbc_codes[$look_for[0]]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2061
					{
2062
						foreach ($bbc_codes[$look_for[0]] as $temp)
2063
							if ($temp['tag'] == $look_for)
2064
							{
2065
								$block_level = !empty($temp['block_level']);
2066
								break;
2067
							}
2068
					}
2069
2070
					if ($block_level !== true)
2071
					{
2072
						$block_level = false;
2073
						array_push($open_tags, $tag);
2074
						break;
2075
					}
2076
				}
2077
2078
				$to_close[] = $tag;
2079
			}
2080
			while ($tag['tag'] != $look_for);
2081
2082
			// Did we just eat through everything and not find it?
2083
			if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)))
2084
			{
2085
				$open_tags = $to_close;
2086
				continue;
2087
			}
2088
			elseif (!empty($to_close) && $tag['tag'] != $look_for)
2089
			{
2090 View Code Duplication
				if ($block_level === null && isset($look_for[0], $bbc_codes[$look_for[0]]))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2091
				{
2092
					foreach ($bbc_codes[$look_for[0]] as $temp)
2093
						if ($temp['tag'] == $look_for)
2094
						{
2095
							$block_level = !empty($temp['block_level']);
2096
							break;
2097
						}
2098
				}
2099
2100
				// We're not looking for a block level tag (or maybe even a tag that exists...)
2101
				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...
2102
				{
2103
					foreach ($to_close as $tag)
2104
						array_push($open_tags, $tag);
2105
					continue;
2106
				}
2107
			}
2108
2109
			foreach ($to_close as $tag)
2110
			{
2111
				$message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
2112
				$pos += strlen($tag['after']) + 2;
2113
				$pos2 = $pos - 1;
2114
2115
				// See the comment at the end of the big loop - just eating whitespace ;).
2116 View Code Duplication
				if (!empty($tag['block_level']) && substr($message, $pos, 4) == '<br>')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2117
					$message = substr($message, 0, $pos) . substr($message, $pos + 4);
2118 View Code Duplication
				if (!empty($tag['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos), $matches) != 0)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2119
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2120
			}
2121
2122
			if (!empty($to_close))
2123
			{
2124
				$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...
2125
				$pos--;
2126
			}
2127
2128
			continue;
2129
		}
2130
2131
		// No tags for this character, so just keep going (fastest possible course.)
2132
		if (!isset($bbc_codes[$tags]))
2133
			continue;
2134
2135
		$inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
2136
		$tag = null;
2137
		foreach ($bbc_codes[$tags] as $possible)
2138
		{
2139
			$pt_strlen = strlen($possible['tag']);
2140
2141
			// Not a match?
2142
			if (strtolower(substr($message, $pos + 1, $pt_strlen)) != $possible['tag'])
2143
				continue;
2144
2145
			$next_c = $message[$pos + 1 + $pt_strlen];
2146
2147
			// A test validation?
2148
			if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + $pt_strlen + 1)) === 0)
2149
				continue;
2150
			// Do we want parameters?
2151
			elseif (!empty($possible['parameters']))
2152
			{
2153
				if ($next_c != ' ')
2154
					continue;
2155
			}
2156
			elseif (isset($possible['type']))
2157
			{
2158
				// Do we need an equal sign?
2159
				if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=')
2160
					continue;
2161
				// Maybe we just want a /...
2162
				if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + $pt_strlen, 2) != '/]' && substr($message, $pos + 1 + $pt_strlen, 3) != ' /]')
2163
					continue;
2164
				// An immediate ]?
2165
				if ($possible['type'] == 'unparsed_content' && $next_c != ']')
2166
					continue;
2167
			}
2168
			// No type means 'parsed_content', which demands an immediate ] without parameters!
2169
			elseif ($next_c != ']')
2170
				continue;
2171
2172
			// Check allowed tree?
2173
			if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents'])))
2174
				continue;
2175
			elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children']))
2176
				continue;
2177
			// If this is in the list of disallowed child tags, don't parse it.
2178
			elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children']))
2179
				continue;
2180
2181
			$pos1 = $pos + 1 + $pt_strlen + 1;
2182
2183
			// Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
2184
			if ($possible['tag'] == 'quote')
2185
			{
2186
				// Start with standard
2187
				$quote_alt = false;
2188
				foreach ($open_tags as $open_quote)
2189
				{
2190
					// Every parent quote this quote has flips the styling
2191
					if ($open_quote['tag'] == 'quote')
2192
						$quote_alt = !$quote_alt;
2193
				}
2194
				// Add a class to the quote to style alternating blockquotes
2195
				$possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
2196
			}
2197
2198
			// This is long, but it makes things much easier and cleaner.
2199
			if (!empty($possible['parameters']))
2200
			{
2201
				// Build a regular expression for each parameter for the current tag.
2202
				$preg = array();
2203
				foreach ($possible['parameters'] as $p => $info)
2204
					$preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . '\s*)' . (empty($info['optional']) ? '' : '?');
2205
2206
				// Extract the string that potentially holds our parameters.
2207
				$blob = preg_split('~\[/?(?:' . $alltags_regex . ')~i', substr($message, $pos));
2208
				$blobs = preg_split('~\]~i', $blob[1]);
2209
2210
				$splitters = implode('=|', array_keys($possible['parameters'])) . '=';
2211
2212
				// Progressively append more blobs until we find our parameters or run out of blobs
2213
				$blob_counter = 1;
2214
				while ($blob_counter <= count($blobs))
2215
				{
2216
2217
					$given_param_string = implode(']', array_slice($blobs, 0, $blob_counter++));
2218
2219
					$given_params = preg_split('~\s(?=(' . $splitters . '))~i', $given_param_string);
2220
					sort($given_params, SORT_STRING);
2221
2222
					$match = preg_match('~^' . implode('', $preg) . '$~i', implode(' ', $given_params), $matches) !== 0;
2223
2224
					if ($match)
2225
						$blob_counter = count($blobs) + 1;
2226
				}
2227
2228
				// Didn't match our parameter list, try the next possible.
2229
				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...
2230
					continue;
2231
2232
				$params = array();
2233
				for ($i = 1, $n = count($matches); $i < $n; $i += 2)
2234
				{
2235
					$key = strtok(ltrim($matches[$i]), '=');
2236
					if (isset($possible['parameters'][$key]['value']))
2237
						$params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
2238
					elseif (isset($possible['parameters'][$key]['validate']))
2239
						$params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
2240
					else
2241
						$params['{' . $key . '}'] = $matches[$i + 1];
2242
2243
					// Just to make sure: replace any $ or { so they can't interpolate wrongly.
2244
					$params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
2245
				}
2246
2247
				foreach ($possible['parameters'] as $p => $info)
2248
				{
2249
					if (!isset($params['{' . $p . '}']))
2250
						$params['{' . $p . '}'] = '';
2251
				}
2252
2253
				$tag = $possible;
2254
2255
				// Put the parameters into the string.
2256
				if (isset($tag['before']))
2257
					$tag['before'] = strtr($tag['before'], $params);
2258
				if (isset($tag['after']))
2259
					$tag['after'] = strtr($tag['after'], $params);
2260
				if (isset($tag['content']))
2261
					$tag['content'] = strtr($tag['content'], $params);
2262
2263
				$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...
2264
			}
2265
			else
2266
			{
2267
				$tag = $possible;
2268
				$params = array();
2269
			}
2270
			break;
2271
		}
2272
2273
		// Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
2274
		if ($smileys !== false && $tag === null && isset($itemcodes[$message[$pos + 1]]) && $message[$pos + 2] == ']' && !isset($disabled['list']) && !isset($disabled['li']))
2275
		{
2276
			if ($message[$pos + 1] == '0' && !in_array($message[$pos - 1], array(';', ' ', "\t", "\n", '>')))
2277
				continue;
2278
2279
			$tag = $itemcodes[$message[$pos + 1]];
2280
2281
			// First let's set up the tree: it needs to be in a list, or after an li.
2282
			if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li'))
2283
			{
2284
				$open_tags[] = array(
2285
					'tag' => 'list',
2286
					'after' => '</ul>',
2287
					'block_level' => true,
2288
					'require_children' => array('li'),
2289
					'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2290
				);
2291
				$code = '<ul class="bbc_list">';
2292
			}
2293
			// We're in a list item already: another itemcode?  Close it first.
2294
			elseif ($inside['tag'] == 'li')
2295
			{
2296
				array_pop($open_tags);
2297
				$code = '</li>';
2298
			}
2299
			else
2300
				$code = '';
2301
2302
			// Now we open a new tag.
2303
			$open_tags[] = array(
2304
				'tag' => 'li',
2305
				'after' => '</li>',
2306
				'trim' => 'outside',
2307
				'block_level' => true,
2308
				'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2309
			);
2310
2311
			// First, open the tag...
2312
			$code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>';
2313
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
2314
			$pos += strlen($code) - 1 + 2;
2315
2316
			// Next, find the next break (if any.)  If there's more itemcode after it, keep it going - otherwise close!
2317
			$pos2 = strpos($message, '<br>', $pos);
2318
			$pos3 = strpos($message, '[/', $pos);
2319
			if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false))
2320
			{
2321
				preg_match('~^(<br>|&nbsp;|\s|\[)+~', substr($message, $pos2 + 4), $matches);
2322
				$message = substr($message, 0, $pos2) . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . substr($message, $pos2);
2323
2324
				$open_tags[count($open_tags) - 2]['after'] = '</ul>';
2325
			}
2326
			// Tell the [list] that it needs to close specially.
2327
			else
2328
			{
2329
				// Move the li over, because we're not sure what we'll hit.
2330
				$open_tags[count($open_tags) - 1]['after'] = '';
2331
				$open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
2332
			}
2333
2334
			continue;
2335
		}
2336
2337
		// Implicitly close lists and tables if something other than what's required is in them.  This is needed for itemcode.
2338
		if ($tag === null && $inside !== null && !empty($inside['require_children']))
2339
		{
2340
			array_pop($open_tags);
2341
2342
			$message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
2343
			$pos += strlen($inside['after']) - 1 + 2;
2344
		}
2345
2346
		// No tag?  Keep looking, then.  Silly people using brackets without actual tags.
2347
		if ($tag === null)
2348
			continue;
2349
2350
		// Propagate the list to the child (so wrapping the disallowed tag won't work either.)
2351
		if (isset($inside['disallow_children']))
2352
			$tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
2353
2354
		// Is this tag disabled?
2355
		if (isset($disabled[$tag['tag']]))
2356
		{
2357
			if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content']))
2358
			{
2359
				$tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
2360
				$tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
2361
				$tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
2362
			}
2363
			elseif (isset($tag['disabled_before']) || isset($tag['disabled_after']))
2364
			{
2365
				$tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
2366
				$tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
2367
			}
2368
			else
2369
				$tag['content'] = $tag['disabled_content'];
2370
		}
2371
2372
		// we use this a lot
2373
		$tag_strlen = strlen($tag['tag']);
2374
2375
		// The only special case is 'html', which doesn't need to close things.
2376
		if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level']))
2377
		{
2378
			$n = count($open_tags) - 1;
2379
			while (empty($open_tags[$n]['block_level']) && $n >= 0)
2380
				$n--;
2381
2382
			// Close all the non block level tags so this tag isn't surrounded by them.
2383
			for ($i = count($open_tags) - 1; $i > $n; $i--)
2384
			{
2385
				$message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
2386
				$ot_strlen = strlen($open_tags[$i]['after']);
2387
				$pos += $ot_strlen + 2;
2388
				$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...
2389
2390
				// Trim or eat trailing stuff... see comment at the end of the big loop.
2391 View Code Duplication
				if (!empty($open_tags[$i]['block_level']) && substr($message, $pos, 4) == '<br>')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2392
					$message = substr($message, 0, $pos) . substr($message, $pos + 4);
2393 View Code Duplication
				if (!empty($open_tags[$i]['trim']) && $tag['trim'] != 'inside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos), $matches) != 0)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2394
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2395
2396
				array_pop($open_tags);
2397
			}
2398
		}
2399
2400
		// No type means 'parsed_content'.
2401
		if (!isset($tag['type']))
2402
		{
2403
			// @todo Check for end tag first, so people can say "I like that [i] tag"?
2404
			$open_tags[] = $tag;
2405
			$message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
2406
			$pos += strlen($tag['before']) - 1 + 2;
2407
		}
2408
		// Don't parse the content, just skip it.
2409
		elseif ($tag['type'] == 'unparsed_content')
2410
		{
2411
			$pos2 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos1);
2412
			if ($pos2 === false)
2413
				continue;
2414
2415
			$data = substr($message, $pos1, $pos2 - $pos1);
2416
2417
			if (!empty($tag['block_level']) && substr($data, 0, 4) == '<br>')
2418
				$data = substr($data, 4);
2419
2420
			if (isset($tag['validate']))
2421
				$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...
2422
2423
			$code = strtr($tag['content'], array('$1' => $data));
2424
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + $tag_strlen);
2425
2426
			$pos += strlen($code) - 1 + 2;
2427
			$last_pos = $pos + 1;
2428
2429
		}
2430
		// Don't parse the content, just skip it.
2431
		elseif ($tag['type'] == 'unparsed_equals_content')
2432
		{
2433
			// The value may be quoted for some tags - check.
2434 View Code Duplication
			if (isset($tag['quoted']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2435
			{
2436
				$quoted = substr($message, $pos1, 6) == '&quot;';
2437
				if ($tag['quoted'] != 'optional' && !$quoted)
2438
					continue;
2439
2440
				if ($quoted)
2441
					$pos1 += 6;
2442
			}
2443
			else
2444
				$quoted = false;
2445
2446
			$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...
2447
			if ($pos2 === false)
2448
				continue;
2449
2450
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2451
			if ($pos3 === false)
2452
				continue;
2453
2454
			$data = array(
2455
				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...
2456
				substr($message, $pos1, $pos2 - $pos1)
2457
			);
2458
2459
			if (!empty($tag['block_level']) && substr($data[0], 0, 4) == '<br>')
2460
				$data[0] = substr($data[0], 4);
2461
2462
			// Validation for my parking, please!
2463
			if (isset($tag['validate']))
2464
				$tag['validate']($tag, $data, $disabled, $params);
2465
2466
			$code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
2467
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2468
			$pos += strlen($code) - 1 + 2;
2469
		}
2470
		// A closed tag, with no content or value.
2471
		elseif ($tag['type'] == 'closed')
2472
		{
2473
			$pos2 = strpos($message, ']', $pos);
2474
			$message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
2475
			$pos += strlen($tag['content']) - 1 + 2;
2476
		}
2477
		// This one is sorta ugly... :/.  Unfortunately, it's needed for flash.
2478
		elseif ($tag['type'] == 'unparsed_commas_content')
2479
		{
2480
			$pos2 = strpos($message, ']', $pos1);
2481
			if ($pos2 === false)
2482
				continue;
2483
2484
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2485
			if ($pos3 === false)
2486
				continue;
2487
2488
			// We want $1 to be the content, and the rest to be csv.
2489
			$data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
2490
			$data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
2491
2492
			if (isset($tag['validate']))
2493
				$tag['validate']($tag, $data, $disabled, $params);
2494
2495
			$code = $tag['content'];
2496 View Code Duplication
			foreach ($data as $k => $d)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2497
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2498
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2499
			$pos += strlen($code) - 1 + 2;
2500
		}
2501
		// This has parsed content, and a csv value which is unparsed.
2502
		elseif ($tag['type'] == 'unparsed_commas')
2503
		{
2504
			$pos2 = strpos($message, ']', $pos1);
2505
			if ($pos2 === false)
2506
				continue;
2507
2508
			$data = explode(',', substr($message, $pos1, $pos2 - $pos1));
2509
2510
			if (isset($tag['validate']))
2511
				$tag['validate']($tag, $data, $disabled, $params);
2512
2513
			// Fix after, for disabled code mainly.
2514 View Code Duplication
			foreach ($data as $k => $d)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2515
				$tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
2516
2517
			$open_tags[] = $tag;
2518
2519
			// 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...
2520
			$code = $tag['before'];
2521 View Code Duplication
			foreach ($data as $k => $d)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2522
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2523
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
2524
			$pos += strlen($code) - 1 + 2;
2525
		}
2526
		// A tag set to a value, parsed or not.
2527
		elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals')
2528
		{
2529
			// The value may be quoted for some tags - check.
2530 View Code Duplication
			if (isset($tag['quoted']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2531
			{
2532
				$quoted = substr($message, $pos1, 6) == '&quot;';
2533
				if ($tag['quoted'] != 'optional' && !$quoted)
2534
					continue;
2535
2536
				if ($quoted)
2537
					$pos1 += 6;
2538
			}
2539
			else
2540
				$quoted = false;
2541
2542
			$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...
2543
			if ($pos2 === false)
2544
				continue;
2545
2546
			$data = substr($message, $pos1, $pos2 - $pos1);
2547
2548
			// Validation for my parking, please!
2549
			if (isset($tag['validate']))
2550
				$tag['validate']($tag, $data, $disabled, $params);
2551
2552
			// For parsed content, we must recurse to avoid security problems.
2553
			if ($tag['type'] != 'unparsed_equals')
2554
				$data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
2555
2556
			$tag['after'] = strtr($tag['after'], array('$1' => $data));
2557
2558
			$open_tags[] = $tag;
2559
2560
			$code = strtr($tag['before'], array('$1' => $data));
2561
			$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...
2562
			$pos += strlen($code) - 1 + 2;
2563
		}
2564
2565
		// If this is block level, eat any breaks after it.
2566 View Code Duplication
		if (!empty($tag['block_level']) && substr($message, $pos + 1, 4) == '<br>')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2567
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 5);
2568
2569
		// Are we trimming outside this tag?
2570
		if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos + 1), $matches) != 0)
2571
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
2572
	}
2573
2574
	// Close any remaining tags.
2575
	while ($tag = array_pop($open_tags))
2576
		$message .= "\n" . $tag['after'] . "\n";
2577
2578
	// Parse the smileys within the parts where it can be done safely.
2579
	if ($smileys === true)
2580
	{
2581
		$message_parts = explode("\n", $message);
2582
		for ($i = 0, $n = count($message_parts); $i < $n; $i += 2)
2583
			parsesmileys($message_parts[$i]);
2584
2585
		$message = implode('', $message_parts);
2586
	}
2587
2588
	// No smileys, just get rid of the markers.
2589
	else
2590
		$message = strtr($message, array("\n" => ''));
2591
2592
	if ($message !== '' && $message[0] === ' ')
2593
		$message = '&nbsp;' . substr($message, 1);
2594
2595
	// Cleanup whitespace.
2596
	$message = strtr($message, array('  ' => ' &nbsp;', "\r" => '', "\n" => '<br>', '<br> ' => '<br>&nbsp;', '&#13;' => "\n"));
2597
2598
	// Allow mods access to what parse_bbc created
2599
	call_integration_hook('integrate_post_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
2600
2601
	// Cache the output if it took some time...
2602
	if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05)
2603
		cache_put_data($cache_key, $message, 240);
2604
2605
	// If this was a force parse revert if needed.
2606
	if (!empty($parse_tags))
2607
	{
2608
		if (empty($temp_bbc))
2609
			$bbc_codes = array();
2610
		else
2611
		{
2612
			$bbc_codes = $temp_bbc;
2613
			unset($temp_bbc);
2614
		}
2615
	}
2616
2617
	return $message;
2618
}
2619
2620
/**
2621
 * Parse smileys in the passed message.
2622
 *
2623
 * The smiley parsing function which makes pretty faces appear :).
2624
 * If custom smiley sets are turned off by smiley_enable, the default set of smileys will be used.
2625
 * These are specifically not parsed in code tags [url=mailto:[email protected]]
2626
 * Caches the smileys from the database or array in memory.
2627
 * Doesn't return anything, but rather modifies message directly.
2628
 *
2629
 * @param string &$message The message to parse smileys in
2630
 */
2631
function parsesmileys(&$message)
2632
{
2633
	global $modSettings, $txt, $user_info, $context, $smcFunc;
2634
	static $smileyPregSearch = null, $smileyPregReplacements = array();
2635
2636
	// No smiley set at all?!
2637
	if ($user_info['smiley_set'] == 'none' || trim($message) == '')
2638
		return;
2639
2640
	// If smileyPregSearch hasn't been set, do it now.
2641
	if (empty($smileyPregSearch))
2642
	{
2643
		// Use the default smileys if it is disabled. (better for "portability" of smileys.)
2644
		if (empty($modSettings['smiley_enable']))
2645
		{
2646
			$smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)');
2647
			$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');
2648
			$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'], '', '', '', '');
2649
		}
2650
		else
2651
		{
2652
			// Load the smileys in reverse order by length so they don't get parsed wrong.
2653
			if (($temp = cache_get_data('parsing_smileys', 480)) == null)
2654
			{
2655
				$result = $smcFunc['db_query']('', '
2656
					SELECT code, filename, description
2657
					FROM {db_prefix}smileys
2658
					ORDER BY LENGTH(code) DESC',
2659
					array(
2660
					)
2661
				);
2662
				$smileysfrom = array();
2663
				$smileysto = array();
2664
				$smileysdescs = array();
2665
				while ($row = $smcFunc['db_fetch_assoc']($result))
2666
				{
2667
					$smileysfrom[] = $row['code'];
2668
					$smileysto[] = $smcFunc['htmlspecialchars']($row['filename']);
2669
					$smileysdescs[] = $row['description'];
2670
				}
2671
				$smcFunc['db_free_result']($result);
2672
2673
				cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480);
2674
			}
2675
			else
2676
				list ($smileysfrom, $smileysto, $smileysdescs) = $temp;
2677
		}
2678
2679
		// The non-breaking-space is a complex thing...
2680
		$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0';
2681
2682
		// This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:[email protected]] doesn't parse the :D smiley)
2683
		$smileyPregReplacements = array();
2684
		$searchParts = array();
2685
		$smileys_path = $smcFunc['htmlspecialchars']($modSettings['smileys_url'] . '/' . $user_info['smiley_set'] . '/');
2686
2687
		for ($i = 0, $n = count($smileysfrom); $i < $n; $i++)
2688
		{
2689
			$specialChars = $smcFunc['htmlspecialchars']($smileysfrom[$i], ENT_QUOTES);
2690
			$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">';
2691
2692
			$smileyPregReplacements[$smileysfrom[$i]] = $smileyCode;
2693
2694
			$searchParts[] = preg_quote($smileysfrom[$i], '~');
2695
			if ($smileysfrom[$i] != $specialChars)
2696
			{
2697
				$smileyPregReplacements[$specialChars] = $smileyCode;
2698
				$searchParts[] = preg_quote($specialChars, '~');
2699
			}
2700
		}
2701
2702
		$smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|(?<![a-zA-Z0-9])\(|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~' . ($context['utf8'] ? 'u' : '');
2703
	}
2704
2705
	// Replace away!
2706
	$message = preg_replace_callback($smileyPregSearch,
2707
		function ($matches) use ($smileyPregReplacements)
2708
		{
2709
			return $smileyPregReplacements[$matches[1]];
2710
		}, $message);
2711
}
2712
2713
/**
2714
 * Highlight any code.
2715
 *
2716
 * Uses PHP's highlight_string() to highlight PHP syntax
2717
 * does special handling to keep the tabs in the code available.
2718
 * used to parse PHP code from inside [code] and [php] tags.
2719
 *
2720
 * @param string $code The code
2721
 * @return string The code with highlighted HTML.
2722
 */
2723
function highlight_php_code($code)
2724
{
2725
	// Remove special characters.
2726
	$code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", '<br>' => "\n", "\t" => 'SMF_TAB();', '&#91;' => '[')));
2727
2728
	$oldlevel = error_reporting(0);
2729
2730
	$buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true));
2731
2732
	error_reporting($oldlevel);
2733
2734
	// Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P.
2735
	$buffer = preg_replace('~SMF_TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer);
2736
2737
	return strtr($buffer, array('\'' => '&#039;', '<code>' => '', '</code>' => ''));
2738
}
2739
2740
/**
2741
 * Make sure the browser doesn't come back and repost the form data.
2742
 * Should be used whenever anything is posted.
2743
 *
2744
 * @param string $setLocation The URL to redirect them to
2745
 * @param bool $refresh Whether to use a meta refresh instead
2746
 * @param bool $permanent Whether to send a 301 Moved Permanently instead of a 302 Moved Temporarily
2747
 */
2748
function redirectexit($setLocation = '', $refresh = false, $permanent = false)
2749
{
2750
	global $scripturl, $context, $modSettings, $db_show_debug, $db_cache;
2751
2752
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
2753
	if (!empty($context['flush_mail']))
2754
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
2755
		AddMailQueue(true);
2756
2757
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
2758
2759
	if ($add)
2760
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
2761
2762
	// Put the session ID in.
2763
	if (defined('SID') && SID != '')
2764
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
2765
	// Keep that debug in their for template debugging!
2766 View Code Duplication
	elseif (isset($_GET['debug']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2767
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
2768
2769
	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'])))
2770
	{
2771
		if (defined('SID') && SID != '')
2772
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~',
2773
				function ($m) use ($scripturl)
2774
				{
2775
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html?' . SID. (isset($m[2]) ? "$m[2]" : "");
2776
				}, $setLocation);
2777 View Code Duplication
		else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2778
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~',
2779
				function ($m) use ($scripturl)
2780
				{
2781
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? "$m[2]" : "");
2782
				}, $setLocation);
2783
	}
2784
2785
	// Maybe integrations want to change where we are heading?
2786
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh, &$permanent));
2787
2788
	// Set the header.
2789
	header('Location: ' . str_replace(' ', '%20', $setLocation), true, $permanent ? 301 : 302);
2790
2791
	// Debugging.
2792
	if (isset($db_show_debug) && $db_show_debug === true)
2793
		$_SESSION['debug_redirect'] = $db_cache;
2794
2795
	obExit(false);
2796
}
2797
2798
/**
2799
 * Ends execution.  Takes care of template loading and remembering the previous URL.
2800
 * @param bool $header Whether to do the header
2801
 * @param bool $do_footer Whether to do the footer
2802
 * @param bool $from_index Whether we're coming from the board index
2803
 * @param bool $from_fatal_error Whether we're coming from a fatal error
2804
 */
2805
function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false)
2806
{
2807
	global $context, $settings, $modSettings, $txt, $smcFunc;
2808
	static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false;
2809
2810
	// Attempt to prevent a recursive loop.
2811
	++$level;
2812
	if ($level > 1 && !$from_fatal_error && !$has_fatal_error)
2813
		exit;
2814
	if ($from_fatal_error)
2815
		$has_fatal_error = true;
2816
2817
	// Clear out the stat cache.
2818
	trackStats();
2819
2820
	// If we have mail to send, send it.
2821
	if (!empty($context['flush_mail']))
2822
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
2823
		AddMailQueue(true);
2824
2825
	$do_header = $header === null ? !$header_done : $header;
2826
	if ($do_footer === null)
2827
		$do_footer = $do_header;
2828
2829
	// Has the template/header been done yet?
2830
	if ($do_header)
2831
	{
2832
		// Was the page title set last minute? Also update the HTML safe one.
2833
		if (!empty($context['page_title']) && empty($context['page_title_html_safe']))
2834
			$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
2835
2836
		// Start up the session URL fixer.
2837
		ob_start('ob_sessrewrite');
2838
2839
		if (!empty($settings['output_buffers']) && is_string($settings['output_buffers']))
2840
			$buffers = explode(',', $settings['output_buffers']);
2841
		elseif (!empty($settings['output_buffers']))
2842
			$buffers = $settings['output_buffers'];
2843
		else
2844
			$buffers = array();
2845
2846
		if (isset($modSettings['integrate_buffer']))
2847
			$buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers);
2848
2849
		if (!empty($buffers))
2850
			foreach ($buffers as $function)
2851
			{
2852
				$call = call_helper($function, true);
2853
2854
				// Is it valid?
2855
				if (!empty($call))
2856
					ob_start($call);
2857
			}
2858
2859
		// Display the screen in the logical order.
2860
		template_header();
2861
		$header_done = true;
2862
	}
2863
	if ($do_footer)
2864
	{
2865
		loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main');
2866
2867
		// Anything special to put out?
2868
		if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml']))
2869
			echo $context['insert_after_template'];
2870
2871
		// Just so we don't get caught in an endless loop of errors from the footer...
2872
		if (!$footer_done)
2873
		{
2874
			$footer_done = true;
2875
			template_footer();
2876
2877
			// (since this is just debugging... it's okay that it's after </html>.)
2878
			if (!isset($_REQUEST['xml']))
2879
				displayDebug();
2880
		}
2881
	}
2882
2883
	// Remember this URL in case someone doesn't like sending HTTP_REFERER.
2884
	if (strpos($_SERVER['REQUEST_URL'], 'action=dlattach') === false && strpos($_SERVER['REQUEST_URL'], 'action=viewsmfile') === false)
2885
		$_SESSION['old_url'] = $_SERVER['REQUEST_URL'];
2886
2887
	// For session check verification.... don't switch browsers...
2888
	$_SESSION['USER_AGENT'] = empty($_SERVER['HTTP_USER_AGENT']) ? '' : $_SERVER['HTTP_USER_AGENT'];
2889
2890
	// Hand off the output to the portal, etc. we're integrated with.
2891
	call_integration_hook('integrate_exit', array($do_footer));
2892
2893
	// Don't exit if we're coming from index.php; that will pass through normally.
2894
	if (!$from_index)
2895
		exit;
2896
}
2897
2898
/**
2899
 * Get the size of a specified image with better error handling.
2900
 * @todo see if it's better in Subs-Graphics, but one step at the time.
2901
 * Uses getimagesize() to determine the size of a file.
2902
 * Attempts to connect to the server first so it won't time out.
2903
 *
2904
 * @param string $url The URL of the image
2905
 * @return array|false The image size as array (width, height), or false on failure
2906
 */
2907
function url_image_size($url)
2908
{
2909
	global $sourcedir;
2910
2911
	// Make sure it is a proper URL.
2912
	$url = str_replace(' ', '%20', $url);
2913
2914
	// Can we pull this from the cache... please please?
2915
	if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null)
2916
		return $temp;
2917
	$t = microtime();
2918
2919
	// Get the host to pester...
2920
	preg_match('~^\w+://(.+?)/(.*)$~', $url, $match);
2921
2922
	// Can't figure it out, just try the image size.
2923
	if ($url == '' || $url == 'http://' || $url == 'https://')
2924
	{
2925
		return false;
2926
	}
2927
	elseif (!isset($match[1]))
2928
	{
2929
		$size = @getimagesize($url);
2930
	}
2931
	else
2932
	{
2933
		// Try to connect to the server... give it half a second.
2934
		$temp = 0;
2935
		$fp = @fsockopen($match[1], 80, $temp, $temp, 0.5);
2936
2937
		// 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...
2938
		if ($fp != false)
2939
		{
2940
			// Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.)
2941
			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");
2942
2943
			// Read in the HTTP/1.1 or whatever.
2944
			$test = substr(fgets($fp, 11), -1);
2945
			fclose($fp);
2946
2947
			// See if it returned a 404/403 or something.
2948
			if ($test < 4)
2949
			{
2950
				$size = @getimagesize($url);
2951
2952
				// This probably means allow_url_fopen is off, let's try GD.
2953
				if ($size === false && function_exists('imagecreatefromstring'))
2954
				{
2955
					include_once($sourcedir . '/Subs-Package.php');
2956
2957
					// It's going to hate us for doing this, but another request...
2958
					$image = @imagecreatefromstring(fetch_web_data($url));
2959
					if ($image !== false)
2960
					{
2961
						$size = array(imagesx($image), imagesy($image));
2962
						imagedestroy($image);
2963
					}
2964
				}
2965
			}
2966
		}
2967
	}
2968
2969
	// If we didn't get it, we failed.
2970
	if (!isset($size))
2971
		$size = false;
2972
2973
	// If this took a long time, we may never have to do it again, but then again we might...
2974 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.8)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
2975
		cache_put_data('url_image_size-' . md5($url), $size, 240);
2976
2977
	// Didn't work.
2978
	return $size;
2979
}
2980
2981
/**
2982
 * Sets up the basic theme context stuff.
2983
 * @param bool $forceload Whether to load the theme even if it's already loaded
2984
 */
2985
function setupThemeContext($forceload = false)
2986
{
2987
	global $modSettings, $user_info, $scripturl, $context, $settings, $options, $txt, $maintenance;
2988
	global $smcFunc;
2989
	static $loaded = false;
2990
2991
	// Under SSI this function can be called more then once.  That can cause some problems.
2992
	//   So only run the function once unless we are forced to run it again.
2993
	if ($loaded && !$forceload)
2994
		return;
2995
2996
	$loaded = true;
2997
2998
	$context['in_maintenance'] = !empty($maintenance);
2999
	$context['current_time'] = timeformat(time(), false);
3000
	$context['current_action'] = isset($_GET['action']) ? $smcFunc['htmlspecialchars']($_GET['action']) : '';
3001
3002
	// Get some news...
3003
	$context['news_lines'] = array_filter(explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news'])))));
3004
	for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++)
3005
	{
3006
		if (trim($context['news_lines'][$i]) == '')
3007
			continue;
3008
3009
		// Clean it up for presentation ;).
3010
		$context['news_lines'][$i] = parse_bbc(stripslashes(trim($context['news_lines'][$i])), true, 'news' . $i);
3011
	}
3012
	if (!empty($context['news_lines']))
3013
		$context['random_news_line'] = $context['news_lines'][mt_rand(0, count($context['news_lines']) - 1)];
3014
3015
	if (!$user_info['is_guest'])
3016
	{
3017
		$context['user']['messages'] = &$user_info['messages'];
3018
		$context['user']['unread_messages'] = &$user_info['unread_messages'];
3019
		$context['user']['alerts'] = &$user_info['alerts'];
3020
3021
		// Personal message popup...
3022
		if ($user_info['unread_messages'] > (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0))
3023
			$context['user']['popup_messages'] = true;
3024
		else
3025
			$context['user']['popup_messages'] = false;
3026
		$_SESSION['unread_messages'] = $user_info['unread_messages'];
3027
3028
		if (allowedTo('moderate_forum'))
3029
			$context['unapproved_members'] = (!empty($modSettings['registration_method']) && ($modSettings['registration_method'] == 2 || (!empty($modSettings['coppaType']) && $modSettings['coppaType'] == 2))) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0;
3030
3031
		$context['user']['avatar'] = array();
3032
3033
		// Check for gravatar first since we might be forcing them...
3034
		if (($modSettings['gravatarEnabled'] && substr($user_info['avatar']['url'], 0, 11) == 'gravatar://') || !empty($modSettings['gravatarOverride']))
3035
		{
3036
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($user_info['avatar']['url'], 'gravatar://') && strlen($user_info['avatar']['url']) > 11)
3037
				$context['user']['avatar']['href'] = get_gravatar_url($smcFunc['substr']($user_info['avatar']['url'], 11));
3038
			else
3039
				$context['user']['avatar']['href'] = get_gravatar_url($user_info['email']);
3040
		}
3041
		// Uploaded?
3042
		elseif ($user_info['avatar']['url'] == '' && !empty($user_info['avatar']['id_attach']))
3043
			$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';
3044
		// Full URL?
3045
		elseif (strpos($user_info['avatar']['url'], 'http://') === 0 || strpos($user_info['avatar']['url'], 'https://') === 0)
3046
			$context['user']['avatar']['href'] = $user_info['avatar']['url'];
3047
		// Otherwise we assume it's server stored.
3048
		elseif ($user_info['avatar']['url'] != '')
3049
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/' . $smcFunc['htmlspecialchars']($user_info['avatar']['url']);
3050
		// No avatar at all? Fine, we have a big fat default avatar ;)
3051
		else
3052
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/default.png';
3053
3054
		if (!empty($context['user']['avatar']))
3055
			$context['user']['avatar']['image'] = '<img src="' . $context['user']['avatar']['href'] . '" alt="" class="avatar">';
3056
3057
		// Figure out how long they've been logged in.
3058
		$context['user']['total_time_logged_in'] = array(
3059
			'days' => floor($user_info['total_time_logged_in'] / 86400),
3060
			'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600),
3061
			'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60)
3062
		);
3063
	}
3064
	else
3065
	{
3066
		$context['user']['messages'] = 0;
3067
		$context['user']['unread_messages'] = 0;
3068
		$context['user']['avatar'] = array();
3069
		$context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0);
3070
		$context['user']['popup_messages'] = false;
3071
3072
		if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1)
3073
			$txt['welcome_guest'] .= $txt['welcome_guest_activate'];
3074
3075
		// If we've upgraded recently, go easy on the passwords.
3076
		if (!empty($modSettings['disableHashTime']) && ($modSettings['disableHashTime'] == 1 || time() < $modSettings['disableHashTime']))
3077
			$context['disable_login_hashing'] = true;
3078
	}
3079
3080
	// Setup the main menu items.
3081
	setupMenuContext();
3082
3083
	// This is here because old index templates might still use it.
3084
	$context['show_news'] = !empty($settings['enable_news']);
3085
3086
	// This is done to allow theme authors to customize it as they want.
3087
	$context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != 'pm');
3088
3089
	// 2.1+: Add the PM popup here instead. Theme authors can still override it simply by editing/removing the 'fPmPopup' in the array.
3090
	if ($context['show_pm_popup'])
3091
		addInlineJavaScript('
3092
		jQuery(document).ready(function($) {
3093
			new smc_Popup({
3094
				heading: ' . JavaScriptEscape($txt['show_personal_messages_heading']) . ',
3095
				content: ' . JavaScriptEscape(sprintf($txt['show_personal_messages'], $context['user']['unread_messages'], $scripturl . '?action=pm')) . ',
3096
				icon_class: \'generic_icons mail_new\'
3097
			});
3098
		});');
3099
3100
	// Add a generic "Are you sure?" confirmation message.
3101
	addInlineJavaScript('
3102
	var smf_you_sure =' . JavaScriptEscape($txt['quickmod_confirm']) .';');
3103
3104
	// Now add the capping code for avatars.
3105
	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')
3106
		addInlineCss('
3107
img.avatar { max-width: ' . $modSettings['avatar_max_width_external'] . 'px; max-height: ' . $modSettings['avatar_max_height_external'] . 'px; }');
3108
3109
	// This looks weird, but it's because BoardIndex.php references the variable.
3110
	$context['common_stats']['latest_member'] = array(
3111
		'id' => $modSettings['latestMember'],
3112
		'name' => $modSettings['latestRealName'],
3113
		'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'],
3114
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $modSettings['latestRealName'] . '</a>',
3115
	);
3116
	$context['common_stats'] = array(
3117
		'total_posts' => comma_format($modSettings['totalMessages']),
3118
		'total_topics' => comma_format($modSettings['totalTopics']),
3119
		'total_members' => comma_format($modSettings['totalMembers']),
3120
		'latest_member' => $context['common_stats']['latest_member'],
3121
	);
3122
	$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']);
3123
3124
	if (empty($settings['theme_version']))
3125
		addJavaScriptVar('smf_scripturl', $scripturl);
3126
3127
	if (!isset($context['page_title']))
3128
		$context['page_title'] = '';
3129
3130
	// Set some specific vars.
3131
	$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
3132
	$context['meta_keywords'] = !empty($modSettings['meta_keywords']) ? $smcFunc['htmlspecialchars']($modSettings['meta_keywords']) : '';
3133
3134
	// Content related meta tags, including Open Graph
3135
	$context['meta_tags'][] = array('property' => 'og:site_name', 'content' => $context['forum_name']);
3136
	$context['meta_tags'][] = array('property' => 'og:title', 'content' => $context['page_title_html_safe']);
3137
3138
	if (!empty($context['meta_keywords']))
3139
		$context['meta_tags'][] = array('name' => 'keywords', 'content' => $context['meta_keywords']);
3140
3141
	if (!empty($context['canonical_url']))
3142
		$context['meta_tags'][] = array('property' => 'og:url', 'content' => $context['canonical_url']);
3143
3144
	if (!empty($settings['og_image']))
3145
		$context['meta_tags'][] = array('property' => 'og:image', 'content' => $settings['og_image']);
3146
3147
	if (!empty($context['meta_description']))
3148
	{
3149
		$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['meta_description']);
3150
		$context['meta_tags'][] = array('name' => 'description', 'content' => $context['meta_description']);
3151
	}
3152
	else
3153
	{
3154
		$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['page_title_html_safe']);
3155
		$context['meta_tags'][] = array('name' => 'description', 'content' => $context['page_title_html_safe']);
3156
	}
3157
3158
	call_integration_hook('integrate_theme_context');
3159
}
3160
3161
/**
3162
 * Helper function to set the system memory to a needed value
3163
 * - If the needed memory is greater than current, will attempt to get more
3164
 * - if in_use is set to true, will also try to take the current memory usage in to account
3165
 *
3166
 * @param string $needed The amount of memory to request, if needed, like 256M
3167
 * @param bool $in_use Set to true to account for current memory usage of the script
3168
 * @return boolean True if we have at least the needed memory
3169
 */
3170
function setMemoryLimit($needed, $in_use = false)
3171
{
3172
	// everything in bytes
3173
	$memory_used = 0;
0 ignored issues
show
Unused Code introduced by
$memory_used 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...
3174
	$memory_current = memoryReturnBytes(ini_get('memory_limit'));
3175
	$memory_needed = memoryReturnBytes($needed);
3176
3177
	// should we account for how much is currently being used?
3178
	if ($in_use)
3179
		$memory_needed += function_exists('memory_get_usage') ? memory_get_usage() : (2 * 1048576);
3180
3181
	// if more is needed, request it
3182
	if ($memory_current < $memory_needed)
3183
	{
3184
		@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...
3185
		$memory_current = memoryReturnBytes(ini_get('memory_limit'));
3186
	}
3187
3188
	$memory_current = max($memory_current, memoryReturnBytes(get_cfg_var('memory_limit')));
3189
3190
	// return success or not
3191
	return (bool) ($memory_current >= $memory_needed);
3192
}
3193
3194
/**
3195
 * Helper function to convert memory string settings to bytes
3196
 *
3197
 * @param string $val The byte string, like 256M or 1G
3198
 * @return integer The string converted to a proper integer in bytes
3199
 */
3200
function memoryReturnBytes($val)
3201
{
3202
	if (is_integer($val))
3203
		return $val;
3204
3205
	// Separate the number from the designator
3206
	$val = trim($val);
3207
	$num = intval(substr($val, 0, strlen($val) - 1));
3208
	$last = strtolower(substr($val, -1));
3209
3210
	// convert to bytes
3211
	switch ($last)
3212
	{
3213
		case 'g':
3214
			$num *= 1024;
3215
		case 'm':
3216
			$num *= 1024;
3217
		case 'k':
3218
			$num *= 1024;
3219
	}
3220
	return $num;
3221
}
3222
3223
/**
3224
 * The header template
3225
 */
3226
function template_header()
3227
{
3228
	global $txt, $modSettings, $context, $user_info, $boarddir, $cachedir;
3229
3230
	setupThemeContext();
3231
3232
	// Print stuff to prevent caching of pages (except on attachment errors, etc.)
3233
	if (empty($context['no_last_modified']))
3234
	{
3235
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
3236
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3237
3238
		// Are we debugging the template/html content?
3239
		if (!isset($_REQUEST['xml']) && isset($_GET['debug']) && !isBrowser('ie'))
3240
			header('Content-Type: application/xhtml+xml');
3241 View Code Duplication
		elseif (!isset($_REQUEST['xml']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
3242
			header('Content-Type: text/html; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3243
	}
3244
3245
	header('Content-Type: text/' . (isset($_REQUEST['xml']) ? 'xml' : 'html') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3246
3247
	// We need to splice this in after the body layer, or after the main layer for older stuff.
3248
	if ($context['in_maintenance'] && $context['user']['is_admin'])
3249
	{
3250
		$position = array_search('body', $context['template_layers']);
3251
		if ($position === false)
3252
			$position = array_search('main', $context['template_layers']);
3253
3254
		if ($position !== false)
3255
		{
3256
			$before = array_slice($context['template_layers'], 0, $position + 1);
3257
			$after = array_slice($context['template_layers'], $position + 1);
3258
			$context['template_layers'] = array_merge($before, array('maint_warning'), $after);
3259
		}
3260
	}
3261
3262
	$checked_securityFiles = false;
3263
	$showed_banned = false;
3264
	foreach ($context['template_layers'] as $layer)
3265
	{
3266
		loadSubTemplate($layer . '_above', true);
3267
3268
		// May seem contrived, but this is done in case the body and main layer aren't there...
3269
		if (in_array($layer, array('body', 'main')) && allowedTo('admin_forum') && !$user_info['is_guest'] && !$checked_securityFiles)
3270
		{
3271
			$checked_securityFiles = true;
3272
3273
			$securityFiles = array('install.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~');
3274
3275
			// Add your own files.
3276
			call_integration_hook('integrate_security_files', array(&$securityFiles));
3277
3278
			foreach ($securityFiles as $i => $securityFile)
3279
			{
3280
				if (!file_exists($boarddir . '/' . $securityFile))
3281
					unset($securityFiles[$i]);
3282
			}
3283
3284
			// We are already checking so many files...just few more doesn't make any difference! :P
3285 View Code Duplication
			if (!empty($modSettings['currentAttachmentUploadDir']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
3286
				$path = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
3287
3288
			else
3289
			{
3290
				$path = $modSettings['attachmentUploadDir'];
3291
				$id_folder_thumb = 1;
0 ignored issues
show
Unused Code introduced by
$id_folder_thumb 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...
3292
			}
3293
			secureDirectory($path, true);
3294
			secureDirectory($cachedir);
3295
3296
			// If agreement is enabled, at least the english version shall exists
3297
			if ($modSettings['requireAgreement'])
3298
				$agreement = !file_exists($boarddir . '/agreement.txt');
3299
3300
			if (!empty($securityFiles) || (!empty($modSettings['cache_enable']) && !is_writable($cachedir)) || !empty($agreement))
3301
			{
3302
				echo '
3303
		<div class="errorbox">
3304
			<p class="alert">!!</p>
3305
			<h3>', empty($securityFiles) ? $txt['generic_warning'] : $txt['security_risk'], '</h3>
3306
			<p>';
3307
3308
				foreach ($securityFiles as $securityFile)
3309
				{
3310
					echo '
3311
				', $txt['not_removed'], '<strong>', $securityFile, '</strong>!<br>';
3312
3313
					if ($securityFile == 'Settings.php~' || $securityFile == 'Settings_bak.php~')
3314
						echo '
3315
				', sprintf($txt['not_removed_extra'], $securityFile, substr($securityFile, 0, -1)), '<br>';
3316
				}
3317
3318
				if (!empty($modSettings['cache_enable']) && !is_writable($cachedir))
3319
					echo '
3320
				<strong>', $txt['cache_writable'], '</strong><br>';
3321
3322
				if (!empty($agreement))
3323
					echo '
3324
				<strong>', $txt['agreement_missing'], '</strong><br>';
3325
3326
				echo '
3327
			</p>
3328
		</div>';
3329
			}
3330
		}
3331
		// If the user is banned from posting inform them of it.
3332
		elseif (in_array($layer, array('main', 'body')) && isset($_SESSION['ban']['cannot_post']) && !$showed_banned)
3333
		{
3334
			$showed_banned = true;
3335
			echo '
3336
				<div class="windowbg alert" style="margin: 2ex; padding: 2ex; border: 2px dashed red;">
3337
					', sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']);
3338
3339
			if (!empty($_SESSION['ban']['cannot_post']['reason']))
3340
				echo '
3341
					<div style="padding-left: 4ex; padding-top: 1ex;">', $_SESSION['ban']['cannot_post']['reason'], '</div>';
3342
3343
			if (!empty($_SESSION['ban']['expire_time']))
3344
				echo '
3345
					<div>', sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)), '</div>';
3346
			else
3347
				echo '
3348
					<div>', $txt['your_ban_expires_never'], '</div>';
3349
3350
			echo '
3351
				</div>';
3352
		}
3353
	}
3354
}
3355
3356
/**
3357
 * Show the copyright.
3358
 */
3359
function theme_copyright()
3360
{
3361
	global $forum_copyright, $software_year, $forum_version;
3362
3363
	// Don't display copyright for things like SSI.
3364
	if (!isset($forum_version) || !isset($software_year))
3365
		return;
3366
3367
	// Put in the version...
3368
	printf($forum_copyright, $forum_version, $software_year);
3369
}
3370
3371
/**
3372
 * The template footer
3373
 */
3374
function template_footer()
3375
{
3376
	global $context, $modSettings, $time_start, $db_count;
3377
3378
	// Show the load time?  (only makes sense for the footer.)
3379
	$context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']);
3380
	$context['load_time'] = comma_format(round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)), 3));
3381
	$context['load_queries'] = $db_count;
3382
3383
	foreach (array_reverse($context['template_layers']) as $layer)
3384
		loadSubTemplate($layer . '_below', true);
3385
}
3386
3387
/**
3388
 * Output the Javascript files
3389
 * 	- tabbing in this function is to make the HTML source look good proper
3390
 *  - if defered is set function will output all JS (source & inline) set to load at page end
3391
 *
3392
 * @param bool $do_deferred If true will only output the deferred JS (the stuff that goes right before the closing body tag)
3393
 */
3394
function template_javascript($do_deferred = false)
3395
{
3396
	global $context, $modSettings, $settings;
3397
3398
	// Use this hook to minify/optimize Javascript files and vars
3399
	call_integration_hook('integrate_pre_javascript_output', array(&$do_deferred));
3400
3401
	$toMinify = array();
3402
	$toMinifyDefer = array();
3403
3404
	// Ouput the declared Javascript variables.
3405
	if (!empty($context['javascript_vars']) && !$do_deferred)
3406
	{
3407
		echo '
3408
	<script>';
3409
3410
		foreach ($context['javascript_vars'] as $key => $value)
3411
		{
3412
			if (empty($value))
3413
			{
3414
				echo '
3415
		var ', $key, ';';
3416
			}
3417
			else
3418
			{
3419
				echo '
3420
		var ', $key, ' = ', $value, ';';
3421
			}
3422
		}
3423
3424
		echo '
3425
	</script>';
3426
	}
3427
3428
	// While we have JavaScript files to place in the template.
3429
	foreach ($context['javascript_files'] as $id => $js_file)
3430
	{
3431
		// Last minute call! allow theme authors to disable single files.
3432
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3433
			continue;
3434
3435
		// By default all files don't get minimized unless the file explicitly says so!
3436
		if (!empty($js_file['options']['minimize']) && !empty($modSettings['minimize_files']))
3437
		{
3438
			if ($do_deferred && !empty($js_file['options']['defer']))
3439
				$toMinifyDefer[] = $js_file;
3440
3441
			elseif (!$do_deferred && empty($js_file['options']['defer']))
3442
				$toMinify[] = $js_file;
3443
3444
			// Grab a random seed.
3445
			if (!isset($minSeed))
3446
				$minSeed = $js_file['options']['seed'];
3447
		}
3448
3449
		elseif ((!$do_deferred && empty($js_file['options']['defer'])) || ($do_deferred && !empty($js_file['options']['defer'])))
3450
			echo '
3451
	<script src="', $js_file['fileUrl'], '"', !empty($js_file['options']['async']) ? ' async="async"' : '', '></script>';
3452
	}
3453
3454
	if ((!$do_deferred && !empty($toMinify)) || ($do_deferred && !empty($toMinifyDefer)))
3455
	{
3456
		$result = custMinify(($do_deferred ? $toMinifyDefer : $toMinify), 'js', $do_deferred);
3457
3458
		// Minify process couldn't work, print each individual files.
3459
		if (!empty($result) && is_array($result))
3460
			foreach ($result as $minFailedFile)
3461
				echo '
3462
	<script src="', $minFailedFile['fileUrl'], '"', !empty($minFailedFile['options']['async']) ? ' async="async"' : '', '></script>';
3463
3464
		else
3465
			echo '
3466
	<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...
3467
	}
3468
3469
	// Inline JavaScript - Actually useful some times!
3470
	if (!empty($context['javascript_inline']))
3471
	{
3472 View Code Duplication
		if (!empty($context['javascript_inline']['defer']) && $do_deferred)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
3473
		{
3474
			echo '
3475
<script>';
3476
3477
			foreach ($context['javascript_inline']['defer'] as $js_code)
3478
				echo $js_code;
3479
3480
			echo '
3481
</script>';
3482
		}
3483
3484 View Code Duplication
		if (!empty($context['javascript_inline']['standard']) && !$do_deferred)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
3485
		{
3486
			echo '
3487
	<script>';
3488
3489
			foreach ($context['javascript_inline']['standard'] as $js_code)
3490
				echo $js_code;
3491
3492
			echo '
3493
	</script>';
3494
		}
3495
	}
3496
}
3497
3498
/**
3499
 * Output the CSS files
3500
 *
3501
 */
3502
function template_css()
3503
{
3504
	global $context, $db_show_debug, $boardurl, $settings, $modSettings;
3505
3506
	// Use this hook to minify/optimize CSS files
3507
	call_integration_hook('integrate_pre_css_output');
3508
3509
	$toMinify = array();
3510
	$normal = array();
3511
3512
	foreach ($context['css_files'] as $id => $file)
3513
	{
3514
		// Last minute call! allow theme authors to disable single files.
3515
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3516
			continue;
3517
3518
		// By default all files don't get minimized unless the file explicitly says so!
3519
		if (!empty($file['options']['minimize']) && !empty($modSettings['minimize_files']))
3520
		{
3521
			$toMinify[] = $file;
3522
3523
			// Grab a random seed.
3524
			if (!isset($minSeed))
3525
				$minSeed = $file['options']['seed'];
3526
		}
3527
3528
		else
3529
			$normal[] = $file['fileUrl'];
3530
	}
3531
3532
	if (!empty($toMinify))
3533
	{
3534
		$result = custMinify($toMinify, 'css');
3535
3536
		// Minify process couldn't work, print each individual files.
3537
		if (!empty($result) && is_array($result))
3538
			foreach ($result as $minFailedFile)
3539
				echo '
3540
	<link rel="stylesheet" href="', $minFailedFile['fileUrl'], '">';
3541
3542
		else
3543
			echo '
3544
	<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...
3545
	}
3546
3547
	// Print the rest after the minified files.
3548
	if (!empty($normal))
3549
		foreach ($normal as $nf)
3550
			echo '
3551
	<link rel="stylesheet" href="', $nf ,'">';
3552
3553
	if ($db_show_debug === true)
3554
	{
3555
		// Try to keep only what's useful.
3556
		$repl = array($boardurl . '/Themes/' => '', $boardurl . '/' => '');
3557
		foreach ($context['css_files'] as $file)
3558
			$context['debug']['sheets'][] = strtr($file['fileName'], $repl);
3559
	}
3560
3561
	if (!empty($context['css_header']))
3562
	{
3563
		echo '
3564
	<style>';
3565
3566
		foreach ($context['css_header'] as $css)
3567
			echo $css .'
3568
	';
3569
3570
		echo'
3571
	</style>';
3572
	}
3573
}
3574
3575
/**
3576
 * Get an array of previously defined files and adds them to our main minified file.
3577
 * Sets a one day cache to avoid re-creating a file on every request.
3578
 *
3579
 * @param array $data The files to minify.
3580
 * @param string $type either css or js.
3581
 * @param bool $do_deferred use for type js to indicate if the minified file will be deferred, IE, put at the closing </body> tag.
3582
 * @return bool|array If an array the minify process failed and the data is returned intact.
3583
 */
3584
function custMinify($data, $type, $do_deferred = false)
3585
{
3586
	global $sourcedir, $smcFunc, $settings, $txt, $context;
3587
3588
	$types = array('css', 'js');
3589
	$type = !empty($type) && in_array($type, $types) ? $type : false;
3590
	$data = !empty($data) ? $data : false;
3591
	$minFailed = array();
0 ignored issues
show
Unused Code introduced by
$minFailed 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...
3592
3593
	if (empty($type) || empty($data))
3594
		return false;
3595
3596
	// Did we already did this?
3597
	$toCache = cache_get_data('minimized_'. $settings['theme_id'] .'_'. $type, 86400);
3598
3599
	// Already done?
3600
	if (!empty($toCache))
3601
		return true;
3602
3603
	// Yep, need a bunch of files.
3604
	require_once($sourcedir . '/minify/src/Minify.php');
3605
	require_once($sourcedir . '/minify/src/'. strtoupper($type) .'.php');
3606
	require_once($sourcedir . '/minify/src/Exception.php');
3607
	require_once($sourcedir . '/minify/src/Converter.php');
3608
3609
	// No namespaces, sorry!
3610
	$classType = 'MatthiasMullie\\Minify\\'. strtoupper($type);
3611
3612
	// Temp path.
3613
	$cTempPath = $settings['theme_dir'] .'/'. ($type == 'css' ? 'css' : 'scripts') .'/';
3614
3615
	// What kind of file are we going to create?
3616
	$toCreate = $cTempPath .'minified'. ($do_deferred ? '_deferred' : '') .'.'. $type;
3617
3618
	// File has to exists, if it isn't try to create it.
3619
	if ((!file_exists($toCreate) && @fopen($toCreate, 'w') === false) || !smf_chmod($toCreate))
3620
	{
3621
		loadLanguage('Errors');
3622
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3623
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3624
3625
		// The process failed so roll back to print each individual file.
3626
		return $data;
3627
	}
3628
3629
	$minifier = new $classType();
3630
3631
	foreach ($data as $file)
3632
	{
3633
		$tempFile = str_replace($file['options']['seed'], '', $file['filePath']);
3634
		$toAdd = file_exists($tempFile) ? $tempFile : false;
3635
3636
		// The file couldn't be located so it won't be added, log this error.
3637
		if (empty($toAdd))
3638
		{
3639
			loadLanguage('Errors');
3640
			log_error(sprintf($txt['file_minimize_fail'], $file['fileName']), 'general');
3641
			continue;
3642
		}
3643
3644
		// Add this file to the list.
3645
		$minifier->add($toAdd);
3646
	}
3647
3648
	// Create the file.
3649
	$minifier->minify($toCreate);
3650
	unset($minifier);
3651
	clearstatcache();
3652
3653
	// Minify process failed.
3654
	if (!filesize($toCreate))
3655
	{
3656
		loadLanguage('Errors');
3657
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3658
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3659
3660
		// The process failed so roll back to print each individual file.
3661
		return $data;
3662
	}
3663
3664
	// And create a long lived cache entry.
3665
	cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, $toCreate, 86400);
3666
3667
	return true;
3668
}
3669
3670
/**
3671
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
3672
 * @todo this currently returns the hash if new, and the full filename otherwise.
3673
 * Something messy like that.
3674
 * @todo and of course everything relies on this behavior and work around it. :P.
3675
 * Converters included.
3676
 *
3677
 * @param string $filename The name of the file
3678
 * @param int $attachment_id The ID of the attachment
3679
 * @param string $dir Which directory it should be in (null to use current one)
3680
 * @param bool $new Whether this is a new attachment
3681
 * @param string $file_hash The file hash
3682
 * @return string The path to the file
3683
 */
3684
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
3685
{
3686
	global $modSettings, $smcFunc;
3687
3688
	// Just make up a nice hash...
3689
	if ($new)
3690
		return sha1(md5($filename . time()) . mt_rand());
3691
3692
	// Grab the file hash if it wasn't added.
3693
	// Left this for legacy.
3694
	if ($file_hash === '')
3695
	{
3696
		$request = $smcFunc['db_query']('', '
3697
			SELECT file_hash
3698
			FROM {db_prefix}attachments
3699
			WHERE id_attach = {int:id_attach}',
3700
			array(
3701
				'id_attach' => $attachment_id,
3702
			));
3703
3704
		if ($smcFunc['db_num_rows']($request) === 0)
3705
			return false;
3706
3707
		list ($file_hash) = $smcFunc['db_fetch_row']($request);
3708
		$smcFunc['db_free_result']($request);
3709
	}
3710
3711
	// Still no hash? mmm...
3712
	if (empty($file_hash))
3713
		$file_hash = sha1(md5($filename . time()) . mt_rand());
3714
3715
	// Are we using multiple directories?
3716 View Code Duplication
	if (!empty($modSettings['currentAttachmentUploadDir']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
3717
		$path = $modSettings['attachmentUploadDir'][$dir];
3718
3719
	else
3720
		$path = $modSettings['attachmentUploadDir'];
3721
3722
	return $path . '/' . $attachment_id . '_' . $file_hash .'.dat';
3723
}
3724
3725
/**
3726
 * Convert a single IP to a ranged IP.
3727
 * internal function used to convert a user-readable format to a format suitable for the database.
3728
 *
3729
 * @param string $fullip The full IP
3730
 * @return array An array of IP parts
3731
 */
3732
function ip2range($fullip)
3733
{
3734
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
3735
	if ($fullip == 'unknown')
3736
		$fullip = '255.255.255.255';
3737
3738
	$ip_parts = explode('-', $fullip);
3739
	$ip_array = array();
3740
3741
	// 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...
3742
	if (count($ip_parts) == 1 && isValidIP($fullip))
3743
	{
3744
		$ip_array['low'] = $fullip;
3745
		$ip_array['high'] = $fullip;
3746
		return $ip_array;
3747
	} // if ip 22.12.* -> 22.12.* - 22.12.*
3748
	elseif (count($ip_parts) == 1)
3749
	{
3750
		$ip_parts[0] = $fullip;
3751
		$ip_parts[1] = $fullip;
3752
	}
3753
3754
	// 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...
3755
	if (count($ip_parts) == 2 && isValidIP($ip_parts[0]) && isValidIP($ip_parts[1]))
3756
	{
3757
		$ip_array['low'] = $ip_parts[0];
3758
		$ip_array['high'] = $ip_parts[1];
3759
		return $ip_array;
3760
	}
3761
	elseif (count($ip_parts) == 2) // if ip 22.22.*-22.22.*
3762
	{
3763
		$valid_low = isValidIP($ip_parts[0]);
3764
		$valid_high = isValidIP($ip_parts[1]);
3765
		$count = 0;
3766
		$mode = (preg_match('/:/',$ip_parts[0]) > 0 ? ':' : '.');
3767
		$max = ($mode == ':' ? 'ffff' : '255');
3768
		$min = 0;
3769 View Code Duplication
		if(!$valid_low)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
3770
		{
3771
			$ip_parts[0] = preg_replace('/\*/', '0', $ip_parts[0]);
3772
			$valid_low = isValidIP($ip_parts[0]);
3773
			while (!$valid_low)
3774
			{
3775
				$ip_parts[0] .= $mode . $min;
3776
				$valid_low = isValidIP($ip_parts[0]);
3777
				$count++;
3778
				if ($count > 9) break;
3779
			}
3780
		}
3781
3782
		$count = 0;
3783 View Code Duplication
		if(!$valid_high)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
3784
		{
3785
			$ip_parts[1] = preg_replace('/\*/', $max, $ip_parts[1]);
3786
			$valid_high = isValidIP($ip_parts[1]);
3787
			while (!$valid_high)
3788
			{
3789
				$ip_parts[1] .= $mode . $max;
3790
				$valid_high = isValidIP($ip_parts[1]);
3791
				$count++;
3792
				if ($count > 9) break;
3793
			}
3794
		}
3795
3796
		if($valid_high && $valid_low)
3797
		{
3798
			$ip_array['low'] = $ip_parts[0];
3799
			$ip_array['high'] = $ip_parts[1];
3800
		}
3801
3802
	}
3803
3804
	return $ip_array;
3805
}
3806
3807
/**
3808
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
3809
 *
3810
 * @param string $ip The IP to get the hostname from
3811
 * @return string The hostname
3812
 */
3813
function host_from_ip($ip)
3814
{
3815
	global $modSettings;
3816
3817
	if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null)
3818
		return $host;
3819
	$t = microtime();
3820
3821
	// Try the Linux host command, perhaps?
3822
	if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
3823
	{
3824
		if (!isset($modSettings['host_to_dis']))
3825
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
3826
		else
3827
			$test = @shell_exec('host ' . @escapeshellarg($ip));
3828
3829
		// Did host say it didn't find anything?
3830
		if (strpos($test, 'not found') !== false)
3831
			$host = '';
3832
		// Invalid server option?
3833
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
3834
			updateSettings(array('host_to_dis' => 1));
3835
		// Maybe it found something, after all?
3836
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
3837
			$host = $match[1];
3838
	}
3839
3840
	// This is nslookup; usually only Windows, but possibly some Unix?
3841
	if (!isset($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
3842
	{
3843
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
3844
		if (strpos($test, 'Non-existent domain') !== false)
3845
			$host = '';
3846
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
3847
			$host = $match[1];
3848
	}
3849
3850
	// This is the last try :/.
3851
	if (!isset($host) || $host === false)
3852
		$host = @gethostbyaddr($ip);
3853
3854
	// It took a long time, so let's cache it!
3855 View Code Duplication
	if (array_sum(explode(' ', microtime())) - array_sum(explode(' ', $t)) > 0.5)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
3856
		cache_put_data('hostlookup-' . $ip, $host, 600);
3857
3858
	return $host;
3859
}
3860
3861
/**
3862
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
3863
 *
3864
 * @param string $text The text to split into words
3865
 * @param int $max_chars The maximum number of characters per word
3866
 * @param bool $encrypt Whether to encrypt the results
3867
 * @return array An array of ints or words depending on $encrypt
3868
 */
3869
function text2words($text, $max_chars = 20, $encrypt = false)
0 ignored issues
show
Best Practice introduced by
The function text2words() has been defined more than once; this definition is ignored, only the first definition in other/upgrade.php (L157-177) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
3870
{
3871
	global $smcFunc, $context;
3872
3873
	// Step 1: Remove entities/things we don't consider words:
3874
	$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>' => ' ')));
3875
3876
	// Step 2: Entities we left to letters, where applicable, lowercase.
3877
	$words = un_htmlspecialchars($smcFunc['strtolower']($words));
3878
3879
	// Step 3: Ready to split apart and index!
3880
	$words = explode(' ', $words);
3881
3882
	if ($encrypt)
3883
	{
3884
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
3885
		$returned_ints = array();
3886
		foreach ($words as $word)
3887
		{
3888
			if (($word = trim($word, '-_\'')) !== '')
3889
			{
3890
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
3891
				$total = 0;
3892
				for ($i = 0; $i < $max_chars; $i++)
3893
					$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
3894
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
3895
			}
3896
		}
3897
		return array_unique($returned_ints);
3898
	}
3899
	else
3900
	{
3901
		// Trim characters before and after and add slashes for database insertion.
3902
		$returned_words = array();
3903
		foreach ($words as $word)
3904
			if (($word = trim($word, '-_\'')) !== '')
3905
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
3906
3907
		// Filter out all words that occur more than once.
3908
		return array_unique($returned_words);
3909
	}
3910
}
3911
3912
/**
3913
 * Creates an image/text button
3914
 *
3915
 * @param string $name The name of the button (should be a generic_icons class or the name of an image)
3916
 * @param string $alt The alt text
3917
 * @param string $label The $txt string to use as the label
3918
 * @param string $custom Custom text/html to add to the img tag (only when using an actual image)
3919
 * @param boolean $force_use Whether to force use of this when template_create_button is available
3920
 * @return string The HTML to display the button
3921
 */
3922
function create_button($name, $alt, $label = '', $custom = '', $force_use = false)
3923
{
3924
	global $settings, $txt;
3925
3926
	// Does the current loaded theme have this and we are not forcing the usage of this function?
3927
	if (function_exists('template_create_button') && !$force_use)
3928
		return template_create_button($name, $alt, $label = '', $custom = '');
3929
3930
	if (!$settings['use_image_buttons'])
3931
		return $txt[$alt];
3932
	elseif (!empty($settings['use_buttons']))
3933
		return '<span class="generic_icons ' . $name . '" alt="' . $txt[$alt] . '"></span>' . ($label != '' ? '&nbsp;<strong>' . $txt[$label] . '</strong>' : '');
3934
	else
3935
		return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . '>';
3936
}
3937
3938
/**
3939
 * Empty out the cache in use as best it can
3940
 *
3941
 * It may only remove the files of a certain type (if the $type parameter is given)
3942
 * Type can be user, data or left blank
3943
 * 	- user clears out user data
3944
 *  - data clears out system / opcode data
3945
 *  - If no type is specified will perform a complete cache clearing
3946
 * For cache engines that do not distinguish on types, a full cache flush will be done
3947
 *
3948
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3949
 */
3950
function clean_cache($type = '')
0 ignored issues
show
Best Practice introduced by
The function clean_cache() has been defined more than once; this definition is ignored, only the first definition in other/upgrade.php (L183-204) is considered.

This check looks for functions that have already been defined in other files.

Some Codebases, like WordPress, make a practice of defining functions multiple times. This may lead to problems with the detection of function parameters and types. If you really need to do this, you can mark the duplicate definition with the @ignore annotation.

/**
 * @ignore
 */
function getUser() {

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
3951
{
3952
	global $cachedir, $sourcedir, $cache_accelerator, $modSettings, $memcached;
3953
3954
	switch ($cache_accelerator)
3955
	{
3956
		case 'memcached':
3957
			if (function_exists('memcache_flush') || function_exists('memcached_flush') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '')
3958
			{
3959
				// Not connected yet?
3960
				if (empty($memcached))
3961
					get_memcached_server();
3962
				if (!$memcached)
3963
					return;
3964
3965
				// clear it out
3966
				if (function_exists('memcache_flush'))
3967
					memcache_flush($memcached);
3968
				else
3969
					memcached_flush($memcached);
3970
			}
3971
			break;
3972
		case 'apc':
3973
			if (function_exists('apc_clear_cache'))
3974
			{
3975
				// if passed a type, clear that type out
3976
				if ($type === '' || $type === 'data')
3977
				{
3978
					apc_clear_cache('user');
3979
					apc_clear_cache('system');
3980
				}
3981
				elseif ($type === 'user')
3982
					apc_clear_cache('user');
3983
			}
3984
			break;
3985
		case 'zend':
3986
			if (function_exists('zend_shm_cache_clear'))
3987
				zend_shm_cache_clear('SMF');
3988
			break;
3989
		case 'xcache':
3990
			if (function_exists('xcache_clear_cache'))
3991
			{
3992
				//
3993
				if ($type === '')
3994
				{
3995
					xcache_clear_cache(XC_TYPE_VAR, 0);
3996
					xcache_clear_cache(XC_TYPE_PHP, 0);
3997
				}
3998
				if ($type === 'user')
3999
					xcache_clear_cache(XC_TYPE_VAR, 0);
4000
				if ($type === 'data')
4001
					xcache_clear_cache(XC_TYPE_PHP, 0);
4002
			}
4003
			break;
4004
		default:
4005
			// No directory = no game.
4006
			if (!is_dir($cachedir))
4007
				return;
4008
4009
			// Remove the files in SMF's own disk cache, if any
4010
			$dh = opendir($cachedir);
4011 View Code Duplication
			while ($file = readdir($dh))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4012
			{
4013
				if ($file != '.' && $file != '..' && $file != 'index.php' && $file != '.htaccess' && (!$type || substr($file, 0, strlen($type)) == $type))
4014
					@unlink($cachedir . '/' . $file);
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...
4015
			}
4016
			closedir($dh);
4017
			break;
4018
	}
4019
4020
	// Invalidate cache, to be sure!
4021
	// ... as long as index.php can be modified, anyway.
4022
	if (empty($type))
4023
		@touch($cachedir . '/' . 'index.php');
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...
4024
4025
	call_integration_hook('integrate_clean_cache');
4026
	clearstatcache();
4027
}
4028
4029
/**
4030
 * Sets up all of the top menu buttons
4031
 * Saves them in the cache if it is available and on
4032
 * Places the results in $context
4033
 *
4034
 */
4035
function setupMenuContext()
4036
{
4037
	global $context, $modSettings, $user_info, $txt, $scripturl, $sourcedir, $settings;
4038
4039
	// Set up the menu privileges.
4040
	$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts'));
4041
	$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'));
4042
4043
	$context['allow_memberlist'] = allowedTo('view_mlist');
4044
	$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
4045
	$context['allow_moderation_center'] = $context['user']['can_mod'];
4046
	$context['allow_pm'] = allowedTo('pm_read');
4047
4048
	$cacheTime = $modSettings['lastActive'] * 60;
4049
4050
	// Initial "can you post an event in the calendar" option - but this might have been set in the calendar already.
4051
	if (!isset($context['allow_calendar_event']))
4052
	{
4053
		$context['allow_calendar_event'] = $context['allow_calendar'] && allowedTo('calendar_post');
4054
4055
		// If you don't allow events not linked to posts and you're not an admin, we have more work to do...
4056 View Code Duplication
		if ($context['allow_calendar'] && $context['allow_calendar_event'] && empty($modSettings['cal_allow_unlinked']) && !$user_info['is_admin'])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4057
		{
4058
			$boards_can_post = boardsAllowedTo('post_new');
4059
			$context['allow_calendar_event'] &= !empty($boards_can_post);
4060
		}
4061
	}
4062
4063
	// There is some menu stuff we need to do if we're coming at this from a non-guest perspective.
4064
	if (!$context['user']['is_guest'])
4065
	{
4066
		addInlineJavaScript('
4067
	var user_menus = new smc_PopupMenu();
4068
	user_menus.add("profile", "' . $scripturl . '?action=profile;area=popup");
4069
	user_menus.add("alerts", "' . $scripturl . '?action=profile;area=alerts_popup;u='. $context['user']['id'] .'");', true);
4070
		if ($context['allow_pm'])
4071
			addInlineJavaScript('
4072
	user_menus.add("pm", "' . $scripturl . '?action=pm;sa=popup");', true);
4073
4074
		if (!empty($modSettings['enable_ajax_alerts']))
4075
		{
4076
			require_once($sourcedir . '/Subs-Notify.php');
4077
4078
			$timeout = getNotifyPrefs($context['user']['id'], 'alert_timeout', true);
4079
			$timeout = empty($timeout) ? 10000 : $timeout[$context['user']['id']]['alert_timeout'] * 1000;
4080
4081
			addInlineJavaScript('
4082
	var new_alert_title = "' . $context['forum_name'] . '";
4083
	var alert_timeout = ' . $timeout . ';');
4084
			loadJavaScriptFile('alerts.js', array(), 'smf_alerts');
4085
		}
4086
	}
4087
4088
	// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
4089
	if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
4090
	{
4091
		$buttons = array(
4092
			'home' => array(
4093
				'title' => $txt['home'],
4094
				'href' => $scripturl,
4095
				'show' => true,
4096
				'sub_buttons' => array(
4097
				),
4098
				'is_last' => $context['right_to_left'],
4099
			),
4100
			'search' => array(
4101
				'title' => $txt['search'],
4102
				'href' => $scripturl . '?action=search',
4103
				'show' => $context['allow_search'],
4104
				'sub_buttons' => array(
4105
				),
4106
			),
4107
			'admin' => array(
4108
				'title' => $txt['admin'],
4109
				'href' => $scripturl . '?action=admin',
4110
				'show' => $context['allow_admin'],
4111
				'sub_buttons' => array(
4112
					'featuresettings' => array(
4113
						'title' => $txt['modSettings_title'],
4114
						'href' => $scripturl . '?action=admin;area=featuresettings',
4115
						'show' => allowedTo('admin_forum'),
4116
					),
4117
					'packages' => array(
4118
						'title' => $txt['package'],
4119
						'href' => $scripturl . '?action=admin;area=packages',
4120
						'show' => allowedTo('admin_forum'),
4121
					),
4122
					'errorlog' => array(
4123
						'title' => $txt['errlog'],
4124
						'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
4125
						'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']),
4126
					),
4127
					'permissions' => array(
4128
						'title' => $txt['edit_permissions'],
4129
						'href' => $scripturl . '?action=admin;area=permissions',
4130
						'show' => allowedTo('manage_permissions'),
4131
					),
4132
					'memberapprove' => array(
4133
						'title' => $txt['approve_members_waiting'],
4134
						'href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve',
4135
						'show' => !empty($context['unapproved_members']),
4136
						'is_last' => true,
4137
					),
4138
				),
4139
			),
4140
			'moderate' => array(
4141
				'title' => $txt['moderate'],
4142
				'href' => $scripturl . '?action=moderate',
4143
				'show' => $context['allow_moderation_center'],
4144
				'sub_buttons' => array(
4145
					'modlog' => array(
4146
						'title' => $txt['modlog_view'],
4147
						'href' => $scripturl . '?action=moderate;area=modlog',
4148
						'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4149
					),
4150
					'poststopics' => array(
4151
						'title' => $txt['mc_unapproved_poststopics'],
4152
						'href' => $scripturl . '?action=moderate;area=postmod;sa=posts',
4153
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4154
					),
4155
					'attachments' => array(
4156
						'title' => $txt['mc_unapproved_attachments'],
4157
						'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments',
4158
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4159
					),
4160
					'reports' => array(
4161
						'title' => $txt['mc_reported_posts'],
4162
						'href' => $scripturl . '?action=moderate;area=reportedposts',
4163
						'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4164
					),
4165
					'reported_members' => array(
4166
						'title' => $txt['mc_reported_members'],
4167
						'href' => $scripturl . '?action=moderate;area=reportedmembers',
4168
						'show' => allowedTo('moderate_forum'),
4169
						'is_last' => true,
4170
					)
4171
				),
4172
			),
4173
			'calendar' => array(
4174
				'title' => $txt['calendar'],
4175
				'href' => $scripturl . '?action=calendar',
4176
				'show' => $context['allow_calendar'],
4177
				'sub_buttons' => array(
4178
					'view' => array(
4179
						'title' => $txt['calendar_menu'],
4180
						'href' => $scripturl . '?action=calendar',
4181
						'show' => $context['allow_calendar_event'],
4182
					),
4183
					'post' => array(
4184
						'title' => $txt['calendar_post_event'],
4185
						'href' => $scripturl . '?action=calendar;sa=post',
4186
						'show' => $context['allow_calendar_event'],
4187
						'is_last' => true,
4188
					),
4189
				),
4190
			),
4191
			'mlist' => array(
4192
				'title' => $txt['members_title'],
4193
				'href' => $scripturl . '?action=mlist',
4194
				'show' => $context['allow_memberlist'],
4195
				'sub_buttons' => array(
4196
					'mlist_view' => array(
4197
						'title' => $txt['mlist_menu_view'],
4198
						'href' => $scripturl . '?action=mlist',
4199
						'show' => true,
4200
					),
4201
					'mlist_search' => array(
4202
						'title' => $txt['mlist_search'],
4203
						'href' => $scripturl . '?action=mlist;sa=search',
4204
						'show' => true,
4205
						'is_last' => true,
4206
					),
4207
				),
4208
			),
4209
			'signup' => array(
4210
				'title' => $txt['register'],
4211
				'href' => $scripturl . '?action=signup',
4212
				'show' => $user_info['is_guest'] && $context['can_register'],
4213
				'sub_buttons' => array(
4214
				),
4215
				'is_last' => !$context['right_to_left'],
4216
			),
4217
			'logout' => array(
4218
				'title' => $txt['logout'],
4219
				'href' => $scripturl . '?action=logout;%1$s=%2$s',
4220
				'show' => !$user_info['is_guest'],
4221
				'sub_buttons' => array(
4222
				),
4223
				'is_last' => !$context['right_to_left'],
4224
			),
4225
		);
4226
4227
		// Allow editing menu buttons easily.
4228
		call_integration_hook('integrate_menu_buttons', array(&$buttons));
4229
4230
		// Now we put the buttons in the context so the theme can use them.
4231
		$menu_buttons = array();
4232
		foreach ($buttons as $act => $button)
4233
			if (!empty($button['show']))
4234
			{
4235
				$button['active_button'] = false;
4236
4237
				// This button needs some action.
4238
				if (isset($button['action_hook']))
4239
					$needs_action_hook = true;
4240
4241
				// Make sure the last button truly is the last button.
4242
				if (!empty($button['is_last']))
4243
				{
4244
					if (isset($last_button))
4245
						unset($menu_buttons[$last_button]['is_last']);
4246
					$last_button = $act;
4247
				}
4248
4249
				// Go through the sub buttons if there are any.
4250
				if (!empty($button['sub_buttons']))
4251
					foreach ($button['sub_buttons'] as $key => $subbutton)
4252
					{
4253
						if (empty($subbutton['show']))
4254
							unset($button['sub_buttons'][$key]);
4255
4256
						// 2nd level sub buttons next...
4257
						if (!empty($subbutton['sub_buttons']))
4258
						{
4259
							foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2)
4260
							{
4261
								if (empty($sub_button2['show']))
4262
									unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
4263
							}
4264
						}
4265
					}
4266
4267
				// Does this button have its own icon?
4268
				if (isset($button['icon']) && file_exists($settings['theme_dir'] . '/images/' . $button['icon']))
4269
					$button['icon'] = '<img src="' . $settings['images_url'] . '/' . $button['icon'] . '" alt="">';
4270
				elseif (isset($button['icon']) && file_exists($settings['default_theme_dir'] . '/images/' . $button['icon']))
4271
					$button['icon'] = '<img src="' . $settings['default_images_url'] . '/' . $button['icon'] . '" alt="">';
4272
				elseif (isset($button['icon']))
4273
					$button['icon'] = '<span class="generic_icons ' . $button['icon'] . '"></span>';
4274
				else
4275
					$button['icon'] = '<span class="generic_icons ' . $act . '"></span>';
4276
4277
				$menu_buttons[$act] = $button;
4278
			}
4279
4280
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
4281
			cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
4282
	}
4283
4284
	$context['menu_buttons'] = $menu_buttons;
4285
4286
	// Logging out requires the session id in the url.
4287
	if (isset($context['menu_buttons']['logout']))
4288
		$context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']);
4289
4290
	// Figure out which action we are doing so we can set the active tab.
4291
	// Default to home.
4292
	$current_action = 'home';
4293
4294
	if (isset($context['menu_buttons'][$context['current_action']]))
4295
		$current_action = $context['current_action'];
4296
	elseif ($context['current_action'] == 'search2')
4297
		$current_action = 'search';
4298
	elseif ($context['current_action'] == 'theme')
4299
		$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin';
4300
	elseif ($context['current_action'] == 'register2')
4301
		$current_action = 'register';
4302
	elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder'))
4303
		$current_action = 'login';
4304
	elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center'])
4305
		$current_action = 'moderate';
4306
4307
	// There are certain exceptions to the above where we don't want anything on the menu highlighted.
4308
	if ($context['current_action'] == 'profile' && !empty($context['user']['is_owner']))
4309
	{
4310
		$current_action = !empty($_GET['area']) && $_GET['area'] == 'showalerts' ? 'self_alerts' : 'self_profile';
4311
		$context[$current_action] = true;
4312
	}
4313
	elseif ($context['current_action'] == 'pm')
4314
	{
4315
		$current_action = 'self_pm';
4316
		$context['self_pm'] = true;
4317
	}
4318
4319
	$total_mod_reports = 0;
4320
4321
	if (!empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1' && !empty($context['open_mod_reports']))
4322
	{
4323
		$total_mod_reports = $context['open_mod_reports'];
4324
		$context['menu_buttons']['moderate']['sub_buttons']['reports']['title'] .= ' <span class="amt">' . $context['open_mod_reports'] . '</span>';
4325
	}
4326
4327
	// Show how many errors there are
4328 View Code Duplication
	if (!empty($context['num_errors']) && allowedTo('admin_forum'))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4329
	{
4330
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4331
		$context['menu_buttons']['admin']['sub_buttons']['errorlog']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4332
	}
4333
4334
	/**
4335
	 * @todo For some reason, $context['open_member_reports'] isn't getting set
4336
	 */
4337
	if (!empty($context['open_member_reports']) && allowedTo('moderate_forum'))
4338
	{
4339
		$total_mod_reports += $context['open_member_reports'];
4340
		$context['menu_buttons']['moderate']['sub_buttons']['reported_members']['title'] .= ' <span class="amt">' . $context['open_member_reports'] . '</span>';
4341
	}
4342
4343 View Code Duplication
	if (!empty($context['unapproved_members']))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4344
	{
4345
		$context['menu_buttons']['admin']['sub_buttons']['memberapprove']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4346
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4347
	}
4348
4349
	// Do we have any open reports?
4350
	if ($total_mod_reports > 0)
4351
	{
4352
		$context['menu_buttons']['moderate']['title'] .= ' <span class="amt">' . $total_mod_reports . '</span>';
4353
	}
4354
4355
	// Not all actions are simple.
4356
	if (!empty($needs_action_hook))
4357
		call_integration_hook('integrate_current_action', array(&$current_action));
4358
4359
	if (isset($context['menu_buttons'][$current_action]))
4360
		$context['menu_buttons'][$current_action]['active_button'] = true;
4361
}
4362
4363
/**
4364
 * Generate a random seed and ensure it's stored in settings.
4365
 */
4366
function smf_seed_generator()
4367
{
4368
	updateSettings(array('rand_seed' => microtime() * 1000000));
4369
}
4370
4371
/**
4372
 * Process functions of an integration hook.
4373
 * calls all functions of the given hook.
4374
 * supports static class method calls.
4375
 *
4376
 * @param string $hook The hook name
4377
 * @param array $parameters An array of parameters this hook implements
4378
 * @return array The results of the functions
4379
 */
4380
function call_integration_hook($hook, $parameters = array())
4381
{
4382
	global $modSettings, $settings, $boarddir, $sourcedir, $db_show_debug;
4383
	global $context, $txt;
4384
4385
	if ($db_show_debug === true)
4386
		$context['debug']['hooks'][] = $hook;
4387
4388
	// Need to have some control.
4389
	if (!isset($context['instances']))
4390
		$context['instances'] = array();
4391
4392
	$results = array();
4393
	if (empty($modSettings[$hook]))
4394
		return $results;
4395
4396
	// Define some needed vars.
4397
	$function = false;
4398
4399
	$functions = explode(',', $modSettings[$hook]);
4400
	// Loop through each function.
4401
	foreach ($functions as $function)
4402
	{
4403
		// Hook has been marked as "disabled". Skip it!
4404
		if (strpos($function, '!') !== false)
4405
			continue;
4406
4407
		$call = call_helper($function, true);
4408
4409
		// Is it valid?
4410
		if (!empty($call))
4411
			$results[$function] = call_user_func_array($call, $parameters);
4412
4413
		// Whatever it was suppose to call, it failed :(
4414
		elseif (!empty($function))
4415
		{
4416
			loadLanguage('Errors');
4417
4418
			// Get a full path to show on error.
4419
			if (strpos($function, '|') !== false)
4420
			{
4421
				list ($file, $string) = explode('|', $function);
4422
				$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'])));
4423
				log_error(sprintf($txt['hook_fail_call_to'], $string, $absPath), 'general');
4424
			}
4425
4426
			// "Assume" the file resides on $boarddir somewhere...
4427
			else
4428
				log_error(sprintf($txt['hook_fail_call_to'], $function, $boarddir), 'general');
4429
		}
4430
	}
4431
4432
	return $results;
4433
}
4434
4435
/**
4436
 * Add a function for integration hook.
4437
 * does nothing if the function is already added.
4438
 *
4439
 * @param string $hook The complete hook name.
4440
 * @param string $function The function name. Can be a call to a method via Class::method.
4441
 * @param bool $permanent If true, updates the value in settings table.
4442
 * @param string $file The file. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4443
 * @param bool $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4444
 */
4445
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
4446
{
4447
	global $smcFunc, $modSettings;
4448
4449
	// Any objects?
4450
	if ($object)
4451
		$function = $function . '#';
4452
4453
	// Any files  to load?
4454
	if (!empty($file) && is_string($file))
4455
		$function = $file . (!empty($function) ? '|' . $function : '');
4456
4457
	// Get the correct string.
4458
	$integration_call = $function;
4459
4460
	// Is it going to be permanent?
4461
	if ($permanent)
4462
	{
4463
		$request = $smcFunc['db_query']('', '
4464
			SELECT value
4465
			FROM {db_prefix}settings
4466
			WHERE variable = {string:variable}',
4467
			array(
4468
				'variable' => $hook,
4469
			)
4470
		);
4471
		list ($current_functions) = $smcFunc['db_fetch_row']($request);
4472
		$smcFunc['db_free_result']($request);
4473
4474
		if (!empty($current_functions))
4475
		{
4476
			$current_functions = explode(',', $current_functions);
4477
			if (in_array($integration_call, $current_functions))
4478
				return;
4479
4480
			$permanent_functions = array_merge($current_functions, array($integration_call));
4481
		}
4482
		else
4483
			$permanent_functions = array($integration_call);
4484
4485
		updateSettings(array($hook => implode(',', $permanent_functions)));
4486
	}
4487
4488
	// Make current function list usable.
4489
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4490
4491
	// Do nothing, if it's already there.
4492
	if (in_array($integration_call, $functions))
4493
		return;
4494
4495
	$functions[] = $integration_call;
4496
	$modSettings[$hook] = implode(',', $functions);
4497
}
4498
4499
/**
4500
 * Remove an integration hook function.
4501
 * Removes the given function from the given hook.
4502
 * Does nothing if the function is not available.
4503
 *
4504
 * @param string $hook The complete hook name.
4505
 * @param string $function The function name. Can be a call to a method via Class::method.
4506
 * @param boolean $permanent Irrelevant for the function itself but need to declare it to match
4507
 * @param string $file The filename. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4508
 * @param boolean $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4509
 * @see add_integration_function
4510
 */
4511
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...
4512
{
4513
	global $smcFunc, $modSettings;
4514
4515
	// Any objects?
4516
	if ($object)
4517
		$function = $function . '#';
4518
4519
	// Any files  to load?
4520
	if (!empty($file) && is_string($file))
4521
		$function = $file . '|' . $function;
4522
4523
	// Get the correct string.
4524
	$integration_call = $function;
4525
4526
	// Get the permanent functions.
4527
	$request = $smcFunc['db_query']('', '
4528
		SELECT value
4529
		FROM {db_prefix}settings
4530
		WHERE variable = {string:variable}',
4531
		array(
4532
			'variable' => $hook,
4533
		)
4534
	);
4535
	list ($current_functions) = $smcFunc['db_fetch_row']($request);
4536
	$smcFunc['db_free_result']($request);
4537
4538
	if (!empty($current_functions))
4539
	{
4540
		$current_functions = explode(',', $current_functions);
4541
4542
		if (in_array($integration_call, $current_functions))
4543
			updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call)))));
4544
	}
4545
4546
	// Turn the function list into something usable.
4547
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4548
4549
	// You can only remove it if it's available.
4550
	if (!in_array($integration_call, $functions))
4551
		return;
4552
4553
	$functions = array_diff($functions, array($integration_call));
4554
	$modSettings[$hook] = implode(',', $functions);
4555
}
4556
4557
/**
4558
 * Receives a string and tries to figure it out if its a method or a function.
4559
 * If a method is found, it looks for a "#" which indicates SMF should create a new instance of the given class.
4560
 * Checks the string/array for is_callable() and return false/fatal_lang_error is the given value results in a non callable string/array.
4561
 * Prepare and returns a callable depending on the type of method/function found.
4562
 *
4563
 * @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)
4564
 * @param boolean $return If true, the function will not call the function/method but instead will return the formatted string.
4565
 * @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.
4566
 */
4567
function call_helper($string, $return = false)
4568
{
4569
	global $context, $smcFunc, $txt, $db_show_debug;
4570
4571
	// Really?
4572
	if (empty($string))
4573
		return false;
4574
4575
	// An array? should be a "callable" array IE array(object/class, valid_callable).
4576
	// A closure? should be a callable one.
4577
	if (is_array($string) || $string instanceof Closure)
4578
		return $return ? $string : (is_callable($string) ? call_user_func($string) : false);
4579
4580
	// No full objects, sorry! pass a method or a property instead!
4581
	if (is_object($string))
4582
		return false;
4583
4584
	// Stay vitaminized my friends...
4585
	$string = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($string));
4586
4587
	// The soon to be populated var.
4588
	$func = false;
0 ignored issues
show
Unused Code introduced by
$func 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...
4589
4590
	// Is there a file to load?
4591
	$string = load_file($string);
4592
4593
	// Loaded file failed
4594
	if (empty($string))
4595
		return false;
4596
4597
	// Found a method.
4598
	if (strpos($string, '::') !== false)
4599
	{
4600
		list ($class, $method) = explode('::', $string);
4601
4602
		// Check if a new object will be created.
4603
		if (strpos($method, '#') !== false)
4604
		{
4605
			// Need to remove the # thing.
4606
			$method = str_replace('#', '', $method);
4607
4608
			// Don't need to create a new instance for every method.
4609
			if (empty($context['instances'][$class]) || !($context['instances'][$class] instanceof $class))
4610
			{
4611
				$context['instances'][$class] = new $class;
4612
4613
				// Add another one to the list.
4614
				if ($db_show_debug === true)
4615
				{
4616
					if (!isset($context['debug']['instances']))
4617
						$context['debug']['instances'] = array();
4618
4619
					$context['debug']['instances'][$class] = $class;
4620
				}
4621
			}
4622
4623
			$func = array($context['instances'][$class], $method);
4624
		}
4625
4626
		// Right then. This is a call to a static method.
4627
		else
4628
			$func = array($class, $method);
4629
	}
4630
4631
	// Nope! just a plain regular function.
4632
	else
4633
		$func = $string;
4634
4635
	// Right, we got what we need, time to do some checks.
4636
	if (!is_callable($func, false, $callable_name))
4637
	{
4638
		loadLanguage('Errors');
4639
		log_error(sprintf($txt['subAction_fail'], $callable_name), 'general');
4640
4641
		// Gotta tell everybody.
4642
		return false;
4643
	}
4644
4645
	// Everything went better than expected.
4646
	else
4647
	{
4648
		// What are we gonna do about it?
4649
		if ($return)
4650
			return $func;
4651
4652
		// If this is a plain function, avoid the heat of calling call_user_func().
4653
		else
4654
		{
4655
			if (is_array($func))
4656
				call_user_func($func);
4657
4658
			else
4659
				$func();
4660
		}
4661
	}
4662
}
4663
4664
/**
4665
 * Receives a string and tries to figure it out if it contains info to load a file.
4666
 * Checks for a | (pipe) symbol and tries to load a file with the info given.
4667
 * 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.
4668
 *
4669
 * @param string $string The string containing a valid format.
4670
 * @return string|boolean The given string with the pipe and file info removed. Boolean false if the file couldn't be loaded.
4671
 */
4672
function load_file($string)
4673
{
4674
	global $sourcedir, $txt, $boarddir, $settings;
4675
4676
	if (empty($string))
4677
		return false;
4678
4679
	if (strpos($string, '|') !== false)
4680
	{
4681
		list ($file, $string) = explode('|', $string);
4682
4683
		// Match the wildcards to their regular vars.
4684
		if (empty($settings['theme_dir']))
4685
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
4686
4687
		else
4688
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
4689
4690
		// Load the file if it can be loaded.
4691
		if (file_exists($absPath))
4692
			require_once($absPath);
4693
4694
		// No? try a fallback to $sourcedir
4695
		else
4696
		{
4697
			$absPath = $sourcedir .'/'. $file;
4698
4699
			if (file_exists($absPath))
4700
				require_once($absPath);
4701
4702
			// Sorry, can't do much for you at this point.
4703
			else
4704
			{
4705
				loadLanguage('Errors');
4706
				log_error(sprintf($txt['hook_fail_loading_file'], $absPath), 'general');
4707
4708
				// File couldn't be loaded.
4709
				return false;
4710
			}
4711
		}
4712
	}
4713
4714
	return $string;
4715
}
4716
4717
/**
4718
 * Prepares an array of "likes" info for the topic specified by $topic
4719
 * @param integer $topic The topic ID to fetch the info from.
4720
 * @return array An array of IDs of messages in the specified topic that the current user likes
4721
 */
4722
function prepareLikesContext($topic)
4723
{
4724
	global $user_info, $smcFunc;
4725
4726
	// Make sure we have something to work with.
4727
	if (empty($topic))
4728
		return array();
4729
4730
4731
	// We already know the number of likes per message, we just want to know whether the current user liked it or not.
4732
	$user = $user_info['id'];
4733
	$cache_key = 'likes_topic_' . $topic . '_' . $user;
4734
	$ttl = 180;
4735
4736
	if (($temp = cache_get_data($cache_key, $ttl)) === null)
4737
	{
4738
		$temp = array();
4739
		$request = $smcFunc['db_query']('', '
4740
			SELECT content_id
4741
			FROM {db_prefix}user_likes AS l
4742
				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
4743
			WHERE l.id_member = {int:current_user}
4744
				AND l.content_type = {literal:msg}
4745
				AND m.id_topic = {int:topic}',
4746
			array(
4747
				'current_user' => $user,
4748
				'topic' => $topic,
4749
			)
4750
		);
4751
		while ($row = $smcFunc['db_fetch_assoc']($request))
4752
			$temp[] = (int) $row['content_id'];
4753
4754
		cache_put_data($cache_key, $temp, $ttl);
4755
	}
4756
4757
	return $temp;
4758
}
4759
4760
/**
4761
 * Microsoft uses their own character set Code Page 1252 (CP1252), which is a
4762
 * superset of ISO 8859-1, defining several characters between DEC 128 and 159
4763
 * that are not normally displayable.  This converts the popular ones that
4764
 * appear from a cut and paste from windows.
4765
 *
4766
 * @param string $string The string
4767
 * @return string The sanitized string
4768
 */
4769
function sanitizeMSCutPaste($string)
4770
{
4771
	global $context;
4772
4773
	if (empty($string))
4774
		return $string;
4775
4776
	// UTF-8 occurences of MS special characters
4777
	$findchars_utf8 = array(
4778
		"\xe2\80\x9a",	// single low-9 quotation mark
4779
		"\xe2\80\x9e",	// double low-9 quotation mark
4780
		"\xe2\80\xa6",	// horizontal ellipsis
4781
		"\xe2\x80\x98",	// left single curly quote
4782
		"\xe2\x80\x99",	// right single curly quote
4783
		"\xe2\x80\x9c",	// left double curly quote
4784
		"\xe2\x80\x9d",	// right double curly quote
4785
		"\xe2\x80\x93",	// en dash
4786
		"\xe2\x80\x94",	// em dash
4787
	);
4788
4789
	// windows 1252 / iso equivalents
4790
	$findchars_iso = array(
4791
		chr(130),
4792
		chr(132),
4793
		chr(133),
4794
		chr(145),
4795
		chr(146),
4796
		chr(147),
4797
		chr(148),
4798
		chr(150),
4799
		chr(151),
4800
	);
4801
4802
	// safe replacements
4803
	$replacechars = array(
4804
		',',	// &sbquo;
4805
		',,',	// &bdquo;
4806
		'...',	// &hellip;
4807
		"'",	// &lsquo;
4808
		"'",	// &rsquo;
4809
		'"',	// &ldquo;
4810
		'"',	// &rdquo;
4811
		'-',	// &ndash;
4812
		'--',	// &mdash;
4813
	);
4814
4815
	if ($context['utf8'])
4816
		$string = str_replace($findchars_utf8, $replacechars, $string);
4817
	else
4818
		$string = str_replace($findchars_iso, $replacechars, $string);
4819
4820
	return $string;
4821
}
4822
4823
/**
4824
 * Decode numeric html entities to their ascii or UTF8 equivalent character.
4825
 *
4826
 * Callback function for preg_replace_callback in subs-members
4827
 * Uses capture group 2 in the supplied array
4828
 * Does basic scan to ensure characters are inside a valid range
4829
 *
4830
 * @param array $matches An array of matches (relevant info should be the 3rd item)
4831
 * @return string A fixed string
4832
 */
4833
function replaceEntities__callback($matches)
4834
{
4835
	global $context;
4836
4837
	if (!isset($matches[2]))
4838
		return '';
4839
4840
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4841
4842
	// remove left to right / right to left overrides
4843
	if ($num === 0x202D || $num === 0x202E)
4844
		return '';
4845
4846
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
4847
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
4848
		return '&#' . $num . ';';
4849
4850
	if (empty($context['utf8']))
4851
	{
4852
		// no control characters
4853
		if ($num < 0x20)
4854
			return '';
4855
		// text is text
4856
		elseif ($num < 0x80)
4857
			return chr($num);
4858
		// all others get html-ised
4859
		else
4860
			return '&#' . $matches[2] . ';';
4861
	}
4862
	else
4863
	{
4864
		// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
4865
		// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
4866
		if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
4867
			return '';
4868
		// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4869
		elseif ($num < 0x80)
4870
			return chr($num);
4871
		// <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...
4872 View Code Duplication
		elseif ($num < 0x800)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4873
			return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4874
		// < 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...
4875 View Code Duplication
		elseif ($num < 0x10000)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4876
			return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4877
		// <= 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...
4878 View Code Duplication
		else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4879
			return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4880
	}
4881
}
4882
4883
/**
4884
 * Converts html entities to utf8 equivalents
4885
 *
4886
 * Callback function for preg_replace_callback
4887
 * Uses capture group 1 in the supplied array
4888
 * Does basic checks to keep characters inside a viewable range.
4889
 *
4890
 * @param array $matches An array of matches (relevant info should be the 2nd item in the array)
4891
 * @return string The fixed string
4892
 */
4893
function fixchar__callback($matches)
4894
{
4895
	if (!isset($matches[1]))
4896
		return '';
4897
4898
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
4899
4900
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
4901
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
4902
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
4903
		return '';
4904
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4905
	elseif ($num < 0x80)
4906
		return chr($num);
4907
	// <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...
4908 View Code Duplication
	elseif ($num < 0x800)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4909
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4910
	// < 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...
4911 View Code Duplication
	elseif ($num < 0x10000)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4912
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4913
	// <= 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...
4914 View Code Duplication
	else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4915
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4916
}
4917
4918
/**
4919
 * Strips out invalid html entities, replaces others with html style &#123; codes
4920
 *
4921
 * Callback function used of preg_replace_callback in smcFunc $ent_checks, for example
4922
 * strpos, strlen, substr etc
4923
 *
4924
 * @param array $matches An array of matches (relevant info should be the 3rd item in the array)
4925
 * @return string The fixed string
4926
 */
4927
function entity_fix__callback($matches)
4928
{
4929
	if (!isset($matches[2]))
4930
		return '';
4931
4932
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4933
4934
	// we don't allow control characters, characters out of range, byte markers, etc
4935
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
4936
		return '';
4937
	else
4938
		return '&#' . $num . ';';
4939
}
4940
4941
/**
4942
 * Return a Gravatar URL based on
4943
 * - the supplied email address,
4944
 * - the global maximum rating,
4945
 * - the global default fallback,
4946
 * - maximum sizes as set in the admin panel.
4947
 *
4948
 * It is SSL aware, and caches most of the parameters.
4949
 *
4950
 * @param string $email_address The user's email address
4951
 * @return string The gravatar URL
4952
 */
4953
function get_gravatar_url($email_address)
4954
{
4955
	global $modSettings, $smcFunc;
4956
	static $url_params = null;
4957
4958
	if ($url_params === null)
4959
	{
4960
		$ratings = array('G', 'PG', 'R', 'X');
4961
		$defaults = array('mm', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank');
4962
		$url_params = array();
4963 View Code Duplication
		if (!empty($modSettings['gravatarMaxRating']) && in_array($modSettings['gravatarMaxRating'], $ratings))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4964
			$url_params[] = 'rating=' . $modSettings['gravatarMaxRating'];
4965 View Code Duplication
		if (!empty($modSettings['gravatarDefault']) && in_array($modSettings['gravatarDefault'], $defaults))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
4966
			$url_params[] = 'default=' . $modSettings['gravatarDefault'];
4967
		if (!empty($modSettings['avatar_max_width_external']))
4968
			$size_string = (int) $modSettings['avatar_max_width_external'];
4969
		if (!empty($modSettings['avatar_max_height_external']) && !empty($size_string))
4970
			if ((int) $modSettings['avatar_max_height_external'] < $size_string)
4971
				$size_string = $modSettings['avatar_max_height_external'];
4972
4973
		if (!empty($size_string))
4974
			$url_params[] = 's=' . $size_string;
4975
	}
4976
	$http_method = !empty($modSettings['force_ssl']) && $modSettings['force_ssl'] == 2 ? 'https://secure' : 'http://www';
4977
4978
	return $http_method . '.gravatar.com/avatar/' . md5($smcFunc['strtolower']($email_address)) . '?' . implode('&', $url_params);
4979
}
4980
4981
/**
4982
 * Get a list of timezoned.
4983
 *
4984
 * @return array An array of timezone info.
4985
 */
4986
function smf_list_timezones()
4987
{
4988
	return array(
4989
		'' => '(Forum Default)',
4990
		'UTC' => '[UTC] UTC',
4991
		'Pacific/Midway' => '[UTC-11:00] Midway Island, Samoa',
4992
		'America/Adak' => '[UTC-10:00] Hawaii-Aleutian',
4993
		'Pacific/Honolulu' => '[UTC-10:00] Hawaii',
4994
		'Pacific/Marquesas' => '[UTC-09:30] Marquesas Islands',
4995
		'Pacific/Gambier' => '[UTC-09:00] Gambier Islands',
4996
		'America/Anchorage' => '[UTC-09:00] Alaska',
4997
		'America/Ensenada' => '[UTC-08:00] Tijuana, Baja California',
4998
		'Pacific/Pitcairn' => '[UTC-08:00] Pitcairn Islands',
4999
		'America/Los_Angeles' => '[UTC-08:00] Pacific Time (USA, Canada)',
5000
		'America/Denver' => '[UTC-07:00] Mountain Time (USA, Canada)',
5001
		'America/Phoenix' => '[UTC-07:00] Arizona',
5002
		'America/Chihuahua' => '[UTC-07:00] Chihuahua, Mazatlan',
5003
		'America/Belize' => '[UTC-06:00] Saskatchewan, Central America',
5004
		'America/Cancun' => '[UTC-06:00] Guadalajara, Mexico City, Monterrey',
5005
		'Chile/EasterIsland' => '[UTC-06:00] Easter Island',
5006
		'America/Chicago' => '[UTC-06:00] Central Time (USA, Canada)',
5007
		'America/New_York' => '[UTC-05:00] Eastern Time (USA, Canada)',
5008
		'America/Havana' => '[UTC-05:00] Cuba',
5009
		'America/Bogota' => '[UTC-05:00] Bogota, Lima, Quito',
5010
		'America/Caracas' => '[UTC-04:30] Caracas',
5011
		'America/Santiago' => '[UTC-04:00] Santiago',
5012
		'America/La_Paz' => '[UTC-04:00] La Paz, San Juan, Manaus',
5013
		'Atlantic/Stanley' => '[UTC-04:00] Falkland Islands',
5014
		'America/Cuiaba' => '[UTC-04:00] Cuiaba',
5015
		'America/Goose_Bay' => '[UTC-04:00] Atlantic Time (Goose Bay)',
5016
		'America/Glace_Bay' => '[UTC-04:00] Atlantic Time (Canada)',
5017
		'America/St_Johns' => '[UTC-03:30] Newfoundland',
5018
		'America/Araguaina' => '[UTC-03:00] Araguaina',
5019
		'America/Montevideo' => '[UTC-03:00] Montevideo',
5020
		'America/Miquelon' => '[UTC-03:00] Saint Pierre and Miquelon',
5021
		'America/Argentina/Buenos_Aires' => '[UTC-03:00] Buenos Aires',
5022
		'America/Sao_Paulo' => '[UTC-03:00] Brasilia',
5023
		'America/Godthab' => '[UTC-02:00] Greenland',
5024
		'America/Noronha' => '[UTC-02:00] Fernando de Noronha',
5025
		'Atlantic/Cape_Verde' => '[UTC-01:00] Cape Verde',
5026
		'Atlantic/Azores' => '[UTC-01:00] Azores',
5027
		'Africa/Abidjan' => '[UTC] Monrovia, Reykjavik',
5028
		'Europe/London' => '[UTC] London, Edinburgh, Dublin, Lisbon (Greenwich Mean Time)',
5029
		'Europe/Brussels' => '[UTC+01:00] Central European Time',
5030
		'Africa/Algiers' => '[UTC+01:00] West Central Africa',
5031
		'Africa/Windhoek' => '[UTC+01:00] Windhoek',
5032
		'Africa/Cairo' => '[UTC+02:00] Cairo',
5033
		'Africa/Blantyre' => '[UTC+02:00] Harare, Maputo, Pretoria',
5034
		'Asia/Jerusalem' => '[UTC+02:00] Jerusalem',
5035
		'Europe/Minsk' => '[UTC+02:00] Minsk',
5036
		'Asia/Damascus' => '[UTC+02:00] Damascus, Nicosia, Gaza, Beirut',
5037
		'Africa/Addis_Ababa' => '[UTC+03:00] Addis Ababa, Nairobi',
5038
		'Asia/Tehran' => '[UTC+03:30] Tehran',
5039
		'Europe/Moscow' => '[UTC+04:00] Moscow, St. Petersburg, Volgograd',
5040
		'Asia/Dubai' => '[UTC+04:00] Abu Dhabi, Muscat',
5041
		'Asia/Baku' => '[UTC+04:00] Baku',
5042
		'Asia/Yerevan' => '[UTC+04:00] Yerevan',
5043
		'Asia/Kabul' => '[UTC+04:30] Kabul',
5044
		'Asia/Tashkent' => '[UTC+05:00] Tashkent',
5045
		'Asia/Kolkata' => '[UTC+05:30] Chennai, Kolkata, Mumbai, New Delhi',
5046
		'Asia/Katmandu' => '[UTC+05:45] Kathmandu',
5047
		'Asia/Yekaterinburg' => '[UTC+06:00] Yekaterinburg, Tyumen',
5048
		'Asia/Dhaka' => '[UTC+06:00] Astana, Thimphu, Dhaka',
5049
		'Asia/Novosibirsk' => '[UTC+06:00] Omsk, Novosibirsk',
5050
		'Asia/Rangoon' => '[UTC+06:30] Yangon Rangoon',
5051
		'Asia/Bangkok' => '[UTC+07:00] Bangkok, Hanoi, Jakarta',
5052
		'Asia/Krasnoyarsk' => '[UTC+08:00] Krasnoyarsk',
5053
		'Asia/Hong_Kong' => '[UTC+08:00] Beijing, Chongqing, Hong Kong, Urumqi',
5054
		'Asia/Ulaanbaatar' => '[UTC+08:00] Ulaan Bataar',
5055
		'Asia/Irkutsk' => '[UTC+09:00] Irkutsk',
5056
		'Australia/Perth' => '[UTC+08:00] Perth',
5057
		'Australia/Eucla' => '[UTC+08:45] Eucla',
5058
		'Asia/Tokyo' => '[UTC+09:00] Tokyo, Osaka, Sapporo',
5059
		'Asia/Seoul' => '[UTC+09:00] Seoul',
5060
		'Australia/Adelaide' => '[UTC+09:30] Adelaide',
5061
		'Australia/Darwin' => '[UTC+09:30] Darwin',
5062
		'Australia/Brisbane' => '[UTC+10:00] Brisbane, Guam',
5063
		'Australia/Sydney' => '[UTC+10:00] Sydney, Hobart',
5064
		'Asia/Yakutsk' => '[UTC+10:00] Yakutsk',
5065
		'Australia/Lord_Howe' => '[UTC+10:30] Lord Howe Island',
5066
		'Asia/Vladivostok' => '[UTC+11:00] Vladivostok',
5067
		'Pacific/Noumea' => '[UTC+11:00] Solomon Islands, New Caledonia',
5068
		'Pacific/Norfolk' => '[UTC+11:30] Norfolk Island',
5069
		'Pacific/Auckland' => '[UTC+12:00] Auckland, Wellington',
5070
		'Asia/Magadan' => '[UTC+12:00] Magadan, Kamchatka, Anadyr',
5071
		'Pacific/Fiji' => '[UTC+12:00] Fiji',
5072
		'Pacific/Majuro' => '[UTC+12:00] Marshall Islands',
5073
		'Pacific/Chatham' => '[UTC+12:45] Chatham Islands',
5074
		'Pacific/Tongatapu' => '[UTC+13:00] Nuku\'alofa',
5075
		'Pacific/Kiritimati' => '[UTC+14:00] Kiritimati',
5076
	);
5077
}
5078
5079
/**
5080
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
5081
 * @return binary The IP address in binary or false
5082
 */
5083
function inet_ptod($ip_address)
5084
{
5085
	if (!isValidIP($ip_address))
5086
		return $ip_address;
5087
5088
	$bin = inet_pton($ip_address);
5089
	return $bin;
5090
}
5091
5092
/**
5093
 * @param binary $bin An IP address in IPv4, IPv6 (Either string (postgresql) or binary (other databases))
5094
 * @return string The IP address in presentation format or false on error
5095
 */
5096
function inet_dtop($bin)
5097
{
5098
	if(empty($bin))
5099
		return '';
5100
5101
	global $db_type;
5102
5103
	if ($db_type == 'postgresql')
5104
		return $bin;
5105
5106
	$ip_address = inet_ntop($bin);
5107
5108
	return $ip_address;
5109
}
5110
5111
/**
5112
 * Safe serialize() and unserialize() replacements
5113
 *
5114
 * @license Public Domain
5115
 *
5116
 * @author anthon (dot) pang (at) gmail (dot) com
5117
 */
5118
5119
/**
5120
 * Safe serialize() replacement. Recursive
5121
 * - output a strict subset of PHP's native serialized representation
5122
 * - does not serialize objects
5123
 *
5124
 * @param mixed $value
5125
 * @return string
5126
 */
5127
function _safe_serialize($value)
5128
{
5129
	if(is_null($value))
5130
		return 'N;';
5131
5132
	if(is_bool($value))
5133
		return 'b:'. (int) $value .';';
5134
5135
	if(is_int($value))
5136
		return 'i:'. $value .';';
5137
5138
	if(is_float($value))
5139
		return 'd:'. str_replace(',', '.', $value) .';';
5140
5141
	if(is_string($value))
5142
		return 's:'. strlen($value) .':"'. $value .'";';
5143
5144
	if(is_array($value))
5145
	{
5146
		$out = '';
5147
		foreach($value as $k => $v)
5148
			$out .= _safe_serialize($k) . _safe_serialize($v);
5149
5150
		return 'a:'. count($value) .':{'. $out .'}';
5151
	}
5152
5153
	// safe_serialize cannot serialize resources or objects.
5154
	return false;
5155
}
5156
/**
5157
 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issues.
5158
 *
5159
 * @param mixed $value
5160
 * @return string
5161
 */
5162 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...
5163
{
5164
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5165
	if (function_exists('mb_internal_encoding') &&
5166
		(((int) ini_get('mbstring.func_overload')) & 2))
5167
	{
5168
		$mbIntEnc = mb_internal_encoding();
5169
		mb_internal_encoding('ASCII');
5170
	}
5171
5172
	$out = _safe_serialize($value);
5173
5174
	if (isset($mbIntEnc))
5175
		mb_internal_encoding($mbIntEnc);
5176
5177
	return $out;
5178
}
5179
5180
/**
5181
 * Safe unserialize() replacement
5182
 * - accepts a strict subset of PHP's native serialized representation
5183
 * - does not unserialize objects
5184
 *
5185
 * @param string $str
5186
 * @return mixed
5187
 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
5188
 */
5189
function _safe_unserialize($str)
5190
{
5191
	// Input  is not a string.
5192
	if(empty($str) || !is_string($str))
5193
		return false;
5194
5195
	$stack = array();
5196
	$expected = array();
5197
5198
	/*
5199
	 * states:
5200
	 *   0 - initial state, expecting a single value or array
5201
	 *   1 - terminal state
5202
	 *   2 - in array, expecting end of array or a key
5203
	 *   3 - in array, expecting value or another array
5204
	 */
5205
	$state = 0;
5206
	while($state != 1)
5207
	{
5208
		$type = isset($str[0]) ? $str[0] : '';
5209
		if($type == '}')
5210
			$str = substr($str, 1);
5211
5212
		else if($type == 'N' && $str[1] == ';')
5213
		{
5214
			$value = null;
5215
			$str = substr($str, 2);
5216
		}
5217
		else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
5218
		{
5219
			$value = $matches[1] == '1' ? true : false;
5220
			$str = substr($str, 4);
5221
		}
5222
		else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
5223
		{
5224
			$value = (int)$matches[1];
5225
			$str = $matches[2];
5226
		}
5227
		else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
5228
		{
5229
			$value = (float)$matches[1];
5230
			$str = $matches[3];
5231
		}
5232
		else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
5233
		{
5234
			$value = substr($matches[2], 0, (int)$matches[1]);
5235
			$str = substr($matches[2], (int)$matches[1] + 2);
5236
		}
5237
		else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches))
5238
		{
5239
			$expectedLength = (int)$matches[1];
5240
			$str = $matches[2];
5241
		}
5242
5243
		// Object or unknown/malformed type.
5244
		else
5245
			return false;
5246
5247
		switch($state)
5248
		{
5249
			case 3: // In array, expecting value or another array.
5250
				if($type == 'a')
5251
				{
5252
					$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...
5253
					$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...
5254
					$list = &$list[$key];
5255
					$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...
5256
					$state = 2;
5257
					break;
5258
				}
5259
				if($type != '}')
5260
				{
5261
					$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...
5262
					$state = 2;
5263
					break;
5264
				}
5265
5266
				// Missing array value.
5267
				return false;
5268
5269
			case 2: // in array, expecting end of array or a key
5270
				if($type == '}')
5271
				{
5272
					// Array size is less than expected.
5273
					if(count($list) < end($expected))
5274
						return false;
5275
5276
					unset($list);
5277
					$list = &$stack[count($stack)-1];
5278
					array_pop($stack);
5279
5280
					// Go to terminal state if we're at the end of the root array.
5281
					array_pop($expected);
5282
5283
					if(count($expected) == 0)
5284
						$state = 1;
5285
5286
					break;
5287
				}
5288
5289
				if($type == 'i' || $type == 's')
5290
				{
5291
					// Array size exceeds expected length.
5292
					if(count($list) >= end($expected))
5293
						return false;
5294
5295
					$key = $value;
5296
					$state = 3;
5297
					break;
5298
				}
5299
5300
				// Illegal array index type.
5301
				return false;
5302
5303
			// Expecting array or value.
5304
			case 0:
5305
				if($type == 'a')
5306
				{
5307
					$data = array();
5308
					$list = &$data;
5309
					$expected[] = $expectedLength;
5310
					$state = 2;
5311
					break;
5312
				}
5313
5314
				if($type != '}')
5315
				{
5316
					$data = $value;
5317
					$state = 1;
5318
					break;
5319
				}
5320
5321
				// Not in array.
5322
				return false;
5323
		}
5324
	}
5325
5326
	// Trailing data in input.
5327
	if(!empty($str))
5328
		return false;
5329
5330
	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...
5331
}
5332
5333
/**
5334
 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
5335
 *
5336
 * @param string $str
5337
 * @return mixed
5338
 */
5339 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...
5340
{
5341
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5342
	if (function_exists('mb_internal_encoding') &&
5343
		(((int) ini_get('mbstring.func_overload')) & 0x02))
5344
	{
5345
		$mbIntEnc = mb_internal_encoding();
5346
		mb_internal_encoding('ASCII');
5347
	}
5348
5349
	$out = _safe_unserialize($str);
5350
5351
	if (isset($mbIntEnc))
5352
		mb_internal_encoding($mbIntEnc);
5353
5354
	return $out;
5355
}
5356
5357
/**
5358
 * Tries different modes to make file/dirs writable. Wrapper function for chmod()
5359
5360
 * @param string $file The file/dir full path.
5361
 * @param int $value Not needed, added for legacy reasons.
5362
 * @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.
5363
 */
5364
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...
5365
{
5366
	// No file? no checks!
5367
	if (empty($file))
5368
		return false;
5369
5370
	// Already writable?
5371
	if (is_writable($file))
5372
		return true;
5373
5374
	// Do we have a file or a dir?
5375
	$isDir = is_dir($file);
5376
	$isWritable = false;
5377
5378
	// Set different modes.
5379
	$chmodValues = $isDir ? array(0750, 0755, 0775, 0777) : array(0644, 0664, 0666);
5380
5381
	foreach($chmodValues as $val)
5382
	{
5383
		// If it's writable, break out of the loop.
5384
		if (is_writable($file))
5385
		{
5386
			$isWritable = true;
5387
			break;
5388
		}
5389
5390
		else
5391
			@chmod($file, $val);
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...
5392
	}
5393
5394
	return $isWritable;
5395
}
5396
5397
/**
5398
 * Wrapper function for json_decode() with error handling.
5399
5400
 * @param string $json The string to decode.
5401
 * @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.
5402
 * @param bool $logIt To specify if the error will be logged if theres any.
5403
 * @return array Either an empty array or the decoded data as an array.
5404
 */
5405
function smf_json_decode($json, $returnAsArray = false, $logIt = true)
5406
{
5407
	global $txt;
5408
5409
	// Come on...
5410
	if (empty($json) || !is_string($json))
5411
		return array();
5412
5413
	$returnArray = array();
0 ignored issues
show
Unused Code introduced by
$returnArray 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...
5414
	$jsonError = false;
0 ignored issues
show
Unused Code introduced by
$jsonError 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...
5415
5416
	$returnArray = @json_decode($json, $returnAsArray);
5417
5418
	// PHP 5.3 so no json_last_error_msg()
5419
	switch(json_last_error())
5420
	{
5421
		case JSON_ERROR_NONE:
5422
			$jsonError = false;
5423
			break;
5424
		case JSON_ERROR_DEPTH:
5425
			$jsonError =  'JSON_ERROR_DEPTH';
5426
			break;
5427
		case JSON_ERROR_STATE_MISMATCH:
5428
			$jsonError = 'JSON_ERROR_STATE_MISMATCH';
5429
			break;
5430
		case JSON_ERROR_CTRL_CHAR:
5431
			$jsonError = 'JSON_ERROR_CTRL_CHAR';
5432
			break;
5433
		case JSON_ERROR_SYNTAX:
5434
			$jsonError = 'JSON_ERROR_SYNTAX';
5435
			break;
5436
		case JSON_ERROR_UTF8:
5437
			$jsonError = 'JSON_ERROR_UTF8';
5438
			break;
5439
		default:
5440
			$jsonError = 'unknown';
5441
			break;
5442
	}
5443
5444
	// Something went wrong!
5445
	if (!empty($jsonError) && $logIt)
5446
	{
5447
		// Being a wrapper means we lost our smf_error_handler() privileges :(
5448
		$jsonDebug = debug_backtrace();
5449
		$jsonDebug = $jsonDebug[0];
5450
		loadLanguage('Errors');
5451
5452
		if (!empty($jsonDebug))
5453
			log_error($txt['json_'. $jsonError], 'critical', $jsonDebug['file'], $jsonDebug['line']);
5454
5455
		else
5456
			log_error($txt['json_'. $jsonError], 'critical');
5457
5458
		// Everyone expects an array.
5459
		return array();
5460
	}
5461
5462
	return $returnArray;
5463
}
5464
5465
/**
5466
 * Check the given String if he is a valid IPv4 or IPv6
5467
 * return true or false
5468
 */
5469
function isValidIP($IPString)
5470
{
5471
	return filter_var($IPString, FILTER_VALIDATE_IP) !== false;
5472
}
5473
5474
/**
5475
 * Outputs a response.
5476
 * It assumes the data is already a string.
5477
 * @param string $data The data to print
5478
 * @param string $type The content type. Defaults to Json.
5479
 * @return void
5480
 */
5481
function smf_serverResponse($data = '', $type = 'Content-Type: application/json')
5482
{
5483
	global $db_show_debug, $modSettings;
5484
5485
	// Defensive programming anyone?
5486
	if (empty($data))
5487
		return false;
5488
5489
	// Don't need extra stuff...
5490
	$db_show_debug = false;
5491
5492
	// Kill anything else.
5493
	ob_end_clean();
5494
5495
	if (!empty($modSettings['CompressedOutput']))
5496
		@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...
5497
5498
	else
5499
		ob_start();
5500
5501
	// Set the header.
5502
	header($type);
5503
5504
	// Echo!
5505
	echo $data;
5506
5507
	// Done.
5508
	obExit(false);
5509
}
5510
5511
/**
5512
 * Creates an optimized regex to match all known top level domains.
5513
 *
5514
 * The optimized regex is stored in $modSettings['tld_regex'].
5515
 *
5516
 * To update the stored version of the regex to use the latest list of valid TLDs from iana.org, set
5517
 * the $update parameter to true. Updating can take some time, based on network connectivity, so it
5518
 * should normally only be done by calling this function from a background or scheduled task.
5519
 *
5520
 * If $update is not true, but the regex is missing or invalid, the regex will be regenerated from a
5521
 * hard-coded list of TLDs. This regenerated regex will be overwritten on the next scheduled update.
5522
 *
5523
 * @param bool $update If true, fetch and process the latest offical list of TLDs from iana.org.
5524
 */
5525
function set_tld_regex($update = false)
5526
{
5527
	global $sourcedir, $smcFunc;
5528
	static $done = false;
5529
5530
	// If we don't need to do anything, don't
5531
	if (!$update && $done)
5532
		return;
5533
5534
	// Should we get a new copy of the official list of TLDs?
5535
	if ($update)
5536
	{
5537
		require_once($sourcedir . '/Subs-Package.php');
5538
		$tlds = fetch_web_data('http://data.iana.org/TLD/tlds-alpha-by-domain.txt');
5539
	}
5540
	// If we aren't updating and the regex is valid, we're done
5541
	elseif (!empty($modSettings['tld_regex']) && @preg_match('~' . $modSettings['tld_regex'] . '~', null) !== false)
0 ignored issues
show
Bug introduced by
The variable $modSettings seems to never exist, and therefore empty should always return true. 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...
5542
	{
5543
		$done = true;
5544
		return;
5545
	}
5546
5547
	// If we successfully got an update, process the list into an array
5548
	if (!empty($tlds))
5549
	{
5550
		// Clean $tlds and convert it to an array
5551
		$tlds = array_filter(explode("\n", strtolower($tlds)), function($line) {
5552
			$line = trim($line);
5553
			if (empty($line) || strpos($line, '#') !== false || strpos($line, ' ') !== false)
5554
				return false;
5555
			else
5556
				return true;
5557
		});
5558
5559
		// Convert Punycode to Unicode
5560
		$tlds = array_map(function ($input) {
5561
			$prefix = 'xn--';
5562
			$safe_char = 0xFFFC;
5563
			$base = 36;
5564
			$tmin = 1;
5565
			$tmax = 26;
5566
			$skew = 38;
5567
			$damp = 700;
5568
			$output_parts = array();
5569
5570
			$input = str_replace(strtoupper($prefix), $prefix, $input);
5571
5572
			$enco_parts = (array) explode('.', $input);
5573
5574
			foreach ($enco_parts as $encoded)
5575
			{
5576
				if (strpos($encoded,$prefix) !== 0 || strlen(trim(str_replace($prefix,'',$encoded))) == 0)
5577
				{
5578
					$output_parts[] = $encoded;
5579
					continue;
5580
				}
5581
5582
				$is_first = true;
5583
				$bias = 72;
5584
				$idx = 0;
5585
				$char = 0x80;
5586
				$decoded = array();
5587
				$output='';
5588
				$delim_pos = strrpos($encoded, '-');
5589
5590
				if ($delim_pos > strlen($prefix))
5591
				{
5592
					for ($k = strlen($prefix); $k < $delim_pos; ++$k)
5593
					{
5594
						$decoded[] = ord($encoded{$k});
5595
					}
5596
				}
5597
5598
				$deco_len = count($decoded);
5599
				$enco_len = strlen($encoded);
5600
5601
				for ($enco_idx = $delim_pos ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len)
5602
				{
5603
					for ($old_idx = $idx, $w = 1, $k = $base; 1 ; $k += $base)
5604
					{
5605
						$cp = ord($encoded{$enco_idx++});
5606
						$digit = ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $base));
5607
						$idx += $digit * $w;
5608
						$t = ($k <= $bias) ? $tmin : (($k >= $bias + $tmax) ? $tmax : ($k - $bias));
5609
5610
						if ($digit < $t)
5611
							break;
5612
5613
						$w = (int) ($w * ($base - $t));
5614
					}
5615
5616
					$delta = $idx - $old_idx;
5617
					$delta = intval($is_first ? ($delta / $damp) : ($delta / 2));
5618
					$delta += intval($delta / ($deco_len + 1));
5619
5620
					for ($k = 0; $delta > (($base - $tmin) * $tmax) / 2; $k += $base)
5621
						$delta = intval($delta / ($base - $tmin));
5622
5623
					$bias = intval($k + ($base - $tmin + 1) * $delta / ($delta + $skew));
5624
					$is_first = false;
5625
					$char += (int) ($idx / ($deco_len + 1));
5626
					$idx %= ($deco_len + 1);
5627
5628
					if ($deco_len > 0)
5629
					{
5630
						for ($i = $deco_len; $i > $idx; $i--)
5631
							$decoded[$i] = $decoded[($i - 1)];
5632
					}
5633
					$decoded[$idx++] = $char;
5634
				}
5635
5636
				foreach ($decoded as $k => $v)
5637
				{
5638
					// 7bit are transferred literally
5639
					if ($v < 128)
5640
						$output .= chr($v);
5641
5642
					// 2 bytes
5643
					elseif ($v < (1 << 11))
5644
						$output .= chr(192+($v >> 6)) . chr(128+($v & 63));
5645
5646
					// 3 bytes
5647
					elseif ($v < (1 << 16))
5648
						$output .= chr(224+($v >> 12)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5649
5650
					// 4 bytes
5651
					elseif ($v < (1 << 21))
5652
						$output .= chr(240+($v >> 18)) . chr(128+(($v >> 12) & 63)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5653
5654
					//  'Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k
5655
					else
5656
						$output .= $safe_char;
5657
				}
5658
5659
				$output_parts[] = $output;
5660
			}
5661
5662
			return implode('.', $output_parts);
5663
		}, $tlds);
5664
5665
		$schedule_update = false;
5666
	}
5667
	// Otherwise, use the 2012 list of gTLDs and ccTLDs for now and schedule a background update
5668
	else
5669
	{
5670
		$tlds = array('com', 'net', 'org', 'edu', 'gov', 'mil', 'aero', 'asia', 'biz', 'cat',
5671
			'coop', 'info', 'int', 'jobs', 'mobi', 'museum', 'name', 'post', 'pro', 'tel',
5672
			'travel', 'xxx', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq',
5673
			'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
5674
			'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc',
5675
			'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cs', 'cu', 'cv',
5676
			'cx', 'cy', 'cz', 'dd', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh',
5677
			'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge',
5678
			'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw',
5679
			'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq',
5680
			'ir', 'is', 'it', 'ja', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn',
5681
			'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu',
5682
			'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp',
5683
			'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf',
5684
			'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph',
5685
			'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru',
5686
			'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn',
5687
			'so', 'sr', 'ss', 'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th',
5688
			'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug',
5689
			'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye',
5690
			'yt', 'yu', 'za', 'zm', 'zw');
5691
5692
		$schedule_update = true;
5693
	}
5694
5695
	// build_regex() returns an array. We only need the first item.
5696
	$tld_regex = build_regex($tlds);
5697
	$tld_regex = array_shift($tld_regex);
5698
5699
	// Remember the new regex in $modSettings
5700
	updateSettings(array('tld_regex' => $tld_regex));
5701
5702
	// Schedule a background update if we need one
5703
	if (!empty($schedule_update))
5704
	{
5705
		$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
5706
			array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
5707
			array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array()
5708
		);
5709
	}
5710
5711
	// Redundant repetition is redundant
5712
	$done = true;
5713
}
5714
5715
/**
5716
 * Creates optimized regular expressions from an array of strings.
5717
 *
5718
 * An optimized regex built using this function will be much faster than a simple regex built using
5719
 * `implode('|', $strings)` --- anywhere from several times to several orders of magnitude faster.
5720
 *
5721
 * However, the time required to build the optimized regex is approximately equal to the time it
5722
 * takes to execute the simple regex. Therefore, it is only worth calling this function if the
5723
 * resulting regex will be used more than once.
5724
 *
5725
 * Because PHP places an upper limit on the allowed length of a regex, very large arrays may be
5726
 * split and returned as multiple regexes. In such cases, you will need to iterate through all
5727
 * elements of the returned array in order to test all possible matches. (Note: if your array of
5728
 * alternative strings is large enough to require multiple regexes to accomodate it all, it is
5729
 * probably time to reconsider your coding choices. There is almost certainly a better way to do
5730
 * whatever you are trying to do with these giant regexes.)
5731
 *
5732
 * @param array $strings An array of strings to make a regex for.
5733
 * @param string $delim An optional delimiter character to pass to preg_quote().
5734
 * @return array An array of one or more regular expressions to match any of the input strings.
5735
 */
5736
function build_regex($strings, $delim = null)
5737
{
5738
	global $smcFunc;
5739
5740
	// The mb_* functions are faster than the $smcFunc ones, but may not be available
5741
	if (function_exists('mb_internal_encoding') && function_exists('mb_detect_encoding') && function_exists('mb_strlen') && function_exists('mb_substr'))
5742
	{
5743
		if (($string_encoding = mb_detect_encoding(implode(' ', $strings))) !== false)
5744
		{
5745
			$current_encoding = mb_internal_encoding();
5746
			mb_internal_encoding($string_encoding);
5747
		}
5748
5749
		$strlen = 'mb_strlen';
5750
		$substr = 'mb_substr';
5751
	}
5752
	else
5753
	{
5754
		$strlen = $smcFunc['strlen'];
5755
		$substr = $smcFunc['substr'];
5756
	}
5757
5758
	// This recursive function creates the index array from the strings
5759
	$add_string_to_index = function ($string, $index) use (&$strlen, &$substr, &$add_string_to_index)
5760
	{
5761
		static $depth = 0;
5762
		$depth++;
5763
5764
		$first = $substr($string, 0, 1);
5765
5766
		if (empty($index[$first]))
5767
			$index[$first] = array();
5768
5769
		if ($strlen($string) > 1)
5770
		{
5771
			// Sanity check on recursion
5772
			if ($depth > 99)
5773
				$index[$first][$substr($string, 1)] = '';
5774
5775
			else
5776
				$index[$first] = $add_string_to_index($substr($string, 1), $index[$first]);
5777
		}
5778
		else
5779
			$index[$first][''] = '';
5780
5781
		$depth--;
5782
		return $index;
5783
	};
5784
5785
	// This recursive function turns the index array into a regular expression
5786
	$index_to_regex = function (&$index, $delim) use (&$strlen, &$index_to_regex)
5787
	{
5788
		static $depth = 0;
5789
		$depth++;
5790
5791
		// Absolute max length for a regex is 32768, but we might need wiggle room
5792
		$max_length = 30000;
5793
5794
		$regex = array();
5795
		$length = 0;
5796
5797
		foreach ($index as $key => $value)
5798
		{
5799
			$key_regex = preg_quote($key, $delim);
5800
			$new_key = $key;
5801
5802
			if (empty($value))
5803
				$sub_regex = '';
5804
			else
5805
			{
5806
				$sub_regex = $index_to_regex($value, $delim);
5807
5808
				if (count(array_keys($value)) == 1)
5809
					$new_key .= explode('(?'.'>', $sub_regex)[0];
5810
				else
5811
					$sub_regex = '(?'.'>' . $sub_regex . ')';
5812
			}
5813
5814
			if ($depth > 1)
5815
				$regex[$new_key] = $key_regex . $sub_regex;
5816
			else
5817
			{
5818
				if (($length += strlen($key_regex) + 1) < $max_length || empty($regex))
5819
				{
5820
					$regex[$new_key] = $key_regex . $sub_regex;
5821
					unset($index[$key]);
5822
				}
5823
				else
5824
					break;
5825
			}
5826
		}
5827
5828
		// Sort by key length and then alphabetically
5829
		uksort($regex, function($k1, $k2) use (&$strlen) {
5830
			$l1 = $strlen($k1);
5831
			$l2 = $strlen($k2);
5832
5833
			if ($l1 == $l2)
5834
				return strcmp($k1, $k2) > 0 ? 1 : -1;
5835
			else
5836
				return $l1 > $l2 ? -1 : 1;
5837
		});
5838
5839
		$depth--;
5840
		return implode('|', $regex);
5841
	};
5842
5843
	// Now that the functions are defined, let's do this thing
5844
	$index = array();
5845
	$regexes = array();
5846
5847
	foreach ($strings as $string)
5848
		$index = $add_string_to_index($string, $index);
5849
5850
	while (!empty($index))
5851
		$regexes[] = '(?'.'>' . $index_to_regex($index, $delim) . ')';
5852
5853
	// Restore PHP's internal character encoding to whatever it was originally
5854
	if (!empty($current_encoding))
5855
		mb_internal_encoding($current_encoding);
5856
5857
	return $regexes;
5858
}
5859
5860
?>
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...