Passed
Pull Request — master (#6623)
by
unknown
12:05 queued 02:42
created

timeago_resolve_language()   F

Complexity

Conditions 19
Paths 360

Size

Total Lines 88
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 52
nc 360
nop 1
dl 0
loc 88
rs 1.6833
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\DataFixtures\LanguageFixtures;
6
use Chamilo\CoreBundle\Framework\Container;
7
use ChamiloSession as Session;
8
use Westsworld\TimeAgo;
9
10
/**
11
 * File: internationalization.lib.php
12
 * Internationalization library for Chamilo 1.x LMS
13
 * A library implementing internationalization related functions.
14
 * License: GNU General Public License Version 3 (Free Software Foundation)ww.
15
 *
16
 * @author Ivan Tcholakov, <[email protected]>, 2009, 2010
17
 * @author More authors, mentioned in the correpsonding fragments of this source.
18
 */
19
// Predefined date formats in Chamilo provided by the language sub-system.
20
// To be used as a parameter for the function api_format_date()
21
define('TIME_NO_SEC_FORMAT', 0); // 15:23
22
define('DATE_FORMAT_SHORT', 1); // Aug 25, 09
23
define('DATE_FORMAT_LONG', 2); // Monday August 25, 09
24
define('DATE_FORMAT_LONG_NO_DAY', 10); // August 25, 2009
25
define('DATE_TIME_FORMAT_LONG', 3); // Monday August 25, 2009 at 03:28 PM
26
27
define('DATE_FORMAT_NUMBER', 4); // 25.08.09
28
define('DATE_TIME_FORMAT_LONG_24H', 5); // August 25, 2009 at 15:28
29
define('DATE_TIME_FORMAT_SHORT', 6); // Aug 25, 2009 at 03:28 PM
30
define('DATE_TIME_FORMAT_SHORT_TIME_FIRST', 7); // 03:28 PM, Aug 25 2009
31
define('DATE_FORMAT_NUMBER_NO_YEAR', 8); // 25.08 dd-mm
32
define('DATE_FORMAT_ONLY_DAYNAME', 9); // Monday, Sunday, etc
33
define('DATE_TIME_FORMAT_SHORT_LOCALIZED', 11);
34
35
// Formatting person's name.
36
// Formatting a person's name using the pattern as it has been
37
// configured in the internationalization database for every language.
38
// This (default) option would be the most used.
39
define('PERSON_NAME_COMMON_CONVENTION', 0);
40
// The following options may be used in limited number of places for overriding the common convention:
41
42
// Formatting a person's name in Western order: first_name last_name
43
define('PERSON_NAME_WESTERN_ORDER', 1);
44
// Formatting a person's name in Eastern order: last_name first_name
45
define('PERSON_NAME_EASTERN_ORDER', 2);
46
// Contextual: formatting person's name in library order: last_name, first_name
47
define('PERSON_NAME_LIBRARY_ORDER', 3);
48
// Contextual: formatting a person's name assotiated with an email-address.
49
// Ivan: I am not sure how seems email servers an clients would interpret name order, so I assign the Western order.
50
define('PERSON_NAME_EMAIL_ADDRESS', PERSON_NAME_WESTERN_ORDER);
51
// Contextual: formatting a person's name for data-exporting operations.
52
// For backward compatibility this format has been set to Eastern order.
53
define('PERSON_NAME_DATA_EXPORT', PERSON_NAME_EASTERN_ORDER);
54
55
/**
56
 * Returns a translated (localized) string.
57
 */
58
function get_lang(string $variable, ?string $locale = null): string
59
{
60
    $translator = Container::$translator ?: Container::$container?->get('translator');
61
62
    if (!$translator) {
63
        return $variable;
64
    }
65
66
    // Using symfony
67
    $defaultDomain = 'messages';
68
69
    // Check for locale fallbacks (in case no translation is available).
70
    static $fallbacks = null;
71
    $englishInQueue = (!empty($locale) && $locale === 'en_US');
72
    if ($fallbacks === null) {
73
        if (!empty($locale)) {
74
            while (!empty($parent = SubLanguageManager::getParentLocale($locale))) {
75
                $fallbacks[] = $parent;
76
                if ($parent === 'en_US') {
77
                    $englishInQueue = true;
78
                }
79
            }
80
        }
81
        // If there were no parent language, still consider en_US as global fallback
82
        if (!$englishInQueue) {
83
            $fallbacks[] = 'en_US';
84
        }
85
    }
86
    // Test a basic translation to the current language.
87
    $translation = $translator->trans(
88
        $variable,
89
        [],
90
        $defaultDomain,
91
        $locale
92
    );
93
    // If no translation was found, $translation is empty.
94
    // Check fallbacks for a valid translation.
95
    $i = 0;
96
    while (empty($translation) && !empty($fallbacks[$i])) {
97
        $fallback = $fallbacks[$i];
98
        $translation = $translator->trans(
99
            $variable,
100
            [],
101
            $defaultDomain,
102
            $fallback
103
        );
104
        $i++;
105
    }
106
107
    return $translation;
108
}
109
110
/**
111
 * Gets language iso code.
112
 */
113
function api_get_language_isocode()
114
{
115
    $request = Container::getRequest();
116
117
    if ($request) {
118
        return $request->getLocale();
119
    }
120
121
    return 'en_US';
122
}
123
124
/**
125
 * Gets language iso code column from the language table.
126
 *
127
 * @return array An array with the current isocodes
128
 *
129
 * */
130
function api_get_platform_isocodes()
131
{
132
    $list = [];
133
    $sql = "SELECT isocode
134
            FROM ".Database::get_main_table(TABLE_MAIN_LANGUAGE)."
135
            ORDER BY isocode ";
136
    $result = Database::query($sql);
137
    if (Database::num_rows($result)) {
138
        while ($row = Database::fetch_array($result)) {
139
            $list[] = trim($row['isocode']);
140
        }
141
    }
142
143
    return $list;
144
}
145
146
/**
147
 * Gets text direction according to the given language.
148
 *
149
 * @param string $iso Iso code example en_US, fr_FR, If $language is omitted, interface language is assumed then.
150
 *
151
 * @return string the correspondent to the language text direction ('ltr' or 'rtl')
152
 */
153
function api_get_text_direction($iso = '')
154
{
155
    $languages = LanguageFixtures::getLanguages();
156
    $rightDirection = array_column($languages, 'direction', 'isocode');
157
158
    if (empty($iso)) {
159
        $iso = api_get_language_isocode();
160
    }
161
162
    if (isset($rightDirection[$iso])) {
163
        return 'rtl';
164
    }
165
166
    return 'ltr';
167
}
168
169
/**
170
 * Returns an alphabetized list of timezones in an associative array
171
 * that can be used to populate a select.
172
 *
173
 * @return array List of timezone identifiers
174
 *
175
 * @author Guillaume Viguier <[email protected]>
176
 */
177
function api_get_timezones()
178
{
179
    $timezone_identifiers = DateTimeZone::listIdentifiers();
180
    sort($timezone_identifiers);
181
    $out = [];
182
    foreach ($timezone_identifiers as $tz) {
183
        $out[$tz] = $tz;
184
    }
185
    $null_option = ['' => ''];
186
187
    return array_merge($null_option, $out);
188
}
189
190
/**
191
 * Returns the timezone to be converted to/from, based on user or admin preferences.
192
 *
193
 * @return string The timezone chosen
194
 */
195
function api_get_timezone(): string
196
{
197
    $timezone = Session::read('system_timezone');
198
    if (empty($timezone)) {
199
        // 1. Default server timezone
200
        $timezone = date_default_timezone_get();
201
202
        // 2. Platform-specific timezone setting (overrides server default)
203
        $timezoneFromSettings = api_get_setting('platform.timezone', false, 'timezones');
204
        if (!empty($timezoneFromSettings)) {
205
            $timezone = $timezoneFromSettings;
206
        }
207
208
        // 3. User-specific timezone if allowed
209
        $allowUserTimezones = api_get_setting('profile.use_users_timezone', false, 'timezones');
210
        $userId = api_get_user_id();
211
212
        if ('true' === $allowUserTimezones && !empty($userId)) {
213
            $user = api_get_user_entity($userId);
214
215
            if ($user && $user->getTimezone()) {
216
                $timezone = $user->getTimezone();
217
            }
218
        }
219
        Session::write('system_timezone', $timezone);
220
    }
221
222
    // Replace backslashes by forward slashes in case of wrong timezone format
223
    $timezone = str_replace('\\', '/', $timezone);
224
225
    return $timezone;
226
}
227
228
/**
229
 * Returns the given date as a DATETIME in UTC timezone.
230
 * This function should be used before entering any date in the DB.
231
 *
232
 * @param mixed $time                    Date to be converted (can be a string supported by date() or a timestamp)
233
 * @param bool  $returnNullIfInvalidDate If the date is not correct return null instead of the current date
234
 * @param bool  $returnObj               Returns a DateTime object
235
 *
236
 * @return string|DateTime The DATETIME in UTC to be inserted in the DB,
237
 *                         or null if the format of the argument is not supported
238
 *
239
 * @author Julio Montoya - Adding the 2nd parameter
240
 * @author Guillaume Viguier <[email protected]>
241
 */
242
function api_get_utc_datetime($time = null, $returnNullIfInvalidDate = false, $returnObj = false)
243
{
244
    if (is_null($time) || empty($time) || '0000-00-00 00:00:00' === $time) {
245
        if ($returnNullIfInvalidDate) {
246
            return null;
247
        }
248
        if ($returnObj) {
249
            return new DateTime(gmdate('Y-m-d H:i:s'), new DateTimeZone('UTC'));
250
        }
251
252
        return gmdate('Y-m-d H:i:s');
253
    }
254
255
    // If time is a timestamp, return directly in utc
256
    if (is_numeric($time)) {
257
        $time = (int) $time;
258
        $time = gmdate('Y-m-d H:i:s', $time);
259
        if ($returnObj) {
260
            return new DateTime($time, new DateTimeZone('UTC'));
261
        }
262
263
        return $time;
264
    }
265
    try {
266
        $date = new DateTime($time, new DateTimezone('UTC'));
267
        if ($returnObj) {
268
            return $date;
269
        } else {
270
            return $date->format('Y-m-d H:i:s');
271
        }
272
    } catch (Exception $e) {
273
        return null;
274
    }
275
}
276
277
/**
278
 * Returns a DATETIME string converted to the right timezone.
279
 *
280
 * @param mixed  $time                    The time to be converted
281
 * @param string $to_timezone             The timezone to be converted to.
282
 *                                        If null, the timezone will be determined based on user preference,
283
 *                                        or timezone chosen by the admin for the platform.
284
 * @param string $from_timezone           The timezone to be converted from. If null, UTC will be assumed.
285
 * @param bool   $returnNullIfInvalidDate
286
 * @param bool   $showTime
287
 * @param bool   $humanForm
288
 * @param string $format
289
 *
290
 * @return string The converted time formatted as Y-m-d H:i:s
291
 *
292
 * @author Guillaume Viguier <[email protected]>
293
 */
294
function api_get_local_time(
295
    $time = null,
296
    $to_timezone = null,
297
    $from_timezone = null,
298
    $returnNullIfInvalidDate = false,
299
    $showTime = true,
300
    $humanForm = false,
301
    $format = ''
302
) {
303
    // Determining the timezone to be converted from
304
    if (is_null($from_timezone)) {
305
        $from_timezone = 'UTC';
306
    }
307
308
    // If time is a timestamp, convert it to a string
309
    if (is_null($time) || empty($time) || '0000-00-00 00:00:00' == $time) {
310
        if ($returnNullIfInvalidDate) {
311
            return null;
312
        }
313
        $from_timezone = 'UTC';
314
        $time = gmdate('Y-m-d H:i:s');
315
    }
316
317
    if (is_numeric($time)) {
318
        $time = (int) $time;
319
        if ($returnNullIfInvalidDate) {
320
            if (strtotime(date('d-m-Y H:i:s', $time)) !== $time) {
321
                return null;
322
            }
323
        }
324
325
        $from_timezone = 'UTC';
326
        $time = gmdate('Y-m-d H:i:s', $time);
327
    }
328
329
    if ($time instanceof DateTime) {
330
        $time = $time->format('Y-m-d H:i:s');
331
        $from_timezone = 'UTC';
332
    }
333
334
    try {
335
        // Determining the timezone to be converted to
336
        if (is_null($to_timezone)) {
337
            $to_timezone = api_get_timezone();
338
        }
339
340
        $date = new DateTime($time, new DateTimezone($from_timezone));
341
        $date->setTimezone(new DateTimeZone($to_timezone));
342
343
        if (!empty($format)) {
344
            return $date->format($format);
345
        }
346
347
        return api_get_human_date_time($date, $showTime, $humanForm);
348
    } catch (Exception $e) {
349
        return '';
350
    }
351
}
352
353
/**
354
 * Converts a string into a timestamp safely (handling timezones), using strtotime.
355
 *
356
 * @param string $time     to be converted
357
 * @param string $timezone (if null, the timezone will be determined based
358
 *                         on user preference, or timezone chosen by the admin for the platform)
359
 *
360
 * @return int Timestamp
361
 *
362
 * @author Guillaume Viguier <[email protected]>
363
 */
364
function api_strtotime($time, $timezone = null)
365
{
366
    $system_timezone = date_default_timezone_get();
367
    if (!empty($timezone)) {
368
        date_default_timezone_set($timezone);
369
    }
370
    $timestamp = strtotime($time);
371
    if (!empty($timezone)) {
372
        // only reset timezone if it was changed
373
        date_default_timezone_set($system_timezone);
374
    }
375
376
    return $timestamp;
377
}
378
379
/**
380
 * Returns formatted date/time, correspondent to a given language.
381
 * The given date should be in the timezone chosen by the administrator
382
 * and/or user. Use api_get_local_time to get it.
383
 *
384
 * @param mixed $time Timestamp or datetime string
385
 * @param string|int Date format (see date formats in the Chamilo system:
386
 *                         TIME_NO_SEC_FORMAT,
387
 *                         DATE_FORMAT_SHORT,
388
 *                         DATE_FORMAT_LONG,
389
 *                         DATE_TIME_FORMAT_LONG
390
 * @param string $language (optional) Language id
391
 *                         If it is omitted, the current interface language is assumed
392
 *
393
 * @return string returns the formatted date
394
 *
395
 * @author Patrick Cool <[email protected]>, Ghent University
396
 * @author Christophe Gesche<[email protected]>
397
 *         originally inspired from from PhpMyAdmin
398
 * @author Ivan Tcholakov, 2009, code refactoring, adding support for predefined date/time formats.
399
 * @author Guillaume Viguier <[email protected]>
400
 *
401
 * @see    http://php.net/manual/en/function.strftime.php
402
 */
403
function api_format_date($time, $format = null, $language = null)
404
{
405
    if (empty($time)) {
406
        return '';
407
    }
408
409
    $system_timezone = date_default_timezone_get();
410
    date_default_timezone_set(api_get_timezone());
411
412
    if (is_string($time)) {
413
        $time = strtotime($time);
414
    }
415
416
    if (is_null($format)) {
417
        $format = DATE_TIME_FORMAT_LONG;
418
    }
419
    if ($time instanceof DateTime) {
420
        $time = $time->format('Y-m-d H:i:s');
421
    }
422
423
    $datetype = null;
424
    $timetype = null;
425
426
    if (is_int($format)) {
427
        switch ($format) {
428
            case DATE_FORMAT_ONLY_DAYNAME:
429
                $datetype = IntlDateFormatter::SHORT;
430
                $timetype = IntlDateFormatter::NONE;
431
432
                break;
433
            case DATE_FORMAT_NUMBER_NO_YEAR:
434
                $datetype = IntlDateFormatter::SHORT;
435
                $timetype = IntlDateFormatter::NONE;
436
437
                break;
438
            case DATE_FORMAT_NUMBER:
439
                $datetype = IntlDateFormatter::SHORT;
440
                $timetype = IntlDateFormatter::NONE;
441
442
                break;
443
            case TIME_NO_SEC_FORMAT:
444
                $datetype = IntlDateFormatter::NONE;
445
                $timetype = IntlDateFormatter::SHORT;
446
447
                break;
448
            case DATE_FORMAT_SHORT:
449
                $datetype = IntlDateFormatter::LONG;
450
                $timetype = IntlDateFormatter::NONE;
451
452
                break;
453
            case DATE_FORMAT_LONG:
454
                $datetype = IntlDateFormatter::FULL;
455
                $timetype = IntlDateFormatter::NONE;
456
457
                break;
458
            case DATE_TIME_FORMAT_LONG:
459
                $datetype = IntlDateFormatter::FULL;
460
                $timetype = IntlDateFormatter::SHORT;
461
462
                break;
463
            case DATE_FORMAT_LONG_NO_DAY:
464
                $datetype = IntlDateFormatter::FULL;
465
                $timetype = IntlDateFormatter::SHORT;
466
467
                break;
468
            case DATE_TIME_FORMAT_SHORT:
469
                $datetype = IntlDateFormatter::FULL;
470
                $timetype = IntlDateFormatter::SHORT;
471
472
                break;
473
            case DATE_TIME_FORMAT_SHORT_TIME_FIRST:
474
                $datetype = IntlDateFormatter::FULL;
475
                $timetype = IntlDateFormatter::SHORT;
476
477
                break;
478
            case DATE_TIME_FORMAT_LONG_24H:
479
                $datetype = IntlDateFormatter::FULL;
480
                $timetype = IntlDateFormatter::SHORT;
481
482
                break;
483
            case DATE_TIME_FORMAT_SHORT_LOCALIZED:
484
                $datetype = IntlDateFormatter::SHORT;
485
                $timetype = IntlDateFormatter::SHORT;
486
                break;
487
            default:
488
                $datetype = IntlDateFormatter::FULL;
489
                $timetype = IntlDateFormatter::SHORT;
490
        }
491
    }
492
493
    // Use ICU
494
    if (is_null($language)) {
495
        $language = api_get_language_isocode();
496
    }
497
    $date_formatter = new IntlDateFormatter($language, $datetype, $timetype, date_default_timezone_get());
498
    $formatted_date = api_to_system_encoding($date_formatter->format($time), 'UTF-8');
499
500
    date_default_timezone_set($system_timezone);
501
502
    return $formatted_date;
503
}
504
505
/**
506
 * Return a Westsworld\TimeAgo translation instance based on Chamilo's isocode.
507
 * Tolerates custom isocodes like "pl_polish2", "es_spanish", etc.
508
 *
509
 * @return object Instance of Westsworld\TimeAgo\Translations\*
510
 */
511
function timeago_resolve_language(?string $iso)
512
{
513
    // Normalize ISO (snake_case, lowercase)
514
    $iso = $iso ?: api_get_language_isocode();
515
    $norm = strtolower(str_replace('-', '_', trim($iso)));
516
517
    // Candidates: full, parent chain (if available), then base (first 2 letters)
518
    $candidates = [$norm];
519
520
    if (class_exists('SubLanguageManager')) {
521
        $tmp = $iso;
522
        // SubLanguageManager returns parents like "en_US"; normalize to snake_case
523
        while (!empty($parent = SubLanguageManager::getParentLocale($tmp))) {
524
            $candidates[] = strtolower(str_replace('-', '_', $parent));
525
            $tmp = $parent;
526
        }
527
    }
528
529
    if (preg_match('/^[a-z]{2}/', $norm, $m)) {
530
        $base = $m[0]; // e.g. "pl" from "pl_polish2", "es" from "es_spanish"
531
        $candidates[] = $base;
532
    } else {
533
        $base = $norm;
534
    }
535
536
    // Map of classes supported by the library (key = snake_case, value = class suffix)
537
    $map = [
538
        'ar' => 'Ar', 'bg' => 'Bg', 'cs' => 'Cs', 'da' => 'Da',
539
        'de' => 'De', 'el' => 'El', 'en' => 'En', 'es' => 'Es',
540
        'fa' => 'Fa', 'fi' => 'Fi', 'fr' => 'Fr', 'he' => 'He',
541
        'hr' => 'Hr', 'hu' => 'Hu', 'id' => 'Id', 'it' => 'It',
542
        'ja' => 'Ja', 'ko' => 'Ko', 'nb' => 'Nb', 'nl' => 'Nl',
543
        'no' => 'No', 'pl' => 'Pl',
544
        'pt_br' => 'Pt_BR', 'pt_pt' => 'Pt_PT',
545
        'ro' => 'Ro', 'ru' => 'Ru', 'sk' => 'Sk', 'sr' => 'Sr',
546
        'sv' => 'Sv', 'tr' => 'Tr', 'uk' => 'Uk', 'vi' => 'Vi',
547
        'zh_cn' => 'Zh_CN', 'zh_tw' => 'Zh_TW',
548
        'pt' => 'Pt_BR',
549
        'zh' => 'Zh_CN',
550
        'nn' => 'Nb',
551
    ];
552
553
    // Try exact candidates first, then special rules, then the base
554
    foreach ($candidates as $cand) {
555
        if (isset($map[$cand])) {
556
            $class = "Westsworld\\TimeAgo\\Translations\\{$map[$cand]}";
557
            if (class_exists($class)) {
558
                return new $class();
559
            }
560
        }
561
562
        // Special handling when the candidate is an ambiguous base
563
        if ($cand === 'pt') {
564
            foreach (['Pt_BR', 'Pt_PT'] as $suf) {
565
                $class = "Westsworld\\TimeAgo\\Translations\\$suf";
566
                if (class_exists($class)) {
567
                    return new $class();
568
                }
569
            }
570
        }
571
        if ($cand === 'zh') {
572
            foreach (['Zh_CN', 'Zh_TW'] as $suf) {
573
                $class = "Westsworld\\TimeAgo\\Translations\\$suf";
574
                if (class_exists($class)) {
575
                    return new $class();
576
                }
577
            }
578
        }
579
        if ($cand === 'nn') {
580
            foreach (['Nb', 'No'] as $suf) {
581
                $class = "Westsworld\\TimeAgo\\Translations\\$suf";
582
                if (class_exists($class)) {
583
                    return new $class();
584
                }
585
            }
586
        }
587
    }
588
589
    // Last attempt: try base directly if it has a mapping
590
    if (isset($map[$base])) {
591
        $class = "Westsworld\\TimeAgo\\Translations\\{$map[$base]}";
592
        if (class_exists($class)) {
593
            return new $class();
594
        }
595
    }
596
597
    // Final fallback: English
598
    return new Westsworld\TimeAgo\Translations\En();
599
}
600
601
/**
602
 * Time-ago function with proper locale resolution (including custom isocodes)
603
 * and consistent timezone handling.
604
 */
605
function date_to_str_ago($date, $timeZone = null, $returnDateDifference = false)
606
{
607
    if (empty($date) || '0000-00-00 00:00:00' === $date) {
608
        return '';
609
    }
610
611
    // Resolve timezone: prefer parameter, otherwise user/platform timezone
612
    $tz = $timeZone ?: api_get_timezone();
613
614
    // Resolve language for TimeAgo (tolerant to pl_polish2, es_spanish, etc.)
615
    $language = timeago_resolve_language(api_get_language_isocode());
616
    $timeAgo  = new TimeAgo($language);
617
618
    // Normalize $date to DateTime in the same timezone as "now"
619
    if (!($date instanceof DateTime)) {
620
        if (is_numeric($date)) {
621
            // Timestamp: create from UTC epoch and then set target TZ
622
            $dateObj = new DateTime('@'.(int) $date);
623
            $dateObj->setTimezone(new DateTimeZone($tz));
624
            $date = $dateObj;
625
        } else {
626
            // Assume DB string is UTC, then convert to target TZ
627
            $date = new DateTime($date, new DateTimeZone('UTC'));
628
            $date->setTimezone(new DateTimeZone($tz));
629
        }
630
    } else {
631
        // Ensure provided DateTime uses the target TZ
632
        $date->setTimezone(new DateTimeZone($tz));
633
    }
634
635
    // Ensure the library computes "now" in the same TZ
636
    $oldTz = date_default_timezone_get();
637
    date_default_timezone_set($tz);
638
639
    if ($returnDateDifference) {
640
        $now  = new DateTime('now', new DateTimeZone($tz));
641
        $diff = $date->diff($now);
642
        date_default_timezone_set($oldTz);
643
644
        return [
645
            'years' => $diff->y,
646
            'months' => $diff->m,
647
            'days' => $diff->d,
648
            'hours' => $diff->h,
649
            'minutes' => $diff->i,
650
            'seconds' => $diff->s,
651
        ];
652
    }
653
654
    $value = $timeAgo->inWords($date);
655
    date_default_timezone_set($oldTz);
656
657
    return $value;
658
}
659
660
/**
661
 * Converts a date to the right timezone and localizes it in the format given as an argument.
662
 *
663
 * @param mixed The time to be converted
664
 * @param mixed Format to be used (TIME_NO_SEC_FORMAT, DATE_FORMAT_SHORT, DATE_FORMAT_LONG, DATE_TIME_FORMAT_LONG)
665
 * @param string Timezone to be converted from. If null, UTC will be assumed.
666
 *
667
 * @return string Converted and localized date
668
 *
669
 * @author Guillaume Viguier <[email protected]>
670
 */
671
function api_convert_and_format_date($time = null, $format = null, $from_timezone = null)
672
{
673
    // First, convert the datetime to the right timezone
674
    $time = api_get_local_time($time, null, $from_timezone, true);
675
676
    // Second, localize the date
677
    return api_format_date($time, $format);
678
}
679
680
/**
681
 * Returns an array of translated week days in short names.
682
 *
683
 * @param string $language (optional) Language id. If it is omitted, the current interface language is assumed.
684
 *
685
 * @return string Returns an array of week days (short names).
686
 *                Example: api_get_week_days_short('english') means array('Sun', 'Mon', ... 'Sat').
687
 *                Note: For all languges returned days are in the English order.
688
 */
689
function api_get_week_days_short($language = null)
690
{
691
    $days = &_api_get_day_month_names($language);
692
693
    return $days['days_short'];
694
}
695
696
/**
697
 * Returns an array of translated week days.
698
 *
699
 * @param string $language (optional) Language id. If it is omitted,
700
 *                         the current interface language is assumed.
701
 *
702
 * @return string Returns an array of week days.
703
 *                Example: api_get_week_days_long('english') means array('Sunday, 'Monday', ... 'Saturday').
704
 *                Note: For all languges returned days are in the English order.
705
 */
706
function api_get_week_days_long($language = null)
707
{
708
    $days = &_api_get_day_month_names($language);
709
710
    return $days['days_long'];
711
}
712
713
/**
714
 * Returns an array of translated months in short names.
715
 *
716
 * @param string $language (optional)    Language id.
717
 *                         If it is omitted, the current interface language is assumed.
718
 *
719
 * @return string Returns an array of months (short names).
720
 *                Example: api_get_months_short('english') means array('Jan', 'Feb', ... 'Dec').
721
 */
722
function api_get_months_short($language = null)
723
{
724
    $months = &_api_get_day_month_names($language);
725
726
    return $months['months_short'];
727
}
728
729
/**
730
 * Returns an array of translated months.
731
 *
732
 * @param string $language (optional)    Language id.
733
 *                         If it is omitted, the current interface language is assumed.
734
 *
735
 * @return string Returns an array of months.
736
 *                Example: api_get_months_long('english') means array('January, 'February' ... 'December').
737
 */
738
function api_get_months_long($language = null)
739
{
740
    $months = &_api_get_day_month_names($language);
741
742
    return $months['months_long'];
743
}
744
745
/**
746
 * Name order conventions.
747
 */
748
749
/**
750
 * Builds a person (full) name depending on the convention for a given language.
751
 *
752
 * @param string     $first_name the first name of the person
753
 * @param string     $last_name  the last name of the person
754
 * @param string     $title      the title of the person
755
 * @param int|string $format     (optional) The person name format.
756
 *                               It may be a pattern-string (for example '%t %l, %f' or '%T %F %L', ...) or
757
 *                               some of the constants
758
 *                               PERSON_NAME_COMMON_CONVENTION (default),
759
 *                               PERSON_NAME_WESTERN_ORDER,
760
 *                               PERSON_NAME_EASTERN_ORDER,
761
 *                               PERSON_NAME_LIBRARY_ORDER.
762
 * @param string     $language   (optional)
763
 *                               The language id. If it is omitted, the current interface language is assumed.
764
 *                               This parameter has meaning with the format PERSON_NAME_COMMON_CONVENTION only.
765
 * @param string     $username
766
 *
767
 * @return string The result is sort of full name of the person.
768
 *                Sample results:
769
 *                Peter Ustinoff or Dr. Peter Ustinoff     - the Western order
770
 *                Ustinoff Peter or Dr. Ustinoff Peter     - the Eastern order
771
 *                Ustinoff, Peter or - Dr. Ustinoff, Peter - the library order
772
 *                Note: See the file main/inc/lib/internationalization_database/name_order_conventions.php
773
 *                where you can check the convention for your language.
774
 *
775
 * @author Carlos Vargas <[email protected]> - initial implementation.
776
 * @author Ivan Tcholakov
777
 */
778
function api_get_person_name(
779
    $first_name,
780
    $last_name,
781
    $title = null,
782
    $format = null,
783
    $language = null,
784
    $username = null
785
) {
786
    static $valid = [];
787
    if (empty($format)) {
788
        $format = PERSON_NAME_COMMON_CONVENTION;
789
    }
790
    // We check if the language is supported, otherwise we check the
791
    // interface language of the parent language of sublanguage
792
    if (empty($language)) {
793
        // Do not set $setParentLanguageName because this function is called before
794
        // the main language is loaded in global.inc.php
795
        $language = api_get_language_isocode();
796
    }
797
    if (!isset($valid[$format][$language])) {
798
        if (is_int($format)) {
799
            switch ($format) {
800
                case PERSON_NAME_COMMON_CONVENTION:
801
                    $valid[$format][$language] = _api_get_person_name_convention($language, 'format');
802
                    break;
803
                case PERSON_NAME_WESTERN_ORDER:
804
                    $valid[$format][$language] = '%t %f %l';
805
                    break;
806
                case PERSON_NAME_EASTERN_ORDER:
807
                    $valid[$format][$language] = '%t %l %f';
808
                    break;
809
                case PERSON_NAME_LIBRARY_ORDER:
810
                    $valid[$format][$language] = '%t %l, %f';
811
                    break;
812
                default:
813
                    $valid[$format][$language] = '%t %f %l';
814
                    break;
815
            }
816
        } else {
817
            $valid[$format][$language] = _api_validate_person_name_format($format);
818
        }
819
    }
820
821
    $format = $valid[$format][$language];
822
823
    $keywords = [
824
        '%firstname',
825
        '%f',
826
        '%F',
827
        '%lastname',
828
        '%l',
829
        '%L',
830
        '%title',
831
        '%t',
832
        '%T',
833
        '%username',
834
        '%u',
835
        '%U',
836
    ];
837
838
    $values = [
839
        $first_name,
840
        $first_name,
841
        api_strtoupper($first_name),
842
        $last_name,
843
        $last_name,
844
        api_strtoupper($last_name),
845
        $title,
846
        $title,
847
        api_strtoupper($title),
848
        $username,
849
        $username,
850
        api_strtoupper($username),
851
    ];
852
    $person_name = str_replace($keywords, $values, $format);
853
854
    return _api_clean_person_name($person_name);
855
}
856
857
/**
858
 * Checks whether a given format represents person name in Western order (for which first name is first).
859
 *
860
 * @param int|string $format   (optional)    The person name format.
861
 *                             It may be a pattern-string (for example '%t. %l, %f') or some of the constants
862
 *                             PERSON_NAME_COMMON_CONVENTION (default),
863
 *                             PERSON_NAME_WESTERN_ORDER,
864
 *                             PERSON_NAME_EASTERN_ORDER,
865
 *                             PERSON_NAME_LIBRARY_ORDER.
866
 * @param string     $language (optional) The language id. If it is omitted,
867
 *                             the current interface language is assumed. This parameter has meaning with the
868
 *                             format PERSON_NAME_COMMON_CONVENTION only.
869
 *
870
 * @return bool The result TRUE means that the order is first_name last_name,
871
 *              FALSE means last_name first_name.
872
 *              Note: You may use this function for determining the order of the fields or
873
 *              columns "First name" and "Last name" in forms, tables and reports.
874
 *
875
 * @author Ivan Tcholakov
876
 */
877
function api_is_western_name_order($format = null, $language = null)
878
{
879
    static $order = [];
880
    if (empty($format)) {
881
        $format = PERSON_NAME_COMMON_CONVENTION;
882
    }
883
884
    if (empty($language)) {
885
        $language = api_get_language_isocode();
886
    }
887
    if (!isset($order[$format][$language])) {
888
        $test_name = api_get_person_name('%f', '%l', '%t', $format, $language);
889
        $order[$format][$language] = stripos($test_name, '%f') <= stripos($test_name, '%l');
890
    }
891
892
    return $order[$format][$language];
893
}
894
895
/**
896
 * Returns a directive for sorting person names depending on a given language
897
 * and based on the options in the internationalization "database".
898
 *
899
 * @param string $language (optional) The input language.
900
 *                         If it is omitted, the current interface language is assumed.
901
 *
902
 * @return bool Returns boolean value. TRUE means ORDER BY first_name, last_name
903
 *              FALSE means ORDER BY last_name, first_name.
904
 *              Note: You may use this function:
905
 *              2. for constructing the ORDER clause of SQL queries, related to first_name and last_name;
906
 *              3. for adjusting php-implemented sorting in tables and reports.
907
 *
908
 * @author Ivan Tcholakov
909
 */
910
function api_sort_by_first_name($language = null)
911
{
912
    static $sort_by_first_name = [];
913
914
    if (empty($language)) {
915
        $language = api_get_language_isocode();
916
    }
917
    if (!isset($sort_by_first_name[$language])) {
918
        $sort_by_first_name[$language] = _api_get_person_name_convention($language, 'sort_by');
919
    }
920
921
    return $sort_by_first_name[$language];
922
}
923
924
/**
925
 * Multibyte string conversion functions.
926
 */
927
928
/**
929
 * Converts character encoding of a given string.
930
 *
931
 * @param string $string        the string being converted
932
 * @param string $to_encoding   the encoding that $string is being converted to
933
 * @param string $from_encoding (optional)    The encoding that $string is being converted from.
934
 *                              If it is omitted, the platform character set is assumed.
935
 *
936
 * @return string Returns the converted string.
937
 *                This function is aimed at replacing the function mb_convert_encoding() for human-language strings.
938
 *
939
 * @see http://php.net/manual/en/function.mb-convert-encoding
940
 */
941
function api_convert_encoding($string, $to_encoding, $from_encoding = 'UTF-8')
942
{
943
    if (strtoupper($to_encoding) === strtoupper($from_encoding)) {
944
        return $string;
945
    }
946
947
    return mb_convert_encoding($string, $to_encoding, $from_encoding);
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_convert_encodi...coding, $from_encoding) also could return the type array which is incompatible with the documented return type string.
Loading history...
948
}
949
950
/**
951
 * Converts a given string into UTF-8 encoded string.
952
 *
953
 * @param string $string        the string being converted
954
 * @param string $from_encoding (optional) The encoding that $string is being converted from.
955
 *                              If it is omitted, the platform character set is assumed.
956
 *
957
 * @return string Returns the converted string.
958
 *                This function is aimed at replacing the function utf8_encode() for human-language strings.
959
 *
960
 * @see http://php.net/manual/en/function.utf8-encode
961
 */
962
function api_utf8_encode($string, $from_encoding = 'UTF-8')
963
{
964
    return mb_convert_encoding($string, 'UTF-8', $from_encoding);
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_convert_encodi...UTF-8', $from_encoding) also could return the type array which is incompatible with the documented return type string.
Loading history...
965
}
966
967
/**
968
 * Converts a given string from UTF-8 encoding to a specified encoding.
969
 *
970
 * @param string $string     the string being converted
971
 * @param string $toEncoding (optional)    The encoding that $string is being converted to.
972
 *                           If it is omitted, the platform character set is assumed.
973
 *
974
 * @return string Returns the converted string.
975
 *                This function is aimed at replacing the function utf8_decode() for human-language strings.
976
 *
977
 * @see http://php.net/manual/en/function.utf8-decode
978
 */
979
function api_utf8_decode($string, $toEncoding = null)
980
{
981
    if (null === $toEncoding) {
982
        return $string;
983
    }
984
985
    return mb_convert_encoding($string, $toEncoding, 'UTF-8');
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_convert_encodi..., $toEncoding, 'UTF-8') also could return the type array which is incompatible with the documented return type string.
Loading history...
986
}
987
988
/**
989
 * Converts a given string into the system encoding (or platform character set).
990
 * When $from encoding is omitted on UTF-8 platforms then language dependent encoding
991
 * is guessed/assumed. On non-UTF-8 platforms omitted $from encoding is assumed as UTF-8.
992
 * When the parameter $check_utf8_validity is true the function checks string's
993
 * UTF-8 validity and decides whether to try to convert it or not.
994
 * This function is useful for problem detection or making workarounds.
995
 *
996
 * @param string $string              the string being converted
997
 * @param string $from_encoding       (optional) The encoding that $string is being converted from.
998
 *                                    It is guessed when it is omitted.
999
 * @param bool   $check_utf8_validity (optional)    A flag for UTF-8 validity check as condition for making conversion
1000
 *
1001
 * @return string returns the converted string
1002
 */
1003
function api_to_system_encoding($string, $from_encoding = null, $check_utf8_validity = false)
1004
{
1005
    $system_encoding = api_get_system_encoding();
1006
1007
    return api_convert_encoding($string, $system_encoding, $from_encoding);
1008
}
1009
1010
/**
1011
 * Converts all applicable characters to HTML entities.
1012
 *
1013
 * @param string $string      the input string
1014
 * @param int    $quote_style (optional)    The quote style - ENT_COMPAT (default), ENT_QUOTES, ENT_NOQUOTES
1015
 *
1016
 * @return string Returns the converted string.
1017
 *                This function is aimed at replacing the function htmlentities() for human-language strings.
1018
 *
1019
 * @see http://php.net/manual/en/function.htmlentities
1020
 */
1021
function api_htmlentities($string, $quote_style = ENT_COMPAT)
1022
{
1023
    switch ($quote_style) {
1024
        case ENT_COMPAT:
1025
            $string = str_replace(['&', '"', '<', '>'], ['&amp;', '&quot;', '&lt;', '&gt;'], $string);
1026
            break;
1027
        case ENT_QUOTES:
1028
            $string = str_replace(['&', '\'', '"', '<', '>'], ['&amp;', '&#039;', '&quot;', '&lt;', '&gt;'], $string);
1029
            break;
1030
    }
1031
1032
    return mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_convert_encodi...TML-ENTITIES', 'UTF-8') also could return the type array which is incompatible with the documented return type string.
Loading history...
1033
}
1034
1035
/**
1036
 * Converts HTML entities into normal characters.
1037
 *
1038
 * @param string $string      the input string
1039
 * @param int    $quote_style (optional)    The quote style - ENT_COMPAT (default), ENT_QUOTES, ENT_NOQUOTES
1040
 * @param string $encoding    (optional)    The encoding (of the result) used in conversion.
1041
 *                            If it is omitted, the platform character set is assumed.
1042
 *
1043
 * @return string Returns the converted string.
1044
 *                This function is aimed at replacing the function html_entity_decode() for human-language strings.
1045
 *
1046
 * @see http://php.net/html_entity_decode
1047
 */
1048
function api_html_entity_decode($string, $quoteStyle = ENT_COMPAT, $encoding = 'UTF-8')
1049
{
1050
    return html_entity_decode($string, $quoteStyle, $encoding);
1051
1052
    /*if (empty($encoding)) {
1053
        $encoding = _api_mb_internal_encoding();
1054
    }
1055
    if (api_is_encoding_supported($encoding)) {
1056
        if (!api_is_utf8($encoding)) {
1057
            $string = api_utf8_encode($string, $encoding);
1058
        }
1059
        $string = html_entity_decode($string, $quote_style, 'UTF-8');
1060
        if (!api_is_utf8($encoding)) {
1061
            return api_utf8_decode($string, $encoding);
1062
        }
1063
1064
        return $string;
1065
    }
1066
1067
    return $string; // Here the function gives up.*/
1068
}
1069
1070
/**
1071
 * This function encodes (conditionally) a given string to UTF-8 if XmlHttp-request has been detected.
1072
 *
1073
 * @param string $string        the string being converted
1074
 * @param string $from_encoding (optional)    The encoding that $string is being converted from.
1075
 *                              If it is omitted, the platform character set is assumed.
1076
 *
1077
 * @return string returns the converted string
1078
 */
1079
function api_xml_http_response_encode($string, $from_encoding = 'UTF8')
1080
{
1081
    if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 'xmlhttprequest' == strtolower($_SERVER['HTTP_X_REQUESTED_WITH'])) {
1082
        if (empty($from_encoding)) {
1083
            $from_encoding = _api_mb_internal_encoding();
1084
        }
1085
        /*if (!api_is_utf8($from_encoding)) {
1086
            return api_utf8_encode($string, $from_encoding);
1087
        }*/
1088
    }
1089
1090
    return $string;
1091
}
1092
1093
/**
1094
 * Transliterates a string with arbitrary encoding into a plain ASCII string.
1095
 *
1096
 * Example:
1097
 * echo api_transliterate(api_html_entity_decode(
1098
 *    '&#1060;&#1105;&#1076;&#1086;&#1088; '.
1099
 *    '&#1052;&#1080;&#1093;&#1072;&#1081;&#1083;&#1086;&#1074;&#1080;&#1095; '.
1100
 *    '&#1044;&#1086;&#1089;&#1090;&#1086;&#1077;&#1074;&#1082;&#1080;&#1081;',
1101
 *    ENT_QUOTES, 'UTF-8'), 'X', 'UTF-8');
1102
 * The output should be: Fyodor Mihaylovich Dostoevkiy
1103
 *
1104
 * @param string $string        the input string
1105
 * @param string $unknown       (optional) Replacement character for unknown characters and illegal UTF-8 sequences
1106
 * @param string $from_encoding (optional) The encoding of the input string.
1107
 *                              If it is omitted, the platform character set is assumed.
1108
 *
1109
 * @return string plain ASCII output
1110
 */
1111
function api_transliterate($string, $unknown = '?', $from_encoding = null)
1112
{
1113
    return URLify::transliterate($string);
1114
}
1115
1116
/**
1117
 * Takes the first character in a string and returns its Unicode codepoint.
1118
 *
1119
 * @param string $character the input string
1120
 * @param string $encoding  (optional) The encoding of the input string.
1121
 *                          If it is omitted, the platform character set will be used by default.
1122
 *
1123
 * @return int Returns: the codepoint of the first character; or 0xFFFD (unknown character) when the input string is
1124
 *             empty. This is a multibyte aware version of the function ord().
1125
 *
1126
 * @see http://php.net/manual/en/function.ord.php
1127
 * Note the difference with the original funtion ord(): ord('') returns 0, api_ord('') returns 0xFFFD (unknown
1128
 * character).
1129
 */
1130
function api_ord($character, $encoding = 'UTF-8')
1131
{
1132
    return ord(api_utf8_encode($character, $encoding));
1133
}
1134
1135
/**
1136
 * This function returns a string or an array with all occurrences of search
1137
 * in subject (ignoring case) replaced with the given replace value.
1138
 *
1139
 * @param mixed  $search   string or array of strings to be found
1140
 * @param mixed  $replace  string or array of strings used for replacement
1141
 * @param mixed  $subject  string or array of strings being searched
1142
 * @param int    $count    (optional) The number of matched and replaced needles
1143
 *                         will be returned in count, which is passed by reference
1144
 * @param string $encoding (optional) The used internally by this function character encoding.
1145
 *                         If it is omitted, the platform character set will be used by default.
1146
 *
1147
 * @return mixed String or array as a result.
1148
 *               Notes:
1149
 *               If $subject is an array, then the search and replace is performed with
1150
 *               every entry of subject, the return value is an array.
1151
 *               If $search and $replace are arrays, then the function takes a value from
1152
 *               each array and uses it to do search and replace on subject.
1153
 *               If $replace has fewer values than search, then an empty string is used for the rest of replacement
1154
 *               values. If $search is an array and $replace is a string, then this replacement string is used for
1155
 *               every value of search. This function is aimed at replacing the function str_ireplace() for
1156
 *               human-language strings.
1157
 *
1158
 * @see    http://php.net/manual/en/function.str-ireplace
1159
 *
1160
 * @author Henri Sivonen, mailto:[email protected]
1161
 *
1162
 * @see    http://hsivonen.iki.fi/php-utf8/
1163
 * Adaptation for Chamilo 1.8.7, 2010
1164
 * Initial implementation Dokeos LMS, August 2009
1165
 *
1166
 * @author Ivan Tcholakov
1167
 */
1168
function api_str_ireplace($search, $replace, $subject, &$count = null, $encoding = null)
1169
{
1170
    return str_ireplace($search, $replace, $subject, $count);
1171
}
1172
1173
/**
1174
 * Converts a string to an array.
1175
 *
1176
 * @param string $string       the input string
1177
 * @param int    $split_length maximum character-length of the chunk, one character by default
1178
 * @param string $encoding     (optional) The used internally by this function
1179
 *                             character encoding. If it is omitted, the platform character set will be used by
1180
 *                             default.
1181
 *
1182
 * @return array The result array of chunks with the spcified length.
1183
 *               Notes:
1184
 *               If the optional split_length parameter is specified, the returned array will be broken down into
1185
 *               chunks
1186
 *               with each being split_length in length, otherwise each chunk will be one character in length.
1187
 *               FALSE is returned if split_length is less than 1.
1188
 *               If the split_length length exceeds the length of string, the entire string is returned as the first
1189
 *               (and only) array element. This function is aimed at replacing the function str_split() for
1190
 *               human-language strings.
1191
 *
1192
 * @see http://php.net/str_split
1193
 */
1194
function api_str_split($string, $split_length = 1, $encoding = null)
1195
{
1196
    return str_split($string, $split_length);
0 ignored issues
show
Bug Best Practice introduced by
The expression return str_split($string, $split_length) also could return the type true which is incompatible with the documented return type array.
Loading history...
1197
}
1198
1199
/**
1200
 * Finds position of first occurrence of a string within another, case insensitive.
1201
 *
1202
 * @param string $haystack the string from which to get the position of the first occurrence
1203
 * @param string $needle   the string to be found
1204
 * @param int    $offset   The position in $haystack to start searching from.
1205
 *                         If it is omitted, searching starts from the beginning.
1206
 * @param string $encoding (optional) The used internally by this function
1207
 *                         character encoding. If it is omitted, the platform character set will be used by default.
1208
 *
1209
 * @return mixed Returns the numeric position of the first occurrence of
1210
 *               $needle in the $haystack, or FALSE if $needle is not found.
1211
 *               Note: The first character's position is 0, the second character position is 1, and so on.
1212
 *               This function is aimed at replacing the functions stripos() and mb_stripos() for human-language
1213
 *               strings.
1214
 *
1215
 * @see http://php.net/manual/en/function.stripos
1216
 * @see http://php.net/manual/en/function.mb-stripos
1217
 */
1218
function api_stripos($haystack, $needle, $offset = 0, $encoding = null)
1219
{
1220
    return stripos($haystack, $needle, $offset);
1221
}
1222
1223
/**
1224
 * Finds first occurrence of a string within another, case insensitive.
1225
 *
1226
 * @param string $haystack      the string from which to get the first occurrence
1227
 * @param mixed  $needle        the string to be found
1228
 * @param bool   $before_needle (optional) Determines which portion of $haystack
1229
 *                              this function returns. The default value is FALSE.
1230
 * @param string $encoding      (optional) The used internally by this function
1231
 *                              character encoding. If it is omitted, the platform character set will be used by
1232
 *                              default.
1233
 *
1234
 * @return mixed Returns the portion of $haystack, or FALSE if $needle is not found.
1235
 *               Notes:
1236
 *               If $needle is not a string, it is converted to an integer and applied as the
1237
 *               ordinal value (codepoint if the encoding is UTF-8) of a character.
1238
 *               If $before_needle is set to TRUE, the function returns all of $haystack
1239
 *               from the beginning to the first occurrence of $needle.
1240
 *               If $before_needle is set to FALSE, the function returns all of $haystack f
1241
 *               rom the first occurrence of $needle to the end.
1242
 *               This function is aimed at replacing the functions stristr() and mb_stristr() for human-language
1243
 *               strings.
1244
 *
1245
 * @see http://php.net/manual/en/function.stristr
1246
 * @see http://php.net/manual/en/function.mb-stristr
1247
 */
1248
function api_stristr($haystack, $needle, $before_needle = false, $encoding = null)
1249
{
1250
    return stristr($haystack, $needle, $before_needle);
1251
}
1252
1253
/**
1254
 * Returns length of the input string.
1255
 *
1256
 * @param string $string   the string which length is to be calculated
1257
 * @param string $encoding (optional) The used internally by this function character encoding. If it is omitted, the
1258
 *                         platform character set will be used by default.
1259
 *
1260
 * @return int Returns the number of characters within the string. A multi-byte character is counted as 1.
1261
 *             This function is aimed at replacing the functions strlen() and mb_strlen() for human-language strings.
1262
 *
1263
 * @see http://php.net/manual/en/function.strlen
1264
 * @see http://php.net/manual/en/function.mb-strlen
1265
 * Note: When you use strlen() to test for an empty string, you needn't change it to api_strlen().
1266
 * For example, in lines like the following:
1267
 * if (strlen($string) > 0)
1268
 * if (strlen($string) != 0)
1269
 * there is no need the original function strlen() to be changed, it works correctly and faster for these cases.
1270
 */
1271
function api_strlen($string, $encoding = null)
1272
{
1273
    return strlen($string);
1274
}
1275
1276
/**
1277
 * Finds position of first occurrence of a string within another.
1278
 *
1279
 * @param string $haystack the string from which to get the position of the first occurrence
1280
 * @param string $needle   the string to be found
1281
 * @param int    $offset   (optional) The position in $haystack to start searching from. If it is omitted, searching
1282
 *                         starts from the beginning.
1283
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted, the
1284
 *                         platform character set will be used by default.
1285
 *
1286
 * @return mixed Returns the numeric position of the first occurrence of $needle in the $haystack, or FALSE if $needle
1287
 *               is not found. Note: The first character's position is 0, the second character position is 1, and so
1288
 *               on. This function is aimed at replacing the functions strpos() and mb_strpos() for human-language
1289
 *               strings.
1290
 *
1291
 * @see http://php.net/manual/en/function.strpos
1292
 * @see http://php.net/manual/en/function.mb-strpos
1293
 */
1294
function api_strpos($haystack, $needle, $offset = 0, $encoding = null)
1295
{
1296
    return strpos($haystack, $needle, $offset);
1297
}
1298
1299
/**
1300
 * Finds the last occurrence of a character in a string.
1301
 *
1302
 * @param string $haystack      the string from which to get the last occurrence
1303
 * @param mixed  $needle        the string which first character is to be found
1304
 * @param bool   $before_needle (optional) Determines which portion of $haystack this function returns. The default
1305
 *                              value is FALSE.
1306
 * @param string $encoding      (optional) The used internally by this function character encoding. If it is omitted,
1307
 *                              the platform character set will be used by default.
1308
 *
1309
 * @return mixed Returns the portion of $haystack, or FALSE if the first character from $needle is not found.
1310
 *               Notes:
1311
 *               If $needle is not a string, it is converted to an integer and applied as the ordinal value (codepoint
1312
 *               if the encoding is UTF-8) of a character. If $before_needle is set to TRUE, the function returns all
1313
 *               of $haystack from the beginning to the first occurrence. If $before_needle is set to FALSE, the
1314
 *               function returns all of $haystack from the first occurrence to the end. This function is aimed at
1315
 *               replacing the functions strrchr() and mb_strrchr() for human-language strings.
1316
 *
1317
 * @see http://php.net/manual/en/function.strrchr
1318
 * @see http://php.net/manual/en/function.mb-strrchr
1319
 */
1320
function api_strrchr($haystack, $needle, $before_needle = false, $encoding = null)
1321
{
1322
    return strrchr($haystack, $needle);
1323
}
1324
1325
/**
1326
 * Finds the position of last occurrence (case insensitive) of a string in a string.
1327
 *
1328
 * @param string $haystack the string from which to get the position of the last occurrence
1329
 * @param string $needle   the string to be found
1330
 * @param int    $offset   (optional) $offset may be specified to begin searching an arbitrary position. Negative
1331
 *                         values will stop searching at an arbitrary point prior to the end of the string.
1332
 * @param string $encoding (optional) The used internally by this function character encoding. If it is omitted, the
1333
 *                         platform character set will be used by default.
1334
 *
1335
 * @return mixed Returns the numeric position of the first occurrence (case insensitive) of $needle in the $haystack,
1336
 *               or FALSE if $needle is not found. Note: The first character's position is 0, the second character
1337
 *               position is 1, and so on. This function is aimed at replacing the functions strripos() and
1338
 *               mb_strripos() for human-language strings.
1339
 *
1340
 * @see http://php.net/manual/en/function.strripos
1341
 * @see http://php.net/manual/en/function.mb-strripos
1342
 */
1343
function api_strripos($haystack, $needle, $offset = 0, $encoding = null)
1344
{
1345
    return strripos($haystack, $needle, $offset);
1346
}
1347
1348
/**
1349
 * Finds the position of last occurrence of a string in a string.
1350
 *
1351
 * @param string $haystack the string from which to get the position of the last occurrence
1352
 * @param string $needle   the string to be found
1353
 * @param int    $offset   (optional) $offset may be specified to begin searching an arbitrary position. Negative
1354
 *                         values will stop searching at an arbitrary point prior to the end of the string.
1355
 * @param string $encoding (optional) The used internally by this function character encoding. If it is omitted, the
1356
 *                         platform character set will be used by default.
1357
 *
1358
 * @return mixed Returns the numeric position of the first occurrence of $needle in the $haystack, or FALSE if $needle
1359
 *               is not found. Note: The first character's position is 0, the second character position is 1, and so
1360
 *               on. This function is aimed at replacing the functions strrpos() and mb_strrpos() for human-language
1361
 *               strings.
1362
 *
1363
 * @see http://php.net/manual/en/function.strrpos
1364
 * @see http://php.net/manual/en/function.mb-strrpos
1365
 */
1366
function api_strrpos($haystack, $needle, $offset = 0, $encoding = null)
1367
{
1368
    return strrpos($haystack, $needle, $offset);
1369
}
1370
1371
/**
1372
 * Finds first occurrence of a string within another.
1373
 *
1374
 * @param string $haystack      the string from which to get the first occurrence
1375
 * @param mixed  $needle        the string to be found
1376
 * @param bool   $before_needle (optional) Determines which portion of $haystack this function returns. The default
1377
 *                              value is FALSE.
1378
 * @param string $encoding      (optional) The used internally by this function character encoding. If it is omitted,
1379
 *                              the platform character set will be used by default.
1380
 *
1381
 * @return mixed Returns the portion of $haystack, or FALSE if $needle is not found.
1382
 *               Notes:
1383
 *               If $needle is not a string, it is converted to an integer and applied as the ordinal value (codepoint
1384
 *               if the encoding is UTF-8) of a character. If $before_needle is set to TRUE, the function returns all
1385
 *               of $haystack from the beginning to the first occurrence of $needle. If $before_needle is set to FALSE,
1386
 *               the function returns all of $haystack from the first occurrence of $needle to the end. This function
1387
 *               is aimed at replacing the functions strstr() and mb_strstr() for human-language strings.
1388
 *
1389
 * @see http://php.net/manual/en/function.strstr
1390
 * @see http://php.net/manual/en/function.mb-strstr
1391
 */
1392
function api_strstr($haystack, $needle, $before_needle = false, $encoding = null)
1393
{
1394
    return strstr($haystack, $needle, $before_needle);
1395
}
1396
1397
/**
1398
 * Makes a string lowercase.
1399
 *
1400
 * @param string $string   the string being lowercased
1401
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted, the
1402
 *                         platform character set will be used by default.
1403
 *
1404
 * @return string Returns the string with all alphabetic characters converted to lowercase.
1405
 *                This function is aimed at replacing the functions strtolower() and mb_strtolower() for human-language
1406
 *                strings.
1407
 *
1408
 * @see http://php.net/manual/en/function.strtolower
1409
 * @see http://php.net/manual/en/function.mb-strtolower
1410
 */
1411
function api_strtolower($string, $encoding = null)
1412
{
1413
    return strtolower($string);
1414
}
1415
1416
/**
1417
 * Makes a string uppercase.
1418
 *
1419
 * @param string $string   the string being uppercased
1420
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted, the
1421
 *                         platform character set will be used by default.
1422
 *
1423
 * @return string Returns the string with all alphabetic characters converted to uppercase.
1424
 *                This function is aimed at replacing the functions strtoupper() and mb_strtoupper() for human-language
1425
 *                strings.
1426
 *
1427
 * @see http://php.net/manual/en/function.strtoupper
1428
 * @see http://php.net/manual/en/function.mb-strtoupper
1429
 */
1430
function api_strtoupper(?string $string, $encoding = null)
1431
{
1432
    return strtoupper((string) $string);
1433
}
1434
1435
/**
1436
 * // Gets part of a string.
1437
 *
1438
 * @param string $string   the input string
1439
 * @param int    $start    the first position from which the extracted part begins
1440
 * @param int    $length   the length in character of the extracted part
1441
 * @param string $encoding (optional) The used internally by this function
1442
 *                         character encoding. If it is omitted, the platform character set will be used by default.
1443
 *
1444
 * @return string Returns the part of the string specified by the start and length parameters.
1445
 *                Note: First character's position is 0. Second character position is 1, and so on.
1446
 *                This function is aimed at replacing the functions substr() and mb_substr() for human-language strings.
1447
 *
1448
 * @see http://php.net/manual/en/function.substr
1449
 * @see http://php.net/manual/en/function.mb-substr
1450
 */
1451
function api_substr($string, $start, $length = null, $encoding = null)
1452
{
1453
    if (is_null($length)) {
1454
        $length = api_strlen($string, $encoding);
1455
    }
1456
1457
    return substr($string, $start, $length);
1458
}
1459
1460
/**
1461
 * Counts the number of substring occurrences.
1462
 *
1463
 * @param string $haystack the string being checked
1464
 * @param string $needle   the string being found
1465
 * @param string $encoding (optional) The used internally by this function character encoding.
1466
 *                         If it is omitted, the platform character set will be used by default.
1467
 *
1468
 * @return int the number of times the needle substring occurs in the haystack string
1469
 *
1470
 * @see http://php.net/manual/en/function.mb-substr-count.php
1471
 */
1472
function api_substr_count($haystack, $needle, $encoding = null)
1473
{
1474
    return substr_count($haystack, $needle);
1475
}
1476
1477
/**
1478
 * Replaces text within a portion of a string.
1479
 *
1480
 * @param string $string      the input string
1481
 * @param string $replacement the replacement string
1482
 * @param int    $start       The position from which replacing will begin.
1483
 *                            Notes:
1484
 *                            If $start is positive, the replacing will begin at the $start'th offset into the string.
1485
 *                            If $start is negative, the replacing will begin at the $start'th character from the end
1486
 *                            of the string.
1487
 * @param int    $length      (optional) The position where replacing will end.
1488
 *                            Notes:
1489
 *                            If given and is positive, it represents the length of the portion of the string which is
1490
 *                            to be replaced. If it is negative, it represents the number of characters from the end of
1491
 *                            string at which to stop replacing. If it is not given, then it will default to
1492
 *                            api_strlen($string); i.e. end the replacing at the end of string. If $length is zero,
1493
 *                            then this function will have the effect of inserting replacement into the string at the
1494
 *                            given start offset.
1495
 * @param string $encoding    (optional)    The used internally by this function character encoding.
1496
 *                            If it is omitted, the platform character set will be used by default.
1497
 *
1498
 * @return string The result string is returned.
1499
 *                This function is aimed at replacing the function substr_replace() for human-language strings.
1500
 *
1501
 * @see http://php.net/manual/function.substr-replace
1502
 */
1503
function api_substr_replace($string, $replacement, $start, $length = null, $encoding = null)
1504
{
1505
    if (is_null($length)) {
1506
        $length = api_strlen($string);
1507
    }
1508
1509
    return substr_replace($string, $replacement, $start, $length);
0 ignored issues
show
Bug Best Practice introduced by
The expression return substr_replace($s...ement, $start, $length) also could return the type array which is incompatible with the documented return type string.
Loading history...
1510
}
1511
1512
/**
1513
 * Makes a string's first character uppercase.
1514
 *
1515
 * @param string $string   the input string
1516
 * @param string $encoding (optional)    The used internally by this function character encoding.
1517
 *                         If it is omitted, the platform character set will be used by default.
1518
 *
1519
 * @return string Returns a string with the first character capitalized, if that character is alphabetic.
1520
 *                This function is aimed at replacing the function ucfirst() for human-language strings.
1521
 *
1522
 * @see http://php.net/manual/en/function.ucfirst
1523
 */
1524
function api_ucfirst($string, $encoding = null)
1525
{
1526
    return ucfirst($string);
1527
}
1528
1529
/**
1530
 * Uppercases the first character of each word in a string.
1531
 *
1532
 * @param string $string   the input string
1533
 * @param string $encoding (optional) The used internally by this function character encoding.
1534
 *                         If it is omitted, the platform character set will be used by default.
1535
 *
1536
 * @return string Returns the modified string.
1537
 *                This function is aimed at replacing the function ucwords() for human-language strings.
1538
 *
1539
 * @see http://php.net/manual/en/function.ucwords
1540
 */
1541
function api_ucwords($string, $encoding = null)
1542
{
1543
    return ucwords($string);
1544
}
1545
1546
/**
1547
 * Performs a regular expression match, UTF-8 aware when it is applicable.
1548
 *
1549
 * @param string $pattern  the pattern to search for, as a string
1550
 * @param string $subject  the input string
1551
 * @param array  &$matches (optional) If matches is provided,
1552
 *                         then it is filled with the results of search (as an array).
1553
 *                         $matches[0] will contain the text that matched the full pattern, $matches[1] will have the
1554
 *                         text that matched the first captured parenthesized subpattern, and so on.
1555
 * @param int    $flags    (optional) Could be PREG_OFFSET_CAPTURE. If this flag is passed, for every occurring match
1556
 *                         the appendant string offset will also be returned. Note that this changes the return value
1557
 *                         in an array where every element is an array consisting of the matched string at index 0 and
1558
 *                         its string offset into subject at index 1.
1559
 * @param int    $offset   (optional)        Normally, the search starts from the beginning of the subject string. The
1560
 *                         optional parameter offset can be used to specify the alternate place from which to start
1561
 *                         the search.
1562
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted,
1563
 *                         the platform character set will be used by default.
1564
 *
1565
 * @return int|bool returns the number of times pattern matches or FALSE if an error occurred
1566
 *
1567
 * @see http://php.net/preg_match
1568
 */
1569
function api_preg_match(
1570
    $pattern,
1571
    $subject,
1572
    &$matches = null,
1573
    $flags = 0,
1574
    $offset = 0,
1575
    $encoding = null
1576
) {
1577
    return preg_match($pattern.'u', $subject, $matches, $flags, $offset);
1578
}
1579
1580
/**
1581
 * Performs a global regular expression match, UTF-8 aware when it is applicable.
1582
 *
1583
 * @param string $pattern  the pattern to search for, as a string
1584
 * @param string $subject  the input string
1585
 * @param array  &$matches (optional)    Array of all matches in multi-dimensional array ordered according to $flags
1586
 * @param int    $flags    (optional)            Can be a combination of the following flags (note that it doesn't
1587
 *                         make sense to use PREG_PATTERN_ORDER together with PREG_SET_ORDER): PREG_PATTERN_ORDER -
1588
 *                         orders results so that $matches[0] is an array of full pattern matches, $matches[1] is an
1589
 *                         array of strings matched by the first parenthesized subpattern, and so on; PREG_SET_ORDER -
1590
 *                         orders results so that $matches[0] is an array of first set of matches, $matches[1] is an
1591
 *                         array of second set of matches, and so on; PREG_OFFSET_CAPTURE - If this flag is passed,
1592
 *                         for every occurring match the appendant string offset will also be returned. Note that this
1593
 *                         changes the value of matches in an array where every element is an array consisting of the
1594
 *                         matched string at offset 0 and its string offset into subject at offset 1. If no order flag
1595
 *                         is given, PREG_PATTERN_ORDER is assumed.
1596
 * @param int    $offset   (optional)        Normally, the search starts from the beginning of the subject string. The
1597
 *                         optional parameter offset can be used to specify the alternate place from which to start
1598
 *                         the search.
1599
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted,
1600
 *                         the platform character set will be used by default.
1601
 *
1602
 * @return int|bool returns the number of full pattern matches (which might be zero), or FALSE if an error occurred
1603
 *
1604
 * @see http://php.net/preg_match_all
1605
 */
1606
function api_preg_match_all($pattern, $subject, &$matches, $flags = PREG_PATTERN_ORDER, $offset = 0, $encoding = null)
1607
{
1608
    if (empty($encoding)) {
1609
        $encoding = _api_mb_internal_encoding();
1610
    }
1611
    if (is_null($flags)) {
1612
        $flags = PREG_PATTERN_ORDER;
1613
    }
1614
1615
    return preg_match_all($pattern.'u', $subject, $matches, $flags, $offset);
1616
1617
    //return preg_match_all(api_is_utf8($encoding) ? $pattern.'u' : $pattern, $subject, $matches, $flags, $offset);
1618
}
1619
1620
/**
1621
 * Performs a regular expression search and replace, UTF-8 aware when it is applicable.
1622
 *
1623
 * @param string|array $pattern     The pattern to search for. It can be either a string or an array with strings.
1624
 * @param string|array $replacement the string or an array with strings to replace
1625
 * @param string|array $subject     the string or an array with strings to search and replace
1626
 * @param int          $limit       The maximum possible replacements for each pattern in each subject string.
1627
 *                                  Defaults to -1 (no limit).
1628
 * @param int          &$count      If specified, this variable will be filled with the number of replacements done
1629
 * @param string       $encoding    (optional)    The used internally by this function character encoding.
1630
 *                                  If it is omitted, the platform character set will be used by default.
1631
 *
1632
 * @return array|string|null returns an array if the subject parameter is an array, or a string otherwise.
1633
 *                           If matches are found, the new subject will be returned, otherwise subject will be returned
1634
 *                           unchanged or NULL if an error occurred.
1635
 *
1636
 * @see http://php.net/preg_replace
1637
 */
1638
function api_preg_replace($pattern, $replacement, $subject, $limit = -1, $count = 0, $encoding = null)
1639
{
1640
    /*if (empty($encoding)) {
1641
        $encoding = _api_mb_internal_encoding();
1642
    }
1643
    $is_utf8 = api_is_utf8($encoding);
1644
    if (is_array($pattern)) {
1645
        foreach ($pattern as &$p) {
1646
            $p = $is_utf8 ? $p.'u' : $p;
1647
        }
1648
    } else {
1649
        $pattern = $is_utf8 ? $pattern.'u' : $pattern;
1650
    }*/
1651
    $pattern = $pattern.'u';
0 ignored issues
show
Bug introduced by
Are you sure $pattern of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1651
    $pattern = /** @scrutinizer ignore-type */ $pattern.'u';
Loading history...
1652
1653
    return preg_replace($pattern, $replacement, $subject, $limit, $count);
1654
}
1655
1656
/**
1657
 * Splits a string by a regular expression, UTF-8 aware when it is applicable.
1658
 *
1659
 * @param string $pattern  the pattern to search for, as a string
1660
 * @param string $subject  the input string
1661
 * @param int    $limit    (optional)            If specified, then only substrings up to $limit are returned with the
1662
 *                         rest of the string being placed in the last substring. A limit of -1, 0 or null means "no
1663
 *                         limit" and, as is standard across PHP.
1664
 * @param int    $flags    (optional)            $flags can be any combination of the following flags (combined with
1665
 *                         bitwise | operator): PREG_SPLIT_NO_EMPTY - if this flag is set, only non-empty pieces will
1666
 *                         be returned; PREG_SPLIT_DELIM_CAPTURE - if this flag is set, parenthesized expression in the
1667
 *                         delimiter pattern will be captured and returned as well; PREG_SPLIT_OFFSET_CAPTURE - If this
1668
 *                         flag is set, for every occurring match the appendant string offset will also be returned.
1669
 *                         Note that this changes the return value in an array where every element is an array
1670
 *                         consisting of the matched string at offset 0 and its string offset into subject at offset 1.
1671
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted, the
1672
 *                         platform character set will be used by default.
1673
 *
1674
 * @return array returns an array containing substrings of $subject split along boundaries matched by $pattern
1675
 *
1676
 * @see http://php.net/preg_split
1677
 */
1678
function api_preg_split($pattern, $subject, $limit = -1, $flags = 0)
1679
{
1680
    return preg_split($pattern.'u', $subject, $limit, $flags);
1681
}
1682
1683
/**
1684
 * String comparison.
1685
 */
1686
1687
/**
1688
 * Performs string comparison, case insensitive, language sensitive, with extended multibyte support.
1689
 *
1690
 * @param string $string1  the first string
1691
 * @param string $string2  the second string
1692
 * @param string $language (optional) The language in which comparison is to be made. If language is omitted, interface
1693
 *                         language is assumed then.
1694
 * @param string $encoding (optional) The used internally by this function character encoding. If it is omitted, the
1695
 *                         platform character set will be used by default.
1696
 *
1697
 * @return int Returns < 0 if $string1 is less than $string2; > 0 if $string1 is greater than $string2; and 0 if the
1698
 *             strings are equal. This function is aimed at replacing the function strcasecmp() for human-language
1699
 *             strings.
1700
 *
1701
 * @see http://php.net/manual/en/function.strcasecmp
1702
 */
1703
function api_strcasecmp($string1, $string2, $language = null, $encoding = null)
1704
{
1705
    return api_strcmp(api_strtolower($string1, $encoding), api_strtolower($string2, $encoding), $language, $encoding);
1706
}
1707
1708
/**
1709
 * Performs string comparison, case sensitive, language sensitive, with extended multibyte support.
1710
 *
1711
 * @param string $string1  the first string
1712
 * @param string $string2  the second string
1713
 * @param string $language (optional)    The language in which comparison is to be made. If language is omitted,
1714
 *                         interface language is assumed then.
1715
 * @param string $encoding (optional)    The used internally by this function character encoding.
1716
 *                         If it is omitted, the platform character set will be used by default.
1717
 *
1718
 * @return int Returns < 0 if $string1 is less than $string2; > 0 if $string1 is greater than $string2; and 0 if the
1719
 *             strings are equal. This function is aimed at replacing the function strcmp() for human-language strings.
1720
 *
1721
 * @see http://php.net/manual/en/function.strcmp.php
1722
 * @see http://php.net/manual/en/collator.compare.php
1723
 */
1724
function api_strcmp($string1, $string2, $language = null, $encoding = null)
1725
{
1726
    return strcmp($string1, $string2);
1727
}
1728
1729
/**
1730
 * Performs string comparison in so called "natural order", case sensitive, language sensitive, with extended multibyte
1731
 * support.
1732
 *
1733
 * @param string $string1  the first string
1734
 * @param string $string2  the second string
1735
 * @param string $language (optional)    The language in which comparison is to be made. If language is omitted,
1736
 *                         interface language is assumed then.
1737
 * @param string $encoding (optional)    The used internally by this function character encoding.
1738
 *                         If it is omitted, the platform character set will be used by default.
1739
 *
1740
 * @return int Returns < 0 if $string1 is less than $string2; > 0 if $string1 is greater than $string2; and 0 if the
1741
 *             strings are equal. This function is aimed at replacing the function strnatcmp() for human-language
1742
 *             strings.
1743
 *
1744
 * @see http://php.net/manual/en/function.strnatcmp.php
1745
 * @see http://php.net/manual/en/collator.compare.php
1746
 */
1747
function api_strnatcmp($string1, $string2, $language = null, $encoding = null)
1748
{
1749
    return strnatcmp($string1, $string2);
1750
}
1751
1752
/**
1753
 * Sorting arrays.
1754
 */
1755
1756
/**
1757
 * Sorts an array using natural order algorithm.
1758
 *
1759
 * @param array  $array    the input array
1760
 * @param string $language (optional)    The language in which comparison is to be made. If language is omitted,
1761
 *                         interface language is assumed then.
1762
 * @param string $encoding (optional)    The used internally by this function character encoding.
1763
 *                         If it is omitted, the platform character set will be used by default.
1764
 *
1765
 * @return bool Returns TRUE on success, FALSE on error.
1766
 *              This function is aimed at replacing the function natsort() for sorting human-language strings.
1767
 *
1768
 * @see http://php.net/manual/en/function.natsort.php
1769
 */
1770
function api_natsort(&$array, $language = null, $encoding = null)
1771
{
1772
    return natsort($array);
1773
}
1774
1775
/**
1776
 * Sorts an array using natural order algorithm in reverse order.
1777
 *
1778
 * @param array  $array    the input array
1779
 * @param string $language (optional)    The language in which comparison is to be made. If language is omitted,
1780
 *                         interface language is assumed then.
1781
 * @param string $encoding (optional)    The used internally by this function character encoding.
1782
 *                         If it is omitted, the platform character set will be used by default.
1783
 *
1784
 * @return bool returns TRUE on success, FALSE on error
1785
 */
1786
function api_natrsort(&$array, $language = null, $encoding = null)
1787
{
1788
    return uasort($array, '_api_strnatrcmp');
1789
}
1790
1791
/**
1792
 * Encoding management functions.
1793
 */
1794
1795
/**
1796
 * This function unifies the encoding identificators, so they could be compared.
1797
 *
1798
 * @param string|array $encoding the specified encoding
1799
 *
1800
 * @return string returns the encoding identificator modified in suitable for comparison way
1801
 */
1802
function api_refine_encoding_id($encoding)
1803
{
1804
    if (is_array($encoding)) {
1805
        return array_map('api_refine_encoding_id', $encoding);
1806
    }
1807
1808
    return strtoupper(str_replace('_', '-', $encoding));
1809
}
1810
1811
/**
1812
 * This function returns the encoding, currently used by the system.
1813
 *
1814
 * @return string The system's encoding.
1815
 *                Note: The value of api_get_setting('platform_charset') is tried to be returned first,
1816
 *                on the second place the global variable $charset is tried to be returned. If for some
1817
 *                reason both attempts fail, then the libraly's internal value will be returned.
1818
 */
1819
function api_get_system_encoding()
1820
{
1821
    return 'UTF-8';
1822
}
1823
1824
/**
1825
 * Detects encoding of plain text.
1826
 *
1827
 * @param string $string the input text
1828
 *
1829
 * @return string returns the detected encoding
1830
 */
1831
function api_detect_encoding($string)
1832
{
1833
    // Testing against valid UTF-8 first.
1834
    if (api_is_valid_utf8($string)) {
1835
        return 'UTF-8';
1836
    }
1837
1838
    return mb_detect_encoding($string);
1839
}
1840
1841
/**
1842
 * String validation functions concerning certain encodings.
1843
 */
1844
1845
/**
1846
 * Checks a string for UTF-8 validity.
1847
 *
1848
 * @param string $string
1849
 *
1850
 * @return string
1851
 */
1852
function api_is_valid_utf8($string)
1853
{
1854
    return mb_check_encoding($string, 'UTF-8');
1855
}
1856
1857
/**
1858
 * Checks whether a string contains 7-bit ASCII characters only.
1859
 *
1860
 * @param string $string the string to be tested/validated
1861
 *
1862
 * @return bool returns TRUE when the tested string contains 7-bit
1863
 *              ASCII characters only, FALSE othewise
1864
 */
1865
function api_is_valid_ascii(&$string)
1866
{
1867
    return 'ASCII' == mb_detect_encoding($string, 'ASCII', true) ? true : false;
1868
}
1869
1870
/**
1871
 * Return true a date is valid.
1872
 *
1873
 * @param string $date   example: 2014-06-30 13:05:05
1874
 * @param string $format example: "Y-m-d H:i:s"
1875
 *
1876
 * @return bool
1877
 */
1878
function api_is_valid_date($date, $format = 'Y-m-d H:i:s')
1879
{
1880
    $d = DateTime::createFromFormat($format, $date);
1881
1882
    return $d && $d->format($format) == $date;
1883
}
1884
1885
/**
1886
 * Returns the variable translated.
1887
 *
1888
 * @param string $variable   the string to translate
1889
 * @param string $pluginName the Plugin name
1890
 *
1891
 * @return string the variable translated
1892
 */
1893
function get_plugin_lang($variable, $pluginName)
1894
{
1895
    $plugin = $pluginName::create();
1896
1897
    return $plugin->get_lang($variable);
1898
}
1899
1900
/**
1901
 * Returns an array of translated week days and months, short and normal names.
1902
 *
1903
 * @param string $language (optional Language id. If it is omitted,
1904
 *                         the current interface language is assumed.
1905
 *
1906
 * @return array returns a multidimensional array with translated week days and months
1907
 */
1908
function &_api_get_day_month_names($language = null)
1909
{
1910
    static $date_parts = [];
1911
    if (empty($language)) {
1912
        $language = api_get_language_isocode();
1913
    }
1914
    if (!isset($date_parts[$language])) {
1915
        $week_day = [
1916
            'Sunday',
1917
            'Monday',
1918
            'Tuesday',
1919
            'Wednesday',
1920
            'Thursday',
1921
            'Friday',
1922
            'Saturday',
1923
        ];
1924
        $month = [
1925
            'January',
1926
            'February',
1927
            'March',
1928
            'April',
1929
            'May',
1930
            'June',
1931
            'July',
1932
            'August',
1933
            'September',
1934
            'October',
1935
            'November',
1936
            'December',
1937
        ];
1938
        for ($i = 0; $i < 7; $i++) {
1939
            $date_parts[$language]['days_short'][] = get_lang(
1940
                $week_day[$i], //.'Short',
1941
                '',
1942
                $language
1943
            );
1944
            $date_parts[$language]['days_long'][] = get_lang(
1945
                $week_day[$i], //.'Long',
1946
                '',
1947
                $language
1948
            );
1949
        }
1950
        for ($i = 0; $i < 12; $i++) {
1951
            $date_parts[$language]['months_short'][] = get_lang(
1952
                $month[$i], //.'Short',
1953
                '',
1954
                $language
1955
            );
1956
            $date_parts[$language]['months_long'][] = get_lang(
1957
                $month[$i], //.'Long',
1958
                '',
1959
                $language
1960
            );
1961
        }
1962
    }
1963
1964
    return $date_parts[$language];
1965
}
1966
1967
/**
1968
 * Returns returns person name convention for a given language.
1969
 *
1970
 * @param string $iso
1971
 * @param string $type The type of the requested convention.
1972
 *                     It may be 'format' for name order convention or 'sort_by' for name sorting convention.
1973
 *
1974
 * @return mixed Depending of the requested type,
1975
 *               the returned result may be string or boolean; null is returned on error;
1976
 */
1977
function _api_get_person_name_convention($iso, $type)
1978
{
1979
    $conventions = LanguageFixtures::getLanguages();
1980
1981
    // Overwrite classic conventions
1982
    //$customConventions = api_get_configuration_value('name_order_conventions');
1983
1984
    $search1 = ['FIRST_NAME', 'LAST_NAME', 'TITLE'];
1985
    $replacement1 = ['%F', '%L', '%T'];
1986
    $search2 = ['first_name', 'last_name', 'title'];
1987
    $replacement2 = ['%f', '%l', '%t'];
1988
    $conventionsFormatted = [];
1989
    foreach ($conventions as $language) {
1990
        $iso = $language['isocode'];
1991
        $conventionsFormatted[$iso]['format'] = $language['format'];
1992
        $conventionsFormatted[$iso]['sort_by'] = $language['sort_by'];
1993
1994
        $conventionsFormatted[$iso]['format'] = str_replace(
1995
            $search1,
1996
            $replacement1,
1997
            $conventionsFormatted[$iso]['format']
1998
        );
1999
        $conventionsFormatted[$iso]['format'] = _api_validate_person_name_format(
2000
            _api_clean_person_name(
2001
                str_replace(
2002
                    '%',
2003
                    ' %',
2004
                    str_ireplace(
2005
                        $search2,
2006
                        $replacement2,
2007
                        $conventionsFormatted[$iso]['format']
2008
                    )
2009
                )
2010
            )
2011
        );
2012
2013
        $conventionsFormatted[$iso]['sort_by'] = 'last_name' !== strtolower(
2014
            $conventionsFormatted[$iso]['sort_by']
2015
        ) ? true : false;
2016
    }
2017
2018
    switch ($type) {
2019
        case 'format':
2020
            return is_string(
2021
                $conventionsFormatted[$iso]['format']
2022
            ) ? $conventionsFormatted[$iso]['format'] : '%t %f %l';
2023
        case 'sort_by':
2024
            return is_bool($conventionsFormatted[$iso]['sort_by']) ? $conventionsFormatted[$iso]['sort_by'] : true;
2025
    }
2026
2027
    return null;
2028
}
2029
2030
/**
2031
 * Replaces non-valid formats for person names with the default (English) format.
2032
 *
2033
 * @param string $format the input format to be verified
2034
 *
2035
 * @return bool returns the same format if is is valid, otherwise returns a valid English format
2036
 */
2037
function _api_validate_person_name_format($format)
2038
{
2039
    if (empty($format) || false === stripos($format, '%f') || false === stripos($format, '%l')) {
2040
        return '%t %f %l';
2041
    }
2042
2043
    return $format;
2044
}
2045
2046
/**
2047
 * Removes leading, trailing and duplicate whitespace and/or commas in a full person name.
2048
 * Cleaning is needed for the cases when not all parts of the name are available
2049
 * or when the name is constructed using a "dirty" pattern.
2050
 *
2051
 * @param string $person_name the input person name
2052
 *
2053
 * @return string returns cleaned person name
2054
 */
2055
function _api_clean_person_name($person_name)
2056
{
2057
    return preg_replace(['/\s+/', '/, ,/', '/,+/', '/^[ ,]/', '/[ ,]$/'], [' ', ', ', ',', '', ''], $person_name);
2058
}
2059
2060
/**
2061
 * A reverse function from php-core function strnatcmp(),
2062
 * performs string comparison in reverse natural (alpha-numerical) order.
2063
 *
2064
 * @param string $string1 the first string
2065
 * @param string $string2 the second string
2066
 *
2067
 * @return int returns 0 if $string1 = $string2; >0 if $string1 < $string2; <0 if $string1 > $string2
2068
 */
2069
function _api_strnatrcmp($string1, $string2)
2070
{
2071
    return strnatcmp($string2, $string1);
2072
}
2073
2074
/**
2075
 * Sets/Gets internal character encoding of the common string functions within the PHP mbstring extension.
2076
 *
2077
 * @param string $encoding (optional)    When this parameter is given, the function sets the internal encoding
2078
 *
2079
 * @return string When $encoding parameter is not given, the function returns the internal encoding.
2080
 *                Note: This function is used in the global initialization script for setting the
2081
 *                internal encoding to the platform's character set.
2082
 *
2083
 * @see http://php.net/manual/en/function.mb-internal-encoding
2084
 */
2085
function _api_mb_internal_encoding($encoding = 'UTF-8')
2086
{
2087
    return mb_internal_encoding($encoding);
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_internal_encoding($encoding) also could return the type true which is incompatible with the documented return type string.
Loading history...
2088
}
2089
2090
/**
2091
 * Given a date object, return a human or ISO format, with or without h:m:s.
2092
 *
2093
 * @param object $date      The Date object
2094
 * @param bool   $showTime  Whether to show the time and date (true) or only the date (false)
2095
 * @param bool   $humanForm Whether to show day-month-year (true) or year-month-day (false)
2096
 *
2097
 * @return string Formatted date
2098
 */
2099
function api_get_human_date_time($date, $showTime = true, $humanForm = false)
2100
{
2101
    if ($showTime) {
2102
        if ($humanForm) {
2103
            return $date->format('j M Y H:i:s');
2104
        } else {
2105
            return $date->format('Y-m-d H:i:s');
2106
        }
2107
    } else {
2108
        if ($humanForm) {
2109
            return $date->format('j M Y');
2110
        } else {
2111
            return $date->format('Y-m-d');
2112
        }
2113
    }
2114
}
2115
2116
/**
2117
 * Return an array with the start and end dates of a quarter (as in 3 months period).
2118
 * If no DateTime is not sent, use the current date.
2119
 *
2120
 * @param string|null $date (optional) The date or null.
2121
 *
2122
 * @return array E.G.: ['quarter_start' => '2022-10-11',
2123
 *               'quarter_end' => '2022-12-31',
2124
 *               'quarter_title' => 'Q4 2022']
2125
 */
2126
function getQuarterDates(string $date = null): array
2127
{
2128
    if (empty($date)) {
2129
        $date = api_get_utc_datetime();
2130
    }
2131
    if (strlen($date > 10)) {
2132
        $date = substr($date, 0, 10);
2133
    }
2134
    $month = substr($date, 5, 2);
2135
    $year = substr($date, 0, 4);
2136
    switch ($month) {
2137
        case $month >= 1 && $month <= 3:
2138
            $start = "$year-01-01";
2139
            $end = "$year-03-31";
2140
            $quarter = 1;
2141
            break;
2142
        case $month >= 4 && $month <= 6:
2143
            $start = "$year-04-01";
2144
            $end = "$year-06-30";
2145
            $quarter = 2;
2146
            break;
2147
        case $month >= 7 && $month <= 9:
2148
            $start = "$year-07-01";
2149
            $end = "$year-09-30";
2150
            $quarter = 3;
2151
            break;
2152
        case $month >= 10 && $month <= 12:
2153
            $start = "$year-10-01";
2154
            $end = "$year-12-31";
2155
            $quarter = 4;
2156
            break;
2157
        default:
2158
            // Should never happen
2159
            $start = "$year-01-01";
2160
            $end = "$year-03-31";
2161
            $quarter = 1;
2162
            break;
2163
    }
2164
    return [
2165
        'quarter_start' => $start,
2166
        'quarter_end' => $end,
2167
        'quarter_title' => sprintf(get_lang('Q%s %s'), $quarter, $year),
2168
    ];
2169
}
2170