Completed
Push — release-2.1 ( 3ac280...d0dbee )
by Mert
07:21
created

Subs.php ➔ custMinify()   F

Complexity

Conditions 19
Paths 544

Size

Total Lines 91
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 19
eloc 44
c 1
b 0
f 1
nc 544
nop 3
dl 0
loc 91
rs 2.6661

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to, well, everything.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2016 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 3
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Update some basic statistics.
21
 *
22
 * 'member' statistic updates the latest member, the total member
23
 *  count, and the number of unapproved members.
24
 * 'member' also only counts approved members when approval is on, but
25
 *  is much more efficient with it off.
26
 *
27
 * 'message' changes the total number of messages, and the
28
 *  highest message id by id_msg - which can be parameters 1 and 2,
29
 *  respectively.
30
 *
31
 * 'topic' updates the total number of topics, or if parameter1 is true
32
 *  simply increments them.
33
 *
34
 * 'subject' updates the log_search_subjects in the event of a topic being
35
 *  moved, removed or split.  parameter1 is the topicid, parameter2 is the new subject
36
 *
37
 * 'postgroups' case updates those members who match condition's
38
 *  post-based membergroups in the database (restricted by parameter1).
39
 *
40
 * @param string $type Stat type - can be 'member', 'message', 'topic', 'subject' or 'postgroups'
41
 * @param mixed $parameter1 A parameter for updating the stats
42
 * @param mixed $parameter2 A 2nd parameter for updating the stats
43
 */
44
function updateStats($type, $parameter1 = null, $parameter2 = null)
45
{
46
	global $modSettings, $smcFunc;
47
48
	switch ($type)
49
	{
50
		case 'member':
51
			$changes = array(
52
				'memberlist_updated' => time(),
53
			);
54
55
			// #1 latest member ID, #2 the real name for a new registration.
56
			if (is_numeric($parameter1))
57
			{
58
				$changes['latestMember'] = $parameter1;
59
				$changes['latestRealName'] = $parameter2;
60
61
				updateSettings(array('totalMembers' => true), true);
62
			}
63
64
			// We need to calculate the totals.
65
			else
66
			{
67
				// Update the latest activated member (highest id_member) and count.
68
				$result = $smcFunc['db_query']('', '
69
				SELECT COUNT(*), MAX(id_member)
70
				FROM {db_prefix}members
71
				WHERE is_activated = {int:is_activated}',
72
					array(
73
						'is_activated' => 1,
74
					)
75
				);
76
				list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result);
77
				$smcFunc['db_free_result']($result);
78
79
				// Get the latest activated member's display name.
80
				$result = $smcFunc['db_query']('', '
81
				SELECT real_name
82
				FROM {db_prefix}members
83
				WHERE id_member = {int:id_member}
84
				LIMIT 1',
85
					array(
86
						'id_member' => (int) $changes['latestMember'],
87
					)
88
				);
89
				list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result);
90
				$smcFunc['db_free_result']($result);
91
92
				if (!empty($modSettings['registration_method']))
93
				{
94
					// Are we using registration approval?
95
					if ($modSettings['registration_method'] == 2 || !empty($modSettings['approveAccountDeletion']))
96
					{
97
						// Update the amount of members awaiting approval
98
						$result = $smcFunc['db_query']('', '
99
						SELECT COUNT(*)
100
						FROM {db_prefix}members
101
						WHERE is_activated IN ({array_int:activation_status})',
102
							array(
103
								'activation_status' => array(3, 4),
104
							)
105
						);
106
						list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result);
107
						$smcFunc['db_free_result']($result);
108
					}
109
110
					// What about unapproved COPPA registrations?
111 View Code Duplication
					if (!empty($modSettings['coppaType']) && $modSettings['coppaType'] != 1)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
112
					{
113
						$result = $smcFunc['db_query']('', '
114
						SELECT COUNT(*)
115
						FROM {db_prefix}members
116
						WHERE is_activated = {int:coppa_approval}',
117
							array(
118
								'coppa_approval' => 5,
119
							)
120
						);
121
						list ($coppa_approvals) = $smcFunc['db_fetch_row']($result);
122
						$smcFunc['db_free_result']($result);
123
124
						// Add this to the number of unapproved members
125
						if (!empty($changes['unapprovedMembers']))
126
							$changes['unapprovedMembers'] += $coppa_approvals;
127
						else
128
							$changes['unapprovedMembers'] = $coppa_approvals;
129
					}
130
				}
131
			}
132
			updateSettings($changes);
133
			break;
134
135
		case 'message':
136
			if ($parameter1 === true && $parameter2 !== null)
137
				updateSettings(array('totalMessages' => true, 'maxMsgID' => $parameter2), true);
138
			else
139
			{
140
				// SUM and MAX on a smaller table is better for InnoDB tables.
141
				$result = $smcFunc['db_query']('', '
142
				SELECT SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id
143
				FROM {db_prefix}boards
144
				WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
145
					AND id_board != {int:recycle_board}' : ''),
146
					array(
147
						'recycle_board' => isset($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
148
						'blank_redirect' => '',
149
					)
150
				);
151
				$row = $smcFunc['db_fetch_assoc']($result);
152
				$smcFunc['db_free_result']($result);
153
154
				updateSettings(array(
155
					'totalMessages' => $row['total_messages'] === null ? 0 : $row['total_messages'],
156
					'maxMsgID' => $row['max_msg_id'] === null ? 0 : $row['max_msg_id']
157
				));
158
			}
159
			break;
160
161
		case 'subject':
162
			// Remove the previous subject (if any).
163
			$smcFunc['db_query']('', '
164
			DELETE FROM {db_prefix}log_search_subjects
165
			WHERE id_topic = {int:id_topic}',
166
				array(
167
					'id_topic' => (int) $parameter1,
168
				)
169
			);
170
171
			// Insert the new subject.
172
			if ($parameter2 !== null)
173
			{
174
				$parameter1 = (int) $parameter1;
175
				$parameter2 = text2words($parameter2);
176
177
				$inserts = array();
178
				foreach ($parameter2 as $word)
179
					$inserts[] = array($word, $parameter1);
180
181 View Code Duplication
				if (!empty($inserts))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
182
					$smcFunc['db_insert']('ignore',
183
						'{db_prefix}log_search_subjects',
184
						array('word' => 'string', 'id_topic' => 'int'),
185
						$inserts,
186
						array('word', 'id_topic')
187
					);
188
			}
189
			break;
190
191
		case 'topic':
192
			if ($parameter1 === true)
193
				updateSettings(array('totalTopics' => true), true);
194
			else
195
			{
196
				// Get the number of topics - a SUM is better for InnoDB tables.
197
				// We also ignore the recycle bin here because there will probably be a bunch of one-post topics there.
198
				$result = $smcFunc['db_query']('', '
199
				SELECT SUM(num_topics + unapproved_topics) AS total_topics
200
				FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
201
				WHERE id_board != {int:recycle_board}' : ''),
202
					array(
203
						'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
204
					)
205
				);
206
				$row = $smcFunc['db_fetch_assoc']($result);
207
				$smcFunc['db_free_result']($result);
208
209
				updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics']));
210
			}
211
			break;
212
213
		case 'postgroups':
214
			// Parameter two is the updated columns: we should check to see if we base groups off any of these.
215
			if ($parameter2 !== null && !in_array('posts', $parameter2))
216
				return;
217
218
			$postgroups = cache_get_data('updateStats:postgroups', 360);
219
			if ($postgroups == null || $parameter1 == null)
220
			{
221
				// Fetch the postgroups!
222
				$request = $smcFunc['db_query']('', '
223
				SELECT id_group, min_posts
224
				FROM {db_prefix}membergroups
225
				WHERE min_posts != {int:min_posts}',
226
					array(
227
						'min_posts' => -1,
228
					)
229
				);
230
				$postgroups = array();
231
				while ($row = $smcFunc['db_fetch_assoc']($request))
232
					$postgroups[$row['id_group']] = $row['min_posts'];
233
				$smcFunc['db_free_result']($request);
234
235
				// Sort them this way because if it's done with MySQL it causes a filesort :(.
236
				arsort($postgroups);
237
238
				cache_put_data('updateStats:postgroups', $postgroups, 360);
239
			}
240
241
			// Oh great, they've screwed their post groups.
242
			if (empty($postgroups))
243
				return;
244
245
			// Set all membergroups from most posts to least posts.
246
			$conditions = '';
247
			$lastMin = 0;
248
			foreach ($postgroups as $id => $min_posts)
0 ignored issues
show
Bug introduced by
The expression $postgroups of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
249
			{
250
				$conditions .= '
251
					WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id;
252
				$lastMin = $min_posts;
253
			}
254
255
			// A big fat CASE WHEN... END is faster than a zillion UPDATE's ;).
256
			$smcFunc['db_query']('', '
257
			UPDATE {db_prefix}members
258
			SET id_post_group = CASE ' . $conditions . '
259
					ELSE 0
260
				END' . ($parameter1 != null ? '
261
			WHERE ' . (is_array($parameter1) ? 'id_member IN ({array_int:members})' : 'id_member = {int:members}') : ''),
262
				array(
263
					'members' => $parameter1,
264
				)
265
			);
266
			break;
267
268
		default:
269
			trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE);
270
	}
271
}
272
273
/**
274
 * Updates the columns in the members table.
275
 * Assumes the data has been htmlspecialchar'd.
276
 * this function should be used whenever member data needs to be
277
 * updated in place of an UPDATE query.
278
 *
279
 * id_member is either an int or an array of ints to be updated.
280
 *
281
 * data is an associative array of the columns to be updated and their respective values.
282
 * any string values updated should be quoted and slashed.
283
 *
284
 * the value of any column can be '+' or '-', which mean 'increment'
285
 * and decrement, respectively.
286
 *
287
 * if the member's post number is updated, updates their post groups.
288
 *
289
 * @param mixed $members An array of member IDs, null to update this for all members or the ID of a single member
290
 * @param array $data The info to update for the members
291
 */
292
function updateMemberData($members, $data)
293
{
294
	global $modSettings, $user_info, $smcFunc;
295
296
	$parameters = array();
297
	if (is_array($members))
298
	{
299
		$condition = 'id_member IN ({array_int:members})';
300
		$parameters['members'] = $members;
301
	}
302
	elseif ($members === null)
303
		$condition = '1=1';
304
	else
305
	{
306
		$condition = 'id_member = {int:member}';
307
		$parameters['member'] = $members;
308
	}
309
310
	// Everything is assumed to be a string unless it's in the below.
311
	$knownInts = array(
312
		'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages',
313
		'new_pm', 'pm_prefs', 'gender', 'show_online', 'pm_receive_from', 'alerts',
314
		'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning',
315
	);
316
	$knownFloats = array(
317
		'time_offset',
318
	);
319
320
	if (!empty($modSettings['integrate_change_member_data']))
321
	{
322
		// Only a few member variables are really interesting for integration.
323
		$integration_vars = array(
324
			'member_name',
325
			'real_name',
326
			'email_address',
327
			'id_group',
328
			'gender',
329
			'birthdate',
330
			'website_title',
331
			'website_url',
332
			'location',
333
			'time_format',
334
			'time_offset',
335
			'avatar',
336
			'lngfile',
337
		);
338
		$vars_to_integrate = array_intersect($integration_vars, array_keys($data));
339
340
		// Only proceed if there are any variables left to call the integration function.
341
		if (count($vars_to_integrate) != 0)
342
		{
343
			// Fetch a list of member_names if necessary
344
			if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members)))
345
				$member_names = array($user_info['username']);
346
			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-suppported 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_equals_content',
1087
				'parameters' => array(
1088
					'name' => array('optional' => true),
1089
				),
1090
				'content' => '$1',
1091
				'validate' => function (&$tag, &$data, $disabled) use ($modSettings, $context, $sourcedir, $txt)
1092
				{
1093
					$returnContext = '';
1094
1095
					// BBC or the entire attachments feature is disabled
1096
					if (empty($modSettings['attachmentEnable']) || !empty($disabled['attach']))
1097
						return $data;
1098
1099
					// Save the attach ID.
1100
					$attachID = is_array($data) ? $data[0] : $data;
1101
1102
					// Kinda need this.
1103
					require_once($sourcedir . '/Subs-Attachments.php');
1104
1105
					$currentAttachment = parseAttachBBC($attachID);
1106
1107
					// parseAttachBBC will return a string ($txt key) rather than diying with a fatal_error. Up to you to decide what to do.
1108
					if (is_string($currentAttachment))
1109
						return $data[0] = !empty($txt[$currentAttachment]) ? $txt[$currentAttachment] : $currentAttachment;
1110
1111
					if (!empty($currentAttachment['is_image']))
1112
					{
1113
						if ($currentAttachment['thumbnail']['has_thumb'])
1114
							$returnContext .= '
1115
													<a href="'. $currentAttachment['href']. ';image" id="link_'. $currentAttachment['id']. '" onclick="'. $currentAttachment['thumbnail']['javascript']. '"><img src="'. $currentAttachment['thumbnail']['href']. '" alt="" id="thumb_'. $currentAttachment['id']. '"></a>';
1116
						else
1117
							$returnContext .= '
1118
													<img src="' . $currentAttachment['href'] . ';image" alt="" width="' . $currentAttachment['width'] . '" height="' . $currentAttachment['height'] . '"/>';
1119
					}
1120
1121
					// No image. Show a link.
1122
					else
1123
						$returnContext .= $currentAttachment['link'];
1124
1125
					// Gotta append what we just did.
1126
					$data[0] = $returnContext;
1127
				},
1128
			),
1129
			array(
1130
				'tag' => 'b',
1131
				'before' => '<b>',
1132
				'after' => '</b>',
1133
			),
1134
			array(
1135
				'tag' => 'center',
1136
				'before' => '<div class="centertext">',
1137
				'after' => '</div>',
1138
				'block_level' => true,
1139
			),
1140
			array(
1141
				'tag' => 'code',
1142
				'type' => 'unparsed_content',
1143
				'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>',
1144
				// @todo Maybe this can be simplified?
1145
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1146
				{
1147
					if (!isset($disabled['code']))
1148
					{
1149
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
1150
1151 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...
1152
						{
1153
							// Do PHP code coloring?
1154
							if ($php_parts[$php_i] != '&lt;?php')
1155
								continue;
1156
1157
							$php_string = '';
1158
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1159
							{
1160
								$php_string .= $php_parts[$php_i];
1161
								$php_parts[$php_i++] = '';
1162
							}
1163
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1164
						}
1165
1166
						// Fix the PHP code stuff...
1167
						$data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1168
						$data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);
1169
1170
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1171
						if ($context['browser']['is_opera'])
1172
							$data .= '&nbsp;';
1173
					}
1174
				},
1175
				'block_level' => true,
1176
			),
1177
			array(
1178
				'tag' => 'code',
1179
				'type' => 'unparsed_equals_content',
1180
				'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>',
1181
				// @todo Maybe this can be simplified?
1182
				'validate' => isset($disabled['code']) ? null : function (&$tag, &$data, $disabled) use ($context)
1183
				{
1184
					if (!isset($disabled['code']))
1185
					{
1186
						$php_parts = preg_split('~(&lt;\?php|\?&gt;)~', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);
1187
1188 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...
1189
						{
1190
							// Do PHP code coloring?
1191
							if ($php_parts[$php_i] != '&lt;?php')
1192
								continue;
1193
1194
							$php_string = '';
1195
							while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != '?&gt;')
1196
							{
1197
								$php_string .= $php_parts[$php_i];
1198
								$php_parts[$php_i++] = '';
1199
							}
1200
							$php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
1201
						}
1202
1203
						// Fix the PHP code stuff...
1204
						$data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode('', $php_parts));
1205
						$data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]);
1206
1207
						// Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
1208
						if ($context['browser']['is_opera'])
1209
							$data[0] .= '&nbsp;';
1210
					}
1211
				},
1212
				'block_level' => true,
1213
			),
1214
			array(
1215
				'tag' => 'color',
1216
				'type' => 'unparsed_equals',
1217
				'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]?)\))\]',
1218
				'before' => '<span style="color: $1;" class="bbc_color">',
1219
				'after' => '</span>',
1220
			),
1221
			array(
1222
				'tag' => 'email',
1223
				'type' => 'unparsed_content',
1224
				'content' => '<a href="mailto:$1" class="bbc_email">$1</a>',
1225
				// @todo Should this respect guest_hideContacts?
1226
				'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...
1227
				{
1228
					$data = strtr($data, array('<br>' => ''));
1229
				},
1230
			),
1231
			array(
1232
				'tag' => 'email',
1233
				'type' => 'unparsed_equals',
1234
				'before' => '<a href="mailto:$1" class="bbc_email">',
1235
				'after' => '</a>',
1236
				// @todo Should this respect guest_hideContacts?
1237
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1238
				'disabled_after' => ' ($1)',
1239
			),
1240
			array(
1241
				'tag' => 'flash',
1242
				'type' => 'unparsed_commas_content',
1243
				'test' => '\d+,\d+\]',
1244
				'content' => '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never">',
1245
				'validate' => function (&$tag, &$data, $disabled)
1246
				{
1247
					if (isset($disabled['url']))
1248
						$tag['content'] = '$1';
1249
					elseif (strpos($data[0], 'http://') !== 0 && strpos($data[0], 'https://') !== 0)
1250
						$data[0] = 'http://' . $data[0];
1251
				},
1252
				'disabled_content' => '<a href="$1" target="_blank" class="new_win">$1</a>',
1253
			),
1254
			array(
1255
				'tag' => 'font',
1256
				'type' => 'unparsed_equals',
1257
				'test' => '[A-Za-z0-9_,\-\s]+?\]',
1258
				'before' => '<span style="font-family: $1;" class="bbc_font">',
1259
				'after' => '</span>',
1260
			),
1261
			array(
1262
				'tag' => 'html',
1263
				'type' => 'unparsed_content',
1264
				'content' => '$1',
1265
				'block_level' => true,
1266
				'disabled_content' => '$1',
1267
			),
1268
			array(
1269
				'tag' => 'hr',
1270
				'type' => 'closed',
1271
				'content' => '<hr>',
1272
				'block_level' => true,
1273
			),
1274
			array(
1275
				'tag' => 'i',
1276
				'before' => '<i>',
1277
				'after' => '</i>',
1278
			),
1279
			array(
1280
				'tag' => 'img',
1281
				'type' => 'unparsed_content',
1282
				'parameters' => array(
1283
					'alt' => array('optional' => true),
1284
					'title' => array('optional' => true),
1285
					'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
1286
					'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
1287
				),
1288
				'content' => '<img src="$1" alt="{alt}" title="{title}"{width}{height} class="bbc_img resized">',
1289 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...
1290
				{
1291
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1292
1293
					$data = strtr($data, array('<br>' => ''));
1294
					if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1295
						$data = 'http://' . $data;
1296
1297
					if (substr($data, 0, 8) != 'https://' && $image_proxy_enabled)
1298
						$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1299
				},
1300
				'disabled_content' => '($1)',
1301
			),
1302
			array(
1303
				'tag' => 'img',
1304
				'type' => 'unparsed_content',
1305
				'content' => '<img src="$1" alt="" class="bbc_img">',
1306 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...
1307
				{
1308
					global $image_proxy_enabled, $image_proxy_secret, $boardurl;
1309
1310
					$data = strtr($data, array('<br>' => ''));
1311
					if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1312
						$data = 'http://' . $data;
1313
1314
					if (substr($data, 0, 8) != 'https://' && $image_proxy_enabled)
1315
						$data = $boardurl . '/proxy.php?request=' . urlencode($data) . '&hash=' . md5($data . $image_proxy_secret);
1316
				},
1317
				'disabled_content' => '($1)',
1318
			),
1319
			array(
1320
				'tag' => 'iurl',
1321
				'type' => 'unparsed_content',
1322
				'content' => '<a href="$1" class="bbc_link">$1</a>',
1323 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...
1324
				{
1325
					$data = strtr($data, array('<br>' => ''));
1326
					if (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1327
						$data = 'http://' . $data;
1328
				},
1329
			),
1330
			array(
1331
				'tag' => 'iurl',
1332
				'type' => 'unparsed_equals',
1333
				'before' => '<a href="$1" class="bbc_link">',
1334
				'after' => '</a>',
1335
				'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...
1336
				{
1337
					if (substr($data, 0, 1) == '#')
1338
						$data = '#post_' . substr($data, 1);
1339
					elseif (strpos($data, 'http://') !== 0 && strpos($data, 'https://') !== 0)
1340
						$data = 'http://' . $data;
1341
				},
1342
				'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
1343
				'disabled_after' => ' ($1)',
1344
			),
1345
			array(
1346
				'tag' => 'left',
1347
				'before' => '<div style="text-align: left;">',
1348
				'after' => '</div>',
1349
				'block_level' => true,
1350
			),
1351
			array(
1352
				'tag' => 'li',
1353
				'before' => '<li>',
1354
				'after' => '</li>',
1355
				'trim' => 'outside',
1356
				'require_parents' => array('list'),
1357
				'block_level' => true,
1358
				'disabled_before' => '',
1359
				'disabled_after' => '<br>',
1360
			),
1361
			array(
1362
				'tag' => 'list',
1363
				'before' => '<ul class="bbc_list">',
1364
				'after' => '</ul>',
1365
				'trim' => 'inside',
1366
				'require_children' => array('li', 'list'),
1367
				'block_level' => true,
1368
			),
1369
			array(
1370
				'tag' => 'list',
1371
				'parameters' => array(
1372
					'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)'),
1373
				),
1374
				'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
1375
				'after' => '</ul>',
1376
				'trim' => 'inside',
1377
				'require_children' => array('li'),
1378
				'block_level' => true,
1379
			),
1380
			array(
1381
				'tag' => 'ltr',
1382
				'before' => '<bdo dir="ltr">',
1383
				'after' => '</bdo>',
1384
				'block_level' => true,
1385
			),
1386
			array(
1387
				'tag' => 'me',
1388
				'type' => 'unparsed_equals',
1389
				'before' => '<div class="meaction">* $1 ',
1390
				'after' => '</div>',
1391
				'quoted' => 'optional',
1392
				'block_level' => true,
1393
				'disabled_before' => '/me ',
1394
				'disabled_after' => '<br>',
1395
			),
1396
			array(
1397
				'tag' => 'member',
1398
				'type' => 'unparsed_equals',
1399
				'before' => '<a href="' . $scripturl . '?action=profile;u=$1" class="mention">@',
1400
				'after' => '</a>',
1401
			),
1402
			array(
1403
				'tag' => 'nobbc',
1404
				'type' => 'unparsed_content',
1405
				'content' => '$1',
1406
			),
1407
			array(
1408
				'tag' => 'php',
1409
				'type' => 'unparsed_content',
1410
				'content' => '<span class="phpcode">$1</span>',
1411
				'validate' => isset($disabled['php']) ? null : function (&$tag, &$data, $disabled)
1412
				{
1413
					if (!isset($disabled['php']))
1414
					{
1415
						$add_begin = substr(trim($data), 0, 5) != '&lt;?';
1416
						$data = highlight_php_code($add_begin ? '&lt;?php ' . $data . '?&gt;' : $data);
1417
						if ($add_begin)
1418
							$data = preg_replace(array('~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~', '~\?&gt;((?:</(font|span)>)*)$~'), '$1', $data, 2);
1419
					}
1420
				},
1421
				'block_level' => false,
1422
				'disabled_content' => '$1',
1423
			),
1424
			array(
1425
				'tag' => 'pre',
1426
				'before' => '<pre>',
1427
				'after' => '</pre>',
1428
			),
1429
			array(
1430
				'tag' => 'quote',
1431
				'before' => '<blockquote><cite>' . $txt['quote'] . '</cite>',
1432
				'after' => '</blockquote>',
1433
				'trim' => 'both',
1434
				'block_level' => true,
1435
			),
1436
			array(
1437
				'tag' => 'quote',
1438
				'parameters' => array(
1439
					'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
1440
				),
1441
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1442
				'after' => '</blockquote>',
1443
				'trim' => 'both',
1444
				'block_level' => true,
1445
			),
1446
			array(
1447
				'tag' => 'quote',
1448
				'type' => 'parsed_equals',
1449
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': $1</cite>',
1450
				'after' => '</blockquote>',
1451
				'trim' => 'both',
1452
				'quoted' => 'optional',
1453
				// Don't allow everything to be embedded with the author name.
1454
				'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
1455
				'block_level' => true,
1456
			),
1457
			array(
1458
				'tag' => 'quote',
1459
				'parameters' => array(
1460
					'author' => array('match' => '([^<>]{1,192}?)'),
1461
					'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|msg=\d+?|action=profile;u=\d+)'),
1462
					'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
1463
				),
1464
				'before' => '<blockquote><cite><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></cite>',
1465
				'after' => '</blockquote>',
1466
				'trim' => 'both',
1467
				'block_level' => true,
1468
			),
1469
			array(
1470
				'tag' => 'quote',
1471
				'parameters' => array(
1472
					'author' => array('match' => '(.{1,192}?)'),
1473
				),
1474
				'before' => '<blockquote><cite>' . $txt['quote_from'] . ': {author}</cite>',
1475
				'after' => '</blockquote>',
1476
				'trim' => 'both',
1477
				'block_level' => true,
1478
			),
1479
			array(
1480
				'tag' => 'right',
1481
				'before' => '<div style="text-align: right;">',
1482
				'after' => '</div>',
1483
				'block_level' => true,
1484
			),
1485
			array(
1486
				'tag' => 'rtl',
1487
				'before' => '<bdo dir="rtl">',
1488
				'after' => '</bdo>',
1489
				'block_level' => true,
1490
			),
1491
			array(
1492
				'tag' => 's',
1493
				'before' => '<s>',
1494
				'after' => '</s>',
1495
			),
1496
			array(
1497
				'tag' => 'size',
1498
				'type' => 'unparsed_equals',
1499
				'test' => '([1-9][\d]?p[xt]|small(?:er)?|large[r]?|x[x]?-(?:small|large)|medium|(0\.[1-9]|[1-9](\.[\d][\d]?)?)?em)\]',
1500
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1501
				'after' => '</span>',
1502
			),
1503
			array(
1504
				'tag' => 'size',
1505
				'type' => 'unparsed_equals',
1506
				'test' => '[1-7]\]',
1507
				'before' => '<span style="font-size: $1;" class="bbc_size">',
1508
				'after' => '</span>',
1509
				'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...
1510
				{
1511
					$sizes = array(1 => 0.7, 2 => 1.0, 3 => 1.35, 4 => 1.45, 5 => 2.0, 6 => 2.65, 7 => 3.95);
1512
					$data = $sizes[$data] . 'em';
1513
				},
1514
			),
1515
			array(
1516
				'tag' => 'sub',
1517
				'before' => '<sub>',
1518
				'after' => '</sub>',
1519
			),
1520
			array(
1521
				'tag' => 'sup',
1522
				'before' => '<sup>',
1523
				'after' => '</sup>',
1524
			),
1525
			array(
1526
				'tag' => 'table',
1527
				'before' => '<table class="bbc_table">',
1528
				'after' => '</table>',
1529
				'trim' => 'inside',
1530
				'require_children' => array('tr'),
1531
				'block_level' => true,
1532
			),
1533
			array(
1534
				'tag' => 'td',
1535
				'before' => '<td>',
1536
				'after' => '</td>',
1537
				'require_parents' => array('tr'),
1538
				'trim' => 'outside',
1539
				'block_level' => true,
1540
				'disabled_before' => '',
1541
				'disabled_after' => '',
1542
			),
1543
			array(
1544
				'tag' => 'time',
1545
				'type' => 'unparsed_content',
1546
				'content' => '$1',
1547
				'validate' => function (&$tag, &$data, $disabled)
0 ignored issues
show
Unused Code introduced by
The parameter $disabled is not used and could be removed.

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

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

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

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

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