Completed
Branch development (176841)
by Elk
06:59
created

Subs.php ➔ standardTime()   F

Complexity

Conditions 38
Paths 12168

Size

Total Lines 89

Duplication

Lines 4
Ratio 4.49 %

Code Coverage

Tests 30
CRAP Score 77.8916

Importance

Changes 0
Metric Value
cc 38
nc 12168
nop 3
dl 4
loc 89
rs 0
c 0
b 0
f 0
ccs 30
cts 43
cp 0.6977
crap 77.8916

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

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
315
 */
316 4
function comma_format($number, $override_decimal_count = false)
317 4
{
318
	global $txt;
319
	static $thousands_separator = null, $decimal_separator = null, $decimal_count = null;
320 4
321
	// Cache these values...
322
	if ($decimal_separator === null)
323 2
	{
324
		// Not set for whatever reason?
325
		if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1)
326
			return $number;
327 2
328 2
		// Cache these each load...
329 2
		$thousands_separator = $matches[1];
330
		$decimal_separator = $matches[2];
331
		$decimal_count = strlen($matches[3]);
332
	}
333 4
334
	// Format the string with our friend, number_format.
335
	return number_format($number, (float) $number === $number ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator);
336
}
337
338
/**
339
 * Formats a number to a multiple of thousands x, x k, x M, x G, x T
340
 *
341
 * @param float $number The value to format
342
 * @param integer|bool $override_decimal_count = false or number of decimals
343
 *
344
 * @return string
345
 */
346
function thousands_format($number, $override_decimal_count = false)
347
{
348
	foreach (array('', ' k', ' M', ' G', ' T') as $kb)
349
	{
350
		if ($number < 1000)
351
		{
352
			break;
353
		}
354
355
		$number /= 1000;
356
	}
357
358
	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 348. 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...
359
}
360
361
/**
362
 * Formats a number to a computer byte size value xB, xKB, xMB, xGB
363
 *
364
 * @param int $number
365
 *
366
 * @return string
367
 */
368
function byte_format($number)
369
{
370
	global $txt;
371
372
	$kb = '';
373
	foreach (array('byte', 'kilobyte', 'megabyte', 'gigabyte') as $kb)
374
	{
375
		if ($number < 1024)
376
		{
377
			break;
378
		}
379
380
		$number /= 1024;
381
	}
382
383
	return comma_format($number) . ' ' . $txt[$kb];
384
}
385
386
/**
387
 * Format a time to make it look purdy.
388
 *
389
 * What it does:
390
 *
391
 * - Returns a pretty formatted version of time based on the user's format in User::$info->time_format.
392
 * - Applies all necessary time offsets to the timestamp, unless offset_type is set.
393
 * - If todayMod is set and show_today was not not specified or true, an
394
 *   alternate format string is used to show the date with something to show it is "today" or "yesterday".
395
 * - Performs localization (more than just strftime would do alone.)
396
 *
397
 * @param int $log_time A unix timestamp
398
 * @param string|bool $show_today = true show "Today"/"Yesterday",
399
 *   false shows the date, a string can force a date format to use %b %d, %Y
400
 * @param string|bool $offset_type = false If false, uses both user time offset and forum offset.
401
 *   If 'forum', uses only the forum offset. Otherwise no offset is applied.
402
 *
403
 * @return string
404
 */
405 12
function standardTime($log_time, $show_today = true, $offset_type = false)
406 12
{
407
	global $txt, $modSettings;
408 12
	static $non_twelve_hour, $is_win = null;
409
410 2
	if ($is_win === null)
411
	{
412
		$is_win = detectServer()->is('windows');
413
	}
414 12
415 6
	// Offset the time.
416
	if (!$offset_type)
417 10
		$time = $log_time + (User::$info->time_offset + $modSettings['time_offset']) * 3600;
0 ignored issues
show
Documentation introduced by
The property time_offset does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
418
	// Just the forum offset?
419
	elseif ($offset_type === 'forum')
420 10
		$time = $log_time + $modSettings['time_offset'] * 3600;
421
	else
422
		$time = $log_time;
423 12
424
	// We can't have a negative date (on Windows, at least.)
425
	if ($log_time < 0)
426
		$log_time = 0;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $log_time. This often makes code more readable.
Loading history...
427 12
428
	// Today and Yesterday?
429
	if ($modSettings['todayMod'] >= 1 && $show_today === true)
430 12
	{
431
		// Get the current time.
432 12
		$nowtime = forum_time();
433 12
434
		$then = @getdate($time);
435
		$now = @getdate($nowtime);
436 12
437 12
		// Try to make something of a time format string...
438
		$s = strpos(User::$info->time_format, '%S') === false ? '' : ':%S';
0 ignored issues
show
Documentation introduced by
The property time_format does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
439 12
		if (strpos(User::$info->time_format, '%H') === false && strpos(User::$info->time_format, '%T') === false)
0 ignored issues
show
Documentation introduced by
The property time_format does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
440 12
		{
441
			$h = strpos(User::$info->time_format, '%l') === false ? '%I' : '%l';
0 ignored issues
show
Documentation introduced by
The property time_format does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
442
			$today_fmt = $h . ':%M' . $s . ' %p';
443
		}
444
		else
445
			$today_fmt = '%H:%M' . $s;
446 12
447 6
		// Same day of the year, same year.... Today!
448
		if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
449
			return sprintf($txt['today'], standardTime($log_time, $today_fmt, $offset_type));
450 6
451
		// 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...
452
		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))
453
			return sprintf($txt['yesterday'], standardTime($log_time, $today_fmt, $offset_type));
454 12
	}
455
456
	$str = !is_bool($show_today) ? $show_today : User::$info->time_format;
0 ignored issues
show
Documentation introduced by
The property time_format does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
457
458 12
	// Windows requires a slightly different language code identifier (LCID).
459
	// https://msdn.microsoft.com/en-us/library/cc233982.aspx
460
	if ($is_win)
461
	{
462
		$txt['lang_locale'] = strtr($txt['lang_locale'], '_', '-');
463 12
	}
464
465
	if (setlocale(LC_TIME, $txt['lang_locale']))
466
	{
467
		if (!isset($non_twelve_hour))
468
			$non_twelve_hour = trim(strftime('%p')) === '';
469 View Code Duplication
		if ($non_twelve_hour && strpos($str, '%p') !== false)
470
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
471
472
		foreach (array('%a', '%A', '%b', '%B') as $token)
473
			if (strpos($str, $token) !== false)
474
				$str = str_replace($token, !empty($txt['lang_capitalize_dates']) ? ElkArte\Util::ucwords(strftime($token, $time)) : strftime($token, $time), $str);
475
	}
476
	else
477 12
	{
478 12
		// Do-it-yourself time localization.  Fun.
479 6
		foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label)
480
			if (strpos($str, $token) !== false)
481 12
				$str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str);
482 6
483 View Code Duplication
		if (strpos($str, '%p') !== false)
484
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
485
	}
486 12
487
	// Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that.
488
	if ($is_win && strpos($str, '%e') !== false)
489
		$str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str);
490 12
491
	// Format any other characters..
492
	return strftime($str, $time);
493
}
494
495
/**
496
 * Used to render a timestamp to html5 <time> tag format.
497
 *
498
 * @param int $timestamp A unix timestamp
499
 *
500
 * @return string
501
 */
502 10
function htmlTime($timestamp)
503
{
504 10
	global $txt, $context;
505
506
	if (empty($timestamp))
507 10
		return '';
508 10
509 10
	$timestamp = forum_time(true, $timestamp);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $timestamp. This often makes code more readable.
Loading history...
510
	$time = date('Y-m-d H:i', $timestamp);
511
	$stdtime = standardTime($timestamp, true, true);
512 10
513
	// @todo maybe htmlspecialchars on the title attribute?
514
	return '<time title="' . (!empty($context['using_relative_time']) ? $stdtime : $txt['last_post']) . '" datetime="' . $time . '" data-timestamp="' . $timestamp . '">' . $stdtime . '</time>';
515
}
516
517
/**
518
 * Gets the current time with offset.
519
 *
520
 * What it does:
521
 *
522
 * - Always applies the offset in the time_offset setting.
523
 *
524
 * @param bool $use_user_offset = true if use_user_offset is true, applies the user's offset as well
525
 * @param int|null $timestamp = null A unix timestamp (null to use current time)
526
 *
527
 * @return int seconds since the unix epoch
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
528
 */
529 13
function forum_time($use_user_offset = true, $timestamp = null)
530
{
531 13
	global $modSettings;
532 13
533 12
	if ($timestamp === null)
534
		$timestamp = time();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $timestamp. This often makes code more readable.
Loading history...
535
	elseif ($timestamp == 0)
536 13
		return 0;
537
538
	return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? User::$info->time_offset : 0)) * 3600;
0 ignored issues
show
Documentation introduced by
The property time_offset does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
539
}
540
541
/**
542
 * Removes special entities from strings.  Compatibility...
543
 *
544
 * - Faster than html_entity_decode
545
 * - Removes the base entities ( &amp; &quot; &#039; &lt; and &gt;. ) from text with htmlspecialchars_decode
546
 * - Additionally converts &nbsp with str_replace
547
 *
548
 * @param string $string The string to apply htmlspecialchars_decode
549
 *
550
 * @return string string without entities
551
 */
552 44
function un_htmlspecialchars($string)
553 44
{
554
	$string = htmlspecialchars_decode($string, ENT_QUOTES);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $string. This often makes code more readable.
Loading history...
555 44
	$string = str_replace('&nbsp;', ' ', $string);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $string. This often makes code more readable.
Loading history...
556
557
	return $string;
558
}
559
560
/**
561
 * Lexicographic permutation function.
562
 *
563
 * This is a special type of permutation which involves the order of the set. The next
564
 * lexicographic permutation of '32541' is '34125'. Numerically, it is simply the smallest
565
 * set larger than the current one.
566
 *
567
 * The benefit of this over a recursive solution is that the whole list does NOT need
568
 * to be held in memory. So it's actually possible to run 30! permutations without
569
 * causing a memory overflow.
570
 *
571
 * Source: O'Reilly PHP Cookbook
572
 *
573
 * @param mixed[] $p The array keys to apply permutation
574
 * @param int $size The size of our permutation array
575
 *
576
 * @return mixed[]|bool the next permutation of the passed array $p
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
577
 */
578
function pc_next_permutation($p, $size)
579 4
{
580
	// Slide down the array looking for where we're smaller than the next guy
581
	for ($i = $size - 1; isset($p[$i]) && $p[$i] >= $p[$i + 1]; --$i)
582
	{
583
	}
584
585 4
	// If this doesn't occur, we've finished our permutations
586
	// the array is reversed: (1, 2, 3, 4) => (4, 3, 2, 1)
587 4
	if ($i === -1)
588
	{
589
		return false;
590
	}
591 4
592
	// Slide down the array looking for a bigger number than what we found before
593
	for ($j = $size; $p[$j] <= $p[$i]; --$j)
594
	{
595
	}
596 4
597 4
	// Swap them
598 4
	$tmp = $p[$i];
599
	$p[$i] = $p[$j];
600
	$p[$j] = $tmp;
601 4
602
	// Now reverse the elements in between by swapping the ends
603 4
	for (++$i, $j = $size; $i < $j; ++$i, --$j)
604 4
	{
605 4
		$tmp = $p[$i];
606
		$p[$i] = $p[$j];
607
		$p[$j] = $tmp;
608 4
	}
609
610
	return $p;
611
}
612
613
/**
614
 * Ends execution and redirects the user to a new location
615
 *
616
 * What it does:
617
 *
618
 * - Makes sure the browser doesn't come back and repost the form data.
619
 * - Should be used whenever anything is posted.
620
 * - Calls AddMailQueue to process any mail queue items its can
621
 * - Calls call_integration_hook integrate_redirect before headers are sent
622
 * - Diverts final execution to obExit() which means a end to processing and sending of final output
623
 *
624
 * @event integrate_redirect called before headers are sent
625
 * @param string $setLocation = '' The URL to redirect to
626
 * @param bool $refresh = false, enable to send a refresh header, default is a location header
627
 * @throws \ElkArte\Exceptions\Exception
628
 */
629
function redirectexit($setLocation = '', $refresh = false)
630
{
631
	global $scripturl, $context, $modSettings, $db_show_debug;
632
633
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
634
	if (!empty($context['flush_mail']))
635
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
636
		AddMailQueue(true);
637
638
	\ElkArte\Notifications::instance()->send();
639
640
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
641
642
	if ($add)
643
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $setLocation. This often makes code more readable.
Loading history...
644
645
	// Put the session ID in.
646 View Code Duplication
	if (empty($_COOKIE) && defined('SID') && SID != '')
647
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $setLocation. This often makes code more readable.
Loading history...
648
	// Keep that debug in their for template debugging!
649
	elseif (isset($_GET['debug']))
650
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $setLocation. This often makes code more readable.
Loading history...
651
652
	// Maybe integrations want to change where we are heading?
653
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh));
654
655
	// We send a Refresh header only in special cases because Location looks better. (and is quicker...)
656
	if ($refresh)
657
		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.

16 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/ElkArte/AdminController/Admin.php on line 987
  1. Fetching key HTTP_REFERER from $_SERVER, and $_SERVER['HTTP_REFERER'] is passed to redirectexit()
    in sources/ElkArte/AdminController/Admin.php on line 987
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  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/ElkArte/Controller/Karma.php on line 179
  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/ElkArte/Controller/Karma.php on line 179
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  3. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in sources/ElkArte/Controller/Post.php on line 1163
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in sources/ElkArte/Controller/Post.php on line 1163
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  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/ElkArte/Controller/Search.php on line 61
  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/ElkArte/Controller/Search.php on line 61
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  5. Path: Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and 'action=memberlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit() in sources/ElkArte/Controller/Search.php on line 68
  1. Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and 'action=memberlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit()
    in sources/ElkArte/Controller/Search.php on line 68
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  6. Path: Read from $_GET, and $_GET is passed through key(), and 'wwwRedirect;' . key($_GET) . '=' . current($_GET) is passed to redirectexit() in sources/ElkArte/Themes/ThemeLoader.php on line 563
  1. Read from $_GET, and $_GET is passed through key(), and 'wwwRedirect;' . key($_GET) . '=' . current($_GET) is passed to redirectexit()
    in sources/ElkArte/Themes/ThemeLoader.php on line 563
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  7. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in sources/Load.php on line 222
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in sources/Load.php on line 222
  2. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  8. Path: Read from $_REQUEST, and $context is assigned in sources/ElkArte/Controller/Post.php on line 320
  1. Read from $_REQUEST, and $context is assigned
    in sources/ElkArte/Controller/Post.php on line 320
  2. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 351
  3. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 352
  4. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 353
  5. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 366
  6. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 367
  7. $context['name'] is passed to ValuesContainer::__set()
    in sources/ElkArte/Controller/Post.php on line -1
  8. ValuesContainer::$data is assigned
    in sources/ElkArte/ValuesContainer.php on line 53
  9. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  10. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  11. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  9. Path: Read from $_POST in sources/ElkArte/Controller/Post.php on line 946
  1. Read from $_POST
    in sources/ElkArte/Controller/Post.php on line 946
  2. $_POST['guestname'] is passed to ValuesContainer::__set()
    in sources/ElkArte/Controller/Post.php on line -1
  3. ValuesContainer::$data is assigned
    in sources/ElkArte/ValuesContainer.php on line 53
  4. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  5. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  6. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  10. Path: Read from $_POST, and $_POST is passed to DataValidator::is_valid() in sources/ElkArte/Controller/Post.php on line 900
  1. Read from $_POST, and $_POST is passed to DataValidator::is_valid()
    in sources/ElkArte/Controller/Post.php on line 900
  2. $data is passed to DataValidator::validate()
    in sources/ElkArte/DataValidator.php on line 155
  3. DataValidator::$_data is assigned
    in sources/ElkArte/DataValidator.php on line 268
  4. Tainted property DataValidator::$_data is read
    in sources/ElkArte/DataValidator.php on line 305
  5. DataValidator::validation_data() returns tainted data
    in sources/ElkArte/HttpReq.php on line 385
  6. HttpReq::cleanValue() returns tainted data, and HttpReq::$_param is assigned
    in sources/ElkArte/HttpReq.php on line 225
  7. Tainted property HttpReq::$_param is read
    in sources/ElkArte/HttpReq.php on line 290
  8. HttpReq::getQuery() returns tainted data, and $start is assigned
    in sources/ElkArte/AdminController/BadBehavior.php on line 222
  9. $redirect_path . ';start=' . $start . (!empty($filter) ? $filter['href'] : '') is passed to redirectexit()
    in sources/ElkArte/AdminController/BadBehavior.php on line 225
  10. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  11. Path: Read from $_REQUEST, and $_REQUEST['params'] ?? '' is passed to SearchParams::__construct() in sources/ElkArte/Controller/Search.php on line 151
  1. Read from $_REQUEST, and $_REQUEST['params'] ?? '' is passed to SearchParams::__construct()
    in sources/ElkArte/Controller/Search.php on line 151
  2. SearchParams::$_search_string is assigned
    in sources/ElkArte/Search/SearchParams.php on line 100
  3. Tainted property SearchParams::$_search_string is read, and $this->_search_string is passed through str_replace(), and str_replace(array('-', '_', '.'), array('+', '/', '='), $this->_search_string) is decoded by base64_decode(), and $temp_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 160
  4. !empty($temp_params2) ? $temp_params2 : $temp_params is passed through explode(), and $temp_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 164
  5. $data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 166
  6. $data is passed through explode(), and explode('|\'|', $data) is passed through array_pad(), and $v is assigned
    in sources/ElkArte/Search/SearchParams.php on line 168
  7. SearchParams::$_search_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 169
  8. Tainted property SearchParams::$_search_params is read, and ValuesContainer::$data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 102
  9. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  10. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  11. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  12. Path: Read from $_REQUEST, and $_REQUEST['params'] ?? '' is passed to SearchParams::__construct() in sources/ElkArte/Controller/Search.php on line 280
  1. Read from $_REQUEST, and $_REQUEST['params'] ?? '' is passed to SearchParams::__construct()
    in sources/ElkArte/Controller/Search.php on line 280
  2. SearchParams::$_search_string is assigned
    in sources/ElkArte/Search/SearchParams.php on line 100
  3. Tainted property SearchParams::$_search_string is read, and $this->_search_string is passed through str_replace(), and str_replace(array('-', '_', '.'), array('+', '/', '='), $this->_search_string) is decoded by base64_decode(), and $temp_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 160
  4. !empty($temp_params2) ? $temp_params2 : $temp_params is passed through explode(), and $temp_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 164
  5. $data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 166
  6. $data is passed through explode(), and explode('|\'|', $data) is passed through array_pad(), and $v is assigned
    in sources/ElkArte/Search/SearchParams.php on line 168
  7. SearchParams::$_search_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 169
  8. Tainted property SearchParams::$_search_params is read, and ValuesContainer::$data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 102
  9. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  10. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  11. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  13. Path: Read from $_REQUEST, and $_REQUEST is passed to SearchParams::merge() in sources/ElkArte/Controller/Search.php on line 281
  1. Read from $_REQUEST, and $_REQUEST is passed to SearchParams::merge()
    in sources/ElkArte/Controller/Search.php on line 281
  2. SearchParams::$_search_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 256
  3. Tainted property SearchParams::$_search_params is read, and ValuesContainer::$data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 102
  4. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  5. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  6. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  14. Path: Read from $_POST, and SearchParams::$_search_params is assigned in sources/ElkArte/Search/SearchParams.php on line 478
  1. Read from $_POST, and SearchParams::$_search_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 478
  2. Tainted property SearchParams::$_search_params is read, and ValuesContainer::$data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 102
  3. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  4. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  5. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  15. Path: Read from $_REQUEST, and $_REQUEST['guestname'] is passed through trim(), and $context is assigned in sources/ElkArte/Controller/Post.php on line 366
  1. Read from $_REQUEST, and $_REQUEST['guestname'] is passed through trim(), and $context is assigned
    in sources/ElkArte/Controller/Post.php on line 366
  2. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 367
  3. $context['name'] is passed to ValuesContainer::__set()
    in sources/ElkArte/Controller/Post.php on line -1
  4. ValuesContainer::$data is assigned
    in sources/ElkArte/ValuesContainer.php on line 53
  5. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  6. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  7. $setLocation is passed through strtr()
    in sources/Subs.php on line 657
  16. Path: Read from $_REQUEST, and $_REQUEST['email'] is passed through trim(), and $context is assigned in sources/ElkArte/Controller/Post.php on line 367
  1. Read from $_REQUEST, and $_REQUEST['email'] is passed through trim(), and $context is assigned
    in sources/ElkArte/Controller/Post.php on line 367
  2. $context['name'] is passed to ValuesContainer::__set()
    in sources/ElkArte/Controller/Post.php on line -1
  3. ValuesContainer::$data is assigned
    in sources/ElkArte/ValuesContainer.php on line 53
  4. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  5. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  6. $setLocation is passed through strtr()
    in sources/Subs.php on line 657

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...
658
	else
659
		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.

16 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/ElkArte/AdminController/Admin.php on line 987
  1. Fetching key HTTP_REFERER from $_SERVER, and $_SERVER['HTTP_REFERER'] is passed to redirectexit()
    in sources/ElkArte/AdminController/Admin.php on line 987
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  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/ElkArte/Controller/Karma.php on line 179
  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/ElkArte/Controller/Karma.php on line 179
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  3. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in sources/ElkArte/Controller/Post.php on line 1163
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in sources/ElkArte/Controller/Post.php on line 1163
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  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/ElkArte/Controller/Search.php on line 61
  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/ElkArte/Controller/Search.php on line 61
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  5. Path: Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and 'action=memberlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit() in sources/ElkArte/Controller/Search.php on line 68
  1. Read from $_REQUEST, and $_REQUEST['search'] is escaped by urlencode() for all (url-encoded) context(s), and 'action=memberlist;sa=search;fields=name,email;search=' . urlencode($_REQUEST['search']) is passed to redirectexit()
    in sources/ElkArte/Controller/Search.php on line 68
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  6. Path: Read from $_GET, and $_GET is passed through key(), and 'wwwRedirect;' . key($_GET) . '=' . current($_GET) is passed to redirectexit() in sources/ElkArte/Themes/ThemeLoader.php on line 563
  1. Read from $_GET, and $_GET is passed through key(), and 'wwwRedirect;' . key($_GET) . '=' . current($_GET) is passed to redirectexit()
    in sources/ElkArte/Themes/ThemeLoader.php on line 563
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  7. Path: Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit() in sources/Load.php on line 222
  1. Read from $_REQUEST, and 'topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg'] is passed to redirectexit()
    in sources/Load.php on line 222
  2. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  8. Path: Read from $_REQUEST, and $context is assigned in sources/ElkArte/Controller/Post.php on line 320
  1. Read from $_REQUEST, and $context is assigned
    in sources/ElkArte/Controller/Post.php on line 320
  2. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 351
  3. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 352
  4. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 353
  5. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 366
  6. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 367
  7. $context['name'] is passed to ValuesContainer::__set()
    in sources/ElkArte/Controller/Post.php on line -1
  8. ValuesContainer::$data is assigned
    in sources/ElkArte/ValuesContainer.php on line 53
  9. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  10. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  11. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  9. Path: Read from $_POST in sources/ElkArte/Controller/Post.php on line 946
  1. Read from $_POST
    in sources/ElkArte/Controller/Post.php on line 946
  2. $_POST['guestname'] is passed to ValuesContainer::__set()
    in sources/ElkArte/Controller/Post.php on line -1
  3. ValuesContainer::$data is assigned
    in sources/ElkArte/ValuesContainer.php on line 53
  4. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  5. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  6. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  10. Path: Read from $_POST, and $_POST is passed to DataValidator::is_valid() in sources/ElkArte/Controller/Post.php on line 900
  1. Read from $_POST, and $_POST is passed to DataValidator::is_valid()
    in sources/ElkArte/Controller/Post.php on line 900
  2. $data is passed to DataValidator::validate()
    in sources/ElkArte/DataValidator.php on line 155
  3. DataValidator::$_data is assigned
    in sources/ElkArte/DataValidator.php on line 268
  4. Tainted property DataValidator::$_data is read
    in sources/ElkArte/DataValidator.php on line 305
  5. DataValidator::validation_data() returns tainted data
    in sources/ElkArte/HttpReq.php on line 385
  6. HttpReq::cleanValue() returns tainted data, and HttpReq::$_param is assigned
    in sources/ElkArte/HttpReq.php on line 225
  7. Tainted property HttpReq::$_param is read
    in sources/ElkArte/HttpReq.php on line 290
  8. HttpReq::getQuery() returns tainted data, and $start is assigned
    in sources/ElkArte/AdminController/BadBehavior.php on line 222
  9. $redirect_path . ';start=' . $start . (!empty($filter) ? $filter['href'] : '') is passed to redirectexit()
    in sources/ElkArte/AdminController/BadBehavior.php on line 225
  10. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  11. Path: Read from $_REQUEST, and $_REQUEST['params'] ?? '' is passed to SearchParams::__construct() in sources/ElkArte/Controller/Search.php on line 151
  1. Read from $_REQUEST, and $_REQUEST['params'] ?? '' is passed to SearchParams::__construct()
    in sources/ElkArte/Controller/Search.php on line 151
  2. SearchParams::$_search_string is assigned
    in sources/ElkArte/Search/SearchParams.php on line 100
  3. Tainted property SearchParams::$_search_string is read, and $this->_search_string is passed through str_replace(), and str_replace(array('-', '_', '.'), array('+', '/', '='), $this->_search_string) is decoded by base64_decode(), and $temp_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 160
  4. !empty($temp_params2) ? $temp_params2 : $temp_params is passed through explode(), and $temp_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 164
  5. $data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 166
  6. $data is passed through explode(), and explode('|\'|', $data) is passed through array_pad(), and $v is assigned
    in sources/ElkArte/Search/SearchParams.php on line 168
  7. SearchParams::$_search_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 169
  8. Tainted property SearchParams::$_search_params is read, and ValuesContainer::$data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 102
  9. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  10. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  11. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  12. Path: Read from $_REQUEST, and $_REQUEST['params'] ?? '' is passed to SearchParams::__construct() in sources/ElkArte/Controller/Search.php on line 280
  1. Read from $_REQUEST, and $_REQUEST['params'] ?? '' is passed to SearchParams::__construct()
    in sources/ElkArte/Controller/Search.php on line 280
  2. SearchParams::$_search_string is assigned
    in sources/ElkArte/Search/SearchParams.php on line 100
  3. Tainted property SearchParams::$_search_string is read, and $this->_search_string is passed through str_replace(), and str_replace(array('-', '_', '.'), array('+', '/', '='), $this->_search_string) is decoded by base64_decode(), and $temp_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 160
  4. !empty($temp_params2) ? $temp_params2 : $temp_params is passed through explode(), and $temp_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 164
  5. $data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 166
  6. $data is passed through explode(), and explode('|\'|', $data) is passed through array_pad(), and $v is assigned
    in sources/ElkArte/Search/SearchParams.php on line 168
  7. SearchParams::$_search_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 169
  8. Tainted property SearchParams::$_search_params is read, and ValuesContainer::$data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 102
  9. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  10. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  11. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  13. Path: Read from $_REQUEST, and $_REQUEST is passed to SearchParams::merge() in sources/ElkArte/Controller/Search.php on line 281
  1. Read from $_REQUEST, and $_REQUEST is passed to SearchParams::merge()
    in sources/ElkArte/Controller/Search.php on line 281
  2. SearchParams::$_search_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 256
  3. Tainted property SearchParams::$_search_params is read, and ValuesContainer::$data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 102
  4. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  5. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  6. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  14. Path: Read from $_POST, and SearchParams::$_search_params is assigned in sources/ElkArte/Search/SearchParams.php on line 478
  1. Read from $_POST, and SearchParams::$_search_params is assigned
    in sources/ElkArte/Search/SearchParams.php on line 478
  2. Tainted property SearchParams::$_search_params is read, and ValuesContainer::$data is assigned
    in sources/ElkArte/Search/SearchParams.php on line 102
  3. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  4. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  5. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  15. Path: Read from $_REQUEST, and $_REQUEST['guestname'] is passed through trim(), and $context is assigned in sources/ElkArte/Controller/Post.php on line 366
  1. Read from $_REQUEST, and $_REQUEST['guestname'] is passed through trim(), and $context is assigned
    in sources/ElkArte/Controller/Post.php on line 366
  2. $context is assigned
    in sources/ElkArte/Controller/Post.php on line 367
  3. $context['name'] is passed to ValuesContainer::__set()
    in sources/ElkArte/Controller/Post.php on line -1
  4. ValuesContainer::$data is assigned
    in sources/ElkArte/ValuesContainer.php on line 53
  5. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  6. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  7. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659
  16. Path: Read from $_REQUEST, and $_REQUEST['email'] is passed through trim(), and $context is assigned in sources/ElkArte/Controller/Post.php on line 367
  1. Read from $_REQUEST, and $_REQUEST['email'] is passed through trim(), and $context is assigned
    in sources/ElkArte/Controller/Post.php on line 367
  2. $context['name'] is passed to ValuesContainer::__set()
    in sources/ElkArte/Controller/Post.php on line -1
  3. ValuesContainer::$data is assigned
    in sources/ElkArte/ValuesContainer.php on line 53
  4. Tainted property ValuesContainer::$data is read
    in sources/ElkArte/ValuesContainer.php on line 65
  5. ValuesContainer::__get() returns tainted data, and 'action=profile;area=showlikes;sa=given;u=' . $this->user->id is passed to redirectexit()
    in sources/ElkArte/Controller/Likes.php on line 189
  6. $setLocation is passed through str_replace()
    in sources/Subs.php on line 659

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...
660
661
	// Debugging.
662
	if ($db_show_debug === true)
663
	{
664
		$_SESSION['debug_redirect'] = \ElkArte\Debug::instance()->get_db();
665
	}
666
667
	obExit(false);
668
}
669
670
/**
671
 * Ends execution.
672
 *
673
 * What it does:
674
 *
675
 * - Takes care of template loading and remembering the previous URL.
676
 * - Calls ob_start() with ob_sessrewrite to fix URLs if necessary.
677
 *
678
 * @event integrate_invalid_old_url allows adding to "from" urls we don't save
679
 * @event integrate_exit inform portal, etc. that we're integrated with to exit
680
 * @param bool|null $header = null Output the header
681
 * @param bool|null $do_footer = null Output the footer
682
 * @param bool $from_index = false If we're coming from index.php
683
 * @param bool $from_fatal_error = false If we are exiting due to a fatal error
684
 *
685
 */
686
function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false)
687
{
688
	global $context, $txt, $db_show_debug;
689
690
	static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false;
691
692
	// Attempt to prevent a recursive loop.
693
	++$level;
694
	if ($level > 1 && !$from_fatal_error && !$has_fatal_error)
695
		exit;
696
697
	if ($from_fatal_error)
698
		$has_fatal_error = true;
699
700
	// Clear out the stat cache.
701
	trackStats();
702
703
	\ElkArte\Notifications::instance()->send();
704
705
	// If we have mail to send, send it.
706
	if (!empty($context['flush_mail']))
707
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
708
		AddMailQueue(true);
709
710
	$do_header = $header === null ? !$header_done : $header;
711
	if ($do_footer === null)
712
		$do_footer = $do_header;
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $do_footer. This often makes code more readable.
Loading history...
713
714
	// Has the template/header been done yet?
715
	if ($do_header)
716
	{
717
		// Was the page title set last minute? Also update the HTML safe one.
718
		if (!empty($context['page_title']) && empty($context['page_title_html_safe']))
719
			$context['page_title_html_safe'] = ElkArte\Util::htmlspecialchars(un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
720
721
		// Start up the session URL fixer.
722
		ob_start('ob_sessrewrite');
723
724
		call_integration_buffer();
725
726
		// Display the screen in the logical order.
727
		template_header();
728
		$header_done = true;
729
	}
730
731
	if ($do_footer)
732
	{
733
		// Show the footer.
734
		theme()->getTemplates()->loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main');
735
736
		// Just so we don't get caught in an endless loop of errors from the footer...
737
		if (!$footer_done)
738
		{
739
			$footer_done = true;
740
			template_footer();
741
742
			// Add $db_show_debug = true; to Settings.php if you want to show the debugging information.
743
			// (since this is just debugging... it's okay that it's after </html>.)
744
			if ($db_show_debug === true)
745
			{
746
				if (!isset($_REQUEST['xml']) && ((!isset($_GET['action']) || $_GET['action'] != 'viewquery') && !isset($_GET['api'])))
747
				{
748
					\ElkArte\Debug::instance()->display();
749
				}
750
			}
751
		}
752
	}
753
754
	// Need user agent
755
	$req = request();
756
757
	setOldUrl();
758
759
	// For session check verification.... don't switch browsers...
760
	$_SESSION['USER_AGENT'] = $req->user_agent();
761
762
	// Hand off the output to the portal, etc. we're integrated with.
763
	call_integration_hook('integrate_exit', array($do_footer));
764
765
	// Don't exit if we're coming from index.php; that will pass through normally.
766
	if (!$from_index)
767
		exit;
768
}
769
770
function setOldUrl($index = 'old_url')
771
{
772
	// Remember this URL in case someone doesn't like sending HTTP_REFERER.
773
	$invalid_old_url = array(
774
		'action=dlattach',
775
		'action=jsoption',
776
		';xml',
777
		';api',
778
	);
779
	call_integration_hook('integrate_invalid_old_url', array(&$invalid_old_url));
780
	$make_old = true;
781
	foreach ($invalid_old_url as $url)
782
	{
783
		if (strpos($_SERVER['REQUEST_URL'], $url) !== false)
784
		{
785
			$make_old = false;
786
			break;
787
		}
788
	}
789
	if ($make_old === true)
790
	{
791
		$_SESSION[$index] = $_SERVER['REQUEST_URL'];
792
	}
793
}
794
795
/**
796
 * Sets the class of the current topic based on is_very_hot, veryhot, hot, etc
797
 *
798
 * @param mixed[] $topic_context array of topic information
799
 */
800
function determineTopicClass(&$topic_context)
801 2
{
802
	// Set topic class depending on locked status and number of replies.
803 2
	if ($topic_context['is_very_hot'])
804
		$topic_context['class'] = 'veryhot';
805
	elseif ($topic_context['is_hot'])
806 2
		$topic_context['class'] = 'hot';
807
	else
808 2
		$topic_context['class'] = 'normal';
809
810 2
	$topic_context['class'] .= !empty($topic_context['is_poll']) ? '_poll' : '_post';
811
812
	if ($topic_context['is_locked'])
813 2
		$topic_context['class'] .= '_locked';
814
815 2
	if ($topic_context['is_sticky'])
816
		$topic_context['class'] .= '_sticky';
817
}
818
819
/**
820
 * Sets up the basic theme context stuff.
821
 *
822
 * @param bool $forceload = false
823
 *
824
 * @return
825
 */
826
function setupThemeContext($forceload = false)
827
{
828
	return theme()->setupThemeContext($forceload);
829
}
830
831
/**
832
 * Helper function to convert memory string settings to bytes
833
 *
834
 * @param string $val The byte string, like 256M or 1G
835
 *
836
 * @return integer The string converted to a proper integer in bytes
837
 */
838 2
function memoryReturnBytes($val)
839
{
840
	if (is_integer($val))
841
		return $val;
842 2
843 2
	// Separate the number from the designator
844 2
	$val = trim($val);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $val. This often makes code more readable.
Loading history...
845
	$num = intval(substr($val, 0, strlen($val) - 1));
846
	$last = strtolower(substr($val, -1));
847 1
848
	// Convert to bytes
849
	switch ($last)
850 2
	{
851 2
		// fall through select g = 1024*1024*1024
852
		case 'g':
853 2
			$num *= 1024;
854 2
		// fall through select m = 1024*1024
855
		case 'm':
856
			$num *= 1024;
857 2
		// fall through select k = 1024
858
		case 'k':
859
			$num *= 1024;
860 2
	}
861
862
	return $num;
863
}
864
865
/**
866
 * This is the only template included in the sources.
867
 */
868
function template_rawdata()
869
{
870
	return theme()->template_rawdata();
871
}
872
873
/**
874
 * The header template
875
 */
876
function template_header()
877
{
878
	return theme()->template_header();
879
}
880
881
/**
882
 * Show the copyright.
883
 */
884
function theme_copyright()
885
{
886
	return theme()->theme_copyright();
887
}
888
889
/**
890
 * The template footer
891
 */
892
function template_footer()
893
{
894
	return theme()->template_footer();
895
}
896
897
/**
898
 * Output the Javascript files
899
 *
900
 * What it does:
901
 *
902
 * - tabbing in this function is to make the HTML source look proper
903
 * - outputs jQuery/jQueryUI from the proper source (local/CDN)
904
 * - if deferred is set function will output all JS (source & inline) set to load at page end
905
 * - if the admin option to combine files is set, will use Combiner.class
906
 *
907
 * @param bool $do_deferred = false
908
 */
909
function template_javascript($do_deferred = false)
910
{
911
	theme()->template_javascript($do_deferred);
912
	return;
913
}
914
915
/**
916
 * Output the CSS files
917
 *
918
 * What it does:
919
 *  - If the admin option to combine files is set, will use Combiner.class
920
 */
921
function template_css()
922
{
923
	theme()->template_css();
924
	return;
925
}
926
927
/**
928
 * Calls on template_show_error from index.template.php to show warnings
929
 * and security errors for admins
930
 */
931
function template_admin_warning_above()
932
{
933
	theme()->template_admin_warning_above();
934
	return;
935
}
936
937
/**
938
 * Convert a single IP to a ranged IP.
939
 *
940
 * - Internal function used to convert a user-readable format to a format suitable for the database.
941
 *
942
 * @param string $fullip A full dot notation IP address
943
 *
944
 * @return array|string 'unknown' if the ip in the input was '255.255.255.255'
945
 */
946
function ip2range($fullip)
947
{
948
	// If its IPv6, validate it first.
949
	if (isValidIPv6($fullip) !== false)
950
	{
951
		$ip_parts = explode(':', expandIPv6($fullip, false));
952
		$ip_array = array();
953
954
		if (count($ip_parts) != 8)
955
			return array();
956
957
		for ($i = 0; $i < 8; $i++)
958
		{
959
			if ($ip_parts[$i] == '*')
960
				$ip_array[$i] = array('low' => '0', 'high' => hexdec('ffff'));
961 View Code Duplication
			elseif (preg_match('/^([0-9A-Fa-f]{1,4})\-([0-9A-Fa-f]{1,4})$/', $ip_parts[$i], $range) == 1)
962
				$ip_array[$i] = array('low' => hexdec($range[1]), 'high' => hexdec($range[2]));
963
			elseif (is_numeric(hexdec($ip_parts[$i])))
964
				$ip_array[$i] = array('low' => hexdec($ip_parts[$i]), 'high' => hexdec($ip_parts[$i]));
965
		}
966
967
		return $ip_array;
968
	}
969
970
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
971
	if ($fullip == 'unknown')
972
		$fullip = '255.255.255.255';
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $fullip. This often makes code more readable.
Loading history...
973
974
	$ip_parts = explode('.', $fullip);
975
	$ip_array = array();
976
977
	if (count($ip_parts) != 4)
978
		return array();
979
980
	for ($i = 0; $i < 4; $i++)
981
	{
982
		if ($ip_parts[$i] == '*')
983
			$ip_array[$i] = array('low' => '0', 'high' => '255');
984 View Code Duplication
		elseif (preg_match('/^(\d{1,3})\-(\d{1,3})$/', $ip_parts[$i], $range) == 1)
985
			$ip_array[$i] = array('low' => $range[1], 'high' => $range[2]);
986
		elseif (is_numeric($ip_parts[$i]))
987
			$ip_array[$i] = array('low' => $ip_parts[$i], 'high' => $ip_parts[$i]);
988
	}
989
990
	// Makes it simpler to work with.
991
	$ip_array[4] = array('low' => 0, 'high' => 0);
992
	$ip_array[5] = array('low' => 0, 'high' => 0);
993
	$ip_array[6] = array('low' => 0, 'high' => 0);
994
	$ip_array[7] = array('low' => 0, 'high' => 0);
995
996
	return $ip_array;
997
}
998
999
/**
1000
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
1001
 *
1002
 * @param string $ip A full dot notation IP address
1003
 *
1004
 * @return string
1005
 */
1006
function host_from_ip($ip)
1007
{
1008
	global $modSettings;
1009
1010
	$cache = \ElkArte\Cache\Cache::instance();
1011
1012
	$host = '';
1013
	if ($cache->getVar($host, 'hostlookup-' . $ip, 600) || empty($ip))
1014
		return $host;
1015
1016
	$t = microtime(true);
1017
1018
	// Try the Linux host command, perhaps?
1019
	if ((strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
1020
	{
1021
		if (!isset($modSettings['host_to_dis']))
1022
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
1023
		else
1024
			$test = @shell_exec('host ' . @escapeshellarg($ip));
1025
1026
		// Did host say it didn't find anything?
1027
		if (strpos($test, 'not found') !== false)
1028
			$host = '';
1029
		// Invalid server option?
1030
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
1031
			updateSettings(array('host_to_dis' => 1));
1032
		// Maybe it found something, after all?
1033
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
1034
			$host = $match[1];
1035
	}
1036
1037
	// This is nslookup; usually only Windows, but possibly some Unix?
1038
	if (empty($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
1039
	{
1040
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
1041
1042 View Code Duplication
		if (strpos($test, 'Non-existent domain') !== false)
1043
			$host = '';
1044
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
1045
			$host = $match[1];
1046
	}
1047
1048
	// This is the last try :/.
1049
	if (!isset($host) || $host === false)
1050
		$host = @gethostbyaddr($ip);
1051
1052
	// It took a long time, so let's cache it!
1053
	if (microtime(true) - $t > 0.5)
1054
		$cache->put('hostlookup-' . $ip, $host, 600);
1055
1056
	return $host;
1057
}
1058
1059
/**
1060
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
1061
 *
1062
 * @param string $text The string to process
1063
 * @param int $max_chars defaults to 20
1064
 *     - if encrypt = true this is the maximum number of bytes to use in integer hashes (for searching)
1065
 *     - if encrypt = false this is the maximum number of letters in each word
1066
 * @param bool $encrypt = false Used for custom search indexes to return an int[] array representing the words
1067
 *
1068
 * @return array
1069
 */
1070
function text2words($text, $max_chars = 20, $encrypt = false)
1071 22
{
1072
	// Step 1: Remove entities/things we don't consider words:
1073
	$words = preg_replace('~(?:[\x0B\0\x{A0}\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~u', ' ', strtr($text, array('<br />' => ' ')));
1074 22
1075
	// Step 2: Entities we left to letters, where applicable, lowercase.
1076
	$words = un_htmlspecialchars(ElkArte\Util::strtolower($words));
1077 22
1078
	// Step 3: Ready to split apart and index!
1079 22
	$words = explode(' ', $words);
1080
1081
	if ($encrypt)
1082
	{
1083
		// Range of characters that crypt will produce (0-9, a-z, A-Z .)
1084
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
1085
		$returned_ints = array();
1086
		foreach ($words as $word)
1087
		{
1088
			if (($word = trim($word, '-_\'')) !== '')
1089
			{
1090
				// Get a crypt representation of this work
1091
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
1092
				$total = 0;
1093
1094
				// Create an integer representation
1095
				for ($i = 0; $i < $max_chars; $i++)
1096
					$total += $possible_chars[ord($encrypted{$i})] * pow(63, $i);
1097
1098
				// Return the value
1099
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
1100
			}
1101
		}
1102
		return array_unique($returned_ints);
1103
	}
1104
	else
1105 22
	{
1106 22
		// Trim characters before and after and add slashes for database insertion.
1107 22
		$returned_words = array();
1108 22
		foreach ($words as $word)
1109
			if (($word = trim($word, '-_\'')) !== '')
1110
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
1111 22
1112
		// Filter out all words that occur more than once.
1113
		return array_unique($returned_words);
1114
	}
1115
}
1116
1117
/**
1118
 * Sets up all of the top menu buttons
1119
 *
1120
 * What it does:
1121
 *
1122
 * - Defines every master item in the menu, as well as any sub-items
1123
 * - Ensures the chosen action is set so the menu is highlighted
1124
 * - Saves them in the cache if it is available and on
1125
 * - Places the results in $context
1126
 */
1127
function setupMenuContext()
1128
{
1129
	return theme()->setupMenuContext();
1130
}
1131
1132
/**
1133
 * Process functions of an integration hook.
1134
 *
1135
 * What it does:
1136
 *
1137
 * - Calls all functions of the given hook.
1138
 * - Supports static class method calls.
1139
 *
1140
 * @param string $hook The name of the hook to call
1141
 * @param mixed[] $parameters = array() Parameters to pass to the hook
1142
 *
1143
 * @return mixed[] the results of the functions
1144
 */
1145 83
function call_integration_hook($hook, $parameters = array())
1146
{
1147
	return \ElkArte\Hooks::instance()->hook($hook, $parameters);
1148
}
1149
1150
/**
1151
 * Includes files for hooks that only do that (i.e. integrate_pre_include)
1152
 *
1153
 * @param string $hook The name to include
1154
 */
1155 17
function call_integration_include_hook($hook)
1156 17
{
1157
	\ElkArte\Hooks::instance()->include_hook($hook);
1158
}
1159
1160
/**
1161
 * Special hook call executed during obExit
1162
 */
1163
function call_integration_buffer()
1164
{
1165
	\ElkArte\Hooks::instance()->buffer_hook();
1166
}
1167
1168
/**
1169
 * Add a function for integration hook.
1170
 *
1171
 * - Does nothing if the function is already added.
1172
 *
1173
 * @param string $hook The name of the hook to add
1174
 * @param string $function The function associated with the hook
1175
 * @param string $file The file that contains the function
1176
 * @param bool $permanent = true if true, updates the value in settings table
1177
 */
1178 4
function add_integration_function($hook, $function, $file = '', $permanent = true)
1179 4
{
1180
	\ElkArte\Hooks::instance()->add($hook, $function, $file, $permanent);
1181
}
1182
1183
/**
1184
 * Remove an integration hook function.
1185
 *
1186
 * What it does:
1187
 *
1188
 * - Removes the given function from the given hook.
1189
 * - Does nothing if the function is not available.
1190
 *
1191
 * @param string $hook The name of the hook to remove
1192
 * @param string $function The name of the function
1193
 * @param string $file The file its located in
1194
 */
1195 2
function remove_integration_function($hook, $function, $file = '')
1196 2
{
1197
	\ElkArte\Hooks::instance()->remove($hook, $function, $file);
1198
}
1199
1200
/**
1201
 * Decode numeric html entities to their UTF8 equivalent character.
1202
 *
1203
 * What it does:
1204
 *
1205
 * - Callback function for preg_replace_callback in subs-members
1206
 * - Uses capture group 2 in the supplied array
1207
 * - Does basic scan to ensure characters are inside a valid range
1208
 *
1209
 * @param mixed[] $matches matches from a preg_match_all
1210
 *
1211
 * @return string $string
1212
 */
1213
function replaceEntities__callback($matches)
1214
{
1215
	if (!isset($matches[2]))
1216
		return '';
1217
1218
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
1219
1220
	// remove left to right / right to left overrides
1221
	if ($num === 0x202D || $num === 0x202E)
1222
		return '';
1223
1224
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
1225
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
1226
		return '&#' . $num . ';';
1227
1228
	// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
1229
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
1230
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
1231
		return '';
1232
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
1233
	elseif ($num < 0x80)
1234
		return chr($num);
1235
	// <0x800 (2048)
1236 View Code Duplication
	elseif ($num < 0x800)
1237
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
1238
	// < 0x10000 (65536)
1239 View Code Duplication
	elseif ($num < 0x10000)
1240
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1241
	// <= 0x10FFFF (1114111)
1242 View Code Duplication
	else
1243
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1244
}
1245
1246
/**
1247
 * Converts html entities to utf8 equivalents
1248
 *
1249
 * What it does:
1250
 *
1251
 * - Callback function for preg_replace_callback
1252
 * - Uses capture group 1 in the supplied array
1253
 * - Does basic checks to keep characters inside a viewable range.
1254
 *
1255
 * @param mixed[] $matches array of matches as output from preg_match_all
1256
 *
1257
 * @return string $string
1258
 */
1259
function fixchar__callback($matches)
1260
{
1261
	if (!isset($matches[1]))
1262
		return '';
1263
1264
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
1265
1266
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
1267
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
1268
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
1269
		return '';
1270
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
1271
	elseif ($num < 0x80)
1272
		return chr($num);
1273
	// <0x800 (2048)
1274 View Code Duplication
	elseif ($num < 0x800)
1275
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
1276
	// < 0x10000 (65536)
1277 View Code Duplication
	elseif ($num < 0x10000)
1278
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1279
	// <= 0x10FFFF (1114111)
1280 View Code Duplication
	else
1281
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1282
}
1283
1284
/**
1285
 * Strips out invalid html entities, replaces others with html style &#123; codes
1286
 *
1287
 * What it does:
1288
 *
1289
 * - Callback function used of preg_replace_callback in various $ent_checks,
1290
 * - For example strpos, strlen, substr etc
1291
 *
1292
 * @param mixed[] $matches array of matches for a preg_match_all
1293
 *
1294
 * @return string
1295
 */
1296 6
function entity_fix__callback($matches)
1297
{
1298
	if (!isset($matches[2]))
1299 6
		return '';
1300
1301
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
1302 6
1303 4
	// We don't allow control characters, characters out of range, byte markers, etc
1304 View Code Duplication
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
1305 2
		return '';
1306
	else
1307
		return '&#' . $num . ';';
1308
}
1309
1310
/**
1311
 * Retrieve additional search engines, if there are any, as an array.
1312
 *
1313
 * @return mixed[] array of engines
1314
 */
1315
function prepareSearchEngines()
1316
{
1317
	global $modSettings;
1318
1319
	$engines = array();
1320
	if (!empty($modSettings['additional_search_engines']))
1321
	{
1322
		$search_engines = ElkArte\Util::unserialize($modSettings['additional_search_engines']);
1323
		foreach ($search_engines as $engine)
1324
			$engines[strtolower(preg_replace('~[^A-Za-z0-9 ]~', '', $engine['name']))] = $engine;
1325
	}
1326
1327
	return $engines;
1328
}
1329
1330
/**
1331
 * This function receives a request handle and attempts to retrieve the next result.
1332
 *
1333
 * What it does:
1334
 *
1335
 * - It is used by the controller callbacks from the template, such as
1336
 * posts in topic display page, posts search results page, or personal messages.
1337
 *
1338
 * @param resource $messages_request holds a query result
1339
 * @param bool $reset
1340
 *
1341
 * @return integer|boolean
1342
 */
1343
function currentContext($messages_request, $reset = false)
1344
{
1345
	// Can't work with a database without a database :P
1346
	$db = database();
1347
1348
	// Start from the beginning...
1349
	if ($reset)
1350
		return $db->data_seek($messages_request, 0);
1351
1352
	// If the query has already returned false, get out of here
1353
	if ($messages_request->hasResults())
0 ignored issues
show
Bug introduced by
The method hasResults cannot be called on $messages_request (of type resource).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1354
	{
1355
		return false;
1356
	}
1357
1358
	// Attempt to get the next message.
1359
	$message = $db->fetch_assoc($messages_request);
1360
	if (!$message)
1361
	{
1362
		$db->free_result($messages_request);
1363
1364
		return false;
1365
	}
1366
1367
	return $message;
1368
}
1369
1370
/**
1371
 * Helper function to insert an array in to an existing array
1372
 *
1373
 * What it does:
1374
 *
1375
 * - Intended for addon use to allow such things as
1376
 * - Adding in a new menu item to an existing menu array
1377
 *
1378
 * @param mixed[] $input the array we will insert to
1379
 * @param string $key the key in the array that we are looking to find for the insert action
1380
 * @param mixed[] $insert the actual data to insert before or after the key
1381
 * @param string $where adding before or after
1382
 * @param bool $assoc if the array is a assoc array with named keys or a basic index array
1383
 * @param bool $strict search for identical elements, this means it will also check the types of the needle.
1384
 *
1385
 * @return array|mixed[]
1386
 */
1387
function elk_array_insert($input, $key, $insert, $where = 'before', $assoc = true, $strict = false)
1388
{
1389
	// Search for key names or values
1390
	if ($assoc)
1391
		$position = array_search($key, array_keys($input), $strict);
1392
	else
1393
		$position = array_search($key, $input, $strict);
1394
1395
	// If the key is not found, just insert it at the end
1396
	if ($position === false)
1397
		return array_merge($input, $insert);
1398
1399
	if ($where === 'after')
1400
		$position++;
1401
1402
	// Insert as first
1403
	if ($position === 0)
1404
		$input = array_merge($insert, $input);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $input. This often makes code more readable.
Loading history...
1405
	else
1406
		$input = array_merge(array_slice($input, 0, $position), $insert, array_slice($input, $position));
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $input. This often makes code more readable.
Loading history...
1407
1408
	return $input;
1409
}
1410
1411
/**
1412
 * Run a scheduled task now
1413
 *
1414
 * What it does:
1415
 *
1416
 * - From time to time it may be necessary to fire a scheduled task ASAP
1417
 * - This function sets the scheduled task to be called before any other one
1418
 *
1419
 * @param string $task the name of a scheduled task
1420
 */
1421
function scheduleTaskImmediate($task)
1422
{
1423
	global $modSettings;
1424
1425
	if (!isset($modSettings['scheduleTaskImmediate']))
1426
		$scheduleTaskImmediate = array();
1427
	else
1428
		$scheduleTaskImmediate = \ElkArte\Util::unserialize($modSettings['scheduleTaskImmediate']);
1429
1430
	// If it has not been scheduled, the do so now
1431
	if (!isset($scheduleTaskImmediate[$task]))
1432
	{
1433
		$scheduleTaskImmediate[$task] = 0;
1434
		updateSettings(array('scheduleTaskImmediate' => serialize($scheduleTaskImmediate)));
1435
1436
		require_once(SUBSDIR . '/ScheduledTasks.subs.php');
1437
1438
		// Ensure the task is on
1439
		toggleTaskStatusByName($task, true);
1440
1441
		// Before trying to run it **NOW** :P
1442
		calculateNextTrigger($task, true);
1443
	}
1444
}
1445
1446
/**
1447
 * For diligent people: remove scheduleTaskImmediate when done, otherwise
1448
 * a maximum of 10 executions is allowed
1449
 *
1450
 * @param string $task the name of a scheduled task
1451
 * @param bool $calculateNextTrigger if recalculate the next task to execute
1452
 */
1453
function removeScheduleTaskImmediate($task, $calculateNextTrigger = true)
1454
{
1455
	global $modSettings;
1456
1457
	// Not on, bail
1458
	if (!isset($modSettings['scheduleTaskImmediate']))
1459
		return;
1460
	else
1461
		$scheduleTaskImmediate = \ElkArte\Util::unserialize($modSettings['scheduleTaskImmediate']);
1462
1463
	// Clear / remove the task if it was set
1464
	if (isset($scheduleTaskImmediate[$task]))
1465
	{
1466
		unset($scheduleTaskImmediate[$task]);
1467
		updateSettings(array('scheduleTaskImmediate' => serialize($scheduleTaskImmediate)));
1468
1469
		// Recalculate the next task to execute
1470
		if ($calculateNextTrigger)
1471
		{
1472
			require_once(SUBSDIR . '/ScheduledTasks.subs.php');
1473
			calculateNextTrigger($task);
1474
		}
1475
	}
1476
}
1477
1478
/**
1479
 * Helper function to replace commonly used urls in text strings
1480
 *
1481
 * @event integrate_basic_url_replacement add additional place holder replacements
1482
 * @param string $string the string to inject URLs into
1483
 *
1484
 * @return string the input string with the place-holders replaced with
1485
 *           the correct URLs
1486
 */
1487 3
function replaceBasicActionUrl($string)
1488 3
{
1489
	global $scripturl, $context, $boardurl;
1490 3
	static $find_replace = null;
1491
1492
	if ($find_replace === null)
1493 3
	{
1494 3
		$find_replace = array(
1495 3
			'{forum_name}' => $context['forum_name'],
1496 3
			'{forum_name_html_safe}' => $context['forum_name_html_safe'],
1497 3
			'{forum_name_html_unsafe}' => un_htmlspecialchars($context['forum_name_html_safe']),
1498 3
			'{script_url}' => $scripturl,
1499 3
			'{board_url}' => $boardurl,
1500 3
			'{login_url}' => getUrl('action', ['action' => 'login']),
1501 3
			'{register_url}' => getUrl('action', ['action' => 'register']),
1502 3
			'{activate_url}' => getUrl('action', ['action' => 'register', 'sa' => 'activate']),
1503 3
			'{help_url}' => getUrl('action', ['action' => 'help']),
1504 3
			'{admin_url}' => getUrl('admin', ['action' => 'admin']),
1505 3
			'{moderate_url}' => getUrl('moderate', ['action' => 'moderate']),
1506 3
			'{recent_url}' => getUrl('action', ['action' => 'recent']),
1507 3
			'{search_url}' => getUrl('action', ['action' => 'search']),
1508 3
			'{who_url}' => getUrl('action', ['action' => 'who']),
1509 3
			'{credits_url}' => getUrl('action', ['action' => 'who', 'sa' => 'credits']),
1510 3
			'{calendar_url}' => getUrl('action', ['action' => 'calendar']),
1511
			'{memberlist_url}' => getUrl('action', ['action' => 'memberlist']),
1512 3
			'{stats_url}' => getUrl('action', ['action' => 'stats']),
1513
		);
1514
		call_integration_hook('integrate_basic_url_replacement', array(&$find_replace));
1515 3
	}
1516
1517
	return str_replace(array_keys($find_replace), array_values($find_replace), $string);
1518
}
1519
1520
/**
1521
 * This function creates a new GenericList from all the passed options.
1522
 *
1523
 * What it does:
1524
 *
1525
 * - Calls integration hook integrate_list_"unique_list_id" to allow easy modifying
1526
 *
1527
 * @event integrate_list_$listID called before every createlist to allow access to its listoptions
1528
 * @param mixed[] $listOptions associative array of option => value
1529
 */
1530
function createList($listOptions)
1531
{
1532
	call_integration_hook('integrate_list_' . $listOptions['id'], array(&$listOptions));
1533
1534
	$list = new \ElkArte\GenericList($listOptions);
1535
1536
	$list->buildList();
1537
}
1538
1539
/**
1540
 * This handy function retrieves a Request instance and passes it on.
1541
 *
1542
 * What it does:
1543
 *
1544
 * - To get hold of a Request, you can use this function or directly Request::instance().
1545
 * - This is for convenience, it simply delegates to Request::instance().
1546
 */
1547 37
function request()
1548
{
1549
	return ElkArte\Request::instance();
1550
}
1551
1552
/**
1553
 * Meant to replace any usage of $db_last_error.
1554
 *
1555
 * What it does:
1556
 *
1557
 * - Reads the file db_last_error.txt, if a time() is present returns it,
1558
 * otherwise returns 0.
1559
 */
1560
function db_last_error()
1561
{
1562
	$time = trim(file_get_contents(BOARDDIR . '/db_last_error.txt'));
1563
1564
	if (preg_match('~^\d{10}$~', $time) === 1)
1565
		return $time;
1566
	else
1567
		return 0;
1568
}
1569
1570
/**
1571
 * This function has the only task to retrieve the correct prefix to be used
1572
 * in responses.
1573
 *
1574
 * @return string - The prefix in the default language of the forum
1575
 */
1576 2
function response_prefix()
1577 2
{
1578
	global $language, $txt;
1579 2
	static $response_prefix = null;
1580
1581
	$cache = \ElkArte\Cache\Cache::instance();
1582 2
1583
	// Get a response prefix, but in the forum's default language.
1584 2
	if ($response_prefix === null && (!$cache->getVar($response_prefix, 'response_prefix') || !$response_prefix))
1585 2
	{
1586
		if ($language === User::$info->language)
0 ignored issues
show
Documentation introduced by
The property language does not exist on object<ElkArte\ValuesContainer>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1587
			$response_prefix = $txt['response_prefix'];
1588 View Code Duplication
		else
1589
		{
1590
			theme()->getTemplates()->loadLanguageFile('index', $language, false);
1591
			$response_prefix = $txt['response_prefix'];
1592
			theme()->getTemplates()->loadLanguageFile('index');
1593 2
		}
1594
1595
		$cache->put('response_prefix', $response_prefix, 600);
1596 2
	}
1597
1598
	return $response_prefix;
1599
}
1600
1601
/**
1602
 * A very simple function to determine if an email address is "valid" for Elkarte.
1603
 *
1604
 * - A valid email for ElkArte is something that resembles an email (filter_var) and
1605
 * is less than 255 characters (for database limits)
1606
 *
1607
 * @param string $value - The string to evaluate as valid email
1608
 *
1609
 * @return string|false - The email if valid, false if not a valid email
1610
 */
1611 2
function isValidEmail($value)
1612 2
{
1613 2
	$value = trim($value);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $value. This often makes code more readable.
Loading history...
1614
	if (filter_var($value, FILTER_VALIDATE_EMAIL) && ElkArte\Util::strlen($value) < 255)
1615
		return $value;
1616
	else
1617
		return false;
1618
}
1619
1620
/**
1621
 * Adds a protocol (http/s, ftp/mailto) to the beginning of an url if missing
1622
 *
1623
 * @param string $url - The url
1624
 * @param string[] $protocols - A list of protocols to check, the first is
1625
 *                 added if none is found (optional, default array('http://', 'https://'))
1626
 *
1627
 * @return string - The url with the protocol
1628
 */
1629 6
function addProtocol($url, $protocols = array())
1630
{
1631 6
	if (empty($protocols))
1632 6
	{
1633
		$pattern = '~^(http://|https://)~i';
1634
		$protocols = array('http://');
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $protocols. This often makes code more readable.
Loading history...
1635
	}
1636
	else
1637
	{
1638
		$pattern = '~^(' . implode('|', array_map(function ($val) {return preg_quote($val, '~'); }, $protocols)) . ')~i';
1639 6
	}
1640
1641 6
	$found = false;
1642
	$url = preg_replace_callback($pattern, function ($match) use (&$found) {
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $url. This often makes code more readable.
Loading history...
1643 6
		$found = true;
1644 6
1645
		return strtolower($match[0]);
1646 6
	}, $url);
1647
1648 6
	if ($found === true)
1649
	{
1650
			return $url;
1651 2
	}
1652
1653
	return $protocols[0] . $url;
1654
}
1655
1656
/**
1657
 * Removes nested quotes from a text string.
1658
 *
1659
 * @param string $text - The body we want to remove nested quotes from
1660
 *
1661
 * @return string - The same body, just without nested quotes
1662
 */
1663
function removeNestedQuotes($text)
1664
{
1665
	global $modSettings;
1666
1667
	// Remove any nested quotes, if necessary.
1668
	if (!empty($modSettings['removeNestedQuotes']))
1669
	{
1670
		return preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $text);
1671
	}
1672
	else
1673
	{
1674
		return $text;
1675
	}
1676
}
1677
1678
/**
1679
 * Change a \t to a span that will show a tab
1680
 *
1681
 * @param string $string
1682
 *
1683
 * @return string
1684
 */
1685 4
function tabToHtmlTab($string)
1686
{
1687
	return str_replace("\t", "<span class=\"tab\">\t</span>", $string);
1688
}
1689
1690
/**
1691
 * Remove <br />
1692
 *
1693
 * @param string $string
1694
 *
1695
 * @return string
1696
 */
1697
function removeBr($string)
1698
{
1699
	return str_replace('<br />', '', $string);
1700
}
1701
1702
/**
1703
 * Are we using this browser?
1704
 *
1705
 * - Wrapper function for detectBrowser
1706
 *
1707
 * @param string $browser the browser we are checking for.
1708
 *
1709
 * @return bool
1710
 */
1711 15
function isBrowser($browser)
1712
{
1713
	global $context;
1714 15
1715
	// Don't know any browser!
1716
	if (empty($context['browser']))
1717
	{
1718
		detectBrowser();
1719 15
	}
1720
1721
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1722
}
1723
1724
/**
1725
 * Replace all vulgar words with respective proper words. (substring or whole words..)
1726
 *
1727
 * What it does:
1728
 * - it censors the passed string.
1729
 * - if the admin setting allow_no_censored is on it does not censor unless force is also set.
1730
 * - if the admin setting allow_no_censored is off will censor words unless the user has set
1731
 * it to not censor in their profile and force is off
1732
 * - it caches the list of censored words to reduce parsing.
1733
 * - Returns the censored text
1734
 *
1735
 * @param string $text
1736
 * @param bool $force = false
1737
 *
1738
 * @return string
1739
 */
1740 8
function censor($text, $force = false)
1741 8
{
1742
	global $modSettings;
1743 8
	static $censor = null;
1744
1745 2
	if ($censor === null)
1746
	{
1747
		$censor = new \ElkArte\Censor(explode("\n", $modSettings['censor_vulgar']), explode("\n", $modSettings['censor_proper']), $modSettings);
1748 8
	}
1749
1750
	return $censor->censor($text, $force);
1751
}
1752
1753
/**
1754
 * Helper function able to determine if the current member can see at least
1755
 * one button of a button strip.
1756
 *
1757
 * @param mixed[] $button_strip
1758
 *
1759
 * @return bool
1760
 */
1761
function can_see_button_strip($button_strip)
1762
{
1763
	global $context;
1764
1765
	foreach ($button_strip as $key => $value)
1766
	{
1767
		if (!isset($value['test']) || !empty($context[$value['test']]))
1768
			return true;
1769
	}
1770
1771
	return false;
1772
}
1773
1774
/**
1775
 * @return \ElkArte\Themes\DefaultTheme\Theme
1776
 */
1777 106
function theme()
1778
{
1779
	return $GLOBALS['context']['theme_instance'];
1780
}
1781
1782
/**
1783
 * Stops the execution with a 1x1 gif file
1784
 *
1785
 * @param bool $expired Sends an expired header.
1786
 */
1787
function dieGif($expired = false)
1788
{
1789
	// The following is an attempt at stopping the behavior identified in #2391
1790
	if (function_exists('fastcgi_finish_request'))
1791
	{
1792
		die();
1793
	}
1794
1795
	if ($expired === true)
1796
	{
1797
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
1798
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
1799
	}
1800
1801
	header('Content-Type: image/gif');
1802
	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");
1803
}
1804
1805
/**
1806
 * Prepare ob_start with or without gzip compression
1807
 *
1808
 * @param bool $use_compression Starts compressed headers.
1809
 */
1810
function obStart($use_compression = false)
1811
{
1812
	// This is done to clear any output that was made before now.
1813
	while (ob_get_level() > 0)
1814
	{
1815
		@ob_end_clean();
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...
1816
	}
1817
1818
	if ($use_compression === true)
1819
	{
1820
		ob_start('ob_gzhandler');
1821
	}
1822
	else
1823
	{
1824
		ob_start();
1825
		header('Content-Encoding: none');
1826
	}
1827
}
1828
1829
/**
1830
 * Returns an URL based on the parameters passed and the selected generator
1831
 *
1832
 * @param string $type The type of the URL (depending on the type, the
1833
 *                     generator can act differently
1834
 * @param mixed[] $params All the parameters of the URL
1835
 *
1836
 * @return string An URL
1837
 */
1838 11
function getUrl($type, $params)
1839
{
1840 11
	static $generator = null;
1841
1842 1
	if ($generator === null)
1843
	{
1844
		$generator = initUrlGenerator();
1845 11
	}
1846
1847
	return $generator->get($type, $params);
1848
}
1849
1850
/**
1851
 * Returns the query part of an URL based on the parameters passed and the selected generator
1852
 *
1853
 * @param string $type The type of the URL (depending on the type, the
1854
 *                     generator can act differently
1855
 * @param mixed[] $params All the parameters of the URL
1856
 *
1857
 * @return string The query part of an URL
1858
 */
1859
function getUrlQuery($type, $params)
1860
{
1861
	static $generator = null;
1862
1863
	if ($generator === null)
1864
	{
1865
		$generator = initUrlGenerator();
1866
	}
1867
	return $generator->getQuery($type, $params);
1868
}
1869
1870
/**
1871
 * Initialize the URL generator
1872
 *
1873
 * @return object The URL generator object
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use ElkArte\UrlGenerator\UrlGenerator.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1874
 */
1875 2
function initUrlGenerator()
1876
{
1877 2
	global $scripturl, $context, $url_format;
1878 2
1879 2
	$generator = new \ElkArte\UrlGenerator\UrlGenerator([
1880
		'generator' => ucfirst($url_format ?? 'standard'),
1881 2
		'scripturl' => $scripturl,
1882
		'replacements' => [
1883
			'{session_data}' => isset($context['session_var']) ? $context['session_var'] . '=' . $context['session_id'] : ''
1884
		]
1885 2
	]);
1886 2
1887 2
	$generator->register('Topic');
1888
	$generator->register('Board');
1889 2
	$generator->register('Profile');
1890
1891
	return $generator;
1892
}
1893
1894
/**
1895
 * This function only checks if a certain feature (in core features)
1896
 * is enabled or not.
1897
 *
1898
 * @param string $feature The abbreviated code of a core feature
1899
 * @return bool true/false for enabled/disabled
1900
 */
1901 5
function featureEnabled($feature)
1902 5
{
1903
	global $modSettings, $context;
1904 5
	static $features = null;
1905
1906
	if ($features === null)
1907 1
	{
1908 1
		// This allows us to change the way things look for the admin.
1909
		$features = explode(',', isset($modSettings['admin_features']) ?
1910
			$modSettings['admin_features'] : 'cd,cp,k,w,rg,ml,pm');
1911 1
1912
		// @deprecated since 2.0 - Next line is just for backward compatibility to remove before release
1913
		$context['admin_features'] = $features;
1914 5
	}
1915
1916
	return in_array($feature, $features);
1917
}
1918