Completed
Push — master ( f74224...a8b857 )
by Nazar
04:25
created

functions.php ➔ format_time()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 18
nc 4
nop 1
dl 0
loc 23
ccs 0
cts 0
cp 0
crap 20
rs 8.7972
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
	$units = [
190
		60 * 60 * 24 * 30 * 365 => 'y',
191
		60 * 60 * 24 * 30       => 'M',
192
		60 * 60 * 24            => 'd',
193
		60 * 60                 => 'h',
194
		60                      => 'm',
195
		1                       => 's'
196
	];
197
	foreach ($units as $time_frame => $key) {
198
		if ($time >= $time_frame) {
199
			$time_full = floor($time / $time_frame);
200
			$time -= $time_full * $time_frame;
201
			$res[] = $L->time($time_full, $key);
202
		}
203
	}
204
	return implode(' ', $res);
205
}
206
207
/**
208
 * Formatting of data size in bytes to human-readable form
209
 *
210
 * @param int      $size
211
 * @param bool|int $round
212
 *
213
 * @return float|string
214
 */
215
function format_filesize ($size, $round = false) {
216
	if (!is_numeric($size)) {
217
		return $size;
218
	}
219
	$L    = Language::prefix('system_filesize_');
220
	$unit = '';
221
	if ($size >= 1099511627776) {
222
		$size /= 1099511627776;
223
		$unit = " $L->TiB";
224
	} elseif ($size >= 1073741824) {
225
		$size /= 1073741824;
226
		$unit = " $L->GiB";
227
	} elseif ($size >= 1048576) {
228
		$size /= 1048576;
229
		$unit = " $L->MiB";
230
	} elseif ($size >= 1024) {
231
		$size /= 1024;
232
		$unit = " $L->KiB";
233
	} else {
234
		$size = "$size $L->Bytes";
235
	}
236
	return $round ? round($size, $round).$unit : $size.$unit;
237
}
238
239
/**
240
 * Get list of timezones
241
 *
242
 * @return array
243
 */
244
function get_timezones_list () {
245 8
	$timezones = [];
246 8
	foreach (timezone_identifiers_list() as $timezone) {
247 8
		$offset          = (new DateTimeZone($timezone))->getOffset(new DateTime);
248 8
		$key             = (39600 + $offset).$timezone;
249 8
		$sign            = ($offset < 0 ? '-' : '+');
250 8
		$hours           = str_pad(floor(abs($offset / 3600)), 2, 0, STR_PAD_LEFT);
251 8
		$minutes         = str_pad(abs(($offset % 3600) / 60), 2, 0, STR_PAD_LEFT);
252 8
		$timezones[$key] = [
253 8
			'key'   => str_replace('_', ' ', $timezone)." ($sign$hours:$minutes)",
254 8
			'value' => $timezone
255
		];
256
	}
257 8
	ksort($timezones, SORT_NATURAL);
258 8
	return array_column($timezones, 'value', 'key');
259
}
260
261
/**
262
 * String representation of HTTP status code
263
 *
264
 * @param int $code
265
 *
266
 * @return null|string
267
 */
268
function status_code_string ($code) {
269
	$code_to_string = [
270 14
		201 => '201 Created',
271
		202 => '202 Accepted',
272
		301 => '301 Moved Permanently',
273
		302 => '302 Found',
274
		303 => '303 See Other',
275
		307 => '307 Temporary Redirect',
276
		400 => '400 Bad Request',
277
		403 => '403 Forbidden',
278
		404 => '404 Not Found',
279
		405 => '405 Method Not Allowed',
280
		409 => '409 Conflict',
281
		429 => '429 Too Many Requests',
282
		500 => '500 Internal Server Error',
283
		501 => '501 Not Implemented',
284
		503 => '503 Service Unavailable'
285
	];
286 14
	return @$code_to_string[$code];
287
}
288
289
/**
290
 * Pages navigation based on links
291
 *
292
 * @param int             $page       Current page
293
 * @param int             $total      Total pages number
294
 * @param callable|string $url        if string - it will be formatted with sprintf with one parameter - page number<br>
295
 *                                    if callable - one parameter will be given, callable should return url string
296
 * @param bool            $head_links If <b>true</b> - links with rel="prev" and rel="next" will be added
297
 *
298
 * @return bool|string <b>false</b> if single page, otherwise string, set of navigation links
299
 */
300
function pages ($page, $total, $url, $head_links = false) {
301 2
	if ($total == 1) {
302 2
		return false;
303
	}
304 2
	$Page             = Page::instance();
305 2
	$original_url     = $url;
306 2
	$base_url         = Config::instance()->base_url();
307
	$url              = function ($page) use ($original_url, $base_url) {
308 2
		$href = is_callable($original_url) ? $original_url($page) : sprintf($original_url, $page);
309 2
		if (is_string($href) && strpos($href, 'http') !== 0) {
310 2
			$href = ltrim($href, '/');
311 2
			$href = "$base_url/$href";
312
		}
313 2
		return $href;
314 2
	};
315 2
	$output           = [];
316
	$render_page_item = function ($i) use ($Page, $page, $url, $head_links, &$output) {
317 2
		$href = $url($i);
318 2
		if ($head_links) {
319
			switch ($i) {
320 2
				case $page - 1:
321 2
					$Page->link(['href' => $href, 'rel' => 'prev']);
322 2
					break;
323 2
				case $page + 1:
324 2
					$Page->link(['href' => $href, 'rel' => 'next']);
325 2
					break;
326 2
				case $page:
327 2
					$Page->canonical_url($href);
328 2
					break;
329
			}
330
		}
331 2
		$output[] = [
332 2
			$i,
333
			[
334 2
				'href'    => $i == $page ? false : $href,
335 2
				'is'      => 'cs-link-button',
336 2
				'primary' => $i == $page
337
			]
338
		];
339 2
	};
340 2
	if ($total <= 11) {
341 2
		array_map($render_page_item, range(1, $total));
342
	} else {
343 2
		if ($page <= 6) {
344 2
			array_map($render_page_item, range(1, 7));
345 2
			$output[] = [
346
				'...',
347
				[
348
					'disabled' => true
349
				]
350
			];
351 2
			array_map($render_page_item, range($total - 2, $total));
352 2
		} elseif ($page >= $total - 5) {
353 2
			array_map($render_page_item, range(1, 3));
354 2
			$output[] = [
355
				'...',
356
				[
357
					'disabled' => true
358
				]
359
			];
360 2
			array_map($render_page_item, range($total - 6, $total));
361
		} else {
362 2
			array_map($render_page_item, range(1, 2));
363 2
			$output[] = [
364
				'...',
365
				[
366
					'disabled' => true
367
				]
368
			];
369 2
			array_map($render_page_item, range($page - 2, $page + 2));
370 2
			$output[] = [
371
				'...',
372
				[
373
					'disabled' => true
374
				]
375
			];
376 2
			array_map($render_page_item, range($total - 1, $total));
377
		}
378
	}
379 2
	return h::{'a[is=cs-link-button]'}($output);
380
}
381
382
/**
383
 * Pages navigation based on buttons (for search forms, etc.)
384
 *
385
 * @param int                  $page  Current page
386
 * @param int                  $total Total pages number
387
 * @param bool|callable|string $url   Adds <i>formaction</i> parameter to every button<br>
388
 *                                    if <b>false</b> - only form parameter <i>page</i> will we added<br>
389
 *                                    if string - it will be formatted with sprintf with one parameter - page number<br>
390
 *                                    if callable - one parameter will be given, callable should return url string
391
 *
392
 * @return false|string                        <b>false</b> if single page, otherwise string, set of navigation buttons
393
 */
394
function pages_buttons ($page, $total, $url = false) {
395 2
	if ($total == 1) {
396 2
		return false;
397
	}
398 2
	if (!is_callable($url)) {
399 2
		$original_url = $url;
400
		$url          = function ($page) use ($original_url) {
401 2
			return sprintf($original_url, $page);
402 2
		};
403
	}
404 2
	$output           = [];
405
	$render_page_item = function ($i) use ($page, $url, &$output) {
406 2
		$output[] = [
407 2
			$i,
408
			[
409 2
				'formaction' => $i == $page || $url === false ? false : $url($i),
410 2
				'value'      => $i == $page ? false : $i,
411 2
				'type'       => $i == $page ? 'button' : 'submit',
412 2
				'primary'    => $i == $page
413
			]
414
		];
415 2
	};
416 2
	if ($total <= 11) {
417 2
		array_map($render_page_item, range(1, $total));
418
	} else {
419 2
		if ($page <= 6) {
420 2
			array_map($render_page_item, range(1, 7));
421 2
			$output[] = [
422
				'...',
423
				[
424
					'type' => 'button',
425
					'disabled'
426
				]
427
			];
428 2
			array_map($render_page_item, range($total - 2, $total));
429 2
		} elseif ($page >= $total - 5) {
430 2
			array_map($render_page_item, range(1, 3));
431 2
			$output[] = [
432
				'...',
433
				[
434
					'type' => 'button',
435
					'disabled'
436
				]
437
			];
438 2
			array_map($render_page_item, range($total - 6, $total));
439
		} else {
440 2
			array_map($render_page_item, range(1, 2));
441 2
			$output[] = [
442
				'...',
443
				[
444
					'type' => 'button',
445
					'disabled'
446
				]
447
			];
448 2
			array_map($render_page_item, range($page - 2, $page + 2));
449 2
			$output[] = [
450
				'...',
451
				[
452
					'type' => 'button',
453
					'disabled'
454
				]
455
			];
456 2
			array_map($render_page_item, range($total - 1, $total));
457
		}
458
	}
459 2
	return h::{'button[is=cs-button][name=page]'}($output);
460
}
461
462
/**
463
 * Checks whether specified functionality available or not
464
 *
465
 * @param string|string[] $functionality One functionality or array of them
466
 *
467
 * @return bool `true` if all functionality available, `false` otherwise
468
 */
469
function functionality ($functionality) {
470 2
	if (is_array($functionality)) {
471 2
		$result = true;
472 2
		foreach ($functionality as $f) {
473 2
			$result = $result && functionality($f);
474
		}
475 2
		return $result;
476
	}
477 2
	$all = Cache::instance()->get(
478 2
		'functionality',
479
		function () {
480 2
			$functionality = [];
481 2
			$Config        = Config::instance();
482 2
			foreach (array_keys($Config->components['modules']) as $module) {
483 2
				if (!$Config->module($module)->enabled() || !file_exists(MODULES."/$module/meta.json")) {
484 2
					continue;
485
				}
486 2
				$functionality[] = [$module];
487 2
				$meta            = file_get_json(MODULES."/$module/meta.json");
488 2
				if (isset($meta['provide'])) {
489 2
					$functionality[] = (array)$meta['provide'];
490
				}
491
			}
492 2
			return array_merge(...$functionality);
493 2
		}
494
	);
495 2
	return in_array($functionality, $all);
496
}
497
498
/**
499
 * XSS Attack Protection. Returns secure string using several types of filters
500
 *
501
 * @param string|string[] $in     HTML code
502
 * @param bool|string     $html   <b>text</b> - text at output (default)<br>
503
 *                                <b>true</b> - processed HTML at output<br>
504
 *                                <b>false</b> - HTML tags will be deleted
505
 * @param bool            $iframe Whether to allow iframes without inner content (for example, video from youtube)<br>
506
 *                                Works only if <i>$html === true</i>
507
 *
508
 * @return string|string[]
509
 */
510
function xap ($in, $html = 'text', $iframe = false) {
511 52
	static $purifier, $purifier_iframe, $purifier_no_tags;
512 52
	if (is_array($in)) {
513 34
		foreach ($in as &$item) {
514 34
			$item = xap($item, $html, $iframe);
515
		}
516 34
		return $in;
517
	}
518
	/**
519
	 * Text mode
520
	 */
521 52
	if ($html === 'text') {
522 48
		return htmlspecialchars($in, ENT_NOQUOTES | ENT_HTML5 | ENT_DISALLOWED | ENT_SUBSTITUTE | ENT_HTML5);
523
	}
524 16
	if (!isset($purifier)) {
525
		$config_array = [
526 16
			'HTML.Doctype'         => 'HTML 4.01 Transitional',
527
			'Attr.EnableID'        => true,
528
			'Attr.ID.HTML5'        => true,
529
			'Attr.IDPrefix'        => 'content-',
530
			'CSS.MaxImgLength'     => null,
531
			'Cache.DefinitionImpl' => null,
532
			'Output.Newline'       => "\n",
533
			'URI.Munge'            => 'redirect/%s'
534
		];
535
536 16
		$config = HTMLPurifier_Config::createDefault();
537 16
		$config->loadArray($config_array);
538 16
		$purifier = new HTMLPurifier($config_array);
539
540 16
		$config_no_tags = HTMLPurifier_Config::createDefault();
541 16
		$config_no_tags->loadArray($config_array);
542 16
		$config_no_tags->set('HTML.AllowedElements', []);
543 16
		$purifier_no_tags = new HTMLPurifier($config_no_tags);
544
545 16
		$config_iframe = HTMLPurifier_Config::createDefault();
546 16
		$config_iframe->loadArray($config_array);
547 16
		$config_iframe->set('Filter.Custom', [new HTMLPurifier_Filter_iframe_sandbox]);
548 16
		$purifier_iframe = new HTMLPurifier($config_iframe);
549
	}
550 16
	if ($html === false) {
551 2
		return $purifier_no_tags->purify($in);
552
	}
553 16
	if ($iframe) {
554 2
		return $purifier_iframe->purify($in);
555
	} else {
556 16
		return $purifier->purify($in);
557
	}
558
}
559
560
/**
561
 * @param string $class
562
 *
563
 * @return bool
564
 */
565
function __htmlpurifier_autoload ($class) {
566 192
	$class = ltrim($class, '\\');
567 192
	if (strpos($class, 'HTMLPurifier_') === 0) {
568 16
		spl_autoload_unregister('__htmlpurifier_autoload');
569 16
		Phar::loadPhar(__DIR__.'/thirdparty/htmlpurifier.tar.gz', 'htmlpurifier.phar');
570 16
		require_once 'phar://htmlpurifier.phar/HTMLPurifier.standalone.php';
571 16
		return true;
572
	}
573 190
}
574
575
spl_autoload_register('__htmlpurifier_autoload', true, true);
576