Completed
Push — master ( 510ba9...4a3457 )
by Nazar
19:55
created

functions.php ➔ __htmlpurifier_autoload()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 9
ccs 0
cts 0
cp 0
crap 6
rs 9.6666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
/**
9
 * Base system functions, do not edit this file, or make it very carefully
10
 * otherwise system workability may be broken
11
 */
12
use
13
	cs\Cache,
14
	cs\Config,
15
	cs\HTMLPurifier_Filter_iframe_sandbox,
16
	cs\Language,
17
	cs\Page;
18
19
/**
20
 * @param string $file
21
 *
22
 * @return array
23
 */
24
function __classes_get_from_cache ($file) {
25 192
	return defined('CACHE') && file_exists(CACHE."/classes/$file") ? file_get_json(CACHE."/classes/$file") : [];
26
}
27
28
/**
29
 * @param string $file
30
 * @param array  $content
31
 */
32
function __classes_put_into_cache ($file, $content) {
33 128
	if (defined('CACHE') && is_dir(CACHE)) {
34
		/** @noinspection MkdirRaceConditionInspection */
35 36
		@mkdir(CACHE.'/classes', 0770);
36 36
		file_put_json(CACHE."/classes/$file", $content);
37
	}
38 128
}
39
40
/**
41
 * Clean cache of classes autoload and customization
42
 */
43
function __classes_clean_cache () {
44
	@unlink(CACHE.'/classes/autoload');
45
	@unlink(CACHE.'/classes/aliases');
46
	@unlink(CACHE.'/classes/modified');
47
}
48
49
/**
50
 * Auto Loading of classes
51
 */
52 202
spl_autoload_register(
53
	function ($class) {
54 192
		static $cache, $aliases;
55 192
		if (!isset($cache)) {
56 192
			$cache   = __classes_get_from_cache('autoload');
57 192
			$aliases = __classes_get_from_cache('aliases');
58
		}
59 192
		if (isset($aliases[$class])) {
60 2
			spl_autoload_call($aliases[$class]);
61 2
			return class_exists($class, false) || (class_exists($aliases[$class], false) && class_alias($aliases[$class], $class));
62
		}
63 192
		if (isset($cache[$class])) {
64 98
			if ($cache[$class]) {
65 96
				require $cache[$class];
66
			}
67 98
			return (bool)$cache[$class];
68
		}
69 118
		$prepared_class_name = ltrim($class, '\\');
70 118
		if (strpos($prepared_class_name, 'cs\\') === 0) {
71 116
			$prepared_class_name = substr($prepared_class_name, 3);
72
		}
73 118
		$prepared_class_name = explode('\\', $prepared_class_name);
74 118
		$namespace           = count($prepared_class_name) > 1 ? implode('/', array_slice($prepared_class_name, 0, -1)) : '';
75 118
		$class_name          = array_pop($prepared_class_name);
76 118
		$cache[$class]       = false;
77
		/**
78
		 * Try to load classes from different places. If not found in one place - try in another.
79
		 */
80
		if (
81 118
			file_exists($file = CORE."/classes/$namespace/$class_name.php") ||    //Core classes
82 92
			file_exists($file = CORE."/thirdparty/$namespace/$class_name.php") || //Third party classes
83 88
			file_exists($file = CORE."/traits/$namespace/$class_name.php") ||     //Core traits
84 30
			file_exists($file = CORE."/drivers/$namespace/$class_name.php") ||    //Core drivers
85 118
			file_exists($file = MODULES."/../$namespace/$class_name.php")         //Classes in modules
86
		) {
87 118
			$cache[$class] = realpath($file);
88 118
			__classes_put_into_cache('autoload', $cache);
89 118
			require $file;
90 118
			return true;
91
		}
92 8
		__classes_put_into_cache('autoload', $cache);
93
		// Processing components aliases
94 8
		if (strpos($namespace, 'modules') === 0) {
95 2
			$Config      = Config::instance();
96 2
			$directories = [];
97 2
			foreach ($Config->components['modules'] ?: [] as $module_name => $module_data) {
98 2
				if ($module_data['active'] == Config\Module_Properties::UNINSTALLED) {
99 2
					continue;
100
				}
101 2
				$directories[] = MODULES."/$module_name";
102
			}
103 2
			$class_exploded = explode('\\', $class);
104 2
			foreach ($directories as $directory) {
105 2
				if (file_exists("$directory/meta.json")) {
106 2
					$meta = file_get_json("$directory/meta.json") + ['provide' => []];
107 2
					if ($class_exploded[2] != $meta['package'] && in_array($class_exploded[2], (array)$meta['provide'])) {
108 2
						$class_exploded[2] = $meta['package'];
109 2
						$alias             = implode('\\', $class_exploded);
110 2
						$aliases[$class]   = $alias;
111 2
						__classes_put_into_cache('aliases', $aliases);
112 2
						spl_autoload_call($alias);
113 2
						return class_exists($class, false) || (class_exists($alias, false) && class_alias($alias, $class));
114
					}
115
				}
116
			}
117
		}
118 8
		return false;
119 202
	}
120
);
121
122
/**
123
 * Get or set modified classes (used in Singleton trait)
124
 *
125
 * @param array|null $updated_modified_classes
126
 *
127
 * @return array
128
 */
129
function modified_classes ($updated_modified_classes = null) {
130 144
	static $modified_classes;
131 144
	if (!isset($modified_classes)) {
132 144
		$modified_classes = __classes_get_from_cache('modified');
133
	}
134 144
	if ($updated_modified_classes) {
135 68
		$modified_classes = $updated_modified_classes;
136 68
		__classes_put_into_cache('modified', $modified_classes);
137
	}
138 144
	return $modified_classes;
139
}
140
141
/**
142
 * Easy getting of translations
143
 *
144
 * @param string  $item
145
 * @param mixed[] $arguments There can be any necessary number of arguments here
146
 *
147
 * @return string
148
 */
149
function __ ($item, ...$arguments) {
150 2
	$L = Language::instance();
151 2
	if (func_num_args() > 1) {
152 2
		return $L->format($item, ...$arguments);
153
	} else {
154 2
		return $L->$item;
155
	}
156
}
157
158
/**
159
 * Public cache cleaning
160
 *
161
 * @return bool
162
 */
163
function clean_pcache () {
164
	$ok   = true;
165
	$list = get_files_list(PUBLIC_CACHE, false, 'fd', true, true, 'name|desc');
166
	foreach ($list as $item) {
167
		if (is_writable($item)) {
168
			is_dir($item) ? @rmdir_recursive($item) : @unlink($item);
169
		} else {
170
			$ok = false;
171
		}
172
	}
173
	return $ok;
174
}
175
176
/**
177
 * Formatting of time in seconds to human-readable form
178
 *
179
 * @param int $time Time in seconds
180
 *
181
 * @return string
182
 */
183
function format_time ($time) {
184
	if (!is_numeric($time)) {
185
		return $time;
186
	}
187
	$L   = Language::instance();
188
	$res = [];
189
	if ($time >= 31536000) {
190
		$time_x = round($time / 31536000);
191
		$time -= $time_x * 31536000;
192
		$res[] = $L->time($time_x, 'y');
193
	}
194
	if ($time >= 2592000) {
195
		$time_x = round($time / 2592000);
196
		$time -= $time_x * 2592000;
197
		$res[] = $L->time($time_x, 'M');
198
	}
199
	if ($time >= 86400) {
200
		$time_x = round($time / 86400);
201
		$time -= $time_x * 86400;
202
		$res[] = $L->time($time_x, 'd');
203
	}
204
	if ($time >= 3600) {
205
		$time_x = round($time / 3600);
206
		$time -= $time_x * 3600;
207
		$res[] = $L->time($time_x, 'h');
208
	}
209
	if ($time >= 60) {
210
		$time_x = round($time / 60);
211
		$time -= $time_x * 60;
212
		$res[] = $L->time($time_x, 'm');
213
	}
214
	if ($time > 0 || empty($res)) {
215
		$res[] = $L->time($time, 's');
216
	}
217
	return implode(' ', $res);
218
}
219
220
/**
221
 * Formatting of data size in bytes to human-readable form
222
 *
223
 * @param int      $size
224
 * @param bool|int $round
225
 *
226
 * @return float|string
227
 */
228
function format_filesize ($size, $round = false) {
229
	if (!is_numeric($size)) {
230
		return $size;
231
	}
232
	$L    = Language::prefix('system_filesize_');
233
	$unit = '';
234
	if ($size >= 1099511627776) {
235
		$size /= 1099511627776;
236
		$unit = " $L->TiB";
237
	} elseif ($size >= 1073741824) {
238
		$size /= 1073741824;
239
		$unit = " $L->GiB";
240
	} elseif ($size >= 1048576) {
241
		$size /= 1048576;
242
		$unit = " $L->MiB";
243
	} elseif ($size >= 1024) {
244
		$size /= 1024;
245
		$unit = " $L->KiB";
246
	} else {
247
		$size = "$size $L->Bytes";
248
	}
249
	return $round ? round($size, $round).$unit : $size.$unit;
250
}
251
252
/**
253
 * Get list of timezones
254
 *
255
 * @return array
256
 */
257
function get_timezones_list () {
258 8
	$timezones = [];
259 8
	foreach (timezone_identifiers_list() as $timezone) {
260 8
		$offset          = (new DateTimeZone($timezone))->getOffset(new DateTime);
261 8
		$key             = (39600 + $offset).$timezone;
262 8
		$sign            = ($offset < 0 ? '-' : '+');
263 8
		$hours           = str_pad(floor(abs($offset / 3600)), 2, 0, STR_PAD_LEFT);
264 8
		$minutes         = str_pad(abs(($offset % 3600) / 60), 2, 0, STR_PAD_LEFT);
265 8
		$timezones[$key] = [
266 8
			'key'   => str_replace('_', ' ', $timezone)." ($sign$hours:$minutes)",
267 8
			'value' => $timezone
268
		];
269
	}
270 8
	ksort($timezones, SORT_NATURAL);
271 8
	return array_column($timezones, 'value', 'key');
272
}
273
274
/**
275
 * String representation of HTTP status code
276
 *
277
 * @param int $code
278
 *
279
 * @return null|string
280
 */
281
function status_code_string ($code) {
282
	$code_to_string = [
283 14
		201 => '201 Created',
284
		202 => '202 Accepted',
285
		301 => '301 Moved Permanently',
286
		302 => '302 Found',
287
		303 => '303 See Other',
288
		307 => '307 Temporary Redirect',
289
		400 => '400 Bad Request',
290
		403 => '403 Forbidden',
291
		404 => '404 Not Found',
292
		405 => '405 Method Not Allowed',
293
		409 => '409 Conflict',
294
		429 => '429 Too Many Requests',
295
		500 => '500 Internal Server Error',
296
		501 => '501 Not Implemented',
297
		503 => '503 Service Unavailable'
298
	];
299 14
	return @$code_to_string[$code];
300
}
301
302
/**
303
 * Pages navigation based on links
304
 *
305
 * @param int             $page       Current page
306
 * @param int             $total      Total pages number
307
 * @param callable|string $url        if string - it will be formatted with sprintf with one parameter - page number<br>
308
 *                                    if callable - one parameter will be given, callable should return url string
309
 * @param bool            $head_links If <b>true</b> - links with rel="prev" and rel="next" will be added
310
 *
311
 * @return bool|string <b>false</b> if single page, otherwise string, set of navigation links
312
 */
313
function pages ($page, $total, $url, $head_links = false) {
314 2
	if ($total == 1) {
315 2
		return false;
316
	}
317 2
	$Page             = Page::instance();
318 2
	$original_url     = $url;
319 2
	$base_url         = Config::instance()->base_url();
320
	$url              = function ($page) use ($original_url, $base_url) {
321 2
		$href = is_callable($original_url) ? $original_url($page) : sprintf($original_url, $page);
322 2
		if (is_string($href) && strpos($href, 'http') !== 0) {
323 2
			$href = ltrim($href, '/');
324 2
			$href = "$base_url/$href";
325
		}
326 2
		return $href;
327 2
	};
328 2
	$output           = [];
329
	$render_page_item = function ($i) use ($Page, $page, $url, $head_links, &$output) {
330 2
		$href = $url($i);
331 2
		if ($head_links) {
332
			switch ($i) {
333 2
				case $page - 1:
334 2
					$Page->link(['href' => $href, 'rel' => 'prev']);
335 2
					break;
336 2
				case $page + 1:
337 2
					$Page->link(['href' => $href, 'rel' => 'next']);
338 2
					break;
339 2
				case $page:
340 2
					$Page->canonical_url($href);
341 2
					break;
342
			}
343
		}
344 2
		$output[] = [
345 2
			$i,
346
			[
347 2
				'href'    => $i == $page ? false : $href,
348 2
				'is'      => 'cs-link-button',
349 2
				'primary' => $i == $page
350
			]
351
		];
352 2
	};
353 2
	if ($total <= 11) {
354 2
		array_map($render_page_item, range(1, $total));
355
	} else {
356 2
		if ($page <= 6) {
357 2
			array_map($render_page_item, range(1, 7));
358 2
			$output[] = [
359
				'...',
360
				[
361
					'disabled' => true
362
				]
363
			];
364 2
			array_map($render_page_item, range($total - 2, $total));
365 2
		} elseif ($page >= $total - 5) {
366 2
			array_map($render_page_item, range(1, 3));
367 2
			$output[] = [
368
				'...',
369
				[
370
					'disabled' => true
371
				]
372
			];
373 2
			array_map($render_page_item, range($total - 6, $total));
374
		} else {
375 2
			array_map($render_page_item, range(1, 2));
376 2
			$output[] = [
377
				'...',
378
				[
379
					'disabled' => true
380
				]
381
			];
382 2
			array_map($render_page_item, range($page - 2, $page + 2));
383 2
			$output[] = [
384
				'...',
385
				[
386
					'disabled' => true
387
				]
388
			];
389 2
			array_map($render_page_item, range($total - 1, $total));
390
		}
391
	}
392 2
	return h::{'a[is=cs-link-button]'}($output);
393
}
394
395
/**
396
 * Pages navigation based on buttons (for search forms, etc.)
397
 *
398
 * @param int                  $page  Current page
399
 * @param int                  $total Total pages number
400
 * @param bool|callable|string $url   Adds <i>formaction</i> parameter to every button<br>
401
 *                                    if <b>false</b> - only form parameter <i>page</i> will we added<br>
402
 *                                    if string - it will be formatted with sprintf with one parameter - page number<br>
403
 *                                    if callable - one parameter will be given, callable should return url string
404
 *
405
 * @return false|string                        <b>false</b> if single page, otherwise string, set of navigation buttons
406
 */
407
function pages_buttons ($page, $total, $url = false) {
408 2
	if ($total == 1) {
409 2
		return false;
410
	}
411 2
	if (!is_callable($url)) {
412 2
		$original_url = $url;
413
		$url          = function ($page) use ($original_url) {
414 2
			return sprintf($original_url, $page);
415 2
		};
416
	}
417 2
	$output           = [];
418
	$render_page_item = function ($i) use ($page, $url, &$output) {
419 2
		$output[] = [
420 2
			$i,
421
			[
422 2
				'formaction' => $i == $page || $url === false ? false : $url($i),
423 2
				'value'      => $i == $page ? false : $i,
424 2
				'type'       => $i == $page ? 'button' : 'submit',
425 2
				'primary'    => $i == $page
426
			]
427
		];
428 2
	};
429 2
	if ($total <= 11) {
430 2
		array_map($render_page_item, range(1, $total));
431
	} else {
432 2
		if ($page <= 6) {
433 2
			array_map($render_page_item, range(1, 7));
434 2
			$output[] = [
435
				'...',
436
				[
437
					'type' => 'button',
438
					'disabled'
439
				]
440
			];
441 2
			array_map($render_page_item, range($total - 2, $total));
442 2
		} elseif ($page >= $total - 5) {
443 2
			array_map($render_page_item, range(1, 3));
444 2
			$output[] = [
445
				'...',
446
				[
447
					'type' => 'button',
448
					'disabled'
449
				]
450
			];
451 2
			array_map($render_page_item, range($total - 6, $total));
452
		} else {
453 2
			array_map($render_page_item, range(1, 2));
454 2
			$output[] = [
455
				'...',
456
				[
457
					'type' => 'button',
458
					'disabled'
459
				]
460
			];
461 2
			array_map($render_page_item, range($page - 2, $page + 2));
462 2
			$output[] = [
463
				'...',
464
				[
465
					'type' => 'button',
466
					'disabled'
467
				]
468
			];
469 2
			array_map($render_page_item, range($total - 1, $total));
470
		}
471
	}
472 2
	return h::{'button[is=cs-button][name=page]'}($output);
473
}
474
475
/**
476
 * Checks whether specified functionality available or not
477
 *
478
 * @param string|string[] $functionality One functionality or array of them
479
 *
480
 * @return bool `true` if all functionality available, `false` otherwise
481
 */
482
function functionality ($functionality) {
483 2
	if (is_array($functionality)) {
484 2
		$result = true;
485 2
		foreach ($functionality as $f) {
486 2
			$result = $result && functionality($f);
487
		}
488 2
		return $result;
489
	}
490 2
	$all = Cache::instance()->get(
491 2
		'functionality',
492
		function () {
493 2
			$functionality = [];
494 2
			$Config        = Config::instance();
495 2
			foreach (array_keys($Config->components['modules']) as $module) {
496 2
				if (!$Config->module($module)->enabled() || !file_exists(MODULES."/$module/meta.json")) {
497 2
					continue;
498
				}
499 2
				$functionality[] = [$module];
500 2
				$meta            = file_get_json(MODULES."/$module/meta.json");
501 2
				if (isset($meta['provide'])) {
502 2
					$functionality[] = (array)$meta['provide'];
503
				}
504
			}
505 2
			return array_merge(...$functionality);
506 2
		}
507
	);
508 2
	return in_array($functionality, $all);
509
}
510
511
/**
512
 * XSS Attack Protection. Returns secure string using several types of filters
513
 *
514
 * @param string|string[] $in     HTML code
515
 * @param bool|string     $html   <b>text</b> - text at output (default)<br>
516
 *                                <b>true</b> - processed HTML at output<br>
517
 *                                <b>false</b> - HTML tags will be deleted
518
 * @param bool            $iframe Whether to allow iframes without inner content (for example, video from youtube)<br>
519
 *                                Works only if <i>$html === true</i>
520
 *
521
 * @return string|string[]
522
 */
523
function xap ($in, $html = 'text', $iframe = false) {
524 52
	static $purifier, $purifier_iframe, $purifier_no_tags;
525 52
	if (is_array($in)) {
526 34
		foreach ($in as &$item) {
527 34
			$item = xap($item, $html, $iframe);
528
		}
529 34
		return $in;
530
	}
531
	/**
532
	 * Text mode
533
	 */
534 52
	if ($html === 'text') {
535 48
		return htmlspecialchars($in, ENT_NOQUOTES | ENT_HTML5 | ENT_DISALLOWED | ENT_SUBSTITUTE | ENT_HTML5);
536
	}
537 16
	if (!isset($purifier)) {
538 16
		$config_array = [
539 16
			'HTML.Doctype'         => 'HTML 4.01 Transitional',
540
			'Attr.EnableID'        => true,
541 16
			'Attr.ID.HTML5'        => true,
542
			'Attr.IDPrefix'        => 'content-',
543
			'CSS.MaxImgLength'     => null,
544
			'Cache.DefinitionImpl' => null,
545
			'Output.Newline'       => "\n",
546
			'URI.Munge'            => 'redirect/%s'
547
		];
548
549
		$config = HTMLPurifier_Config::createDefault();
550
		$config->loadArray($config_array);
551 16
		$purifier = new HTMLPurifier($config_array);
552 16
553 16
		$config_no_tags = HTMLPurifier_Config::createDefault();
554
		$config_no_tags->loadArray($config_array);
555 16
		$config_no_tags->set('HTML.AllowedElements', []);
556 16
		$purifier_no_tags = new HTMLPurifier($config_no_tags);
557 16
558 16
		$config_iframe = HTMLPurifier_Config::createDefault();
559
		$config_iframe->loadArray($config_array);
560 16
		$config_iframe->set('Filter.Custom', [new HTMLPurifier_Filter_iframe_sandbox]);
561 16
		$purifier_iframe = new HTMLPurifier($config_iframe);
562 16
	}
563 16
	if ($html === false) {
564
		return $purifier_no_tags->purify($in);
565 16
	}
566 2
	if ($iframe) {
567
		return $purifier_iframe->purify($in);
568 16
	} else {
569 2
		return $purifier->purify($in);
570
	}
571 16
}
572
573 202
/**
574
 * @param string $class
575
 *
576
 * @return bool
1 ignored issue
show
Documentation introduced by
Should the return type not be boolean|null?

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...
577
 */
578
function __htmlpurifier_autoload ($class) {
579
	$class = ltrim($class, '\\');
580
	if (strpos($class, 'HTMLPurifier_') === 0) {
581
		spl_autoload_unregister('__htmlpurifier_autoload');
582
		Phar::loadPhar(__DIR__.'/thirdparty/htmlpurifier.tar.gz', 'htmlpurifier.phar');
583
		require_once 'phar://htmlpurifier.phar/HTMLPurifier.standalone.php';
584
		return true;
585
	}
586
}
587
588
spl_autoload_register('__htmlpurifier_autoload', true, true);
589