Completed
Push — release-2.1 ( 2d94b4...31dcfd )
by Mert
07:42
created

Subs.php ➔ smf_serverResponse()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 29
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 13
nc 3
nop 2
dl 0
loc 29
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to, well, everything.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 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
			else
347
			{
348
				$member_names = array();
349
				$request = $smcFunc['db_query']('', '
350
					SELECT member_name
351
					FROM {db_prefix}members
352
					WHERE ' . $condition,
353
					$parameters
354
				);
355
				while ($row = $smcFunc['db_fetch_assoc']($request))
356
					$member_names[] = $row['member_name'];
357
				$smcFunc['db_free_result']($request);
358
			}
359
360
			if (!empty($member_names))
361
				foreach ($vars_to_integrate as $var)
362
					call_integration_hook('integrate_change_member_data', array($member_names, $var, &$data[$var], &$knownInts, &$knownFloats));
363
		}
364
	}
365
366
	$setString = '';
367
	foreach ($data as $var => $val)
368
	{
369
		$type = 'string';
370
		if (in_array($var, $knownInts))
371
			$type = 'int';
372
		elseif (in_array($var, $knownFloats))
373
			$type = 'float';
374
		elseif ($var == 'birthdate')
375
			$type = 'date';
376
		elseif ($var == 'member_ip')
377
			$type = 'inet';
378
		elseif ($var == 'member_ip2')
379
			$type = 'inet';
380
381
		// Doing an increment?
382
		if ($type == 'int' && ($val === '+' || $val === '-'))
383
		{
384
			$val = $var . ' ' . $val . ' 1';
385
			$type = 'raw';
386
		}
387
388
		// Ensure posts, instant_messages, and unread_messages don't overflow or underflow.
389
		if (in_array($var, array('posts', 'instant_messages', 'unread_messages')))
390
		{
391
			if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match))
392
			{
393
				if ($match[1] != '+ ')
394
					$val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END';
395
				$type = 'raw';
396
			}
397
		}
398
399
		$setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},';
400
		$parameters['p_' . $var] = $val;
401
	}
402
403
	$smcFunc['db_query']('', '
404
		UPDATE {db_prefix}members
405
		SET' . substr($setString, 0, -1) . '
406
		WHERE ' . $condition,
407
		$parameters
408
	);
409
410
	updateStats('postgroups', $members, array_keys($data));
411
412
	// Clear any caching?
413
	if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members))
414
	{
415
		if (!is_array($members))
416
			$members = array($members);
417
418
		foreach ($members as $member)
419
		{
420
			if ($modSettings['cache_enable'] >= 3)
421
			{
422
				cache_put_data('member_data-profile-' . $member, null, 120);
423
				cache_put_data('member_data-normal-' . $member, null, 120);
424
				cache_put_data('member_data-minimal-' . $member, null, 120);
425
			}
426
			cache_put_data('user_settings-' . $member, null, 60);
427
		}
428
	}
429
}
430
431
/**
432
 * Updates the settings table as well as $modSettings... only does one at a time if $update is true.
433
 *
434
 * - updates both the settings table and $modSettings array.
435
 * - all of changeArray's indexes and values are assumed to have escaped apostrophes (')!
436
 * - if a variable is already set to what you want to change it to, that
437
 *   variable will be skipped over; it would be unnecessary to reset.
438
 * - When use_update is true, UPDATEs will be used instead of REPLACE.
439
 * - when use_update is true, the value can be true or false to increment
440
 *  or decrement it, respectively.
441
 *
442
 * @param array $changeArray An array of info about what we're changing in 'setting' => 'value' format
443
 * @param bool $update Whether to use an UPDATE query instead of a REPLACE query
444
 */
445
function updateSettings($changeArray, $update = false)
446
{
447
	global $modSettings, $smcFunc;
448
449
	if (empty($changeArray) || !is_array($changeArray))
450
		return;
451
452
	$toRemove = array();
453
454
	// Go check if there is any setting to be removed.
455
	foreach ($changeArray as $k => $v)
456
		if ($v === null)
457
		{
458
			// Found some, remove them from the original array and add them to ours.
459
			unset($changeArray[$k]);
460
			$toRemove[] = $k;
461
		}
462
463
	// Proceed with the deletion.
464
	if (!empty($toRemove))
465
		$smcFunc['db_query']('', '
466
			DELETE FROM {db_prefix}settings
467
			WHERE variable IN ({array_string:remove})',
468
			array(
469
				'remove' => $toRemove,
470
			)
471
		);
472
473
	// In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs.
474
	if ($update)
475
	{
476
		foreach ($changeArray as $variable => $value)
477
		{
478
			$smcFunc['db_query']('', '
479
				UPDATE {db_prefix}settings
480
				SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value}
481
				WHERE variable = {string:variable}',
482
				array(
483
					'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value),
484
					'variable' => $variable,
485
				)
486
			);
487
			$modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value);
488
		}
489
490
		// Clean out the cache and make sure the cobwebs are gone too.
491
		cache_put_data('modSettings', null, 90);
492
493
		return;
494
	}
495
496
	$replaceArray = array();
497
	foreach ($changeArray as $variable => $value)
498
	{
499
		// Don't bother if it's already like that ;).
500
		if (isset($modSettings[$variable]) && $modSettings[$variable] == $value)
501
			continue;
502
		// If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it.
503
		elseif (!isset($modSettings[$variable]) && empty($value))
504
			continue;
505
506
		$replaceArray[] = array($variable, $value);
507
508
		$modSettings[$variable] = $value;
509
	}
510
511
	if (empty($replaceArray))
512
		return;
513
514
	$smcFunc['db_insert']('replace',
515
		'{db_prefix}settings',
516
		array('variable' => 'string-255', 'value' => 'string-65534'),
517
		$replaceArray,
518
		array('variable')
519
	);
520
521
	// Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
522
	cache_put_data('modSettings', null, 90);
523
}
524
525
/**
526
 * Constructs a page list.
527
 *
528
 * - builds the page list, e.g. 1 ... 6 7 [8] 9 10 ... 15.
529
 * - flexible_start causes it to use "url.page" instead of "url;start=page".
530
 * - very importantly, cleans up the start value passed, and forces it to
531
 *   be a multiple of num_per_page.
532
 * - checks that start is not more than max_value.
533
 * - base_url should be the URL without any start parameter on it.
534
 * - uses the compactTopicPagesEnable and compactTopicPagesContiguous
535
 *   settings to decide how to display the menu.
536
 *
537
 * an example is available near the function definition.
538
 * $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true);
539
 *
540
 * @param string $base_url The basic URL to be used for each link.
541
 * @param int &$start The start position, by reference. If this is not a multiple of the number of items per page, it is sanitized to be so and the value will persist upon the function's return.
542
 * @param int $max_value The total number of items you are paginating for.
543
 * @param int $num_per_page The number of items to be displayed on a given page. $start will be forced to be a multiple of this value.
544
 * @param bool $flexible_start Whether a ;start=x component should be introduced into the URL automatically (see above)
545
 * @param bool $show_prevnext Whether the Previous and Next links should be shown (should be on only when navigating the list)
546
 *
547
 * @return string The complete HTML of the page index that was requested, formatted by the template.
548
 */
549
function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false, $show_prevnext = true)
550
{
551
	global $modSettings, $context, $smcFunc, $settings, $txt;
552
553
	// Save whether $start was less than 0 or not.
554
	$start = (int) $start;
555
	$start_invalid = $start < 0;
556
557
	// Make sure $start is a proper variable - not less than 0.
558
	if ($start_invalid)
559
		$start = 0;
560
	// Not greater than the upper bound.
561
	elseif ($start >= $max_value)
562
		$start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page)));
563
	// And it has to be a multiple of $num_per_page!
564
	else
565
		$start = max(0, (int) $start - ((int) $start % (int) $num_per_page));
566
567
	$context['current_page'] = $start / $num_per_page;
568
569
	// Define some default page index settings if we don't already have it...
570
	if (!isset($settings['page_index']))
571
	{
572
		// This defines the formatting for the page indexes used throughout the forum.
573
		$settings['page_index'] = array(
574
			'extra_before' => '<span class="pages">' . $txt['pages'] . '</span>',
575
			'previous_page' => '<span class="generic_icons previous_page"></span>',
576
			'current_page' => '<span class="current_page">%1$d</span> ',
577
			'page' => '<a class="navPages" href="{URL}">%2$s</a> ',
578
			'expand_pages' => '<span class="expand_pages" onclick="expandPages(this, {LINK}, {FIRST_PAGE}, {LAST_PAGE}, {PER_PAGE});"> ... </span>',
579
			'next_page' => '<span class="generic_icons next_page"></span>',
580
			'extra_after' => '',
581
		);
582
	}
583
584
	$base_link = strtr($settings['page_index']['page'], array('{URL}' => $flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d'));
585
	$pageindex = $settings['page_index']['extra_before'];
586
587
	// Compact pages is off or on?
588
	if (empty($modSettings['compactTopicPagesEnable']))
589
	{
590
		// Show the left arrow.
591
		$pageindex .= $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']);
592
593
		// Show all the pages.
594
		$display_page = 1;
595
		for ($counter = 0; $counter < $max_value; $counter += $num_per_page)
596
			$pageindex .= $start == $counter && !$start_invalid ? sprintf($settings['page_index']['current_page'], $display_page++) : sprintf($base_link, $counter, $display_page++);
597
598
		// Show the right arrow.
599
		$display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page);
600
		if ($start != $counter - $max_value && !$start_invalid)
601
			$pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, $settings['page_index']['next_page']);
602
	}
603
	else
604
	{
605
		// If they didn't enter an odd value, pretend they did.
606
		$PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2;
607
608
		// Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page)
609
		if (!empty($start) && $show_prevnext)
610
			$pageindex .= sprintf($base_link, $start - $num_per_page, $settings['page_index']['previous_page']);
611
		else
612
			$pageindex .= '';
613
614
		// Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15)
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
	// Allow mods access before entering the main parse_bbc loop
964
	call_integration_hook('integrate_pre_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
965
966
	// Sift out the bbc for a performance improvement.
967
	if (empty($bbc_codes) || $message === false || !empty($parse_tags))
968
	{
969
		if (!empty($modSettings['disabledBBC']))
970
		{
971
			$disabled = array();
972
973
			$temp = explode(',', strtolower($modSettings['disabledBBC']));
974
975
			foreach ($temp as $tag)
976
				$disabled[trim($tag)] = true;
977
		}
978
979
		if (empty($modSettings['enableEmbeddedFlash']))
980
			$disabled['flash'] = true;
981
982
		/* The following bbc are formatted as an array, with keys as follows:
983
984
			tag: the tag's name - should be lowercase!
985
986
			type: one of...
987
				- (missing): [tag]parsed content[/tag]
988
				- unparsed_equals: [tag=xyz]parsed content[/tag]
989
				- parsed_equals: [tag=parsed data]parsed content[/tag]
990
				- unparsed_content: [tag]unparsed content[/tag]
991
				- closed: [tag], [tag/], [tag /]
992
				- unparsed_commas: [tag=1,2,3]parsed content[/tag]
993
				- unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
994
				- unparsed_equals_content: [tag=...]unparsed content[/tag]
995
996
			parameters: an optional array of parameters, for the form
997
			  [tag abc=123]content[/tag].  The array is an associative array
998
			  where the keys are the parameter names, and the values are an
999
			  array which may contain the following:
1000
				- match: a regular expression to validate and match the value.
1001
				- quoted: true if the value should be quoted.
1002
				- validate: callback to evaluate on the data, which is $data.
1003
				- value: a string in which to replace $1 with the data.
1004
				  either it or validate may be used, not both.
1005
				- optional: true if the parameter is optional.
1006
1007
			test: a regular expression to test immediately after the tag's
1008
			  '=', ' ' or ']'.  Typically, should have a \] at the end.
1009
			  Optional.
1010
1011
			content: only available for unparsed_content, closed,
1012
			  unparsed_commas_content, and unparsed_equals_content.
1013
			  $1 is replaced with the content of the tag.  Parameters
1014
			  are replaced in the form {param}.  For unparsed_commas_content,
1015
			  $2, $3, ..., $n are replaced.
1016
1017
			before: only when content is not used, to go before any
1018
			  content.  For unparsed_equals, $1 is replaced with the value.
1019
			  For unparsed_commas, $1, $2, ..., $n are replaced.
1020
1021
			after: similar to before in every way, except that it is used
1022
			  when the tag is closed.
1023
1024
			disabled_content: used in place of content when the tag is
1025
			  disabled.  For closed, default is '', otherwise it is '$1' if
1026
			  block_level is false, '<div>$1</div>' elsewise.
1027
1028
			disabled_before: used in place of before when disabled.  Defaults
1029
			  to '<div>' if block_level, '' if not.
1030
1031
			disabled_after: used in place of after when disabled.  Defaults
1032
			  to '</div>' if block_level, '' if not.
1033
1034
			block_level: set to true the tag is a "block level" tag, similar
1035
			  to HTML.  Block level tags cannot be nested inside tags that are
1036
			  not block level, and will not be implicitly closed as easily.
1037
			  One break following a block level tag may also be removed.
1038
1039
			trim: if set, and 'inside' whitespace after the begin tag will be
1040
			  removed.  If set to 'outside', whitespace after the end tag will
1041
			  meet the same fate.
1042
1043
			validate: except when type is missing or 'closed', a callback to
1044
			  validate the data as $data.  Depending on the tag's type, $data
1045
			  may be a string or an array of strings (corresponding to the
1046
			  replacement.)
1047
1048
			quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
1049
			  may be not set, 'optional', or 'required' corresponding to if
1050
			  the content may be quoted.  This allows the parser to read
1051
			  [tag="abc]def[esdf]"] properly.
1052
1053
			require_parents: an array of tag names, or not set.  If set, the
1054
			  enclosing tag *must* be one of the listed tags, or parsing won't
1055
			  occur.
1056
1057
			require_children: similar to require_parents, if set children
1058
			  won't be parsed if they are not in the list.
1059
1060
			disallow_children: similar to, but very different from,
1061
			  require_children, if it is set the listed tags will not be
1062
			  parsed inside the tag.
1063
1064
			parsed_tags_allowed: an array restricting what BBC can be in the
1065
			  parsed_equals parameter, if desired.
1066
		*/
1067
1068
		$codes = array(
1069
			array(
1070
				'tag' => 'abbr',
1071
				'type' => 'unparsed_equals',
1072
				'before' => '<abbr title="$1">',
1073
				'after' => '</abbr>',
1074
				'quoted' => 'optional',
1075
				'disabled_after' => ' ($1)',
1076
			),
1077
			array(
1078
				'tag' => 'anchor',
1079
				'type' => 'unparsed_equals',
1080
				'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]',
1081
				'before' => '<span id="post_$1">',
1082
				'after' => '</span>',
1083
			),
1084
			array(
1085
				'tag' => 'attach',
1086
				'type' => 'unparsed_content',
1087
				'parameters' => array(
1088
					'name' => array('optional' => true),
1089
					'type' => array('optional' => true),
1090
					'alt' => array('optional' => true),
1091
					'title' => array('optional' => true),
1092
					'width' => array('optional' => true, 'match' => '(\d+)'),
1093
					'height' => array('optional' => true, 'match' => '(\d+)'),
1094
				),
1095
				'content' => '$1',
1096
				'validate' => function (&$tag, &$data, $disabled, $params) use ($modSettings, $context, $sourcedir, $txt)
1097
				{
1098
					$returnContext = '';
1099
1100
					// BBC or the entire attachments feature is disabled
1101
					if (empty($modSettings['attachmentEnable']) || !empty($disabled['attach']))
1102
						return $data;
1103
1104
					// Save the attach ID.
1105
					$attachID = $data;
1106
1107
					// Kinda need this.
1108
					require_once($sourcedir . '/Subs-Attachments.php');
1109
1110
					$currentAttachment = parseAttachBBC($attachID);
1111
1112
					// parseAttachBBC will return a string ($txt key) rather than diying with a fatal_error. Up to you to decide what to do.
1113
					if (is_string($currentAttachment))
1114
						return $data = !empty($txt[$currentAttachment]) ? $txt[$currentAttachment] : $currentAttachment;
1115
1116
					if (!empty($currentAttachment['is_image']))
1117
					{
1118
						$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...
1119
						$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...
1120
1121
						if (!empty($params['{width}']) && !empty($params['{height}']))
1122
						{
1123
							$width = ' width="' . $params['{width}'] . '"';
1124
							$height = ' height="' . $params['{height}'] . '"';
1125
						}
1126 View Code Duplication
						elseif (!empty($params['{width}']) && empty($params['{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...
1127
						{
1128
							$width = ' width="' . $params['{width}'] . '"';
1129
							$height = '';
1130
						}
1131 View Code Duplication
						elseif (empty($params['{width}']) && !empty($params['{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...
1132
						{
1133
							$width = '';
1134
							$height = ' height="' . $params['{height}'] . '"';
1135
						}
1136
						else
1137
						{
1138
							$width = ' width="' . $currentAttachment['width'] . '"';
1139
							$height = ' height="' . $currentAttachment['height'] . '"';
1140
						}
1141
1142
						if ($currentAttachment['thumbnail']['has_thumb'] && empty($params['{width}']) && empty($params['{height}']))
1143
							$returnContext .= '
1144
													<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>';
1145
						else
1146
							$returnContext .= '
1147
													<img src="' . $currentAttachment['href'] . ';image" alt="' . $currentAttachment['name'] . '"' . $width . $height . '/>';
1148
					}
1149
1150
					// No image. Show a link.
1151
					else
1152
						$returnContext .= $currentAttachment['link'];
1153
1154
					// Gotta append what we just did.
1155
					$data = $returnContext;
1156
				},
1157
			),
1158
			array(
1159
				'tag' => 'b',
1160
				'before' => '<b>',
1161
				'after' => '</b>',
1162
			),
1163
			array(
1164
				'tag' => 'center',
1165
				'before' => '<div class="centertext">',
1166
				'after' => '</div>',
1167
				'block_level' => true,
1168
			),
1169
			array(
1170
				'tag' => 'code',
1171
				'type' => 'unparsed_content',
1172
				'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>',
1173
				// @todo Maybe this can be simplified?
1174
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1175
				{
1176
					if (!isset($disabled['code']))
1177
					{
1178
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
1179
1180 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...
1181
						{
1182
							// Do PHP code coloring?
1183
							if ($php_parts[$php_i] != '&lt;?php')
1184
								continue;
1185
1186
							$php_string = '';
1187
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1188
							{
1189
								$php_string .= $php_parts[$php_i];
1190
								$php_parts[$php_i++] = '';
1191
							}
1192
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1193
						}
1194
1195
						// Fix the PHP code stuff...
1196
						$data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1197
						$data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);
1198
1199
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1200
						if ($context['browser']['is_opera'])
1201
							$data .= '&nbsp;';
1202
					}
1203
				},
1204
				'block_level' => true,
1205
			),
1206
			array(
1207
				'tag' => 'code',
1208
				'type' => 'unparsed_equals_content',
1209
				'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>',
1210
				// @todo Maybe this can be simplified?
1211
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1212
				{
1213
					if (!isset($disabled['code']))
1214
					{
1215
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);
1216
1217 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...
1218
						{
1219
							// Do PHP code coloring?
1220
							if ($php_parts[$php_i] != '&lt;?php')
1221
								continue;
1222
1223
							$php_string = '';
1224
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1225
							{
1226
								$php_string .= $php_parts[$php_i];
1227
								$php_parts[$php_i++] = '';
1228
							}
1229
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1230
						}
1231
1232
						// Fix the PHP code stuff...
1233
						$data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1234
						$data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]);
1235
1236
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1237
						if ($context['browser']['is_opera'])
1238
							$data[0] .= '&nbsp;';
1239
					}
1240
				},
1241
				'block_level' => true,
1242
			),
1243
			array(
1244
				'tag' => 'color',
1245
				'type' => 'unparsed_equals',
1246
				'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]?)\))\]',
1247
				'before' => '<span style="color: $1;" class="bbc_color">',
1248
				'after' => '</span>',
1249
			),
1250
			array(
1251
				'tag' => 'email',
1252
				'type' => 'unparsed_content',
1253
				'content' => '<a href="mailto:$1" class="bbc_email">$1</a>',
1254
				// @todo Should this respect guest_hideContacts?
1255
				'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...
1256
				{
1257
					$data = strtr($data, array('<br>' => ''));
1258
				},
1259
			),
1260
			array(
1261
				'tag' => 'email',
1262
				'type' => 'unparsed_equals',
1263
				'before' => '<a href="mailto:$1" class="bbc_email">',
1264
				'after' => '</a>',
1265
				// @todo Should this respect guest_hideContacts?
1266
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1267
				'disabled_after' => ' ($1)',
1268
			),
1269
			array(
1270
				'tag' => 'flash',
1271
				'type' => 'unparsed_commas_content',
1272
				'test' => '\d+,\d+\]',
1273
				'content' => '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never">',
1274
				'validate' => function (&$tag, &$data, $disabled)
1275
				{
1276
					if (isset($disabled['url']))
1277
						$tag['content'] = '$1';
1278
					if (empty(parse_url($data[0], PHP_URL_SCHEME)))
1279
						$data[0] = 'http://' . ltrim($data[0], ':/');
1280
				},
1281
				'disabled_content' => '<a href="$1" target="_blank" class="new_win">$1</a>',
1282
			),
1283
			array(
1284
				'tag' => 'font',
1285
				'type' => 'unparsed_equals',
1286
				'test' => '[A-Za-z0-9_,\-\s]+?\]',
1287
				'before' => '<span style="font-family: $1;" class="bbc_font">',
1288
				'after' => '</span>',
1289
			),
1290
			array(
1291
				'tag' => 'html',
1292
				'type' => 'unparsed_content',
1293
				'content' => '$1',
1294
				'block_level' => true,
1295
				'disabled_content' => '$1',
1296
			),
1297
			array(
1298
				'tag' => 'hr',
1299
				'type' => 'closed',
1300
				'content' => '<hr>',
1301
				'block_level' => true,
1302
			),
1303
			array(
1304
				'tag' => 'i',
1305
				'before' => '<i>',
1306
				'after' => '</i>',
1307
			),
1308
			array(
1309
				'tag' => 'img',
1310
				'type' => 'unparsed_content',
1311
				'parameters' => array(
1312
					'alt' => array('optional' => true),
1313
					'title' => array('optional' => true),
1314
					'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
1315
					'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
1316
				),
1317
				'content' => '<img src="$1" alt="{alt}" title="{title}"{width}{height} class="bbc_img resized">',
1318 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...
1319
				{
1320
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1321
1322
					$data = strtr($data, array('<br>' => ''));
1323
					if (empty(parse_url($data, PHP_URL_SCHEME)))
1324
						$data = 'http://' . ltrim($data, ':/');
1325
1326
					if (substr($data, 0, 8) != 'https://' && $image_proxy_enabled)
1327
						$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1328
				},
1329
				'disabled_content' => '($1)',
1330
			),
1331
			array(
1332
				'tag' => 'img',
1333
				'type' => 'unparsed_content',
1334
				'content' => '<img src="$1" alt="" class="bbc_img">',
1335 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...
1336
				{
1337
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1338
1339
					$data = strtr($data, array('<br>' => ''));
1340
					if (empty(parse_url($data, PHP_URL_SCHEME)))
1341
						$data = 'http://' . ltrim($data, ':/');
1342
1343
					if (substr($data, 0, 8) != 'https://' && $image_proxy_enabled)
1344
						$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1345
				},
1346
				'disabled_content' => '($1)',
1347
			),
1348
			array(
1349
				'tag' => 'iurl',
1350
				'type' => 'unparsed_content',
1351
				'content' => '<a href="$1" class="bbc_link">$1</a>',
1352 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...
1353
				{
1354
					$data = strtr($data, array('<br>' => ''));
1355
					if (empty(parse_url($data, PHP_URL_SCHEME)))
1356
						$data = 'http://' . ltrim($data, ':/');
1357
				},
1358
			),
1359
			array(
1360
				'tag' => 'iurl',
1361
				'type' => 'unparsed_equals',
1362
				'quoted' => 'optional',
1363
				'before' => '<a href="$1" class="bbc_link">',
1364
				'after' => '</a>',
1365
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

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

Loading history...
1366
				{
1367 View Code Duplication
					if (substr($data, 0, 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...
1368
						$data = '#post_' . substr($data, 1);
1369
					if (empty(parse_url($data, PHP_URL_SCHEME)))
1370
						$data = 'http://' . ltrim($data, ':/');
1371
				},
1372
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1373
				'disabled_after' => ' ($1)',
1374
			),
1375
			array(
1376
				'tag' => 'left',
1377
				'before' => '<div style="text-align: left;">',
1378
				'after' => '</div>',
1379
				'block_level' => true,
1380
			),
1381
			array(
1382
				'tag' => 'li',
1383
				'before' => '<li>',
1384
				'after' => '</li>',
1385
				'trim' => 'outside',
1386
				'require_parents' => array('list'),
1387
				'block_level' => true,
1388
				'disabled_before' => '',
1389
				'disabled_after' => '<br>',
1390
			),
1391
			array(
1392
				'tag' => 'list',
1393
				'before' => '<ul class="bbc_list">',
1394
				'after' => '</ul>',
1395
				'trim' => 'inside',
1396
				'require_children' => array('li', 'list'),
1397
				'block_level' => true,
1398
			),
1399
			array(
1400
				'tag' => 'list',
1401
				'parameters' => array(
1402
					'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)'),
1403
				),
1404
				'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
1405
				'after' => '</ul>',
1406
				'trim' => 'inside',
1407
				'require_children' => array('li'),
1408
				'block_level' => true,
1409
			),
1410
			array(
1411
				'tag' => 'ltr',
1412
				'before' => '<bdo dir="ltr">',
1413
				'after' => '</bdo>',
1414
				'block_level' => true,
1415
			),
1416
			array(
1417
				'tag' => 'me',
1418
				'type' => 'unparsed_equals',
1419
				'before' => '<div class="meaction">* $1 ',
1420
				'after' => '</div>',
1421
				'quoted' => 'optional',
1422
				'block_level' => true,
1423
				'disabled_before' => '/me ',
1424
				'disabled_after' => '<br>',
1425
			),
1426
			array(
1427
				'tag' => 'member',
1428
				'type' => 'unparsed_equals',
1429
				'before' => '<a href="' . $scripturl . '?action=profile;u=$1" class="mention">@',
1430
				'after' => '</a>',
1431
			),
1432
			array(
1433
				'tag' => 'nobbc',
1434
				'type' => 'unparsed_content',
1435
				'content' => '$1',
1436
			),
1437
			array(
1438
				'tag' => 'php',
1439
				'type' => 'unparsed_content',
1440
				'content' => '<span class="phpcode">$1</span>',
1441
				'validate' => isset($disabled['php']) ? null : function (&$tag, &$data, $disabled)
1442
				{
1443
					if (!isset($disabled['php']))
1444
					{
1445
						$add_begin = substr(trim($data), 0, 5) != '&lt;?';
1446
						$data = highlight_php_code($add_begin ? '&lt;?php ' . $data . '?&gt;' : $data);
1447
						if ($add_begin)
1448
							$data = preg_replace(array('~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~', '~\?&gt;((?:</(font|span)>)*)$~'), '$1', $data, 2);
1449
					}
1450
				},
1451
				'block_level' => false,
1452
				'disabled_content' => '$1',
1453
			),
1454
			array(
1455
				'tag' => 'pre',
1456
				'before' => '<pre>',
1457
				'after' => '</pre>',
1458
			),
1459
			array(
1460
				'tag' => 'quote',
1461
				'before' => '<blockquote><cite>' . $txt['quote'] . '</cite>',
1462
				'after' => '</blockquote>',
1463
				'trim' => 'both',
1464
				'block_level' => true,
1465
			),
1466
			array(
1467
				'tag' => 'quote',
1468
				'parameters' => array(
1469
					'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
1470
				),
1471
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1472
				'after' => '</blockquote>',
1473
				'trim' => 'both',
1474
				'block_level' => true,
1475
			),
1476
			array(
1477
				'tag' => 'quote',
1478
				'type' => 'parsed_equals',
1479
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': $1</cite>',
1480
				'after' => '</blockquote>',
1481
				'trim' => 'both',
1482
				'quoted' => 'optional',
1483
				// Don't allow everything to be embedded with the author name.
1484
				'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
1485
				'block_level' => true,
1486
			),
1487
			array(
1488
				'tag' => 'quote',
1489
				'parameters' => array(
1490
					'author' => array('match' => '([^<>]{1,192}?)'),
1491
					'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|msg=\d+?|action=profile;u=\d+)'),
1492
					'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
1493
				),
1494
				'before' => '<blockquote><cite><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></cite>',
1495
				'after' => '</blockquote>',
1496
				'trim' => 'both',
1497
				'block_level' => true,
1498
			),
1499
			array(
1500
				'tag' => 'quote',
1501
				'parameters' => array(
1502
					'author' => array('match' => '(.{1,192}?)'),
1503
				),
1504
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1505
				'after' => '</blockquote>',
1506
				'trim' => 'both',
1507
				'block_level' => true,
1508
			),
1509
			array(
1510
				'tag' => 'right',
1511
				'before' => '<div style="text-align: right;">',
1512
				'after' => '</div>',
1513
				'block_level' => true,
1514
			),
1515
			array(
1516
				'tag' => 'rtl',
1517
				'before' => '<bdo dir="rtl">',
1518
				'after' => '</bdo>',
1519
				'block_level' => true,
1520
			),
1521
			array(
1522
				'tag' => 's',
1523
				'before' => '<s>',
1524
				'after' => '</s>',
1525
			),
1526
			array(
1527
				'tag' => 'size',
1528
				'type' => 'unparsed_equals',
1529
				'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]',
1530
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1531
				'after' => '</span>',
1532
			),
1533
			array(
1534
				'tag' => 'size',
1535
				'type' => 'unparsed_equals',
1536
				'test' => '[1-7]\]',
1537
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1538
				'after' => '</span>',
1539
				'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...
1540
				{
1541
					$sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
1542
					$data = $sizes[$data] . 'em';
1543
				},
1544
			),
1545
			array(
1546
				'tag' => 'sub',
1547
				'before' => '<sub>',
1548
				'after' => '</sub>',
1549
			),
1550
			array(
1551
				'tag' => 'sup',
1552
				'before' => '<sup>',
1553
				'after' => '</sup>',
1554
			),
1555
			array(
1556
				'tag' => 'table',
1557
				'before' => '<table class="bbc_table">',
1558
				'after' => '</table>',
1559
				'trim' => 'inside',
1560
				'require_children' => array('tr'),
1561
				'block_level' => true,
1562
			),
1563
			array(
1564
				'tag' => 'td',
1565
				'before' => '<td>',
1566
				'after' => '</td>',
1567
				'require_parents' => array('tr'),
1568
				'trim' => 'outside',
1569
				'block_level' => true,
1570
				'disabled_before' => '',
1571
				'disabled_after' => '',
1572
			),
1573
			array(
1574
				'tag' => 'time',
1575
				'type' => 'unparsed_content',
1576
				'content' => '$1',
1577
				'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...
1578
				{
1579
					if (is_numeric($data))
1580
						$data = timeformat($data);
1581
					else
1582
						$tag['content'] = '[time]$1[/time]';
1583
				},
1584
			),
1585
			array(
1586
				'tag' => 'tr',
1587
				'before' => '<tr>',
1588
				'after' => '</tr>',
1589
				'require_parents' => array('table'),
1590
				'require_children' => array('td'),
1591
				'trim' => 'both',
1592
				'block_level' => true,
1593
				'disabled_before' => '',
1594
				'disabled_after' => '',
1595
			),
1596
			array(
1597
				'tag' => 'u',
1598
				'before' => '<u>',
1599
				'after' => '</u>',
1600
			),
1601
			array(
1602
				'tag' => 'url',
1603
				'type' => 'unparsed_content',
1604
				'content' => '<a href="$1" class="bbc_link" target="_blank">$1</a>',
1605 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...
1606
				{
1607
					$data = strtr($data, array('<br>' => ''));
1608
					if (empty(parse_url($data, PHP_URL_SCHEME)))
1609
						$data = 'http://' . ltrim($data, ':/');
1610
				},
1611
			),
1612
			array(
1613
				'tag' => 'url',
1614
				'type' => 'unparsed_equals',
1615
				'quoted' => 'optional',
1616
				'before' => '<a href="$1" class="bbc_link" target="_blank">',
1617
				'after' => '</a>',
1618
				'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...
1619
				{
1620
					if (empty(parse_url($data, PHP_URL_SCHEME)))
1621
						$data = 'http://' . ltrim($data, ':/');
1622
				},
1623
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1624
				'disabled_after' => ' ($1)',
1625
			),
1626
		);
1627
1628
		// Inside these tags autolink is not recommendable.
1629
		$no_autolink_tags = array(
1630
			'url',
1631
			'iurl',
1632
			'email',
1633
		);
1634
1635
		// Let mods add new BBC without hassle.
1636
		call_integration_hook('integrate_bbc_codes', array(&$codes, &$no_autolink_tags));
1637
1638
		// This is mainly for the bbc manager, so it's easy to add tags above.  Custom BBC should be added above this line.
1639
		if ($message === false)
1640
		{
1641
			if (isset($temp_bbc))
1642
				$bbc_codes = $temp_bbc;
1643
			usort($codes, 'sort_bbc_tags');
1644
			return $codes;
1645
		}
1646
1647
		// So the parser won't skip them.
1648
		$itemcodes = array(
1649
			'*' => 'disc',
1650
			'@' => 'disc',
1651
			'+' => 'square',
1652
			'x' => 'square',
1653
			'#' => 'square',
1654
			'o' => 'circle',
1655
			'O' => 'circle',
1656
			'0' => 'circle',
1657
		);
1658
		if (!isset($disabled['li']) && !isset($disabled['list']))
1659
		{
1660
			foreach ($itemcodes as $c => $dummy)
1661
				$bbc_codes[$c] = array();
1662
		}
1663
1664
		// Shhhh!
1665
		if (!isset($disabled['color']))
1666
		{
1667
			$codes[] = array(
1668
				'tag' => 'chrissy',
1669
				'before' => '<span style="color: #cc0099;">',
1670
				'after' => ' :-*</span>',
1671
			);
1672
			$codes[] = array(
1673
				'tag' => 'kissy',
1674
				'before' => '<span style="color: #cc0099;">',
1675
				'after' => ' :-*</span>',
1676
			);
1677
		}
1678
1679
		foreach ($codes as $code)
1680
		{
1681
			// Make it easier to process parameters later
1682
			if (!empty($code['parameters']))
1683
				ksort($code['parameters'], SORT_STRING);
1684
1685
			// If we are not doing every tag only do ones we are interested in.
1686
			if (empty($parse_tags) || in_array($code['tag'], $parse_tags))
1687
				$bbc_codes[substr($code['tag'], 0, 1)][] = $code;
1688
		}
1689
		$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...
1690
	}
1691
1692
	// Shall we take the time to cache this?
1693
	if ($cache_id != '' && !empty($modSettings['cache_enable']) && (($modSettings['cache_enable'] >= 2 && isset($message[1000])) || isset($message[2400])) && empty($parse_tags))
1694
	{
1695
		// It's likely this will change if the message is modified.
1696
		$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']);
1697
1698
		if (($temp = cache_get_data($cache_key, 240)) != null)
1699
			return $temp;
1700
1701
		$cache_t = microtime();
1702
	}
1703
1704
	if ($smileys === 'print')
1705
	{
1706
		// [glow], [shadow], and [move] can't really be printed.
1707
		$disabled['glow'] = true;
1708
		$disabled['shadow'] = true;
1709
		$disabled['move'] = true;
1710
1711
		// Colors can't well be displayed... supposed to be black and white.
1712
		$disabled['color'] = true;
1713
		$disabled['black'] = true;
1714
		$disabled['blue'] = true;
1715
		$disabled['white'] = true;
1716
		$disabled['red'] = true;
1717
		$disabled['green'] = true;
1718
		$disabled['me'] = true;
1719
1720
		// Color coding doesn't make sense.
1721
		$disabled['php'] = true;
1722
1723
		// Links are useless on paper... just show the link.
1724
		$disabled['ftp'] = true;
1725
		$disabled['url'] = true;
1726
		$disabled['iurl'] = true;
1727
		$disabled['email'] = true;
1728
		$disabled['flash'] = true;
1729
1730
		// @todo Change maybe?
1731
		if (!isset($_GET['images']))
1732
			$disabled['img'] = true;
1733
1734
		// @todo Interface/setting to add more?
1735
	}
1736
1737
	$open_tags = array();
1738
	$message = strtr($message, array("\n" => '<br>'));
1739
1740
	foreach ($bbc_codes as $section) {
1741
		foreach ($section as $code) {
1742
			$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...
1743
		}
1744
	}
1745
	$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...
1746
1747
	// The non-breaking-space looks a bit different each time.
1748
	$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0';
1749
1750
	$pos = -1;
1751
	while ($pos !== false)
1752
	{
1753
		$last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
1754
		preg_match('~\[/?(?=' . $alltags_regex . ')~', $message, $matches, PREG_OFFSET_CAPTURE, $pos + 1);
1755
		$pos = isset($matches[0][1]) ? $matches[0][1] : false;
1756
1757
		// Failsafe.
1758
		if ($pos === false || $last_pos > $pos)
1759
			$pos = strlen($message) + 1;
1760
1761
		// Can't have a one letter smiley, URL, or email! (sorry.)
1762
		if ($last_pos < $pos - 1)
1763
		{
1764
			// Make sure the $last_pos is not negative.
1765
			$last_pos = max($last_pos, 0);
1766
1767
			// Pick a block of data to do some raw fixing on.
1768
			$data = substr($message, $last_pos, $pos - $last_pos);
1769
1770
			// Take care of some HTML!
1771
			if (!empty($modSettings['enablePostHTML']) && strpos($data, '&lt;') !== false)
1772
			{
1773
				$data = preg_replace('~&lt;a\s+href=((?:&quot;)?)((?:https?://|ftps?://|mailto:)\S+?)\\1&gt;~i', '[url=&quot;$2&quot;]', $data);
1774
				$data = preg_replace('~&lt;/a&gt;~i', '[/url]', $data);
1775
1776
				// <br> should be empty.
1777
				$empty_tags = array('br', 'hr');
1778
				foreach ($empty_tags as $tag)
1779
					$data = str_replace(array('&lt;' . $tag . '&gt;', '&lt;' . $tag . '/&gt;', '&lt;' . $tag . ' /&gt;'), '[' . $tag . ' /]', $data);
1780
1781
				// b, u, i, s, pre... basic tags.
1782
				$closable_tags = array('b', 'u', 'i', 's', 'em', 'ins', 'del', 'pre', 'blockquote');
1783
				foreach ($closable_tags as $tag)
1784
				{
1785
					$diff = substr_count($data, '&lt;' . $tag . '&gt;') - substr_count($data, '&lt;/' . $tag . '&gt;');
1786
					$data = strtr($data, array('&lt;' . $tag . '&gt;' => '<' . $tag . '>', '&lt;/' . $tag . '&gt;' => '</' . $tag . '>'));
1787
1788
					if ($diff > 0)
1789
						$data = substr($data, 0, -1) . str_repeat('</' . $tag . '>', $diff) . substr($data, -1);
1790
				}
1791
1792
				// Do <img ...> - with security... action= -> action-.
1793
				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);
1794
				if (!empty($matches[0]))
1795
				{
1796
					$replaces = array();
1797
					foreach ($matches[2] as $match => $imgtag)
1798
					{
1799
						$alt = empty($matches[3][$match]) ? '' : ' alt=' . preg_replace('~^&quot;|&quot;$~', '', $matches[3][$match]);
1800
1801
						// Remove action= from the URL - no funny business, now.
1802
						if (preg_match('~action(=|%3d)(?!dlattach)~i', $imgtag) != 0)
1803
							$imgtag = preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $imgtag);
1804
1805
						// Check if the image is larger than allowed.
1806
						if (!empty($modSettings['max_image_width']) && !empty($modSettings['max_image_height']))
1807
						{
1808
							list ($width, $height) = url_image_size($imgtag);
1809
1810 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...
1811
							{
1812
								$height = (int) (($modSettings['max_image_width'] * $height) / $width);
1813
								$width = $modSettings['max_image_width'];
1814
							}
1815
1816 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...
1817
							{
1818
								$width = (int) (($modSettings['max_image_height'] * $width) / $height);
1819
								$height = $modSettings['max_image_height'];
1820
							}
1821
1822
							// Set the new image tag.
1823
							$replaces[$matches[0][$match]] = '[img width=' . $width . ' height=' . $height . $alt . ']' . $imgtag . '[/img]';
1824
						}
1825
						else
1826
							$replaces[$matches[0][$match]] = '[img' . $alt . ']' . $imgtag . '[/img]';
1827
					}
1828
1829
					$data = strtr($data, $replaces);
1830
				}
1831
			}
1832
1833
			if (!empty($modSettings['autoLinkUrls']))
1834
			{
1835
				// Are we inside tags that should be auto linked?
1836
				$no_autolink_area = false;
1837
				if (!empty($open_tags))
1838
				{
1839
					foreach ($open_tags as $open_tag)
1840
						if (in_array($open_tag['tag'], $no_autolink_tags))
1841
							$no_autolink_area = true;
1842
				}
1843
1844
				// Don't go backwards.
1845
				// @todo Don't think is the real solution....
1846
				$lastAutoPos = isset($lastAutoPos) ? $lastAutoPos : 0;
1847
				if ($pos < $lastAutoPos)
1848
					$no_autolink_area = true;
1849
				$lastAutoPos = $pos;
1850
1851
				if (!$no_autolink_area)
1852
				{
1853
					// Parse any URLs
1854
					if (!isset($disabled['url']) && strpos($data, '[url') === false)
1855
					{
1856
						// @todo Add a cron job to download the current list of TLDs from http://data.iana.org/TLD/tlds-alpha-by-domain.txt, process it into a regex, and store it somewhere. Then replace this variable with a call of some sort to retrieve the stored regex.
1857
						$tld_regex = '(?:com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|ja|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)';
1858
1859
						$url_regex = '(?xi)
1860
\b
1861
(?:
1862
	[a-z][\w-]+:						# URL scheme and colon
1863
	(?:
1864
		/{1,3}							# 1-3 slashes
1865
		|								#	or
1866
		[a-z0-9%]						# Single letter or digit or "%"
1867
										# (Trying not to match e.g. "URI::Escape")
1868
	)
1869
	|									#	or
1870
	www\d{0,3}[.]						# "www.", "www1.", "www2." … "www999."
1871
	|									#	or
1872
	[a-z0-9.\-]+[.][a-z]{2,4}/			# looks like domain name followed by a slash
1873
)
1874
(?:										# One or more:
1875
	[^\s()<>]+							# Run of non-space, non-()<>
1876
	|									#	or
1877
	\(([^\s()<>]+|(\([^\s()<>]+\)))*\)	# balanced parens, up to 2 levels
1878
)+
1879
(?:										# End with:
1880
	\(([^\s()<>]+|(\([^\s()<>]+\)))*\)	# balanced parens, up to 2 levels
1881
	|									#	or
1882
	[^\s`!()\[\]{};:\'".,<>?«»“”‘’]		# not a space or one of these punct char
1883
)
1884
1885
|										# OR, the following to match naked domains:
1886
(?:
1887
	(?<!@)								# not preceded by a @, avoid matching foo@_gmail.com_
1888
	[a-z0-9]+
1889
	(?:[.\-][a-z0-9]+)*
1890
	[.]
1891
	'. $tld_regex . '
1892
	\b
1893
	/?
1894
	(?!@)								# not succeeded by a @, avoid matching "foo.na" in "[email protected]"
1895
)';
1896
1897
						$data = preg_replace_callback('~' . $url_regex . '~', function ($matches) {
1898
									$url = array_shift($matches);
1899
1900
									// If this isn't a clean URL, bail out
1901
									if ($url != filter_var($url, FILTER_SANITIZE_URL))
1902
										return $url;
1903
									
1904
									// Are we linking a naked domain name (e.g. "example.com")?
1905
									if (empty(parse_url($url, PHP_URL_SCHEME)))
1906
										$fullUrl = 'http://' . ltrim($url, ':/');
1907
									else
1908
										$fullUrl = $url;
1909
									
1910
									// Make sure that $fullUrl really is a valid URL, including a valid host name
1911
									if (filter_var($fullUrl, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED) === false)
1912
										return $url;
1913
1914
									// Time to do the deed
1915
									if (parse_url($fullUrl, PHP_URL_SCHEME) == 'mailto')
1916
									{
1917
										$email_address = str_replace('mailto:', '', $url);
1918
										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...
1919
											return '[email=' . $url . ']' . $url . '[/email]';
1920
										else
1921
											return $url;
1922
									}
1923
									else
1924
										return '[url=&quot;' . str_replace(array('[', ']'), array('&#91;', '&#93;'), $fullUrl) . '&quot;]' . $url . '[/url]';
1925
								}, $data);
1926
					}
1927
1928
					// Next, emails...
1929
					if (!isset($disabled['email']) && strpos($data, '@') !== false && strpos($data, '[email') === false)
1930
					{
1931
						$data = preg_replace('~(?<=[\?\s' . $non_breaking_space . '\[\]()*\\\;>]|^)([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?,\s' . $non_breaking_space . '\[\]()*\\\]|$|<br>|&nbsp;|&gt;|&lt;|&quot;|&#039;|\.(?:\.|;|&nbsp;|\s|$|<br>))~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data);
1932
						$data = preg_replace('~(?<=<br>)([\w\-\.]{1,80}@[\w\-]+\.[\w\-\.]+[\w\-])(?=[?\.,;\s' . $non_breaking_space . '\[\]()*\\\]|$|<br>|&nbsp;|&gt;|&lt;|&quot;|&#039;)~' . ($context['utf8'] ? 'u' : ''), '[email]$1[/email]', $data);
1933
					}
1934
				}
1935
			}
1936
1937
			$data = strtr($data, array("\t" => '&nbsp;&nbsp;&nbsp;'));
1938
1939
			// If it wasn't changed, no copying or other boring stuff has to happen!
1940
			if ($data != substr($message, $last_pos, $pos - $last_pos))
1941
			{
1942
				$message = substr($message, 0, $last_pos) . $data . substr($message, $pos);
1943
1944
				// Since we changed it, look again in case we added or removed a tag.  But we don't want to skip any.
1945
				$old_pos = strlen($data) + $last_pos;
1946
				$pos = strpos($message, '[', $last_pos);
1947
				$pos = $pos === false ? $old_pos : min($pos, $old_pos);
1948
			}
1949
		}
1950
1951
		// Are we there yet?  Are we there yet?
1952
		if ($pos >= strlen($message) - 1)
1953
			break;
1954
1955
		$tags = strtolower($message[$pos + 1]);
1956
1957
		if ($tags == '/' && !empty($open_tags))
1958
		{
1959
			$pos2 = strpos($message, ']', $pos + 1);
1960
			if ($pos2 == $pos + 2)
1961
				continue;
1962
1963
			$look_for = strtolower(substr($message, $pos + 2, $pos2 - $pos - 2));
1964
1965
			$to_close = array();
1966
			$block_level = null;
1967
1968
			do
1969
			{
1970
				$tag = array_pop($open_tags);
1971
				if (!$tag)
1972
					break;
1973
1974
				if (!empty($tag['block_level']))
1975
				{
1976
					// Only find out if we need to.
1977
					if ($block_level === false)
1978
					{
1979
						array_push($open_tags, $tag);
1980
						break;
1981
					}
1982
1983
					// The idea is, if we are LOOKING for a block level tag, we can close them on the way.
1984 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...
1985
					{
1986
						foreach ($bbc_codes[$look_for[0]] as $temp)
1987
							if ($temp['tag'] == $look_for)
1988
							{
1989
								$block_level = !empty($temp['block_level']);
1990
								break;
1991
							}
1992
					}
1993
1994
					if ($block_level !== true)
1995
					{
1996
						$block_level = false;
1997
						array_push($open_tags, $tag);
1998
						break;
1999
					}
2000
				}
2001
2002
				$to_close[] = $tag;
2003
			}
2004
			while ($tag['tag'] != $look_for);
2005
2006
			// Did we just eat through everything and not find it?
2007
			if ((empty($open_tags) && (empty($tag) || $tag['tag'] != $look_for)))
2008
			{
2009
				$open_tags = $to_close;
2010
				continue;
2011
			}
2012
			elseif (!empty($to_close) && $tag['tag'] != $look_for)
2013
			{
2014 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...
2015
				{
2016
					foreach ($bbc_codes[$look_for[0]] as $temp)
2017
						if ($temp['tag'] == $look_for)
2018
						{
2019
							$block_level = !empty($temp['block_level']);
2020
							break;
2021
						}
2022
				}
2023
2024
				// We're not looking for a block level tag (or maybe even a tag that exists...)
2025
				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...
2026
				{
2027
					foreach ($to_close as $tag)
2028
						array_push($open_tags, $tag);
2029
					continue;
2030
				}
2031
			}
2032
2033
			foreach ($to_close as $tag)
2034
			{
2035
				$message = substr($message, 0, $pos) . "\n" . $tag['after'] . "\n" . substr($message, $pos2 + 1);
2036
				$pos += strlen($tag['after']) + 2;
2037
				$pos2 = $pos - 1;
2038
2039
				// See the comment at the end of the big loop - just eating whitespace ;).
2040 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...
2041
					$message = substr($message, 0, $pos) . substr($message, $pos + 4);
2042 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...
2043
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2044
			}
2045
2046
			if (!empty($to_close))
2047
			{
2048
				$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...
2049
				$pos--;
2050
			}
2051
2052
			continue;
2053
		}
2054
2055
		// No tags for this character, so just keep going (fastest possible course.)
2056
		if (!isset($bbc_codes[$tags]))
2057
			continue;
2058
2059
		$inside = empty($open_tags) ? null : $open_tags[count($open_tags) - 1];
2060
		$tag = null;
2061
		foreach ($bbc_codes[$tags] as $possible)
2062
		{
2063
			$pt_strlen = strlen($possible['tag']);
2064
2065
			// Not a match?
2066
			if (strtolower(substr($message, $pos + 1, $pt_strlen)) != $possible['tag'])
2067
				continue;
2068
2069
			$next_c = $message[$pos + 1 + $pt_strlen];
2070
2071
			// A test validation?
2072
			if (isset($possible['test']) && preg_match('~^' . $possible['test'] . '~', substr($message, $pos + 1 + $pt_strlen + 1)) === 0)
2073
				continue;
2074
			// Do we want parameters?
2075
			elseif (!empty($possible['parameters']))
2076
			{
2077
				if ($next_c != ' ')
2078
					continue;
2079
			}
2080
			elseif (isset($possible['type']))
2081
			{
2082
				// Do we need an equal sign?
2083
				if (in_array($possible['type'], array('unparsed_equals', 'unparsed_commas', 'unparsed_commas_content', 'unparsed_equals_content', 'parsed_equals')) && $next_c != '=')
2084
					continue;
2085
				// Maybe we just want a /...
2086
				if ($possible['type'] == 'closed' && $next_c != ']' && substr($message, $pos + 1 + $pt_strlen, 2) != '/]' && substr($message, $pos + 1 + $pt_strlen, 3) != ' /]')
2087
					continue;
2088
				// An immediate ]?
2089
				if ($possible['type'] == 'unparsed_content' && $next_c != ']')
2090
					continue;
2091
			}
2092
			// No type means 'parsed_content', which demands an immediate ] without parameters!
2093
			elseif ($next_c != ']')
2094
				continue;
2095
2096
			// Check allowed tree?
2097
			if (isset($possible['require_parents']) && ($inside === null || !in_array($inside['tag'], $possible['require_parents'])))
2098
				continue;
2099
			elseif (isset($inside['require_children']) && !in_array($possible['tag'], $inside['require_children']))
2100
				continue;
2101
			// If this is in the list of disallowed child tags, don't parse it.
2102
			elseif (isset($inside['disallow_children']) && in_array($possible['tag'], $inside['disallow_children']))
2103
				continue;
2104
2105
			$pos1 = $pos + 1 + $pt_strlen + 1;
2106
2107
			// Quotes can have alternate styling, we do this php-side due to all the permutations of quotes.
2108
			if ($possible['tag'] == 'quote')
2109
			{
2110
				// Start with standard
2111
				$quote_alt = false;
2112
				foreach ($open_tags as $open_quote)
2113
				{
2114
					// Every parent quote this quote has flips the styling
2115
					if ($open_quote['tag'] == 'quote')
2116
						$quote_alt = !$quote_alt;
2117
				}
2118
				// Add a class to the quote to style alternating blockquotes
2119
				$possible['before'] = strtr($possible['before'], array('<blockquote>' => '<blockquote class="bbc_' . ($quote_alt ? 'alternate' : 'standard') . '_quote">'));
2120
			}
2121
2122
			// This is long, but it makes things much easier and cleaner.
2123
			if (!empty($possible['parameters']))
2124
			{
2125
				// Build a regular expression for each parameter for the current tag.
2126
				$preg = array();
2127
				foreach ($possible['parameters'] as $p => $info)
2128
					$preg[] = '(\s+' . $p . '=' . (empty($info['quoted']) ? '' : '&quot;') . (isset($info['match']) ? $info['match'] : '(.+?)') . (empty($info['quoted']) ? '' : '&quot;') . '\s*)' . (empty($info['optional']) ? '' : '?');
2129
2130
				// Extract the string that potentially holds our parameters.
2131
				$blob = preg_split('~\[/?(?:' . $alltags_regex . ')~i', substr($message, $pos));
2132
				$blobs = preg_split('~\]~i', $blob[1]);
2133
2134
				$splitters = implode('=|', array_keys($possible['parameters'])) . '=';
2135
2136
				// Progressively append more blobs until we find our parameters or run out of blobs
2137
				$blob_counter = 1;
2138
				while ($blob_counter <= count($blobs))
2139
				{
2140
2141
					$given_param_string = implode(']', array_slice($blobs, 0, $blob_counter++));
2142
2143
					$given_params = preg_split('~\s(?=(' . $splitters . '))~i', $given_param_string);
2144
					sort($given_params, SORT_STRING);
2145
2146
					$match = preg_match('~^' . implode('', $preg) . '$~i', implode(' ', $given_params), $matches) !== 0;
2147
2148
					if ($match)
2149
						$blob_counter = count($blobs) + 1;
2150
				}
2151
2152
				// Didn't match our parameter list, try the next possible.
2153
				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...
2154
					continue;
2155
2156
				$params = array();
2157
				for ($i = 1, $n = count($matches); $i < $n; $i += 2)
2158
				{
2159
					$key = strtok(ltrim($matches[$i]), '=');
2160
					if (isset($possible['parameters'][$key]['value']))
2161
						$params['{' . $key . '}'] = strtr($possible['parameters'][$key]['value'], array('$1' => $matches[$i + 1]));
2162
					elseif (isset($possible['parameters'][$key]['validate']))
2163
						$params['{' . $key . '}'] = $possible['parameters'][$key]['validate']($matches[$i + 1]);
2164
					else
2165
						$params['{' . $key . '}'] = $matches[$i + 1];
2166
2167
					// Just to make sure: replace any $ or { so they can't interpolate wrongly.
2168
					$params['{' . $key . '}'] = strtr($params['{' . $key . '}'], array('$' => '&#036;', '{' => '&#123;'));
2169
				}
2170
2171
				foreach ($possible['parameters'] as $p => $info)
2172
				{
2173
					if (!isset($params['{' . $p . '}']))
2174
						$params['{' . $p . '}'] = '';
2175
				}
2176
2177
				$tag = $possible;
2178
2179
				// Put the parameters into the string.
2180
				if (isset($tag['before']))
2181
					$tag['before'] = strtr($tag['before'], $params);
2182
				if (isset($tag['after']))
2183
					$tag['after'] = strtr($tag['after'], $params);
2184
				if (isset($tag['content']))
2185
					$tag['content'] = strtr($tag['content'], $params);
2186
2187
				$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...
2188
			}
2189
			else
2190
			{
2191
				$tag = $possible;
2192
				$params = array();
2193
			}
2194
			break;
2195
		}
2196
2197
		// Item codes are complicated buggers... they are implicit [li]s and can make [list]s!
2198
		if ($smileys !== false && $tag === null && isset($itemcodes[$message[$pos + 1]]) && $message[$pos + 2] == ']' && !isset($disabled['list']) && !isset($disabled['li']))
2199
		{
2200
			if ($message[$pos + 1] == '0' && !in_array($message[$pos - 1], array(';', ' ', "\t", "\n", '>')))
2201
				continue;
2202
2203
			$tag = $itemcodes[$message[$pos + 1]];
2204
2205
			// First let's set up the tree: it needs to be in a list, or after an li.
2206
			if ($inside === null || ($inside['tag'] != 'list' && $inside['tag'] != 'li'))
2207
			{
2208
				$open_tags[] = array(
2209
					'tag' => 'list',
2210
					'after' => '</ul>',
2211
					'block_level' => true,
2212
					'require_children' => array('li'),
2213
					'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2214
				);
2215
				$code = '<ul class="bbc_list">';
2216
			}
2217
			// We're in a list item already: another itemcode?  Close it first.
2218
			elseif ($inside['tag'] == 'li')
2219
			{
2220
				array_pop($open_tags);
2221
				$code = '</li>';
2222
			}
2223
			else
2224
				$code = '';
2225
2226
			// Now we open a new tag.
2227
			$open_tags[] = array(
2228
				'tag' => 'li',
2229
				'after' => '</li>',
2230
				'trim' => 'outside',
2231
				'block_level' => true,
2232
				'disallow_children' => isset($inside['disallow_children']) ? $inside['disallow_children'] : null,
2233
			);
2234
2235
			// First, open the tag...
2236
			$code .= '<li' . ($tag == '' ? '' : ' type="' . $tag . '"') . '>';
2237
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos + 3);
2238
			$pos += strlen($code) - 1 + 2;
2239
2240
			// Next, find the next break (if any.)  If there's more itemcode after it, keep it going - otherwise close!
2241
			$pos2 = strpos($message, '<br>', $pos);
2242
			$pos3 = strpos($message, '[/', $pos);
2243
			if ($pos2 !== false && ($pos2 <= $pos3 || $pos3 === false))
2244
			{
2245
				preg_match('~^(<br>|&nbsp;|\s|\[)+~', substr($message, $pos2 + 4), $matches);
2246
				$message = substr($message, 0, $pos2) . (!empty($matches[0]) && substr($matches[0], -1) == '[' ? '[/li]' : '[/li][/list]') . substr($message, $pos2);
2247
2248
				$open_tags[count($open_tags) - 2]['after'] = '</ul>';
2249
			}
2250
			// Tell the [list] that it needs to close specially.
2251
			else
2252
			{
2253
				// Move the li over, because we're not sure what we'll hit.
2254
				$open_tags[count($open_tags) - 1]['after'] = '';
2255
				$open_tags[count($open_tags) - 2]['after'] = '</li></ul>';
2256
			}
2257
2258
			continue;
2259
		}
2260
2261
		// Implicitly close lists and tables if something other than what's required is in them.  This is needed for itemcode.
2262
		if ($tag === null && $inside !== null && !empty($inside['require_children']))
2263
		{
2264
			array_pop($open_tags);
2265
2266
			$message = substr($message, 0, $pos) . "\n" . $inside['after'] . "\n" . substr($message, $pos);
2267
			$pos += strlen($inside['after']) - 1 + 2;
2268
		}
2269
2270
		// No tag?  Keep looking, then.  Silly people using brackets without actual tags.
2271
		if ($tag === null)
2272
			continue;
2273
2274
		// Propagate the list to the child (so wrapping the disallowed tag won't work either.)
2275
		if (isset($inside['disallow_children']))
2276
			$tag['disallow_children'] = isset($tag['disallow_children']) ? array_unique(array_merge($tag['disallow_children'], $inside['disallow_children'])) : $inside['disallow_children'];
2277
2278
		// Is this tag disabled?
2279
		if (isset($disabled[$tag['tag']]))
2280
		{
2281
			if (!isset($tag['disabled_before']) && !isset($tag['disabled_after']) && !isset($tag['disabled_content']))
2282
			{
2283
				$tag['before'] = !empty($tag['block_level']) ? '<div>' : '';
2284
				$tag['after'] = !empty($tag['block_level']) ? '</div>' : '';
2285
				$tag['content'] = isset($tag['type']) && $tag['type'] == 'closed' ? '' : (!empty($tag['block_level']) ? '<div>$1</div>' : '$1');
2286
			}
2287
			elseif (isset($tag['disabled_before']) || isset($tag['disabled_after']))
2288
			{
2289
				$tag['before'] = isset($tag['disabled_before']) ? $tag['disabled_before'] : (!empty($tag['block_level']) ? '<div>' : '');
2290
				$tag['after'] = isset($tag['disabled_after']) ? $tag['disabled_after'] : (!empty($tag['block_level']) ? '</div>' : '');
2291
			}
2292
			else
2293
				$tag['content'] = $tag['disabled_content'];
2294
		}
2295
2296
		// we use this a lot
2297
		$tag_strlen = strlen($tag['tag']);
2298
2299
		// The only special case is 'html', which doesn't need to close things.
2300
		if (!empty($tag['block_level']) && $tag['tag'] != 'html' && empty($inside['block_level']))
2301
		{
2302
			$n = count($open_tags) - 1;
2303
			while (empty($open_tags[$n]['block_level']) && $n >= 0)
2304
				$n--;
2305
2306
			// Close all the non block level tags so this tag isn't surrounded by them.
2307
			for ($i = count($open_tags) - 1; $i > $n; $i--)
2308
			{
2309
				$message = substr($message, 0, $pos) . "\n" . $open_tags[$i]['after'] . "\n" . substr($message, $pos);
2310
				$ot_strlen = strlen($open_tags[$i]['after']);
2311
				$pos += $ot_strlen + 2;
2312
				$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...
2313
2314
				// Trim or eat trailing stuff... see comment at the end of the big loop.
2315 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...
2316
					$message = substr($message, 0, $pos) . substr($message, $pos + 4);
2317 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...
2318
					$message = substr($message, 0, $pos) . substr($message, $pos + strlen($matches[0]));
2319
2320
				array_pop($open_tags);
2321
			}
2322
		}
2323
2324
		// No type means 'parsed_content'.
2325
		if (!isset($tag['type']))
2326
		{
2327
			// @todo Check for end tag first, so people can say "I like that [i] tag"?
2328
			$open_tags[] = $tag;
2329
			$message = substr($message, 0, $pos) . "\n" . $tag['before'] . "\n" . substr($message, $pos1);
2330
			$pos += strlen($tag['before']) - 1 + 2;
2331
		}
2332
		// Don't parse the content, just skip it.
2333
		elseif ($tag['type'] == 'unparsed_content')
2334
		{
2335
			$pos2 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos1);
2336
			if ($pos2 === false)
2337
				continue;
2338
2339
			$data = substr($message, $pos1, $pos2 - $pos1);
2340
2341
			if (!empty($tag['block_level']) && substr($data, 0, 4) == '<br>')
2342
				$data = substr($data, 4);
2343
2344
			if (isset($tag['validate']))
2345
				$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...
2346
2347
			$code = strtr($tag['content'], array('$1' => $data));
2348
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 3 + $tag_strlen);
2349
2350
			$pos += strlen($code) - 1 + 2;
2351
			$last_pos = $pos + 1;
2352
2353
		}
2354
		// Don't parse the content, just skip it.
2355
		elseif ($tag['type'] == 'unparsed_equals_content')
2356
		{
2357
			// The value may be quoted for some tags - check.
2358 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...
2359
			{
2360
				$quoted = substr($message, $pos1, 6) == '&quot;';
2361
				if ($tag['quoted'] != 'optional' && !$quoted)
2362
					continue;
2363
2364
				if ($quoted)
2365
					$pos1 += 6;
2366
			}
2367
			else
2368
				$quoted = false;
2369
2370
			$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...
2371
			if ($pos2 === false)
2372
				continue;
2373
2374
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2375
			if ($pos3 === false)
2376
				continue;
2377
2378
			$data = array(
2379
				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...
2380
				substr($message, $pos1, $pos2 - $pos1)
2381
			);
2382
2383
			if (!empty($tag['block_level']) && substr($data[0], 0, 4) == '<br>')
2384
				$data[0] = substr($data[0], 4);
2385
2386
			// Validation for my parking, please!
2387
			if (isset($tag['validate']))
2388
				$tag['validate']($tag, $data, $disabled, $params);
2389
2390
			$code = strtr($tag['content'], array('$1' => $data[0], '$2' => $data[1]));
2391
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2392
			$pos += strlen($code) - 1 + 2;
2393
		}
2394
		// A closed tag, with no content or value.
2395
		elseif ($tag['type'] == 'closed')
2396
		{
2397
			$pos2 = strpos($message, ']', $pos);
2398
			$message = substr($message, 0, $pos) . "\n" . $tag['content'] . "\n" . substr($message, $pos2 + 1);
2399
			$pos += strlen($tag['content']) - 1 + 2;
2400
		}
2401
		// This one is sorta ugly... :/.  Unfortunately, it's needed for flash.
2402
		elseif ($tag['type'] == 'unparsed_commas_content')
2403
		{
2404
			$pos2 = strpos($message, ']', $pos1);
2405
			if ($pos2 === false)
2406
				continue;
2407
2408
			$pos3 = stripos($message, '[/' . substr($message, $pos + 1, $tag_strlen) . ']', $pos2);
2409
			if ($pos3 === false)
2410
				continue;
2411
2412
			// We want $1 to be the content, and the rest to be csv.
2413
			$data = explode(',', ',' . substr($message, $pos1, $pos2 - $pos1));
2414
			$data[0] = substr($message, $pos2 + 1, $pos3 - $pos2 - 1);
2415
2416
			if (isset($tag['validate']))
2417
				$tag['validate']($tag, $data, $disabled, $params);
2418
2419
			$code = $tag['content'];
2420 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...
2421
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2422
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos3 + 3 + $tag_strlen);
2423
			$pos += strlen($code) - 1 + 2;
2424
		}
2425
		// This has parsed content, and a csv value which is unparsed.
2426
		elseif ($tag['type'] == 'unparsed_commas')
2427
		{
2428
			$pos2 = strpos($message, ']', $pos1);
2429
			if ($pos2 === false)
2430
				continue;
2431
2432
			$data = explode(',', substr($message, $pos1, $pos2 - $pos1));
2433
2434
			if (isset($tag['validate']))
2435
				$tag['validate']($tag, $data, $disabled, $params);
2436
2437
			// Fix after, for disabled code mainly.
2438 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...
2439
				$tag['after'] = strtr($tag['after'], array('$' . ($k + 1) => trim($d)));
2440
2441
			$open_tags[] = $tag;
2442
2443
			// Replace them out, $1, $2, $3, $4, etc.
2444
			$code = $tag['before'];
2445 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...
2446
				$code = strtr($code, array('$' . ($k + 1) => trim($d)));
2447
			$message = substr($message, 0, $pos) . "\n" . $code . "\n" . substr($message, $pos2 + 1);
2448
			$pos += strlen($code) - 1 + 2;
2449
		}
2450
		// A tag set to a value, parsed or not.
2451
		elseif ($tag['type'] == 'unparsed_equals' || $tag['type'] == 'parsed_equals')
2452
		{
2453
			// The value may be quoted for some tags - check.
2454 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...
2455
			{
2456
				$quoted = substr($message, $pos1, 6) == '&quot;';
2457
				if ($tag['quoted'] != 'optional' && !$quoted)
2458
					continue;
2459
2460
				if ($quoted)
2461
					$pos1 += 6;
2462
			}
2463
			else
2464
				$quoted = false;
2465
2466
			$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...
2467
			if ($pos2 === false)
2468
				continue;
2469
2470
			$data = substr($message, $pos1, $pos2 - $pos1);
2471
2472
			// Validation for my parking, please!
2473
			if (isset($tag['validate']))
2474
				$tag['validate']($tag, $data, $disabled, $params);
2475
2476
			// For parsed content, we must recurse to avoid security problems.
2477
			if ($tag['type'] != 'unparsed_equals')
2478
				$data = parse_bbc($data, !empty($tag['parsed_tags_allowed']) ? false : true, '', !empty($tag['parsed_tags_allowed']) ? $tag['parsed_tags_allowed'] : array());
2479
2480
			$tag['after'] = strtr($tag['after'], array('$1' => $data));
2481
2482
			$open_tags[] = $tag;
2483
2484
			$code = strtr($tag['before'], array('$1' => $data));
2485
			$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...
2486
			$pos += strlen($code) - 1 + 2;
2487
		}
2488
2489
		// If this is block level, eat any breaks after it.
2490 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...
2491
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 5);
2492
2493
		// Are we trimming outside this tag?
2494
		if (!empty($tag['trim']) && $tag['trim'] != 'outside' && preg_match('~(<br>|&nbsp;|\s)*~', substr($message, $pos + 1), $matches) != 0)
2495
			$message = substr($message, 0, $pos + 1) . substr($message, $pos + 1 + strlen($matches[0]));
2496
	}
2497
2498
	// Close any remaining tags.
2499
	while ($tag = array_pop($open_tags))
2500
		$message .= "\n" . $tag['after'] . "\n";
2501
2502
	// Parse the smileys within the parts where it can be done safely.
2503
	if ($smileys === true)
2504
	{
2505
		$message_parts = explode("\n", $message);
2506
		for ($i = 0, $n = count($message_parts); $i < $n; $i += 2)
2507
			parsesmileys($message_parts[$i]);
2508
2509
		$message = implode('', $message_parts);
2510
	}
2511
2512
	// No smileys, just get rid of the markers.
2513
	else
2514
		$message = strtr($message, array("\n" => ''));
2515
2516
	if ($message !== '' && $message[0] === ' ')
2517
		$message = '&nbsp;' . substr($message, 1);
2518
2519
	// Cleanup whitespace.
2520
	$message = strtr($message, array('  ' => ' &nbsp;', "\r" => '', "\n" => '<br>', '<br> ' => '<br>&nbsp;', '&#13;' => "\n"));
2521
2522
	// Allow mods access to what parse_bbc created
2523
	call_integration_hook('integrate_post_parsebbc', array(&$message, &$smileys, &$cache_id, &$parse_tags));
2524
2525
	// Cache the output if it took some time...
2526
	if (isset($cache_key, $cache_t) && array_sum(explode(' ', microtime())) - array_sum(explode(' ', $cache_t)) > 0.05)
2527
		cache_put_data($cache_key, $message, 240);
2528
2529
	// If this was a force parse revert if needed.
2530
	if (!empty($parse_tags))
2531
	{
2532
		if (empty($temp_bbc))
2533
			$bbc_codes = array();
2534
		else
2535
		{
2536
			$bbc_codes = $temp_bbc;
2537
			unset($temp_bbc);
2538
		}
2539
	}
2540
2541
	return $message;
2542
}
2543
2544
/**
2545
 * Helper function for usort(), used in parse_bbc().
2546
 * @param array $a An array containing a tag
2547
 * @param array $b Another array containing a tag
2548
 * @return int A number indicating whether $a is bigger than $b
2549
 */
2550
function sort_bbc_tags($a, $b)
2551
{
2552
	return strcmp($a['tag'], $b['tag']);
2553
}
2554
2555
/**
2556
 * Parse smileys in the passed message.
2557
 *
2558
 * The smiley parsing function which makes pretty faces appear :).
2559
 * If custom smiley sets are turned off by smiley_enable, the default set of smileys will be used.
2560
 * These are specifically not parsed in code tags [url=mailto:[email protected]]
2561
 * Caches the smileys from the database or array in memory.
2562
 * Doesn't return anything, but rather modifies message directly.
2563
 *
2564
 * @param string &$message The message to parse smileys in
2565
 */
2566
function parsesmileys(&$message)
2567
{
2568
	global $modSettings, $txt, $user_info, $context, $smcFunc;
2569
	static $smileyPregSearch = null, $smileyPregReplacements = array();
2570
2571
	// No smiley set at all?!
2572
	if ($user_info['smiley_set'] == 'none' || trim($message) == '')
2573
		return;
2574
2575
	// If smileyPregSearch hasn't been set, do it now.
2576
	if (empty($smileyPregSearch))
2577
	{
2578
		// Use the default smileys if it is disabled. (better for "portability" of smileys.)
2579
		if (empty($modSettings['smiley_enable']))
2580
		{
2581
			$smileysfrom = array('>:D', ':D', '::)', '>:(', ':))', ':)', ';)', ';D', ':(', ':o', '8)', ':P', '???', ':-[', ':-X', ':-*', ':\'(', ':-\\', '^-^', 'O0', 'C:-)', '0:)');
2582
			$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');
2583
			$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'], '', '', '', '');
2584
		}
2585
		else
2586
		{
2587
			// Load the smileys in reverse order by length so they don't get parsed wrong.
2588
			if (($temp = cache_get_data('parsing_smileys', 480)) == null)
2589
			{
2590
				$result = $smcFunc['db_query']('', '
2591
					SELECT code, filename, description
2592
					FROM {db_prefix}smileys
2593
					ORDER BY LENGTH(code) DESC',
2594
					array(
2595
					)
2596
				);
2597
				$smileysfrom = array();
2598
				$smileysto = array();
2599
				$smileysdescs = array();
2600
				while ($row = $smcFunc['db_fetch_assoc']($result))
2601
				{
2602
					$smileysfrom[] = $row['code'];
2603
					$smileysto[] = $smcFunc['htmlspecialchars']($row['filename']);
2604
					$smileysdescs[] = $row['description'];
2605
				}
2606
				$smcFunc['db_free_result']($result);
2607
2608
				cache_put_data('parsing_smileys', array($smileysfrom, $smileysto, $smileysdescs), 480);
2609
			}
2610
			else
2611
				list ($smileysfrom, $smileysto, $smileysdescs) = $temp;
2612
		}
2613
2614
		// The non-breaking-space is a complex thing...
2615
		$non_breaking_space = $context['utf8'] ? '\x{A0}' : '\xA0';
2616
2617
		// This smiley regex makes sure it doesn't parse smileys within code tags (so [url=mailto:[email protected]] doesn't parse the :D smiley)
2618
		$smileyPregReplacements = array();
2619
		$searchParts = array();
2620
		$smileys_path = $smcFunc['htmlspecialchars']($modSettings['smileys_url'] . '/' . $user_info['smiley_set'] . '/');
2621
2622
		for ($i = 0, $n = count($smileysfrom); $i < $n; $i++)
2623
		{
2624
			$specialChars = $smcFunc['htmlspecialchars']($smileysfrom[$i], ENT_QUOTES);
2625
			$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">';
2626
2627
			$smileyPregReplacements[$smileysfrom[$i]] = $smileyCode;
2628
2629
			$searchParts[] = preg_quote($smileysfrom[$i], '~');
2630
			if ($smileysfrom[$i] != $specialChars)
2631
			{
2632
				$smileyPregReplacements[$specialChars] = $smileyCode;
2633
				$searchParts[] = preg_quote($specialChars, '~');
2634
			}
2635
		}
2636
2637
		$smileyPregSearch = '~(?<=[>:\?\.\s' . $non_breaking_space . '[\]()*\\\;]|(?<![a-zA-Z0-9])\(|^)(' . implode('|', $searchParts) . ')(?=[^[:alpha:]0-9]|$)~' . ($context['utf8'] ? 'u' : '');
2638
	}
2639
2640
	// Replace away!
2641
	$message = preg_replace_callback($smileyPregSearch,
2642
		function ($matches) use ($smileyPregReplacements)
2643
		{
2644
			return $smileyPregReplacements[$matches[1]];
2645
		}, $message);
2646
}
2647
2648
/**
2649
 * Highlight any code.
2650
 *
2651
 * Uses PHP's highlight_string() to highlight PHP syntax
2652
 * does special handling to keep the tabs in the code available.
2653
 * used to parse PHP code from inside [code] and [php] tags.
2654
 *
2655
 * @param string $code The code
2656
 * @return string The code with highlighted HTML.
2657
 */
2658
function highlight_php_code($code)
2659
{
2660
	// Remove special characters.
2661
	$code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", '<br>' => "\n", "\t" => 'SMF_TAB();', '&#91;' => '[')));
2662
2663
	$oldlevel = error_reporting(0);
2664
2665
	$buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true));
2666
2667
	error_reporting($oldlevel);
2668
2669
	// Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P.
2670
	$buffer = preg_replace('~SMF_TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer);
2671
2672
	return strtr($buffer, array('\'' => '&#039;', '<code>' => '', '</code>' => ''));
2673
}
2674
2675
/**
2676
 * Make sure the browser doesn't come back and repost the form data.
2677
 * Should be used whenever anything is posted.
2678
 *
2679
 * @param string $setLocation The URL to redirect them to
2680
 * @param bool $refresh Whether to use a meta refresh instead
2681
 * @param bool $permanent Whether to send a 301 Moved Permanently instead of a 302 Moved Temporarily
2682
 */
2683
function redirectexit($setLocation = '', $refresh = false, $permanent = false)
2684
{
2685
	global $scripturl, $context, $modSettings, $db_show_debug, $db_cache;
2686
2687
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
2688
	if (!empty($context['flush_mail']))
2689
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
2690
		AddMailQueue(true);
2691
2692
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
2693
2694
	if ($add)
2695
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
2696
2697
	// Put the session ID in.
2698
	if (defined('SID') && SID != '')
2699
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
2700
	// Keep that debug in their for template debugging!
2701 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...
2702
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
2703
2704
	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'])))
2705
	{
2706
		if (defined('SID') && SID != '')
2707
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~',
2708
				function ($m) use ($scripturl)
2709
				{
2710
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html?' . SID. (isset($m[2]) ? "$m[2]" : "");
2711
				}, $setLocation);
2712 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...
2713
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '/') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~',
2714
				function ($m) use ($scripturl)
2715
				{
2716
					return $scripturl . '/' . strtr("$m[1]", '&;=', '//,') . '.html' . (isset($m[2]) ? "$m[2]" : "");
2717
				}, $setLocation);
2718
	}
2719
2720
	// Maybe integrations want to change where we are heading?
2721
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh, &$permanent));
2722
2723
	// Set the header.
2724
	header('Location: ' . str_replace(' ', '%20', $setLocation), true, $permanent ? 301 : 302);
2725
2726
	// Debugging.
2727
	if (isset($db_show_debug) && $db_show_debug === true)
2728
		$_SESSION['debug_redirect'] = $db_cache;
2729
2730
	obExit(false);
2731
}
2732
2733
/**
2734
 * Ends execution.  Takes care of template loading and remembering the previous URL.
2735
 * @param bool $header Whether to do the header
2736
 * @param bool $do_footer Whether to do the footer
2737
 * @param bool $from_index Whether we're coming from the board index
2738
 * @param bool $from_fatal_error Whether we're coming from a fatal error
2739
 */
2740
function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false)
2741
{
2742
	global $context, $settings, $modSettings, $txt, $smcFunc;
2743
	static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false;
2744
2745
	// Attempt to prevent a recursive loop.
2746
	++$level;
2747
	if ($level > 1 && !$from_fatal_error && !$has_fatal_error)
2748
		exit;
2749
	if ($from_fatal_error)
2750
		$has_fatal_error = true;
2751
2752
	// Clear out the stat cache.
2753
	trackStats();
2754
2755
	// If we have mail to send, send it.
2756
	if (!empty($context['flush_mail']))
2757
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
2758
		AddMailQueue(true);
2759
2760
	$do_header = $header === null ? !$header_done : $header;
2761
	if ($do_footer === null)
2762
		$do_footer = $do_header;
2763
2764
	// Has the template/header been done yet?
2765
	if ($do_header)
2766
	{
2767
		// Was the page title set last minute? Also update the HTML safe one.
2768
		if (!empty($context['page_title']) && empty($context['page_title_html_safe']))
2769
			$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
2770
2771
		// Start up the session URL fixer.
2772
		ob_start('ob_sessrewrite');
2773
2774
		if (!empty($settings['output_buffers']) && is_string($settings['output_buffers']))
2775
			$buffers = explode(',', $settings['output_buffers']);
2776
		elseif (!empty($settings['output_buffers']))
2777
			$buffers = $settings['output_buffers'];
2778
		else
2779
			$buffers = array();
2780
2781
		if (isset($modSettings['integrate_buffer']))
2782
			$buffers = array_merge(explode(',', $modSettings['integrate_buffer']), $buffers);
2783
2784
		if (!empty($buffers))
2785
			foreach ($buffers as $function)
2786
			{
2787
				$call = call_helper($function, true);
2788
2789
				// Is it valid?
2790
				if (!empty($call))
2791
					ob_start($call);
2792
			}
2793
2794
		// Display the screen in the logical order.
2795
		template_header();
2796
		$header_done = true;
2797
	}
2798
	if ($do_footer)
2799
	{
2800
		loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main');
2801
2802
		// Anything special to put out?
2803
		if (!empty($context['insert_after_template']) && !isset($_REQUEST['xml']))
2804
			echo $context['insert_after_template'];
2805
2806
		// Just so we don't get caught in an endless loop of errors from the footer...
2807
		if (!$footer_done)
2808
		{
2809
			$footer_done = true;
2810
			template_footer();
2811
2812
			// (since this is just debugging... it's okay that it's after </html>.)
2813
			if (!isset($_REQUEST['xml']))
2814
				displayDebug();
2815
		}
2816
	}
2817
2818
	// Remember this URL in case someone doesn't like sending HTTP_REFERER.
2819
	if (strpos($_SERVER['REQUEST_URL'], 'action=dlattach') === false && strpos($_SERVER['REQUEST_URL'], 'action=viewsmfile') === false)
2820
		$_SESSION['old_url'] = $_SERVER['REQUEST_URL'];
2821
2822
	// For session check verification.... don't switch browsers...
2823
	$_SESSION['USER_AGENT'] = empty($_SERVER['HTTP_USER_AGENT']) ? '' : $_SERVER['HTTP_USER_AGENT'];
2824
2825
	// Hand off the output to the portal, etc. we're integrated with.
2826
	call_integration_hook('integrate_exit', array($do_footer));
2827
2828
	// Don't exit if we're coming from index.php; that will pass through normally.
2829
	if (!$from_index)
2830
		exit;
2831
}
2832
2833
/**
2834
 * Get the size of a specified image with better error handling.
2835
 * @todo see if it's better in Subs-Graphics, but one step at the time.
2836
 * Uses getimagesize() to determine the size of a file.
2837
 * Attempts to connect to the server first so it won't time out.
2838
 *
2839
 * @param string $url The URL of the image
2840
 * @return array|false The image size as array (width, height), or false on failure
2841
 */
2842
function url_image_size($url)
2843
{
2844
	global $sourcedir;
2845
2846
	// Make sure it is a proper URL.
2847
	$url = str_replace(' ', '%20', $url);
2848
2849
	// Can we pull this from the cache... please please?
2850
	if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null)
2851
		return $temp;
2852
	$t = microtime();
2853
2854
	// Get the host to pester...
2855
	preg_match('~^\w+://(.+?)/(.*)$~', $url, $match);
2856
2857
	// Can't figure it out, just try the image size.
2858
	if ($url == '' || $url == 'http://' || $url == 'https://')
2859
	{
2860
		return false;
2861
	}
2862
	elseif (!isset($match[1]))
2863
	{
2864
		$size = @getimagesize($url);
2865
	}
2866
	else
2867
	{
2868
		// Try to connect to the server... give it half a second.
2869
		$temp = 0;
2870
		$fp = @fsockopen($match[1], 80, $temp, $temp, 0.5);
2871
2872
		// Successful?  Continue...
2873
		if ($fp != false)
2874
		{
2875
			// Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.)
2876
			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");
2877
2878
			// Read in the HTTP/1.1 or whatever.
2879
			$test = substr(fgets($fp, 11), -1);
2880
			fclose($fp);
2881
2882
			// See if it returned a 404/403 or something.
2883
			if ($test < 4)
2884
			{
2885
				$size = @getimagesize($url);
2886
2887
				// This probably means allow_url_fopen is off, let's try GD.
2888
				if ($size === false && function_exists('imagecreatefromstring'))
2889
				{
2890
					include_once($sourcedir . '/Subs-Package.php');
2891
2892
					// It's going to hate us for doing this, but another request...
2893
					$image = @imagecreatefromstring(fetch_web_data($url));
2894
					if ($image !== false)
2895
					{
2896
						$size = array(imagesx($image), imagesy($image));
2897
						imagedestroy($image);
2898
					}
2899
				}
2900
			}
2901
		}
2902
	}
2903
2904
	// If we didn't get it, we failed.
2905
	if (!isset($size))
2906
		$size = false;
2907
2908
	// If this took a long time, we may never have to do it again, but then again we might...
2909 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...
2910
		cache_put_data('url_image_size-' . md5($url), $size, 240);
2911
2912
	// Didn't work.
2913
	return $size;
2914
}
2915
2916
/**
2917
 * Sets up the basic theme context stuff.
2918
 * @param bool $forceload Whether to load the theme even if it's already loaded
2919
 */
2920
function setupThemeContext($forceload = false)
2921
{
2922
	global $modSettings, $user_info, $scripturl, $context, $settings, $options, $txt, $maintenance;
2923
	global $smcFunc;
2924
	static $loaded = false;
2925
2926
	// Under SSI this function can be called more then once.  That can cause some problems.
2927
	//   So only run the function once unless we are forced to run it again.
2928
	if ($loaded && !$forceload)
2929
		return;
2930
2931
	$loaded = true;
2932
2933
	$context['in_maintenance'] = !empty($maintenance);
2934
	$context['current_time'] = timeformat(time(), false);
2935
	$context['current_action'] = isset($_GET['action']) ? $smcFunc['htmlspecialchars']($_GET['action']) : '';
2936
2937
	// Get some news...
2938
	$context['news_lines'] = array_filter(explode("\n", str_replace("\r", '', trim(addslashes($modSettings['news'])))));
2939
	for ($i = 0, $n = count($context['news_lines']); $i < $n; $i++)
2940
	{
2941
		if (trim($context['news_lines'][$i]) == '')
2942
			continue;
2943
2944
		// Clean it up for presentation ;).
2945
		$context['news_lines'][$i] = parse_bbc(stripslashes(trim($context['news_lines'][$i])), true, 'news' . $i);
2946
	}
2947
	if (!empty($context['news_lines']))
2948
		$context['random_news_line'] = $context['news_lines'][mt_rand(0, count($context['news_lines']) - 1)];
2949
2950
	if (!$user_info['is_guest'])
2951
	{
2952
		$context['user']['messages'] = &$user_info['messages'];
2953
		$context['user']['unread_messages'] = &$user_info['unread_messages'];
2954
		$context['user']['alerts'] = &$user_info['alerts'];
2955
2956
		// Personal message popup...
2957
		if ($user_info['unread_messages'] > (isset($_SESSION['unread_messages']) ? $_SESSION['unread_messages'] : 0))
2958
			$context['user']['popup_messages'] = true;
2959
		else
2960
			$context['user']['popup_messages'] = false;
2961
		$_SESSION['unread_messages'] = $user_info['unread_messages'];
2962
2963
		if (allowedTo('moderate_forum'))
2964
			$context['unapproved_members'] = (!empty($modSettings['registration_method']) && ($modSettings['registration_method'] == 2 || (!empty($modSettings['coppaType']) && $modSettings['coppaType'] == 2))) || !empty($modSettings['approveAccountDeletion']) ? $modSettings['unapprovedMembers'] : 0;
2965
2966
		$context['user']['avatar'] = array();
2967
2968
		// Check for gravatar first since we might be forcing them...
2969
		if (($modSettings['gravatarEnabled'] && substr($user_info['avatar']['url'], 0, 11) == 'gravatar://') || !empty($modSettings['gravatarOverride']))
2970
		{
2971
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($user_info['avatar']['url'], 'gravatar://') && strlen($user_info['avatar']['url']) > 11)
2972
				$context['user']['avatar']['href'] = get_gravatar_url($smcFunc['substr']($user_info['avatar']['url'], 11));
2973
			else
2974
				$context['user']['avatar']['href'] = get_gravatar_url($user_info['email']);
2975
		}
2976
		// Uploaded?
2977
		elseif ($user_info['avatar']['url'] == '' && !empty($user_info['avatar']['id_attach']))
2978
			$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';
2979
		// Full URL?
2980
		elseif (strpos($user_info['avatar']['url'], 'http://') === 0 || strpos($user_info['avatar']['url'], 'https://') === 0)
2981
			$context['user']['avatar']['href'] = $user_info['avatar']['url'];
2982
		// Otherwise we assume it's server stored.
2983
		elseif ($user_info['avatar']['url'] != '')
2984
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/' . $smcFunc['htmlspecialchars']($user_info['avatar']['url']);
2985
		// No avatar at all? Fine, we have a big fat default avatar ;)
2986
		else
2987
			$context['user']['avatar']['href'] = $modSettings['avatar_url'] . '/default.png';
2988
2989
		if (!empty($context['user']['avatar']))
2990
			$context['user']['avatar']['image'] = '<img src="' . $context['user']['avatar']['href'] . '" alt="" class="avatar">';
2991
2992
		// Figure out how long they've been logged in.
2993
		$context['user']['total_time_logged_in'] = array(
2994
			'days' => floor($user_info['total_time_logged_in'] / 86400),
2995
			'hours' => floor(($user_info['total_time_logged_in'] % 86400) / 3600),
2996
			'minutes' => floor(($user_info['total_time_logged_in'] % 3600) / 60)
2997
		);
2998
	}
2999
	else
3000
	{
3001
		$context['user']['messages'] = 0;
3002
		$context['user']['unread_messages'] = 0;
3003
		$context['user']['avatar'] = array();
3004
		$context['user']['total_time_logged_in'] = array('days' => 0, 'hours' => 0, 'minutes' => 0);
3005
		$context['user']['popup_messages'] = false;
3006
3007
		if (!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 1)
3008
			$txt['welcome_guest'] .= $txt['welcome_guest_activate'];
3009
3010
		// If we've upgraded recently, go easy on the passwords.
3011
		if (!empty($modSettings['disableHashTime']) && ($modSettings['disableHashTime'] == 1 || time() < $modSettings['disableHashTime']))
3012
			$context['disable_login_hashing'] = true;
3013
	}
3014
3015
	// Setup the main menu items.
3016
	setupMenuContext();
3017
3018
	// This is here because old index templates might still use it.
3019
	$context['show_news'] = !empty($settings['enable_news']);
3020
3021
	// This is done to allow theme authors to customize it as they want.
3022
	$context['show_pm_popup'] = $context['user']['popup_messages'] && !empty($options['popup_messages']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != 'pm');
3023
3024
	// 2.1+: Add the PM popup here instead. Theme authors can still override it simply by editing/removing the 'fPmPopup' in the array.
3025
	if ($context['show_pm_popup'])
3026
		addInlineJavascript('
3027
		jQuery(document).ready(function($) {
3028
			new smc_Popup({
3029
				heading: ' . JavaScriptEscape($txt['show_personal_messages_heading']) . ',
3030
				content: ' . JavaScriptEscape(sprintf($txt['show_personal_messages'], $context['user']['unread_messages'], $scripturl . '?action=pm')) . ',
3031
				icon_class: \'generic_icons mail_new\'
3032
			});
3033
		});');
3034
3035
	// Add a generic "Are you sure?" confirmation message.
3036
	addInlineJavascript('
3037
	var smf_you_sure =' . JavaScriptEscape($txt['quickmod_confirm']) .';');
3038
3039
	// Now add the capping code for avatars.
3040
	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')
3041
		addInlineCss('
3042
img.avatar { max-width: ' . $modSettings['avatar_max_width_external'] . 'px; max-height: ' . $modSettings['avatar_max_height_external'] . 'px; }');
3043
3044
	// This looks weird, but it's because BoardIndex.php references the variable.
3045
	$context['common_stats']['latest_member'] = array(
3046
		'id' => $modSettings['latestMember'],
3047
		'name' => $modSettings['latestRealName'],
3048
		'href' => $scripturl . '?action=profile;u=' . $modSettings['latestMember'],
3049
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $modSettings['latestMember'] . '">' . $modSettings['latestRealName'] . '</a>',
3050
	);
3051
	$context['common_stats'] = array(
3052
		'total_posts' => comma_format($modSettings['totalMessages']),
3053
		'total_topics' => comma_format($modSettings['totalTopics']),
3054
		'total_members' => comma_format($modSettings['totalMembers']),
3055
		'latest_member' => $context['common_stats']['latest_member'],
3056
	);
3057
	$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']);
3058
3059
	if (empty($settings['theme_version']))
3060
		addJavascriptVar('smf_scripturl', $scripturl);
3061
3062
	if (!isset($context['page_title']))
3063
		$context['page_title'] = '';
3064
3065
	// Set some specific vars.
3066
	$context['page_title_html_safe'] = $smcFunc['htmlspecialchars'](un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
3067
	$context['meta_keywords'] = !empty($modSettings['meta_keywords']) ? $smcFunc['htmlspecialchars']($modSettings['meta_keywords']) : '';
3068
3069
	// Content related meta tags, including Open Graph
3070
	$context['meta_tags'][] = array('property' => 'og:site_name', 'content' => $context['forum_name']);
3071
	$context['meta_tags'][] = array('property' => 'og:title', 'content' => $context['page_title_html_safe']);
3072
3073
	if (!empty($context['meta_keywords']))
3074
		$context['meta_tags'][] = array('name' => 'keywords', 'content' => $context['meta_keywords']);
3075
3076
	if (!empty($context['canonical_url']))
3077
		$context['meta_tags'][] = array('property' => 'og:url', 'content' => $context['canonical_url']);
3078
3079
	if (!empty($settings['og_image']))
3080
		$context['meta_tags'][] = array('property' => 'og:image', 'content' => $settings['og_image']);
3081
3082
	if (!empty($context['meta_description']))
3083
	{
3084
		$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['meta_description']);
3085
		$context['meta_tags'][] = array('name' => 'description', 'content' => $context['meta_description']);
3086
	}
3087
	else
3088
	{
3089
		$context['meta_tags'][] = array('property' => 'og:description', 'content' => $context['page_title_html_safe']);
3090
		$context['meta_tags'][] = array('name' => 'description', 'content' => $context['page_title_html_safe']);
3091
	}
3092
3093
	call_integration_hook('integrate_theme_context');
3094
}
3095
3096
/**
3097
 * Helper function to set the system memory to a needed value
3098
 * - If the needed memory is greater than current, will attempt to get more
3099
 * - if in_use is set to true, will also try to take the current memory usage in to account
3100
 *
3101
 * @param string $needed The amount of memory to request, if needed, like 256M
3102
 * @param bool $in_use Set to true to account for current memory usage of the script
3103
 * @return boolean True if we have at least the needed memory
3104
 */
3105
function setMemoryLimit($needed, $in_use = false)
3106
{
3107
	// everything in bytes
3108
	$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...
3109
	$memory_current = memoryReturnBytes(ini_get('memory_limit'));
3110
	$memory_needed = memoryReturnBytes($needed);
3111
3112
	// should we account for how much is currently being used?
3113
	if ($in_use)
3114
		$memory_needed += function_exists('memory_get_usage') ? memory_get_usage() : (2 * 1048576);
3115
3116
	// if more is needed, request it
3117
	if ($memory_current < $memory_needed)
3118
	{
3119
		@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...
3120
		$memory_current = memoryReturnBytes(ini_get('memory_limit'));
3121
	}
3122
3123
	$memory_current = max($memory_current, memoryReturnBytes(get_cfg_var('memory_limit')));
3124
3125
	// return success or not
3126
	return (bool) ($memory_current >= $memory_needed);
3127
}
3128
3129
/**
3130
 * Helper function to convert memory string settings to bytes
3131
 *
3132
 * @param string $val The byte string, like 256M or 1G
3133
 * @return integer The string converted to a proper integer in bytes
3134
 */
3135
function memoryReturnBytes($val)
3136
{
3137
	if (is_integer($val))
3138
		return $val;
3139
3140
	// Separate the number from the designator
3141
	$val = trim($val);
3142
	$num = intval(substr($val, 0, strlen($val) - 1));
3143
	$last = strtolower(substr($val, -1));
3144
3145
	// convert to bytes
3146
	switch ($last)
3147
	{
3148
		case 'g':
3149
			$num *= 1024;
3150
		case 'm':
3151
			$num *= 1024;
3152
		case 'k':
3153
			$num *= 1024;
3154
	}
3155
	return $num;
3156
}
3157
3158
/**
3159
 * The header template
3160
 */
3161
function template_header()
3162
{
3163
	global $txt, $modSettings, $context, $user_info, $boarddir, $cachedir;
3164
3165
	setupThemeContext();
3166
3167
	// Print stuff to prevent caching of pages (except on attachment errors, etc.)
3168
	if (empty($context['no_last_modified']))
3169
	{
3170
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
3171
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3172
3173
		// Are we debugging the template/html content?
3174
		if (!isset($_REQUEST['xml']) && isset($_GET['debug']) && !isBrowser('ie'))
3175
			header('Content-Type: application/xhtml+xml');
3176 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...
3177
			header('Content-Type: text/html; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3178
	}
3179
3180
	header('Content-Type: text/' . (isset($_REQUEST['xml']) ? 'xml' : 'html') . '; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3181
3182
	// We need to splice this in after the body layer, or after the main layer for older stuff.
3183
	if ($context['in_maintenance'] && $context['user']['is_admin'])
3184
	{
3185
		$position = array_search('body', $context['template_layers']);
3186
		if ($position === false)
3187
			$position = array_search('main', $context['template_layers']);
3188
3189
		if ($position !== false)
3190
		{
3191
			$before = array_slice($context['template_layers'], 0, $position + 1);
3192
			$after = array_slice($context['template_layers'], $position + 1);
3193
			$context['template_layers'] = array_merge($before, array('maint_warning'), $after);
3194
		}
3195
	}
3196
3197
	$checked_securityFiles = false;
3198
	$showed_banned = false;
3199
	foreach ($context['template_layers'] as $layer)
3200
	{
3201
		loadSubTemplate($layer . '_above', true);
3202
3203
		// May seem contrived, but this is done in case the body and main layer aren't there...
3204
		if (in_array($layer, array('body', 'main')) && allowedTo('admin_forum') && !$user_info['is_guest'] && !$checked_securityFiles)
3205
		{
3206
			$checked_securityFiles = true;
3207
3208
			$securityFiles = array('install.php', 'upgrade.php', 'convert.php', 'repair_paths.php', 'repair_settings.php', 'Settings.php~', 'Settings_bak.php~');
3209
3210
			// Add your own files.
3211
			call_integration_hook('integrate_security_files', array(&$securityFiles));
3212
3213
			foreach ($securityFiles as $i => $securityFile)
3214
			{
3215
				if (!file_exists($boarddir . '/' . $securityFile))
3216
					unset($securityFiles[$i]);
3217
			}
3218
3219
			// We are already checking so many files...just few more doesn't make any difference! :P
3220 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...
3221
				$path = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
3222
3223
			else
3224
			{
3225
				$path = $modSettings['attachmentUploadDir'];
3226
				$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...
3227
			}
3228
			secureDirectory($path, true);
3229
			secureDirectory($cachedir);
3230
3231
			// If agreement is enabled, at least the english version shall exists
3232
			if ($modSettings['requireAgreement'])
3233
				$agreement = !file_exists($boarddir . '/agreement.txt');
3234
3235
			if (!empty($securityFiles) || (!empty($modSettings['cache_enable']) && !is_writable($cachedir)) || !empty($agreement))
3236
			{
3237
				echo '
3238
		<div class="errorbox">
3239
			<p class="alert">!!</p>
3240
			<h3>', empty($securityFiles) ? $txt['generic_warning'] : $txt['security_risk'], '</h3>
3241
			<p>';
3242
3243
				foreach ($securityFiles as $securityFile)
3244
				{
3245
					echo '
3246
				', $txt['not_removed'], '<strong>', $securityFile, '</strong>!<br>';
3247
3248
					if ($securityFile == 'Settings.php~' || $securityFile == 'Settings_bak.php~')
3249
						echo '
3250
				', sprintf($txt['not_removed_extra'], $securityFile, substr($securityFile, 0, -1)), '<br>';
3251
				}
3252
3253
				if (!empty($modSettings['cache_enable']) && !is_writable($cachedir))
3254
					echo '
3255
				<strong>', $txt['cache_writable'], '</strong><br>';
3256
3257
				if (!empty($agreement))
3258
					echo '
3259
				<strong>', $txt['agreement_missing'], '</strong><br>';
3260
3261
				echo '
3262
			</p>
3263
		</div>';
3264
			}
3265
		}
3266
		// If the user is banned from posting inform them of it.
3267
		elseif (in_array($layer, array('main', 'body')) && isset($_SESSION['ban']['cannot_post']) && !$showed_banned)
3268
		{
3269
			$showed_banned = true;
3270
			echo '
3271
				<div class="windowbg alert" style="margin: 2ex; padding: 2ex; border: 2px dashed red;">
3272
					', sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']);
3273
3274
			if (!empty($_SESSION['ban']['cannot_post']['reason']))
3275
				echo '
3276
					<div style="padding-left: 4ex; padding-top: 1ex;">', $_SESSION['ban']['cannot_post']['reason'], '</div>';
3277
3278
			if (!empty($_SESSION['ban']['expire_time']))
3279
				echo '
3280
					<div>', sprintf($txt['your_ban_expires'], timeformat($_SESSION['ban']['expire_time'], false)), '</div>';
3281
			else
3282
				echo '
3283
					<div>', $txt['your_ban_expires_never'], '</div>';
3284
3285
			echo '
3286
				</div>';
3287
		}
3288
	}
3289
}
3290
3291
/**
3292
 * Show the copyright.
3293
 */
3294
function theme_copyright()
3295
{
3296
	global $forum_copyright, $software_year, $forum_version;
3297
3298
	// Don't display copyright for things like SSI.
3299
	if (!isset($forum_version) || !isset($software_year))
3300
		return;
3301
3302
	// Put in the version...
3303
	printf($forum_copyright, $forum_version, $software_year);
3304
}
3305
3306
/**
3307
 * The template footer
3308
 */
3309
function template_footer()
3310
{
3311
	global $context, $modSettings, $time_start, $db_count;
3312
3313
	// Show the load time?  (only makes sense for the footer.)
3314
	$context['show_load_time'] = !empty($modSettings['timeLoadPageEnable']);
3315
	$context['load_time'] = comma_format(round(array_sum(explode(' ', microtime())) - array_sum(explode(' ', $time_start)), 3));
3316
	$context['load_queries'] = $db_count;
3317
3318
	foreach (array_reverse($context['template_layers']) as $layer)
3319
		loadSubTemplate($layer . '_below', true);
3320
}
3321
3322
/**
3323
 * Output the Javascript files
3324
 * 	- tabbing in this function is to make the HTML source look good proper
3325
 *  - if defered is set function will output all JS (source & inline) set to load at page end
3326
 *
3327
 * @param bool $do_deferred If true will only output the deferred JS (the stuff that goes right before the closing body tag)
3328
 */
3329
function template_javascript($do_deferred = false)
3330
{
3331
	global $context, $modSettings, $settings;
3332
3333
	// Use this hook to minify/optimize Javascript files and vars
3334
	call_integration_hook('integrate_pre_javascript_output', array(&$do_deferred));
3335
3336
	$toMinify = array();
3337
	$toMinifyDefer = array();
3338
3339
	// Ouput the declared Javascript variables.
3340
	if (!empty($context['javascript_vars']) && !$do_deferred)
3341
	{
3342
		echo '
3343
	<script>';
3344
3345
		foreach ($context['javascript_vars'] as $key => $value)
3346
		{
3347
			if (empty($value))
3348
			{
3349
				echo '
3350
		var ', $key, ';';
3351
			}
3352
			else
3353
			{
3354
				echo '
3355
		var ', $key, ' = ', $value, ';';
3356
			}
3357
		}
3358
3359
		echo '
3360
	</script>';
3361
	}
3362
3363
	// While we have JavaScript files to place in the template.
3364
	foreach ($context['javascript_files'] as $id => $js_file)
3365
	{
3366
		// Last minute call! allow theme authors to disable single files.
3367
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3368
			continue;
3369
3370
		// By default all files don't get minimized unless the file explicitly says so!
3371
		if (!empty($js_file['options']['minimize']) && !empty($modSettings['minimize_files']))
3372
		{
3373
			if ($do_deferred && !empty($js_file['options']['defer']))
3374
				$toMinifyDefer[] = $js_file;
3375
3376
			elseif (!$do_deferred && empty($js_file['options']['defer']))
3377
				$toMinify[] = $js_file;
3378
3379
			// Grab a random seed.
3380
			if (!isset($minSeed))
3381
				$minSeed = $js_file['options']['seed'];
3382
		}
3383
3384
		elseif ((!$do_deferred && empty($js_file['options']['defer'])) || ($do_deferred && !empty($js_file['options']['defer'])))
3385
			echo '
3386
	<script src="', $js_file['fileUrl'], '"', !empty($js_file['options']['async']) ? ' async="async"' : '', '></script>';
3387
	}
3388
3389
	if ((!$do_deferred && !empty($toMinify)) || ($do_deferred && !empty($toMinifyDefer)))
3390
	{
3391
		$result = custMinify(($do_deferred ? $toMinifyDefer : $toMinify), 'js', $do_deferred);
3392
3393
		// Minify process couldn't work, print each individual files.
3394
		if (!empty($result) && is_array($result))
3395
			foreach ($result as $minFailedFile)
3396
				echo '
3397
	<script src="', $minFailedFile['fileUrl'], '"', !empty($minFailedFile['options']['async']) ? ' async="async"' : '', '></script>';
3398
3399
		else
3400
			echo '
3401
	<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...
3402
	}
3403
3404
	// Inline JavaScript - Actually useful some times!
3405
	if (!empty($context['javascript_inline']))
3406
	{
3407 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...
3408
		{
3409
			echo '
3410
<script>';
3411
3412
			foreach ($context['javascript_inline']['defer'] as $js_code)
3413
				echo $js_code;
3414
3415
			echo '
3416
</script>';
3417
		}
3418
3419 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...
3420
		{
3421
			echo '
3422
	<script>';
3423
3424
			foreach ($context['javascript_inline']['standard'] as $js_code)
3425
				echo $js_code;
3426
3427
			echo '
3428
	</script>';
3429
		}
3430
	}
3431
}
3432
3433
/**
3434
 * Output the CSS files
3435
 *
3436
 */
3437
function template_css()
3438
{
3439
	global $context, $db_show_debug, $boardurl, $settings, $modSettings;
3440
3441
	// Use this hook to minify/optimize CSS files
3442
	call_integration_hook('integrate_pre_css_output');
3443
3444
	$toMinify = array();
3445
	$normal = array();
3446
3447
	foreach ($context['css_files'] as $id => $file)
3448
	{
3449
		// Last minute call! allow theme authors to disable single files.
3450
		if (!empty($settings['disable_files']) && in_array($id, $settings['disable_files']))
3451
			continue;
3452
3453
		// By default all files don't get minimized unless the file explicitly says so!
3454
		if (!empty($file['options']['minimize']) && !empty($modSettings['minimize_files']))
3455
		{
3456
			$toMinify[] = $file;
3457
3458
			// Grab a random seed.
3459
			if (!isset($minSeed))
3460
				$minSeed = $file['options']['seed'];
3461
		}
3462
3463
		else
3464
			$normal[] = $file['fileUrl'];
3465
	}
3466
3467
	if (!empty($toMinify))
3468
	{
3469
		$result = custMinify($toMinify, 'css');
3470
3471
		// Minify process couldn't work, print each individual files.
3472
		if (!empty($result) && is_array($result))
3473
			foreach ($result as $minFailedFile)
3474
				echo '
3475
	<link rel="stylesheet" href="', $minFailedFile['fileUrl'], '">';
3476
3477
		else
3478
			echo '
3479
	<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...
3480
	}
3481
3482
	// Print the rest after the minified files.
3483
	if (!empty($normal))
3484
		foreach ($normal as $nf)
3485
			echo '
3486
	<link rel="stylesheet" href="', $nf ,'">';
3487
3488
	if ($db_show_debug === true)
3489
	{
3490
		// Try to keep only what's useful.
3491
		$repl = array($boardurl . '/Themes/' => '', $boardurl . '/' => '');
3492
		foreach ($context['css_files'] as $file)
3493
			$context['debug']['sheets'][] = strtr($file['fileName'], $repl);
3494
	}
3495
3496
	if (!empty($context['css_header']))
3497
	{
3498
		echo '
3499
	<style>';
3500
3501
		foreach ($context['css_header'] as $css)
3502
			echo $css .'
3503
	';
3504
3505
		echo'
3506
	</style>';
3507
	}
3508
}
3509
3510
/**
3511
 * Get an array of previously defined files and adds them to our main minified file.
3512
 * Sets a one day cache to avoid re-creating a file on every request.
3513
 *
3514
 * @param array $data The files to minify.
3515
 * @param string $type either css or js.
3516
 * @param bool $do_deferred use for type js to indicate if the minified file will be deferred, IE, put at the closing </body> tag.
3517
 * @return bool|array If an array the minify process failed and the data is returned intact.
3518
 */
3519
function custMinify($data, $type, $do_deferred = false)
3520
{
3521
	global $sourcedir, $smcFunc, $settings, $txt, $context;
3522
3523
	$types = array('css', 'js');
3524
	$type = !empty($type) && in_array($type, $types) ? $type : false;
3525
	$data = !empty($data) ? $data : false;
3526
	$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...
3527
3528
	if (empty($type) || empty($data))
3529
		return false;
3530
3531
	// Did we already did this?
3532
	$toCache = cache_get_data('minimized_'. $settings['theme_id'] .'_'. $type, 86400);
3533
3534
	// Already done?
3535
	if (!empty($toCache))
3536
		return true;
3537
3538
	// Yep, need a bunch of files.
3539
	require_once($sourcedir . '/minify/src/Minify.php');
3540
	require_once($sourcedir . '/minify/src/'. strtoupper($type) .'.php');
3541
	require_once($sourcedir . '/minify/src/Exception.php');
3542
	require_once($sourcedir . '/minify/src/Converter.php');
3543
3544
	// No namespaces, sorry!
3545
	$classType = 'MatthiasMullie\\Minify\\'. strtoupper($type);
3546
3547
	// Temp path.
3548
	$cTempPath = $settings['theme_dir'] .'/'. ($type == 'css' ? 'css' : 'scripts') .'/';
3549
3550
	// What kind of file are we going to create?
3551
	$toCreate = $cTempPath .'minified'. ($do_deferred ? '_deferred' : '') .'.'. $type;
3552
3553
	// File has to exists, if it isn't try to create it.
3554
	if ((!file_exists($toCreate) && @fopen($toCreate, 'w') === false) || !smf_chmod($toCreate))
3555
	{
3556
		loadLanguage('Errors');
3557
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3558
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3559
3560
		// The process failed so roll back to print each individual file.
3561
		return $data;
3562
	}
3563
3564
	$minifier = new $classType();
3565
3566
	foreach ($data as $file)
3567
	{
3568
		$tempFile = str_replace($file['options']['seed'], '', $file['filePath']);
3569
		$toAdd = file_exists($tempFile) ? $tempFile : false;
3570
3571
		// The file couldn't be located so it won't be added, log this error.
3572
		if (empty($toAdd))
3573
		{
3574
			loadLanguage('Errors');
3575
			log_error(sprintf($txt['file_minimize_fail'], $file['fileName']), 'general');
3576
			continue;
3577
		}
3578
3579
		// Add this file to the list.
3580
		$minifier->add($toAdd);
3581
	}
3582
3583
	// Create the file.
3584
	$minifier->minify($toCreate);
3585
	unset($minifier);
3586
	clearstatcache();
3587
3588
	// Minify process failed.
3589
	if (!filesize($toCreate))
3590
	{
3591
		loadLanguage('Errors');
3592
		log_error(sprintf($txt['file_not_created'], $toCreate), 'general');
3593
		cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, null);
3594
3595
		// The process failed so roll back to print each individual file.
3596
		return $data;
3597
	}
3598
3599
	// And create a long lived cache entry.
3600
	cache_put_data('minimized_'. $settings['theme_id'] .'_'. $type, $toCreate, 86400);
3601
3602
	return true;
3603
}
3604
3605
/**
3606
 * Get an attachment's encrypted filename. If $new is true, won't check for file existence.
3607
 * @todo this currently returns the hash if new, and the full filename otherwise.
3608
 * Something messy like that.
3609
 * @todo and of course everything relies on this behavior and work around it. :P.
3610
 * Converters included.
3611
 *
3612
 * @param string $filename The name of the file
3613
 * @param int $attachment_id The ID of the attachment
3614
 * @param string $dir Which directory it should be in (null to use current one)
3615
 * @param bool $new Whether this is a new attachment
3616
 * @param string $file_hash The file hash
3617
 * @return string The path to the file
3618
 */
3619
function getAttachmentFilename($filename, $attachment_id, $dir = null, $new = false, $file_hash = '')
3620
{
3621
	global $modSettings, $smcFunc;
3622
3623
	// Just make up a nice hash...
3624
	if ($new)
3625
		return sha1(md5($filename . time()) . mt_rand());
3626
3627
	// Grab the file hash if it wasn't added.
3628
	// Left this for legacy.
3629
	if ($file_hash === '')
3630
	{
3631
		$request = $smcFunc['db_query']('', '
3632
			SELECT file_hash
3633
			FROM {db_prefix}attachments
3634
			WHERE id_attach = {int:id_attach}',
3635
			array(
3636
				'id_attach' => $attachment_id,
3637
			));
3638
3639
		if ($smcFunc['db_num_rows']($request) === 0)
3640
			return false;
3641
3642
		list ($file_hash) = $smcFunc['db_fetch_row']($request);
3643
		$smcFunc['db_free_result']($request);
3644
	}
3645
3646
	// Still no hash? mmm...
3647
	if (empty($file_hash))
3648
		$file_hash = sha1(md5($filename . time()) . mt_rand());
3649
3650
	// Are we using multiple directories?
3651 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...
3652
		$path = $modSettings['attachmentUploadDir'][$dir];
3653
3654
	else
3655
		$path = $modSettings['attachmentUploadDir'];
3656
3657
	return $path . '/' . $attachment_id . '_' . $file_hash .'.dat';
3658
}
3659
3660
/**
3661
 * Convert a single IP to a ranged IP.
3662
 * internal function used to convert a user-readable format to a format suitable for the database.
3663
 *
3664
 * @param string $fullip The full IP
3665
 * @return array An array of IP parts
3666
 */
3667
function ip2range($fullip)
3668
{
3669
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
3670
	if ($fullip == 'unknown')
3671
		$fullip = '255.255.255.255';
3672
3673
	$ip_parts = explode('-', $fullip);
3674
	$ip_array = array();
3675
3676
	// if ip 22.12.31.21
3677
	if (count($ip_parts) == 1 && isValidIP($fullip))
3678
	{
3679
		$ip_array['low'] = $fullip;
3680
		$ip_array['high'] = $fullip;
3681
		return $ip_array;
3682
	} // if ip 22.12.* -> 22.12.* - 22.12.*
3683
	elseif (count($ip_parts) == 1)
3684
	{
3685
		$ip_parts[0] = $fullip;
3686
		$ip_parts[1] = $fullip;
3687
	}
3688
3689
	// if ip 22.12.31.21-12.21.31.21
3690
	if (count($ip_parts) == 2 && isValidIP($ip_parts[0]) && isValidIP($ip_parts[1]))
3691
	{
3692
		$ip_array['low'] = $ip_parts[0];
3693
		$ip_array['high'] = $ip_parts[1];
3694
		return $ip_array;
3695
	}
3696
	elseif (count($ip_parts) == 2) // if ip 22.22.*-22.22.*
3697
	{
3698
		$valid_low = isValidIP($ip_parts[0]);
3699
		$valid_high = isValidIP($ip_parts[1]);
3700
		$count = 0;
3701
		$mode = (preg_match('/:/',$ip_parts[0]) > 0 ? ':' : '.');
3702
		$max = ($mode == ':' ? 'ffff' : '255');
3703
		$min = 0;
3704 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...
3705
		{
3706
			$ip_parts[0] = preg_replace('/\*/', '0', $ip_parts[0]);
3707
			$valid_low = isValidIP($ip_parts[0]);
3708
			while (!$valid_low)
3709
			{
3710
				$ip_parts[0] .= $mode . $min;
3711
				$valid_low = isValidIP($ip_parts[0]);
3712
				$count++;
3713
				if ($count > 9) break;
3714
			}
3715
		}
3716
3717
		$count = 0;
3718 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...
3719
		{
3720
			$ip_parts[1] = preg_replace('/\*/', $max, $ip_parts[1]);
3721
			$valid_high = isValidIP($ip_parts[1]);
3722
			while (!$valid_high)
3723
			{
3724
				$ip_parts[1] .= $mode . $max;
3725
				$valid_high = isValidIP($ip_parts[1]);
3726
				$count++;
3727
				if ($count > 9) break;
3728
			}
3729
		}
3730
3731
		if($valid_high && $valid_low)
3732
		{
3733
			$ip_array['low'] = $ip_parts[0];
3734
			$ip_array['high'] = $ip_parts[1];
3735
		}
3736
3737
	}
3738
3739
	return $ip_array;
3740
}
3741
3742
/**
3743
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
3744
 *
3745
 * @param string $ip The IP to get the hostname from
3746
 * @return string The hostname
3747
 */
3748
function host_from_ip($ip)
3749
{
3750
	global $modSettings;
3751
3752
	if (($host = cache_get_data('hostlookup-' . $ip, 600)) !== null)
3753
		return $host;
3754
	$t = microtime();
3755
3756
	// Try the Linux host command, perhaps?
3757
	if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
3758
	{
3759
		if (!isset($modSettings['host_to_dis']))
3760
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
3761
		else
3762
			$test = @shell_exec('host ' . @escapeshellarg($ip));
3763
3764
		// Did host say it didn't find anything?
3765
		if (strpos($test, 'not found') !== false)
3766
			$host = '';
3767
		// Invalid server option?
3768
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
3769
			updateSettings(array('host_to_dis' => 1));
3770
		// Maybe it found something, after all?
3771
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
3772
			$host = $match[1];
3773
	}
3774
3775
	// This is nslookup; usually only Windows, but possibly some Unix?
3776
	if (!isset($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
3777
	{
3778
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
3779
		if (strpos($test, 'Non-existent domain') !== false)
3780
			$host = '';
3781
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
3782
			$host = $match[1];
3783
	}
3784
3785
	// This is the last try :/.
3786
	if (!isset($host) || $host === false)
3787
		$host = @gethostbyaddr($ip);
3788
3789
	// It took a long time, so let's cache it!
3790 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...
3791
		cache_put_data('hostlookup-' . $ip, $host, 600);
3792
3793
	return $host;
3794
}
3795
3796
/**
3797
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
3798
 *
3799
 * @param string $text The text to split into words
3800
 * @param int $max_chars The maximum number of characters per word
3801
 * @param bool $encrypt Whether to encrypt the results
3802
 * @return array An array of ints or words depending on $encrypt
3803
 */
3804
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...
3805
{
3806
	global $smcFunc, $context;
3807
3808
	// Step 1: Remove entities/things we don't consider words:
3809
	$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>' => ' ')));
3810
3811
	// Step 2: Entities we left to letters, where applicable, lowercase.
3812
	$words = un_htmlspecialchars($smcFunc['strtolower']($words));
3813
3814
	// Step 3: Ready to split apart and index!
3815
	$words = explode(' ', $words);
3816
3817
	if ($encrypt)
3818
	{
3819
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
3820
		$returned_ints = array();
3821
		foreach ($words as $word)
3822
		{
3823
			if (($word = trim($word, '-_\'')) !== '')
3824
			{
3825
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
3826
				$total = 0;
3827
				for ($i = 0; $i < $max_chars; $i++)
3828
					$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
3829
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
3830
			}
3831
		}
3832
		return array_unique($returned_ints);
3833
	}
3834
	else
3835
	{
3836
		// Trim characters before and after and add slashes for database insertion.
3837
		$returned_words = array();
3838
		foreach ($words as $word)
3839
			if (($word = trim($word, '-_\'')) !== '')
3840
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
3841
3842
		// Filter out all words that occur more than once.
3843
		return array_unique($returned_words);
3844
	}
3845
}
3846
3847
/**
3848
 * Creates an image/text button
3849
 *
3850
 * @param string $name The name of the button (should be a generic_icons class or the name of an image)
3851
 * @param string $alt The alt text
3852
 * @param string $label The $txt string to use as the label
3853
 * @param string $custom Custom text/html to add to the img tag (only when using an actual image)
3854
 * @param boolean $force_use Whether to force use of this when template_create_button is available
3855
 * @return string The HTML to display the button
3856
 */
3857
function create_button($name, $alt, $label = '', $custom = '', $force_use = false)
3858
{
3859
	global $settings, $txt;
3860
3861
	// Does the current loaded theme have this and we are not forcing the usage of this function?
3862
	if (function_exists('template_create_button') && !$force_use)
3863
		return template_create_button($name, $alt, $label = '', $custom = '');
3864
3865
	if (!$settings['use_image_buttons'])
3866
		return $txt[$alt];
3867
	elseif (!empty($settings['use_buttons']))
3868
		return '<span class="generic_icons ' . $name . '" alt="' . $txt[$alt] . '"></span>' . ($label != '' ? '&nbsp;<strong>' . $txt[$label] . '</strong>' : '');
3869
	else
3870
		return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . '>';
3871
}
3872
3873
/**
3874
 * Empty out the cache in use as best it can
3875
 *
3876
 * It may only remove the files of a certain type (if the $type parameter is given)
3877
 * Type can be user, data or left blank
3878
 * 	- user clears out user data
3879
 *  - data clears out system / opcode data
3880
 *  - If no type is specified will perform a complete cache clearing
3881
 * For cache engines that do not distinguish on types, a full cache flush will be done
3882
 *
3883
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3884
 */
3885
function clean_cache($type = '')
0 ignored issues
show
Best Practice introduced by
The function clean_cache() has been defined more than once; this definition is ignored, only the first definition in other/upgrade.php (L183-204) is considered.

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

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

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

}

function getUser($id, $realm) {

}

See also the PhpDoc documentation for @ignore.

Loading history...
3886
{
3887
	global $cachedir, $sourcedir, $cache_accelerator, $modSettings, $memcached;
3888
3889
	switch ($cache_accelerator)
3890
	{
3891
		case 'memcached':
3892
			if (function_exists('memcache_flush') || function_exists('memcached_flush') && isset($modSettings['cache_memcached']) && trim($modSettings['cache_memcached']) != '')
3893
			{
3894
				// Not connected yet?
3895
				if (empty($memcached))
3896
					get_memcached_server();
3897
				if (!$memcached)
3898
					return;
3899
3900
				// clear it out
3901
				if (function_exists('memcache_flush'))
3902
					memcache_flush($memcached);
3903
				else
3904
					memcached_flush($memcached);
3905
			}
3906
			break;
3907
		case 'apc':
3908
			if (function_exists('apc_clear_cache'))
3909
			{
3910
				// if passed a type, clear that type out
3911
				if ($type === '' || $type === 'data')
3912
				{
3913
					apc_clear_cache('user');
3914
					apc_clear_cache('system');
3915
				}
3916
				elseif ($type === 'user')
3917
					apc_clear_cache('user');
3918
			}
3919
			break;
3920
		case 'zend':
3921
			if (function_exists('zend_shm_cache_clear'))
3922
				zend_shm_cache_clear('SMF');
3923
			break;
3924
		case 'xcache':
3925
			if (function_exists('xcache_clear_cache'))
3926
			{
3927
				//
3928
				if ($type === '')
3929
				{
3930
					xcache_clear_cache(XC_TYPE_VAR, 0);
3931
					xcache_clear_cache(XC_TYPE_PHP, 0);
3932
				}
3933
				if ($type === 'user')
3934
					xcache_clear_cache(XC_TYPE_VAR, 0);
3935
				if ($type === 'data')
3936
					xcache_clear_cache(XC_TYPE_PHP, 0);
3937
			}
3938
			break;
3939
		default:
3940
			// No directory = no game.
3941
			if (!is_dir($cachedir))
3942
				return;
3943
3944
			// Remove the files in SMF's own disk cache, if any
3945
			$dh = opendir($cachedir);
3946 View Code Duplication
			while ($file = readdir($dh))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3947
			{
3948
				if ($file != '.' && $file != '..' && $file != 'index.php' && $file != '.htaccess' && (!$type || substr($file, 0, strlen($type)) == $type))
3949
					@unlink($cachedir . '/' . $file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3950
			}
3951
			closedir($dh);
3952
			break;
3953
	}
3954
3955
	// Invalidate cache, to be sure!
3956
	// ... as long as index.php can be modified, anyway.
3957
	if (empty($type))
3958
		@touch($cachedir . '/' . 'index.php');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3959
3960
	call_integration_hook('integrate_clean_cache');
3961
	clearstatcache();
3962
}
3963
3964
/**
3965
 * Sets up all of the top menu buttons
3966
 * Saves them in the cache if it is available and on
3967
 * Places the results in $context
3968
 *
3969
 */
3970
function setupMenuContext()
3971
{
3972
	global $context, $modSettings, $user_info, $txt, $scripturl, $sourcedir, $settings;
3973
3974
	// Set up the menu privileges.
3975
	$context['allow_search'] = !empty($modSettings['allow_guestAccess']) ? allowedTo('search_posts') : (!$user_info['is_guest'] && allowedTo('search_posts'));
3976
	$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'));
3977
3978
	$context['allow_memberlist'] = allowedTo('view_mlist');
3979
	$context['allow_calendar'] = allowedTo('calendar_view') && !empty($modSettings['cal_enabled']);
3980
	$context['allow_moderation_center'] = $context['user']['can_mod'];
3981
	$context['allow_pm'] = allowedTo('pm_read');
3982
3983
	$cacheTime = $modSettings['lastActive'] * 60;
3984
3985
	// Initial "can you post an event in the calendar" option - but this might have been set in the calendar already.
3986
	if (!isset($context['allow_calendar_event']))
3987
	{
3988
		$context['allow_calendar_event'] = $context['allow_calendar'] && allowedTo('calendar_post');
3989
3990
		// If you don't allow events not linked to posts and you're not an admin, we have more work to do...
3991
		if ($context['allow_calendar'] && $context['allow_calendar_event'] && empty($modSettings['cal_allow_unlinked']) && !$user_info['is_admin'])
3992
		{
3993
			$boards_can_post = boardsAllowedTo('post_new');
3994
			$context['allow_calendar_event'] &= !empty($boards_can_post);
3995
		}
3996
	}
3997
3998
	// There is some menu stuff we need to do if we're coming at this from a non-guest perspective.
3999
	if (!$context['user']['is_guest'])
4000
	{
4001
		addInlineJavascript('
4002
	var user_menus = new smc_PopupMenu();
4003
	user_menus.add("profile", "' . $scripturl . '?action=profile;area=popup");
4004
	user_menus.add("alerts", "' . $scripturl . '?action=profile;area=alerts_popup;u='. $context['user']['id'] .'");', true);
4005
		if ($context['allow_pm'])
4006
			addInlineJavascript('
4007
	user_menus.add("pm", "' . $scripturl . '?action=pm;sa=popup");', true);
4008
4009
		if (!empty($modSettings['enable_ajax_alerts']))
4010
		{
4011
			require_once($sourcedir . '/Subs-Notify.php');
4012
4013
			$timeout = getNotifyPrefs($context['user']['id'], 'alert_timeout', true);
4014
			$timeout = empty($timeout) ? 10000 : $timeout[$context['user']['id']]['alert_timeout'] * 1000;
4015
4016
			addInlineJavascript('
4017
	var new_alert_title = "' . $context['forum_name'] . '";
4018
	var alert_timeout = ' . $timeout . ';');
4019
			loadJavascriptFile('alerts.js', array(), 'smf_alerts');
4020
		}
4021
	}
4022
4023
	// All the buttons we can possible want and then some, try pulling the final list of buttons from cache first.
4024
	if (($menu_buttons = cache_get_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $cacheTime)) === null || time() - $cacheTime <= $modSettings['settings_updated'])
4025
	{
4026
		$buttons = array(
4027
			'home' => array(
4028
				'title' => $txt['home'],
4029
				'href' => $scripturl,
4030
				'show' => true,
4031
				'sub_buttons' => array(
4032
				),
4033
				'is_last' => $context['right_to_left'],
4034
			),
4035
			'search' => array(
4036
				'title' => $txt['search'],
4037
				'href' => $scripturl . '?action=search',
4038
				'show' => $context['allow_search'],
4039
				'sub_buttons' => array(
4040
				),
4041
			),
4042
			'admin' => array(
4043
				'title' => $txt['admin'],
4044
				'href' => $scripturl . '?action=admin',
4045
				'show' => $context['allow_admin'],
4046
				'sub_buttons' => array(
4047
					'featuresettings' => array(
4048
						'title' => $txt['modSettings_title'],
4049
						'href' => $scripturl . '?action=admin;area=featuresettings',
4050
						'show' => allowedTo('admin_forum'),
4051
					),
4052
					'packages' => array(
4053
						'title' => $txt['package'],
4054
						'href' => $scripturl . '?action=admin;area=packages',
4055
						'show' => allowedTo('admin_forum'),
4056
					),
4057
					'errorlog' => array(
4058
						'title' => $txt['errlog'],
4059
						'href' => $scripturl . '?action=admin;area=logs;sa=errorlog;desc',
4060
						'show' => allowedTo('admin_forum') && !empty($modSettings['enableErrorLogging']),
4061
					),
4062
					'permissions' => array(
4063
						'title' => $txt['edit_permissions'],
4064
						'href' => $scripturl . '?action=admin;area=permissions',
4065
						'show' => allowedTo('manage_permissions'),
4066
					),
4067
					'memberapprove' => array(
4068
						'title' => $txt['approve_members_waiting'],
4069
						'href' => $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve',
4070
						'show' => !empty($context['unapproved_members']),
4071
						'is_last' => true,
4072
					),
4073
				),
4074
			),
4075
			'moderate' => array(
4076
				'title' => $txt['moderate'],
4077
				'href' => $scripturl . '?action=moderate',
4078
				'show' => $context['allow_moderation_center'],
4079
				'sub_buttons' => array(
4080
					'modlog' => array(
4081
						'title' => $txt['modlog_view'],
4082
						'href' => $scripturl . '?action=moderate;area=modlog',
4083
						'show' => !empty($modSettings['modlog_enabled']) && !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4084
					),
4085
					'poststopics' => array(
4086
						'title' => $txt['mc_unapproved_poststopics'],
4087
						'href' => $scripturl . '?action=moderate;area=postmod;sa=posts',
4088
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4089
					),
4090
					'attachments' => array(
4091
						'title' => $txt['mc_unapproved_attachments'],
4092
						'href' => $scripturl . '?action=moderate;area=attachmod;sa=attachments',
4093
						'show' => $modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap']),
4094
					),
4095
					'reports' => array(
4096
						'title' => $txt['mc_reported_posts'],
4097
						'href' => $scripturl . '?action=moderate;area=reportedposts',
4098
						'show' => !empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1',
4099
					),
4100
					'reported_members' => array(
4101
						'title' => $txt['mc_reported_members'],
4102
						'href' => $scripturl . '?action=moderate;area=reportedmembers',
4103
						'show' => allowedTo('moderate_forum'),
4104
						'is_last' => true,
4105
					)
4106
				),
4107
			),
4108
			'calendar' => array(
4109
				'title' => $txt['calendar'],
4110
				'href' => $scripturl . '?action=calendar',
4111
				'show' => $context['allow_calendar'],
4112
				'sub_buttons' => array(
4113
					'view' => array(
4114
						'title' => $txt['calendar_menu'],
4115
						'href' => $scripturl . '?action=calendar',
4116
						'show' => $context['allow_calendar_event'],
4117
					),
4118
					'post' => array(
4119
						'title' => $txt['calendar_post_event'],
4120
						'href' => $scripturl . '?action=calendar;sa=post',
4121
						'show' => $context['allow_calendar_event'],
4122
						'is_last' => true,
4123
					),
4124
				),
4125
			),
4126
			'mlist' => array(
4127
				'title' => $txt['members_title'],
4128
				'href' => $scripturl . '?action=mlist',
4129
				'show' => $context['allow_memberlist'],
4130
				'sub_buttons' => array(
4131
					'mlist_view' => array(
4132
						'title' => $txt['mlist_menu_view'],
4133
						'href' => $scripturl . '?action=mlist',
4134
						'show' => true,
4135
					),
4136
					'mlist_search' => array(
4137
						'title' => $txt['mlist_search'],
4138
						'href' => $scripturl . '?action=mlist;sa=search',
4139
						'show' => true,
4140
						'is_last' => true,
4141
					),
4142
				),
4143
			),
4144
			'signup' => array(
4145
				'title' => $txt['register'],
4146
				'href' => $scripturl . '?action=signup',
4147
				'show' => $user_info['is_guest'] && $context['can_register'],
4148
				'sub_buttons' => array(
4149
				),
4150
				'is_last' => !$context['right_to_left'],
4151
			),
4152
			'logout' => array(
4153
				'title' => $txt['logout'],
4154
				'href' => $scripturl . '?action=logout;%1$s=%2$s',
4155
				'show' => !$user_info['is_guest'],
4156
				'sub_buttons' => array(
4157
				),
4158
				'is_last' => !$context['right_to_left'],
4159
			),
4160
		);
4161
4162
		// Allow editing menu buttons easily.
4163
		call_integration_hook('integrate_menu_buttons', array(&$buttons));
4164
4165
		// Now we put the buttons in the context so the theme can use them.
4166
		$menu_buttons = array();
4167
		foreach ($buttons as $act => $button)
4168
			if (!empty($button['show']))
4169
			{
4170
				$button['active_button'] = false;
4171
4172
				// This button needs some action.
4173
				if (isset($button['action_hook']))
4174
					$needs_action_hook = true;
4175
4176
				// Make sure the last button truly is the last button.
4177
				if (!empty($button['is_last']))
4178
				{
4179
					if (isset($last_button))
4180
						unset($menu_buttons[$last_button]['is_last']);
4181
					$last_button = $act;
4182
				}
4183
4184
				// Go through the sub buttons if there are any.
4185
				if (!empty($button['sub_buttons']))
4186
					foreach ($button['sub_buttons'] as $key => $subbutton)
4187
					{
4188
						if (empty($subbutton['show']))
4189
							unset($button['sub_buttons'][$key]);
4190
4191
						// 2nd level sub buttons next...
4192
						if (!empty($subbutton['sub_buttons']))
4193
						{
4194
							foreach ($subbutton['sub_buttons'] as $key2 => $sub_button2)
4195
							{
4196
								if (empty($sub_button2['show']))
4197
									unset($button['sub_buttons'][$key]['sub_buttons'][$key2]);
4198
							}
4199
						}
4200
					}
4201
4202
				// Does this button have its own icon?
4203
				if (isset($button['icon']) && file_exists($settings['theme_dir'] . '/images/' . $button['icon']))
4204
					$button['icon'] = '<img src="' . $settings['images_url'] . '/' . $button['icon'] . '" alt="">';
4205
				elseif (isset($button['icon']) && file_exists($settings['default_theme_dir'] . '/images/' . $button['icon']))
4206
					$button['icon'] = '<img src="' . $settings['default_images_url'] . '/' . $button['icon'] . '" alt="">';
4207
				elseif (isset($button['icon']))
4208
					$button['icon'] = '<span class="generic_icons ' . $button['icon'] . '"></span>';
4209
				else
4210
					$button['icon'] = '<span class="generic_icons ' . $act . '"></span>';
4211
4212
				$menu_buttons[$act] = $button;
4213
			}
4214
4215
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
4216
			cache_put_data('menu_buttons-' . implode('_', $user_info['groups']) . '-' . $user_info['language'], $menu_buttons, $cacheTime);
4217
	}
4218
4219
	$context['menu_buttons'] = $menu_buttons;
4220
4221
	// Logging out requires the session id in the url.
4222
	if (isset($context['menu_buttons']['logout']))
4223
		$context['menu_buttons']['logout']['href'] = sprintf($context['menu_buttons']['logout']['href'], $context['session_var'], $context['session_id']);
4224
4225
	// Figure out which action we are doing so we can set the active tab.
4226
	// Default to home.
4227
	$current_action = 'home';
4228
4229
	if (isset($context['menu_buttons'][$context['current_action']]))
4230
		$current_action = $context['current_action'];
4231
	elseif ($context['current_action'] == 'search2')
4232
		$current_action = 'search';
4233
	elseif ($context['current_action'] == 'theme')
4234
		$current_action = isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'pick' ? 'profile' : 'admin';
4235
	elseif ($context['current_action'] == 'register2')
4236
		$current_action = 'register';
4237
	elseif ($context['current_action'] == 'login2' || ($user_info['is_guest'] && $context['current_action'] == 'reminder'))
4238
		$current_action = 'login';
4239
	elseif ($context['current_action'] == 'groups' && $context['allow_moderation_center'])
4240
		$current_action = 'moderate';
4241
4242
	// There are certain exceptions to the above where we don't want anything on the menu highlighted.
4243
	if ($context['current_action'] == 'profile' && !empty($context['user']['is_owner']))
4244
	{
4245
		$current_action = !empty($_GET['area']) && $_GET['area'] == 'showalerts' ? 'self_alerts' : 'self_profile';
4246
		$context[$current_action] = true;
4247
	}
4248
	elseif ($context['current_action'] == 'pm')
4249
	{
4250
		$current_action = 'self_pm';
4251
		$context['self_pm'] = true;
4252
	}
4253
4254
	$total_mod_reports = 0;
4255
4256
	if (!empty($user_info['mod_cache']) && $user_info['mod_cache']['bq'] != '0=1' && !empty($context['open_mod_reports']))
4257
	{
4258
		$total_mod_reports = $context['open_mod_reports'];
4259
		$context['menu_buttons']['moderate']['sub_buttons']['reports']['title'] .= ' <span class="amt">' . $context['open_mod_reports'] . '</span>';
4260
	}
4261
4262
	// Show how many errors there are
4263 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...
4264
	{
4265
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4266
		$context['menu_buttons']['admin']['sub_buttons']['errorlog']['title'] .= ' <span class="amt">' . $context['num_errors'] . '</span>';
4267
	}
4268
4269
	/**
4270
	 * @todo For some reason, $context['open_member_reports'] isn't getting set
4271
	 */
4272
	if (!empty($context['open_member_reports']) && allowedTo('moderate_forum'))
4273
	{
4274
		$total_mod_reports += $context['open_member_reports'];
4275
		$context['menu_buttons']['moderate']['sub_buttons']['reported_members']['title'] .= ' <span class="amt">' . $context['open_member_reports'] . '</span>';
4276
	}
4277
4278 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...
4279
	{
4280
		$context['menu_buttons']['admin']['sub_buttons']['memberapprove']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4281
		$context['menu_buttons']['admin']['title'] .= ' <span class="amt">' . $context['unapproved_members'] . '</span>';
4282
	}
4283
4284
	// Do we have any open reports?
4285
	if ($total_mod_reports > 0)
4286
	{
4287
		$context['menu_buttons']['moderate']['title'] .= ' <span class="amt">' . $total_mod_reports . '</span>';
4288
	}
4289
4290
	// Not all actions are simple.
4291
	if (!empty($needs_action_hook))
4292
		call_integration_hook('integrate_current_action', array(&$current_action));
4293
4294
	if (isset($context['menu_buttons'][$current_action]))
4295
		$context['menu_buttons'][$current_action]['active_button'] = true;
4296
}
4297
4298
/**
4299
 * Generate a random seed and ensure it's stored in settings.
4300
 */
4301
function smf_seed_generator()
4302
{
4303
	updateSettings(array('rand_seed' => microtime() * 1000000));
4304
}
4305
4306
/**
4307
 * Process functions of an integration hook.
4308
 * calls all functions of the given hook.
4309
 * supports static class method calls.
4310
 *
4311
 * @param string $hook The hook name
4312
 * @param array $parameters An array of parameters this hook implements
4313
 * @return array The results of the functions
4314
 */
4315
function call_integration_hook($hook, $parameters = array())
4316
{
4317
	global $modSettings, $settings, $boarddir, $sourcedir, $db_show_debug;
4318
	global $context, $txt;
4319
4320
	if ($db_show_debug === true)
4321
		$context['debug']['hooks'][] = $hook;
4322
4323
	// Need to have some control.
4324
	if (!isset($context['instances']))
4325
		$context['instances'] = array();
4326
4327
	$results = array();
4328
	if (empty($modSettings[$hook]))
4329
		return $results;
4330
4331
	// Define some needed vars.
4332
	$function = false;
4333
4334
	$functions = explode(',', $modSettings[$hook]);
4335
	// Loop through each function.
4336
	foreach ($functions as $function)
4337
	{
4338
		// Hook has been marked as "disabled". Skip it!
4339
		if (strpos($function, '!') !== false)
4340
			continue;
4341
4342
		$call = call_helper($function, true);
4343
4344
		// Is it valid?
4345
		if (!empty($call))
4346
			$results[$function] = call_user_func_array($call, $parameters);
4347
4348
		// Whatever it was suppose to call, it failed :(
4349
		elseif (!empty($function))
4350
		{
4351
			loadLanguage('Errors');
4352
4353
			// Get a full path to show on error.
4354
			if (strpos($function, '|') !== false)
4355
			{
4356
				list ($file, $string) = explode('|', $function);
4357
				$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'])));
4358
				log_error(sprintf($txt['hook_fail_call_to'], $string, $absPath), 'general');
4359
			}
4360
4361
			// "Assume" the file resides on $boarddir somewhere...
4362
			else
4363
				log_error(sprintf($txt['hook_fail_call_to'], $function, $boarddir), 'general');
4364
		}
4365
	}
4366
4367
	return $results;
4368
}
4369
4370
/**
4371
 * Add a function for integration hook.
4372
 * does nothing if the function is already added.
4373
 *
4374
 * @param string $hook The complete hook name.
4375
 * @param string $function The function name. Can be a call to a method via Class::method.
4376
 * @param bool $permanent If true, updates the value in settings table.
4377
 * @param string $file The file. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4378
 * @param bool $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4379
 */
4380
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
4381
{
4382
	global $smcFunc, $modSettings;
4383
4384
	// Any objects?
4385
	if ($object)
4386
		$function = $function . '#';
4387
4388
	// Any files  to load?
4389
	if (!empty($file) && is_string($file))
4390
		$function = $file . '|' . $function;
4391
4392
	// Get the correct string.
4393
	$integration_call = $function;
4394
4395
	// Is it going to be permanent?
4396
	if ($permanent)
4397
	{
4398
		$request = $smcFunc['db_query']('', '
4399
			SELECT value
4400
			FROM {db_prefix}settings
4401
			WHERE variable = {string:variable}',
4402
			array(
4403
				'variable' => $hook,
4404
			)
4405
		);
4406
		list ($current_functions) = $smcFunc['db_fetch_row']($request);
4407
		$smcFunc['db_free_result']($request);
4408
4409
		if (!empty($current_functions))
4410
		{
4411
			$current_functions = explode(',', $current_functions);
4412
			if (in_array($integration_call, $current_functions))
4413
				return;
4414
4415
			$permanent_functions = array_merge($current_functions, array($integration_call));
4416
		}
4417
		else
4418
			$permanent_functions = array($integration_call);
4419
4420
		updateSettings(array($hook => implode(',', $permanent_functions)));
4421
	}
4422
4423
	// Make current function list usable.
4424
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4425
4426
	// Do nothing, if it's already there.
4427
	if (in_array($integration_call, $functions))
4428
		return;
4429
4430
	$functions[] = $integration_call;
4431
	$modSettings[$hook] = implode(',', $functions);
4432
}
4433
4434
/**
4435
 * Remove an integration hook function.
4436
 * Removes the given function from the given hook.
4437
 * Does nothing if the function is not available.
4438
 *
4439
 * @param string $hook The complete hook name.
4440
 * @param string $function The function name. Can be a call to a method via Class::method.
4441
 * @params boolean $permanent Irrelevant for the function itself but need to declare it to match
4442
 * @param string $file The filename. Must include one of the following wildcards: $boarddir, $sourcedir, $themedir, example: $sourcedir/Test.php
4443
add_integration_function
4444
 * @param boolean $object Indicates if your class will be instantiated when its respective hook is called. If true, your function must be a method.
4445
 * @see add_integration_function
4446
 */
4447
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...
4448
{
4449
	global $smcFunc, $modSettings;
4450
4451
	// Any objects?
4452
	if ($object)
4453
		$function = $function . '#';
4454
4455
	// Any files  to load?
4456
	if (!empty($file) && is_string($file))
4457
		$function = $file . '|' . $function;
4458
4459
	// Get the correct string.
4460
	$integration_call = $function;
4461
4462
	// Get the permanent functions.
4463
	$request = $smcFunc['db_query']('', '
4464
		SELECT value
4465
		FROM {db_prefix}settings
4466
		WHERE variable = {string:variable}',
4467
		array(
4468
			'variable' => $hook,
4469
		)
4470
	);
4471
	list ($current_functions) = $smcFunc['db_fetch_row']($request);
4472
	$smcFunc['db_free_result']($request);
4473
4474
	if (!empty($current_functions))
4475
	{
4476
		$current_functions = explode(',', $current_functions);
4477
4478
		if (in_array($integration_call, $current_functions))
4479
			updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call)))));
4480
	}
4481
4482
	// Turn the function list into something usable.
4483
	$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);
4484
4485
	// You can only remove it if it's available.
4486
	if (!in_array($integration_call, $functions))
4487
		return;
4488
4489
	$functions = array_diff($functions, array($integration_call));
4490
	$modSettings[$hook] = implode(',', $functions);
4491
}
4492
4493
/**
4494
 * Receives a string and tries to figure it out if its a method or a function.
4495
 * If a method is found, it looks for a "#" which indicates SMF should create a new instance of the given class.
4496
 * Checks the string/array for is_callable() and return false/fatal_lang_error is the given value results in a non callable string/array.
4497
 * Prepare and returns a callable depending on the type of method/function found.
4498
 *
4499
 * @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)
4500
 * @param boolean $return If true, the function will not call the function/method but instead will return the formatted string.
4501
 * @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.
4502
 */
4503
function call_helper($string, $return = false)
4504
{
4505
	global $context, $smcFunc, $txt, $db_show_debug;
4506
4507
	// Really?
4508
	if (empty($string))
4509
		return false;
4510
4511
	// An array? should be a "callable" array IE array(object/class, valid_callable).
4512
	// A closure? should be a callable one.
4513
	if (is_array($string) || $string instanceof Closure)
4514
		return $return ? $string : (is_callable($string) ? call_user_func($string) : false);
4515
4516
	// No full objects, sorry! pass a method or a property instead!
4517
	if (is_object($string))
4518
		return false;
4519
4520
	// Stay vitaminized my friends...
4521
	$string = $smcFunc['htmlspecialchars']($smcFunc['htmltrim']($string));
4522
4523
	// The soon to be populated var.
4524
	$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...
4525
4526
	// Is there a file to load?
4527
	$string = load_file($string);
4528
4529
	// Loaded file failed
4530
	if (empty($string))
4531
		return false;
4532
4533
	// Found a method.
4534
	if (strpos($string, '::') !== false)
4535
	{
4536
		list ($class, $method) = explode('::', $string);
4537
4538
		// Check if a new object will be created.
4539
		if (strpos($method, '#') !== false)
4540
		{
4541
			// Need to remove the # thing.
4542
			$method = str_replace('#', '', $method);
4543
4544
			// Don't need to create a new instance for every method.
4545
			if (empty($context['instances'][$class]) || !($context['instances'][$class] instanceof $class))
4546
			{
4547
				$context['instances'][$class] = new $class;
4548
4549
				// Add another one to the list.
4550
				if ($db_show_debug === true)
4551
				{
4552
					if (!isset($context['debug']['instances']))
4553
						$context['debug']['instances'] = array();
4554
4555
					$context['debug']['instances'][$class] = $class;
4556
				}
4557
			}
4558
4559
			$func = array($context['instances'][$class], $method);
4560
		}
4561
4562
		// Right then. This is a call to a static method.
4563
		else
4564
			$func = array($class, $method);
4565
	}
4566
4567
	// Nope! just a plain regular function.
4568
	else
4569
		$func = $string;
4570
4571
	// Right, we got what we need, time to do some checks.
4572
	if (!is_callable($func, false, $callable_name))
4573
	{
4574
		loadLanguage('Errors');
4575
		log_error(sprintf($txt['subAction_fail'], $callable_name), 'general');
4576
4577
		// Gotta tell everybody.
4578
		return false;
4579
	}
4580
4581
	// Everything went better than expected.
4582
	else
4583
	{
4584
		// What are we gonna do about it?
4585
		if ($return)
4586
			return $func;
4587
4588
		// If this is a plain function, avoid the heat of calling call_user_func().
4589
		else
4590
		{
4591
			if (is_array($func))
4592
				call_user_func($func);
4593
4594
			else
4595
				$func();
4596
		}
4597
	}
4598
}
4599
4600
/**
4601
 * Receives a string and tries to figure it out if it contains info to load a file.
4602
 * Checks for a | (pipe) symbol and tries to load a file with the info given.
4603
 * 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.
4604
 *
4605
 * @param string $string The string containing a valid format.
4606
 * @return string|boolean The given string with the pipe and file info removed. Boolean false if the file couldn't be loaded.
4607
 */
4608
function load_file($string)
4609
{
4610
	global $sourcedir, $txt, $boarddir, $settings;
4611
4612
	if (empty($string))
4613
		return false;
4614
4615
	if (strpos($string, '|') !== false)
4616
	{
4617
		list ($file, $string) = explode('|', $string);
4618
4619
		// Match the wildcards to their regular vars.
4620
		if (empty($settings['theme_dir']))
4621
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
4622
4623
		else
4624
			$absPath = strtr(trim($file), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
4625
4626
		// Load the file if it can be loaded.
4627
		if (file_exists($absPath))
4628
			require_once($absPath);
4629
4630
		// No? try a fallback to $sourcedir
4631
		else
4632
		{
4633
			$absPath = $sourcedir .'/'. $file;
4634
4635
			if (file_exists($absPath))
4636
				require_once($absPath);
4637
4638
			// Sorry, can't do much for you at this point.
4639
			else
4640
			{
4641
				loadLanguage('Errors');
4642
				log_error(sprintf($txt['hook_fail_loading_file'], $absPath), 'general');
4643
4644
				// File couldn't be loaded.
4645
				return false;
4646
			}
4647
		}
4648
	}
4649
4650
	return $string;
4651
}
4652
4653
/**
4654
 * Prepares an array of "likes" info for the topic specified by $topic
4655
 * @param integer $topic The topic ID to fetch the info from.
4656
 * @return array An array of IDs of messages in the specified topic that the current user likes
4657
 */
4658
function prepareLikesContext($topic)
4659
{
4660
	global $user_info, $smcFunc;
4661
4662
	// Make sure we have something to work with.
4663
	if (empty($topic))
4664
		return array();
4665
4666
4667
	// We already know the number of likes per message, we just want to know whether the current user liked it or not.
4668
	$user = $user_info['id'];
4669
	$cache_key = 'likes_topic_' . $topic . '_' . $user;
4670
	$ttl = 180;
4671
4672
	if (($temp = cache_get_data($cache_key, $ttl)) === null)
4673
	{
4674
		$temp = array();
4675
		$request = $smcFunc['db_query']('', '
4676
			SELECT content_id
4677
			FROM {db_prefix}user_likes AS l
4678
				INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
4679
			WHERE l.id_member = {int:current_user}
4680
				AND l.content_type = {literal:msg}
4681
				AND m.id_topic = {int:topic}',
4682
			array(
4683
				'current_user' => $user,
4684
				'topic' => $topic,
4685
			)
4686
		);
4687
		while ($row = $smcFunc['db_fetch_assoc']($request))
4688
			$temp[] = (int) $row['content_id'];
4689
4690
		cache_put_data($cache_key, $temp, $ttl);
4691
	}
4692
4693
	return $temp;
4694
}
4695
4696
/**
4697
 * Microsoft uses their own character set Code Page 1252 (CP1252), which is a
4698
 * superset of ISO 8859-1, defining several characters between DEC 128 and 159
4699
 * that are not normally displayable.  This converts the popular ones that
4700
 * appear from a cut and paste from windows.
4701
 *
4702
 * @param string $string The string
4703
 * @return string The sanitized string
4704
 */
4705
function sanitizeMSCutPaste($string)
4706
{
4707
	global $context;
4708
4709
	if (empty($string))
4710
		return $string;
4711
4712
	// UTF-8 occurences of MS special characters
4713
	$findchars_utf8 = array(
4714
		"\xe2\80\x9a",	// single low-9 quotation mark
4715
		"\xe2\80\x9e",	// double low-9 quotation mark
4716
		"\xe2\80\xa6",	// horizontal ellipsis
4717
		"\xe2\x80\x98",	// left single curly quote
4718
		"\xe2\x80\x99",	// right single curly quote
4719
		"\xe2\x80\x9c",	// left double curly quote
4720
		"\xe2\x80\x9d",	// right double curly quote
4721
		"\xe2\x80\x93",	// en dash
4722
		"\xe2\x80\x94",	// em dash
4723
	);
4724
4725
	// windows 1252 / iso equivalents
4726
	$findchars_iso = array(
4727
		chr(130),
4728
		chr(132),
4729
		chr(133),
4730
		chr(145),
4731
		chr(146),
4732
		chr(147),
4733
		chr(148),
4734
		chr(150),
4735
		chr(151),
4736
	);
4737
4738
	// safe replacements
4739
	$replacechars = array(
4740
		',',	// &sbquo;
4741
		',,',	// &bdquo;
4742
		'...',	// &hellip;
4743
		"'",	// &lsquo;
4744
		"'",	// &rsquo;
4745
		'"',	// &ldquo;
4746
		'"',	// &rdquo;
4747
		'-',	// &ndash;
4748
		'--',	// &mdash;
4749
	);
4750
4751
	if ($context['utf8'])
4752
		$string = str_replace($findchars_utf8, $replacechars, $string);
4753
	else
4754
		$string = str_replace($findchars_iso, $replacechars, $string);
4755
4756
	return $string;
4757
}
4758
4759
/**
4760
 * Decode numeric html entities to their ascii or UTF8 equivalent character.
4761
 *
4762
 * Callback function for preg_replace_callback in subs-members
4763
 * Uses capture group 2 in the supplied array
4764
 * Does basic scan to ensure characters are inside a valid range
4765
 *
4766
 * @param array $matches An array of matches (relevant info should be the 3rd item)
4767
 * @return string A fixed string
4768
 */
4769
function replaceEntities__callback($matches)
4770
{
4771
	global $context;
4772
4773
	if (!isset($matches[2]))
4774
		return '';
4775
4776
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4777
4778
	// remove left to right / right to left overrides
4779
	if ($num === 0x202D || $num === 0x202E)
4780
		return '';
4781
4782
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
4783
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
4784
		return '&#' . $num . ';';
4785
4786
	if (empty($context['utf8']))
4787
	{
4788
		// no control characters
4789
		if ($num < 0x20)
4790
			return '';
4791
		// text is text
4792
		elseif ($num < 0x80)
4793
			return chr($num);
4794
		// all others get html-ised
4795
		else
4796
			return '&#' . $matches[2] . ';';
4797
	}
4798
	else
4799
	{
4800
		// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
4801
		// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
4802
		if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
4803
			return '';
4804
		// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4805
		elseif ($num < 0x80)
4806
			return chr($num);
4807
		// <0x800 (2048)
4808 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...
4809
			return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4810
		// < 0x10000 (65536)
4811 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...
4812
			return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4813
		// <= 0x10FFFF (1114111)
4814 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...
4815
			return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4816
	}
4817
}
4818
4819
/**
4820
 * Converts html entities to utf8 equivalents
4821
 *
4822
 * Callback function for preg_replace_callback
4823
 * Uses capture group 1 in the supplied array
4824
 * Does basic checks to keep characters inside a viewable range.
4825
 *
4826
 * @param array $matches An array of matches (relevant info should be the 2nd item in the array)
4827
 * @return string The fixed string
4828
 */
4829
function fixchar__callback($matches)
4830
{
4831
	if (!isset($matches[1]))
4832
		return '';
4833
4834
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
4835
4836
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
4837
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
4838
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
4839
		return '';
4840
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
4841
	elseif ($num < 0x80)
4842
		return chr($num);
4843
	// <0x800 (2048)
4844 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...
4845
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
4846
	// < 0x10000 (65536)
4847 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...
4848
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4849
	// <= 0x10FFFF (1114111)
4850 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...
4851
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
4852
}
4853
4854
/**
4855
 * Strips out invalid html entities, replaces others with html style &#123; codes
4856
 *
4857
 * Callback function used of preg_replace_callback in smcFunc $ent_checks, for example
4858
 * strpos, strlen, substr etc
4859
 *
4860
 * @param array $matches An array of matches (relevant info should be the 3rd item in the array)
4861
 * @return string The fixed string
4862
 */
4863
function entity_fix__callback($matches)
4864
{
4865
	if (!isset($matches[2]))
4866
		return '';
4867
4868
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
4869
4870
	// we don't allow control characters, characters out of range, byte markers, etc
4871
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
4872
		return '';
4873
	else
4874
		return '&#' . $num . ';';
4875
}
4876
4877
/**
4878
 * Return a Gravatar URL based on
4879
 * - the supplied email address,
4880
 * - the global maximum rating,
4881
 * - the global default fallback,
4882
 * - maximum sizes as set in the admin panel.
4883
 *
4884
 * It is SSL aware, and caches most of the parameters.
4885
 *
4886
 * @param string $email_address The user's email address
4887
 * @return string The gravatar URL
4888
 */
4889
function get_gravatar_url($email_address)
4890
{
4891
	global $modSettings, $smcFunc;
4892
	static $url_params = null;
4893
4894
	if ($url_params === null)
4895
	{
4896
		$ratings = array('G', 'PG', 'R', 'X');
4897
		$defaults = array('mm', 'identicon', 'monsterid', 'wavatar', 'retro', 'blank');
4898
		$url_params = array();
4899 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...
4900
			$url_params[] = 'rating=' . $modSettings['gravatarMaxRating'];
4901 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...
4902
			$url_params[] = 'default=' . $modSettings['gravatarDefault'];
4903
		if (!empty($modSettings['avatar_max_width_external']))
4904
			$size_string = (int) $modSettings['avatar_max_width_external'];
4905
		if (!empty($modSettings['avatar_max_height_external']) && !empty($size_string))
4906
			if ((int) $modSettings['avatar_max_height_external'] < $size_string)
4907
				$size_string = $modSettings['avatar_max_height_external'];
4908
4909
		if (!empty($size_string))
4910
			$url_params[] = 's=' . $size_string;
4911
	}
4912
	$http_method = !empty($modSettings['force_ssl']) && $modSettings['force_ssl'] == 2 ? 'https://secure' : 'http://www';
4913
4914
	return $http_method . '.gravatar.com/avatar/' . md5($smcFunc['strtolower']($email_address)) . '?' . implode('&', $url_params);
4915
}
4916
4917
/**
4918
 * Get a list of timezoned.
4919
 *
4920
 * @return array An array of timezone info.
4921
 */
4922
function smf_list_timezones()
4923
{
4924
	return array(
4925
		'' => '(Forum Default)',
4926
		'UTC' => '[UTC] UTC',
4927
		'Pacific/Midway' => '[UTC-11:00] Midway Island, Samoa',
4928
		'America/Adak' => '[UTC-10:00] Hawaii-Aleutian',
4929
		'Pacific/Honolulu' => '[UTC-10:00] Hawaii',
4930
		'Pacific/Marquesas' => '[UTC-09:30] Marquesas Islands',
4931
		'Pacific/Gambier' => '[UTC-09:00] Gambier Islands',
4932
		'America/Anchorage' => '[UTC-09:00] Alaska',
4933
		'America/Ensenada' => '[UTC-08:00] Tijuana, Baja California',
4934
		'Pacific/Pitcairn' => '[UTC-08:00] Pitcairn Islands',
4935
		'America/Los_Angeles' => '[UTC-08:00] Pacific Time (USA, Canada)',
4936
		'America/Denver' => '[UTC-07:00] Mountain Time (USA, Canada)',
4937
		'America/Phoenix' => '[UTC-07:00] Arizona',
4938
		'America/Chihuahua' => '[UTC-07:00] Chihuahua, Mazatlan',
4939
		'America/Belize' => '[UTC-06:00] Saskatchewan, Central America',
4940
		'America/Cancun' => '[UTC-06:00] Guadalajara, Mexico City, Monterrey',
4941
		'Chile/EasterIsland' => '[UTC-06:00] Easter Island',
4942
		'America/Chicago' => '[UTC-06:00] Central Time (USA, Canada)',
4943
		'America/New_York' => '[UTC-05:00] Eastern Time (USA, Canada)',
4944
		'America/Havana' => '[UTC-05:00] Cuba',
4945
		'America/Bogota' => '[UTC-05:00] Bogota, Lima, Quito',
4946
		'America/Caracas' => '[UTC-04:30] Caracas',
4947
		'America/Santiago' => '[UTC-04:00] Santiago',
4948
		'America/La_Paz' => '[UTC-04:00] La Paz, San Juan, Manaus',
4949
		'Atlantic/Stanley' => '[UTC-04:00] Falkland Islands',
4950
		'America/Cuiaba' => '[UTC-04:00] Cuiaba',
4951
		'America/Goose_Bay' => '[UTC-04:00] Atlantic Time (Goose Bay)',
4952
		'America/Glace_Bay' => '[UTC-04:00] Atlantic Time (Canada)',
4953
		'America/St_Johns' => '[UTC-03:30] Newfoundland',
4954
		'America/Araguaina' => '[UTC-03:00] Araguaina',
4955
		'America/Montevideo' => '[UTC-03:00] Montevideo',
4956
		'America/Miquelon' => '[UTC-03:00] Saint Pierre and Miquelon',
4957
		'America/Argentina/Buenos_Aires' => '[UTC-03:00] Buenos Aires',
4958
		'America/Sao_Paulo' => '[UTC-03:00] Brasilia',
4959
		'America/Godthab' => '[UTC-02:00] Greenland',
4960
		'America/Noronha' => '[UTC-02:00] Fernando de Noronha',
4961
		'Atlantic/Cape_Verde' => '[UTC-01:00] Cape Verde',
4962
		'Atlantic/Azores' => '[UTC-01:00] Azores',
4963
		'Africa/Abidjan' => '[UTC] Monrovia, Reykjavik',
4964
		'Europe/London' => '[UTC] London, Edinburgh, Dublin, Lisbon (Greenwich Mean Time)',
4965
		'Europe/Brussels' => '[UTC+01:00] Central European Time',
4966
		'Africa/Algiers' => '[UTC+01:00] West Central Africa',
4967
		'Africa/Windhoek' => '[UTC+01:00] Windhoek',
4968
		'Africa/Cairo' => '[UTC+02:00] Cairo',
4969
		'Africa/Blantyre' => '[UTC+02:00] Harare, Maputo, Pretoria',
4970
		'Asia/Jerusalem' => '[UTC+02:00] Jerusalem',
4971
		'Europe/Minsk' => '[UTC+02:00] Minsk',
4972
		'Asia/Damascus' => '[UTC+02:00] Damascus, Nicosia, Gaza, Beirut',
4973
		'Africa/Addis_Ababa' => '[UTC+03:00] Addis Ababa, Nairobi',
4974
		'Asia/Tehran' => '[UTC+03:30] Tehran',
4975
		'Europe/Moscow' => '[UTC+04:00] Moscow, St. Petersburg, Volgograd',
4976
		'Asia/Dubai' => '[UTC+04:00] Abu Dhabi, Muscat',
4977
		'Asia/Baku' => '[UTC+04:00] Baku',
4978
		'Asia/Yerevan' => '[UTC+04:00] Yerevan',
4979
		'Asia/Kabul' => '[UTC+04:30] Kabul',
4980
		'Asia/Tashkent' => '[UTC+05:00] Tashkent',
4981
		'Asia/Kolkata' => '[UTC+05:30] Chennai, Kolkata, Mumbai, New Delhi',
4982
		'Asia/Katmandu' => '[UTC+05:45] Kathmandu',
4983
		'Asia/Yekaterinburg' => '[UTC+06:00] Yekaterinburg, Tyumen',
4984
		'Asia/Dhaka' => '[UTC+06:00] Astana, Thimphu, Dhaka',
4985
		'Asia/Novosibirsk' => '[UTC+06:00] Omsk, Novosibirsk',
4986
		'Asia/Rangoon' => '[UTC+06:30] Yangon Rangoon',
4987
		'Asia/Bangkok' => '[UTC+07:00] Bangkok, Hanoi, Jakarta',
4988
		'Asia/Krasnoyarsk' => '[UTC+08:00] Krasnoyarsk',
4989
		'Asia/Hong_Kong' => '[UTC+08:00] Beijing, Chongqing, Hong Kong, Urumqi',
4990
		'Asia/Ulaanbaatar' => '[UTC+08:00] Ulaan Bataar',
4991
		'Asia/Irkutsk' => '[UTC+09:00] Irkutsk',
4992
		'Australia/Perth' => '[UTC+08:00] Perth',
4993
		'Australia/Eucla' => '[UTC+08:45] Eucla',
4994
		'Asia/Tokyo' => '[UTC+09:00] Tokyo, Osaka, Sapporo',
4995
		'Asia/Seoul' => '[UTC+09:00] Seoul',
4996
		'Australia/Adelaide' => '[UTC+09:30] Adelaide',
4997
		'Australia/Darwin' => '[UTC+09:30] Darwin',
4998
		'Australia/Brisbane' => '[UTC+10:00] Brisbane, Guam',
4999
		'Australia/Sydney' => '[UTC+10:00] Sydney, Hobart',
5000
		'Asia/Yakutsk' => '[UTC+10:00] Yakutsk',
5001
		'Australia/Lord_Howe' => '[UTC+10:30] Lord Howe Island',
5002
		'Asia/Vladivostok' => '[UTC+11:00] Vladivostok',
5003
		'Pacific/Noumea' => '[UTC+11:00] Solomon Islands, New Caledonia',
5004
		'Pacific/Norfolk' => '[UTC+11:30] Norfolk Island',
5005
		'Pacific/Auckland' => '[UTC+12:00] Auckland, Wellington',
5006
		'Asia/Magadan' => '[UTC+12:00] Magadan, Kamchatka, Anadyr',
5007
		'Pacific/Fiji' => '[UTC+12:00] Fiji',
5008
		'Pacific/Majuro' => '[UTC+12:00] Marshall Islands',
5009
		'Pacific/Chatham' => '[UTC+12:45] Chatham Islands',
5010
		'Pacific/Tongatapu' => '[UTC+13:00] Nuku\'alofa',
5011
		'Pacific/Kiritimati' => '[UTC+14:00] Kiritimati',
5012
	);
5013
}
5014
5015
/**
5016
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
5017
 * @return binary The IP address in binary or false
5018
 */
5019
function inet_ptod($ip_address)
5020
{
5021
	if (!isValidIP($ip_address))
5022
		return $ip_address;
5023
5024
	$bin = inet_pton($ip_address);
5025
	return $bin;
5026
}
5027
5028
/**
5029
 * @param binary $bin An IP address in IPv4, IPv6 (Either string (postgresql) or binary (other databases))
5030
 * @return string The IP address in presentation format or false on error
5031
 */
5032
function inet_dtop($bin)
5033
{
5034
	if(empty($bin))
5035
		return '';
5036
5037
	global $db_type;
5038
5039
	if ($db_type == 'postgresql')
5040
		return $bin;
5041
5042
	$ip_address = inet_ntop($bin);
5043
5044
	return $ip_address;
5045
}
5046
5047
/**
5048
 * Safe serialize() and unserialize() replacements
5049
 *
5050
 * @license Public Domain
5051
 *
5052
 * @author anthon (dot) pang (at) gmail (dot) com
5053
 */
5054
5055
/**
5056
 * Safe serialize() replacement. Recursive
5057
 * - output a strict subset of PHP's native serialized representation
5058
 * - does not serialize objects
5059
 *
5060
 * @param mixed $value
5061
 * @return string
5062
 */
5063
function _safe_serialize($value)
5064
{
5065
	if(is_null($value))
5066
		return 'N;';
5067
5068
	if(is_bool($value))
5069
		return 'b:'. (int) $value .';';
5070
5071
	if(is_int($value))
5072
		return 'i:'. $value .';';
5073
5074
	if(is_float($value))
5075
		return 'd:'. str_replace(',', '.', $value) .';';
5076
5077
	if(is_string($value))
5078
		return 's:'. strlen($value) .':"'. $value .'";';
5079
5080
	if(is_array($value))
5081
	{
5082
		$out = '';
5083
		foreach($value as $k => $v)
5084
			$out .= _safe_serialize($k) . _safe_serialize($v);
5085
5086
		return 'a:'. count($value) .':{'. $out .'}';
5087
	}
5088
5089
	// safe_serialize cannot serialize resources or objects.
5090
	return false;
5091
}
5092
/**
5093
 * Wrapper for _safe_serialize() that handles exceptions and multibyte encoding issues.
5094
 *
5095
 * @param mixed $value
5096
 * @return string
5097
 */
5098 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...
5099
{
5100
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5101
	if (function_exists('mb_internal_encoding') &&
5102
		(((int) ini_get('mbstring.func_overload')) & 2))
5103
	{
5104
		$mbIntEnc = mb_internal_encoding();
5105
		mb_internal_encoding('ASCII');
5106
	}
5107
5108
	$out = _safe_serialize($value);
5109
5110
	if (isset($mbIntEnc))
5111
		mb_internal_encoding($mbIntEnc);
5112
5113
	return $out;
5114
}
5115
5116
/**
5117
 * Safe unserialize() replacement
5118
 * - accepts a strict subset of PHP's native serialized representation
5119
 * - does not unserialize objects
5120
 *
5121
 * @param string $str
5122
 * @return mixed
5123
 * @throw Exception if $str is malformed or contains unsupported types (e.g., resources, objects)
5124
 */
5125
function _safe_unserialize($str)
5126
{
5127
	// Input  is not a string.
5128
	if(empty($str) || !is_string($str))
5129
		return false;
5130
5131
	$stack = array();
5132
	$expected = array();
5133
5134
	/*
5135
	 * states:
5136
	 *   0 - initial state, expecting a single value or array
5137
	 *   1 - terminal state
5138
	 *   2 - in array, expecting end of array or a key
5139
	 *   3 - in array, expecting value or another array
5140
	 */
5141
	$state = 0;
5142
	while($state != 1)
5143
	{
5144
		$type = isset($str[0]) ? $str[0] : '';
5145
		if($type == '}')
5146
			$str = substr($str, 1);
5147
5148
		else if($type == 'N' && $str[1] == ';')
5149
		{
5150
			$value = null;
5151
			$str = substr($str, 2);
5152
		}
5153
		else if($type == 'b' && preg_match('/^b:([01]);/', $str, $matches))
5154
		{
5155
			$value = $matches[1] == '1' ? true : false;
5156
			$str = substr($str, 4);
5157
		}
5158
		else if($type == 'i' && preg_match('/^i:(-?[0-9]+);(.*)/s', $str, $matches))
5159
		{
5160
			$value = (int)$matches[1];
5161
			$str = $matches[2];
5162
		}
5163
		else if($type == 'd' && preg_match('/^d:(-?[0-9]+\.?[0-9]*(E[+-][0-9]+)?);(.*)/s', $str, $matches))
5164
		{
5165
			$value = (float)$matches[1];
5166
			$str = $matches[3];
5167
		}
5168
		else if($type == 's' && preg_match('/^s:([0-9]+):"(.*)/s', $str, $matches) && substr($matches[2], (int)$matches[1], 2) == '";')
5169
		{
5170
			$value = substr($matches[2], 0, (int)$matches[1]);
5171
			$str = substr($matches[2], (int)$matches[1] + 2);
5172
		}
5173
		else if($type == 'a' && preg_match('/^a:([0-9]+):{(.*)/s', $str, $matches))
5174
		{
5175
			$expectedLength = (int)$matches[1];
5176
			$str = $matches[2];
5177
		}
5178
5179
		// Object or unknown/malformed type.
5180
		else
5181
			return false;
5182
5183
		switch($state)
5184
		{
5185
			case 3: // In array, expecting value or another array.
5186
				if($type == 'a')
5187
				{
5188
					$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...
5189
					$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...
5190
					$list = &$list[$key];
5191
					$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...
5192
					$state = 2;
5193
					break;
5194
				}
5195
				if($type != '}')
5196
				{
5197
					$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...
5198
					$state = 2;
5199
					break;
5200
				}
5201
5202
				// Missing array value.
5203
				return false;
5204
5205
			case 2: // in array, expecting end of array or a key
5206
				if($type == '}')
5207
				{
5208
					// Array size is less than expected.
5209
					if(count($list) < end($expected))
5210
						return false;
5211
5212
					unset($list);
5213
					$list = &$stack[count($stack)-1];
5214
					array_pop($stack);
5215
5216
					// Go to terminal state if we're at the end of the root array.
5217
					array_pop($expected);
5218
5219
					if(count($expected) == 0)
5220
						$state = 1;
5221
5222
					break;
5223
				}
5224
5225
				if($type == 'i' || $type == 's')
5226
				{
5227
					// Array size exceeds expected length.
5228
					if(count($list) >= end($expected))
5229
						return false;
5230
5231
					$key = $value;
5232
					$state = 3;
5233
					break;
5234
				}
5235
5236
				// Illegal array index type.
5237
				return false;
5238
5239
			// Expecting array or value.
5240
			case 0:
5241
				if($type == 'a')
5242
				{
5243
					$data = array();
5244
					$list = &$data;
5245
					$expected[] = $expectedLength;
5246
					$state = 2;
5247
					break;
5248
				}
5249
5250
				if($type != '}')
5251
				{
5252
					$data = $value;
5253
					$state = 1;
5254
					break;
5255
				}
5256
5257
				// Not in array.
5258
				return false;
5259
		}
5260
	}
5261
5262
	// Trailing data in input.
5263
	if(!empty($str))
5264
		return false;
5265
5266
	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...
5267
}
5268
5269
/**
5270
 * Wrapper for _safe_unserialize() that handles exceptions and multibyte encoding issue
5271
 *
5272
 * @param string $str
5273
 * @return mixed
5274
 */
5275 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...
5276
{
5277
	// Make sure we use the byte count for strings even when strlen() is overloaded by mb_strlen()
5278
	if (function_exists('mb_internal_encoding') &&
5279
		(((int) ini_get('mbstring.func_overload')) & 0x02))
5280
	{
5281
		$mbIntEnc = mb_internal_encoding();
5282
		mb_internal_encoding('ASCII');
5283
	}
5284
5285
	$out = _safe_unserialize($str);
5286
5287
	if (isset($mbIntEnc))
5288
		mb_internal_encoding($mbIntEnc);
5289
5290
	return $out;
5291
}
5292
5293
/**
5294
 * Tries different modes to make file/dirs writable. Wrapper function for chmod()
5295
5296
 * @param string $file The file/dir full path.
5297
 * @param int $value Not needed, added for legacy reasons.
5298
 * @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.
5299
 */
5300
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...
5301
{
5302
	// No file? no checks!
5303
	if (empty($file))
5304
		return false;
5305
5306
	// Already writable?
5307
	if (is_writable($file))
5308
		return true;
5309
5310
	// Do we have a file or a dir?
5311
	$isDir = is_dir($file);
5312
	$isWritable = false;
5313
5314
	// Set different modes.
5315
	$chmodValues = $isDir ? array(0750, 0755, 0775, 0777) : array(0644, 0664, 0666);
5316
5317
	foreach($chmodValues as $val)
5318
	{
5319
		// If it's writable, break out of the loop.
5320
		if (is_writable($file))
5321
		{
5322
			$isWritable = true;
5323
			break;
5324
		}
5325
5326
		else
5327
			@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...
5328
	}
5329
5330
	return $isWritable;
5331
}
5332
5333
/**
5334
 * Wrapper function for json_decode() with error handling.
5335
5336
 * @param string $json The string to decode.
5337
 * @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.
5338
 * @param bool $logIt To specify if the error will be logged if theres any.
5339
 * @return array Either an empty array or the decoded data as an array.
5340
 */
5341
function smf_json_decode($json, $returnAsArray = false, $logIt = true)
5342
{
5343
	global $txt;
5344
5345
	// Come on...
5346
	if (empty($json) || !is_string($json))
5347
		return array();
5348
5349
	$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...
5350
	$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...
5351
5352
	$returnArray = @json_decode($json, $returnAsArray);
5353
5354
	// PHP 5.3 so no json_last_error_msg()
5355
	switch(json_last_error())
5356
	{
5357
		case JSON_ERROR_NONE:
5358
			$jsonError = false;
5359
			break;
5360
		case JSON_ERROR_DEPTH:
5361
			$jsonError =  'JSON_ERROR_DEPTH';
5362
			break;
5363
		case JSON_ERROR_STATE_MISMATCH:
5364
			$jsonError = 'JSON_ERROR_STATE_MISMATCH';
5365
			break;
5366
		case JSON_ERROR_CTRL_CHAR:
5367
			$jsonError = 'JSON_ERROR_CTRL_CHAR';
5368
			break;
5369
		case JSON_ERROR_SYNTAX:
5370
			$jsonError = 'JSON_ERROR_SYNTAX';
5371
			break;
5372
		case JSON_ERROR_UTF8:
5373
			$jsonError = 'JSON_ERROR_UTF8';
5374
			break;
5375
		default:
5376
			$jsonError = 'unknown';
5377
			break;
5378
	}
5379
5380
	// Something went wrong!
5381
	if (!empty($jsonError) && $logIt)
5382
	{
5383
		// Being a wrapper means we lost our smf_error_handler() privileges :(
5384
		$jsonDebug = debug_backtrace();
5385
		$jsonDebug = $jsonDebug[0];
5386
		loadLanguage('Errors');
5387
5388
		if (!empty($jsonDebug))
5389
			log_error($txt['json_'. $jsonError], 'critical', $jsonDebug['file'], $jsonDebug['line']);
5390
5391
		else
5392
			log_error($txt['json_'. $jsonError], 'critical');
5393
5394
		// Everyone expects an array.
5395
		return array();
5396
	}
5397
5398
	return $returnArray;
5399
}
5400
5401
/**
5402
 * Check the given String if he is a valid IPv4 or IPv6
5403
 * return true or false
5404
 */
5405
function isValidIP($IPString)
5406
{
5407
	return filter_var($IPString, FILTER_VALIDATE_IP) !== false;
5408
}
5409
5410
/**
5411
 * Outputs a response.
5412
 * It assumes the data is already a string.
5413
 * @param string $data The data to print
5414
 * @param string $type The content type. Defaults to Json.
5415
 * @return void
5416
 */
5417
function smf_serverResponse($data = '', $type = 'Content-Type: application/json')
5418
{
5419
	global $db_show_debug, $modSettings;
5420
5421
	// Defensive programming anyone?
5422
	if (empty($data))
5423
		return false;
5424
5425
	// Don't need extra stuff...
5426
	$db_show_debug = false;
5427
5428
	// Kill anything else.
5429
	ob_end_clean();
5430
5431
	if (!empty($modSettings['CompressedOutput']))
5432
		@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...
5433
5434
	else
5435
		ob_start();
5436
5437
	// Set the header.
5438
	header($type);
5439
5440
	// Echo!
5441
	echo $data;
5442
5443
	// Done.
5444
	obExit(false);
5445
}
5446
5447
?>
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...