1<?php
2
3/**
4 +-----------------------------------------------------------------------+
5 | This file is part of the Roundcube webmail client                     |
6 |                                                                       |
7 | Copyright (C) The Roundcube Dev Team                                  |
8 |                                                                       |
9 | Licensed under the GNU General Public License version 3 or            |
10 | any later version with exceptions for skins & plugins.                |
11 | See the README file for a full license statement.                     |
12 |                                                                       |
13 | CONTENTS:                                                             |
14 |   Roundcube Framework Initialization                                  |
15 +-----------------------------------------------------------------------+
16 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17 | Author: Aleksander Machniak <alec@alec.pl>                            |
18 +-----------------------------------------------------------------------+
19*/
20
21/**
22 * Roundcube Framework Initialization
23 *
24 * @package    Framework
25 * @subpackage Core
26 */
27
28$config = [
29    'error_reporting' => E_ALL & ~E_NOTICE & ~E_STRICT,
30    'display_errors'  => false,
31    'log_errors'      => true,
32    // Some users are not using Installer, so we'll check some
33    // critical PHP settings here. Only these, which doesn't provide
34    // an error/warning in the logs later. See (#1486307).
35    'mbstring.func_overload' => 0,
36];
37
38// check these additional ini settings if not called via CLI
39if (php_sapi_name() != 'cli') {
40    $config += [
41        'suhosin.session.encrypt' => false,
42        'file_uploads'            => true,
43        'session.auto_start'      => false,
44        'zlib.output_compression' => false,
45    ];
46}
47
48foreach ($config as $optname => $optval) {
49    $ini_optval = filter_var(ini_get($optname), is_bool($optval) ? FILTER_VALIDATE_BOOLEAN : FILTER_VALIDATE_INT);
50    if ($optval != $ini_optval && @ini_set($optname, $optval) === false) {
51        $optval = !is_bool($optval) ? $optval : ($optval ? 'On' : 'Off');
52        $error  = "ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n"
53            . "Check your PHP configuration (including php_admin_flag).";
54
55        if (defined('STDERR')) fwrite(STDERR, $error); else echo $error;
56        exit(1);
57    }
58}
59
60// framework constants
61define('RCUBE_VERSION', '1.5.1');
62define('RCUBE_CHARSET', 'UTF-8');
63define('RCUBE_TEMP_FILE_PREFIX', 'RCMTEMP');
64
65if (!defined('RCUBE_LIB_DIR')) {
66    define('RCUBE_LIB_DIR', __DIR__ . '/');
67}
68
69if (!defined('RCUBE_INSTALL_PATH')) {
70    define('RCUBE_INSTALL_PATH', RCUBE_LIB_DIR);
71}
72
73if (!defined('RCUBE_CONFIG_DIR')) {
74    define('RCUBE_CONFIG_DIR', RCUBE_INSTALL_PATH . 'config/');
75}
76
77if (!defined('RCUBE_PLUGINS_DIR')) {
78    define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
79}
80
81if (!defined('RCUBE_LOCALIZATION_DIR')) {
82    define('RCUBE_LOCALIZATION_DIR', RCUBE_INSTALL_PATH . 'localization/');
83}
84
85// set internal encoding for mbstring extension
86mb_internal_encoding(RCUBE_CHARSET);
87mb_regex_encoding(RCUBE_CHARSET);
88
89// make sure the Roundcube lib directory is in the include_path
90$rcube_path = realpath(RCUBE_LIB_DIR . '..');
91$sep        = PATH_SEPARATOR;
92$regexp     = "!(^|$sep)" . preg_quote($rcube_path, '!') . "($sep|\$)!";
93$path       = ini_get('include_path');
94
95if (!preg_match($regexp, $path)) {
96    set_include_path($path . PATH_SEPARATOR . $rcube_path);
97}
98
99// Register autoloader
100spl_autoload_register('rcube_autoload');
101
102// set PEAR error handling (will also load the PEAR main class)
103PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, function($err) { rcube::raise_error($err, true); });
104
105
106/**
107 * Similar function as in_array() but case-insensitive with multibyte support.
108 *
109 * @param string $needle   Needle value
110 * @param array  $heystack Array to search in
111 *
112 * @return bool True if found, False if not
113 */
114function in_array_nocase($needle, $haystack)
115{
116    // use much faster method for ascii
117    if (is_ascii($needle)) {
118        foreach ((array) $haystack as $value) {
119            if (strcasecmp($value, $needle) === 0) {
120                return true;
121            }
122        }
123    }
124    else {
125        $needle = mb_strtolower($needle);
126        foreach ((array) $haystack as $value) {
127            if ($needle === mb_strtolower($value)) {
128                return true;
129            }
130        }
131    }
132
133    return false;
134}
135
136/**
137 * Parse a human readable string for a number of bytes.
138 *
139 * @param string $str Input string
140 *
141 * @return float Number of bytes
142 */
143function parse_bytes($str)
144{
145    if (is_numeric($str)) {
146        return floatval($str);
147    }
148
149    $bytes = 0;
150
151    if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs)) {
152        $bytes = floatval($regs[1]);
153        switch (strtolower($regs[2])) {
154        case 'g':
155        case 'gb':
156            $bytes *= 1073741824;
157            break;
158        case 'm':
159        case 'mb':
160            $bytes *= 1048576;
161            break;
162        case 'k':
163        case 'kb':
164            $bytes *= 1024;
165            break;
166        }
167    }
168
169    return floatval($bytes);
170}
171
172/**
173 * Make sure the string ends with a slash
174 *
175 * @param string $str A string
176 *
177 * @return string A string ending with a slash
178 */
179function slashify($str)
180{
181    return unslashify($str) . '/';
182}
183
184/**
185 * Remove slashes at the end of the string
186 *
187 * @param string $str A string
188 *
189 * @return string A string ending with no slash
190 */
191function unslashify($str)
192{
193    return rtrim($str, '/');
194}
195
196/**
197 * Returns number of seconds for a specified offset string.
198 *
199 * @param string $str String representation of the offset (e.g. 20min, 5h, 2days, 1week)
200 *
201 * @return int Number of seconds
202 */
203function get_offset_sec($str)
204{
205    if (preg_match('/^([0-9]+)\s*([smhdw])/i', $str, $regs)) {
206        $amount = (int) $regs[1];
207        $unit   = strtolower($regs[2]);
208    }
209    else {
210        $amount = (int) $str;
211        $unit   = 's';
212    }
213
214    switch ($unit) {
215    case 'w':
216        $amount *= 7;
217    case 'd':
218        $amount *= 24;
219    case 'h':
220        $amount *= 60;
221    case 'm':
222        $amount *= 60;
223    }
224
225    return $amount;
226}
227
228/**
229 * Create a unix timestamp with a specified offset from now.
230 *
231 * @param string $offset_str String representation of the offset (e.g. 20min, 5h, 2days)
232 * @param int    $factor     Factor to multiply with the offset
233 *
234 * @return int Unix timestamp
235 */
236function get_offset_time($offset_str, $factor = 1)
237{
238    return time() + get_offset_sec($offset_str) * $factor;
239}
240
241/**
242 * Truncate string if it is longer than the allowed length.
243 * Replace the middle or the ending part of a string with a placeholder.
244 *
245 * @param string $str         Input string
246 * @param int    $maxlength   Max. length
247 * @param string $placeholder Replace removed chars with this
248 * @param bool   $ending      Set to True if string should be truncated from the end
249 *
250 * @return string Abbreviated string
251 */
252function abbreviate_string($str, $maxlength, $placeholder = '...', $ending = false)
253{
254    $length = mb_strlen($str);
255
256    if ($length > $maxlength) {
257        if ($ending) {
258            return mb_substr($str, 0, $maxlength) . $placeholder;
259        }
260
261        $placeholder_length = mb_strlen($placeholder);
262        $first_part_length  = floor(($maxlength - $placeholder_length)/2);
263        $second_starting_location = $length - $maxlength + $first_part_length + $placeholder_length;
264
265        $prefix = mb_substr($str, 0, $first_part_length);
266        $suffix = mb_substr($str, $second_starting_location);
267        $str    = $prefix . $placeholder . $suffix;
268    }
269
270    return $str;
271}
272
273/**
274 * Get all keys from array (recursive).
275 *
276 * @param array $array Input array
277 *
278 * @return array List of array keys
279 */
280function array_keys_recursive($array)
281{
282    $keys = [];
283
284    if (!empty($array) && is_array($array)) {
285        foreach ($array as $key => $child) {
286            $keys[] = $key;
287            foreach (array_keys_recursive($child) as $val) {
288                $keys[] = $val;
289            }
290        }
291    }
292
293    return $keys;
294}
295
296/**
297 * Get first element from an array
298 *
299 * @param array $array Input array
300 *
301 * @return mixed First element if found, Null otherwise
302 */
303function array_first($array)
304{
305    if (is_array($array)) {
306        reset($array);
307        foreach ($array as $element) {
308            return $element;
309        }
310    }
311}
312
313/**
314 * Remove all non-ascii and non-word chars except ., -, _
315 *
316 * @param string $str          A string
317 * @param bool   $css_id       The result may be used as CSS identifier
318 * @param string $replace_with Replacement character
319 *
320 * @return string Clean string
321 */
322function asciiwords($str, $css_id = false, $replace_with = '')
323{
324    $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
325    return preg_replace("/[^$allowed]+/i", $replace_with, $str);
326}
327
328/**
329 * Check if a string contains only ascii characters
330 *
331 * @param string $str           String to check
332 * @param bool   $control_chars Includes control characters
333 *
334 * @return bool True if the string contains ASCII-only, False otherwise
335 */
336function is_ascii($str, $control_chars = true)
337{
338    $regexp = $control_chars ? '/[^\x00-\x7F]/' : '/[^\x20-\x7E]/';
339    return preg_match($regexp, $str) ? false : true;
340}
341
342/**
343 * Compose a valid representation of name and e-mail address
344 *
345 * @param string $email E-mail address
346 * @param string $name  Person name
347 *
348 * @return string Formatted string
349 */
350function format_email_recipient($email, $name = '')
351{
352    $email = trim($email);
353
354    if ($name && $name != $email) {
355        // Special chars as defined by RFC 822 need to in quoted string (or escaped).
356        if (preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name)) {
357            $name = '"'.addcslashes($name, '"').'"';
358        }
359
360        return "$name <$email>";
361    }
362
363    return $email;
364}
365
366/**
367 * Format e-mail address
368 *
369 * @param string $email E-mail address
370 *
371 * @return string Formatted e-mail address
372 */
373function format_email($email)
374{
375    $email = trim($email);
376    $parts = explode('@', $email);
377    $count = count($parts);
378
379    if ($count > 1) {
380        $parts[$count-1] = mb_strtolower($parts[$count-1]);
381
382        $email = implode('@', $parts);
383    }
384
385    return $email;
386}
387
388/**
389 * Fix version number so it can be used correctly in version_compare()
390 *
391 * @param string $version Version number string
392 *
393 * @param return Version number string
394 */
395function version_parse($version)
396{
397    return str_replace(
398        ['-stable', '-git'],
399        ['.0', '.99'],
400        $version
401    );
402}
403
404/**
405 * Use PHP5 autoload for dynamic class loading
406 *
407 * @param string $classname Class name
408 *
409 * @return bool True when the class file has been found
410 *
411 * @todo Make Zend, PEAR etc play with this
412 * @todo Make our classes conform to a more straight forward CS.
413 */
414function rcube_autoload($classname)
415{
416    if (strpos($classname, 'rcube') === 0) {
417        $classname = preg_replace('/^rcube_(cache|db|session|spellchecker)_/', '\\1/', $classname);
418        $classname = 'Roundcube/' . $classname;
419    }
420    else if (strpos($classname, 'html_') === 0 || $classname === 'html') {
421        $classname = 'Roundcube/html';
422    }
423    else if (strpos($classname, 'Mail_') === 0) {
424        $classname = 'Mail/' . substr($classname, 5);
425    }
426    else if (strpos($classname, 'Net_') === 0) {
427        $classname = 'Net/' . substr($classname, 4);
428    }
429    else if (strpos($classname, 'Auth_') === 0) {
430        $classname = 'Auth/' . substr($classname, 5);
431    }
432
433    // Translate PHP namespaces into directories,
434    // i.e. use \Sabre\VObject; $vcf = VObject\Reader::read(...)
435    //      -> Sabre/VObject/Reader.php
436    $classname = str_replace('\\', '/', $classname);
437
438    if ($fp = @fopen("$classname.php", 'r', true)) {
439        fclose($fp);
440        include_once "$classname.php";
441        return true;
442    }
443
444    return false;
445}
446