1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Utilities - Functions and Classes common to the whole phpMyFAQ architecture. |
5
|
|
|
* |
6
|
|
|
* PHP Version 5.5 |
7
|
|
|
* |
8
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public License, |
9
|
|
|
* v. 2.0. If a copy of the MPL was not distributed with this file, You can |
10
|
|
|
* obtain one at http://mozilla.org/MPL/2.0/. |
11
|
|
|
* |
12
|
|
|
* @category phpMyFAQ |
13
|
|
|
* |
14
|
|
|
* @author Thorsten Rinne <[email protected]> |
15
|
|
|
* @author Matteo Scaramuccia <[email protected]> |
16
|
|
|
* @copyright 2005-2018 phpMyFAQ Team |
17
|
|
|
* @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 |
18
|
|
|
* |
19
|
|
|
* @link https://www.phpmyfaq.de |
20
|
|
|
* @since 2005-11-01 |
21
|
|
|
*/ |
22
|
|
|
if (!defined('IS_VALID_PHPMYFAQ')) { |
23
|
|
|
exit(); |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
/**#@+ |
27
|
|
|
* HTTP GET Parameters PMF accepted keys definitions |
28
|
|
|
*/ |
29
|
|
|
define('HTTP_PARAMS_GET_CATID', 'catid'); |
30
|
|
|
define('HTTP_PARAMS_GET_CURRENTDAY', 'today'); |
31
|
|
|
define('HTTP_PARAMS_GET_DISPOSITION', 'dispos'); |
32
|
|
|
define('HTTP_PARAMS_GET_GIVENDATE', 'givendate'); |
33
|
|
|
define('HTTP_PARAMS_GET_LANG', 'lang'); |
34
|
|
|
define('HTTP_PARAMS_GET_DOWNWARDS', 'downwards'); |
35
|
|
|
define('HTTP_PARAMS_GET_TYPE', 'type'); |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* PMF_Utils class. |
39
|
|
|
* |
40
|
|
|
* This class has only static methods |
41
|
|
|
* |
42
|
|
|
* @category phpMyFAQ |
43
|
|
|
* |
44
|
|
|
* @author Thorsten Rinne <[email protected]> |
45
|
|
|
* @author Matteo Scaramuccia <[email protected]> |
46
|
|
|
* @copyright 2005-2018 phpMyFAQ Team |
47
|
|
|
* @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 |
48
|
|
|
* |
49
|
|
|
* @link https://www.phpmyfaq.de |
50
|
|
|
* @since 2005-11-01 |
51
|
|
|
*/ |
52
|
|
|
class PMF_Utils |
53
|
|
|
{ |
54
|
|
|
/** |
55
|
|
|
* Get the content at the given URL using an HTTP GET call. |
56
|
|
|
* |
57
|
|
|
* @param string $url URL of the content |
58
|
|
|
* |
59
|
|
|
* @return string |
60
|
|
|
*/ |
61
|
|
|
public static function getHTTPContent($url) |
62
|
|
|
{ |
63
|
|
|
// Sanity check |
64
|
|
|
if (empty($url)) { |
65
|
|
|
return false; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
// Create the HTTP options for the HTTP stream context, see below |
69
|
|
|
// Set phpMyFAQ agent related data |
70
|
|
|
$agent = 'phpMyFAQ/'.PMF_System::getVersion().' on PHP/'.PHP_VERSION; |
71
|
|
|
$opts = array( |
72
|
|
|
'header' => 'User-Agent: '.$agent."\r\n", |
73
|
|
|
'method' => 'GET', |
74
|
|
|
); |
75
|
|
|
// HTTP 1.1 Virtual Host |
76
|
|
|
$urlParts = @parse_url($url); |
77
|
|
|
if (isset($urlParts['host'])) { |
78
|
|
|
$opts['header'] = $opts['header'].'Host: '.$urlParts['host']."\r\n"; |
79
|
|
|
} |
80
|
|
|
// Socket timeout |
81
|
|
|
$opts['timeout'] = 5; |
82
|
|
|
|
83
|
|
|
// Create the HTTP stream context |
84
|
|
|
$ctx = stream_context_create( |
85
|
|
|
array( |
86
|
|
|
'http' => $opts, |
87
|
|
|
) |
88
|
|
|
); |
89
|
|
|
|
90
|
|
|
return file_get_contents($url, null, $ctx); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Returns date from out of time. |
95
|
|
|
* |
96
|
|
|
* @return string |
97
|
|
|
*/ |
98
|
|
|
public static function getNeverExpireDate() |
99
|
|
|
{ |
100
|
|
|
// Unix: 13 Dec 1901 20:45:54 -> 19 Jan 2038 03:14:07, signed 32 bit |
|
|
|
|
101
|
|
|
// Windows: 1 Jan 1970 -> 19 Jan 2038. |
102
|
|
|
// So we will use: 1 Jan 2038 -> 2038-01-01, 00:00:01 |
|
|
|
|
103
|
|
|
return self::getPMFDate(mktime(0, 0, 1, 1, 1, 2038)); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Returns a phpMyFAQ date. |
108
|
|
|
* |
109
|
|
|
* @param int $unixTime Unix timestamp |
110
|
|
|
* |
111
|
|
|
* @return string |
112
|
|
|
*/ |
113
|
|
|
public static function getPMFDate($unixTime = null) |
114
|
|
|
{ |
115
|
|
|
if (!isset($unixTime)) { |
116
|
|
|
// localtime |
117
|
|
|
$unixTime = $_SERVER['REQUEST_TIME']; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
return date('YmdHis', $unixTime); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Check if a given string could be a language. |
125
|
|
|
* |
126
|
|
|
* @param string $lang Language |
127
|
|
|
* |
128
|
|
|
* @return bool |
129
|
|
|
*/ |
130
|
|
|
public static function isLanguage($lang) |
131
|
|
|
{ |
132
|
|
|
return preg_match('/^[a-zA-Z\-]+$/', $lang); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Checks if a date is a phpMyFAQ valid date. |
137
|
|
|
* |
138
|
|
|
* @param int $date Date |
139
|
|
|
* |
140
|
|
|
* @return int |
141
|
|
|
*/ |
142
|
|
|
public static function isLikeOnPMFDate($date) |
143
|
|
|
{ |
144
|
|
|
// Test if the passed string is in the format: %YYYYMMDDhhmmss% |
145
|
|
|
$testdate = $date; |
146
|
|
|
// Suppress first occurence of '%' |
147
|
|
|
if (substr($testdate, 0, 1) == '%') { |
148
|
|
|
$testdate = substr($testdate, 1); |
149
|
|
|
} |
150
|
|
|
// Suppress last occurence of '%' |
151
|
|
|
if (substr($testdate, -1, 1) == '%') { |
152
|
|
|
$testdate = substr($testdate, 0, strlen($testdate) - 1); |
153
|
|
|
} |
154
|
|
|
// PMF date consists of numbers only: YYYYMMDDhhmmss |
155
|
|
|
return is_int($testdate); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Shortens a string for a given number of words. |
160
|
|
|
* |
161
|
|
|
* @param string $str String |
162
|
|
|
* @param int $char Characters |
163
|
|
|
* |
164
|
|
|
* @return string |
165
|
|
|
* |
166
|
|
|
* @todo This function doesn't work with Chinese, Japanese and Korean |
167
|
|
|
* because they don't have spaces as word delimiters |
168
|
|
|
*/ |
169
|
|
|
public static function makeShorterText($str, $char) |
170
|
|
|
{ |
171
|
|
|
|
172
|
|
|
$str = PMF_String::preg_replace('/\s+/u', ' ', $str); |
173
|
|
|
$arrStr = explode(' ', $str); |
174
|
|
|
$shortStr = ''; |
175
|
|
|
$num = count($arrStr); |
176
|
|
|
|
177
|
|
|
if ($num > $char) { |
178
|
|
|
for ($j = 0; $j <= $char; ++$j) { |
179
|
|
|
$shortStr .= $arrStr[$j].' '; |
180
|
|
|
} |
181
|
|
|
$shortStr .= '...'; |
182
|
|
|
} else { |
183
|
|
|
$shortStr = $str; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
return $shortStr; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Resolves the PMF markers like e.g. %sitename%. |
191
|
|
|
* |
192
|
|
|
* @param string $text Text contains PMF markers |
193
|
|
|
* @param PMF_Configuration $config |
194
|
|
|
* |
195
|
|
|
* @return string |
196
|
|
|
*/ |
197
|
|
|
public static function resolveMarkers($text, PMF_Configuration $config) |
198
|
|
|
{ |
199
|
|
|
// Available markers: key and resolving value |
200
|
|
|
$markers = array( |
201
|
|
|
'%sitename%' => $config->get('main.titleFAQ'), |
202
|
|
|
); |
203
|
|
|
|
204
|
|
|
// Resolve any known pattern |
205
|
|
|
return str_replace( |
206
|
|
|
array_keys($markers), |
207
|
|
|
array_values($markers), |
208
|
|
|
$text |
209
|
|
|
); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Shuffles an associative array without losing key associations. |
214
|
|
|
* |
215
|
|
|
* @param array $data Array of data |
216
|
|
|
* |
217
|
|
|
* @return array $shuffled_data Array of shuffled data |
218
|
|
|
*/ |
219
|
|
|
public static function shuffleData($data) |
220
|
|
|
{ |
221
|
|
|
$shuffled_data = []; |
222
|
|
|
|
223
|
|
|
if (is_array($data)) { |
224
|
|
|
if (count($data) > 1) { |
225
|
|
|
$randomized_keys = array_rand($data, count($data)); |
226
|
|
|
|
227
|
|
|
foreach ($randomized_keys as $current_key) { |
228
|
|
|
$shuffled_data[$current_key] = $data[$current_key]; |
229
|
|
|
} |
230
|
|
|
} else { |
231
|
|
|
$shuffled_data = $data; |
232
|
|
|
} |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
return $shuffled_data; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* This method chops a string. |
240
|
|
|
* |
241
|
|
|
* @param string $string String to chop |
242
|
|
|
* @param int $words Number of words |
243
|
|
|
* |
244
|
|
|
* @return string |
245
|
|
|
*/ |
246
|
|
|
public static function chopString($string, $words) |
247
|
|
|
{ |
248
|
|
|
$str = ''; |
249
|
|
|
$pieces = explode(' ', $string); |
250
|
|
|
$num = count($pieces); |
251
|
|
|
if ($words > $num) { |
252
|
|
|
$words = $num; |
253
|
|
|
} |
254
|
|
|
for ($i = 0; $i < $words; ++$i) { |
255
|
|
|
$str .= $pieces[$i].' '; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
return $str; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Adds a highlighted word to a string. |
263
|
|
|
* |
264
|
|
|
* @param string $string String |
265
|
|
|
* @param string $highlight Given word for highlighting |
266
|
|
|
* |
267
|
|
|
* @return string |
268
|
|
|
*/ |
269
|
|
|
public static function setHighlightedString($string, $highlight) |
270
|
|
|
{ |
271
|
|
|
$attributes = [ |
272
|
|
|
'href', 'src', 'title', 'alt', 'class', 'style', 'id', 'name', |
273
|
|
|
'face', 'size', 'dir', 'rel', 'rev', 'role', |
274
|
|
|
'onmouseenter', 'onmouseleave', 'onafterprint', 'onbeforeprint', |
275
|
|
|
'onbeforeunload', 'onhashchange', 'onmessage', 'onoffline', 'ononline', |
276
|
|
|
'onpopstate', 'onpagehide', 'onpageshow', 'onresize', 'onunload', |
277
|
|
|
'ondevicemotion', 'ondeviceorientation', 'onabort', 'onblur', |
278
|
|
|
'oncanplay', 'oncanplaythrough', 'onchange', 'onclick', 'oncontextmenu', |
279
|
|
|
'ondblclick', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', |
280
|
|
|
'ondragover', 'ondragstart', 'ondrop', 'ondurationchange', 'onemptied', |
281
|
|
|
'onended', 'onerror', 'onfocus', 'oninput', 'oninvalid', 'onkeydown', |
282
|
|
|
'onkeypress', 'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata', |
283
|
|
|
'onloadstart', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', |
284
|
|
|
'onmouseup', 'onmozfullscreenchange', 'onmozfullscreenerror', 'onpause', |
285
|
|
|
'onplay', 'onplaying', 'onprogress', 'onratechange', 'onreset', |
286
|
|
|
'onscroll', 'onseeked', 'onseeking', 'onselect', 'onshow', 'onstalled', |
287
|
|
|
'onsubmit', 'onsuspend', 'ontimeupdate', 'onvolumechange', 'onwaiting', |
288
|
|
|
'oncopy', 'oncut', 'onpaste', 'onbeforescriptexecute', 'onafterscriptexecute' |
289
|
|
|
]; |
290
|
|
|
|
291
|
|
|
return PMF_String::preg_replace_callback( |
292
|
|
|
'/('.$highlight.'="[^"]*")|'. |
293
|
|
|
'(('.implode('|', $attributes).')="[^"]*'.$highlight.'[^"]*")|'. |
294
|
|
|
'('.$highlight.')/mis', |
295
|
|
|
['PMF_Utils', 'highlightNoLinks'], |
296
|
|
|
$string |
297
|
|
|
); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Callback function for filtering HTML from URLs and images. |
302
|
|
|
* |
303
|
|
|
* @param array $matches Array of matches from regex pattern |
304
|
|
|
* |
305
|
|
|
* @return string |
306
|
|
|
*/ |
307
|
|
|
public static function highlightNoLinks(Array $matches) |
308
|
|
|
{ |
309
|
|
|
$prefix = isset($matches[3]) ? $matches[3] : ''; |
310
|
|
|
$item = isset($matches[4]) ? $matches[4] : ''; |
311
|
|
|
$postfix = isset($matches[5]) ? $matches[5] : ''; |
312
|
|
|
|
313
|
|
|
if (!empty($item) && !self::isForbiddenElement($item)) { |
314
|
|
|
return sprintf( |
315
|
|
|
'<mark class="pmf-highlighted-string">%s</mark>', |
316
|
|
|
$prefix.$item.$postfix |
317
|
|
|
); |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
// Fallback: the original matched string |
321
|
|
|
return $matches[0]; |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Tries to detect if a string could be a HTML element |
326
|
|
|
* |
327
|
|
|
* @param $string |
328
|
|
|
* |
329
|
|
|
* @return bool |
330
|
|
|
*/ |
331
|
|
|
public static function isForbiddenElement($string) |
332
|
|
|
{ |
333
|
|
|
$forbiddenElements = [ |
334
|
|
|
'img', 'picture', 'mark' |
335
|
|
|
]; |
336
|
|
|
|
337
|
|
|
foreach ($forbiddenElements as $element) { |
338
|
|
|
if (strpos($element, $string)) { |
339
|
|
|
return true; |
340
|
|
|
} |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
return false; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* debug_backtrace() wrapper function. |
348
|
|
|
* |
349
|
|
|
* @param $string |
350
|
|
|
* |
351
|
|
|
* @return string |
352
|
|
|
*/ |
353
|
|
|
public static function debug($string) |
354
|
|
|
{ |
355
|
|
|
// sometimes Zend Optimizer causes segfaults with debug_backtrace() |
356
|
|
|
if (extension_loaded('Zend Optimizer')) { |
357
|
|
|
$ret = '<pre>'.$string."</pre><br>\n"; |
358
|
|
|
} else { |
359
|
|
|
$debug = debug_backtrace(); |
360
|
|
|
$ret = ''; |
361
|
|
|
if (isset($debug[2]['class'])) { |
362
|
|
|
$ret = $debug[2]['file'].':<br>'; |
363
|
|
|
$ret .= $debug[2]['class'].$debug[1]['type']; |
364
|
|
|
$ret .= $debug[2]['function'].'() in line '.$debug[2]['line']; |
365
|
|
|
$ret .= ': <pre>'.$string."</pre><br>\n"; |
366
|
|
|
} |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
return $ret; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* Parses a given string and convert all the URLs into links. |
374
|
|
|
* |
375
|
|
|
* @param string $string |
376
|
|
|
* |
377
|
|
|
* @return string |
378
|
|
|
*/ |
379
|
|
|
public static function parseUrl($string) |
380
|
|
|
{ |
381
|
|
|
$protocols = array('http://', 'https://', 'ftp://'); |
382
|
|
|
|
383
|
|
|
$string = str_replace($protocols, '', $string); |
384
|
|
|
$string = str_replace('www.', 'http://www.', $string); |
385
|
|
|
$string = preg_replace('|http://([a-zA-Z0-9-\./]+)|', '<a href="http://$1">$1</a>', $string); |
386
|
|
|
$string = preg_replace( |
387
|
|
|
'/(([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6})/', |
388
|
|
|
'<a href="mailto:$1">$1</a>', |
389
|
|
|
$string |
390
|
|
|
); |
391
|
|
|
|
392
|
|
|
return $string; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* Moves given key of an array to the top |
397
|
|
|
* |
398
|
|
|
* @param array $array |
399
|
|
|
* @param string $key |
400
|
|
|
*/ |
401
|
|
|
public static function moveToTop(&$array, $key) |
402
|
|
|
{ |
403
|
|
|
$temp = [$key => $array[$key]]; |
404
|
|
|
unset($array[$key]); |
405
|
|
|
$array = $temp + $array; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Creates a seed with microseconds. |
410
|
|
|
* @return float|int |
411
|
|
|
*/ |
412
|
|
|
private static function makeSeed() |
413
|
|
|
{ |
414
|
|
|
list($usec, $sec) = explode(' ', microtime()); |
415
|
|
|
return $sec + $usec * 1000000; |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* Returns a random number. |
420
|
|
|
* @param $min |
421
|
|
|
* @param $max |
422
|
|
|
* @return int |
423
|
|
|
*/ |
424
|
|
|
public static function createRandomNumber($min, $max) |
425
|
|
|
{ |
426
|
|
|
mt_srand(PMF_Utils::makeSeed()); |
427
|
|
|
return mt_rand($min, $max); |
428
|
|
|
} |
429
|
|
|
} |
430
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.