Completed
Push — release-2.1 ( e6c696...22bfba )
by Mathias
07:04
created

Subs.php ➔ smf_list_timezones()   F

Complexity

Conditions 20
Paths 4861

Size

Total Lines 167
Code Lines 108

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 108
nc 4861
nop 1
dl 0
loc 167
rs 2
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
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...
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...
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
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...
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...
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
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
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
 * Sets up all of the top menu buttons
3940
 * Saves them in the cache if it is available and on
3941
 * Places the results in $context
3942
 *
3943
 */
3944
function setupMenuContext()
3945
{
3946
	global $context, $modSettings, $user_info, $txt, $scripturl, $sourcedir, $settings;
3947
3948
	// Set up the menu privileges.
3949
	$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts'));
3950
	$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'));
3951
3952
	$context['allow_memberlist'] = allowedTo('view_mlist');
3953
	$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
3954
	$context['allow_moderation_center'] = $context['user']['can_mod'];
3955
	$context['allow_pm'] = allowedTo('pm_read');
3956
3957
	$cacheTime = $modSettings['lastActive'] * 60;
3958
3959
	// Initial "can you post an event in the calendar" option - but this might have been set in the calendar already.
3960
	if (!isset($context['allow_calendar_event']))
3961
	{
3962
		$context['allow_calendar_event'] = $context['allow_calendar'] && allowedTo('calendar_post');
3963
3964
		// If you don't allow events not linked to posts and you're not an admin, we have more work to do...
3965 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...
3966
		{
3967
			$boards_can_post = boardsAllowedTo('post_new');
3968
			$context['allow_calendar_event'] &= !empty($boards_can_post);
3969
		}
3970
	}
3971
3972
	// There is some menu stuff we need to do if we're coming at this from a non-guest perspective.
3973
	if (!$context['user']['is_guest'])
3974
	{
3975
		addInlineJavaScript('
3976
	var user_menus = new smc_PopupMenu();
3977
	user_menus.add("profile", "' . $scripturl . '?action=profile;area=popup");
3978
	user_menus.add("alerts", "' . $scripturl . '?action=profile;area=alerts_popup;u='. $context['user']['id'] .'");', true);
3979
		if ($context['allow_pm'])
3980
			addInlineJavaScript('
3981
	user_menus.add("pm", "' . $scripturl . '?action=pm;sa=popup");', true);
3982
3983
		if (!empty($modSettings['enable_ajax_alerts']))
3984
		{
3985
			require_once($sourcedir . '/Subs-Notify.php');
3986
3987
			$timeout = getNotifyPrefs($context['user']['id'], 'alert_timeout', true);
3988
			$timeout = empty($timeout) ? 10000 : $timeout[$context['user']['id']]['alert_timeout'] * 1000;
3989
3990
			addInlineJavaScript('
3991
	var new_alert_title = "' . $context['forum_name'] . '";
3992
	var alert_timeout = ' . $timeout . ';');
3993
			loadJavaScriptFile('alerts.js', array(), 'smf_alerts');
3994
		}
3995
	}
3996
3997
	// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
3998
	if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
3999
	{
4000
		$buttons = array(
4001
			'home' => array(
4002
				'title' => $txt['home'],
4003
				'href' => $scripturl,
4004
				'show' => true,
4005
				'sub_buttons' => array(
4006
				),
4007
				'is_last' => $context['right_to_left'],
4008
			),
4009
			'search' => array(
4010
				'title' => $txt['search'],
4011
				'href' => $scripturl . '?action=search',
4012
				'show' => $context['allow_search'],
4013
				'sub_buttons' => array(
4014
				),
4015
			),
4016
			'admin' => array(
4017
				'title' => $txt['admin'],
4018
				'href' => $scripturl . '?action=admin',
4019
				'show' => $context['allow_admin'],
4020
				'sub_buttons' => array(
4021
					'featuresettings' => array(
4022
						'title' => $txt['modSettings_title'],
4023
						'href' => $scripturl . '?action=admin;area=featuresettings',
4024
						'show' => allowedTo('admin_forum'),
4025
					),
4026
					'packages' => array(
4027
						'title' => $txt['package'],
4028
						'href' => $scripturl . '?action=admin;area=packages',
4029
						'show' => allowedTo('admin_forum'),
4030
					),
4031
					'errorlog' => array(
4032
						'title' => $txt['errlog'],
4033
						'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
4034
						'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']),
4035
					),
4036
					'permissions' => array(
4037
						'title' => $txt['edit_permissions'],
4038
						'href' => $scripturl . '?action=admin;area=permissions',
4039
						'show' => allowedTo('manage_permissions'),
4040
					),
4041
					'memberapprove' => array(
4042
						'title' => $txt['approve_members_waiting'],
4043
						'href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve',
4044
						'show' => !empty($context['unapproved_members']),
4045
						'is_last' => true,
4046
					),
4047
				),
4048
			),
4049
			'moderate' => array(
4050
				'title' => $txt['moderate'],
4051
				'href' => $scripturl . '?action=moderate',
4052
				'show' => $context['allow_moderation_center'],
4053
				'sub_buttons' => array(
4054
					'modlog' => array(
4055
						'title' => $txt['modlog_view'],
4056
						'href' => $scripturl . '?action=moderate;area=modlog',
4057
						'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4058
					),
4059
					'poststopics' => array(
4060
						'title' => $txt['mc_unapproved_poststopics'],
4061
						'href' => $scripturl . '?action=moderate;area=postmod;sa=posts',
4062
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4063
					),
4064
					'attachments' => array(
4065
						'title' => $txt['mc_unapproved_attachments'],
4066
						'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments',
4067
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4068
					),
4069
					'reports' => array(
4070
						'title' => $txt['mc_reported_posts'],
4071
						'href' => $scripturl . '?action=moderate;area=reportedposts',
4072
						'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4073
					),
4074
					'reported_members' => array(
4075
						'title' => $txt['mc_reported_members'],
4076
						'href' => $scripturl . '?action=moderate;area=reportedmembers',
4077
						'show' => allowedTo('moderate_forum'),
4078
						'is_last' => true,
4079
					)
4080
				),
4081
			),
4082
			'calendar' => array(
4083
				'title' => $txt['calendar'],
4084
				'href' => $scripturl . '?action=calendar',
4085
				'show' => $context['allow_calendar'],
4086
				'sub_buttons' => array(
4087
					'view' => array(
4088
						'title' => $txt['calendar_menu'],
4089
						'href' => $scripturl . '?action=calendar',
4090
						'show' => $context['allow_calendar_event'],
4091
					),
4092
					'post' => array(
4093
						'title' => $txt['calendar_post_event'],
4094
						'href' => $scripturl . '?action=calendar;sa=post',
4095
						'show' => $context['allow_calendar_event'],
4096
						'is_last' => true,
4097
					),
4098
				),
4099
			),
4100
			'mlist' => array(
4101
				'title' => $txt['members_title'],
4102
				'href' => $scripturl . '?action=mlist',
4103
				'show' => $context['allow_memberlist'],
4104
				'sub_buttons' => array(
4105
					'mlist_view' => array(
4106
						'title' => $txt['mlist_menu_view'],
4107
						'href' => $scripturl . '?action=mlist',
4108
						'show' => true,
4109
					),
4110
					'mlist_search' => array(
4111
						'title' => $txt['mlist_search'],
4112
						'href' => $scripturl . '?action=mlist;sa=search',
4113
						'show' => true,
4114
						'is_last' => true,
4115
					),
4116
				),
4117
			),
4118
			'signup' => array(
4119
				'title' => $txt['register'],
4120
				'href' => $scripturl . '?action=signup',
4121
				'show' => $user_info['is_guest'] && $context['can_register'],
4122
				'sub_buttons' => array(
4123
				),
4124
				'is_last' => !$context['right_to_left'],
4125
			),
4126
			'logout' => array(
4127
				'title' => $txt['logout'],
4128
				'href' => $scripturl . '?action=logout;%1$s=%2$s',
4129
				'show' => !$user_info['is_guest'],
4130
				'sub_buttons' => array(
4131
				),
4132
				'is_last' => !$context['right_to_left'],
4133
			),
4134
		);
4135
4136
		// Allow editing menu buttons easily.
4137
		call_integration_hook('integrate_menu_buttons', array(&$buttons));
4138
4139
		// Now we put the buttons in the context so the theme can use them.
4140
		$menu_buttons = array();
4141
		foreach ($buttons as $act => $button)
4142
			if (!empty($button['show']))
4143
			{
4144
				$button['active_button'] = false;
4145
4146
				// This button needs some action.
4147
				if (isset($button['action_hook']))
4148
					$needs_action_hook = true;
4149
4150
				// Make sure the last button truly is the last button.
4151
				if (!empty($button['is_last']))
4152
				{
4153
					if (isset($last_button))
4154
						unset($menu_buttons[$last_button]['is_last']);
4155
					$last_button = $act;
4156
				}
4157
4158
				// Go through the sub buttons if there are any.
4159
				if (!empty($button['sub_buttons']))
4160
					foreach ($button['sub_buttons'] as $key => $subbutton)
4161
					{
4162
						if (empty($subbutton['show']))
4163
							unset($button['sub_buttons'][$key]);
4164
4165
						// 2nd level sub buttons next...
4166
						if (!empty($subbutton['sub_buttons']))
4167
						{
4168
							foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2)
4169
							{
4170
								if (empty($sub_button2['show']))
4171
									unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
4172
							}
4173
						}
4174
					}
4175
4176
				// Does this button have its own icon?
4177
				if (isset($button['icon']) && file_exists($settings['theme_dir'] . '/images/' . $button['icon']))
4178
					$button['icon'] = '<img src="' . $settings['images_url'] . '/' . $button['icon'] . '" alt="">';
4179
				elseif (isset($button['icon']) && file_exists($settings['default_theme_dir'] . '/images/' . $button['icon']))
4180
					$button['icon'] = '<img src="' . $settings['default_images_url'] . '/' . $button['icon'] . '" alt="">';
4181
				elseif (isset($button['icon']))
4182
					$button['icon'] = '<span class="generic_icons ' . $button['icon'] . '"></span>';
4183
				else
4184
					$button['icon'] = '<span class="generic_icons ' . $act . '"></span>';
4185
4186
				$menu_buttons[$act] = $button;
4187
			}
4188
4189
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
4190
			cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
4191
	}
4192
4193
	$context['menu_buttons'] = $menu_buttons;
4194
4195
	// Logging out requires the session id in the url.
4196
	if (isset($context['menu_buttons']['logout']))
4197
		$context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']);
4198
4199
	// Figure out which action we are doing so we can set the active tab.
4200
	// Default to home.
4201
	$current_action = 'home';
4202
4203
	if (isset($context['menu_buttons'][$context['current_action']]))
4204
		$current_action = $context['current_action'];
4205
	elseif ($context['current_action'] == 'search2')
4206
		$current_action = 'search';
4207
	elseif ($context['current_action'] == 'theme')
4208
		$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin';
4209
	elseif ($context['current_action'] == 'register2')
4210
		$current_action = 'register';
4211
	elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder'))
4212
		$current_action = 'login';
4213
	elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center'])
4214
		$current_action = 'moderate';
4215
4216
	// There are certain exceptions to the above where we don't want anything on the menu highlighted.
4217
	if ($context['current_action'] == 'profile' && !empty($context['user']['is_owner']))
4218
	{
4219
		$current_action = !empty($_GET['area']) && $_GET['area'] == 'showalerts' ? 'self_alerts' : 'self_profile';
4220
		$context[$current_action] = true;
4221
	}
4222
	elseif ($context['current_action'] == 'pm')
4223
	{
4224
		$current_action = 'self_pm';
4225
		$context['self_pm'] = true;
4226
	}
4227
4228
	$total_mod_reports = 0;
4229
4230
	if (!empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1' && !empty($context['open_mod_reports']))
4231
	{
4232
		$total_mod_reports = $context['open_mod_reports'];
4233
		$context['menu_buttons']['moderate']['sub_buttons']['reports']['title'] .= ' <span class="amt">' . $context['open_mod_reports'] . '</span>';
4234
	}
4235
4236
	// Show how many errors there are
4237 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...
4238
	{
4239
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4240
		$context['menu_buttons']['admin']['sub_buttons']['errorlog']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4241
	}
4242
4243
	/**
4244
	 * @todo For some reason, $context['open_member_reports'] isn't getting set
4245
	 */
4246
	if (!empty($context['open_member_reports']) && allowedTo('moderate_forum'))
4247
	{
4248
		$total_mod_reports += $context['open_member_reports'];
4249
		$context['menu_buttons']['moderate']['sub_buttons']['reported_members']['title'] .= ' <span class="amt">' . $context['open_member_reports'] . '</span>';
4250
	}
4251
4252 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...
4253
	{
4254
		$context['menu_buttons']['admin']['sub_buttons']['memberapprove']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4255
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4256
	}
4257
4258
	// Do we have any open reports?
4259
	if ($total_mod_reports > 0)
4260
	{
4261
		$context['menu_buttons']['moderate']['title'] .= ' <span class="amt">' . $total_mod_reports . '</span>';
4262
	}
4263
4264
	// Not all actions are simple.
4265
	if (!empty($needs_action_hook))
4266
		call_integration_hook('integrate_current_action', array(&$current_action));
4267
4268
	if (isset($context['menu_buttons'][$current_action]))
4269
		$context['menu_buttons'][$current_action]['active_button'] = true;
4270
}
4271
4272
/**
4273
 * Generate a random seed and ensure it's stored in settings.
4274
 */
4275
function smf_seed_generator()
4276
{
4277
	updateSettings(array('rand_seed' => microtime() * 1000000));
4278
}
4279
4280
/**
4281
 * Process functions of an integration hook.
4282
 * calls all functions of the given hook.
4283
 * supports static class method calls.
4284
 *
4285
 * @param string $hook The hook name
4286
 * @param array $parameters An array of parameters this hook implements
4287
 * @return array The results of the functions
4288
 */
4289
function call_integration_hook($hook, $parameters = array())
4290
{
4291
	global $modSettings, $settings, $boarddir, $sourcedir, $db_show_debug;
4292
	global $context, $txt;
4293
4294
	if ($db_show_debug === true)
4295
		$context['debug']['hooks'][] = $hook;
4296
4297
	// Need to have some control.
4298
	if (!isset($context['instances']))
4299
		$context['instances'] = array();
4300
4301
	$results = array();
4302
	if (empty($modSettings[$hook]))
4303
		return $results;
4304
4305
	// Define some needed vars.
4306
	$function = false;
4307
4308
	$functions = explode(',', $modSettings[$hook]);
4309
	// Loop through each function.
4310
	foreach ($functions as $function)
4311
	{
4312
		// Hook has been marked as "disabled". Skip it!
4313
		if (strpos($function, '!') !== false)
4314
			continue;
4315
4316
		$call = call_helper($function, true);
4317
4318
		// Is it valid?
4319
		if (!empty($call))
4320
			$results[$function] = call_user_func_array($call, $parameters);
4321
4322
		// Whatever it was suppose to call, it failed :(
4323
		elseif (!empty($function))
4324
		{
4325
			loadLanguage('Errors');
4326
4327
			// Get a full path to show on error.
4328
			if (strpos($function, '|') !== false)
4329
			{
4330
				list ($file, $string) = explode('|', $function);
4331
				$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'])));
4332
				log_error(sprintf($txt['hook_fail_call_to'], $string, $absPath), 'general');
4333
			}
4334
4335
			// "Assume" the file resides on $boarddir somewhere...
4336
			else
4337
				log_error(sprintf($txt['hook_fail_call_to'], $function, $boarddir), 'general');
4338
		}
4339
	}
4340
4341
	return $results;
4342
}
4343
4344
/**
4345
 * Add a function for integration hook.
4346
 * does nothing if the function is already added.
4347
 *
4348
 * @param string $hook The complete hook name.
4349
 * @param string $function The function name. Can be a call to a method via Class::method.
4350
 * @param bool $permanent If true, updates the value in settings table.
4351
 * @param string $file The file. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4352
 * @param bool $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4353
 */
4354
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
4355
{
4356
	global $smcFunc, $modSettings;
4357
4358
	// Any objects?
4359
	if ($object)
4360
		$function = $function . '#';
4361
4362
	// Any files  to load?
4363
	if (!empty($file) && is_string($file))
4364
		$function = $file . (!empty($function) ? '|' . $function : '');
4365
4366
	// Get the correct string.
4367
	$integration_call = $function;
4368
4369
	// Is it going to be permanent?
4370
	if ($permanent)
4371
	{
4372
		$request = $smcFunc['db_query']('', '
4373
			SELECT value
4374
			FROM {db_prefix}settings
4375
			WHERE variable = {string:variable}',
4376
			array(
4377
				'variable' => $hook,
4378
			)
4379
		);
4380
		list ($current_functions) = $smcFunc['db_fetch_row']($request);
4381
		$smcFunc['db_free_result']($request);
4382
4383
		if (!empty($current_functions))
4384
		{
4385
			$current_functions = explode(',', $current_functions);
4386
			if (in_array($integration_call, $current_functions))
4387
				return;
4388
4389
			$permanent_functions = array_merge($current_functions, array($integration_call));
4390
		}
4391
		else
4392
			$permanent_functions = array($integration_call);
4393
4394
		updateSettings(array($hook => implode(',', $permanent_functions)));
4395
	}
4396
4397
	// Make current function list usable.
4398
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4399
4400
	// Do nothing, if it's already there.
4401
	if (in_array($integration_call, $functions))
4402
		return;
4403
4404
	$functions[] = $integration_call;
4405
	$modSettings[$hook] = implode(',', $functions);
4406
}
4407
4408
/**
4409
 * Remove an integration hook function.
4410
 * Removes the given function from the given hook.
4411
 * Does nothing if the function is not available.
4412
 *
4413
 * @param string $hook The complete hook name.
4414
 * @param string $function The function name. Can be a call to a method via Class::method.
4415
 * @param boolean $permanent Irrelevant for the function itself but need to declare it to match
4416
 * @param string $file The filename. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4417
 * @param boolean $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4418
 * @see add_integration_function
4419
 */
4420
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...
4421
{
4422
	global $smcFunc, $modSettings;
4423
4424
	// Any objects?
4425
	if ($object)
4426
		$function = $function . '#';
4427
4428
	// Any files  to load?
4429
	if (!empty($file) && is_string($file))
4430
		$function = $file . '|' . $function;
4431
4432
	// Get the correct string.
4433
	$integration_call = $function;
4434
4435
	// Get the permanent functions.
4436
	$request = $smcFunc['db_query']('', '
4437
		SELECT value
4438
		FROM {db_prefix}settings
4439
		WHERE variable = {string:variable}',
4440
		array(
4441
			'variable' => $hook,
4442
		)
4443
	);
4444
	list ($current_functions) = $smcFunc['db_fetch_row']($request);
4445
	$smcFunc['db_free_result']($request);
4446
4447
	if (!empty($current_functions))
4448
	{
4449
		$current_functions = explode(',', $current_functions);
4450
4451
		if (in_array($integration_call, $current_functions))
4452
			updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call)))));
4453
	}
4454
4455
	// Turn the function list into something usable.
4456
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4457
4458
	// You can only remove it if it's available.
4459
	if (!in_array($integration_call, $functions))
4460
		return;
4461
4462
	$functions = array_diff($functions, array($integration_call));
4463
	$modSettings[$hook] = implode(',', $functions);
4464
}
4465
4466
/**
4467
 * Receives a string and tries to figure it out if its a method or a function.
4468
 * If a method is found, it looks for a "#" which indicates SMF should create a new instance of the given class.
4469
 * Checks the string/array for is_callable() and return false/fatal_lang_error is the given value results in a non callable string/array.
4470
 * Prepare and returns a callable depending on the type of method/function found.
4471
 *
4472
 * @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)
4473
 * @param boolean $return If true, the function will not call the function/method but instead will return the formatted string.
4474
 * @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.
4475
 */
4476
function call_helper($string, $return = false)
4477
{
4478
	global $context, $smcFunc, $txt, $db_show_debug;
4479
4480
	// Really?
4481
	if (empty($string))
4482
		return false;
4483
4484
	// An array? should be a "callable" array IE array(object/class, valid_callable).
4485
	// A closure? should be a callable one.
4486
	if (is_array($string) || $string instanceof Closure)
4487
		return $return ? $string : (is_callable($string) ? call_user_func($string) : false);
4488
4489
	// No full objects, sorry! pass a method or a property instead!
4490
	if (is_object($string))
4491
		return false;
4492
4493
	// Stay vitaminized my friends...
4494
	$string = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($string));
4495
4496
	// The soon to be populated var.
4497
	$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...
4498
4499
	// Is there a file to load?
4500
	$string = load_file($string);
4501
4502
	// Loaded file failed
4503
	if (empty($string))
4504
		return false;
4505
4506
	// Found a method.
4507
	if (strpos($string, '::') !== false)
4508
	{
4509
		list ($class, $method) = explode('::', $string);
4510
4511
		// Check if a new object will be created.
4512
		if (strpos($method, '#') !== false)
4513
		{
4514
			// Need to remove the # thing.
4515
			$method = str_replace('#', '', $method);
4516
4517
			// Don't need to create a new instance for every method.
4518
			if (empty($context['instances'][$class]) || !($context['instances'][$class] instanceof $class))
4519
			{
4520
				$context['instances'][$class] = new $class;
4521
4522
				// Add another one to the list.
4523
				if ($db_show_debug === true)
4524
				{
4525
					if (!isset($context['debug']['instances']))
4526
						$context['debug']['instances'] = array();
4527
4528
					$context['debug']['instances'][$class] = $class;
4529
				}
4530
			}
4531
4532
			$func = array($context['instances'][$class], $method);
4533
		}
4534
4535
		// Right then. This is a call to a static method.
4536
		else
4537
			$func = array($class, $method);
4538
	}
4539
4540
	// Nope! just a plain regular function.
4541
	else
4542
		$func = $string;
4543
4544
	// Right, we got what we need, time to do some checks.
4545
	if (!is_callable($func, false, $callable_name))
4546
	{
4547
		loadLanguage('Errors');
4548
		log_error(sprintf($txt['subAction_fail'], $callable_name), 'general');
4549
4550
		// Gotta tell everybody.
4551
		return false;
4552
	}
4553
4554
	// Everything went better than expected.
4555
	else
4556
	{
4557
		// What are we gonna do about it?
4558
		if ($return)
4559
			return $func;
4560
4561
		// If this is a plain function, avoid the heat of calling call_user_func().
4562
		else
4563
		{
4564
			if (is_array($func))
4565
				call_user_func($func);
4566
4567
			else
4568
				$func();
4569
		}
4570
	}
4571
}
4572
4573
/**
4574
 * Receives a string and tries to figure it out if it contains info to load a file.
4575
 * Checks for a | (pipe) symbol and tries to load a file with the info given.
4576
 * 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.
4577
 *
4578
 * @param string $string The string containing a valid format.
4579
 * @return string|boolean The given string with the pipe and file info removed. Boolean false if the file couldn't be loaded.
4580
 */
4581
function load_file($string)
4582
{
4583
	global $sourcedir, $txt, $boarddir, $settings;
4584
4585
	if (empty($string))
4586
		return false;
4587
4588
	if (strpos($string, '|') !== false)
4589
	{
4590
		list ($file, $string) = explode('|', $string);
4591
4592
		// Match the wildcards to their regular vars.
4593
		if (empty($settings['theme_dir']))
4594
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
4595
4596
		else
4597
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
4598
4599
		// Load the file if it can be loaded.
4600
		if (file_exists($absPath))
4601
			require_once($absPath);
4602
4603
		// No? try a fallback to $sourcedir
4604
		else
4605
		{
4606
			$absPath = $sourcedir .'/'. $file;
4607
4608
			if (file_exists($absPath))
4609
				require_once($absPath);
4610
4611
			// Sorry, can't do much for you at this point.
4612
			else
4613
			{
4614
				loadLanguage('Errors');
4615
				log_error(sprintf($txt['hook_fail_loading_file'], $absPath), 'general');
4616
4617
				// File couldn't be loaded.
4618
				return false;
4619
			}
4620
		}
4621
	}
4622
4623
	return $string;
4624
}
4625
4626
/**
4627
 * Prepares an array of "likes" info for the topic specified by $topic
4628
 * @param integer $topic The topic ID to fetch the info from.
4629
 * @return array An array of IDs of messages in the specified topic that the current user likes
4630
 */
4631
function prepareLikesContext($topic)
4632
{
4633
	global $user_info, $smcFunc;
4634
4635
	// Make sure we have something to work with.
4636
	if (empty($topic))
4637
		return array();
4638
4639
4640
	// We already know the number of likes per message, we just want to know whether the current user liked it or not.
4641
	$user = $user_info['id'];
4642
	$cache_key = 'likes_topic_' . $topic . '_' . $user;
4643
	$ttl = 180;
4644
4645
	if (($temp = cache_get_data($cache_key, $ttl)) === null)
4646
	{
4647
		$temp = array();
4648
		$request = $smcFunc['db_query']('', '
4649
			SELECT content_id
4650
			FROM {db_prefix}user_likes AS l
4651
				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
4652
			WHERE l.id_member = {int:current_user}
4653
				AND l.content_type = {literal:msg}
4654
				AND m.id_topic = {int:topic}',
4655
			array(
4656
				'current_user' => $user,
4657
				'topic' => $topic,
4658
			)
4659
		);
4660
		while ($row = $smcFunc['db_fetch_assoc']($request))
4661
			$temp[] = (int) $row['content_id'];
4662
4663
		cache_put_data($cache_key, $temp, $ttl);
4664
	}
4665
4666
	return $temp;
4667
}
4668
4669
/**
4670
 * Microsoft uses their own character set Code Page 1252 (CP1252), which is a
4671
 * superset of ISO 8859-1, defining several characters between DEC 128 and 159
4672
 * that are not normally displayable.  This converts the popular ones that
4673
 * appear from a cut and paste from windows.
4674
 *
4675
 * @param string $string The string
4676
 * @return string The sanitized string
4677
 */
4678
function sanitizeMSCutPaste($string)
4679
{
4680
	global $context;
4681
4682
	if (empty($string))
4683
		return $string;
4684
4685
	// UTF-8 occurences of MS special characters
4686
	$findchars_utf8 = array(
4687
		"\xe2\80\x9a",	// single low-9 quotation mark
4688
		"\xe2\80\x9e",	// double low-9 quotation mark
4689
		"\xe2\80\xa6",	// horizontal ellipsis
4690
		"\xe2\x80\x98",	// left single curly quote
4691
		"\xe2\x80\x99",	// right single curly quote
4692
		"\xe2\x80\x9c",	// left double curly quote
4693
		"\xe2\x80\x9d",	// right double curly quote
4694
		"\xe2\x80\x93",	// en dash
4695
		"\xe2\x80\x94",	// em dash
4696
	);
4697
4698
	// windows 1252 / iso equivalents
4699
	$findchars_iso = array(
4700
		chr(130),
4701
		chr(132),
4702
		chr(133),
4703
		chr(145),
4704
		chr(146),
4705
		chr(147),
4706
		chr(148),
4707
		chr(150),
4708
		chr(151),
4709
	);
4710
4711
	// safe replacements
4712
	$replacechars = array(
4713
		',',	// &sbquo;
4714
		',,',	// &bdquo;
4715
		'...',	// &hellip;
4716
		"'",	// &lsquo;
4717
		"'",	// &rsquo;
4718
		'"',	// &ldquo;
4719
		'"',	// &rdquo;
4720
		'-',	// &ndash;
4721
		'--',	// &mdash;
4722
	);
4723
4724
	if ($context['utf8'])
4725
		$string = str_replace($findchars_utf8, $replacechars, $string);
4726
	else
4727
		$string = str_replace($findchars_iso, $replacechars, $string);
4728
4729
	return $string;
4730
}
4731
4732
/**
4733
 * Decode numeric html entities to their ascii or UTF8 equivalent character.
4734
 *
4735
 * Callback function for preg_replace_callback in subs-members
4736
 * Uses capture group 2 in the supplied array
4737
 * Does basic scan to ensure characters are inside a valid range
4738
 *
4739
 * @param array $matches An array of matches (relevant info should be the 3rd item)
4740
 * @return string A fixed string
4741
 */
4742
function replaceEntities__callback($matches)
4743
{
4744
	global $context;
4745
4746
	if (!isset($matches[2]))
4747
		return '';
4748
4749
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4750
4751
	// remove left to right / right to left overrides
4752
	if ($num === 0x202D || $num === 0x202E)
4753
		return '';
4754
4755
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
4756
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
4757
		return '&#' . $num . ';';
4758
4759
	if (empty($context['utf8']))
4760
	{
4761
		// no control characters
4762
		if ($num < 0x20)
4763
			return '';
4764
		// text is text
4765
		elseif ($num < 0x80)
4766
			return chr($num);
4767
		// all others get html-ised
4768
		else
4769
			return '&#' . $matches[2] . ';';
4770
	}
4771
	else
4772
	{
4773
		// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
4774
		// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
4775
		if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
4776
			return '';
4777
		// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4778
		elseif ($num < 0x80)
4779
			return chr($num);
4780
		// <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...
4781 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...
4782
			return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4783
		// < 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...
4784 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...
4785
			return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4786
		// <= 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...
4787 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...
4788
			return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4789
	}
4790
}
4791
4792
/**
4793
 * Converts html entities to utf8 equivalents
4794
 *
4795
 * Callback function for preg_replace_callback
4796
 * Uses capture group 1 in the supplied array
4797
 * Does basic checks to keep characters inside a viewable range.
4798
 *
4799
 * @param array $matches An array of matches (relevant info should be the 2nd item in the array)
4800
 * @return string The fixed string
4801
 */
4802
function fixchar__callback($matches)
4803
{
4804
	if (!isset($matches[1]))
4805
		return '';
4806
4807
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
4808
4809
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
4810
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
4811
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
4812
		return '';
4813
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4814
	elseif ($num < 0x80)
4815
		return chr($num);
4816
	// <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...
4817 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...
4818
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4819
	// < 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...
4820 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...
4821
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4822
	// <= 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...
4823 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...
4824
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4825
}
4826
4827
/**
4828
 * Strips out invalid html entities, replaces others with html style &#123; codes
4829
 *
4830
 * Callback function used of preg_replace_callback in smcFunc $ent_checks, for example
4831
 * strpos, strlen, substr etc
4832
 *
4833
 * @param array $matches An array of matches (relevant info should be the 3rd item in the array)
4834
 * @return string The fixed string
4835
 */
4836
function entity_fix__callback($matches)
4837
{
4838
	if (!isset($matches[2]))
4839
		return '';
4840
4841
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4842
4843
	// we don't allow control characters, characters out of range, byte markers, etc
4844
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
4845
		return '';
4846
	else
4847
		return '&#' . $num . ';';
4848
}
4849
4850
/**
4851
 * Return a Gravatar URL based on
4852
 * - the supplied email address,
4853
 * - the global maximum rating,
4854
 * - the global default fallback,
4855
 * - maximum sizes as set in the admin panel.
4856
 *
4857
 * It is SSL aware, and caches most of the parameters.
4858
 *
4859
 * @param string $email_address The user's email address
4860
 * @return string The gravatar URL
4861
 */
4862
function get_gravatar_url($email_address)
4863
{
4864
	global $modSettings, $smcFunc;
4865
	static $url_params = null;
4866
4867
	if ($url_params === null)
4868
	{
4869
		$ratings = array('G', 'PG', 'R', 'X');
4870
		$defaults = array('mm', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank');
4871
		$url_params = array();
4872 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...
4873
			$url_params[] = 'rating=' . $modSettings['gravatarMaxRating'];
4874 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...
4875
			$url_params[] = 'default=' . $modSettings['gravatarDefault'];
4876
		if (!empty($modSettings['avatar_max_width_external']))
4877
			$size_string = (int) $modSettings['avatar_max_width_external'];
4878
		if (!empty($modSettings['avatar_max_height_external']) && !empty($size_string))
4879
			if ((int) $modSettings['avatar_max_height_external'] < $size_string)
4880
				$size_string = $modSettings['avatar_max_height_external'];
4881
4882
		if (!empty($size_string))
4883
			$url_params[] = 's=' . $size_string;
4884
	}
4885
	$http_method = !empty($modSettings['force_ssl']) && $modSettings['force_ssl'] == 2 ? 'https://secure' : 'http://www';
4886
4887
	return $http_method . '.gravatar.com/avatar/' . md5($smcFunc['strtolower']($email_address)) . '?' . implode('&', $url_params);
4888
}
4889
4890
/**
4891
 * Get a list of timezones.
4892
 *
4893
 * @param string $when An optional date or time for which to calculate the timezone offset values. May be a Unix timestamp or any string that strtotime() can understand. Defaults to 'now'.
4894
 * @return array An array of timezone info.
4895
 */
4896
function smf_list_timezones($when = 'now')
4897
{
4898
	global $modSettings;
4899
	static $timezones = null, $lastwhen = null;
4900
4901
	// No point doing this over if we already did it once
4902
	if (!empty($timezones) && $when == $lastwhen)
4903
		return $timezones;
4904
	else
4905
		$lastwhen = $when;
4906
4907
	// Parseable datetime string?
4908
	if (is_int($timestamp = strtotime($when)))
4909
		$when = $timestamp;
4910
4911
	// A Unix timestamp?
4912
	elseif (is_numeric($when))
4913
		$when = intval($when);
4914
4915
	// Invalid value? Just get current Unix timestamp.
4916
	else
4917
		$when = time();
4918
4919
	// We'll need this too
4920
	$later = (int) date_format(date_add(date_create('@' . $when), date_interval_create_from_date_string('1 year')), 'U');
4921
4922
	// Prefer and give custom descriptions for these time zones
4923
	// If the description is left empty, it will be filled in with the names of matching cities
4924
	$timezone_descriptions = array(
4925
		'America/Adak' => 'Hawaii-Aleutian',
4926
		'Pacific/Honolulu' => 'Hawaii',
4927
		'Pacific/Marquesas' => 'Marquesas Islands',
4928
		'Pacific/Gambier' => 'Gambier Islands',
4929
		'America/Anchorage' => 'Alaska',
4930
		'Pacific/Pitcairn' => 'Pitcairn Islands',
4931
		'America/Los_Angeles' => 'Pacific Time (USA, Canada)',
4932
		'America/Denver' => 'Mountain Time (USA, Canada)',
4933
		'America/Phoenix' => 'Mountain Time (no DST)',
4934
		'America/Chicago' => 'Central Time (USA, Canada)',
4935
		'America/Belize' => 'Central Time (no DST)',
4936
		'America/New_York' => 'Eastern Time (USA, Canada)',
4937
		'America/Atikokan' => 'Eastern Time (no DST)',
4938
		'America/Halifax' => 'Atlantic Time (Canada)',
4939
		'America/Anguilla' => 'Atlantic Time (no DST)',
4940
		'America/St_Johns' => 'Newfoundland',
4941
		'America/Chihuahua' => 'Chihuahua, Mazatlan',
4942
		'Pacific/Easter' => 'Easter Island',
4943
		'Atlantic/Stanley' => 'Falkland Islands',
4944
		'America/Miquelon' => 'Saint Pierre and Miquelon',
4945
		'America/Argentina/Buenos_Aires' => 'Buenos Aires',
4946
		'America/Sao_Paulo' => 'Brasilia Time',
4947
		'America/Araguaina' => 'Brasilia Time (no DST)',
4948
		'America/Godthab' => 'Greenland',
4949
		'America/Noronha' => 'Fernando de Noronha',
4950
		'Atlantic/Reykjavik' => 'Greenwich Mean Time (no DST)',
4951
		'Europe/London' => '',
4952
		'Europe/Berlin' => 'Central European Time',
4953
		'Europe/Helsinki' => 'Eastern European Time',
4954
		'Africa/Brazzaville' => 'Brazzaville, Lagos, Porto-Novo',
4955
		'Asia/Jerusalem' => 'Jerusalem',
4956
		'Europe/Moscow' => '',
4957
		'Africa/Khartoum' => 'Eastern Africa Time',
4958
		'Asia/Riyadh' => 'Arabia Time',
4959
		'Asia/Kolkata' => 'India, Sri Lanka',
4960
		'Asia/Yekaterinburg' => 'Yekaterinburg, Tyumen',
4961
		'Asia/Dhaka' => 'Astana, Dhaka',
4962
		'Asia/Rangoon' => 'Yangon/Rangoon',
4963
		'Indian/Christmas' => 'Christmas Island',
4964
		'Antarctica/DumontDUrville' => 'Dumont D\'Urville Station',
4965
		'Australia/Lord_Howe' => 'Lord Howe Island',
4966
		'Pacific/Guadalcanal' => 'Solomon Islands',
4967
		'Pacific/Norfolk' => 'Norfolk Island',
4968
		'Pacific/Noumea' => 'New Caledonia',
4969
		'Pacific/Auckland' => 'Auckland, McMurdo Station',
4970
		'Pacific/Kwajalein' => 'Marshall Islands',
4971
		'Pacific/Chatham' => 'Chatham Islands',
4972
	);
4973
4974
	// Should we put time zones from certain countries at the top of the list?
4975
	$priority_countries = !empty($modSettings['timezone_priority_countries']) ? explode(',', $modSettings['timezone_priority_countries']) : array();
4976
	$priority_tzids = array();
4977
	foreach ($priority_countries as $country)
4978
	{
4979
		$country_tzids = @timezone_identifiers_list(DateTimeZone::PER_COUNTRY, strtoupper(trim($country)));
4980
		if (!empty($country_tzids))
4981
			$priority_tzids = array_merge($priority_tzids, $country_tzids);
4982
	}
4983
4984
	// Process the preferred timezones first, then the rest.
4985
	$tzids = array_keys($timezone_descriptions) + array_diff(timezone_identifiers_list(), array_keys($timezone_descriptions));
4986
4987
	// Idea here is to get exactly one representative identifier for each and every unique set of time zone rules.
4988
	foreach ($tzids as $tzid)
4989
	{
4990
		// We don't want UTC right now
4991
		if ($tzid == 'UTC')
4992
			continue;
4993
4994
		// First, get the set of transition rules for this tzid
4995
		$tzinfo = timezone_transitions_get(timezone_open($tzid), $when, $later);
4996
4997
		// There are a handful of time zones that PHP doesn't know the proper shortform for. Fix 'em if we can.
4998
		if (strspn($tzinfo[0]['abbr'], '+-') > 0)
4999
		{
5000
			$tz_location = timezone_location_get(timezone_open($tzid));
5001
5002
			// Kazakstan
5003
			if ($tz_location['country_code'] == 'KZ')
5004
				$tzinfo[0]['abbr'] = str_replace(array('+05', '+06'), array('AQTT', 'ALMT'), $tzinfo[0]['abbr']);
5005
5006
			// Russia likes to experiment with time zones
5007
			if ($tz_location['country_code'] == 'RU')
5008
			{
5009
				$msk_offset = intval($tzinfo[0]['abbr']) - 3;
5010
				$msk_offset = !empty($msk_offset) ? sprintf('%+0d', $msk_offset) : '';
5011
				$tzinfo[0]['abbr'] = 'MSK' . $msk_offset;
5012
			}
5013
5014
			// Still no good? We'll just mark it as a UTC offset
5015
			if (strspn($tzinfo[0]['abbr'], '+-') > 0)
5016
				$tzinfo[0]['abbr'] = 'UTC' . $tzinfo[0]['abbr'];
5017
		}
5018
5019
		$tzkey = serialize($tzinfo);
5020
5021
		// Don't overwrite our preferred tzids
5022
		if (empty($zones[$tzkey]['tzid']))
5023
			$zones[$tzkey]['tzid'] = $tzid;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$zones was never initialized. Although not strictly required by PHP, it is generally a good practice to add $zones = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
5024
5025
		// A time zone from a prioritized country?
5026
		if (in_array($tzid, $priority_tzids))
5027
			$priority_zones[$tzkey] = true;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$priority_zones was never initialized. Although not strictly required by PHP, it is generally a good practice to add $priority_zones = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
5028
5029
		// Keep track of the location and offset for this tzid
5030
		$zones[$tzkey]['locations'][] = str_replace(array('St_', '_'), array('St. ', ' '), array_pop(explode('/', $tzid)));
0 ignored issues
show
Bug introduced by
explode('/', $tzid) cannot be passed to array_pop() as the parameter $array expects a reference.
Loading history...
Bug introduced by
The variable $zones does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
5031
		$offsets[$tzkey] = $tzinfo[0]['offset'];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$offsets was never initialized. Although not strictly required by PHP, it is generally a good practice to add $offsets = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
5032
	}
5033
5034
	// Sort by offset
5035
	array_multisort($offsets, SORT_ASC, $zones);
5036
5037
	// Build the final array of formatted values
5038
	$priority_timezones = array();
5039
	$timezones = array();
5040
	foreach ($zones as $tzkey => $tzvalue)
5041
	{
5042
		$tzinfo = unserialize($tzkey);
5043
5044
		if (!empty($timezone_descriptions[$tzvalue['tzid']]))
5045
			$desc = $timezone_descriptions[$tzvalue['tzid']];
5046
		else
5047
			$desc = implode(', ', array_unique($tzvalue['locations']));
5048
5049
		if (isset($priority_zones[$tzkey]))
5050
			$priority_timezones[$tzvalue['tzid']] = $tzinfo[0]['abbr'] . ' - ' . $desc . ' [UTC' . date_format(date_create($tzvalue['tzid']), 'P') . ']';
5051
		else
5052
			$timezones[$tzvalue['tzid']] = $tzinfo[0]['abbr'] . ' - ' . $desc . ' [UTC' . date_format(date_create($tzvalue['tzid']), 'P') . ']';
5053
	}
5054
5055
	$timezones = array_merge(
5056
		$priority_timezones,
5057
		array('' => '(Forum Default)', 'UTC' => 'UTC - Coordinated Universal Time'),
5058
		$timezones
5059
	);
5060
5061
	return $timezones;
5062
}
5063
5064
/**
5065
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
5066
 * @return binary The IP address in binary or false
5067
 */
5068
function inet_ptod($ip_address)
5069
{
5070
	if (!isValidIP($ip_address))
5071
		return $ip_address;
5072
5073
	$bin = inet_pton($ip_address);
5074
	return $bin;
5075
}
5076
5077
/**
5078
 * @param binary $bin An IP address in IPv4, IPv6 (Either string (postgresql) or binary (other databases))
5079
 * @return string The IP address in presentation format or false on error
5080
 */
5081
function inet_dtop($bin)
5082
{
5083
	if(empty($bin))
5084
		return '';
5085
5086
	global $db_type;
5087
5088
	if ($db_type == 'postgresql')
5089
		return $bin;
5090
5091
	$ip_address = inet_ntop($bin);
5092
5093
	return $ip_address;
5094
}
5095
5096
/**
5097
 * Safe serialize() and unserialize() replacements
5098
 *
5099
 * @license Public Domain
5100
 *
5101
 * @author anthon (dot) pang (at) gmail (dot) com
5102
 */
5103
5104
/**
5105
 * Safe serialize() replacement. Recursive
5106
 * - output a strict subset of PHP's native serialized representation
5107
 * - does not serialize objects
5108
 *
5109
 * @param mixed $value
5110
 * @return string
5111
 */
5112
function _safe_serialize($value)
5113
{
5114
	if(is_null($value))
5115
		return 'N;';
5116
5117
	if(is_bool($value))
5118
		return 'b:'. (int) $value .';';
5119
5120
	if(is_int($value))
5121
		return 'i:'. $value .';';
5122
5123
	if(is_float($value))
5124
		return 'd:'. str_replace(',', '.', $value) .';';
5125
5126
	if(is_string($value))
5127
		return 's:'. strlen($value) .':"'. $value .'";';
5128
5129
	if(is_array($value))
5130
	{
5131
		$out = '';
5132
		foreach($value as $k => $v)
5133
			$out .= _safe_serialize($k) . _safe_serialize($v);
5134
5135
		return 'a:'. count($value) .':{'. $out .'}';
5136
	}
5137
5138
	// safe_serialize cannot serialize resources or objects.
5139
	return false;
5140
}
5141
/**
5142
 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issues.
5143
 *
5144
 * @param mixed $value
5145
 * @return string
5146
 */
5147 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...
5148
{
5149
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5150
	if (function_exists('mb_internal_encoding') &&
5151
		(((int) ini_get('mbstring.func_overload')) & 2))
5152
	{
5153
		$mbIntEnc = mb_internal_encoding();
5154
		mb_internal_encoding('ASCII');
5155
	}
5156
5157
	$out = _safe_serialize($value);
5158
5159
	if (isset($mbIntEnc))
5160
		mb_internal_encoding($mbIntEnc);
5161
5162
	return $out;
5163
}
5164
5165
/**
5166
 * Safe unserialize() replacement
5167
 * - accepts a strict subset of PHP's native serialized representation
5168
 * - does not unserialize objects
5169
 *
5170
 * @param string $str
5171
 * @return mixed
5172
 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
5173
 */
5174
function _safe_unserialize($str)
5175
{
5176
	// Input  is not a string.
5177
	if(empty($str) || !is_string($str))
5178
		return false;
5179
5180
	$stack = array();
5181
	$expected = array();
5182
5183
	/*
5184
	 * states:
5185
	 *   0 - initial state, expecting a single value or array
5186
	 *   1 - terminal state
5187
	 *   2 - in array, expecting end of array or a key
5188
	 *   3 - in array, expecting value or another array
5189
	 */
5190
	$state = 0;
5191
	while($state != 1)
5192
	{
5193
		$type = isset($str[0]) ? $str[0] : '';
5194
		if($type == '}')
5195
			$str = substr($str, 1);
5196
5197
		else if($type == 'N' && $str[1] == ';')
5198
		{
5199
			$value = null;
5200
			$str = substr($str, 2);
5201
		}
5202
		else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
5203
		{
5204
			$value = $matches[1] == '1' ? true : false;
5205
			$str = substr($str, 4);
5206
		}
5207
		else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
5208
		{
5209
			$value = (int)$matches[1];
5210
			$str = $matches[2];
5211
		}
5212
		else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
5213
		{
5214
			$value = (float)$matches[1];
5215
			$str = $matches[3];
5216
		}
5217
		else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
5218
		{
5219
			$value = substr($matches[2], 0, (int)$matches[1]);
5220
			$str = substr($matches[2], (int)$matches[1] + 2);
5221
		}
5222
		else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches))
5223
		{
5224
			$expectedLength = (int)$matches[1];
5225
			$str = $matches[2];
5226
		}
5227
5228
		// Object or unknown/malformed type.
5229
		else
5230
			return false;
5231
5232
		switch($state)
5233
		{
5234
			case 3: // In array, expecting value or another array.
5235
				if($type == 'a')
5236
				{
5237
					$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...
5238
					$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...
5239
					$list = &$list[$key];
5240
					$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...
5241
					$state = 2;
5242
					break;
5243
				}
5244
				if($type != '}')
5245
				{
5246
					$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...
5247
					$state = 2;
5248
					break;
5249
				}
5250
5251
				// Missing array value.
5252
				return false;
5253
5254
			case 2: // in array, expecting end of array or a key
5255
				if($type == '}')
5256
				{
5257
					// Array size is less than expected.
5258
					if(count($list) < end($expected))
5259
						return false;
5260
5261
					unset($list);
5262
					$list = &$stack[count($stack)-1];
5263
					array_pop($stack);
5264
5265
					// Go to terminal state if we're at the end of the root array.
5266
					array_pop($expected);
5267
5268
					if(count($expected) == 0)
5269
						$state = 1;
5270
5271
					break;
5272
				}
5273
5274
				if($type == 'i' || $type == 's')
5275
				{
5276
					// Array size exceeds expected length.
5277
					if(count($list) >= end($expected))
5278
						return false;
5279
5280
					$key = $value;
5281
					$state = 3;
5282
					break;
5283
				}
5284
5285
				// Illegal array index type.
5286
				return false;
5287
5288
			// Expecting array or value.
5289
			case 0:
5290
				if($type == 'a')
5291
				{
5292
					$data = array();
5293
					$list = &$data;
5294
					$expected[] = $expectedLength;
5295
					$state = 2;
5296
					break;
5297
				}
5298
5299
				if($type != '}')
5300
				{
5301
					$data = $value;
5302
					$state = 1;
5303
					break;
5304
				}
5305
5306
				// Not in array.
5307
				return false;
5308
		}
5309
	}
5310
5311
	// Trailing data in input.
5312
	if(!empty($str))
5313
		return false;
5314
5315
	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...
5316
}
5317
5318
/**
5319
 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
5320
 *
5321
 * @param string $str
5322
 * @return mixed
5323
 */
5324 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...
5325
{
5326
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5327
	if (function_exists('mb_internal_encoding') &&
5328
		(((int) ini_get('mbstring.func_overload')) & 0x02))
5329
	{
5330
		$mbIntEnc = mb_internal_encoding();
5331
		mb_internal_encoding('ASCII');
5332
	}
5333
5334
	$out = _safe_unserialize($str);
5335
5336
	if (isset($mbIntEnc))
5337
		mb_internal_encoding($mbIntEnc);
5338
5339
	return $out;
5340
}
5341
5342
/**
5343
 * Tries different modes to make file/dirs writable. Wrapper function for chmod()
5344
5345
 * @param string $file The file/dir full path.
5346
 * @param int $value Not needed, added for legacy reasons.
5347
 * @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.
5348
 */
5349
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...
5350
{
5351
	// No file? no checks!
5352
	if (empty($file))
5353
		return false;
5354
5355
	// Already writable?
5356
	if (is_writable($file))
5357
		return true;
5358
5359
	// Do we have a file or a dir?
5360
	$isDir = is_dir($file);
5361
	$isWritable = false;
5362
5363
	// Set different modes.
5364
	$chmodValues = $isDir ? array(0750, 0755, 0775, 0777) : array(0644, 0664, 0666);
5365
5366
	foreach($chmodValues as $val)
5367
	{
5368
		// If it's writable, break out of the loop.
5369
		if (is_writable($file))
5370
		{
5371
			$isWritable = true;
5372
			break;
5373
		}
5374
5375
		else
5376
			@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...
5377
	}
5378
5379
	return $isWritable;
5380
}
5381
5382
/**
5383
 * Wrapper function for json_decode() with error handling.
5384
5385
 * @param string $json The string to decode.
5386
 * @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.
5387
 * @param bool $logIt To specify if the error will be logged if theres any.
5388
 * @return array Either an empty array or the decoded data as an array.
5389
 */
5390
function smf_json_decode($json, $returnAsArray = false, $logIt = true)
5391
{
5392
	global $txt;
5393
5394
	// Come on...
5395
	if (empty($json) || !is_string($json))
5396
		return array();
5397
5398
	$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...
5399
	$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...
5400
5401
	$returnArray = @json_decode($json, $returnAsArray);
5402
5403
	// PHP 5.3 so no json_last_error_msg()
5404
	switch(json_last_error())
5405
	{
5406
		case JSON_ERROR_NONE:
5407
			$jsonError = false;
5408
			break;
5409
		case JSON_ERROR_DEPTH:
5410
			$jsonError =  'JSON_ERROR_DEPTH';
5411
			break;
5412
		case JSON_ERROR_STATE_MISMATCH:
5413
			$jsonError = 'JSON_ERROR_STATE_MISMATCH';
5414
			break;
5415
		case JSON_ERROR_CTRL_CHAR:
5416
			$jsonError = 'JSON_ERROR_CTRL_CHAR';
5417
			break;
5418
		case JSON_ERROR_SYNTAX:
5419
			$jsonError = 'JSON_ERROR_SYNTAX';
5420
			break;
5421
		case JSON_ERROR_UTF8:
5422
			$jsonError = 'JSON_ERROR_UTF8';
5423
			break;
5424
		default:
5425
			$jsonError = 'unknown';
5426
			break;
5427
	}
5428
5429
	// Something went wrong!
5430
	if (!empty($jsonError) && $logIt)
5431
	{
5432
		// Being a wrapper means we lost our smf_error_handler() privileges :(
5433
		$jsonDebug = debug_backtrace();
5434
		$jsonDebug = $jsonDebug[0];
5435
		loadLanguage('Errors');
5436
5437
		if (!empty($jsonDebug))
5438
			log_error($txt['json_'. $jsonError], 'critical', $jsonDebug['file'], $jsonDebug['line']);
5439
5440
		else
5441
			log_error($txt['json_'. $jsonError], 'critical');
5442
5443
		// Everyone expects an array.
5444
		return array();
5445
	}
5446
5447
	return $returnArray;
5448
}
5449
5450
/**
5451
 * Check the given String if he is a valid IPv4 or IPv6
5452
 * return true or false
5453
 */
5454
function isValidIP($IPString)
5455
{
5456
	return filter_var($IPString, FILTER_VALIDATE_IP) !== false;
5457
}
5458
5459
/**
5460
 * Outputs a response.
5461
 * It assumes the data is already a string.
5462
 * @param string $data The data to print
5463
 * @param string $type The content type. Defaults to Json.
5464
 * @return void
5465
 */
5466
function smf_serverResponse($data = '', $type = 'Content-Type: application/json')
5467
{
5468
	global $db_show_debug, $modSettings;
5469
5470
	// Defensive programming anyone?
5471
	if (empty($data))
5472
		return false;
5473
5474
	// Don't need extra stuff...
5475
	$db_show_debug = false;
5476
5477
	// Kill anything else.
5478
	ob_end_clean();
5479
5480
	if (!empty($modSettings['CompressedOutput']))
5481
		@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...
5482
5483
	else
5484
		ob_start();
5485
5486
	// Set the header.
5487
	header($type);
5488
5489
	// Echo!
5490
	echo $data;
5491
5492
	// Done.
5493
	obExit(false);
5494
}
5495
5496
/**
5497
 * Creates an optimized regex to match all known top level domains.
5498
 *
5499
 * The optimized regex is stored in $modSettings['tld_regex'].
5500
 *
5501
 * To update the stored version of the regex to use the latest list of valid TLDs from iana.org, set
5502
 * the $update parameter to true. Updating can take some time, based on network connectivity, so it
5503
 * should normally only be done by calling this function from a background or scheduled task.
5504
 *
5505
 * If $update is not true, but the regex is missing or invalid, the regex will be regenerated from a
5506
 * hard-coded list of TLDs. This regenerated regex will be overwritten on the next scheduled update.
5507
 *
5508
 * @param bool $update If true, fetch and process the latest offical list of TLDs from iana.org.
5509
 */
5510
function set_tld_regex($update = false)
5511
{
5512
	global $sourcedir, $smcFunc, $modSettings;
5513
	static $done = false;
5514
5515
	// If we don't need to do anything, don't
5516
	if (!$update && $done)
5517
		return;
5518
5519
	// Should we get a new copy of the official list of TLDs?
5520
	if ($update)
5521
	{
5522
		require_once($sourcedir . '/Subs-Package.php');
5523
		$tlds = fetch_web_data('http://data.iana.org/TLD/tlds-alpha-by-domain.txt');
5524
	}
5525
	// If we aren't updating and the regex is valid, we're done
5526
	elseif (!empty($modSettings['tld_regex']) && @preg_match('~' . $modSettings['tld_regex'] . '~', null) !== false)
5527
	{
5528
		$done = true;
5529
		return;
5530
	}
5531
5532
	// If we successfully got an update, process the list into an array
5533
	if (!empty($tlds))
5534
	{
5535
		// Clean $tlds and convert it to an array
5536
		$tlds = array_filter(explode("\n", strtolower($tlds)), function($line) {
5537
			$line = trim($line);
5538
			if (empty($line) || strpos($line, '#') !== false || strpos($line, ' ') !== false)
5539
				return false;
5540
			else
5541
				return true;
5542
		});
5543
5544
		// Convert Punycode to Unicode
5545
		$tlds = array_map(function ($input) {
5546
			$prefix = 'xn--';
5547
			$safe_char = 0xFFFC;
5548
			$base = 36;
5549
			$tmin = 1;
5550
			$tmax = 26;
5551
			$skew = 38;
5552
			$damp = 700;
5553
			$output_parts = array();
5554
5555
			$input = str_replace(strtoupper($prefix), $prefix, $input);
5556
5557
			$enco_parts = (array) explode('.', $input);
5558
5559
			foreach ($enco_parts as $encoded)
5560
			{
5561
				if (strpos($encoded,$prefix) !== 0 || strlen(trim(str_replace($prefix,'',$encoded))) == 0)
5562
				{
5563
					$output_parts[] = $encoded;
5564
					continue;
5565
				}
5566
5567
				$is_first = true;
5568
				$bias = 72;
5569
				$idx = 0;
5570
				$char = 0x80;
5571
				$decoded = array();
5572
				$output='';
5573
				$delim_pos = strrpos($encoded, '-');
5574
5575
				if ($delim_pos > strlen($prefix))
5576
				{
5577
					for ($k = strlen($prefix); $k < $delim_pos; ++$k)
5578
					{
5579
						$decoded[] = ord($encoded{$k});
5580
					}
5581
				}
5582
5583
				$deco_len = count($decoded);
5584
				$enco_len = strlen($encoded);
5585
5586
				for ($enco_idx = $delim_pos ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len)
5587
				{
5588
					for ($old_idx = $idx, $w = 1, $k = $base; 1 ; $k += $base)
5589
					{
5590
						$cp = ord($encoded{$enco_idx++});
5591
						$digit = ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $base));
5592
						$idx += $digit * $w;
5593
						$t = ($k <= $bias) ? $tmin : (($k >= $bias + $tmax) ? $tmax : ($k - $bias));
5594
5595
						if ($digit < $t)
5596
							break;
5597
5598
						$w = (int) ($w * ($base - $t));
5599
					}
5600
5601
					$delta = $idx - $old_idx;
5602
					$delta = intval($is_first ? ($delta / $damp) : ($delta / 2));
5603
					$delta += intval($delta / ($deco_len + 1));
5604
5605
					for ($k = 0; $delta > (($base - $tmin) * $tmax) / 2; $k += $base)
5606
						$delta = intval($delta / ($base - $tmin));
5607
5608
					$bias = intval($k + ($base - $tmin + 1) * $delta / ($delta + $skew));
5609
					$is_first = false;
5610
					$char += (int) ($idx / ($deco_len + 1));
5611
					$idx %= ($deco_len + 1);
5612
5613
					if ($deco_len > 0)
5614
					{
5615
						for ($i = $deco_len; $i > $idx; $i--)
5616
							$decoded[$i] = $decoded[($i - 1)];
5617
					}
5618
					$decoded[$idx++] = $char;
5619
				}
5620
5621
				foreach ($decoded as $k => $v)
5622
				{
5623
					// 7bit are transferred literally
5624
					if ($v < 128)
5625
						$output .= chr($v);
5626
5627
					// 2 bytes
5628
					elseif ($v < (1 << 11))
5629
						$output .= chr(192+($v >> 6)) . chr(128+($v & 63));
5630
5631
					// 3 bytes
5632
					elseif ($v < (1 << 16))
5633
						$output .= chr(224+($v >> 12)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5634
5635
					// 4 bytes
5636
					elseif ($v < (1 << 21))
5637
						$output .= chr(240+($v >> 18)) . chr(128+(($v >> 12) & 63)) . chr(128+(($v >> 6) & 63)) . chr(128+($v & 63));
5638
5639
					//  'Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k
5640
					else
5641
						$output .= $safe_char;
5642
				}
5643
5644
				$output_parts[] = $output;
5645
			}
5646
5647
			return implode('.', $output_parts);
5648
		}, $tlds);
5649
5650
		$schedule_update = false;
5651
	}
5652
	// Otherwise, use the 2012 list of gTLDs and ccTLDs for now and schedule a background update
5653
	else
5654
	{
5655
		$tlds = array('com', 'net', 'org', 'edu', 'gov', 'mil', 'aero', 'asia', 'biz', 'cat',
5656
			'coop', 'info', 'int', 'jobs', 'mobi', 'museum', 'name', 'post', 'pro', 'tel',
5657
			'travel', 'xxx', 'ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq',
5658
			'ar', 'as', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
5659
			'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cc',
5660
			'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr', 'cs', 'cu', 'cv',
5661
			'cx', 'cy', 'cz', 'dd', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'ee', 'eg', 'eh',
5662
			'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge',
5663
			'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw',
5664
			'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq',
5665
			'ir', 'is', 'it', 'ja', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn',
5666
			'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu',
5667
			'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo', 'mp',
5668
			'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'nc', 'ne', 'nf',
5669
			'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph',
5670
			'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru',
5671
			'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn',
5672
			'so', 'sr', 'ss', 'st', 'su', 'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th',
5673
			'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug',
5674
			'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye',
5675
			'yt', 'yu', 'za', 'zm', 'zw');
5676
5677
		$schedule_update = true;
5678
	}
5679
5680
	// build_regex() returns an array. We only need the first item.
5681
	$tld_regex = build_regex($tlds);
5682
	$tld_regex = array_shift($tld_regex);
5683
5684
	// Remember the new regex in $modSettings
5685
	updateSettings(array('tld_regex' => $tld_regex));
5686
5687
	// Schedule a background update if we need one
5688 View Code Duplication
	if (!empty($schedule_update))
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...
5689
	{
5690
		$smcFunc['db_insert']('insert', '{db_prefix}background_tasks',
5691
			array('task_file' => 'string-255', 'task_class' => 'string-255', 'task_data' => 'string', 'claimed_time' => 'int'),
5692
			array('$sourcedir/tasks/UpdateTldRegex.php', 'Update_TLD_Regex', '', 0), array()
5693
		);
5694
	}
5695
5696
	// Redundant repetition is redundant
5697
	$done = true;
5698
}
5699
5700
/**
5701
 * Creates optimized regular expressions from an array of strings.
5702
 *
5703
 * An optimized regex built using this function will be much faster than a simple regex built using
5704
 * `implode('|', $strings)` --- anywhere from several times to several orders of magnitude faster.
5705
 *
5706
 * However, the time required to build the optimized regex is approximately equal to the time it
5707
 * takes to execute the simple regex. Therefore, it is only worth calling this function if the
5708
 * resulting regex will be used more than once.
5709
 *
5710
 * Because PHP places an upper limit on the allowed length of a regex, very large arrays may be
5711
 * split and returned as multiple regexes. In such cases, you will need to iterate through all
5712
 * elements of the returned array in order to test all possible matches. (Note: if your array of
5713
 * alternative strings is large enough to require multiple regexes to accomodate it all, it is
5714
 * probably time to reconsider your coding choices. There is almost certainly a better way to do
5715
 * whatever you are trying to do with these giant regexes.)
5716
 *
5717
 * @param array $strings An array of strings to make a regex for.
5718
 * @param string $delim An optional delimiter character to pass to preg_quote().
5719
 * @return array An array of one or more regular expressions to match any of the input strings.
5720
 */
5721
function build_regex($strings, $delim = null)
5722
{
5723
	global $smcFunc;
5724
5725
	// The mb_* functions are faster than the $smcFunc ones, but may not be available
5726
	if (function_exists('mb_internal_encoding') && function_exists('mb_detect_encoding') && function_exists('mb_strlen') && function_exists('mb_substr'))
5727
	{
5728
		if (($string_encoding = mb_detect_encoding(implode(' ', $strings))) !== false)
5729
		{
5730
			$current_encoding = mb_internal_encoding();
5731
			mb_internal_encoding($string_encoding);
5732
		}
5733
5734
		$strlen = 'mb_strlen';
5735
		$substr = 'mb_substr';
5736
	}
5737
	else
5738
	{
5739
		$strlen = $smcFunc['strlen'];
5740
		$substr = $smcFunc['substr'];
5741
	}
5742
5743
	// This recursive function creates the index array from the strings
5744
	$add_string_to_index = function ($string, $index) use (&$strlen, &$substr, &$add_string_to_index)
5745
	{
5746
		static $depth = 0;
5747
		$depth++;
5748
5749
		$first = $substr($string, 0, 1);
5750
5751
		if (empty($index[$first]))
5752
			$index[$first] = array();
5753
5754
		if ($strlen($string) > 1)
5755
		{
5756
			// Sanity check on recursion
5757
			if ($depth > 99)
5758
				$index[$first][$substr($string, 1)] = '';
5759
5760
			else
5761
				$index[$first] = $add_string_to_index($substr($string, 1), $index[$first]);
5762
		}
5763
		else
5764
			$index[$first][''] = '';
5765
5766
		$depth--;
5767
		return $index;
5768
	};
5769
5770
	// This recursive function turns the index array into a regular expression
5771
	$index_to_regex = function (&$index, $delim) use (&$strlen, &$index_to_regex)
5772
	{
5773
		static $depth = 0;
5774
		$depth++;
5775
5776
		// Absolute max length for a regex is 32768, but we might need wiggle room
5777
		$max_length = 30000;
5778
5779
		$regex = array();
5780
		$length = 0;
5781
5782
		foreach ($index as $key => $value)
5783
		{
5784
			$key_regex = preg_quote($key, $delim);
5785
			$new_key = $key;
5786
5787
			if (empty($value))
5788
				$sub_regex = '';
5789
			else
5790
			{
5791
				$sub_regex = $index_to_regex($value, $delim);
5792
5793
				if (count(array_keys($value)) == 1)
5794
				{
5795
					$new_key_array = explode('(?'.'>', $sub_regex);
5796
					$new_key .= $new_key_array[0];
5797
				}
5798
				else
5799
					$sub_regex = '(?'.'>' . $sub_regex . ')';
5800
			}
5801
5802
			if ($depth > 1)
5803
				$regex[$new_key] = $key_regex . $sub_regex;
5804
			else
5805
			{
5806
				if (($length += strlen($key_regex) + 1) < $max_length || empty($regex))
5807
				{
5808
					$regex[$new_key] = $key_regex . $sub_regex;
5809
					unset($index[$key]);
5810
				}
5811
				else
5812
					break;
5813
			}
5814
		}
5815
5816
		// Sort by key length and then alphabetically
5817
		uksort($regex, function($k1, $k2) use (&$strlen) {
5818
			$l1 = $strlen($k1);
5819
			$l2 = $strlen($k2);
5820
5821
			if ($l1 == $l2)
5822
				return strcmp($k1, $k2) > 0 ? 1 : -1;
5823
			else
5824
				return $l1 > $l2 ? -1 : 1;
5825
		});
5826
5827
		$depth--;
5828
		return implode('|', $regex);
5829
	};
5830
5831
	// Now that the functions are defined, let's do this thing
5832
	$index = array();
5833
	$regexes = array();
5834
5835
	foreach ($strings as $string)
5836
		$index = $add_string_to_index($string, $index);
5837
5838
	while (!empty($index))
5839
		$regexes[] = '(?'.'>' . $index_to_regex($index, $delim) . ')';
5840
5841
	// Restore PHP's internal character encoding to whatever it was originally
5842
	if (!empty($current_encoding))
5843
		mb_internal_encoding($current_encoding);
5844
5845
	return $regexes;
5846
}
5847
5848
?>
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...
5849