Completed
Push — development ( ac22bd...5236fa )
by Stephen
14:46
created

Subs.php ➔ elk_seed_generator()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 4
cp 0
crap 12
1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to, well, everything.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1
15
 *
16
 */
17
18
/**
19
 * Updates the settings table as well as $modSettings... only does one at a time if $update is true.
20
 *
21
 * What it does:
22
 *
23
 * - Updates both the settings table and $modSettings array.
24
 * - All of changeArray's indexes and values are assumed to have escaped apostrophes (')!
25
 * - If a variable is already set to what you want to change it to, that
26
 *   Variable will be skipped over; it would be unnecessary to reset.
27
 * - When update is true, UPDATEs will be used instead of REPLACE.
28
 * - When update is true, the value can be true or false to increment
29
 *  or decrement it, respectively.
30
 *
31
 * @param mixed[] $changeArray An associative array of what we're changing in 'setting' => 'value' format
32
 * @param bool $update Use an UPDATE query instead of a REPLACE query
33
 * @param bool $debug = false Not used at this time, see todo
34
 * @todo: add debugging features, $debug isn't used
35
 */
36
function updateSettings($changeArray, $update = false, $debug = false)
0 ignored issues
show
Unused Code introduced by
The parameter $debug 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...
37
{
38
	global $modSettings;
39
40
	$db = database();
41
	$cache = Cache::instance();
42
43
	if (empty($changeArray) || !is_array($changeArray))
44
		return;
45
46
	// In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs.
47
	if ($update)
48
	{
49
		foreach ($changeArray as $variable => $value)
50
		{
51
			$db->query('', '
52
				UPDATE {db_prefix}settings
53
				SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value}
54
				WHERE variable = {string:variable}',
55
				array(
56
					'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value),
57
					'variable' => $variable,
58
				)
59
			);
60
61
			$modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value);
62
		}
63
64
		// Clean out the cache and make sure the cobwebs are gone too.
65
		$cache->remove('modSettings');
66
67
		return;
68
	}
69
70
	$replaceArray = array();
71
	foreach ($changeArray as $variable => $value)
72
	{
73
		// Don't bother if it's already like that ;).
74
		if (isset($modSettings[$variable]) && $modSettings[$variable] == $value)
75
			continue;
76
		// If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it.
77
		elseif (!isset($modSettings[$variable]) && empty($value))
78
			continue;
79
80
		$replaceArray[] = array($variable, $value);
81
82
		$modSettings[$variable] = $value;
83
	}
84
85
	if (empty($replaceArray))
86
		return;
87
88
	$db->insert('replace',
89
		'{db_prefix}settings',
90
		array('variable' => 'string-255', 'value' => 'string-65534'),
91
		$replaceArray,
92
		array('variable')
93
	);
94 130
95
	// Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
96 130
	$cache->remove('modSettings');
97 130
}
98
99 130
/**
100 88
 * Deletes one setting from the settings table and takes care of $modSettings as well
101
 *
102
 * @param string|string[] $toRemove the setting or the settings to be removed
103 42
 */
104 88
function removeSettings($toRemove)
105 64
{
106
	global $modSettings;
107 64
108
	$db = database();
109 64
110 43
	if (empty($toRemove))
111
		return;
112 64
113 64
	if (!is_array($toRemove))
114
		$toRemove = array($toRemove);
115 43
116
	// Remove the setting from the db
117 64
	$db->query('', '
118 43
		DELETE FROM {db_prefix}settings
119
		WHERE variable IN ({array_string:setting_name})',
120
		array(
121 64
			'setting_name' => $toRemove,
122
		)
123 64
	);
124
125
	// Remove it from $modSettings now so it does not persist
126 106
	foreach ($toRemove as $setting)
127 106
		if (isset($modSettings[$setting]))
128
			unset($modSettings[$setting]);
129
130 106
	// Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
131 94
	Cache::instance()->remove('modSettings');
132
}
133 89
134
/**
135
 * Constructs a page list.
136 89
 *
137
 * What it does:
138 89
 *
139 72
 * - Builds the page list, e.g. 1 ... 6 7 [8] 9 10 ... 15.
140
 * - Flexible_start causes it to use "url.page" instead of "url;start=page".
141 106
 * - Very importantly, cleans up the start value passed, and forces it to
142 82
 *   be a multiple of num_per_page.
143
 * - Checks that start is not more than max_value.
144 89
 * - Base_url should be the URL without any start parameter on it.
145 89
 * - Uses the compactTopicPagesEnable and compactTopicPagesContiguous
146 89
 *   settings to decide how to display the menu.
147 62
 *
148 89
 * @example is available near the function definition.
149 62
 * @example $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true);
150
 *
151
 * @param string $base_url The base URL to be used for each link.
152 89
 * @param int &$start The start position, by reference. If this is not a multiple
153 89
 * of the number of items per page, it is sanitized to be so and the value will persist upon the function's return.
154
 * @param int $max_value The total number of items you are paginating for.
155
 * @param int $num_per_page The number of items to be displayed on a given page.
156
 * @param bool $flexible_start = false Use "url.page" instead of "url;start=page"
157
 * @param mixed[] $show associative array of option => boolean paris
158
 */
159
function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false, $show = array())
160
{
161
	global $modSettings, $context, $txt, $settings;
162 8
163
	// Save whether $start was less than 0 or not.
164 8
	$start = (int) $start;
165
	$start_invalid = $start < 0;
166 8
	$show_defaults = array(
167 5
		'prev_next' => true,
168
		'all' => false,
169 8
	);
170 7
171
	$show = array_merge($show_defaults, $show);
172
173 8
	// Make sure $start is a proper variable - not less than 0.
174
	if ($start_invalid)
175 5
		$start = 0;
176
	// Not greater than the upper bound.
177 8
	elseif ($start >= $max_value)
178
		$start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page)));
179 5
	// And it has to be a multiple of $num_per_page!
180
	else
181
		$start = max(0, (int) $start - ((int) $start % (int) $num_per_page));
182 8
183 8
	$context['current_page'] = $start / $num_per_page;
184 8
185
	$base_link = str_replace('{base_link}', ($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d'), $settings['page_index_template']['base_link']);
186
187 8
	// Compact pages is off or on?
188 8
	if (empty($modSettings['compactTopicPagesEnable']))
189
	{
190
		// Show the left arrow.
191
		$pageindex = $start == 0 || !$show['prev_next'] ? ' ' : sprintf($base_link, $start - $num_per_page, str_replace('{prev_txt}', $txt['prev'], $settings['page_index_template']['previous_page']));
192
193
		// Show all the pages.
194
		$display_page = 1;
195
		for ($counter = 0; $counter < $max_value; $counter += $num_per_page)
196
			$pageindex .= $start == $counter && !$start_invalid && empty($show['all_selected']) ? sprintf($settings['page_index_template']['current_page'], $display_page++) : sprintf($base_link, $counter, $display_page++);
197
198
		// Show the right arrow.
199
		$display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page);
200
		if ($start != $counter - $max_value && !$start_invalid && $show['prev_next'] && empty($show['all_selected']))
201
			$pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, str_replace('{next_txt}', $txt['next'], $settings['page_index_template']['next_page']));
202
	}
203
	else
204
	{
205
		// If they didn't enter an odd value, pretend they did.
206
		$PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2;
207
208
		// Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page)
209
		if (!empty($start) && $show['prev_next'])
210
			$pageindex = sprintf($base_link, $start - $num_per_page, str_replace('{prev_txt}', $txt['prev'], $settings['page_index_template']['previous_page']));
211
		else
212
			$pageindex = '';
213
214
		// Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15)
215
		if ($start > $num_per_page * $PageContiguous)
216
			$pageindex .= sprintf($base_link, 0, '1');
217 12
218
		// Show the ... after the first page.  (prev page 1 >...< 6 7 [8] 9 10 ... 15 next page)
219
		if ($start > $num_per_page * ($PageContiguous + 1))
220 12
			$pageindex .= str_replace('{custom}', 'data-baseurl="' . htmlspecialchars(JavaScriptEscape(($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d')), ENT_COMPAT, 'UTF-8') . '" data-perpage="' . $num_per_page . '" data-firstpage="' . $num_per_page . '" data-lastpage="' . ($start - $num_per_page * $PageContiguous) . '"', $settings['page_index_template']['expand_pages']);
221 12
222
		// Show the pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
223 12
		for ($nCont = $PageContiguous; $nCont >= 1; $nCont--)
224 8 View Code Duplication
			if ($start >= $num_per_page * $nCont)
225 8
			{
226
				$tmpStart = $start - $num_per_page * $nCont;
227 12
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
228
			}
229
230 4
		// Show the current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
231 8
		if (!$start_invalid && empty($show['all_selected']))
232
			$pageindex .= sprintf($settings['page_index_template']['current_page'], ($start / $num_per_page + 1));
233 12
		else
234
			$pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1);
235
236
		// Show the pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
237 12
		$tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page;
238
		for ($nCont = 1; $nCont <= $PageContiguous; $nCont++)
239 12 View Code Duplication
			if ($start + $num_per_page * $nCont <= $tmpMaxPages)
240
			{
241 12
				$tmpStart = $start + $num_per_page * $nCont;
242
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
243
			}
244 12
245 8
		// Show the '...' part near the end. (prev page 1 ... 6 7 [8] 9 10 >...< 15 next page)
246
		if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages)
247
			$pageindex .= str_replace('{custom}', 'data-baseurl="' . htmlspecialchars(JavaScriptEscape(($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d')), ENT_COMPAT, 'UTF-8') . '" data-perpage="' . $num_per_page . '" data-firstpage="' . ($start + $num_per_page * ($PageContiguous + 1)) . '" data-lastpage="' . $tmpMaxPages . '"', $settings['page_index_template']['expand_pages']);
248
249
		// Show the last number in the list. (prev page 1 ... 6 7 [8] 9 10 ... >15<  next page)
250
		if ($start + $num_per_page * $PageContiguous < $tmpMaxPages)
251
			$pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1);
252
253
		// Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
254
		if ($start != $tmpMaxPages && $show['prev_next'] && empty($show['all_selected']))
255
			$pageindex .= sprintf($base_link, $start + $num_per_page, str_replace('{next_txt}', $txt['next'], $settings['page_index_template']['next_page']));
256
	}
257
258
	// The "all" button
259
	if ($show['all'])
260
	{
261
		if (!empty($show['all_selected']))
262 12
			$pageindex .= sprintf($settings['page_index_template']['current_page'], $txt['all']);
263
		else
264
			$pageindex .= sprintf(str_replace('%1$d', '%1$s', $base_link), '0;all', str_replace('{all_txt}', $txt['all'], $settings['page_index_template']['all']));
265 12
	}
266 8
267
	return $pageindex;
268 12
}
269
270
/**
271 12
 * Formats a number.
272 8
 *
273
 * What it does:
274
 *
275 12
 * - Uses the format of number_format to decide how to format the number.
276 8
 *   for example, it might display "1 234,50".
277
 * - Caches the formatting data from the setting for optimization.
278
 *
279 12
 * @param float $number The float value to apply comma formatting
280 12
 * @param integer|bool $override_decimal_count = false or number of decimals
281 8
 *
282
 * @return string
283
 */
284
function comma_format($number, $override_decimal_count = false)
285
{
286
	global $txt;
287 12
	static $thousands_separator = null, $decimal_separator = null, $decimal_count = null;
288 12
289
	// Cache these values...
290
	if ($decimal_separator === null)
291
	{
292
		// Not set for whatever reason?
293 12
		if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1)
294 12
			return $number;
295 12
296 8
		// Cache these each load...
297
		$thousands_separator = $matches[1];
298
		$decimal_separator = $matches[2];
299
		$decimal_count = strlen($matches[3]);
300
	}
301
302 12
	// Format the string with our friend, number_format.
303 8
	return number_format($number, (float) $number === $number ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator);
304
}
305
306 12
/**
307 8
 * Formats a number to a multiple of thousands x, x k, x M, x G, x T
308
 *
309
 * @param float $number The value to format
310 12
 * @param integer|bool $override_decimal_count = false or number of decimals
311 8
 *
312
 * @return string
313
 */
314
function thousands_format($number, $override_decimal_count = false)
315 12
{
316 8
	foreach (array('', ' k', ' M', ' G', ' T') as $kb)
317
	{
318
		if ($number < 1000)
319
		{
320
			break;
321
		}
322
323 12
		$number /= 1000;
324
	}
325
326
	return comma_format($number, $override_decimal_count) . $kb;
0 ignored issues
show
Bug introduced by
The variable $kb seems to be defined by a foreach iteration on line 316. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
327
}
328
329
/**
330
 * Formats a number to a computer byte size value xB, xKB, xMB, xGB
331
 *
332
 * @param int $number
333
 *
334
 * @return string
335
 */
336
function byte_format($number)
337
{
338
	global $txt;
339
340
	$kb = '';
341
	foreach (array('byte', 'kilobyte', 'megabyte', 'gigabyte') as $kb)
342 12
	{
343 12
		if ($number < 1024)
344
		{
345
			break;
346 12
		}
347 8
348
		$number /= 1024;
349 6
	}
350 4
351
	return comma_format($number) . ' ' . $txt[$kb];
352
}
353 6
354 6
/**
355 6
 * Format a time to make it look purdy.
356 4
 *
357
 * What it does:
358
 *
359 12
 * - Returns a pretty formatted version of time based on the user's format in $user_info['time_format'].
360
 * - Applies all necessary time offsets to the timestamp, unless offset_type is set.
361
 * - If todayMod is set and show_today was not not specified or true, an
362
 *   alternate format string is used to show the date with something to show it is "today" or "yesterday".
363
 * - Performs localization (more than just strftime would do alone.)
364
 *
365
 * @param int $log_time A unix timestamp
366
 * @param string|bool $show_today = true show "Today"/"Yesterday",
367
 *   false shows the date, a string can force a date format to use %b %d, %Y
368
 * @param string|bool $offset_type = false If false, uses both user time offset and forum offset.
369
 *   If 'forum', uses only the forum offset. Otherwise no offset is applied.
370
 */
371
function standardTime($log_time, $show_today = true, $offset_type = false)
372
{
373
	global $user_info, $txt, $modSettings;
374
	static $non_twelve_hour, $support_e = null;
375
376
	if ($support_e === null)
377
	{
378
		$support_e = detectServer()->is('windows');
379
	}
380
381
	// Offset the time.
382
	if (!$offset_type)
383
		$time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600;
384
	// Just the forum offset?
385
	elseif ($offset_type === 'forum')
386
		$time = $log_time + $modSettings['time_offset'] * 3600;
387
	else
388
		$time = $log_time;
389
390
	// We can't have a negative date (on Windows, at least.)
391
	if ($log_time < 0)
392
		$log_time = 0;
393
394
	// Today and Yesterday?
395
	if ($modSettings['todayMod'] >= 1 && $show_today === true)
396
	{
397
		// Get the current time.
398
		$nowtime = forum_time();
399
400
		$then = @getdate($time);
401
		$now = @getdate($nowtime);
402
403
		// Try to make something of a time format string...
404
		$s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S';
405
		if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false)
406
		{
407
			$h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l';
408
			$today_fmt = $h . ':%M' . $s . ' %p';
409
		}
410
		else
411
			$today_fmt = '%H:%M' . $s;
412
413
		// Same day of the year, same year.... Today!
414
		if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
415
			return sprintf($txt['today'], standardTime($log_time, $today_fmt, $offset_type));
416
417
		// 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...
418
		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))
419
			return sprintf($txt['yesterday'], standardTime($log_time, $today_fmt, $offset_type));
420
	}
421
422
	$str = !is_bool($show_today) ? $show_today : $user_info['time_format'];
423
424
	if (setlocale(LC_TIME, $txt['lang_locale']))
425
	{
426
		if (!isset($non_twelve_hour))
427
			$non_twelve_hour = trim(strftime('%p')) === '';
428 View Code Duplication
		if ($non_twelve_hour && strpos($str, '%p') !== false)
429 36
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
430 36
431
		foreach (array('%a', '%A', '%b', '%B') as $token)
432 36
			if (strpos($str, $token) !== false)
433 24
				$str = str_replace($token, !empty($txt['lang_capitalize_dates']) ? Util::ucwords(strftime($token, $time)) : strftime($token, $time), $str);
434 6
	}
435 4
	else
436
	{
437
		// Do-it-yourself time localization.  Fun.
438 36
		foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label)
439 30
			if (strpos($str, $token) !== false)
440
				$str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str);
441 30
442 View Code Duplication
		if (strpos($str, '%p') !== false)
443
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
444 30
	}
445
446
	// Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that.
447 36
	if ($support_e && strpos($str, '%e') !== false)
448 24
		$str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str);
449
450
	// Format any other characters..
451 36
	return strftime($str, $time);
452 24
}
453
454 36
/**
455
 * Used to render a timestamp to html5 <time> tag format.
456 36
 *
457 36
 * @param int $timestamp A unix timestamp
458
 *
459
 * @return string
460 36
 */
461 36
function htmlTime($timestamp)
462 24
{
463 36
	global $txt, $context;
464 36
465 24
	if (empty($timestamp))
466
		return '';
467
468
	$timestamp = forum_time(true, $timestamp);
469
	$time = date('Y-m-d H:i', $timestamp);
470 36
	$stdtime = standardTime($timestamp, true, true);
471 30
472
	// @todo maybe htmlspecialchars on the title attribute?
473
	return '<time title="' . (!empty($context['using_relative_time']) ? $stdtime : $txt['last_post']) . '" datetime="' . $time . '" data-timestamp="' . $timestamp . '">' . $stdtime . '</time>';
474 18
}
475 12
476 12
/**
477
 * Gets the current time with offset.
478 36
 *
479
 * What it does:
480 36
 *
481 24
 * - Always applies the offset in the time_offset setting.
482
 *
483
 * @param bool $use_user_offset = true if use_user_offset is true, applies the user's offset as well
484
 * @param int|null $timestamp = null A unix timestamp (null to use current time)
485
 *
486
 * @return int seconds since the unix epoch
487
 */
488
function forum_time($use_user_offset = true, $timestamp = null)
489
{
490
	global $user_info, $modSettings;
491
492
	if ($timestamp === null)
493
		$timestamp = time();
494 36
	elseif ($timestamp == 0)
495 36
		return 0;
496 36
497
	return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600;
498 36
}
499 30
500
/**
501
 * Removes special entities from strings.  Compatibility...
502
 *
503 36
 * - Faster than html_entity_decode
504 24
 * - Removes the base entities ( &amp; &quot; &#039; &lt; and &gt;. ) from text with htmlspecialchars_decode
505
 * - Additionally converts &nbsp with str_replace
506
 *
507 36
 * @param string $string The string to apply htmlspecialchars_decode
508
 *
509
 * @return string string without entities
510
 */
511
function un_htmlspecialchars($string)
512
{
513
	$string = htmlspecialchars_decode($string, ENT_QUOTES);
514
	$string = str_replace('&nbsp;', ' ', $string);
515
516
	return $string;
517
}
518 30
519
/**
520 30
 * Lexicographic permutation function.
521 20
 *
522
 * This is a special type of permutation which involves the order of the set. The next
523 30
 * lexicographic permutation of '32541' is '34125'. Numerically, it is simply the smallest
524 30
 * set larger than the current one.
525 30
 *
526
 * The benefit of this over a recursive solution is that the whole list does NOT need
527
 * to be held in memory. So it's actually possible to run 30! permutations without
528 30
 * causing a memory overflow.
529
 *
530
 * Source: O'Reilly PHP Cookbook
531
 *
532
 * @param mixed[] $p The array keys to apply permutation
533
 * @param int $size The size of our permutation array
534
 *
535
 * @return mixed[] the next permutation of the passed array $p
536
 */
537
function pc_next_permutation($p, $size)
538
{
539
	// Slide down the array looking for where we're smaller than the next guy
540
	for ($i = $size - 1; isset($p[$i]) && $p[$i] >= $p[$i + 1]; --$i)
0 ignored issues
show
Unused Code introduced by
This for loop is empty and can be removed.

This check looks for for loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
541
	{
542
	}
543
544 37
	// If this doesn't occur, we've finished our permutations
545
	// the array is reversed: (1, 2, 3, 4) => (4, 3, 2, 1)
546 37
	if ($i === -1)
547 37
	{
548 36
		return false;
549
	}
550
551 37
	// Slide down the array looking for a bigger number than what we found before
552
	for ($j = $size; $p[$j] <= $p[$i]; --$j)
0 ignored issues
show
Unused Code introduced by
This for loop is empty and can be removed.

This check looks for for loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
553
	{
554
	}
555
556
	// Swap them
557
	$tmp = $p[$i];
558
	$p[$i] = $p[$j];
559
	$p[$j] = $tmp;
560
561
	// Now reverse the elements in between by swapping the ends
562
	for (++$i, $j = $size; $i < $j; ++$i, --$j)
563
	{
564
		$tmp = $p[$i];
565
		$p[$i] = $p[$j];
566 121
		$p[$j] = $tmp;
567 121
	}
568
569 121
	return $p;
570
}
571
572
/**
573
 * Highlight any code.
574
 *
575
 * What it does:
576
 *
577
 * - Uses PHP's highlight_string() to highlight PHP syntax
578
 * - does special handling to keep the tabs in the code available.
579
 * - used to parse PHP code from inside [code] and [php] tags.
580
 *
581
 * @param string $code The string containing php code
582
 *
583
 * @return string the code with highlighted HTML.
584
 */
585
function highlight_php_code($code)
586
{
587
	// Remove special characters.
588
	$code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", "\t" => '___TAB();', '&#91;' => '[')));
589
590
	$buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true));
591
592
	// Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P.
593
	$buffer = preg_replace('~___TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer);
594
595
	return strtr($buffer, array('\'' => '&#039;', '<code>' => '', '</code>' => ''));
596
}
597
598
/**
599
 * Ends execution and redirects the user to a new location
600
 *
601
 * What it does:
602
 *
603
 * - Makes sure the browser doesn't come back and repost the form data.
604
 * - Should be used whenever anything is posted.
605
 * - Calls AddMailQueue to process any mail queue items its can
606
 * - Calls call_integration_hook integrate_redirect before headers are sent
607
 * - Diverts final execution to obExit() which means a end to processing and sending of final output
608
 *
609
 * @event integrate_redirect called before headers are sent
610
 * @param string $setLocation = '' The URL to redirect to
611
 * @param bool $refresh = false, enable to send a refresh header, default is a location header
612
 * @throws Elk_Exception
613
 */
614
function redirectexit($setLocation = '', $refresh = false)
615
{
616
	global $scripturl, $context, $modSettings, $db_show_debug;
617
618
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
619
	if (!empty($context['flush_mail']))
620
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
621
		AddMailQueue(true);
622
623
	Notifications::instance()->send();
624
625
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
626
627
	if ($add)
628
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
629
630
	// Put the session ID in.
631 View Code Duplication
	if (empty($_COOKIE) && defined('SID') && SID != '')
632
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
633 12
	// Keep that debug in their for template debugging!
634
	elseif (isset($_GET['debug']))
635 8
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
636
637
	if (!empty($modSettings['queryless_urls']) && detectServer()->supportRewrite())
638
	{
639 12
		if (defined('SID') && SID != '')
640 8
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '~') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~', 'redirectexit_callback', $setLocation);
641 12
		else
642
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '~') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~', 'redirectexit_callback', $setLocation);
643
	}
644
645 12
	// Maybe integrations want to change where we are heading?
646
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh));
647 8
648
	// We send a Refresh header only in special cases because Location looks better. (and is quicker...)
649
	if ($refresh)
650 12
		header('Refresh: 0; URL=' . strtr($setLocation, array(' ' => '%20')));
0 ignored issues
show
Security Response Splitting introduced by
'Refresh: 0; URL=' . str...n, array(' ' => '%20')) can contain request data and is used in response header context(s) leading to a potential security vulnerability.

8 paths for user data to reach this point

  1. Path: Fetching key HTTP_REFERER from $_SERVER, and $_SERVER['HTTP_REFERER'] is passed to redirectexit() in sources/admin/Admin.controller.php on line 984
  1. Fetching key HTTP_REFERER from $_SERVER, and $_SERVER['HTTP_REFERER'] is passed to redirectexit()
    in sources/admin/Admin.controller.php on line 984
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 650
  2. Path: Read from $_REQUEST, and 'action=pm;f=' . $_REQUEST['f'] . ';start=' . $this->_req->start . (isset($_REQUEST['l']) ? ';l=' . $this->_req->get('l', 'intval') : '') . (isset($_REQUEST['pm']) ? '#' . $this->_req->get('pm', 'intval') : '') is passed to redirectexit() in sources/controllers/Karma.controller.php on line 181
  1. Read from $_REQUEST, and 'action=pm;f=' . $_REQUEST['f'] . ';start=' . $this->_req->start . (isset($_REQUEST['l']) ? ';l=' . $this->_req->get('l', 'intval') : '') . (isset($_REQUEST['pm']) ? '#' . $this->_req->get('pm', 'intval') : '') is passed to redirectexit()
    in sources/controllers/Karma.controller.php on line 181
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 650
  3. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in sources/controllers/Post.controller.php on line 1135
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in sources/controllers/Post.controller.php on line 1135
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 650
  4. Path: Read from $_REQUEST, and $_REQUEST['search'] is passed through explode(), and explode(' ', $_REQUEST['search']) is passed through implode(), and implode($engine['separator'], explode(' ', $_REQUEST['search'])) is escaped by urlencode() for all (url-encoded) context(s), and $engine['url'] . urlencode(implode($engine['separator'], explode(' ', $_REQUEST['search']))) is passed to redirectexit() in sources/controllers/Search.controller.php on line 73
  1. Read from $_REQUEST, and $_REQUEST['search'] is passed through explode(), and explode(' ', $_REQUEST['search']) is passed through implode(), and implode($engine['separator'], explode(' ', $_REQUEST['search'])) is escaped by urlencode() for all (url-encoded) context(s), and $engine['url'] . urlencode(implode($engine['separator'], explode(' ', $_REQUEST['search']))) is passed to redirectexit()
    in sources/controllers/Search.controller.php on line 73
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 650
  5. Path: Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and $scripturl . '?action=memberlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit() in sources/controllers/Search.controller.php on line 80
  1. Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and $scripturl . '?action=memberlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit()
    in sources/controllers/Search.controller.php on line 80
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 650
  6. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in sources/Load.php on line 475
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in sources/Load.php on line 475
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 650
  7. Path: Read from $_GET, and $_GET is passed through key(), and 'wwwRedirect;' . key($_GET) . '=' . current($_GET) is passed to redirectexit() in sources/subs/Themes/ThemeLoader.php on line 541
  1. Read from $_GET, and $_GET is passed through key(), and 'wwwRedirect;' . key($_GET) . '=' . current($_GET) is passed to redirectexit()
    in sources/subs/Themes/ThemeLoader.php on line 541
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 650
  8. Path: Read from $_POST, and $_POST is passed to Data_Validator::is_valid() in sources/controllers/Post.controller.php on line 883
  1. Read from $_POST, and $_POST is passed to Data_Validator::is_valid()
    in sources/controllers/Post.controller.php on line 883
  2. $data is passed to Data_Validator::validate()
    in sources/subs/DataValidator.class.php on line 147
  3. Data_Validator::$_data is assigned
    in sources/subs/DataValidator.class.php on line 249
  4. Tainted property Data_Validator::$_data is read
    in sources/subs/DataValidator.class.php on line 281
  5. Data_Validator::validation_data() returns tainted data
    in sources/subs/HttpReq.class.php on line 359
  6. HttpReq::cleanValue() returns tainted data, and HttpReq::$_param is assigned
    in sources/subs/HttpReq.class.php on line 219
  7. Tainted property HttpReq::$_param is read
    in sources/subs/HttpReq.class.php on line 190
  8. HttpReq::__get() returns tainted data, and 'topic=' . $topic . '.' . $this->_req->start . '#msg' . $this->_req->get('m', 'intval') is passed to redirectexit()
    in sources/controllers/Karma.controller.php on line 178
  9. $setLocation is passed through strtr()
    in sources/Subs.php on line 650

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

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

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

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

$sanitized = (integer) $tainted;
Loading history...
651 12
	else
652 12
		header('Location: ' . str_replace(' ', '%20', $setLocation));
0 ignored issues
show
Security Response Splitting introduced by
'Location: ' . str_repla...', '%20', $setLocation) can contain request data and is used in response header context(s) leading to a potential security vulnerability.

8 paths for user data to reach this point

  1. Path: Fetching key HTTP_REFERER from $_SERVER, and $_SERVER['HTTP_REFERER'] is passed to redirectexit() in sources/admin/Admin.controller.php on line 984
  1. Fetching key HTTP_REFERER from $_SERVER, and $_SERVER['HTTP_REFERER'] is passed to redirectexit()
    in sources/admin/Admin.controller.php on line 984
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 652
  2. Path: Read from $_REQUEST, and 'action=pm;f=' . $_REQUEST['f'] . ';start=' . $this->_req->start . (isset($_REQUEST['l']) ? ';l=' . $this->_req->get('l', 'intval') : '') . (isset($_REQUEST['pm']) ? '#' . $this->_req->get('pm', 'intval') : '') is passed to redirectexit() in sources/controllers/Karma.controller.php on line 181
  1. Read from $_REQUEST, and 'action=pm;f=' . $_REQUEST['f'] . ';start=' . $this->_req->start . (isset($_REQUEST['l']) ? ';l=' . $this->_req->get('l', 'intval') : '') . (isset($_REQUEST['pm']) ? '#' . $this->_req->get('pm', 'intval') : '') is passed to redirectexit()
    in sources/controllers/Karma.controller.php on line 181
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 652
  3. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in sources/controllers/Post.controller.php on line 1135
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in sources/controllers/Post.controller.php on line 1135
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 652
  4. Path: Read from $_REQUEST, and $_REQUEST['search'] is passed through explode(), and explode(' ', $_REQUEST['search']) is passed through implode(), and implode($engine['separator'], explode(' ', $_REQUEST['search'])) is escaped by urlencode() for all (url-encoded) context(s), and $engine['url'] . urlencode(implode($engine['separator'], explode(' ', $_REQUEST['search']))) is passed to redirectexit() in sources/controllers/Search.controller.php on line 73
  1. Read from $_REQUEST, and $_REQUEST['search'] is passed through explode(), and explode(' ', $_REQUEST['search']) is passed through implode(), and implode($engine['separator'], explode(' ', $_REQUEST['search'])) is escaped by urlencode() for all (url-encoded) context(s), and $engine['url'] . urlencode(implode($engine['separator'], explode(' ', $_REQUEST['search']))) is passed to redirectexit()
    in sources/controllers/Search.controller.php on line 73
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 652
  5. Path: Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and $scripturl . '?action=memberlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit() in sources/controllers/Search.controller.php on line 80
  1. Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and $scripturl . '?action=memberlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit()
    in sources/controllers/Search.controller.php on line 80
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 652
  6. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in sources/Load.php on line 475
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in sources/Load.php on line 475
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 652
  7. Path: Read from $_GET, and $_GET is passed through key(), and 'wwwRedirect;' . key($_GET) . '=' . current($_GET) is passed to redirectexit() in sources/subs/Themes/ThemeLoader.php on line 541
  1. Read from $_GET, and $_GET is passed through key(), and 'wwwRedirect;' . key($_GET) . '=' . current($_GET) is passed to redirectexit()
    in sources/subs/Themes/ThemeLoader.php on line 541
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 652
  8. Path: Read from $_POST, and $_POST is passed to Data_Validator::is_valid() in sources/controllers/Post.controller.php on line 883
  1. Read from $_POST, and $_POST is passed to Data_Validator::is_valid()
    in sources/controllers/Post.controller.php on line 883
  2. $data is passed to Data_Validator::validate()
    in sources/subs/DataValidator.class.php on line 147
  3. Data_Validator::$_data is assigned
    in sources/subs/DataValidator.class.php on line 249
  4. Tainted property Data_Validator::$_data is read
    in sources/subs/DataValidator.class.php on line 281
  5. Data_Validator::validation_data() returns tainted data
    in sources/subs/HttpReq.class.php on line 359
  6. HttpReq::cleanValue() returns tainted data, and HttpReq::$_param is assigned
    in sources/subs/HttpReq.class.php on line 219
  7. Tainted property HttpReq::$_param is read
    in sources/subs/HttpReq.class.php on line 190
  8. HttpReq::__get() returns tainted data, and 'topic=' . $topic . '.' . $this->_req->start . '#msg' . $this->_req->get('m', 'intval') is passed to redirectexit()
    in sources/controllers/Karma.controller.php on line 178
  9. $setLocation is passed through str_replace()
    in sources/Subs.php on line 652

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

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

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

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

$sanitized = (integer) $tainted;
Loading history...
653
654
	// Debugging.
655 12
	if ($db_show_debug === true)
656
	{
657 12
		$_SESSION['debug_redirect'] = Debug::instance()->get_db();
658 12
	}
659 12
660 8
	obExit(false);
661
}
662 12
663
/**
664
 * URL fixer for redirect exit
665
 *
666
 * What it does:
667
 *
668
 * - Similar to the callback function used in ob_sessrewrite
669
 * - Evoked by enabling queryless_urls for systems that support that function
670
 *
671
 * @param mixed[] $matches results from the calling preg
672
 */
673
function redirectexit_callback($matches)
674
{
675
	global $scripturl;
676
677
	if (defined('SID') && SID != '')
678
		return $scripturl . '/' . strtr($matches[1], '&;=', '//,') . '.html?' . SID . (isset($matches[2]) ? $matches[2] : '');
679 View Code Duplication
	else
680
		return $scripturl . '/' . strtr($matches[1], '&;=', '//,') . '.html' . (isset($matches[2]) ? $matches[2] : '');
681
}
682
683
/**
684
 * Ends execution.
685
 *
686
 * What it does:
687
 *
688
 * - Takes care of template loading and remembering the previous URL.
689
 * - Calls ob_start() with ob_sessrewrite to fix URLs if necessary.
690
 *
691
 * @event integrate_invalid_old_url allows adding to "from" urls we don't save
692
 * @event integrate_exit inform portal, etc. that we're integrated with to exit
693
 * @param bool|null $header = null Output the header
694
 * @param bool|null $do_footer = null Output the footer
695
 * @param bool $from_index = false If we're coming from index.php
696
 * @param bool $from_fatal_error = false If we are exiting due to a fatal error
697
 *
698
 * @throws Elk_Exception
699
 */
700
function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false)
701
{
702
	global $context, $txt, $db_show_debug;
703
704
	static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false;
705
706
	// Attempt to prevent a recursive loop.
707
	++$level;
708
	if ($level > 1 && !$from_fatal_error && !$has_fatal_error)
709
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function obExit() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
710
711
	if ($from_fatal_error)
712
		$has_fatal_error = true;
713
714
	// Clear out the stat cache.
715
	trackStats();
716
717
	Notifications::instance()->send();
718
719
	// If we have mail to send, send it.
720
	if (!empty($context['flush_mail']))
721
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
722
		AddMailQueue(true);
723
724
	$do_header = $header === null ? !$header_done : $header;
725
	if ($do_footer === null)
726
		$do_footer = $do_header;
727
728
	// Has the template/header been done yet?
729
	if ($do_header)
730
	{
731
		// Was the page title set last minute? Also update the HTML safe one.
732
		if (!empty($context['page_title']) && empty($context['page_title_html_safe']))
733
			$context['page_title_html_safe'] = Util::htmlspecialchars(un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
734
735
		// Start up the session URL fixer.
736
		ob_start('ob_sessrewrite');
737
738
		call_integration_buffer();
739
740
		// Display the screen in the logical order.
741
		template_header();
742
		$header_done = true;
743
	}
744
745
	if ($do_footer)
746
	{
747
		// Show the footer.
748
		theme()->getTemplates()->loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main');
749
750
		// Just so we don't get caught in an endless loop of errors from the footer...
751
		if (!$footer_done)
752
		{
753
			$footer_done = true;
754
			template_footer();
755
756
			// Add $db_show_debug = true; to Settings.php if you want to show the debugging information.
757
			// (since this is just debugging... it's okay that it's after </html>.)
758
			if ($db_show_debug === true)
759
			{
760
				if (!isset($_REQUEST['xml']) && ((!isset($_GET['action']) || $_GET['action'] != 'viewquery') && !isset($_GET['api'])))
761
				{
762
					Debug::instance()->display();
763
				}
764
			}
765
		}
766
	}
767
768
	// Need user agent
769
	$req = request();
770
771
	// Remember this URL in case someone doesn't like sending HTTP_REFERER.
772
	$invalid_old_url = array(
773
		'action=dlattach',
774
		'action=jsoption',
775
		';xml',
776
		';api',
777
	);
778
	call_integration_hook('integrate_invalid_old_url', array(&$invalid_old_url));
779
	$make_old = true;
780
	foreach ($invalid_old_url as $url)
781
	{
782
		if (strpos($_SERVER['REQUEST_URL'], $url) !== false)
783
		{
784
			$make_old = false;
785
			break;
786
		}
787
	}
788
	if ($make_old === true)
789
		$_SESSION['old_url'] = $_SERVER['REQUEST_URL'];
790
791
	// For session check verification.... don't switch browsers...
792
	$_SESSION['USER_AGENT'] = $req->user_agent();
793
794
	// Hand off the output to the portal, etc. we're integrated with.
795
	call_integration_hook('integrate_exit', array($do_footer));
796
797
	// Don't exit if we're coming from index.php; that will pass through normally.
798
	if (!$from_index)
799
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function obExit() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
800
}
801
802
/**
803
 * Sets the class of the current topic based on is_very_hot, veryhot, hot, etc
804
 *
805
 * @param mixed[] $topic_context array of topic information
806
 */
807
function determineTopicClass(&$topic_context)
808
{
809
	// Set topic class depending on locked status and number of replies.
810
	if ($topic_context['is_very_hot'])
811
		$topic_context['class'] = 'veryhot';
812
	elseif ($topic_context['is_hot'])
813
		$topic_context['class'] = 'hot';
814
	else
815
		$topic_context['class'] = 'normal';
816
817
	$topic_context['class'] .= !empty($topic_context['is_poll']) ? '_poll' : '_post';
818
819
	if ($topic_context['is_locked'])
820
		$topic_context['class'] .= '_locked';
821
822
	if ($topic_context['is_sticky'])
823
		$topic_context['class'] .= '_sticky';
824
}
825
826
/**
827
 * Sets up the basic theme context stuff.
828
 *
829
 * @param bool $forceload = false
830
 */
831
function setupThemeContext($forceload = false)
832
{
833
	return theme()->setupThemeContext($forceload);
834
}
835
836
/**
837
 * Helper function to convert memory string settings to bytes
838
 *
839
 * @param string $val The byte string, like 256M or 1G
840
 *
841
 * @return integer The string converted to a proper integer in bytes
842
 */
843
function memoryReturnBytes($val)
844
{
845
	if (is_integer($val))
846
		return $val;
847
848
	// Separate the number from the designator
849
	$val = trim($val);
850
	$num = intval(substr($val, 0, strlen($val) - 1));
851
	$last = strtolower(substr($val, -1));
852
853
	// Convert to bytes
854
	switch ($last)
855
	{
856
		// fall through select g = 1024*1024*1024
857
		case 'g':
858
			$num *= 1024;
859
		// fall through select m = 1024*1024
860
		case 'm':
861
			$num *= 1024;
862
		// fall through select k = 1024
863
		case 'k':
864
			$num *= 1024;
865
	}
866
867
	return $num;
868
}
869
870
/**
871
 * This is the only template included in the sources.
872
 */
873
function template_rawdata()
874
{
875
	return theme()->template_rawdata();
876
}
877
878
/**
879
 * The header template
880
 */
881
function template_header()
882
{
883
	return theme()->template_header();
884
}
885
886
/**
887
 * Show the copyright.
888
 */
889
function theme_copyright()
890
{
891
	return theme()->theme_copyright();
892
}
893
894
/**
895
 * The template footer
896
 */
897
function template_footer()
898
{
899
	return theme()->template_footer();
900
}
901
902
/**
903
 * Output the Javascript files
904
 *
905
 * What it does:
906
 *
907
 * - tabbing in this function is to make the HTML source look proper
908
 * - outputs jQuery/jQueryUI from the proper source (local/CDN)
909
 * - if deferred is set function will output all JS (source & inline) set to load at page end
910
 * - if the admin option to combine files is set, will use Combiner.class
911
 *
912
 * @param bool $do_deferred = false
913
 */
914
function template_javascript($do_deferred = false)
915
{
916
	theme()->template_javascript($do_deferred);
917
	return;
918
}
919
920
/**
921
 * Output the CSS files
922
 *
923
 * What it does:
924
 *  - If the admin option to combine files is set, will use Combiner.class
925
 */
926
function template_css()
927
{
928
	theme()->template_css();
929
	return;
930
}
931
932
/**
933
 * Calls on template_show_error from index.template.php to show warnings
934
 * and security errors for admins
935
 */
936
function template_admin_warning_above()
937
{
938
	theme()->template_admin_warning_above();
939
	return;
940
}
941
942
/**
943
 * Convert a single IP to a ranged IP.
944
 *
945
 * - Internal function used to convert a user-readable format to a format suitable for the database.
946
 *
947
 * @param string $fullip A full dot notation IP address
948
 *
949
 * @return array|string 'unknown' if the ip in the input was '255.255.255.255'
950
 */
951
function ip2range($fullip)
952
{
953 6
	// If its IPv6, validate it first.
954 4
	if (isValidIPv6($fullip) !== false)
955 6
	{
956
		$ip_parts = explode(':', expandIPv6($fullip, false));
957
		$ip_array = array();
958 6
959
		if (count($ip_parts) != 8)
960 6
			return array();
961
962 6
		for ($i = 0; $i < 8; $i++)
963 4
		{
964
			if ($ip_parts[$i] == '*')
965 6
				$ip_array[$i] = array('low' => '0', 'high' => hexdec('ffff'));
966 4 View Code Duplication
			elseif (preg_match('/^([0-9A-Fa-f]{1,4})\-([0-9A-Fa-f]{1,4})$/', $ip_parts[$i], $range) == 1)
967
				$ip_array[$i] = array('low' => hexdec($range[1]), 'high' => hexdec($range[2]));
968
			elseif (is_numeric(hexdec($ip_parts[$i])))
969
				$ip_array[$i] = array('low' => hexdec($ip_parts[$i]), 'high' => hexdec($ip_parts[$i]));
970 6
		}
971 6
972
		return $ip_array;
973
	}
974
975
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
976
	if ($fullip == 'unknown')
977
		$fullip = '255.255.255.255';
978
979
	$ip_parts = explode('.', $fullip);
980
	$ip_array = array();
981
982
	if (count($ip_parts) != 4)
983
		return array();
984
985
	for ($i = 0; $i < 4; $i++)
986
	{
987
		if ($ip_parts[$i] == '*')
988
			$ip_array[$i] = array('low' => '0', 'high' => '255');
989 View Code Duplication
		elseif (preg_match('/^(\d{1,3})\-(\d{1,3})$/', $ip_parts[$i], $range) == 1)
990
			$ip_array[$i] = array('low' => $range[1], 'high' => $range[2]);
991
		elseif (is_numeric($ip_parts[$i]))
992
			$ip_array[$i] = array('low' => $ip_parts[$i], 'high' => $ip_parts[$i]);
993
	}
994
995
	// Makes it simpler to work with.
996
	$ip_array[4] = array('low' => 0, 'high' => 0);
997
	$ip_array[5] = array('low' => 0, 'high' => 0);
998
	$ip_array[6] = array('low' => 0, 'high' => 0);
999
	$ip_array[7] = array('low' => 0, 'high' => 0);
1000
1001
	return $ip_array;
1002
}
1003
1004
/**
1005
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
1006
 *
1007
 * @param string $ip A full dot notation IP address
1008
 *
1009 6
 * @return string
1010 4
 */
1011
function host_from_ip($ip)
1012
{
1013 6
	global $modSettings;
1014 6
1015 6
	$cache = Cache::instance();
1016
1017
	$host = '';
1018
	if ($cache->getVar($host, 'hostlookup-' . $ip, 600) || empty($ip))
1019
		return $host;
1020
1021 6
	$t = microtime(true);
1022 6
1023
	// Try the Linux host command, perhaps?
1024 6
	if (!isset($host) && (strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
1025 6
	{
1026
		if (!isset($modSettings['host_to_dis']))
1027 4
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
1028 6
		else
1029 4
			$test = @shell_exec('host ' . @escapeshellarg($ip));
1030
1031 6
		// Did host say it didn't find anything?
1032
		if (strpos($test, 'not found') !== false)
1033
			$host = '';
1034
		// Invalid server option?
1035
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
1036
			updateSettings(array('host_to_dis' => 1));
1037
		// Maybe it found something, after all?
1038
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
1039
			$host = $match[1];
1040
	}
1041
1042
	// This is nslookup; usually only Windows, but possibly some Unix?
1043
	if (!isset($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
1044
	{
1045
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
1046
1047 View Code Duplication
		if (strpos($test, 'Non-existent domain') !== false)
1048
			$host = '';
1049
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
1050
			$host = $match[1];
1051
	}
1052
1053
	// This is the last try :/.
1054
	if (!isset($host) || $host === false)
1055
		$host = @gethostbyaddr($ip);
1056
1057
	// It took a long time, so let's cache it!
1058
	if (microtime(true) - $t > 0.5)
1059
		$cache->put('hostlookup-' . $ip, $host, 600);
1060
1061
	return $host;
1062
}
1063
1064
/**
1065
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
1066
 *
1067
 * @param string $text The string to process
1068
 * @param int $max_chars defaults to 20
1069
 *     - if encrypt = true this is the maximum number of bytes to use in integer hashes (for searching)
1070
 *     - if encrypt = false this is the maximum number of letters in each word
1071
 * @param bool $encrypt = false Used for custom search indexes to return an int[] array representing the words
1072
 */
1073
function text2words($text, $max_chars = 20, $encrypt = false)
1074
{
1075
	// Step 1: Remove entities/things we don't consider words:
1076
	$words = preg_replace('~(?:[\x0B\0\x{A0}\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~u', ' ', strtr($text, array('<br />' => ' ')));
1077
1078
	// Step 2: Entities we left to letters, where applicable, lowercase.
1079
	$words = un_htmlspecialchars(Util::strtolower($words));
1080
1081
	// Step 3: Ready to split apart and index!
1082
	$words = explode(' ', $words);
1083
1084
	if ($encrypt)
1085
	{
1086
		// Range of characters that crypt will produce (0-9, a-z, A-Z .)
1087
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
1088
		$returned_ints = array();
1089
		foreach ($words as $word)
1090
		{
1091
			if (($word = trim($word, '-_\'')) !== '')
1092
			{
1093
				// Get a crypt representation of this work
1094
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
1095
				$total = 0;
1096
1097
				// Create an integer representation
1098
				for ($i = 0; $i < $max_chars; $i++)
1099
					$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
1100
1101
				// Return the value
1102
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
1103
			}
1104
		}
1105
		return array_unique($returned_ints);
1106
	}
1107
	else
1108
	{
1109
		// Trim characters before and after and add slashes for database insertion.
1110
		$returned_words = array();
1111
		foreach ($words as $word)
1112
			if (($word = trim($word, '-_\'')) !== '')
1113
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
1114
1115
		// Filter out all words that occur more than once.
1116
		return array_unique($returned_words);
1117
	}
1118
}
1119
1120
/**
1121
 * Sets up all of the top menu buttons
1122
 *
1123
 * What it does:
1124
 *
1125
 * - Defines every master item in the menu, as well as any sub-items
1126
 * - Ensures the chosen action is set so the menu is highlighted
1127
 * - Saves them in the cache if it is available and on
1128
 * - Places the results in $context
1129
 */
1130
function setupMenuContext()
1131
{
1132
	return theme()->setupMenuContext();
1133
}
1134
1135
/**
1136
 * Process functions of an integration hook.
1137
 *
1138
 * What it does:
1139
 *
1140
 * - Calls all functions of the given hook.
1141
 * - Supports static class method calls.
1142
 *
1143
 * @param string $hook The name of the hook to call
1144
 * @param mixed[] $parameters = array() Parameters to pass to the hook
1145
 *
1146
 * @return mixed[] the results of the functions
1147
 */
1148
function call_integration_hook($hook, $parameters = array())
1149
{
1150
	return Hooks::instance()->hook($hook, $parameters);
1151
}
1152
1153
/**
1154
 * Includes files for hooks that only do that (i.e. integrate_pre_include)
1155
 *
1156
 * @param string $hook The name to include
1157
 */
1158
function call_integration_include_hook($hook)
1159
{
1160
	Hooks::instance()->include_hook($hook);
1161
}
1162
1163
/**
1164
 * Special hook call executed during obExit
1165
 */
1166
function call_integration_buffer()
1167
{
1168
	Hooks::instance()->buffer_hook();
1169
}
1170
1171
/**
1172
 * Add a function for integration hook.
1173
 *
1174
 * - Does nothing if the function is already added.
1175
 *
1176
 * @param string $hook The name of the hook to add
1177
 * @param string $function The function associated with the hook
1178
 * @param string $file The file that contains the function
1179
 * @param bool $permanent = true if true, updates the value in settings table
1180
 */
1181
function add_integration_function($hook, $function, $file = '', $permanent = true)
1182
{
1183
	Hooks::instance()->add($hook, $function, $file, $permanent);
1184
}
1185
1186
/**
1187
 * Remove an integration hook function.
1188
 *
1189
 * What it does:
1190
 *
1191
 * - Removes the given function from the given hook.
1192
 * - Does nothing if the function is not available.
1193
 *
1194
 * @param string $hook The name of the hook to remove
1195
 * @param string $function The name of the function
1196
 * @param string $file The file its located in
1197
 */
1198
function remove_integration_function($hook, $function, $file = '')
1199
{
1200
	Hooks::instance()->remove($hook, $function, $file);
1201
}
1202
1203
/**
1204
 * Decode numeric html entities to their UTF8 equivalent character.
1205
 *
1206
 * What it does:
1207
 *
1208
 * - Callback function for preg_replace_callback in subs-members
1209
 * - Uses capture group 2 in the supplied array
1210
 * - Does basic scan to ensure characters are inside a valid range
1211
 *
1212
 * @param mixed[] $matches matches from a preg_match_all
1213
 *
1214
 * @return string $string
1215
 */
1216
function replaceEntities__callback($matches)
1217
{
1218
	if (!isset($matches[2]))
1219
		return '';
1220
1221
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
1222
1223
	// remove left to right / right to left overrides
1224
	if ($num === 0x202D || $num === 0x202E)
1225
		return '';
1226
1227
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
1228
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
1229
		return '&#' . $num . ';';
1230
1231
	// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
1232
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
1233
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
1234
		return '';
1235
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
1236
	elseif ($num < 0x80)
1237
		return chr($num);
1238
	// <0x800 (2048)
1239 View Code Duplication
	elseif ($num < 0x800)
1240
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
1241
	// < 0x10000 (65536)
1242 View Code Duplication
	elseif ($num < 0x10000)
1243
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1244
	// <= 0x10FFFF (1114111)
1245 View Code Duplication
	else
1246
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1247
}
1248
1249
/**
1250
 * Converts html entities to utf8 equivalents
1251
 *
1252
 * What it does:
1253
 *
1254
 * - Callback function for preg_replace_callback
1255
 * - Uses capture group 1 in the supplied array
1256
 * - Does basic checks to keep characters inside a viewable range.
1257
 *
1258
 * @param mixed[] $matches array of matches as output from preg_match_all
1259
 *
1260
 * @return string $string
1261
 */
1262
function fixchar__callback($matches)
1263
{
1264
	if (!isset($matches[1]))
1265
		return '';
1266
1267
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
1268 60
1269
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
1270
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
1271 60
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
1272
		return '';
1273
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
1274 60
	elseif ($num < 0x80)
1275
		return chr($num);
1276 20
	// <0x800 (2048)
1277 40 View Code Duplication
	elseif ($num < 0x800)
1278
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
1279
	// < 0x10000 (65536)
1280 View Code Duplication
	elseif ($num < 0x10000)
1281
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1282
	// <= 0x10FFFF (1114111)
1283 View Code Duplication
	else
1284
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1285
}
1286
1287
/**
1288
 * Strips out invalid html entities, replaces others with html style &#123; codes
1289
 *
1290
 * What it does:
1291
 *
1292
 * - Callback function used of preg_replace_callback in various $ent_checks,
1293
 * - For example strpos, strlen, substr etc
1294
 *
1295
 * @param mixed[] $matches array of matches for a preg_match_all
1296
 *
1297
 * @return string
1298
 */
1299
function entity_fix__callback($matches)
1300
{
1301
	if (!isset($matches[2]))
1302 60
		return '';
1303 60
1304 60
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
1305 60
1306
	// We don't allow control characters, characters out of range, byte markers, etc
1307 View Code Duplication
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
1308 60
		return '';
1309
	else
1310
		return '&#' . $num . ';';
1311
}
1312
1313
/**
1314
 * Retrieve additional search engines, if there are any, as an array.
1315
 *
1316
 * @return mixed[] array of engines
1317
 */
1318
function prepareSearchEngines()
1319
{
1320
	global $modSettings;
1321
1322
	$engines = array();
1323
	if (!empty($modSettings['additional_search_engines']))
1324
	{
1325
		$search_engines = Util::unserialize($modSettings['additional_search_engines']);
1326
		foreach ($search_engines as $engine)
1327
			$engines[strtolower(preg_replace('~[^A-Za-z0-9 ]~', '', $engine['name']))] = $engine;
1328
	}
1329
1330
	return $engines;
1331
}
1332
1333
/**
1334
 * This function receives a request handle and attempts to retrieve the next result.
1335
 *
1336
 * What it does:
1337
 *
1338
 * - It is used by the controller callbacks from the template, such as
1339
 * posts in topic display page, posts search results page, or personal messages.
1340
 *
1341
 * @param resource $messages_request holds a query result
1342
 * @param bool $reset
1343
 *
1344
 * @return integer|boolean
1345
 */
1346
function currentContext($messages_request, $reset = false)
1347
{
1348
	// Can't work with a database without a database :P
1349
	$db = database();
1350
1351
	// Start from the beginning...
1352
	if ($reset)
1353
		return $db->data_seek($messages_request, 0);
1354
1355
	// If the query has already returned false, get out of here
1356
	if (empty($messages_request))
1357
		return false;
1358
1359
	// Attempt to get the next message.
1360
	$message = $db->fetch_assoc($messages_request);
1361
	if (!$message)
1362
	{
1363
		$db->free_result($messages_request);
1364
1365
		return false;
1366
	}
1367
1368
	return $message;
1369
}
1370
1371
/**
1372
 * Helper function to insert an array in to an existing array
1373
 *
1374
 * What it does:
1375
 *
1376
 * - Intended for addon use to allow such things as
1377
 * - Adding in a new menu item to an existing menu array
1378
 *
1379
 * @param mixed[] $input the array we will insert to
1380
 * @param string $key the key in the array that we are looking to find for the insert action
1381
 * @param mixed[] $insert the actual data to insert before or after the key
1382 223
 * @param string $where adding before or after
1383
 * @param bool $assoc if the array is a assoc array with named keys or a basic index array
1384
 * @param bool $strict search for identical elements, this means it will also check the types of the needle.
1385
 */
1386
function elk_array_insert($input, $key, $insert, $where = 'before', $assoc = true, $strict = false)
1387
{
1388
	// Search for key names or values
1389
	if ($assoc)
1390
		$position = array_search($key, array_keys($input), $strict);
1391
	else
1392 48
		$position = array_search($key, $input, $strict);
1393 48
1394
	// If the key is not found, just insert it at the end
1395
	if ($position === false)
1396
		return array_merge($input, $insert);
1397
1398
	if ($where === 'after')
1399
		$position++;
1400
1401
	// Insert as first
1402
	if ($position === 0)
1403
		$input = array_merge($insert, $input);
1404
	else
1405
		$input = array_merge(array_slice($input, 0, $position), $insert, array_slice($input, $position));
1406
1407
	return $input;
1408
}
1409
1410
/**
1411
 * Run a scheduled task now
1412
 *
1413
 * What it does:
1414
 *
1415 6
 * - From time to time it may be necessary to fire a scheduled task ASAP
1416 6
 * - This function sets the scheduled task to be called before any other one
1417
 *
1418
 * @param string $task the name of a scheduled task
1419
 */
1420
function scheduleTaskImmediate($task)
1421
{
1422
	global $modSettings;
1423
1424
	if (!isset($modSettings['scheduleTaskImmediate']))
1425
		$scheduleTaskImmediate = array();
1426
	else
1427
		$scheduleTaskImmediate = Util::unserialize($modSettings['scheduleTaskImmediate']);
1428
1429
	// If it has not been scheduled, the do so now
1430
	if (!isset($scheduleTaskImmediate[$task]))
1431
	{
1432 6
		$scheduleTaskImmediate[$task] = 0;
1433 6
		updateSettings(array('scheduleTaskImmediate' => serialize($scheduleTaskImmediate)));
1434
1435
		require_once(SUBSDIR . '/ScheduledTasks.subs.php');
1436
1437
		// Ensure the task is on
1438
		toggleTaskStatusByName($task, true);
1439
1440
		// Before trying to run it **NOW** :P
1441
		calculateNextTrigger($task, true);
1442
	}
1443
}
1444
1445
/**
1446
 * For diligent people: remove scheduleTaskImmediate when done, otherwise
1447
 * a maximum of 10 executions is allowed
1448
 *
1449
 * @param string $task the name of a scheduled task
1450
 * @param bool $calculateNextTrigger if recalculate the next task to execute
1451
 */
1452
function removeScheduleTaskImmediate($task, $calculateNextTrigger = true)
1453
{
1454
	global $modSettings;
1455
1456
	// Not on, bail
1457
	if (!isset($modSettings['scheduleTaskImmediate']))
1458
		return;
1459
	else
1460
		$scheduleTaskImmediate = Util::unserialize($modSettings['scheduleTaskImmediate']);
1461
1462
	// Clear / remove the task if it was set
1463
	if (isset($scheduleTaskImmediate[$task]))
1464
	{
1465
		unset($scheduleTaskImmediate[$task]);
1466
		updateSettings(array('scheduleTaskImmediate' => serialize($scheduleTaskImmediate)));
1467
1468
		// Recalculate the next task to execute
1469
		if ($calculateNextTrigger)
1470
		{
1471
			require_once(SUBSDIR . '/ScheduledTasks.subs.php');
1472
			calculateNextTrigger($task);
1473
		}
1474
	}
1475
}
1476
1477
/**
1478
 * Helper function to replace commonly used urls in text strings
1479
 *
1480
 * @event integrate_basic_url_replacement add additional place holder replacements
1481
 * @param string $string the string to inject URLs into
1482
 *
1483
 * @return string the input string with the place-holders replaced with
1484
 *           the correct URLs
1485
 */
1486
function replaceBasicActionUrl($string)
1487
{
1488
	global $scripturl, $context, $boardurl;
1489
	static $find_replace = null;
1490
1491
	if ($find_replace === null)
1492
	{
1493
		$find_replace = array(
1494
			'{forum_name}' => $context['forum_name'],
1495
			'{forum_name_html_safe}' => $context['forum_name_html_safe'],
1496
			'{forum_name_html_unsafe}' => un_htmlspecialchars($context['forum_name_html_safe']),
1497
			'{script_url}' => $scripturl,
1498
			'{board_url}' => $boardurl,
1499
			'{login_url}' => $scripturl . '?action=login',
1500
			'{register_url}' => $scripturl . '?action=register',
1501
			'{activate_url}' => $scripturl . '?action=register;sa=activate',
1502
			'{help_url}' => $scripturl . '?action=help',
1503
			'{admin_url}' => $scripturl . '?action=admin',
1504
			'{moderate_url}' => $scripturl . '?action=moderate',
1505
			'{recent_url}' => $scripturl . '?action=recent',
1506
			'{search_url}' => $scripturl . '?action=search',
1507
			'{who_url}' => $scripturl . '?action=who',
1508
			'{credits_url}' => $scripturl . '?action=who;sa=credits',
1509
			'{calendar_url}' => $scripturl . '?action=calendar',
1510
			'{memberlist_url}' => $scripturl . '?action=memberlist',
1511
			'{stats_url}' => $scripturl . '?action=stats',
1512
		);
1513
		call_integration_hook('integrate_basic_url_replacement', array(&$find_replace));
1514
	}
1515
1516
	return str_replace(array_keys($find_replace), array_values($find_replace), $string);
1517
}
1518
1519
/**
1520
 * This function creates a new GenericList from all the passed options.
1521
 *
1522
 * What it does:
1523
 *
1524
 * - Calls integration hook integrate_list_"unique_list_id" to allow easy modifying
1525
 *
1526
 * @event integrate_list_$listID called before every createlist to allow access to its listoptions
1527
 * @param mixed[] $listOptions associative array of option => value
1528
 */
1529
function createList($listOptions)
1530 18
{
1531 12
	call_integration_hook('integrate_list_' . $listOptions['id'], array(&$listOptions));
1532
1533 18
	$list = new Generic_List($listOptions);
1534
1535
	$list->buildList();
1536 18
}
1537 16
1538
/**
1539 6
 * This handy function retrieves a Request instance and passes it on.
1540
 *
1541
 * What it does:
1542
 *
1543
 * - To get hold of a Request, you can use this function or directly Request::instance().
1544
 * - This is for convenience, it simply delegates to Request::instance().
1545
 */
1546
function request()
1547
{
1548
	return Request::instance();
1549
}
1550
1551
/**
1552
 * Meant to replace any usage of $db_last_error.
1553
 *
1554
 * What it does:
1555
 *
1556
 * - Reads the file db_last_error.txt, if a time() is present returns it,
1557
 * otherwise returns 0.
1558
 */
1559
function db_last_error()
1560
{
1561
	$time = trim(file_get_contents(BOARDDIR . '/db_last_error.txt'));
1562
1563
	if (preg_match('~^\d{10}$~', $time) === 1)
1564
		return $time;
1565
	else
1566
		return 0;
1567
}
1568
1569
/**
1570
 * This function has the only task to retrieve the correct prefix to be used
1571
 * in responses.
1572
 *
1573
 * @return string - The prefix in the default language of the forum
1574
 */
1575
function response_prefix()
1576
{
1577
	global $language, $user_info, $txt;
1578
	static $response_prefix = null;
1579
1580
	$cache = Cache::instance();
1581
1582
	// Get a response prefix, but in the forum's default language.
1583
	if ($response_prefix === null && (!$cache->getVar($response_prefix, 'response_prefix') || !$response_prefix))
1584
	{
1585 View Code Duplication
		if ($language === $user_info['language'])
1586
			$response_prefix = $txt['response_prefix'];
1587
		else
1588
		{
1589
			theme()->getTemplates()->loadLanguageFile('index', $language, false);
1590
			$response_prefix = $txt['response_prefix'];
1591
			theme()->getTemplates()->loadLanguageFile('index');
1592
		}
1593
1594
		$cache->put('response_prefix', $response_prefix, 600);
1595
	}
1596
1597
	return $response_prefix;
1598
}
1599
1600
/**
1601
 * A very simple function to determine if an email address is "valid" for Elkarte.
1602
 *
1603
 * - A valid email for ElkArte is something that resembles an email (filter_var) and
1604
 * is less than 255 characters (for database limits)
1605
 *
1606
 * @param string $value - The string to evaluate as valid email
1607
 *
1608
 * @return string|false - The email if valid, false if not a valid email
1609
 */
1610
function isValidEmail($value)
1611
{
1612
	$value = trim($value);
1613
	if (filter_var($value, FILTER_VALIDATE_EMAIL) && Util::strlen($value) < 255)
1614
		return $value;
1615
	else
1616
		return false;
1617
}
1618
1619
/**
1620
 * Adds a protocol (http/s, ftp/mailto) to the beginning of an url if missing
1621
 *
1622
 * @param string $url - The url
1623
 * @param string[] $protocols - A list of protocols to check, the first is
1624
 *                 added if none is found (optional, default array('http://', 'https://'))
1625
 *
1626
 * @return string - The url with the protocol
1627
 */
1628
function addProtocol($url, $protocols = array())
1629
{
1630
	if (empty($protocols))
1631
	{
1632
		$pattern = '~^(http://|https://)~i';
1633
		$protocols = array('http://');
1634
	}
1635
	else
1636
	{
1637
		$pattern = '~^(' . implode('|', array_map(function ($val) {return preg_quote($val, '~');}, $protocols)) . ')~i';
0 ignored issues
show
Coding Style introduced by
It is generally recommended to place each PHP statement on a line by itself.

Let’s take a look at an example:

// Bad
$a = 5; $b = 6; $c = 7;

// Good
$a = 5;
$b = 6;
$c = 7;
Loading history...
1638
	}
1639
1640
	$found = false;
1641
	$url = preg_replace_callback($pattern, function($match) use (&$found) {
1642
		$found = true;
1643
1644
		return strtolower($match[0]);
1645
	}, $url);
1646
1647
	if ($found === true)
1648
	{
1649
			return $url;
1650
	}
1651
1652
	return $protocols[0] . $url;
1653
}
1654
1655
/**
1656
 * Removes nested quotes from a text string.
1657
 *
1658
 * @param string $text - The body we want to remove nested quotes from
1659
 *
1660
 * @return string - The same body, just without nested quotes
1661
 */
1662
function removeNestedQuotes($text)
1663
{
1664
	global $modSettings;
1665
1666
	// Remove any nested quotes, if necessary.
1667
	if (!empty($modSettings['removeNestedQuotes']))
1668
	{
1669
		return preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $text);
1670
	}
1671
	else
1672
	{
1673
		return $text;
1674
	}
1675
}
1676
1677
/**
1678
 * Change a \t to a span that will show a tab
1679
 *
1680
 * @param string $string
1681
 *
1682
 * @return string
1683
 */
1684
function tabToHtmlTab($string)
1685
{
1686
	return str_replace("\t", "<span class=\"tab\">\t</span>", $string);
1687
}
1688
1689
/**
1690
 * Remove <br />
1691
 *
1692
 * @param string $string
1693
 *
1694
 * @return string
1695
 */
1696
function removeBr($string)
1697
{
1698
	return str_replace('<br />', '', $string);
1699
}
1700
1701
/**
1702
 * Are we using this browser?
1703
 *
1704
 * - Wrapper function for detectBrowser
1705
 *
1706
 * @param string $browser  the browser we are checking for.
1707
 */
1708
function isBrowser($browser)
1709
{
1710
	global $context;
1711
1712
	// Don't know any browser!
1713
	if (empty($context['browser']))
1714 8
		detectBrowser();
1715 8
1716
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1717 8
}
1718 6
1719
/**
1720 6
 * Replace all vulgar words with respective proper words. (substring or whole words..)
1721 6
 *
1722 6
 * What it does:
1723 6
 * - it censors the passed string.
1724 6
 * - if the admin setting allow_no_censored is on it does not censor unless force is also set.
1725 6
 * - if the admin setting allow_no_censored is off will censor words unless the user has set
1726 6
 * it to not censor in their profile and force is off
1727 6
 * - it caches the list of censored words to reduce parsing.
1728 6
 * - Returns the censored text
1729 6
 *
1730 6
 * @param string $text
1731 6
 * @param bool $force = false
1732 6
 */
1733 6
function censor($text, $force = false)
1734 6
{
1735 6
	global $modSettings;
1736 6
	static $censor = null;
1737 6
1738 4
	if ($censor === null)
1739 6
	{
1740 4
		$censor = new Censor(explode("\n", $modSettings['censor_vulgar']), explode("\n", $modSettings['censor_proper']), $modSettings);
1741
	}
1742 8
1743
	return $censor->censor($text, $force);
1744
}
1745
1746
/**
1747
 * Helper function able to determine if the current member can see at least
1748
 * one button of a button strip.
1749
 *
1750
 * @param mixed[] $button_strip
1751
 *
1752
 * @return bool
1753
 */
1754
function can_see_button_strip($button_strip)
1755
{
1756
	global $context;
1757
1758
	foreach ($button_strip as $key => $value)
1759
	{
1760
		if (!isset($value['test']) || !empty($context[$value['test']]))
1761
			return true;
1762
	}
1763
1764
	return false;
1765
}
1766
1767
/**
1768
 * @return Themes\DefaultTheme\Theme
1769
 */
1770
function theme()
1771
{
1772
	return $GLOBALS['context']['theme_instance'];
1773 108
}
1774
1775
/**
1776
 * Stops the execution with a 1x1 gif file
1777
 *
1778
 * @param bool $expired Sends an expired header.
1779
 */
1780
function dieGif($expired = false)
1781
{
1782
	// The following logging is just for debug, it should be removed before final
1783
	// or at least once the bug is fixes #2391
1784
	$filename = '';
1785
	$linenum = '';
1786
	if (headers_sent($filename, $linenum))
1787
	{
1788
		if (empty($filename))
1789
		{
1790
			ob_clean();
1791
		}
1792
		else
1793
		{
1794
			Errors::instance()->log_error('Headers already sent in ' . $filename . ' at line ' . $linenum);
1795
		}
1796
	}
1797
1798
	if ($expired === true)
1799
	{
1800
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
1801 6
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
1802 6
	}
1803
1804 6
	header('Content-Type: image/gif');
1805
	die("\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B");
0 ignored issues
show
Coding Style Compatibility introduced by
The function dieGif() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1806
}
1807 6
1808 4
/**
1809 6
 * Prepare ob_start with or without gzip compression
1810 6
 *
1811
 * @param bool $use_compression Starts compressed headers.
1812
 */
1813
function obStart($use_compression = false)
1814
{
1815
	// This is done to clear any output that was made before now.
1816
	while (ob_get_level() > 0)
1817
	{
1818 6
		@ob_end_clean();
1819 4
	}
1820
1821 6
	if ($use_compression === true)
1822
	{
1823
		ob_start('ob_gzhandler');
1824
	}
1825
	else
1826
	{
1827
		ob_start();
1828
		header('Content-Encoding: none');
1829
	}
1830
}