1<?php
2
3/**
4 * Utilities - Functions and Classes common to the whole phpMyFAQ architecture.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * @package phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @author    Matteo Scaramuccia <matteo@phpmyfaq.de>
13 * @copyright 2005-2020 phpMyFAQ Team
14 * @license   http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
15 * @link  https://www.phpmyfaq.de
16 * @since 2005-11-01
17 */
18
19namespace phpMyFAQ;
20
21define('HTTP_PARAMS_GET_CATID', 'catid');
22define('HTTP_PARAMS_GET_CURRENTDAY', 'today');
23define('HTTP_PARAMS_GET_DISPOSITION', 'dispos');
24define('HTTP_PARAMS_GET_GIVENDATE', 'givendate');
25define('HTTP_PARAMS_GET_LANG', 'lang');
26define('HTTP_PARAMS_GET_DOWNWARDS', 'downwards');
27define('HTTP_PARAMS_GET_TYPE', 'type');
28
29/**
30 * Class Utils
31 *
32 * @package phpMyFAQ
33 */
34class Utils
35{
36    /**
37     * Check if a given string could be a language.
38     *
39     * @param string $lang Language
40     *
41     * @return integer
42     */
43    public static function isLanguage($lang)
44    {
45        return preg_match('/^[a-zA-Z\-]+$/', $lang);
46    }
47
48    /**
49     * Checks if a date is a phpMyFAQ valid date.
50     *
51     * @param int $date Date
52     *
53     * @return boolean
54     */
55    public static function isLikeOnPMFDate($date)
56    {
57        // Test if the passed string is in the format: %YYYYMMDDhhmmss%
58        $testdate = $date;
59        // Suppress first occurrences of '%'
60        if (substr($testdate, 0, 1) == '%') {
61            $testdate = substr($testdate, 1);
62        }
63        // Suppress last occurrences of '%'
64        if (substr($testdate, -1, 1) == '%') {
65            $testdate = substr($testdate, 0, strlen($testdate) - 1);
66        }
67        // PMF date consists of numbers only: YYYYMMDDhhmmss
68        return is_int($testdate);
69    }
70
71    /**
72     * Shortens a string for a given number of words.
73     *
74     * @param string $str  String
75     * @param int    $char Characters
76     *
77     * @return string
78     *
79     * @todo This function doesn't work with Chinese, Japanese and Korean
80     *       because they don't have spaces as word delimiters
81     */
82    public static function makeShorterText($str, $char)
83    {
84
85        $str = Strings::preg_replace('/\s+/u', ' ', $str);
86        $arrStr = explode(' ', $str);
87        $shortStr = '';
88        $num = count($arrStr);
89
90        if ($num > $char) {
91            for ($j = 0; $j < $char; ++$j) {
92                $shortStr .= $arrStr[$j] . ' ';
93            }
94            $shortStr .= '...';
95        } else {
96            $shortStr = $str;
97        }
98
99        return $shortStr;
100    }
101
102    /**
103     * Resolves the PMF markers like e.g. %sitename%.
104     *
105     * @param string        $text   Text contains PMF markers
106     * @param Configuration $config
107     *
108     * @return string
109     */
110    public static function resolveMarkers($text, Configuration $config)
111    {
112        // Available markers: key and resolving value
113        $markers = [
114            '%sitename%' => $config->get('main.titleFAQ'),
115        ];
116
117        // Resolve any known pattern
118        return str_replace(
119            array_keys($markers),
120            array_values($markers),
121            $text
122        );
123    }
124
125    /**
126     * This method chops a string.
127     *
128     * @param string $string String to chop
129     * @param int    $words  Number of words
130     *
131     * @return string
132     */
133    public static function chopString($string, $words)
134    {
135        $str = '';
136        $pieces = explode(' ', $string);
137        $num = count($pieces);
138        if ($words > $num) {
139            $words = $num;
140        }
141        for ($i = 0; $i < $words; ++$i) {
142            $str .= $pieces[$i] . ' ';
143        }
144
145        return $str;
146    }
147
148    /**
149     * Adds a highlighted word to a string.
150     *
151     * @param string $string    String
152     * @param string $highlight Given word for highlighting
153     *
154     * @return string
155     */
156    public static function setHighlightedString($string, $highlight)
157    {
158        $attributes = [
159            'href', 'src', 'title', 'alt', 'class', 'style', 'id', 'name',
160            'face', 'size', 'dir', 'rel', 'rev', 'role',
161            'onmouseenter', 'onmouseleave', 'onafterprint', 'onbeforeprint',
162            'onbeforeunload', 'onhashchange', 'onmessage', 'onoffline', 'ononline',
163            'onpopstate', 'onpagehide', 'onpageshow', 'onresize', 'onunload',
164            'ondevicemotion', 'ondeviceorientation', 'onabort', 'onblur',
165            'oncanplay', 'oncanplaythrough', 'onchange', 'onclick', 'oncontextmenu',
166            'ondblclick', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave',
167            'ondragover', 'ondragstart', 'ondrop', 'ondurationchange', 'onemptied',
168            'onended', 'onerror', 'onfocus', 'oninput', 'oninvalid', 'onkeydown',
169            'onkeypress', 'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata',
170            'onloadstart', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover',
171            'onmouseup', 'onmozfullscreenchange', 'onmozfullscreenerror', 'onpause',
172            'onplay', 'onplaying', 'onprogress', 'onratechange', 'onreset',
173            'onscroll', 'onseeked', 'onseeking', 'onselect', 'onshow', 'onstalled',
174            'onsubmit', 'onsuspend', 'ontimeupdate', 'onvolumechange', 'onwaiting',
175            'oncopy', 'oncut', 'onpaste', 'onbeforescriptexecute', 'onafterscriptexecute'
176        ];
177
178        return Strings::preg_replace_callback(
179            '/(' . $highlight . '="[^"]*")|' .
180            '((' . implode('|', $attributes) . ')="[^"]*' . $highlight . '[^"]*")|' .
181            '(' . $highlight . ')/mis',
182            ['phpMyFAQ\Utils', 'highlightNoLinks'],
183            $string
184        );
185    }
186
187    /**
188     * Callback function for filtering HTML from URLs and images.
189     *
190     * @param array $matches Array of matches from regex pattern
191     *
192     * @return string
193     */
194    public static function highlightNoLinks(array $matches)
195    {
196        $prefix = isset($matches[3]) ? $matches[3] : '';
197        $item = isset($matches[4]) ? $matches[4] : '';
198        $postfix = isset($matches[5]) ? $matches[5] : '';
199
200        if (!empty($item) && !self::isForbiddenElement($item)) {
201            return sprintf(
202                '<mark class="pmf-highlighted-string">%s</mark>',
203                $prefix . $item . $postfix
204            );
205        }
206
207        // Fallback: the original matched string
208        return $matches[0];
209    }
210
211    /**
212     * Tries to detect if a string could be a HTML element
213     *
214     * @param $string
215     *
216     * @return bool
217     */
218    public static function isForbiddenElement($string)
219    {
220        $forbiddenElements = [
221            'img', 'picture', 'mark'
222        ];
223
224        foreach ($forbiddenElements as $element) {
225            if (strpos($element, $string)) {
226                return true;
227            }
228        }
229
230        return false;
231    }
232
233    /**
234     * debug_backtrace() wrapper function.
235     *
236     * @param string $string
237     *
238     * @return string
239     */
240    public static function debug($string)
241    {
242        // sometimes Zend Optimizer causes segfaults with debug_backtrace()
243        if (extension_loaded('Zend Optimizer')) {
244            $ret = '<code>' . $string . "</code><br>\n";
245        } else {
246            $debug = debug_backtrace();
247            $ret = '';
248            if (isset($debug[2]['class'])) {
249                $ret = $debug[2]['file'] . ':<br>';
250                $ret .= $debug[2]['class'] . $debug[1]['type'];
251                $ret .= $debug[2]['function'] . '() in line ' . $debug[2]['line'];
252                $ret .= ': <code>' . $string . "</code><br>\n";
253            }
254        }
255
256        return $ret;
257    }
258
259    /**
260     * Parses a given string and convert all the URLs into links.
261     *
262     * @param string $string
263     *
264     * @return string
265     */
266    public static function parseUrl($string)
267    {
268        $protocols = array('http://', 'https://', 'ftp://');
269
270        $string = str_replace($protocols, '', $string);
271        $string = str_replace('www.', 'http://www.', $string);
272        $string = preg_replace('|http://([a-zA-Z0-9-\./]+)|', '<a href="http://$1">$1</a>', $string);
273        $string = preg_replace(
274            '/(([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6})/',
275            '<a href="mailto:$1">$1</a>',
276            $string
277        );
278
279        return $string;
280    }
281
282    /**
283     * Moves given key of an array to the top
284     *
285     * @param array  $array
286     * @param string $key
287     */
288    public static function moveToTop(&$array, $key)
289    {
290        $temp = [$key => $array[$key]];
291        unset($array[$key]);
292        $array = $temp + $array;
293    }
294    /**
295     * Creates a seed with microseconds.
296     *
297     * @return integer|null
298     */
299    private static function makeSeed()
300    {
301        list($usec, $sec) = explode(' ', microtime());
302        return $sec + $usec * 1000000;
303    }
304
305    /**
306     * Returns a random number.
307     *
308     * @param  integer $min
309     * @param  integer $max
310     * @return int
311     */
312    public static function createRandomNumber($min, $max)
313    {
314        mt_srand(Utils::makeSeed());
315        return mt_rand($min, $max);
316    }
317}
318