Passed
Push — patch_1-1-7 ( 90a951...ab62ad )
by Emanuele
04:23 queued 03:46
created

byte_format()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 16
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file has all the main functions in it that relate to, well, everything.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1.4
15
 *
16
 */
17
18
/**
19
 * Update some basic statistics.
20
 *
21
 * @deprecated since 1.1 - use directly the update{Statistic}Stats functions instead
22
 *
23
 * 'member' statistic updates the latest member, the total member
24
 *  count, and the number of unapproved members.
25
 * 'member' also only counts approved members when approval is on, but
26
 *  is much more efficient with it off.
27
 *
28
 * 'message' changes the total number of messages, and the
29
 *  highest message id by id_msg - which can be parameters 1 and 2,
30
 *  respectively.
31
 *
32
 * 'topic' updates the total number of topics, or if parameter1 is true
33
 *  simply increments them.
34
 *
35
 * 'subject' updates the log_search_subjects in the event of a topic being
36
 *  moved, removed or split.  parameter1 is the topicid, parameter2 is the new subject
37
 *
38
 * 'postgroups' case updates those members who match condition's
39
 *  post-based membergroups in the database (restricted by parameter1).
40
 *
41
 * @param string $type Stat type - can be 'member', 'message', 'topic', 'subject' or 'postgroups'
42
 * @param int|string|boolean|mixed[]|null $parameter1 pass through value
43
 * @param int|string|boolean|mixed[]|null $parameter2 pass through value
44
 */
45
function updateStats($type, $parameter1 = null, $parameter2 = null)
46
{
47
	switch ($type)
48
	{
49
		case 'member':
50
			require_once(SUBSDIR . '/Members.subs.php');
51
			updateMemberStats($parameter1, $parameter2);
52
			break;
53
		case 'message':
54
			require_once(SUBSDIR . '/Messages.subs.php');
55
			updateMessageStats($parameter1, $parameter2);
56
			break;
57
		case 'subject':
58
			require_once(SUBSDIR . '/Messages.subs.php');
59
			updateSubjectStats($parameter1, $parameter2);
60
			break;
61
		case 'topic':
62
			require_once(SUBSDIR . '/Topic.subs.php');
63
			updateTopicStats($parameter1);
64
			break;
65
		case 'postgroups':
66
			require_once(SUBSDIR . '/Membergroups.subs.php');
67
			updatePostGroupStats($parameter1, $parameter2);
0 ignored issues
show
Bug introduced by
It seems like $parameter1 can also be of type boolean and integer and string; however, parameter $members of updatePostGroupStats() does only seem to accept integer[]|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

67
			updatePostGroupStats(/** @scrutinizer ignore-type */ $parameter1, $parameter2);
Loading history...
Bug introduced by
It seems like $parameter2 can also be of type boolean and integer and string; however, parameter $parameter2 of updatePostGroupStats() does only seem to accept null|string[], maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

67
			updatePostGroupStats($parameter1, /** @scrutinizer ignore-type */ $parameter2);
Loading history...
68
			break;
69
		default:
70
			trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE);
71
	}
72
}
73
74
/**
75
 * Updates the settings table as well as $modSettings... only does one at a time if $update is true.
76
 *
77
 * What it does:
78
 *
79
 * - Updates both the settings table and $modSettings array.
80
 * - All of changeArray's indexes and values are assumed to have escaped apostrophes (')!
81
 * - If a variable is already set to what you want to change it to, that
82
 *   Variable will be skipped over; it would be unnecessary to reset.
83
 * - When update is true, UPDATEs will be used instead of REPLACE.
84
 * - When update is true, the value can be true or false to increment
85
 *  or decrement it, respectively.
86
 *
87
 * @param mixed[] $changeArray An associative array of what we're changing in 'setting' => 'value' format
88
 * @param bool $update Use an UPDATE query instead of a REPLACE query
89
 * @param bool $debug = false Not used at this time, see todo
90
 * @todo: add debugging features, $debug isn't used
91
 */
92
function updateSettings($changeArray, $update = false, $debug = false)
93
{
94
	global $modSettings;
95
96
	$db = database();
97
	$cache = Cache::instance();
98
99
	if (empty($changeArray) || !is_array($changeArray))
100
		return;
101
102
	// In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs.
103
	if ($update)
104
	{
105
		foreach ($changeArray as $variable => $value)
106
		{
107
			$db->query('', '
108
				UPDATE {db_prefix}settings
109
				SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value}
110
				WHERE variable = {string:variable}',
111
				array(
112
					'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value),
113
					'variable' => $variable,
114
				)
115
			);
116
117
			$modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value);
118
		}
119
120
		// Clean out the cache and make sure the cobwebs are gone too.
121
		$cache->remove('modSettings');
122
123
		return;
124
	}
125
126
	$replaceArray = array();
127
	foreach ($changeArray as $variable => $value)
128
	{
129
		// Don't bother if it's already like that ;).
130
		if (isset($modSettings[$variable]) && $modSettings[$variable] == $value)
131
			continue;
132
		// If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it.
133
		elseif (!isset($modSettings[$variable]) && empty($value))
134
			continue;
135
136
		$replaceArray[] = array($variable, $value);
137
138
		$modSettings[$variable] = $value;
139
	}
140
141
	if (empty($replaceArray))
142
		return;
143
144
	$db->insert('replace',
145
		'{db_prefix}settings',
146
		array('variable' => 'string-255', 'value' => 'string-65534'),
147
		$replaceArray,
148
		array('variable')
149
	);
150
151
	// Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
152
	$cache->remove('modSettings');
153
}
154
155
/**
156
 * Deletes one setting from the settings table and takes care of $modSettings as well
157
 *
158
 * @param string|string[] $toRemove the setting or the settings to be removed
159
 */
160
function removeSettings($toRemove)
161
{
162
	global $modSettings;
163
164
	$db = database();
165
166
	if (empty($toRemove))
167
		return;
168
169
	if (!is_array($toRemove))
170
		$toRemove = array($toRemove);
171
172
	// Remove the setting from the db
173
	$db->query('', '
174
		DELETE FROM {db_prefix}settings
175
		WHERE variable IN ({array_string:setting_name})',
176
		array(
177
			'setting_name' => $toRemove,
178
		)
179
	);
180
181
	// Remove it from $modSettings now so it does not persist
182
	foreach ($toRemove as $setting)
183
		if (isset($modSettings[$setting]))
184
			unset($modSettings[$setting]);
185
186
	// Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
187
	Cache::instance()->remove('modSettings');
188
}
189
190
/**
191
 * Constructs a page list.
192
 *
193
 * What it does:
194
 *
195
 * - Builds the page list, e.g. 1 ... 6 7 [8] 9 10 ... 15.
196
 * - Flexible_start causes it to use "url.page" instead of "url;start=page".
197
 * - Very importantly, cleans up the start value passed, and forces it to
198
 *   be a multiple of num_per_page.
199
 * - Checks that start is not more than max_value.
200
 * - Base_url should be the URL without any start parameter on it.
201
 * - Uses the compactTopicPagesEnable and compactTopicPagesContiguous
202
 *   settings to decide how to display the menu.
203
 *
204
 * @example is available near the function definition.
205
 * @example $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true);
206
 *
207
 * @param string $base_url The base URL to be used for each link.
208
 * @param int &$start The start position, by reference. If this is not a multiple
209
 * of the number of items per page, it is sanitized to be so and the value will persist upon the function's return.
210
 * @param int $max_value The total number of items you are paginating for.
211
 * @param int $num_per_page The number of items to be displayed on a given page.
212
 * @param bool $flexible_start = false Use "url.page" instead of "url;start=page"
213
 * @param mixed[] $show associative array of option => boolean paris
214
 */
215
function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false, $show = array())
216
{
217
	global $modSettings, $context, $txt, $settings;
218
219
	// Save whether $start was less than 0 or not.
220
	$start = (int) $start;
221
	$start_invalid = $start < 0;
222
	$show_defaults = array(
223
		'prev_next' => true,
224
		'all' => false,
225
	);
226
227
	$show = array_merge($show_defaults, $show);
228
229
	// Make sure $start is a proper variable - not less than 0.
230
	if ($start_invalid)
231
		$start = 0;
232
	// Not greater than the upper bound.
233
	elseif ($start >= $max_value)
234
		$start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page)));
235
	// And it has to be a multiple of $num_per_page!
236
	else
237
		$start = max(0, (int) $start - ((int) $start % (int) $num_per_page));
238
239
	$context['current_page'] = $start / $num_per_page;
240
241
	$base_link = str_replace('{base_link}', ($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d'), $settings['page_index_template']['base_link']);
242
243
	// Compact pages is off or on?
244
	if (empty($modSettings['compactTopicPagesEnable']))
245
	{
246
		// Show the left arrow.
247
		$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']));
248
249
		// Show all the pages.
250
		$display_page = 1;
251
		for ($counter = 0; $counter < $max_value; $counter += $num_per_page)
252
			$pageindex .= $start == $counter && !$start_invalid && empty($show['all_selected']) ? sprintf($settings['page_index_template']['current_page'], $display_page++) : sprintf($base_link, $counter, $display_page++);
253
254
		// Show the right arrow.
255
		$display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page);
256
		if ($start != $counter - $max_value && !$start_invalid && $show['prev_next'] && empty($show['all_selected']))
257
			$pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, str_replace('{next_txt}', $txt['next'], $settings['page_index_template']['next_page']));
258
	}
259
	else
260
	{
261
		// If they didn't enter an odd value, pretend they did.
262
		$PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2;
263
264
		// Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page)
265
		if (!empty($start) && $show['prev_next'])
266
			$pageindex = sprintf($base_link, $start - $num_per_page, str_replace('{prev_txt}', $txt['prev'], $settings['page_index_template']['previous_page']));
267
		else
268
			$pageindex = '';
269
270
		// Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15)
271
		if ($start > $num_per_page * $PageContiguous)
272
			$pageindex .= sprintf($base_link, 0, '1');
273
274
		// Show the ... after the first page.  (prev page 1 >...< 6 7 [8] 9 10 ... 15 next page)
275
		if ($start > $num_per_page * ($PageContiguous + 1))
276
			$pageindex .= str_replace('{custom}', 'data-baseurl="' . htmlspecialchars(JavaScriptEscape(($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d')), ENT_COMPAT, 'UTF-8') . '" data-perpage="' . $num_per_page . '" data-firstpage="' . $num_per_page . '" data-lastpage="' . ($start - $num_per_page * $PageContiguous) . '"', $settings['page_index_template']['expand_pages']);
277
278
		// Show the pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
279
		for ($nCont = $PageContiguous; $nCont >= 1; $nCont--)
280
			if ($start >= $num_per_page * $nCont)
281
			{
282
				$tmpStart = $start - $num_per_page * $nCont;
283
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
284
			}
285
286
		// Show the current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
287
		if (!$start_invalid && empty($show['all_selected']))
288
			$pageindex .= sprintf($settings['page_index_template']['current_page'], ($start / $num_per_page + 1));
289
		else
290
			$pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1);
291
292
		// Show the pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
293
		$tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page;
294
		for ($nCont = 1; $nCont <= $PageContiguous; $nCont++)
295
			if ($start + $num_per_page * $nCont <= $tmpMaxPages)
296
			{
297
				$tmpStart = $start + $num_per_page * $nCont;
298
				$pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
299
			}
300
301
		// Show the '...' part near the end. (prev page 1 ... 6 7 [8] 9 10 >...< 15 next page)
302
		if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages)
303
			$pageindex .= str_replace('{custom}', 'data-baseurl="' . htmlspecialchars(JavaScriptEscape(($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d')), ENT_COMPAT, 'UTF-8') . '" data-perpage="' . $num_per_page . '" data-firstpage="' . ($start + $num_per_page * ($PageContiguous + 1)) . '" data-lastpage="' . $tmpMaxPages . '"', $settings['page_index_template']['expand_pages']);
304
305
		// Show the last number in the list. (prev page 1 ... 6 7 [8] 9 10 ... >15<  next page)
306
		if ($start + $num_per_page * $PageContiguous < $tmpMaxPages)
307
			$pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1);
308
309
		// Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
310
		if ($start != $tmpMaxPages && $show['prev_next'] && empty($show['all_selected']))
311
			$pageindex .= sprintf($base_link, $start + $num_per_page, str_replace('{next_txt}', $txt['next'], $settings['page_index_template']['next_page']));
312
	}
313
314
	// The "all" button
315
	if ($show['all'])
316
	{
317
		if (!empty($show['all_selected']))
318
			$pageindex .= sprintf($settings['page_index_template']['current_page'], $txt['all']);
319
		else
320
			$pageindex .= sprintf(str_replace('%1$d', '%1$s', $base_link), '0;all', str_replace('{all_txt}', $txt['all'], $settings['page_index_template']['all']));
321
	}
322
323
	return $pageindex;
324
}
325
326
/**
327
 * Formats a number.
328
 *
329
 * What it does:
330
 *
331
 * - Uses the format of number_format to decide how to format the number.
332
 *   for example, it might display "1 234,50".
333
 * - Caches the formatting data from the setting for optimization.
334
 *
335
 * @param float $number The float value to apply comma formatting
336
 * @param integer|bool $override_decimal_count = false or number of decimals
337
 *
338
 * @return string
339
 */
340
function comma_format($number, $override_decimal_count = false)
341
{
342
	global $txt;
343
	static $thousands_separator = null, $decimal_separator = null, $decimal_count = null;
344
345
	// Cache these values...
346
	if ($decimal_separator === null)
347
	{
348
		// Not set for whatever reason?
349
		if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1)
350
			return $number;
351
352
		// Cache these each load...
353
		$thousands_separator = $matches[1];
354
		$decimal_separator = $matches[2];
355
		$decimal_count = strlen($matches[3]);
356
	}
357
358
	// Format the string with our friend, number_format.
359
	return number_format($number, (float) $number === $number ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator);
0 ignored issues
show
Bug introduced by
It seems like (double)$number === $num...rride_decimal_count : 0 can also be of type true; however, parameter $decimals of number_format() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

359
	return number_format($number, /** @scrutinizer ignore-type */ (float) $number === $number ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator);
Loading history...
360
}
361
362
/**
363
 * Formats a number to a multiple of thousands x, x k, x M, x G, x T
364
 *
365
 * @param float $number The value to format
366
 * @param integer|bool $override_decimal_count = false or number of decimals
367
 *
368
 * @return string
369
 */
370
function thousands_format($number, $override_decimal_count = false)
371
{
372
	foreach (array('', ' k', ' M', ' G', ' T') as $kb)
373
	{
374
		if ($number < 1000)
375
		{
376
			break;
377
		}
378
379
		$number /= 1000;
380
	}
381
382
	return comma_format($number, $override_decimal_count) . $kb;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $kb seems to be defined by a foreach iteration on line 372. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
383
}
384
385
/**
386
 * Formats a number to a computer byte size value xB, xKB, xMB, xGB
387
 *
388
 * @param int $number
389
 *
390
 * @return string
391
 */
392
function byte_format($number)
393
{
394
	global $txt;
395
396
	$kb = '';
397
	foreach (array('byte', 'kilobyte', 'megabyte', 'gigabyte') as $kb)
398
	{
399
		if ($number < 1024)
400
		{
401
			break;
402
		}
403
404
		$number /= 1024;
405
	}
406
407
	return comma_format($number) . ' ' . $txt[$kb];
408
}
409
410
/**
411
 * Format a time to make it look purdy.
412
 *
413
 * What it does:
414
 *
415
 * - Returns a pretty formatted version of time based on the user's format in $user_info['time_format'].
416
 * - Applies all necessary time offsets to the timestamp, unless offset_type is set.
417
 * - If todayMod is set and show_today was not not specified or true, an
418
 *   alternate format string is used to show the date with something to show it is "today" or "yesterday".
419
 * - Performs localization (more than just strftime would do alone.)
420
 *
421
 * @param int $log_time A unix timestamp
422
 * @param string|bool $show_today = true show "Today"/"Yesterday",
423
 *   false shows the date, a string can force a date format to use %b %d, %Y
424
 * @param string|bool $offset_type = false If false, uses both user time offset and forum offset.
425
 *   If 'forum', uses only the forum offset. Otherwise no offset is applied.
426
 */
427
function standardTime($log_time, $show_today = true, $offset_type = false)
428
{
429
	global $user_info, $txt, $modSettings;
430
	static $non_twelve_hour, $support_e = null;
431
432
	if ($support_e === null)
433
	{
434
		$support_e = detectServer()->is('windows');
435
	}
436
437
	// Offset the time.
438
	if (!$offset_type)
439
		$time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600;
440
	// Just the forum offset?
441
	elseif ($offset_type === 'forum')
442
		$time = $log_time + $modSettings['time_offset'] * 3600;
443
	else
444
		$time = $log_time;
445
446
	// We can't have a negative date (on Windows, at least.)
447
	if ($log_time < 0)
448
		$log_time = 0;
449
450
	// Today and Yesterday?
451
	if ($modSettings['todayMod'] >= 1 && $show_today === true)
452
	{
453
		// Get the current time.
454
		$nowtime = forum_time();
455
456
		$then = @getdate($time);
457
		$now = @getdate($nowtime);
458
459
		// Try to make something of a time format string...
460
		$s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S';
461
		if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false)
462
		{
463
			$h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l';
464
			$today_fmt = $h . ':%M' . $s . ' %p';
465
		}
466
		else
467
			$today_fmt = '%H:%M' . $s;
468
469
		// Same day of the year, same year.... Today!
470
		if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
471
			return sprintf($txt['today'], standardTime($log_time, $today_fmt, $offset_type));
472
473
		// 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...
474
		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))
475
			return sprintf($txt['yesterday'], standardTime($log_time, $today_fmt, $offset_type));
476
	}
477
478
	$str = !is_bool($show_today) ? $show_today : $user_info['time_format'];
479
480
	if (setlocale(LC_TIME, $txt['lang_locale']))
481
	{
482
		if (!isset($non_twelve_hour))
483
			$non_twelve_hour = trim(strftime('%p')) === '';
484
		if ($non_twelve_hour && strpos($str, '%p') !== false)
485
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
486
487
		foreach (array('%a', '%A', '%b', '%B') as $token)
488
			if (strpos($str, $token) !== false)
489
				$str = str_replace($token, !empty($txt['lang_capitalize_dates']) ? Util::ucwords(strftime($token, $time)) : strftime($token, $time), $str);
490
	}
491
	else
492
	{
493
		// Do-it-yourself time localization.  Fun.
494
		foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label)
495
			if (strpos($str, $token) !== false)
496
				$str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str);
497
498
		if (strpos($str, '%p') !== false)
499
			$str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
500
	}
501
502
	// Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that.
503
	if ($support_e && strpos($str, '%e') !== false)
504
		$str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str);
505
506
	// Format any other characters..
507
	return strftime($str, $time);
508
}
509
510
/**
511
 * Used to render a timestamp to html5 <time> tag format.
512
 *
513
 * @param int $timestamp A unix timestamp
514
 *
515
 * @return string
516
 */
517
function htmlTime($timestamp)
518
{
519
	global $txt, $context;
520
521
	if (empty($timestamp))
522
		return '';
523
524
	$timestamp = forum_time(true, $timestamp);
525
	$time = date('Y-m-d H:i', $timestamp);
526
	$stdtime = standardTime($timestamp, true, true);
527
528
	// @todo maybe htmlspecialchars on the title attribute?
529
	return '<time title="' . (!empty($context['using_relative_time']) ? $stdtime : $txt['last_post']) . '" datetime="' . $time . '" data-timestamp="' . $timestamp . '">' . $stdtime . '</time>';
530
}
531
532
/**
533
 * Gets the current time with offset.
534
 *
535
 * What it does:
536
 *
537
 * - Always applies the offset in the time_offset setting.
538
 *
539
 * @param bool $use_user_offset = true if use_user_offset is true, applies the user's offset as well
540
 * @param int|null $timestamp = null A unix timestamp (null to use current time)
541
 *
542
 * @return int seconds since the unix epoch
543
 */
544
function forum_time($use_user_offset = true, $timestamp = null)
545
{
546
	global $user_info, $modSettings;
547
548
	if ($timestamp === null)
549
		$timestamp = time();
550
	elseif ($timestamp == 0)
551
		return 0;
552
553
	return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600;
554
}
555
556
/**
557
 * Removes special entities from strings.  Compatibility...
558
 *
559
 * - Faster than html_entity_decode
560
 * - Removes the base entities ( &amp; &quot; &#039; &lt; and &gt;. ) from text with htmlspecialchars_decode
561
 * - Additionally converts &nbsp with str_replace
562
 *
563
 * @param string $string The string to apply htmlspecialchars_decode
564
 *
565
 * @return string string without entities
566
 */
567
function un_htmlspecialchars($string)
568
{
569
	$string = htmlspecialchars_decode($string, ENT_QUOTES);
570
	$string = str_replace('&nbsp;', ' ', $string);
571
572
	return $string;
573
}
574
575
/**
576
 * Calculates all the possible permutations (orders) of an array.
577
 *
578
 * What it does:
579
 *
580
 * - Caution: should not be called on arrays bigger than 8 elements as this function is memory hungry
581
 * - returns an array containing each permutation.
582
 * - e.g. (1,2,3) returns (1,2,3), (1,3,2), (2,1,3), (2,3,1), (3,1,2), and (3,2,1)
583
 * - A combinations without repetition N! function so 3! = 6 and 10! = 3,628,800 combinations
584
 * - Used by parse_bbc to allow bbc tag parameters to be in any order and still be
585
 * parsed properly
586
 *
587
 * @deprecated since 1.0.5
588
 * @param mixed[] $array index array of values
589
 *
590
 * @return mixed[] array representing all permutations of the supplied array
591
 */
592
function permute($array)
593
{
594
	$orders = array($array);
595
596
	$n = count($array);
597
	$p = range(0, $n);
598
	for ($i = 1; $i < $n; null)
599
	{
600
		$p[$i]--;
601
		$j = $i % 2 != 0 ? $p[$i] : 0;
602
603
		$temp = $array[$i];
604
		$array[$i] = $array[$j];
605
		$array[$j] = $temp;
606
607
		for ($i = 1; $p[$i] == 0; $i++)
608
			$p[$i] = $i;
609
610
		$orders[] = $array;
611
	}
612
613
	return $orders;
614
}
615
616
/**
617
 * Lexicographic permutation function.
618
 *
619
 * This is a special type of permutation which involves the order of the set. The next
620
 * lexicographic permutation of '32541' is '34125'. Numerically, it is simply the smallest
621
 * set larger than the current one.
622
 *
623
 * The benefit of this over a recursive solution is that the whole list does NOT need
624
 * to be held in memory. So it's actually possible to run 30! permutations without
625
 * causing a memory overflow.
626
 *
627
 * Source: O'Reilly PHP Cookbook
628
 *
629
 * @param mixed[] $p The array keys to apply permutation
630
 * @param int $size The size of our permutation array
631
 *
632
 * @return mixed[] the next permutation of the passed array $p
633
 */
634
function pc_next_permutation($p, $size)
635
{
636
	// Slide down the array looking for where we're smaller than the next guy
637
	for ($i = $size - 1; isset($p[$i]) && $p[$i] >= $p[$i + 1]; --$i)
638
	{
639
	}
640
641
	// If this doesn't occur, we've finished our permutations
642
	// the array is reversed: (1, 2, 3, 4) => (4, 3, 2, 1)
643
	if ($i === -1)
644
	{
645
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array<mixed,mixed>.
Loading history...
646
	}
647
648
	// Slide down the array looking for a bigger number than what we found before
649
	for ($j = $size; $p[$j] <= $p[$i]; --$j)
650
	{
651
	}
652
653
	// Swap them
654
	$tmp = $p[$i];
655
	$p[$i] = $p[$j];
656
	$p[$j] = $tmp;
657
658
	// Now reverse the elements in between by swapping the ends
659
	for (++$i, $j = $size; $i < $j; ++$i, --$j)
660
	{
661
		$tmp = $p[$i];
662
		$p[$i] = $p[$j];
663
		$p[$j] = $tmp;
664
	}
665
666
	return $p;
667
}
668
669
/**
670
 * Parse bulletin board code in a string, as well as smileys optionally.
671
 *
672
 * @deprecated since 1.1b1
673
 *
674
 * What it does:
675
 *
676
 * - Only parses bbc tags which are not disabled in disabledBBC.
677
 * - Handles basic HTML, if enablePostHTML is on.
678
 * - Caches the from/to replace regular expressions so as not to reload them every time a string is parsed.
679
 * - Only parses smileys if smileys is true.
680
 *
681
 * @param string|false $message if false return list of enabled bbc codes
682
 * @param bool $smileys = true if to parse smileys as well
683
 *
684
 * @return string
685
 */
686
function parse_bbc($message, $smileys = true)
687
{
688
	// Don't waste cycles
689
	if ($message === '')
690
		return '';
691
692
	$parser = \BBC\ParserWrapper::instance();
693
694
	// This is a deprecated way of getting codes
695
	if ($message === false)
696
	{
697
		return $parser->getCodes();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $parser->getCodes() returns the type BBC\Codes which is incompatible with the documented return type string.
Loading history...
698
	}
699
700
	return $parser->parseMessage($message, $smileys);
701
}
702
703
/**
704
 * Parse smileys in the passed message.
705
 *
706
 * What it does:
707
 *
708
 * - The smiley parsing function which makes pretty faces appear :).
709
 * - If custom smiley sets are turned off by smiley_enable, the default set of smileys will be used.
710
 * - These are specifically not parsed in code tags [url=mailto:[email protected]]
711
 * - Caches the smileys from the database or array in memory.
712
 * - Doesn't return anything, but rather modifies message directly.
713
 *
714
 * @param string $message The string containing smileys to parse
715
 * @deprecated since 1.1b1
716
 */
717
function parsesmileys(&$message)
718
{
719
	// No smiley set at all?!
720
	if ($GLOBALS['user_info']['smiley_set'] == 'none' || trim($message) == '')
721
	{
722
		return;
723
	}
724
725
	$wrapper = \BBC\ParserWrapper::instance();
726
	$parser = $wrapper->getSmileyParser();
727
	$message = $parser->parseBlock($message);
728
}
729
730
/**
731
 * Highlight any code.
732
 *
733
 * What it does:
734
 *
735
 * - Uses PHP's highlight_string() to highlight PHP syntax
736
 * - does special handling to keep the tabs in the code available.
737
 * - used to parse PHP code from inside [code] and [php] tags.
738
 *
739
 * @param string $code The string containing php code
740
 *
741
 * @return string the code with highlighted HTML.
742
 */
743
function highlight_php_code($code)
744
{
745
	// Remove special characters.
746
	$code = un_htmlspecialchars(strtr($code, array('<br />' => "\n", "\t" => '___TAB();', '&#91;' => '[')));
747
748
	$buffer = str_replace(array("\n", "\r"), '', @highlight_string($code, true));
749
750
	// Yes, I know this is kludging it, but this is the best way to preserve tabs from PHP :P.
751
	$buffer = preg_replace('~___TAB(?:</(?:font|span)><(?:font color|span style)="[^"]*?">)?\\(\\);~', '<pre style="display: inline;">' . "\t" . '</pre>', $buffer);
752
753
	return strtr($buffer, array('\'' => '&#039;', '<code>' => '', '</code>' => ''));
754
}
755
756
/**
757
 * Ends execution and redirects the user to a new location
758
 *
759
 * What it does:
760
 *
761
 * - Makes sure the browser doesn't come back and repost the form data.
762
 * - Should be used whenever anything is posted.
763
 * - Calls AddMailQueue to process any mail queue items its can
764
 * - Calls call_integration_hook integrate_redirect before headers are sent
765
 * - Diverts final execution to obExit() which means a end to processing and sending of final output
766
 *
767
 * @event integrate_redirect called before headers are sent
768
 * @param string $setLocation = '' The URL to redirect to
769
 * @param bool $refresh = false, enable to send a refresh header, default is a location header
770
 * @throws Elk_Exception
771
 */
772
function redirectexit($setLocation = '', $refresh = false)
773
{
774
	global $scripturl, $context, $modSettings, $db_show_debug;
775
776
	// In case we have mail to send, better do that - as obExit doesn't always quite make it...
777
	if (!empty($context['flush_mail']))
778
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
779
		AddMailQueue(true);
780
781
	Notifications::instance()->send();
782
783
	$add = preg_match('~^(ftp|http)[s]?://~', $setLocation) == 0 && substr($setLocation, 0, 6) != 'about:';
784
785
	if ($add)
786
		$setLocation = $scripturl . ($setLocation != '' ? '?' . $setLocation : '');
787
788
	// Put the session ID in.
789
	if (empty($_COOKIE) && defined('SID') && SID != '')
790
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '(?!\?' . preg_quote(SID, '/') . ')\\??/', $scripturl . '?' . SID . ';', $setLocation);
791
	// Keep that debug in their for template debugging!
792
	elseif (isset($_GET['debug']))
793
		$setLocation = preg_replace('/^' . preg_quote($scripturl, '/') . '\\??/', $scripturl . '?debug;', $setLocation);
794
795
	if (!empty($modSettings['queryless_urls']) && detectServer()->supportRewrite())
796
	{
797
		if (defined('SID') && SID != '')
798
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '~') . '\?(?:' . SID . '(?:;|&|&amp;))((?:board|topic)=[^#]+?)(#[^"]*?)?$~', 'redirectexit_callback', $setLocation);
799
		else
800
			$setLocation = preg_replace_callback('~^' . preg_quote($scripturl, '~') . '\?((?:board|topic)=[^#"]+?)(#[^"]*?)?$~', 'redirectexit_callback', $setLocation);
801
	}
802
803
	// Maybe integrations want to change where we are heading?
804
	call_integration_hook('integrate_redirect', array(&$setLocation, &$refresh));
805
806
	// We send a Refresh header only in special cases because Location looks better. (and is quicker...)
807
	if ($refresh)
808
		header('Refresh: 0; URL=' . strtr($setLocation, array(' ' => '%20')));
809
	else
810
		header('Location: ' . str_replace(' ', '%20', $setLocation));
811
812
	// Debugging.
813
	if ($db_show_debug === true)
814
	{
815
		$_SESSION['debug_redirect'] = Debug::instance()->get_db();
816
	}
817
818
	obExit(false);
819
}
820
821
/**
822
 * URL fixer for redirect exit
823
 *
824
 * What it does:
825
 *
826
 * - Similar to the callback function used in ob_sessrewrite
827
 * - Evoked by enabling queryless_urls for systems that support that function
828
 *
829
 * @param mixed[] $matches results from the calling preg
830
 */
831
function redirectexit_callback($matches)
832
{
833
	global $scripturl;
834
835
	if (defined('SID') && SID != '')
836
		return $scripturl . '/' . strtr($matches[1], '&;=', '//,') . '.html?' . SID . (isset($matches[2]) ? $matches[2] : '');
837
	else
838
		return $scripturl . '/' . strtr($matches[1], '&;=', '//,') . '.html' . (isset($matches[2]) ? $matches[2] : '');
839
}
840
841
/**
842
 * Ends execution.
843
 *
844
 * What it does:
845
 *
846
 * - Takes care of template loading and remembering the previous URL.
847
 * - Calls ob_start() with ob_sessrewrite to fix URLs if necessary.
848
 *
849
 * @event integrate_invalid_old_url allows adding to "from" urls we don't save
850
 * @event integrate_exit inform portal, etc. that we're integrated with to exit
851
 * @param bool|null $header = null Output the header
852
 * @param bool|null $do_footer = null Output the footer
853
 * @param bool $from_index = false If we're coming from index.php
854
 * @param bool $from_fatal_error = false If we are exiting due to a fatal error
855
 *
856
 * @throws Elk_Exception
857
 */
858
function obExit($header = null, $do_footer = null, $from_index = false, $from_fatal_error = false)
859
{
860
	global $context, $txt, $db_show_debug;
861
862
	static $header_done = false, $footer_done = false, $level = 0, $has_fatal_error = false;
863
864
	// Attempt to prevent a recursive loop.
865
	++$level;
866
	if ($level > 1 && !$from_fatal_error && !$has_fatal_error)
867
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
868
869
	if ($from_fatal_error)
870
		$has_fatal_error = true;
871
872
	// Clear out the stat cache.
873
	trackStats();
874
875
	Notifications::instance()->send();
876
877
	// If we have mail to send, send it.
878
	if (!empty($context['flush_mail']))
879
		// @todo this relies on 'flush_mail' being only set in AddMailQueue itself... :\
880
		AddMailQueue(true);
881
882
	$do_header = $header === null ? !$header_done : $header;
883
	if ($do_footer === null)
884
		$do_footer = $do_header;
885
886
	// Has the template/header been done yet?
887
	if ($do_header)
888
	{
889
		// Was the page title set last minute? Also update the HTML safe one.
890
		if (!empty($context['page_title']) && empty($context['page_title_html_safe']))
891
			$context['page_title_html_safe'] = Util::htmlspecialchars(un_htmlspecialchars($context['page_title'])) . (!empty($context['current_page']) ? ' - ' . $txt['page'] . ' ' . ($context['current_page'] + 1) : '');
892
893
		// Start up the session URL fixer.
894
		ob_start('ob_sessrewrite');
895
896
		call_integration_buffer();
897
898
		// Display the screen in the logical order.
899
		template_header();
900
		$header_done = true;
901
	}
902
903
	if ($do_footer)
904
	{
905
		// Show the footer.
906
		loadSubTemplate(isset($context['sub_template']) ? $context['sub_template'] : 'main');
907
908
		// Just so we don't get caught in an endless loop of errors from the footer...
909
		if (!$footer_done)
910
		{
911
			$footer_done = true;
912
			template_footer();
913
914
			// Add $db_show_debug = true; to Settings.php if you want to show the debugging information.
915
			// (since this is just debugging... it's okay that it's after </html>.)
916
			if ($db_show_debug === true)
917
			{
918
				if (!isset($_REQUEST['xml']) && ((!isset($_GET['action']) || $_GET['action'] != 'viewquery') && !isset($_GET['api'])))
919
				{
920
					Debug::instance()->display();
921
				}
922
			}
923
		}
924
	}
925
926
	// Need user agent
927
	$req = request();
928
929
	setOldUrl();
930
931
	// For session check verification.... don't switch browsers...
932
	$_SESSION['USER_AGENT'] = $req->user_agent();
933
934
	// Hand off the output to the portal, etc. we're integrated with.
935
	call_integration_hook('integrate_exit', array($do_footer));
936
937
	// Don't exit if we're coming from index.php; that will pass through normally.
938
	if (!$from_index)
939
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
940
}
941
942
function setOldUrl($index = 'old_url')
943
{
944
	// Remember this URL in case someone doesn't like sending HTTP_REFERER.
945
	$invalid_old_url = array(
946
		'action=dlattach',
947
		'action=jsoption',
948
		'action=viewadminfile',
949
		';xml',
950
		';api',
951
	);
952
	call_integration_hook('integrate_invalid_old_url', array(&$invalid_old_url));
953
	$make_old = true;
954
	foreach ($invalid_old_url as $url)
955
	{
956
		if (strpos($_SERVER['REQUEST_URL'], $url) !== false)
957
		{
958
			$make_old = false;
959
			break;
960
		}
961
	}
962
	if ($make_old === true)
963
	{
964
		$_SESSION[$index] = $_SERVER['REQUEST_URL'];
965
	}
966
}
967
968
/**
969
 * Sets the class of the current topic based on is_very_hot, veryhot, hot, etc
970
 *
971
 * @param mixed[] $topic_context array of topic information
972
 */
973
function determineTopicClass(&$topic_context)
974
{
975
	// Set topic class depending on locked status and number of replies.
976
	if ($topic_context['is_very_hot'])
977
		$topic_context['class'] = 'veryhot';
978
	elseif ($topic_context['is_hot'])
979
		$topic_context['class'] = 'hot';
980
	else
981
		$topic_context['class'] = 'normal';
982
983
	$topic_context['class'] .= !empty($topic_context['is_poll']) ? '_poll' : '_post';
984
985
	if ($topic_context['is_locked'])
986
		$topic_context['class'] .= '_locked';
987
988
	if ($topic_context['is_sticky'])
989
		$topic_context['class'] .= '_sticky';
990
991
	// This is so old themes will still work.
992
	// @deprecated since 1.0 do not rely on it
993
	$topic_context['extended_class'] = &$topic_context['class'];
994
}
995
996
/**
997
 * Sets up the basic theme context stuff.
998
 *
999
 * @param bool $forceload = false
1000
 */
1001
function setupThemeContext($forceload = false)
1002
{
1003
	return theme()->setupThemeContext($forceload);
0 ignored issues
show
Bug introduced by
Are you sure the usage of theme()->setupThemeContext($forceload) targeting Themes\DefaultTheme\Theme::setupThemeContext() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1004
}
1005
1006
/**
1007
 * Helper function to set the system memory to a needed value
1008
 *
1009
 * What it does:
1010
 *
1011
 * - If the needed memory is greater than current, will attempt to get more
1012
 * - If in_use is set to true, will also try to take the current memory usage in to account
1013
 *
1014
 * @param string $needed The amount of memory to request, if needed, like 256M
1015
 * @param bool $in_use Set to true to account for current memory usage of the script
1016
 *
1017
 * @return boolean true if we have at least the needed memory
1018
 * @deprecated since 1.1
1019
 */
1020
function setMemoryLimit($needed, $in_use = false)
1021
{
1022
	return detectServer()->setMemoryLimit($needed, $in_use);
1023
}
1024
1025
/**
1026
 * Helper function to convert memory string settings to bytes
1027
 *
1028
 * @param string $val The byte string, like 256M or 1G
1029
 *
1030
 * @return integer The string converted to a proper integer in bytes
1031
 */
1032
function memoryReturnBytes($val)
1033
{
1034
	if (is_integer($val))
0 ignored issues
show
introduced by
The condition is_integer($val) is always false.
Loading history...
1035
		return $val;
1036
1037
	// Separate the number from the designator
1038
	$val = trim($val);
1039
	$num = intval(substr($val, 0, strlen($val) - 1));
1040
	$last = strtolower(substr($val, -1));
1041
1042
	// Convert to bytes
1043
	switch ($last)
1044
	{
1045
		// fall through select g = 1024*1024*1024
1046
		case 'g':
1047
			$num *= 1024;
1048
		// fall through select m = 1024*1024
1049
		case 'm':
1050
			$num *= 1024;
1051
		// fall through select k = 1024
1052
		case 'k':
1053
			$num *= 1024;
1054
	}
1055
1056
	return $num;
1057
}
1058
1059
/**
1060
 * Wrapper function for set_time_limit
1061
 *
1062
 * When called, attempts to restart the timeout counter from zero.
1063
 *
1064
 * This sets the maximum time in seconds a script is allowed to run before it is terminated by the parser.
1065
 * You can not change this setting with ini_set() when running in safe mode.
1066
 * Your web server can have other timeout configurations that may also interrupt PHP execution.
1067
 * Apache has a Timeout directive and IIS has a CGI timeout function.
1068
 * Security extension may also disable this function, such as Suhosin
1069
 * Hosts may add this to the disabled_functions list in php.ini
1070
 *
1071
 * If the current time limit is not unlimited it is possible to decrease the
1072
 * total time limit if the sum of the new time limit and the current time spent
1073
 * running the script is inferior to the original time limit. It is inherent to
1074
 * the way set_time_limit() works, it should rather be called with an
1075
 * appropriate value every time you need to allocate a certain amount of time
1076
 * to execute a task than only once at the beginning of the script.
1077
 *
1078
 * Before calling set_time_limit(), we check if this function is available
1079
 *
1080
 * @param int $time_limit The time limit
1081
 * @param bool $server_reset whether to reset the server timer or not
1082
 * @deprecated since 1.1
1083
 */
1084
function setTimeLimit($time_limit, $server_reset = true)
1085
{
1086
	return detectServer()->setTimeLimit($time_limit, $server_reset);
1087
}
1088
1089
/**
1090
 * This is the only template included in the sources.
1091
 */
1092
function template_rawdata()
1093
{
1094
	return theme()->template_rawdata();
0 ignored issues
show
Bug introduced by
Are you sure the usage of theme()->template_rawdata() targeting Themes\DefaultTheme\Theme::template_rawdata() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1095
}
1096
1097
/**
1098
 * The header template
1099
 */
1100
function template_header()
1101
{
1102
	return theme()->template_header();
0 ignored issues
show
Bug introduced by
Are you sure the usage of theme()->template_header() targeting Themes\DefaultTheme\Theme::template_header() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1103
}
1104
1105
/**
1106
 * Show the copyright.
1107
 */
1108
function theme_copyright()
1109
{
1110
	return theme()->theme_copyright();
0 ignored issues
show
Bug introduced by
Are you sure the usage of theme()->theme_copyright() targeting Themes\DefaultTheme\Theme::theme_copyright() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1111
}
1112
1113
/**
1114
 * The template footer
1115
 */
1116
function template_footer()
1117
{
1118
	return theme()->template_footer();
0 ignored issues
show
Bug introduced by
Are you sure the usage of theme()->template_footer() targeting Themes\DefaultTheme\Theme::template_footer() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1119
}
1120
1121
/**
1122
 * Output the Javascript files
1123
 *
1124
 * What it does:
1125
 *
1126
 * - tabbing in this function is to make the HTML source look proper
1127
 * - outputs jQuery/jQueryUI from the proper source (local/CDN)
1128
 * - if deferred is set function will output all JS (source & inline) set to load at page end
1129
 * - if the admin option to combine files is set, will use Combiner.class
1130
 *
1131
 * @param bool $do_deferred = false
1132
 */
1133
function template_javascript($do_deferred = false)
1134
{
1135
	theme()->template_javascript($do_deferred);
1136
	return;
1137
}
1138
1139
/**
1140
 * Output the CSS files
1141
 *
1142
 * What it does:
1143
 *  - If the admin option to combine files is set, will use Combiner.class
1144
 */
1145
function template_css()
1146
{
1147
	theme()->template_css();
1148
	return;
1149
}
1150
1151
/**
1152
 * Calls on template_show_error from index.template.php to show warnings
1153
 * and security errors for admins
1154
 */
1155
function template_admin_warning_above()
1156
{
1157
	theme()->template_admin_warning_above();
1158
	return;
1159
}
1160
1161
/**
1162
 * Convert a single IP to a ranged IP.
1163
 *
1164
 * - Internal function used to convert a user-readable format to a format suitable for the database.
1165
 *
1166
 * @param string $fullip A full dot notation IP address
1167
 *
1168
 * @return array|string 'unknown' if the ip in the input was '255.255.255.255'
1169
 */
1170
function ip2range($fullip)
1171
{
1172
	// If its IPv6, validate it first.
1173
	if (isValidIPv6($fullip) !== false)
1174
	{
1175
		$ip_parts = explode(':', expandIPv6($fullip, false));
1176
		$ip_array = array();
1177
1178
		if (count($ip_parts) != 8)
1179
			return array();
1180
1181
		for ($i = 0; $i < 8; $i++)
1182
		{
1183
			if ($ip_parts[$i] == '*')
1184
				$ip_array[$i] = array('low' => '0', 'high' => hexdec('ffff'));
1185
			elseif (preg_match('/^([0-9A-Fa-f]{1,4})\-([0-9A-Fa-f]{1,4})$/', $ip_parts[$i], $range) == 1)
1186
				$ip_array[$i] = array('low' => hexdec($range[1]), 'high' => hexdec($range[2]));
1187
			elseif (is_numeric(hexdec($ip_parts[$i])))
1188
				$ip_array[$i] = array('low' => hexdec($ip_parts[$i]), 'high' => hexdec($ip_parts[$i]));
1189
		}
1190
1191
		return $ip_array;
1192
	}
1193
1194
	// Pretend that 'unknown' is 255.255.255.255. (since that can't be an IP anyway.)
1195
	if ($fullip == 'unknown')
1196
		$fullip = '255.255.255.255';
1197
1198
	$ip_parts = explode('.', $fullip);
1199
	$ip_array = array();
1200
1201
	if (count($ip_parts) != 4)
1202
		return array();
1203
1204
	for ($i = 0; $i < 4; $i++)
1205
	{
1206
		if ($ip_parts[$i] == '*')
1207
			$ip_array[$i] = array('low' => '0', 'high' => '255');
1208
		elseif (preg_match('/^(\d{1,3})\-(\d{1,3})$/', $ip_parts[$i], $range) == 1)
1209
			$ip_array[$i] = array('low' => $range[1], 'high' => $range[2]);
1210
		elseif (is_numeric($ip_parts[$i]))
1211
			$ip_array[$i] = array('low' => $ip_parts[$i], 'high' => $ip_parts[$i]);
1212
	}
1213
1214
	// Makes it simpler to work with.
1215
	$ip_array[4] = array('low' => 0, 'high' => 0);
1216
	$ip_array[5] = array('low' => 0, 'high' => 0);
1217
	$ip_array[6] = array('low' => 0, 'high' => 0);
1218
	$ip_array[7] = array('low' => 0, 'high' => 0);
1219
1220
	return $ip_array;
1221
}
1222
1223
/**
1224
 * Lookup an IP; try shell_exec first because we can do a timeout on it.
1225
 *
1226
 * @param string $ip A full dot notation IP address
1227
 *
1228
 * @return string
1229
 */
1230
function host_from_ip($ip)
1231
{
1232
	global $modSettings;
1233
1234
	$cache = Cache::instance();
1235
1236
	$host = '';
1237
	if ($cache->getVar($host, 'hostlookup-' . $ip, 600) || empty($ip))
1238
		return $host;
1239
1240
	$t = microtime(true);
1241
1242
	// Try the Linux host command, perhaps?
1243
	if ((strpos(strtolower(PHP_OS), 'win') === false || strpos(strtolower(PHP_OS), 'darwin') !== false) && mt_rand(0, 1) == 1)
1244
	{
1245
		if (!isset($modSettings['host_to_dis']))
1246
			$test = @shell_exec('host -W 1 ' . @escapeshellarg($ip));
1247
		else
1248
			$test = @shell_exec('host ' . @escapeshellarg($ip));
1249
1250
		// Did host say it didn't find anything?
1251
		if (strpos($test, 'not found') !== false)
1252
			$host = '';
1253
		// Invalid server option?
1254
		elseif ((strpos($test, 'invalid option') || strpos($test, 'Invalid query name 1')) && !isset($modSettings['host_to_dis']))
1255
			updateSettings(array('host_to_dis' => 1));
1256
		// Maybe it found something, after all?
1257
		elseif (preg_match('~\s([^\s]+?)\.\s~', $test, $match) == 1)
1258
			$host = $match[1];
1259
	}
1260
1261
	// This is nslookup; usually only Windows, but possibly some Unix?
1262
	if (empty($host) && stripos(PHP_OS, 'win') !== false && strpos(strtolower(PHP_OS), 'darwin') === false && mt_rand(0, 1) == 1)
1263
	{
1264
		$test = @shell_exec('nslookup -timeout=1 ' . @escapeshellarg($ip));
1265
1266
		if (strpos($test, 'Non-existent domain') !== false)
1267
			$host = '';
1268
		elseif (preg_match('~Name:\s+([^\s]+)~', $test, $match) == 1)
1269
			$host = $match[1];
1270
	}
1271
1272
	// This is the last try :/.
1273
	if (!isset($host) || $host === false)
1274
		$host = @gethostbyaddr($ip);
1275
1276
	// It took a long time, so let's cache it!
1277
	if (microtime(true) - $t > 0.5)
1278
		$cache->put('hostlookup-' . $ip, $host, 600);
1279
1280
	return $host;
1281
}
1282
1283
/**
1284
 * Chops a string into words and prepares them to be inserted into (or searched from) the database.
1285
 *
1286
 * @param string $text The string to process
1287
 * @param int $max_chars defaults to 20
1288
 *     - if encrypt = true this is the maximum number of bytes to use in integer hashes (for searching)
1289
 *     - if encrypt = false this is the maximum number of letters in each word
1290
 * @param bool $encrypt = false Used for custom search indexes to return an int[] array representing the words
1291
 */
1292
function text2words($text, $max_chars = 20, $encrypt = false)
1293
{
1294
	// Step 1: Remove entities/things we don't consider words:
1295
	$words = preg_replace('~(?:[\x0B\0\x{A0}\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~u', ' ', strtr($text, array('<br />' => ' ')));
1296
1297
	// Step 2: Entities we left to letters, where applicable, lowercase.
1298
	$words = un_htmlspecialchars(Util::strtolower($words));
1299
1300
	// Step 3: Ready to split apart and index!
1301
	$words = explode(' ', $words);
1302
1303
	if ($encrypt)
1304
	{
1305
		// Range of characters that crypt will produce (0-9, a-z, A-Z .)
1306
		$possible_chars = array_flip(array_merge(range(46, 57), range(65, 90), range(97, 122)));
1307
		$returned_ints = array();
1308
		foreach ($words as $word)
1309
		{
1310
			if (($word = trim($word, '-_\'')) !== '')
1311
			{
1312
				// Get a crypt representation of this work
1313
				$encrypted = substr(crypt($word, 'uk'), 2, $max_chars);
1314
				$total = 0;
1315
1316
				// Create an integer representation
1317
				for ($i = 0; $i < $max_chars; $i++)
1318
					$total += $possible_chars[ord($encrypted[$i])] * pow(63, $i);
1319
1320
				// Return the value
1321
				$returned_ints[] = $max_chars == 4 ? min($total, 16777215) : $total;
1322
			}
1323
		}
1324
		return array_unique($returned_ints);
1325
	}
1326
	else
1327
	{
1328
		// Trim characters before and after and add slashes for database insertion.
1329
		$returned_words = array();
1330
		foreach ($words as $word)
1331
			if (($word = trim($word, '-_\'')) !== '')
1332
				$returned_words[] = $max_chars === null ? $word : substr($word, 0, $max_chars);
1333
1334
		// Filter out all words that occur more than once.
1335
		return array_unique($returned_words);
1336
	}
1337
}
1338
1339
/**
1340
 * Creates an image/text button
1341
 *
1342
 * @param string $name
1343
 * @param string $alt
1344
 * @param string $label = ''
1345
 * @param string|boolean $custom = ''
1346
 * @param boolean $force_use = false
1347
 *
1348
 * @return string
1349
 *
1350
 * @deprecated since 1.0 this will be removed at some point, do not rely on this function
1351
 */
1352
function create_button($name, $alt, $label = '', $custom = '', $force_use = false)
1353
{
1354
	global $settings, $txt;
1355
1356
	// Does the current loaded theme have this and we are not forcing the usage of this function?
1357
	if (function_exists('template_create_button') && !$force_use)
1358
		return template_create_button($name, $alt, $label = '', $custom = '');
1359
1360
	if (!$settings['use_image_buttons'])
1361
		return $txt[$alt];
1362
	elseif (!empty($settings['use_buttons']))
1363
		return '<img src="' . $settings['images_url'] . '/buttons/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . ' />' . ($label != '' ? '&nbsp;<strong>' . $txt[$label] . '</strong>' : '');
1364
	else
1365
		return '<img src="' . $settings['lang_images_url'] . '/' . $name . '" alt="' . $txt[$alt] . '" ' . $custom . ' />';
1366
}
1367
1368
/**
1369
 * Sets up all of the top menu buttons
1370
 *
1371
 * What it does:
1372
 *
1373
 * - Defines every master item in the menu, as well as any sub-items
1374
 * - Ensures the chosen action is set so the menu is highlighted
1375
 * - Saves them in the cache if it is available and on
1376
 * - Places the results in $context
1377
 */
1378
function setupMenuContext()
1379
{
1380
	return theme()->setupMenuContext();
0 ignored issues
show
Bug introduced by
Are you sure the usage of theme()->setupMenuContext() targeting Themes\DefaultTheme\Theme::setupMenuContext() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1381
}
1382
1383
/**
1384
 * Generate a random seed and ensure it's stored in settings.
1385
 * @deprecated
1386
 */
1387
function elk_seed_generator()
1388
{
1389
	global $modSettings;
1390
1391
	// Change the seed.
1392
	if (mt_rand(1, 250) == 69 || empty($modSettings['rand_seed']))
1393
		updateSettings(array('rand_seed' => mt_rand()));
1394
}
1395
1396
/**
1397
 * Process functions of an integration hook.
1398
 *
1399
 * What it does:
1400
 *
1401
 * - Calls all functions of the given hook.
1402
 * - Supports static class method calls.
1403
 *
1404
 * @param string $hook The name of the hook to call
1405
 * @param mixed[] $parameters = array() Parameters to pass to the hook
1406
 *
1407
 * @return mixed[] the results of the functions
1408
 */
1409
function call_integration_hook($hook, $parameters = array())
1410
{
1411
	return Hooks::instance()->hook($hook, $parameters);
1412
}
1413
1414
/**
1415
 * Includes files for hooks that only do that (i.e. integrate_pre_include)
1416
 *
1417
 * @param string $hook The name to include
1418
 */
1419
function call_integration_include_hook($hook)
1420
{
1421
	Hooks::instance()->include_hook($hook);
1422
}
1423
1424
/**
1425
 * Special hook call executed during obExit
1426
 */
1427
function call_integration_buffer()
1428
{
1429
	Hooks::instance()->buffer_hook();
1430
}
1431
1432
/**
1433
 * Add a function for integration hook.
1434
 *
1435
 * - Does nothing if the function is already added.
1436
 *
1437
 * @param string $hook The name of the hook to add
1438
 * @param string $function The function associated with the hook
1439
 * @param string $file The file that contains the function
1440
 * @param bool $permanent = true if true, updates the value in settings table
1441
 */
1442
function add_integration_function($hook, $function, $file = '', $permanent = true)
1443
{
1444
	Hooks::instance()->add($hook, $function, $file, $permanent);
1445
}
1446
1447
/**
1448
 * Remove an integration hook function.
1449
 *
1450
 * What it does:
1451
 *
1452
 * - Removes the given function from the given hook.
1453
 * - Does nothing if the function is not available.
1454
 *
1455
 * @param string $hook The name of the hook to remove
1456
 * @param string $function The name of the function
1457
 * @param string $file The file its located in
1458
 */
1459
function remove_integration_function($hook, $function, $file = '')
1460
{
1461
	Hooks::instance()->remove($hook, $function, $file);
1462
}
1463
1464
/**
1465
 * Decode numeric html entities to their UTF8 equivalent character.
1466
 *
1467
 * What it does:
1468
 *
1469
 * - Callback function for preg_replace_callback in subs-members
1470
 * - Uses capture group 2 in the supplied array
1471
 * - Does basic scan to ensure characters are inside a valid range
1472
 *
1473
 * @param mixed[] $matches matches from a preg_match_all
1474
 *
1475
 * @return string $string
1476
 */
1477
function replaceEntities__callback($matches)
1478
{
1479
	if (!isset($matches[2]))
1480
		return '';
1481
1482
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
1483
1484
	// remove left to right / right to left overrides
1485
	if ($num === 0x202D || $num === 0x202E)
1486
		return '';
1487
1488
	// Quote, Ampersand, Apostrophe, Less/Greater Than get html replaced
1489
	if (in_array($num, array(0x22, 0x26, 0x27, 0x3C, 0x3E)))
1490
		return '&#' . $num . ';';
1491
1492
	// <0x20 are control characters, 0x20 is a space, > 0x10FFFF is past the end of the utf8 character set
1493
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text)
1494
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF))
1495
		return '';
1496
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
1497
	elseif ($num < 0x80)
1498
		return chr($num);
0 ignored issues
show
Bug introduced by
It seems like $num can also be of type double; however, parameter $ascii of chr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1498
		return chr(/** @scrutinizer ignore-type */ $num);
Loading history...
1499
	// <0x800 (2048)
1500
	elseif ($num < 0x800)
1501
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
1502
	// < 0x10000 (65536)
1503
	elseif ($num < 0x10000)
1504
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1505
	// <= 0x10FFFF (1114111)
1506
	else
1507
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1508
}
1509
1510
/**
1511
 * Converts html entities to utf8 equivalents
1512
 *
1513
 * What it does:
1514
 *
1515
 * - Callback function for preg_replace_callback
1516
 * - Uses capture group 1 in the supplied array
1517
 * - Does basic checks to keep characters inside a viewable range.
1518
 *
1519
 * @param mixed[] $matches array of matches as output from preg_match_all
1520
 *
1521
 * @return string $string
1522
 */
1523
function fixchar__callback($matches)
1524
{
1525
	if (!isset($matches[1]))
1526
		return '';
1527
1528
	$num = $matches[1][0] === 'x' ? hexdec(substr($matches[1], 1)) : (int) $matches[1];
1529
1530
	// <0x20 are control characters, > 0x10FFFF is past the end of the utf8 character set
1531
	// 0xD800 >= $num <= 0xDFFF are surrogate markers (not valid for utf8 text), 0x202D-E are left to right overrides
1532
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202D || $num === 0x202E)
1533
		return '';
1534
	// <0x80 (or less than 128) are standard ascii characters a-z A-Z 0-9 and punctuation
1535
	elseif ($num < 0x80)
1536
		return chr($num);
0 ignored issues
show
Bug introduced by
It seems like $num can also be of type double; however, parameter $ascii of chr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1536
		return chr(/** @scrutinizer ignore-type */ $num);
Loading history...
1537
	// <0x800 (2048)
1538
	elseif ($num < 0x800)
1539
		return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
1540
	// < 0x10000 (65536)
1541
	elseif ($num < 0x10000)
1542
		return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1543
	// <= 0x10FFFF (1114111)
1544
	else
1545
		return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
1546
}
1547
1548
/**
1549
 * Strips out invalid html entities, replaces others with html style &#123; codes
1550
 *
1551
 * What it does:
1552
 *
1553
 * - Callback function used of preg_replace_callback in various $ent_checks,
1554
 * - For example strpos, strlen, substr etc
1555
 *
1556
 * @param mixed[] $matches array of matches for a preg_match_all
1557
 *
1558
 * @return string
1559
 */
1560
function entity_fix__callback($matches)
1561
{
1562
	if (!isset($matches[2]))
1563
		return '';
1564
1565
	$num = $matches[2][0] === 'x' ? hexdec(substr($matches[2], 1)) : (int) $matches[2];
1566
1567
	// We don't allow control characters, characters out of range, byte markers, etc
1568
	if ($num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202D || $num == 0x202E)
1569
		return '';
1570
	else
1571
		return '&#' . $num . ';';
1572
}
1573
1574
/**
1575
 * Retrieve additional search engines, if there are any, as an array.
1576
 *
1577
 * @return mixed[] array of engines
1578
 */
1579
function prepareSearchEngines()
1580
{
1581
	global $modSettings;
1582
1583
	$engines = array();
1584
	if (!empty($modSettings['additional_search_engines']))
1585
	{
1586
		$search_engines = Util::unserialize($modSettings['additional_search_engines']);
1587
		foreach ($search_engines as $engine)
1588
			$engines[strtolower(preg_replace('~[^A-Za-z0-9 ]~', '', $engine['name']))] = $engine;
1589
	}
1590
1591
	return $engines;
1592
}
1593
1594
/**
1595
 * This function receives a request handle and attempts to retrieve the next result.
1596
 *
1597
 * What it does:
1598
 *
1599
 * - It is used by the controller callbacks from the template, such as
1600
 * posts in topic display page, posts search results page, or personal messages.
1601
 *
1602
 * @param resource $messages_request holds a query result
1603
 * @param bool $reset
1604
 *
1605
 * @return integer|boolean
1606
 */
1607
function currentContext($messages_request, $reset = false)
1608
{
1609
	// Can't work with a database without a database :P
1610
	$db = database();
1611
1612
	// Start from the beginning...
1613
	if ($reset)
1614
		return $db->data_seek($messages_request, 0);
1615
1616
	// If the query has already returned false, get out of here
1617
	if (empty($messages_request))
1618
		return false;
1619
1620
	// Attempt to get the next message.
1621
	$message = $db->fetch_assoc($messages_request);
1622
	if (!$message)
1623
	{
1624
		$db->free_result($messages_request);
1625
1626
		return false;
1627
	}
1628
1629
	return $message;
1630
}
1631
1632
/**
1633
 * Helper function to insert an array in to an existing array
1634
 *
1635
 * What it does:
1636
 *
1637
 * - Intended for addon use to allow such things as
1638
 * - Adding in a new menu item to an existing menu array
1639
 *
1640
 * @param mixed[] $input the array we will insert to
1641
 * @param string $key the key in the array that we are looking to find for the insert action
1642
 * @param mixed[] $insert the actual data to insert before or after the key
1643
 * @param string $where adding before or after
1644
 * @param bool $assoc if the array is a assoc array with named keys or a basic index array
1645
 * @param bool $strict search for identical elements, this means it will also check the types of the needle.
1646
 */
1647
function elk_array_insert($input, $key, $insert, $where = 'before', $assoc = true, $strict = false)
1648
{
1649
	// Search for key names or values
1650
	if ($assoc)
1651
		$position = array_search($key, array_keys($input), $strict);
1652
	else
1653
		$position = array_search($key, $input, $strict);
1654
1655
	// If the key is not found, just insert it at the end
1656
	if ($position === false)
1657
		return array_merge($input, $insert);
1658
1659
	if ($where === 'after')
1660
		$position++;
1661
1662
	// Insert as first
1663
	if ($position === 0)
1664
		$input = array_merge($insert, $input);
1665
	else
1666
		$input = array_merge(array_slice($input, 0, $position), $insert, array_slice($input, $position));
0 ignored issues
show
Bug introduced by
It seems like $position can also be of type string; however, parameter $length of array_slice() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1666
		$input = array_merge(array_slice($input, 0, /** @scrutinizer ignore-type */ $position), $insert, array_slice($input, $position));
Loading history...
Bug introduced by
It seems like $position can also be of type string; however, parameter $offset of array_slice() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1666
		$input = array_merge(array_slice($input, 0, $position), $insert, array_slice($input, /** @scrutinizer ignore-type */ $position));
Loading history...
1667
1668
	return $input;
1669
}
1670
1671
/**
1672
 * Run a scheduled task now
1673
 *
1674
 * What it does:
1675
 *
1676
 * - From time to time it may be necessary to fire a scheduled task ASAP
1677
 * - This function sets the scheduled task to be called before any other one
1678
 *
1679
 * @param string $task the name of a scheduled task
1680
 */
1681
function scheduleTaskImmediate($task)
1682
{
1683
	global $modSettings;
1684
1685
	if (!isset($modSettings['scheduleTaskImmediate']))
1686
		$scheduleTaskImmediate = array();
1687
	else
1688
		$scheduleTaskImmediate = Util::unserialize($modSettings['scheduleTaskImmediate']);
1689
1690
	// If it has not been scheduled, the do so now
1691
	if (!isset($scheduleTaskImmediate[$task]))
1692
	{
1693
		$scheduleTaskImmediate[$task] = 0;
1694
		updateSettings(array('scheduleTaskImmediate' => serialize($scheduleTaskImmediate)));
1695
1696
		require_once(SUBSDIR . '/ScheduledTasks.subs.php');
1697
1698
		// Ensure the task is on
1699
		toggleTaskStatusByName($task, true);
1700
1701
		// Before trying to run it **NOW** :P
1702
		calculateNextTrigger($task, true);
1703
	}
1704
}
1705
1706
/**
1707
 * For diligent people: remove scheduleTaskImmediate when done, otherwise
1708
 * a maximum of 10 executions is allowed
1709
 *
1710
 * @param string $task the name of a scheduled task
1711
 * @param bool $calculateNextTrigger if recalculate the next task to execute
1712
 */
1713
function removeScheduleTaskImmediate($task, $calculateNextTrigger = true)
1714
{
1715
	global $modSettings;
1716
1717
	// Not on, bail
1718
	if (!isset($modSettings['scheduleTaskImmediate']))
1719
		return;
1720
	else
1721
		$scheduleTaskImmediate = Util::unserialize($modSettings['scheduleTaskImmediate']);
1722
1723
	// Clear / remove the task if it was set
1724
	if (isset($scheduleTaskImmediate[$task]))
1725
	{
1726
		unset($scheduleTaskImmediate[$task]);
1727
		updateSettings(array('scheduleTaskImmediate' => serialize($scheduleTaskImmediate)));
1728
1729
		// Recalculate the next task to execute
1730
		if ($calculateNextTrigger)
1731
		{
1732
			require_once(SUBSDIR . '/ScheduledTasks.subs.php');
1733
			calculateNextTrigger($task);
1734
		}
1735
	}
1736
}
1737
1738
/**
1739
 * Helper function to replace commonly used urls in text strings
1740
 *
1741
 * @event integrate_basic_url_replacement add additional place holder replacements
1742
 * @param string $string the string to inject URLs into
1743
 *
1744
 * @return string the input string with the place-holders replaced with
1745
 *           the correct URLs
1746
 */
1747
function replaceBasicActionUrl($string)
1748
{
1749
	global $scripturl, $context, $boardurl;
1750
	static $find_replace = null;
1751
1752
	if ($find_replace === null)
1753
	{
1754
		$find_replace = array(
1755
			'{forum_name}' => $context['forum_name'],
1756
			'{forum_name_html_safe}' => $context['forum_name_html_safe'],
1757
			'{forum_name_html_unsafe}' => un_htmlspecialchars($context['forum_name_html_safe']),
1758
			'{script_url}' => $scripturl,
1759
			'{board_url}' => $boardurl,
1760
			'{login_url}' => $scripturl . '?action=login',
1761
			'{register_url}' => $scripturl . '?action=register',
1762
			'{activate_url}' => $scripturl . '?action=register;sa=activate',
1763
			'{help_url}' => $scripturl . '?action=help',
1764
			'{admin_url}' => $scripturl . '?action=admin',
1765
			'{moderate_url}' => $scripturl . '?action=moderate',
1766
			'{recent_url}' => $scripturl . '?action=recent',
1767
			'{search_url}' => $scripturl . '?action=search',
1768
			'{who_url}' => $scripturl . '?action=who',
1769
			'{credits_url}' => $scripturl . '?action=who;sa=credits',
1770
			'{calendar_url}' => $scripturl . '?action=calendar',
1771
			'{memberlist_url}' => $scripturl . '?action=memberlist',
1772
			'{stats_url}' => $scripturl . '?action=stats',
1773
		);
1774
		call_integration_hook('integrate_basic_url_replacement', array(&$find_replace));
1775
	}
1776
1777
	return str_replace(array_keys($find_replace), array_values($find_replace), $string);
1778
}
1779
1780
/**
1781
 * This function creates a new GenericList from all the passed options.
1782
 *
1783
 * What it does:
1784
 *
1785
 * - Calls integration hook integrate_list_"unique_list_id" to allow easy modifying
1786
 *
1787
 * @event integrate_list_$listID called before every createlist to allow access to its listoptions
1788
 * @param mixed[] $listOptions associative array of option => value
1789
 */
1790
function createList($listOptions)
1791
{
1792
	call_integration_hook('integrate_list_' . $listOptions['id'], array(&$listOptions));
1793
1794
	$list = new Generic_List($listOptions);
1795
1796
	$list->buildList();
1797
}
1798
1799
/**
1800
 * This handy function retrieves a Request instance and passes it on.
1801
 *
1802
 * What it does:
1803
 *
1804
 * - To get hold of a Request, you can use this function or directly Request::instance().
1805
 * - This is for convenience, it simply delegates to Request::instance().
1806
 */
1807
function request()
1808
{
1809
	return Request::instance();
1810
}
1811
1812
/**
1813
 * Meant to replace any usage of $db_last_error.
1814
 *
1815
 * What it does:
1816
 *
1817
 * - Reads the file db_last_error.txt, if a time() is present returns it,
1818
 * otherwise returns 0.
1819
 */
1820
function db_last_error()
1821
{
1822
	$time = trim(file_get_contents(BOARDDIR . '/db_last_error.txt'));
1823
1824
	if (preg_match('~^\d{10}$~', $time) === 1)
1825
		return $time;
1826
	else
1827
		return 0;
1828
}
1829
1830
/**
1831
 * This function has the only task to retrieve the correct prefix to be used
1832
 * in responses.
1833
 *
1834
 * @return string - The prefix in the default language of the forum
1835
 */
1836
function response_prefix()
1837
{
1838
	global $language, $user_info, $txt;
1839
	static $response_prefix = null;
1840
1841
	$cache = Cache::instance();
1842
1843
	// Get a response prefix, but in the forum's default language.
1844
	if ($response_prefix === null && (!$cache->getVar($response_prefix, 'response_prefix') || !$response_prefix))
1845
	{
1846
		if ($language === $user_info['language'])
1847
			$response_prefix = $txt['response_prefix'];
1848
		else
1849
		{
1850
			loadLanguage('index', $language, false);
1851
			$response_prefix = $txt['response_prefix'];
1852
			loadLanguage('index');
1853
		}
1854
1855
		$cache->put('response_prefix', $response_prefix, 600);
1856
	}
1857
1858
	return $response_prefix;
1859
}
1860
1861
/**
1862
 * A very simple function to determine if an email address is "valid" for Elkarte.
1863
 *
1864
 * - A valid email for ElkArte is something that resembles an email (filter_var) and
1865
 * is less than 255 characters (for database limits)
1866
 *
1867
 * @param string $value - The string to evaluate as valid email
1868
 *
1869
 * @return string|false - The email if valid, false if not a valid email
1870
 */
1871
function isValidEmail($value)
1872
{
1873
	$value = trim($value);
1874
	if (filter_var($value, FILTER_VALIDATE_EMAIL) && Util::strlen($value) < 255)
1875
		return $value;
1876
	else
1877
		return false;
1878
}
1879
1880
/**
1881
 * Adds a protocol (http/s, ftp/mailto) to the beginning of an url if missing
1882
 *
1883
 * @param string $url - The url
1884
 * @param string[] $protocols - A list of protocols to check, the first is
1885
 *                 added if none is found (optional, default array('http://', 'https://'))
1886
 *
1887
 * @return string - The url with the protocol
1888
 */
1889
function addProtocol($url, $protocols = array())
1890
{
1891
	if (empty($protocols))
1892
	{
1893
		$pattern = '~^(http://|https://)~i';
1894
		$protocols = array('http://');
1895
	}
1896
	else
1897
	{
1898
		$pattern = '~^(' . implode('|', array_map(function ($val) {return preg_quote($val, '~');}, $protocols)) . ')~i';
1899
	}
1900
1901
	$found = false;
1902
	$url = preg_replace_callback($pattern, function($match) use (&$found) {
1903
		$found = true;
1904
1905
		return strtolower($match[0]);
1906
	}, $url);
1907
1908
	if ($found === true)
1909
	{
1910
			return $url;
1911
	}
1912
1913
	return $protocols[0] . $url;
1914
}
1915
1916
/**
1917
 * Removes nested quotes from a text string.
1918
 *
1919
 * @param string $text - The body we want to remove nested quotes from
1920
 *
1921
 * @return string - The same body, just without nested quotes
1922
 */
1923
function removeNestedQuotes($text)
1924
{
1925
	global $modSettings;
1926
1927
	// Remove any nested quotes, if necessary.
1928
	if (!empty($modSettings['removeNestedQuotes']))
1929
	{
1930
		return preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $text);
1931
	}
1932
	else
1933
	{
1934
		return $text;
1935
	}
1936
}
1937
1938
/**
1939
 * Change a \t to a span that will show a tab
1940
 *
1941
 * @param string $string
1942
 *
1943
 * @return string
1944
 */
1945
function tabToHtmlTab($string)
1946
{
1947
	return str_replace("\t", "<span class=\"tab\">\t</span>", $string);
1948
}
1949
1950
/**
1951
 * Remove <br />
1952
 *
1953
 * @param string $string
1954
 *
1955
 * @return string
1956
 */
1957
function removeBr($string)
1958
{
1959
	return str_replace('<br />', '', $string);
1960
}
1961
1962
/**
1963
 * Are we using this browser?
1964
 *
1965
 * - Wrapper function for detectBrowser
1966
 *
1967
 * @param string $browser  the browser we are checking for.
1968
 */
1969
function isBrowser($browser)
1970
{
1971
	global $context;
1972
1973
	// Don't know any browser!
1974
	if (empty($context['browser']))
1975
		detectBrowser();
1976
1977
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1978
}
1979
1980
/**
1981
 * Replace all vulgar words with respective proper words. (substring or whole words..)
1982
 *
1983
 * @deprecated use censor() or Censor class
1984
 *
1985
 * What it does:
1986
 *
1987
 * - it censors the passed string.
1988
 * - if the admin setting allow_no_censored is on it does not censor unless force is also set.
1989
 * - if the admin setting allow_no_censored is off will censor words unless the user has set
1990
 * it to not censor in their profile and force is off
1991
 * - it caches the list of censored words to reduce parsing.
1992
 * - Returns the censored text
1993
 *
1994
 * @param string &$text
1995
 * @param bool $force = false
1996
 */
1997
function censorText(&$text, $force = false)
1998
{
1999
	$text = censor($text, $force);
2000
2001
	return $text;
2002
}
2003
2004
/**
2005
 * Replace all vulgar words with respective proper words. (substring or whole words..)
2006
 *
2007
 * What it does:
2008
 *
2009
 * - it censors the passed string.
2010
 * - if the admin setting allow_no_censored is on it does not censor unless force is also set.
2011
 * - if the admin setting allow_no_censored is off will censor words unless the user has set
2012
 * it to not censor in their profile and force is off
2013
 * - it caches the list of censored words to reduce parsing.
2014
 * - Returns the censored text
2015
 *
2016
 * @param string $text
2017
 * @param bool $force = false
2018
 */
2019
function censor($text, $force = false)
2020
{
2021
	global $modSettings;
2022
	static $censor = null;
2023
2024
	if ($censor === null)
2025
	{
2026
		$censor = new Censor(explode("\n", $modSettings['censor_vulgar']), explode("\n", $modSettings['censor_proper']), $modSettings);
2027
	}
2028
2029
	return $censor->censor($text, $force);
2030
}
2031
2032
/**
2033
 * Helper function able to determine if the current member can see at least
2034
 * one button of a button strip.
2035
 *
2036
 * @param mixed[] $button_strip
2037
 *
2038
 * @return bool
2039
 */
2040
function can_see_button_strip($button_strip)
2041
{
2042
	global $context;
2043
2044
	foreach ($button_strip as $key => $value)
2045
	{
2046
		if (!isset($value['test']) || !empty($context[$value['test']]))
2047
			return true;
2048
	}
2049
2050
	return false;
2051
}
2052
2053
/**
2054
 * @return Themes\DefaultTheme\Theme
2055
 */
2056
function theme()
2057
{
2058
	return $GLOBALS['context']['theme_instance'];
2059
}
2060
2061
/**
2062
 * Stops the execution with a 1x1 gif file
2063
 *
2064
 * @param bool $expired Sends an expired header.
2065
 */
2066
function dieGif($expired = false)
2067
{
2068
	// The following is an attempt at stopping the behavior identified in #2391
2069
	if (function_exists('fastcgi_finish_request'))
2070
	{
2071
		die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2072
	}
2073
2074
	if ($expired === true)
2075
	{
2076
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
2077
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
2078
	}
2079
2080
	header('Content-Type: image/gif');
2081
	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");
2082
}
2083
2084
/**
2085
 * Prepare ob_start with or without gzip compression
2086
 *
2087
 * @param bool $use_compression Starts compressed headers.
2088
 */
2089
function obStart($use_compression = false)
2090
{
2091
	// This is done to clear any output that was made before now.
2092
	while (ob_get_level() > 0)
2093
	{
2094
		@ob_end_clean();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ob_end_clean(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2094
		/** @scrutinizer ignore-unhandled */ @ob_end_clean();

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...
2095
	}
2096
2097
	if ($use_compression === true)
2098
	{
2099
		ob_start('ob_gzhandler');
2100
	}
2101
	else
2102
	{
2103
		ob_start();
2104
		header('Content-Encoding: none');
2105
	}
2106
}
2107