Passed
Push — master ( 2cc8c4...2ca18e )
by Julito
13:12
created

api_purify_language_id()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 14
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Carbon\Carbon;
6
use Chamilo\CoreBundle\DataFixtures\LanguageFixtures;
7
use Chamilo\CoreBundle\Framework\Container;
8
use ChamiloSession as Session;
9
use Westsworld\TimeAgo;
10
11
/**
12
 * File: internationalization.lib.php
13
 * Internationalization library for Chamilo 1.x LMS
14
 * A library implementing internationalization related functions.
15
 * License: GNU General Public License Version 3 (Free Software Foundation)ww.
16
 *
17
 * @author Ivan Tcholakov, <[email protected]>, 2009, 2010
18
 * @author More authors, mentioned in the correpsonding fragments of this source.
19
 */
20
// Predefined date formats in Chamilo provided by the language sub-system.
21
// To be used as a parameter for the function api_format_date()
22
define('TIME_NO_SEC_FORMAT', 0); // 15:23
23
define('DATE_FORMAT_SHORT', 1); // Aug 25, 09
24
define('DATE_FORMAT_LONG', 2); // Monday August 25, 09
25
define('DATE_FORMAT_LONG_NO_DAY', 10); // August 25, 2009
26
define('DATE_TIME_FORMAT_LONG', 3); // Monday August 25, 2009 at 03:28 PM
27
28
define('DATE_FORMAT_NUMBER', 4); // 25.08.09
29
define('DATE_TIME_FORMAT_LONG_24H', 5); // August 25, 2009 at 15:28
30
define('DATE_TIME_FORMAT_SHORT', 6); // Aug 25, 2009 at 03:28 PM
31
define('DATE_TIME_FORMAT_SHORT_TIME_FIRST', 7); // 03:28 PM, Aug 25 2009
32
define('DATE_FORMAT_NUMBER_NO_YEAR', 8); // 25.08 dd-mm
33
define('DATE_FORMAT_ONLY_DAYNAME', 9); // Monday, Sunday, etc
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
 * @param string $variable
59
 *
60
 * @return string
61
 */
62
function get_lang($variable)
63
{
64
    $translator = Container::getTranslator();
65
66
    if (!$translator) {
0 ignored issues
show
introduced by
$translator is of type Symfony\Contracts\Translation\TranslatorInterface, thus it always evaluated to true.
Loading history...
67
        return $variable;
68
    }
69
70
    // Using symfony
71
    $defaultDomain = 'messages';
72
    $locale = api_get_language_isocode();
73
74
    return $translator->trans(
75
        $variable,
76
        [],
77
        $defaultDomain,
78
        $locale
79
    );
80
}
81
82
/**
83
 * Gets language iso code.
84
 */
85
function api_get_language_isocode()
86
{
87
    $request = Container::getRequest();
88
89
    if ($request) {
90
        return $request->getLocale();
91
    }
92
93
    return 'en_US';
94
}
95
96
/**
97
 * Gets language iso code column from the language table.
98
 *
99
 * @return array An array with the current isocodes
100
 *
101
 * */
102
function api_get_platform_isocodes()
103
{
104
    $list = [];
105
    $sql = "SELECT isocode
106
            FROM ".Database::get_main_table(TABLE_MAIN_LANGUAGE)."
107
            ORDER BY isocode ";
108
    $result = Database::query($sql);
109
    if (Database::num_rows($result)) {
110
        while ($row = Database::fetch_array($result)) {
111
            $list[] = trim($row['isocode']);
112
        }
113
    }
114
115
    return $list;
116
}
117
118
/**
119
 * Gets text direction according to the given language.
120
 *
121
 * @param string $iso Iso code example en_US, fr_FR, If $language is omitted, interface language is assumed then.
122
 *
123
 * @return string the correspondent to the language text direction ('ltr' or 'rtl')
124
 */
125
function api_get_text_direction($iso = '')
126
{
127
    $languages = LanguageFixtures::getLanguages();
128
    $rightDirection = array_column($languages, 'direction', 'isocode');
129
130
    if (empty($iso)) {
131
        $iso = api_get_language_isocode();
132
    }
133
134
    if (isset($rightDirection[$iso])) {
135
        return 'rtl';
136
    }
137
138
    return 'ltr';
139
}
140
141
/**
142
 * Returns an alphabetized list of timezones in an associative array
143
 * that can be used to populate a select.
144
 *
145
 * @return array List of timezone identifiers
146
 *
147
 * @author Guillaume Viguier <[email protected]>
148
 */
149
function api_get_timezones()
150
{
151
    $timezone_identifiers = DateTimeZone::listIdentifiers();
152
    sort($timezone_identifiers);
153
    $out = [];
154
    foreach ($timezone_identifiers as $tz) {
155
        $out[$tz] = $tz;
156
    }
157
    $null_option = ['' => ''];
158
159
    return array_merge($null_option, $out);
160
}
161
162
/**
163
 * Returns the timezone to be converted to/from, based on user or admin preferences.
164
 *
165
 * @return string The timezone chosen
166
 */
167
function api_get_timezone()
168
{
169
    $timezone = Session::read('system_timezone');
170
    if (empty($timezone)) {
171
        // First, get the default timezone of the server
172
        $timezone = date_default_timezone_get();
173
        // Second, see if a timezone has been chosen for the platform
174
        $timezoneFromSettings = api_get_setting('timezone_value', 'timezones');
175
176
        if (null != $timezoneFromSettings) {
177
            $timezone = $timezoneFromSettings;
178
        }
179
180
        // If allowed by the administrator
181
        $allowUserTimezones = api_get_setting('use_users_timezone', 'timezones');
182
        $userId = api_get_user_id();
183
184
        if ('true' === $allowUserTimezones && !empty($userId)) {
185
            // Get the timezone based on user preference, if it exists
186
            $newExtraField = new ExtraFieldValue('user');
187
            $data = $newExtraField->get_values_by_handler_and_field_variable($userId, 'timezone');
188
            if (!empty($data) && isset($data['timezone']) && !empty($data['timezone'])) {
189
                $timezone = $data['timezone'];
190
            }
191
        }
192
        Session::write('system_timezone', $timezone);
193
    }
194
195
    return $timezone;
196
}
197
198
/**
199
 * Returns the given date as a DATETIME in UTC timezone.
200
 * This function should be used before entering any date in the DB.
201
 *
202
 * @param mixed $time                    Date to be converted (can be a string supported by date() or a timestamp)
203
 * @param bool  $returnNullIfInvalidDate If the date is not correct return null instead of the current date
204
 * @param bool  $returnObj               Returns a DateTime object
205
 *
206
 * @return string|DateTime The DATETIME in UTC to be inserted in the DB,
207
 *                         or null if the format of the argument is not supported
208
 *
209
 * @author Julio Montoya - Adding the 2nd parameter
210
 * @author Guillaume Viguier <[email protected]>
211
 */
212
function api_get_utc_datetime(
213
    $time = null,
214
    $returnNullIfInvalidDate = false,
215
    $returnObj = false
216
) {
217
    if (is_null($time) || empty($time) || '0000-00-00 00:00:00' === $time) {
218
        if ($returnNullIfInvalidDate) {
219
            return null;
220
        }
221
        if ($returnObj) {
222
            return new DateTime(gmdate('Y-m-d H:i:s'), new DateTimeZone('UTC'));
223
        }
224
225
        return gmdate('Y-m-d H:i:s');
226
    }
227
228
    // If time is a timestamp, return directly in utc
229
    if (is_numeric($time)) {
230
        $time = (int) $time;
231
        $time = gmdate('Y-m-d H:i:s', $time);
232
        if ($returnObj) {
233
            return new DateTime($time, new DateTimeZone('UTC'));
234
        }
235
236
        return $time;
237
    }
238
    try {
239
        $fromTimezone = api_get_timezone();
240
        $date = new DateTime($time, new DateTimezone($fromTimezone));
241
        $date->setTimezone(new DateTimeZone('UTC'));
242
        if ($returnObj) {
243
            return $date;
244
        } else {
245
            return $date->format('Y-m-d H:i:s');
246
        }
247
    } catch (Exception $e) {
248
        return null;
249
    }
250
}
251
252
/**
253
 * Returns a DATETIME string converted to the right timezone.
254
 *
255
 * @param mixed  $time                    The time to be converted
256
 * @param string $to_timezone             The timezone to be converted to.
257
 *                                        If null, the timezone will be determined based on user preference,
258
 *                                        or timezone chosen by the admin for the platform.
259
 * @param string $from_timezone           The timezone to be converted from. If null, UTC will be assumed.
260
 * @param bool   $returnNullIfInvalidDate
261
 * @param bool   $showTime
262
 * @param bool   $humanForm
263
 * @param string $format
264
 *
265
 * @return string The converted time formatted as Y-m-d H:i:s
266
 *
267
 * @author Guillaume Viguier <[email protected]>
268
 */
269
function api_get_local_time(
270
    $time = null,
271
    $to_timezone = null,
272
    $from_timezone = null,
273
    $returnNullIfInvalidDate = false,
274
    $showTime = true,
275
    $humanForm = false,
276
    $format = ''
277
) {
278
    // Determining the timezone to be converted from
279
    if (is_null($from_timezone)) {
280
        $from_timezone = 'UTC';
281
    }
282
283
    // If time is a timestamp, convert it to a string
284
    if (is_null($time) || empty($time) || '0000-00-00 00:00:00' == $time) {
285
        if ($returnNullIfInvalidDate) {
286
            return null;
287
        }
288
        $from_timezone = 'UTC';
289
        $time = gmdate('Y-m-d H:i:s');
290
    }
291
292
    if (is_numeric($time)) {
293
        $time = (int) $time;
294
        if ($returnNullIfInvalidDate) {
295
            if (strtotime(date('d-m-Y H:i:s', $time)) !== $time) {
296
                return null;
297
            }
298
        }
299
300
        $from_timezone = 'UTC';
301
        $time = gmdate('Y-m-d H:i:s', $time);
302
    }
303
304
    if ($time instanceof DateTime) {
305
        $time = $time->format('Y-m-d H:i:s');
306
        $from_timezone = 'UTC';
307
    }
308
309
    try {
310
        // Determining the timezone to be converted to
311
        if (is_null($to_timezone)) {
312
            $to_timezone = api_get_timezone();
313
        }
314
315
        $date = new DateTime($time, new DateTimezone($from_timezone));
316
        $date->setTimezone(new DateTimeZone($to_timezone));
317
318
        if (!empty($format)) {
319
            return $date->format($format);
320
        }
321
322
        return api_get_human_date_time($date, $showTime, $humanForm);
323
    } catch (Exception $e) {
324
        return '';
325
    }
326
}
327
328
/**
329
 * Converts a string into a timestamp safely (handling timezones), using strtotime.
330
 *
331
 * @param string $time     to be converted
332
 * @param string $timezone (if null, the timezone will be determined based
333
 *                         on user preference, or timezone chosen by the admin for the platform)
334
 *
335
 * @return int Timestamp
336
 *
337
 * @author Guillaume Viguier <[email protected]>
338
 */
339
function api_strtotime($time, $timezone = null)
340
{
341
    $system_timezone = date_default_timezone_get();
342
    if (!empty($timezone)) {
343
        date_default_timezone_set($timezone);
344
    }
345
    $timestamp = strtotime($time);
346
    if (!empty($timezone)) {
347
        // only reset timezone if it was changed
348
        date_default_timezone_set($system_timezone);
349
    }
350
351
    return $timestamp;
352
}
353
354
/**
355
 * Returns formatted date/time, correspondent to a given language.
356
 * The given date should be in the timezone chosen by the administrator
357
 * and/or user. Use api_get_local_time to get it.
358
 *
359
 * @param mixed  $time     Timestamp or datetime string
360
 * @param string|int Date format (see date formats in the Chamilo system:
361
 *                         TIME_NO_SEC_FORMAT,
362
 *                         DATE_FORMAT_SHORT,
363
 *                         DATE_FORMAT_LONG,
364
 *                         DATE_TIME_FORMAT_LONG
365
 * @param string $language (optional) Language id
366
 *                         If it is omitted, the current interface language is assumed
367
 *
368
 * @return string returns the formatted date
369
 *
370
 * @author Patrick Cool <[email protected]>, Ghent University
371
 * @author Christophe Gesche<[email protected]>
372
 *         originally inspired from from PhpMyAdmin
373
 * @author Ivan Tcholakov, 2009, code refactoring, adding support for predefined date/time formats.
374
 * @author Guillaume Viguier <[email protected]>
375
 *
376
 * @see    http://php.net/manual/en/function.strftime.php
377
 */
378
function api_format_date($time, $format = null, $language = null)
379
{
380
    if (empty($time)) {
381
        return '';
382
    }
383
384
    $system_timezone = date_default_timezone_get();
385
    date_default_timezone_set(api_get_timezone());
386
387
    if (is_string($time)) {
388
        $time = strtotime($time);
389
    }
390
391
    if (is_null($format)) {
392
        $format = DATE_TIME_FORMAT_LONG;
393
    }
394
    if ($time instanceof DateTime) {
395
        $time = $time->format('Y-m-d H:i:s');
396
    }
397
398
    $datetype = null;
399
    $timetype = null;
400
401
    if (is_int($format)) {
402
        switch ($format) {
403
            case DATE_FORMAT_ONLY_DAYNAME:
404
405
                $datetype = IntlDateFormatter::SHORT;
406
                $timetype = IntlDateFormatter::NONE;
407
408
                break;
409
            case DATE_FORMAT_NUMBER_NO_YEAR:
410
411
                $datetype = IntlDateFormatter::SHORT;
412
                $timetype = IntlDateFormatter::NONE;
413
414
                break;
415
            case DATE_FORMAT_NUMBER:
416
417
                $datetype = IntlDateFormatter::SHORT;
418
                $timetype = IntlDateFormatter::NONE;
419
420
                break;
421
            case TIME_NO_SEC_FORMAT:
422
423
                $datetype = IntlDateFormatter::NONE;
424
                $timetype = IntlDateFormatter::SHORT;
425
426
                break;
427
            case DATE_FORMAT_SHORT:
428
429
                $datetype = IntlDateFormatter::LONG;
430
                $timetype = IntlDateFormatter::NONE;
431
432
                break;
433
            case DATE_FORMAT_LONG:
434
435
                $datetype = IntlDateFormatter::FULL;
436
                $timetype = IntlDateFormatter::NONE;
437
438
                break;
439
            case DATE_TIME_FORMAT_LONG:
440
441
                $datetype = IntlDateFormatter::FULL;
442
                $timetype = IntlDateFormatter::SHORT;
443
444
                break;
445
            case DATE_FORMAT_LONG_NO_DAY:
446
447
                $datetype = IntlDateFormatter::FULL;
448
                $timetype = IntlDateFormatter::SHORT;
449
450
                break;
451
            case DATE_TIME_FORMAT_SHORT:
452
                $datetype = IntlDateFormatter::FULL;
453
                $timetype = IntlDateFormatter::SHORT;
454
455
                break;
456
            case DATE_TIME_FORMAT_SHORT_TIME_FIRST:
457
458
                $datetype = IntlDateFormatter::FULL;
459
                $timetype = IntlDateFormatter::SHORT;
460
461
                break;
462
            case DATE_TIME_FORMAT_LONG_24H:
463
                $datetype = IntlDateFormatter::FULL;
464
                $timetype = IntlDateFormatter::SHORT;
465
466
                break;
467
            default:
468
                $datetype = IntlDateFormatter::FULL;
469
                $timetype = IntlDateFormatter::SHORT;
470
        }
471
    }
472
473
    // Use ICU
474
    if (is_null($language)) {
475
        $language = api_get_language_isocode();
476
    }
477
    $date_formatter = new IntlDateFormatter($language, $datetype, $timetype, date_default_timezone_get());
478
    $formatted_date = api_to_system_encoding($date_formatter->format($time), 'UTF-8');
479
480
    date_default_timezone_set($system_timezone);
481
482
    return $formatted_date;
483
}
484
485
/**
486
 * Returns the difference between the current date (date(now)) with the parameter
487
 * $date in a string format like "2 days ago, 1 hour ago".
488
 * You can use it like this:
489
 * Display::dateToStringAgoAndLongDate($dateInUtc);.
490
 *
491
 * @param string|DateTime $date Result of a date function in this format -> date('Y-m-d H:i:s', time());
492
 * @param string          $timeZone
493
 * @param bool            $returnDateDifference
494
 *
495
 * @return string
496
 *
497
 * @author Julio Montoya
498
 */
499
function date_to_str_ago($date, $timeZone = 'UTC', $returnDateDifference = false)
500
{
501
    if ('0000-00-00 00:00:00' === $date) {
502
        return '';
503
    }
504
505
    $getOldTimezone = api_get_timezone();
506
    $isoCode = api_get_language_isocode();
507
    if ('pt' === $isoCode) {
508
        $isoCode = 'pt-BR';
509
    }
510
    $isoCode = ucfirst($isoCode);
511
    $class = "Westsworld\TimeAgo\Translations\\".$isoCode;
512
513
    if (class_exists($class)) {
514
        $language = new $class();
515
    } else {
516
        $language = new Westsworld\TimeAgo\Translations\En();
517
    }
518
519
    // Use carbon?
520
    //Carbon::instance($this->getCreatedAt())->diffForHumans();
521
    $timeAgo = new TimeAgo($language);
522
    if (!($date instanceof DateTime)) {
523
        $date = api_get_utc_datetime($date, null, true);
524
    }
525
526
    $value = $timeAgo->inWords($date);
527
    date_default_timezone_set($getOldTimezone);
528
529
    if ($returnDateDifference) {
530
        $now = new DateTime('now', $date->getTimezone());
531
        $value = $date->diff($now);
532
533
        return [
534
            'years' => $value->y,
535
            'months' => $value->m,
536
            'days' => $value->d,
537
            'hours' => $value->h,
538
            'minutes' => $value->i,
539
            'seconds' => $value->s,
540
        ];
541
    }
542
543
    return $value;
544
}
545
546
/**
547
 * Converts a date to the right timezone and localizes it in the format given as an argument.
548
 *
549
 * @param mixed The time to be converted
550
 * @param mixed Format to be used (TIME_NO_SEC_FORMAT, DATE_FORMAT_SHORT, DATE_FORMAT_LONG, DATE_TIME_FORMAT_LONG)
551
 * @param string Timezone to be converted from. If null, UTC will be assumed.
552
 *
553
 * @return string Converted and localized date
554
 *
555
 * @author Guillaume Viguier <[email protected]>
556
 */
557
function api_convert_and_format_date($time = null, $format = null, $from_timezone = null)
558
{
559
    // First, convert the datetime to the right timezone
560
    $time = api_get_local_time($time, null, $from_timezone, true);
561
562
    // Second, localize the date
563
    return api_format_date($time, $format);
564
}
565
566
/**
567
 * Returns an array of translated week days in short names.
568
 *
569
 * @param string $language (optional) Language id. If it is omitted, the current interface language is assumed.
570
 *
571
 * @return string Returns an array of week days (short names).
572
 *                Example: api_get_week_days_short('english') means array('Sun', 'Mon', ... 'Sat').
573
 *                Note: For all languges returned days are in the English order.
574
 */
575
function api_get_week_days_short($language = null)
576
{
577
    $days = &_api_get_day_month_names($language);
578
579
    return $days['days_short'];
580
}
581
582
/**
583
 * Returns an array of translated week days.
584
 *
585
 * @param string $language (optional) Language id. If it is omitted,
586
 *                         the current interface language is assumed.
587
 *
588
 * @return string Returns an array of week days.
589
 *                Example: api_get_week_days_long('english') means array('Sunday, 'Monday', ... 'Saturday').
590
 *                Note: For all languges returned days are in the English order.
591
 */
592
function api_get_week_days_long($language = null)
593
{
594
    $days = &_api_get_day_month_names($language);
595
596
    return $days['days_long'];
597
}
598
599
/**
600
 * Returns an array of translated months in short names.
601
 *
602
 * @param string $language (optional)    Language id.
603
 *                         If it is omitted, the current interface language is assumed.
604
 *
605
 * @return string Returns an array of months (short names).
606
 *                Example: api_get_months_short('english') means array('Jan', 'Feb', ... 'Dec').
607
 */
608
function api_get_months_short($language = null)
609
{
610
    $months = &_api_get_day_month_names($language);
611
612
    return $months['months_short'];
613
}
614
615
/**
616
 * Returns an array of translated months.
617
 *
618
 * @param string $language (optional)    Language id.
619
 *                         If it is omitted, the current interface language is assumed.
620
 *
621
 * @return string Returns an array of months.
622
 *                Example: api_get_months_long('english') means array('January, 'February' ... 'December').
623
 */
624
function api_get_months_long($language = null)
625
{
626
    $months = &_api_get_day_month_names($language);
627
628
    return $months['months_long'];
629
}
630
631
/**
632
 * Name order conventions.
633
 */
634
635
/**
636
 * Builds a person (full) name depending on the convention for a given language.
637
 *
638
 * @param string     $first_name the first name of the person
639
 * @param string     $last_name  the last name of the person
640
 * @param string     $title      the title of the person
641
 * @param int|string $format     (optional) The person name format.
642
 *                               It may be a pattern-string (for example '%t %l, %f' or '%T %F %L', ...) or
643
 *                               some of the constants
644
 *                               PERSON_NAME_COMMON_CONVENTION (default),
645
 *                               PERSON_NAME_WESTERN_ORDER,
646
 *                               PERSON_NAME_EASTERN_ORDER,
647
 *                               PERSON_NAME_LIBRARY_ORDER.
648
 * @param string     $language   (optional)
649
 *                               The language id. If it is omitted, the current interface language is assumed.
650
 *                               This parameter has meaning with the format PERSON_NAME_COMMON_CONVENTION only.
651
 * @param string     $username
652
 *
653
 * @return string The result is sort of full name of the person.
654
 *                Sample results:
655
 *                Peter Ustinoff or Dr. Peter Ustinoff     - the Western order
656
 *                Ustinoff Peter or Dr. Ustinoff Peter     - the Eastern order
657
 *                Ustinoff, Peter or - Dr. Ustinoff, Peter - the library order
658
 *                Note: See the file main/inc/lib/internationalization_database/name_order_conventions.php
659
 *                where you can check the convention for your language.
660
 *
661
 * @author Carlos Vargas <[email protected]> - initial implementation.
662
 * @author Ivan Tcholakov
663
 */
664
function api_get_person_name(
665
    $first_name,
666
    $last_name,
667
    $title = null,
668
    $format = null,
669
    $language = null,
670
    $username = null
671
) {
672
    static $valid = [];
673
    if (empty($format)) {
674
        $format = PERSON_NAME_COMMON_CONVENTION;
675
    }
676
    // We check if the language is supported, otherwise we check the
677
    // interface language of the parent language of sublanguage
678
    if (empty($language)) {
679
        // Do not set $setParentLanguageName because this function is called before
680
        // the main language is loaded in global.inc.php
681
        $language = api_get_language_isocode();
682
    }
683
    if (!isset($valid[$format][$language])) {
684
        if (is_int($format)) {
685
            switch ($format) {
686
                case PERSON_NAME_COMMON_CONVENTION:
687
                    $valid[$format][$language] = _api_get_person_name_convention($language, 'format');
688
                    break;
689
                case PERSON_NAME_WESTERN_ORDER:
690
                    $valid[$format][$language] = '%t %f %l';
691
                    break;
692
                case PERSON_NAME_EASTERN_ORDER:
693
                    $valid[$format][$language] = '%t %l %f';
694
                    break;
695
                case PERSON_NAME_LIBRARY_ORDER:
696
                    $valid[$format][$language] = '%t %l, %f';
697
                    break;
698
                default:
699
                    $valid[$format][$language] = '%t %f %l';
700
                    break;
701
            }
702
        } else {
703
            $valid[$format][$language] = _api_validate_person_name_format($format);
704
        }
705
    }
706
707
    $format = $valid[$format][$language];
708
709
    $keywords = [
710
        '%firstname',
711
        '%f',
712
        '%F',
713
        '%lastname',
714
        '%l',
715
        '%L',
716
        '%title',
717
        '%t',
718
        '%T',
719
        '%username',
720
        '%u',
721
        '%U',
722
    ];
723
724
    $values = [
725
        $first_name,
726
        $first_name,
727
        api_strtoupper($first_name),
728
        $last_name,
729
        $last_name,
730
        api_strtoupper($last_name),
731
        $title,
732
        $title,
733
        api_strtoupper($title),
734
        $username,
735
        $username,
736
        api_strtoupper($username),
737
    ];
738
    $person_name = str_replace($keywords, $values, $format);
739
740
    return _api_clean_person_name($person_name);
741
}
742
743
/**
744
 * Checks whether a given format represents person name in Western order (for which first name is first).
745
 *
746
 * @param int|string $format   (optional)    The person name format.
747
 *                             It may be a pattern-string (for example '%t. %l, %f') or some of the constants
748
 *                             PERSON_NAME_COMMON_CONVENTION (default),
749
 *                             PERSON_NAME_WESTERN_ORDER,
750
 *                             PERSON_NAME_EASTERN_ORDER,
751
 *                             PERSON_NAME_LIBRARY_ORDER.
752
 * @param string     $language (optional) The language id. If it is omitted,
753
 *                             the current interface language is assumed. This parameter has meaning with the
754
 *                             format PERSON_NAME_COMMON_CONVENTION only.
755
 *
756
 * @return bool The result TRUE means that the order is first_name last_name,
757
 *              FALSE means last_name first_name.
758
 *              Note: You may use this function for determining the order of the fields or
759
 *              columns "First name" and "Last name" in forms, tables and reports.
760
 *
761
 * @author Ivan Tcholakov
762
 */
763
function api_is_western_name_order($format = null, $language = null)
764
{
765
    static $order = [];
766
    if (empty($format)) {
767
        $format = PERSON_NAME_COMMON_CONVENTION;
768
    }
769
770
    if (empty($language)) {
771
        $language = api_get_language_isocode(false, true);
772
    }
773
    if (!isset($order[$format][$language])) {
774
        $test_name = api_get_person_name('%f', '%l', '%t', $format, $language);
775
        $order[$format][$language] = stripos($test_name, '%f') <= stripos($test_name, '%l');
776
    }
777
778
    return $order[$format][$language];
779
}
780
781
/**
782
 * Returns a directive for sorting person names depending on a given language
783
 * and based on the options in the internationalization "database".
784
 *
785
 * @param string $language (optional) The input language.
786
 *                         If it is omitted, the current interface language is assumed.
787
 *
788
 * @return bool Returns boolean value. TRUE means ORDER BY first_name, last_name
789
 *              FALSE means ORDER BY last_name, first_name.
790
 *              Note: You may use this function:
791
 *              2. for constructing the ORDER clause of SQL queries, related to first_name and last_name;
792
 *              3. for adjusting php-implemented sorting in tables and reports.
793
 *
794
 * @author Ivan Tcholakov
795
 */
796
function api_sort_by_first_name($language = null)
797
{
798
    static $sort_by_first_name = [];
799
800
    if (empty($language)) {
801
        $language = api_get_language_isocode(false, true);
802
    }
803
    if (!isset($sort_by_first_name[$language])) {
804
        $sort_by_first_name[$language] = _api_get_person_name_convention($language, 'sort_by');
805
    }
806
807
    return $sort_by_first_name[$language];
808
}
809
810
/**
811
 * Multibyte string conversion functions.
812
 */
813
814
/**
815
 * Converts character encoding of a given string.
816
 *
817
 * @param string $string        the string being converted
818
 * @param string $to_encoding   the encoding that $string is being converted to
819
 * @param string $from_encoding (optional)    The encoding that $string is being converted from.
820
 *                              If it is omitted, the platform character set is assumed.
821
 *
822
 * @return string Returns the converted string.
823
 *                This function is aimed at replacing the function mb_convert_encoding() for human-language strings.
824
 *
825
 * @see http://php.net/manual/en/function.mb-convert-encoding
826
 */
827
function api_convert_encoding($string, $to_encoding, $from_encoding = 'UTF-8')
828
{
829
    if (strtoupper($to_encoding) === strtoupper($from_encoding)) {
830
        return $string;
831
    }
832
833
    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...
834
}
835
836
/**
837
 * Converts a given string into UTF-8 encoded string.
838
 *
839
 * @param string $string        the string being converted
840
 * @param string $from_encoding (optional) The encoding that $string is being converted from.
841
 *                              If it is omitted, the platform character set is assumed.
842
 *
843
 * @return string Returns the converted string.
844
 *                This function is aimed at replacing the function utf8_encode() for human-language strings.
845
 *
846
 * @see http://php.net/manual/en/function.utf8-encode
847
 */
848
function api_utf8_encode($string, $from_encoding = 'UTF-8')
849
{
850
    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...
851
}
852
853
/**
854
 * Converts a given string from UTF-8 encoding to a specified encoding.
855
 *
856
 * @param string $string      the string being converted
857
 * @param string $to_encoding (optional)    The encoding that $string is being converted to.
858
 *                            If it is omitted, the platform character set is assumed.
859
 *
860
 * @return string Returns the converted string.
861
 *                This function is aimed at replacing the function utf8_decode() for human-language strings.
862
 *
863
 * @see http://php.net/manual/en/function.utf8-decode
864
 */
865
function api_utf8_decode($string, $to_encoding = null)
866
{
867
    return mb_convert_encoding($string, $to_encoding, 'UTF-8');
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_convert_encodi... $to_encoding, 'UTF-8') also could return the type array which is incompatible with the documented return type string.
Loading history...
868
}
869
870
/**
871
 * Converts a given string into the system encoding (or platform character set).
872
 * When $from encoding is omitted on UTF-8 platforms then language dependent encoding
873
 * is guessed/assumed. On non-UTF-8 platforms omitted $from encoding is assumed as UTF-8.
874
 * When the parameter $check_utf8_validity is true the function checks string's
875
 * UTF-8 validity and decides whether to try to convert it or not.
876
 * This function is useful for problem detection or making workarounds.
877
 *
878
 * @param string $string              the string being converted
879
 * @param string $from_encoding       (optional) The encoding that $string is being converted from.
880
 *                                    It is guessed when it is omitted.
881
 * @param bool   $check_utf8_validity (optional)    A flag for UTF-8 validity check as condition for making conversion
882
 *
883
 * @return string returns the converted string
884
 */
885
function api_to_system_encoding($string, $from_encoding = null, $check_utf8_validity = false)
886
{
887
    $system_encoding = api_get_system_encoding();
888
889
    return api_convert_encoding($string, $system_encoding, $from_encoding);
890
}
891
892
/**
893
 * Converts all applicable characters to HTML entities.
894
 *
895
 * @param string $string      the input string
896
 * @param int    $quote_style (optional)    The quote style - ENT_COMPAT (default), ENT_QUOTES, ENT_NOQUOTES
897
 *
898
 * @return string Returns the converted string.
899
 *                This function is aimed at replacing the function htmlentities() for human-language strings.
900
 *
901
 * @see http://php.net/manual/en/function.htmlentities
902
 */
903
function api_htmlentities($string, $quote_style = ENT_COMPAT)
904
{
905
    switch ($quote_style) {
906
        case ENT_COMPAT:
907
            $string = str_replace(['&', '"', '<', '>'], ['&amp;', '&quot;', '&lt;', '&gt;'], $string);
908
            break;
909
        case ENT_QUOTES:
910
            $string = str_replace(['&', '\'', '"', '<', '>'], ['&amp;', '&#039;', '&quot;', '&lt;', '&gt;'], $string);
911
            break;
912
    }
913
914
    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...
915
}
916
917
/**
918
 * Converts HTML entities into normal characters.
919
 *
920
 * @param string $string      the input string
921
 * @param int    $quote_style (optional)    The quote style - ENT_COMPAT (default), ENT_QUOTES, ENT_NOQUOTES
922
 * @param string $encoding    (optional)    The encoding (of the result) used in conversion.
923
 *                            If it is omitted, the platform character set is assumed.
924
 *
925
 * @return string Returns the converted string.
926
 *                This function is aimed at replacing the function html_entity_decode() for human-language strings.
927
 *
928
 * @see http://php.net/html_entity_decode
929
 */
930
function api_html_entity_decode($string, $quote_style = ENT_COMPAT, $encoding = 'UTF-8')
931
{
932
    if (empty($encoding)) {
933
        $encoding = _api_mb_internal_encoding();
934
    }
935
    if (api_is_encoding_supported($encoding)) {
936
        if (!api_is_utf8($encoding)) {
937
            $string = api_utf8_encode($string, $encoding);
938
        }
939
        $string = html_entity_decode($string, $quote_style, 'UTF-8');
940
        if (!api_is_utf8($encoding)) {
941
            return api_utf8_decode($string, $encoding);
942
        }
943
944
        return $string;
945
    }
946
947
    return $string; // Here the function gives up.
948
}
949
950
/**
951
 * This function encodes (conditionally) a given string to UTF-8 if XmlHttp-request has been detected.
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
 */
959
function api_xml_http_response_encode($string, $from_encoding = 'UTF8')
960
{
961
    if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 'xmlhttprequest' == strtolower($_SERVER['HTTP_X_REQUESTED_WITH'])) {
962
        if (empty($from_encoding)) {
963
            $from_encoding = _api_mb_internal_encoding();
964
        }
965
        if (!api_is_utf8($from_encoding)) {
966
            return api_utf8_encode($string, $from_encoding);
967
        }
968
    }
969
970
    return $string;
971
}
972
973
/**
974
 * Transliterates a string with arbitrary encoding into a plain ASCII string.
975
 *
976
 * Example:
977
 * echo api_transliterate(api_html_entity_decode(
978
 *    '&#1060;&#1105;&#1076;&#1086;&#1088; '.
979
 *    '&#1052;&#1080;&#1093;&#1072;&#1081;&#1083;&#1086;&#1074;&#1080;&#1095; '.
980
 *    '&#1044;&#1086;&#1089;&#1090;&#1086;&#1077;&#1074;&#1082;&#1080;&#1081;',
981
 *    ENT_QUOTES, 'UTF-8'), 'X', 'UTF-8');
982
 * The output should be: Fyodor Mihaylovich Dostoevkiy
983
 *
984
 * @param string $string        the input string
985
 * @param string $unknown       (optional) Replacement character for unknown characters and illegal UTF-8 sequences
986
 * @param string $from_encoding (optional) The encoding of the input string.
987
 *                              If it is omitted, the platform character set is assumed.
988
 *
989
 * @return string plain ASCII output
990
 */
991
function api_transliterate($string, $unknown = '?', $from_encoding = null)
992
{
993
    return URLify::transliterate($string);
994
}
995
996
/**
997
 * Takes the first character in a string and returns its Unicode codepoint.
998
 *
999
 * @param string $character the input string
1000
 * @param string $encoding  (optional) The encoding of the input string.
1001
 *                          If it is omitted, the platform character set will be used by default.
1002
 *
1003
 * @return int Returns: the codepoint of the first character; or 0xFFFD (unknown character) when the input string is
1004
 *             empty. This is a multibyte aware version of the function ord().
1005
 *
1006
 * @see http://php.net/manual/en/function.ord.php
1007
 * Note the difference with the original funtion ord(): ord('') returns 0, api_ord('') returns 0xFFFD (unknown
1008
 * character).
1009
 */
1010
function api_ord($character, $encoding = 'UTF-8')
1011
{
1012
    return ord(api_utf8_encode($character, $encoding));
1013
}
1014
1015
/**
1016
 * This function returns a string or an array with all occurrences of search
1017
 * in subject (ignoring case) replaced with the given replace value.
1018
 *
1019
 * @param mixed  $search   string or array of strings to be found
1020
 * @param mixed  $replace  string or array of strings used for replacement
1021
 * @param mixed  $subject  string or array of strings being searched
1022
 * @param int    $count    (optional) The number of matched and replaced needles
1023
 *                         will be returned in count, which is passed by reference
1024
 * @param string $encoding (optional) The used internally by this function character encoding.
1025
 *                         If it is omitted, the platform character set will be used by default.
1026
 *
1027
 * @return mixed String or array as a result.
1028
 *               Notes:
1029
 *               If $subject is an array, then the search and replace is performed with
1030
 *               every entry of subject, the return value is an array.
1031
 *               If $search and $replace are arrays, then the function takes a value from
1032
 *               each array and uses it to do search and replace on subject.
1033
 *               If $replace has fewer values than search, then an empty string is used for the rest of replacement
1034
 *               values. If $search is an array and $replace is a string, then this replacement string is used for
1035
 *               every value of search. This function is aimed at replacing the function str_ireplace() for
1036
 *               human-language strings.
1037
 *
1038
 * @see    http://php.net/manual/en/function.str-ireplace
1039
 *
1040
 * @author Henri Sivonen, mailto:[email protected]
1041
 *
1042
 * @see    http://hsivonen.iki.fi/php-utf8/
1043
 * Adaptation for Chamilo 1.8.7, 2010
1044
 * Initial implementation Dokeos LMS, August 2009
1045
 *
1046
 * @author Ivan Tcholakov
1047
 */
1048
function api_str_ireplace($search, $replace, $subject, &$count = null, $encoding = null)
1049
{
1050
    return str_ireplace($search, $replace, $subject, $count);
1051
}
1052
1053
/**
1054
 * Converts a string to an array.
1055
 *
1056
 * @param string $string       the input string
1057
 * @param int    $split_length maximum character-length of the chunk, one character by default
1058
 * @param string $encoding     (optional) The used internally by this function
1059
 *                             character encoding. If it is omitted, the platform character set will be used by
1060
 *                             default.
1061
 *
1062
 * @return array The result array of chunks with the spcified length.
1063
 *               Notes:
1064
 *               If the optional split_length parameter is specified, the returned array will be broken down into
1065
 *               chunks
1066
 *               with each being split_length in length, otherwise each chunk will be one character in length.
1067
 *               FALSE is returned if split_length is less than 1.
1068
 *               If the split_length length exceeds the length of string, the entire string is returned as the first
1069
 *               (and only) array element. This function is aimed at replacing the function str_split() for
1070
 *               human-language strings.
1071
 *
1072
 * @see http://php.net/str_split
1073
 */
1074
function api_str_split($string, $split_length = 1, $encoding = null)
1075
{
1076
    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...
1077
}
1078
1079
/**
1080
 * Finds position of first occurrence of a string within another, case insensitive.
1081
 *
1082
 * @param string $haystack the string from which to get the position of the first occurrence
1083
 * @param string $needle   the string to be found
1084
 * @param int    $offset   The position in $haystack to start searching from.
1085
 *                         If it is omitted, searching starts from the beginning.
1086
 * @param string $encoding (optional) The used internally by this function
1087
 *                         character encoding. If it is omitted, the platform character set will be used by default.
1088
 *
1089
 * @return mixed Returns the numeric position of the first occurrence of
1090
 *               $needle in the $haystack, or FALSE if $needle is not found.
1091
 *               Note: The first character's position is 0, the second character position is 1, and so on.
1092
 *               This function is aimed at replacing the functions stripos() and mb_stripos() for human-language
1093
 *               strings.
1094
 *
1095
 * @see http://php.net/manual/en/function.stripos
1096
 * @see http://php.net/manual/en/function.mb-stripos
1097
 */
1098
function api_stripos($haystack, $needle, $offset = 0, $encoding = null)
1099
{
1100
    return stripos($haystack, $needle, $offset);
1101
}
1102
1103
/**
1104
 * Finds first occurrence of a string within another, case insensitive.
1105
 *
1106
 * @param string $haystack      the string from which to get the first occurrence
1107
 * @param mixed  $needle        the string to be found
1108
 * @param bool   $before_needle (optional) Determines which portion of $haystack
1109
 *                              this function returns. The default value is FALSE.
1110
 * @param string $encoding      (optional) The used internally by this function
1111
 *                              character encoding. If it is omitted, the platform character set will be used by
1112
 *                              default.
1113
 *
1114
 * @return mixed Returns the portion of $haystack, or FALSE if $needle is not found.
1115
 *               Notes:
1116
 *               If $needle is not a string, it is converted to an integer and applied as the
1117
 *               ordinal value (codepoint if the encoding is UTF-8) of a character.
1118
 *               If $before_needle is set to TRUE, the function returns all of $haystack
1119
 *               from the beginning to the first occurrence of $needle.
1120
 *               If $before_needle is set to FALSE, the function returns all of $haystack f
1121
 *               rom the first occurrence of $needle to the end.
1122
 *               This function is aimed at replacing the functions stristr() and mb_stristr() for human-language
1123
 *               strings.
1124
 *
1125
 * @see http://php.net/manual/en/function.stristr
1126
 * @see http://php.net/manual/en/function.mb-stristr
1127
 */
1128
function api_stristr($haystack, $needle, $before_needle = false, $encoding = null)
1129
{
1130
    return stristr($haystack, $needle, $before_needle);
1131
}
1132
1133
/**
1134
 * Returns length of the input string.
1135
 *
1136
 * @param string $string   the string which length is to be calculated
1137
 * @param string $encoding (optional) The used internally by this function character encoding. If it is omitted, the
1138
 *                         platform character set will be used by default.
1139
 *
1140
 * @return int Returns the number of characters within the string. A multi-byte character is counted as 1.
1141
 *             This function is aimed at replacing the functions strlen() and mb_strlen() for human-language strings.
1142
 *
1143
 * @see http://php.net/manual/en/function.strlen
1144
 * @see http://php.net/manual/en/function.mb-strlen
1145
 * Note: When you use strlen() to test for an empty string, you needn't change it to api_strlen().
1146
 * For example, in lines like the following:
1147
 * if (strlen($string) > 0)
1148
 * if (strlen($string) != 0)
1149
 * there is no need the original function strlen() to be changed, it works correctly and faster for these cases.
1150
 */
1151
function api_strlen($string, $encoding = null)
1152
{
1153
    return strlen($string);
1154
}
1155
1156
/**
1157
 * Finds position of first occurrence of a string within another.
1158
 *
1159
 * @param string $haystack the string from which to get the position of the first occurrence
1160
 * @param string $needle   the string to be found
1161
 * @param int    $offset   (optional) The position in $haystack to start searching from. If it is omitted, searching
1162
 *                         starts from the beginning.
1163
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted, the
1164
 *                         platform character set will be used by default.
1165
 *
1166
 * @return mixed Returns the numeric position of the first occurrence of $needle in the $haystack, or FALSE if $needle
1167
 *               is not found. Note: The first character's position is 0, the second character position is 1, and so
1168
 *               on. This function is aimed at replacing the functions strpos() and mb_strpos() for human-language
1169
 *               strings.
1170
 *
1171
 * @see http://php.net/manual/en/function.strpos
1172
 * @see http://php.net/manual/en/function.mb-strpos
1173
 */
1174
function api_strpos($haystack, $needle, $offset = 0, $encoding = null)
1175
{
1176
    return strpos($haystack, $needle, $offset);
1177
}
1178
1179
/**
1180
 * Finds the last occurrence of a character in a string.
1181
 *
1182
 * @param string $haystack      the string from which to get the last occurrence
1183
 * @param mixed  $needle        the string which first character is to be found
1184
 * @param bool   $before_needle (optional) Determines which portion of $haystack this function returns. The default
1185
 *                              value is FALSE.
1186
 * @param string $encoding      (optional) The used internally by this function character encoding. If it is omitted,
1187
 *                              the platform character set will be used by default.
1188
 *
1189
 * @return mixed Returns the portion of $haystack, or FALSE if the first character from $needle is not found.
1190
 *               Notes:
1191
 *               If $needle is not a string, it is converted to an integer and applied as the ordinal value (codepoint
1192
 *               if the encoding is UTF-8) of a character. If $before_needle is set to TRUE, the function returns all
1193
 *               of $haystack from the beginning to the first occurrence. If $before_needle is set to FALSE, the
1194
 *               function returns all of $haystack from the first occurrence to the end. This function is aimed at
1195
 *               replacing the functions strrchr() and mb_strrchr() for human-language strings.
1196
 *
1197
 * @see http://php.net/manual/en/function.strrchr
1198
 * @see http://php.net/manual/en/function.mb-strrchr
1199
 */
1200
function api_strrchr($haystack, $needle, $before_needle = false, $encoding = null)
1201
{
1202
    return strrchr($haystack, $needle);
1203
}
1204
1205
/**
1206
 * Reverses a string.
1207
 *
1208
 * @param string $string   the string to be reversed
1209
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted, the
1210
 *                         platform character set will be used by default.
1211
 *
1212
 * @return string Returns the reversed string.
1213
 *                This function is aimed at replacing the function strrev() for human-language strings.
1214
 *
1215
 * @see http://php.net/manual/en/function.strrev
1216
 */
1217
function api_strrev($string, $encoding = null)
1218
{
1219
    return strrev($string);
1220
}
1221
1222
/**
1223
 * Finds the position of last occurrence (case insensitive) of a string in a string.
1224
 *
1225
 * @param string $haystack the string from which to get the position of the last occurrence
1226
 * @param string $needle   the string to be found
1227
 * @param int    $offset   (optional) $offset may be specified to begin searching an arbitrary position. Negative
1228
 *                         values will stop searching at an arbitrary point prior to the end of the string.
1229
 * @param string $encoding (optional) The used internally by this function character encoding. If it is omitted, the
1230
 *                         platform character set will be used by default.
1231
 *
1232
 * @return mixed Returns the numeric position of the first occurrence (case insensitive) of $needle in the $haystack,
1233
 *               or FALSE if $needle is not found. Note: The first character's position is 0, the second character
1234
 *               position is 1, and so on. This function is aimed at replacing the functions strripos() and
1235
 *               mb_strripos() for human-language strings.
1236
 *
1237
 * @see http://php.net/manual/en/function.strripos
1238
 * @see http://php.net/manual/en/function.mb-strripos
1239
 */
1240
function api_strripos($haystack, $needle, $offset = 0, $encoding = null)
1241
{
1242
    return strripos($haystack, $needle, $offset);
1243
}
1244
1245
/**
1246
 * Finds the position of last occurrence of a string in a string.
1247
 *
1248
 * @param string $haystack the string from which to get the position of the last occurrence
1249
 * @param string $needle   the string to be found
1250
 * @param int    $offset   (optional) $offset may be specified to begin searching an arbitrary position. Negative
1251
 *                         values will stop searching at an arbitrary point prior to the end of the string.
1252
 * @param string $encoding (optional) The used internally by this function character encoding. If it is omitted, the
1253
 *                         platform character set will be used by default.
1254
 *
1255
 * @return mixed Returns the numeric position of the first occurrence of $needle in the $haystack, or FALSE if $needle
1256
 *               is not found. Note: The first character's position is 0, the second character position is 1, and so
1257
 *               on. This function is aimed at replacing the functions strrpos() and mb_strrpos() for human-language
1258
 *               strings.
1259
 *
1260
 * @see http://php.net/manual/en/function.strrpos
1261
 * @see http://php.net/manual/en/function.mb-strrpos
1262
 */
1263
function api_strrpos($haystack, $needle, $offset = 0, $encoding = null)
1264
{
1265
    return strrpos($haystack, $needle, $offset);
1266
}
1267
1268
/**
1269
 * Finds first occurrence of a string within another.
1270
 *
1271
 * @param string $haystack      the string from which to get the first occurrence
1272
 * @param mixed  $needle        the string to be found
1273
 * @param bool   $before_needle (optional) Determines which portion of $haystack this function returns. The default
1274
 *                              value is FALSE.
1275
 * @param string $encoding      (optional) The used internally by this function character encoding. If it is omitted,
1276
 *                              the platform character set will be used by default.
1277
 *
1278
 * @return mixed Returns the portion of $haystack, or FALSE if $needle is not found.
1279
 *               Notes:
1280
 *               If $needle is not a string, it is converted to an integer and applied as the ordinal value (codepoint
1281
 *               if the encoding is UTF-8) of a character. If $before_needle is set to TRUE, the function returns all
1282
 *               of $haystack from the beginning to the first occurrence of $needle. If $before_needle is set to FALSE,
1283
 *               the function returns all of $haystack from the first occurrence of $needle to the end. This function
1284
 *               is aimed at replacing the functions strstr() and mb_strstr() for human-language strings.
1285
 *
1286
 * @see http://php.net/manual/en/function.strstr
1287
 * @see http://php.net/manual/en/function.mb-strstr
1288
 */
1289
function api_strstr($haystack, $needle, $before_needle = false, $encoding = null)
1290
{
1291
    return strstr($haystack, $needle, $before_needle);
1292
}
1293
1294
/**
1295
 * Makes a string lowercase.
1296
 *
1297
 * @param string $string   the string being lowercased
1298
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted, the
1299
 *                         platform character set will be used by default.
1300
 *
1301
 * @return string Returns the string with all alphabetic characters converted to lowercase.
1302
 *                This function is aimed at replacing the functions strtolower() and mb_strtolower() for human-language
1303
 *                strings.
1304
 *
1305
 * @see http://php.net/manual/en/function.strtolower
1306
 * @see http://php.net/manual/en/function.mb-strtolower
1307
 */
1308
function api_strtolower($string, $encoding = null)
1309
{
1310
    return strtolower($string);
1311
}
1312
1313
/**
1314
 * Makes a string uppercase.
1315
 *
1316
 * @param string $string   the string being uppercased
1317
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted, the
1318
 *                         platform character set will be used by default.
1319
 *
1320
 * @return string Returns the string with all alphabetic characters converted to uppercase.
1321
 *                This function is aimed at replacing the functions strtoupper() and mb_strtoupper() for human-language
1322
 *                strings.
1323
 *
1324
 * @see http://php.net/manual/en/function.strtoupper
1325
 * @see http://php.net/manual/en/function.mb-strtoupper
1326
 */
1327
function api_strtoupper($string, $encoding = null)
1328
{
1329
    return strtoupper($string);
1330
}
1331
1332
/**
1333
 * // Gets part of a string.
1334
 *
1335
 * @param string $string   the input string
1336
 * @param int    $start    the first position from which the extracted part begins
1337
 * @param int    $length   the length in character of the extracted part
1338
 * @param string $encoding (optional) The used internally by this function
1339
 *                         character encoding. If it is omitted, the platform character set will be used by default.
1340
 *
1341
 * @return string Returns the part of the string specified by the start and length parameters.
1342
 *                Note: First character's position is 0. Second character position is 1, and so on.
1343
 *                This function is aimed at replacing the functions substr() and mb_substr() for human-language strings.
1344
 *
1345
 * @see http://php.net/manual/en/function.substr
1346
 * @see http://php.net/manual/en/function.mb-substr
1347
 */
1348
function api_substr($string, $start, $length = null, $encoding = null)
1349
{
1350
    if (is_null($length)) {
1351
        $length = api_strlen($string, $encoding);
1352
    }
1353
1354
    return substr($string, $start, $length);
1355
}
1356
1357
/**
1358
 * Counts the number of substring occurrences.
1359
 *
1360
 * @param string $haystack the string being checked
1361
 * @param string $needle   the string being found
1362
 * @param string $encoding (optional) The used internally by this function character encoding.
1363
 *                         If it is omitted, the platform character set will be used by default.
1364
 *
1365
 * @return int the number of times the needle substring occurs in the haystack string
1366
 *
1367
 * @see http://php.net/manual/en/function.mb-substr-count.php
1368
 */
1369
function api_substr_count($haystack, $needle, $encoding = null)
1370
{
1371
    return substr_count($haystack, $needle);
1372
}
1373
1374
/**
1375
 * Replaces text within a portion of a string.
1376
 *
1377
 * @param string $string      the input string
1378
 * @param string $replacement the replacement string
1379
 * @param int    $start       The position from which replacing will begin.
1380
 *                            Notes:
1381
 *                            If $start is positive, the replacing will begin at the $start'th offset into the string.
1382
 *                            If $start is negative, the replacing will begin at the $start'th character from the end
1383
 *                            of the string.
1384
 * @param int    $length      (optional) The position where replacing will end.
1385
 *                            Notes:
1386
 *                            If given and is positive, it represents the length of the portion of the string which is
1387
 *                            to be replaced. If it is negative, it represents the number of characters from the end of
1388
 *                            string at which to stop replacing. If it is not given, then it will default to
1389
 *                            api_strlen($string); i.e. end the replacing at the end of string. If $length is zero,
1390
 *                            then this function will have the effect of inserting replacement into the string at the
1391
 *                            given start offset.
1392
 * @param string $encoding    (optional)    The used internally by this function character encoding.
1393
 *                            If it is omitted, the platform character set will be used by default.
1394
 *
1395
 * @return string The result string is returned.
1396
 *                This function is aimed at replacing the function substr_replace() for human-language strings.
1397
 *
1398
 * @see http://php.net/manual/function.substr-replace
1399
 */
1400
function api_substr_replace($string, $replacement, $start, $length = null, $encoding = null)
1401
{
1402
    if (is_null($length)) {
1403
        $length = api_strlen($string);
1404
    }
1405
1406
    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...
1407
}
1408
1409
/**
1410
 * Makes a string's first character uppercase.
1411
 *
1412
 * @param string $string   the input string
1413
 * @param string $encoding (optional)    The used internally by this function character encoding.
1414
 *                         If it is omitted, the platform character set will be used by default.
1415
 *
1416
 * @return string Returns a string with the first character capitalized, if that character is alphabetic.
1417
 *                This function is aimed at replacing the function ucfirst() for human-language strings.
1418
 *
1419
 * @see http://php.net/manual/en/function.ucfirst
1420
 */
1421
function api_ucfirst($string, $encoding = null)
1422
{
1423
    return ucfirst($string);
1424
}
1425
1426
/**
1427
 * Uppercases the first character of each word in a string.
1428
 *
1429
 * @param string $string   the input string
1430
 * @param string $encoding (optional) The used internally by this function character encoding.
1431
 *                         If it is omitted, the platform character set will be used by default.
1432
 *
1433
 * @return string Returns the modified string.
1434
 *                This function is aimed at replacing the function ucwords() for human-language strings.
1435
 *
1436
 * @see http://php.net/manual/en/function.ucwords
1437
 */
1438
function api_ucwords($string, $encoding = null)
1439
{
1440
    return ucwords($string);
1441
}
1442
1443
/**
1444
 * Performs a regular expression match, UTF-8 aware when it is applicable.
1445
 *
1446
 * @param string  $pattern  the pattern to search for, as a string
1447
 * @param string  $subject  the input string
1448
 * @param array  &$matches  (optional) If matches is provided,
1449
 *                          then it is filled with the results of search (as an array).
1450
 *                          $matches[0] will contain the text that matched the full pattern, $matches[1] will have the
1451
 *                          text that matched the first captured parenthesized subpattern, and so on.
1452
 * @param int     $flags    (optional) Could be PREG_OFFSET_CAPTURE. If this flag is passed, for every occurring match
1453
 *                          the appendant string offset will also be returned. Note that this changes the return value
1454
 *                          in an array where every element is an array consisting of the matched string at index 0 and
1455
 *                          its string offset into subject at index 1.
1456
 * @param int     $offset   (optional)        Normally, the search starts from the beginning of the subject string. The
1457
 *                          optional parameter offset can be used to specify the alternate place from which to start
1458
 *                          the search.
1459
 * @param string  $encoding (optional)    The used internally by this function character encoding. If it is omitted,
1460
 *                          the platform character set will be used by default.
1461
 *
1462
 * @return int|bool returns the number of times pattern matches or FALSE if an error occurred
1463
 *
1464
 * @see http://php.net/preg_match
1465
 */
1466
function api_preg_match(
1467
    $pattern,
1468
    $subject,
1469
    &$matches = null,
1470
    $flags = 0,
1471
    $offset = 0,
1472
    $encoding = null
1473
) {
1474
    if (empty($encoding)) {
1475
        $encoding = _api_mb_internal_encoding();
1476
    }
1477
1478
    return preg_match(api_is_utf8($encoding) ? $pattern.'u' : $pattern, $subject, $matches, $flags, $offset);
1479
}
1480
1481
/**
1482
 * Performs a global regular expression match, UTF-8 aware when it is applicable.
1483
 *
1484
 * @param string  $pattern  the pattern to search for, as a string
1485
 * @param string  $subject  the input string
1486
 * @param array  &$matches  (optional)    Array of all matches in multi-dimensional array ordered according to $flags
1487
 * @param int     $flags    (optional)            Can be a combination of the following flags (note that it doesn't
1488
 *                          make sense to use PREG_PATTERN_ORDER together with PREG_SET_ORDER): PREG_PATTERN_ORDER -
1489
 *                          orders results so that $matches[0] is an array of full pattern matches, $matches[1] is an
1490
 *                          array of strings matched by the first parenthesized subpattern, and so on; PREG_SET_ORDER -
1491
 *                          orders results so that $matches[0] is an array of first set of matches, $matches[1] is an
1492
 *                          array of second set of matches, and so on; PREG_OFFSET_CAPTURE - If this flag is passed,
1493
 *                          for every occurring match the appendant string offset will also be returned. Note that this
1494
 *                          changes the value of matches in an array where every element is an array consisting of the
1495
 *                          matched string at offset 0 and its string offset into subject at offset 1. If no order flag
1496
 *                          is given, PREG_PATTERN_ORDER is assumed.
1497
 * @param int     $offset   (optional)        Normally, the search starts from the beginning of the subject string. The
1498
 *                          optional parameter offset can be used to specify the alternate place from which to start
1499
 *                          the search.
1500
 * @param string  $encoding (optional)    The used internally by this function character encoding. If it is omitted,
1501
 *                          the platform character set will be used by default.
1502
 *
1503
 * @return int|bool returns the number of full pattern matches (which might be zero), or FALSE if an error occurred
1504
 *
1505
 * @see http://php.net/preg_match_all
1506
 */
1507
function api_preg_match_all($pattern, $subject, &$matches, $flags = PREG_PATTERN_ORDER, $offset = 0, $encoding = null)
1508
{
1509
    if (empty($encoding)) {
1510
        $encoding = _api_mb_internal_encoding();
1511
    }
1512
    if (is_null($flags)) {
1513
        $flags = PREG_PATTERN_ORDER;
1514
    }
1515
1516
    return preg_match_all(api_is_utf8($encoding) ? $pattern.'u' : $pattern, $subject, $matches, $flags, $offset);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match_all(ap...tches, $flags, $offset) also could return the type null which is incompatible with the documented return type boolean|integer.
Loading history...
1517
}
1518
1519
/**
1520
 * Performs a regular expression search and replace, UTF-8 aware when it is applicable.
1521
 *
1522
 * @param string|array  $pattern     The pattern to search for. It can be either a string or an array with strings.
1523
 * @param string|array  $replacement the string or an array with strings to replace
1524
 * @param string|array  $subject     the string or an array with strings to search and replace
1525
 * @param int           $limit       The maximum possible replacements for each pattern in each subject string.
1526
 *                                   Defaults to -1 (no limit).
1527
 * @param int          &$count       If specified, this variable will be filled with the number of replacements done
1528
 * @param string        $encoding    (optional)    The used internally by this function character encoding.
1529
 *                                   If it is omitted, the platform character set will be used by default.
1530
 *
1531
 * @return array|string|null returns an array if the subject parameter is an array, or a string otherwise.
1532
 *                           If matches are found, the new subject will be returned, otherwise subject will be returned
1533
 *                           unchanged or NULL if an error occurred.
1534
 *
1535
 * @see http://php.net/preg_replace
1536
 */
1537
function api_preg_replace($pattern, $replacement, $subject, $limit = -1, $count = 0, $encoding = null)
1538
{
1539
    if (empty($encoding)) {
1540
        $encoding = _api_mb_internal_encoding();
1541
    }
1542
    $is_utf8 = api_is_utf8($encoding);
1543
    if (is_array($pattern)) {
1544
        foreach ($pattern as &$p) {
1545
            $p = $is_utf8 ? $p.'u' : $p;
1546
        }
1547
    } else {
1548
        $pattern = $is_utf8 ? $pattern.'u' : $pattern;
1549
    }
1550
1551
    return preg_replace($pattern, $replacement, $subject, $limit, $count);
1552
}
1553
1554
/**
1555
 * Splits a string by a regular expression, UTF-8 aware when it is applicable.
1556
 *
1557
 * @param string $pattern  the pattern to search for, as a string
1558
 * @param string $subject  the input string
1559
 * @param int    $limit    (optional)            If specified, then only substrings up to $limit are returned with the
1560
 *                         rest of the string being placed in the last substring. A limit of -1, 0 or null means "no
1561
 *                         limit" and, as is standard across PHP.
1562
 * @param int    $flags    (optional)            $flags can be any combination of the following flags (combined with
1563
 *                         bitwise | operator): PREG_SPLIT_NO_EMPTY - if this flag is set, only non-empty pieces will
1564
 *                         be returned; PREG_SPLIT_DELIM_CAPTURE - if this flag is set, parenthesized expression in the
1565
 *                         delimiter pattern will be captured and returned as well; PREG_SPLIT_OFFSET_CAPTURE - If this
1566
 *                         flag is set, for every occurring match the appendant string offset will also be returned.
1567
 *                         Note that this changes the return value in an array where every element is an array
1568
 *                         consisting of the matched string at offset 0 and its string offset into subject at offset 1.
1569
 * @param string $encoding (optional)    The used internally by this function character encoding. If it is omitted, the
1570
 *                         platform character set will be used by default.
1571
 *
1572
 * @return array returns an array containing substrings of $subject split along boundaries matched by $pattern
1573
 *
1574
 * @see http://php.net/preg_split
1575
 */
1576
function api_preg_split($pattern, $subject, $limit = -1, $flags = 0, $encoding = null)
1577
{
1578
    if (empty($encoding)) {
1579
        $encoding = _api_mb_internal_encoding();
1580
    }
1581
1582
    return preg_split(api_is_utf8($encoding) ? $pattern.'u' : $pattern, $subject, $limit, $flags);
1583
}
1584
1585
/**
1586
 * String comparison.
1587
 */
1588
1589
/**
1590
 * Performs string comparison, case insensitive, language sensitive, with extended multibyte support.
1591
 *
1592
 * @param string $string1  the first string
1593
 * @param string $string2  the second string
1594
 * @param string $language (optional) The language in which comparison is to be made. If language is omitted, interface
1595
 *                         language is assumed then.
1596
 * @param string $encoding (optional) The used internally by this function character encoding. If it is omitted, the
1597
 *                         platform character set will be used by default.
1598
 *
1599
 * @return int Returns < 0 if $string1 is less than $string2; > 0 if $string1 is greater than $string2; and 0 if the
1600
 *             strings are equal. This function is aimed at replacing the function strcasecmp() for human-language
1601
 *             strings.
1602
 *
1603
 * @see http://php.net/manual/en/function.strcasecmp
1604
 */
1605
function api_strcasecmp($string1, $string2, $language = null, $encoding = null)
1606
{
1607
    return api_strcmp(api_strtolower($string1, $encoding), api_strtolower($string2, $encoding), $language, $encoding);
1608
}
1609
1610
/**
1611
 * Performs string comparison, case sensitive, language sensitive, with extended multibyte support.
1612
 *
1613
 * @param string $string1  the first string
1614
 * @param string $string2  the second string
1615
 * @param string $language (optional)    The language in which comparison is to be made. If language is omitted,
1616
 *                         interface language is assumed then.
1617
 * @param string $encoding (optional)    The used internally by this function character encoding.
1618
 *                         If it is omitted, the platform character set will be used by default.
1619
 *
1620
 * @return int Returns < 0 if $string1 is less than $string2; > 0 if $string1 is greater than $string2; and 0 if the
1621
 *             strings are equal. This function is aimed at replacing the function strcmp() for human-language strings.
1622
 *
1623
 * @see http://php.net/manual/en/function.strcmp.php
1624
 * @see http://php.net/manual/en/collator.compare.php
1625
 */
1626
function api_strcmp($string1, $string2, $language = null, $encoding = null)
1627
{
1628
    return strcmp($string1, $string2);
1629
}
1630
1631
/**
1632
 * Performs string comparison in so called "natural order", case sensitive, language sensitive, with extended multibyte
1633
 * support.
1634
 *
1635
 * @param string $string1  the first string
1636
 * @param string $string2  the second string
1637
 * @param string $language (optional)    The language in which comparison is to be made. If language is omitted,
1638
 *                         interface language is assumed then.
1639
 * @param string $encoding (optional)    The used internally by this function character encoding.
1640
 *                         If it is omitted, the platform character set will be used by default.
1641
 *
1642
 * @return int Returns < 0 if $string1 is less than $string2; > 0 if $string1 is greater than $string2; and 0 if the
1643
 *             strings are equal. This function is aimed at replacing the function strnatcmp() for human-language
1644
 *             strings.
1645
 *
1646
 * @see http://php.net/manual/en/function.strnatcmp.php
1647
 * @see http://php.net/manual/en/collator.compare.php
1648
 */
1649
function api_strnatcmp($string1, $string2, $language = null, $encoding = null)
1650
{
1651
    return strnatcmp($string1, $string2);
1652
}
1653
1654
/**
1655
 * Sorting arrays.
1656
 */
1657
1658
/**
1659
 * Sorts an array using natural order algorithm.
1660
 *
1661
 * @param array  $array    the input array
1662
 * @param string $language (optional)    The language in which comparison is to be made. If language is omitted,
1663
 *                         interface language is assumed then.
1664
 * @param string $encoding (optional)    The used internally by this function character encoding.
1665
 *                         If it is omitted, the platform character set will be used by default.
1666
 *
1667
 * @return bool Returns TRUE on success, FALSE on error.
1668
 *              This function is aimed at replacing the function natsort() for sorting human-language strings.
1669
 *
1670
 * @see http://php.net/manual/en/function.natsort.php
1671
 */
1672
function api_natsort(&$array, $language = null, $encoding = null)
1673
{
1674
    return natsort($array);
1675
}
1676
1677
/**
1678
 * Sorts an array using natural order algorithm in reverse order.
1679
 *
1680
 * @param array  $array    the input array
1681
 * @param string $language (optional)    The language in which comparison is to be made. If language is omitted,
1682
 *                         interface language is assumed then.
1683
 * @param string $encoding (optional)    The used internally by this function character encoding.
1684
 *                         If it is omitted, the platform character set will be used by default.
1685
 *
1686
 * @return bool returns TRUE on success, FALSE on error
1687
 */
1688
function api_natrsort(&$array, $language = null, $encoding = null)
1689
{
1690
    return uasort($array, '_api_strnatrcmp');
1691
}
1692
1693
/**
1694
 * Encoding management functions.
1695
 */
1696
1697
/**
1698
 * This function unifies the encoding identificators, so they could be compared.
1699
 *
1700
 * @param string|array $encoding the specified encoding
1701
 *
1702
 * @return string returns the encoding identificator modified in suitable for comparison way
1703
 */
1704
function api_refine_encoding_id($encoding)
1705
{
1706
    if (is_array($encoding)) {
1707
        return array_map('api_refine_encoding_id', $encoding);
1708
    }
1709
1710
    return strtoupper(str_replace('_', '-', $encoding));
1711
}
1712
1713
/**
1714
 * This function checks whether two $encoding are equal (same, equvalent).
1715
 *
1716
 * @param string|array $encoding1 The first encoding
1717
 * @param string|array $encoding2 The second encoding
1718
 * @param bool         $strict    When this parameter is TRUE the comparison ignores aliases of encodings.
1719
 *                                When the parameter is FALSE, aliases are taken into account.
1720
 *
1721
 * @return bool returns TRUE if the encodings are equal, FALSE otherwise
1722
 */
1723
function api_equal_encodings($encoding1, $encoding2, $strict = false)
1724
{
1725
    static $equal_encodings = [];
1726
    if (is_array($encoding1)) {
1727
        foreach ($encoding1 as $encoding) {
1728
            if (api_equal_encodings($encoding, $encoding2, $strict)) {
1729
                return true;
1730
            }
1731
        }
1732
1733
        return false;
1734
    } elseif (is_array($encoding2)) {
1735
        foreach ($encoding2 as $encoding) {
1736
            if (api_equal_encodings($encoding1, $encoding, $strict)) {
1737
                return true;
1738
            }
1739
        }
1740
1741
        return false;
1742
    }
1743
    if (!isset($equal_encodings[$encoding1][$encoding2][$strict])) {
1744
        $encoding_1 = api_refine_encoding_id($encoding1);
1745
        $encoding_2 = api_refine_encoding_id($encoding2);
1746
        if ($encoding_1 == $encoding_2) {
1747
            $result = true;
1748
        } else {
1749
            if ($strict) {
1750
                $result = false;
1751
            } else {
1752
                $alias1 = _api_get_character_map_name($encoding_1);
1753
                $alias2 = _api_get_character_map_name($encoding_2);
1754
                $result = !empty($alias1) && !empty($alias2) && $alias1 == $alias2;
1755
            }
1756
        }
1757
        $equal_encodings[$encoding1][$encoding2][$strict] = $result;
1758
    }
1759
1760
    return $equal_encodings[$encoding1][$encoding2][$strict];
1761
}
1762
1763
/**
1764
 * This function checks whether a given encoding is UTF-8.
1765
 *
1766
 * @param string $encoding the tested encoding
1767
 *
1768
 * @return bool returns TRUE if the given encoding id means UTF-8, otherwise returns false
1769
 */
1770
function api_is_utf8($encoding)
1771
{
1772
    static $result = [];
1773
    if (!isset($result[$encoding])) {
1774
        $result[$encoding] = api_equal_encodings($encoding, 'UTF-8');
1775
    }
1776
1777
    return $result[$encoding];
1778
}
1779
1780
/**
1781
 * This function returns the encoding, currently used by the system.
1782
 *
1783
 * @return string The system's encoding.
1784
 *                Note: The value of api_get_setting('platform_charset') is tried to be returned first,
1785
 *                on the second place the global variable $charset is tried to be returned. If for some
1786
 *                reason both attempts fail, then the libraly's internal value will be returned.
1787
 */
1788
function api_get_system_encoding()
1789
{
1790
    return 'UTF-8';
1791
}
1792
1793
/**
1794
 * Checks whether a specified encoding is supported by this API.
1795
 *
1796
 * @param string $encoding the specified encoding
1797
 *
1798
 * @return bool returns TRUE when the specified encoding is supported, FALSE othewise
1799
 */
1800
function api_is_encoding_supported($encoding)
1801
{
1802
    static $supported = [];
1803
    if (!isset($supported[$encoding])) {
1804
        $supported[$encoding] = _api_mb_supports($encoding) || _api_iconv_supports(
1805
                $encoding
1806
            ) || _api_convert_encoding_supports($encoding);
1807
    }
1808
1809
    return $supported[$encoding];
1810
}
1811
1812
/**
1813
 * Detects encoding of plain text.
1814
 *
1815
 * @param string $string   the input text
1816
 * @param string $language (optional) The language of the input text, provided if it is known
1817
 *
1818
 * @return string returns the detected encoding
1819
 */
1820
function api_detect_encoding($string, $language = null)
1821
{
1822
    // Testing against valid UTF-8 first.
1823
    if (api_is_valid_utf8($string)) {
1824
        return 'UTF-8';
1825
    }
1826
1827
    return mb_detect_encoding($string);
1828
}
1829
1830
/**
1831
 * String validation functions concerning certain encodings.
1832
 */
1833
1834
/**
1835
 * Checks a string for UTF-8 validity.
1836
 *
1837
 * @param string $string
1838
 *
1839
 * @return string
1840
 */
1841
function api_is_valid_utf8($string)
1842
{
1843
    return mb_check_encoding($string, 'UTF-8');
1844
}
1845
1846
/**
1847
 * Checks whether a string contains 7-bit ASCII characters only.
1848
 *
1849
 * @param string $string the string to be tested/validated
1850
 *
1851
 * @return bool returns TRUE when the tested string contains 7-bit
1852
 *              ASCII characters only, FALSE othewise
1853
 */
1854
function api_is_valid_ascii(&$string)
1855
{
1856
    return 'ASCII' == mb_detect_encoding($string, 'ASCII', true) ? true : false;
1857
}
1858
1859
/**
1860
 * Return true a date is valid.
1861
 *
1862
 * @param string $date   example: 2014-06-30 13:05:05
1863
 * @param string $format example: "Y-m-d H:i:s"
1864
 *
1865
 * @return bool
1866
 */
1867
function api_is_valid_date($date, $format = 'Y-m-d H:i:s')
1868
{
1869
    $d = DateTime::createFromFormat($format, $date);
1870
1871
    return $d && $d->format($format) == $date;
1872
}
1873
1874
/**
1875
 * Returns the variable translated.
1876
 *
1877
 * @param string $variable   the string to translate
1878
 * @param string $pluginName the Plugin name
1879
 *
1880
 * @return string the variable translated
1881
 */
1882
function get_plugin_lang($variable, $pluginName)
1883
{
1884
    $plugin = $pluginName::create();
1885
1886
    return $plugin->get_lang($variable);
1887
}
1888
1889
/**
1890
 * Returns an array of translated week days and months, short and normal names.
1891
 *
1892
 * @param string $language (optional Language id. If it is omitted,
1893
 *                         the current interface language is assumed.
1894
 *
1895
 * @return array returns a multidimensional array with translated week days and months
1896
 */
1897
function &_api_get_day_month_names($language = null)
1898
{
1899
    static $date_parts = [];
1900
    if (empty($language)) {
1901
        $language = api_get_language_isocode();
1902
    }
1903
    if (!isset($date_parts[$language])) {
1904
        $week_day = [
1905
            'Sunday',
1906
            'Monday',
1907
            'Tuesday',
1908
            'Wednesday',
1909
            'Thursday',
1910
            'Friday',
1911
            'Saturday',
1912
        ];
1913
        $month = [
1914
            'January',
1915
            'February',
1916
            'March',
1917
            'April',
1918
            'May',
1919
            'June',
1920
            'July',
1921
            'August',
1922
            'September',
1923
            'October',
1924
            'November',
1925
            'December',
1926
        ];
1927
        for ($i = 0; $i < 7; $i++) {
1928
            $date_parts[$language]['days_short'][] = get_lang(
1929
                $week_day[$i].'Short',
1930
                '',
1931
                $language
1932
            );
1933
            $date_parts[$language]['days_long'][] = get_lang(
1934
                $week_day[$i].'Long',
1935
                '',
1936
                $language
1937
            );
1938
        }
1939
        for ($i = 0; $i < 12; $i++) {
1940
            $date_parts[$language]['months_short'][] = get_lang(
1941
                $month[$i].'Short',
1942
                '',
1943
                $language
1944
            );
1945
            $date_parts[$language]['months_long'][] = get_lang(
1946
                $month[$i].'Long',
1947
                '',
1948
                $language
1949
            );
1950
        }
1951
    }
1952
1953
    return $date_parts[$language];
1954
}
1955
1956
function getIsoToLegacy()
1957
{
1958
    return array_flip(getLegacyToIso());
1959
}
1960
1961
function getLegacyToIso()
1962
{
1963
    return [
1964
        'asturian' => 'ast_ES',
1965
        'basque' => 'eu_ES',
1966
        'bengali' => 'bn',
1967
        'bosnian' => 'bs',
1968
        'brazilian' => 'pt_BR',
1969
        'bulgarian' => 'bg',
1970
        'catalan' => 'ca_ES',
1971
        'croatian' => 'hr',
1972
        'czech' => 'cs',
1973
        'danish' => 'da',
1974
        'dari' => 'fa_AF',
1975
        'dutch' => 'nl',
1976
        'english' => 'en_US',
1977
        'estonian' => 'et',
1978
        'esperanto' => 'eo',
1979
        'faroese' => 'fo',
1980
        'finnish' => 'fi',
1981
        'french' => 'fr',
1982
        'friulian' => 'fur_IT',
1983
        'galician' => 'gl_ES',
1984
        'georgian' => 'ka',
1985
        'german' => 'de',
1986
        'greek' => 'el',
1987
        'hebrew' => 'he',
1988
        'hindi' => 'hi',
1989
        'hungarian' => 'hu',
1990
        'indonesian' => 'id',
1991
        'italian' => 'it',
1992
        'japanese' => 'ja',
1993
        'korean' => 'ko',
1994
        'latvian' => 'lv',
1995
        'lithuanian' => 'lt',
1996
        'macedonian' => 'mk',
1997
        'malay' => 'ms',
1998
        'norwegian' => 'nn',
1999
        'occitan' => 'oc_FR',
2000
        'pashto' => 'ps',
2001
        'persian' => 'fa',
2002
        'polish' => 'pl',
2003
        'portuguese' => 'pt',
2004
        'quechua_cusco' => 'qu',
2005
        'romanian' => 'ro',
2006
        'russian' => 'ru',
2007
        'serbian' => 'sr',
2008
        'simpl_chinese' => 'zh_CN',
2009
        'slovak' => 'sk',
2010
        'slovenian' => 'sl',
2011
        'somali' => 'so',
2012
        'spanish' => 'es',
2013
        'spanish_latin' => 'es_MX',
2014
        'swahili' => 'sw',
2015
        'swedish' => 'sv',
2016
        'tagalog' => 'tl',
2017
        'thai' => 'th',
2018
        'tibetan' => 'bo',
2019
        'trad_chinese' => 'zh_TW',
2020
        'turkish' => 'tr',
2021
        'ukrainian' => 'uk',
2022
        'vietnamese' => 'vi',
2023
        'xhosa' => 'xh',
2024
        'yoruba' => 'yo',
2025
    ];
2026
}
2027
2028
/**
2029
 * Returns returns person name convention for a given language.
2030
 *
2031
 * @param string $iso
2032
 * @param string $type The type of the requested convention.
2033
 *                     It may be 'format' for name order convention or 'sort_by' for name sorting convention.
2034
 *
2035
 * @return mixed Depending of the requested type,
2036
 *               the returned result may be string or boolean; null is returned on error;
2037
 */
2038
function _api_get_person_name_convention($iso, $type)
2039
{
2040
    $conventions = LanguageFixtures::getLanguages();
2041
2042
    // Overwrite classic conventions
2043
    //$customConventions = api_get_configuration_value('name_order_conventions');
2044
2045
    $search1 = ['FIRST_NAME', 'LAST_NAME', 'TITLE'];
2046
    $replacement1 = ['%F', '%L', '%T'];
2047
    $search2 = ['first_name', 'last_name', 'title'];
2048
    $replacement2 = ['%f', '%l', '%t'];
2049
    $conventionsFormatted = [];
2050
    foreach ($conventions as $language) {
2051
        $iso = $language['isocode'];
2052
        $conventionsFormatted[$iso]['format'] = $language['format'];
2053
        $conventionsFormatted[$iso]['sort_by'] = $language['sort_by'];
2054
2055
        $conventionsFormatted[$iso]['format'] = str_replace(
2056
            $search1,
2057
            $replacement1,
2058
            $conventionsFormatted[$iso]['format']
2059
        );
2060
        $conventionsFormatted[$iso]['format'] = _api_validate_person_name_format(
2061
            _api_clean_person_name(
2062
                str_replace(
2063
                    '%',
2064
                    ' %',
2065
                    str_ireplace(
2066
                        $search2,
2067
                        $replacement2,
2068
                        $conventionsFormatted[$iso]['format']
2069
                    )
2070
                )
2071
            )
2072
        );
2073
2074
        $conventionsFormatted[$iso]['sort_by'] = 'last_name' !== strtolower(
2075
            $conventionsFormatted[$iso]['sort_by']
2076
        ) ? true : false;
2077
    }
2078
2079
    switch ($type) {
2080
        case 'format':
2081
            return is_string(
2082
                $conventionsFormatted[$iso]['format']
2083
            ) ? $conventionsFormatted[$iso]['format'] : '%t %f %l';
2084
        case 'sort_by':
2085
            return is_bool($conventionsFormatted[$iso]['sort_by']) ? $conventionsFormatted[$iso]['sort_by'] : true;
2086
    }
2087
2088
    return null;
2089
}
2090
2091
/**
2092
 * Replaces non-valid formats for person names with the default (English) format.
2093
 *
2094
 * @param string $format the input format to be verified
2095
 *
2096
 * @return bool returns the same format if is is valid, otherwise returns a valid English format
2097
 */
2098
function _api_validate_person_name_format($format)
2099
{
2100
    if (empty($format) || false === stripos($format, '%f') || false === stripos($format, '%l')) {
2101
        return '%t %f %l';
2102
    }
2103
2104
    return $format;
2105
}
2106
2107
/**
2108
 * Removes leading, trailing and duplicate whitespace and/or commas in a full person name.
2109
 * Cleaning is needed for the cases when not all parts of the name are available
2110
 * or when the name is constructed using a "dirty" pattern.
2111
 *
2112
 * @param string $person_name the input person name
2113
 *
2114
 * @return string returns cleaned person name
2115
 */
2116
function _api_clean_person_name($person_name)
2117
{
2118
    return preg_replace(['/\s+/', '/, ,/', '/,+/', '/^[ ,]/', '/[ ,]$/'], [' ', ', ', ',', '', ''], $person_name);
2119
}
2120
2121
/**
2122
 * This function determines the name of corresponding to a given encoding conversion table.
2123
 * It is able to deal with some aliases of the encoding.
2124
 *
2125
 * @param string $encoding the given encoding identificator, for example 'WINDOWS-1252'
2126
 *
2127
 * @return string returns the name of the corresponding conversion table, for the same example - 'CP1252'
2128
 */
2129
function _api_get_character_map_name($encoding)
2130
{
2131
    static $character_map_selector;
2132
    if (!isset($character_map_selector)) {
2133
        $file = __DIR__.'/internationalization_database/conversion/character_map_selector.php';
2134
        if (file_exists($file)) {
2135
            $character_map_selector = include $file;
2136
        } else {
2137
            $character_map_selector = [];
2138
        }
2139
    }
2140
2141
    return isset($character_map_selector[$encoding]) ? $character_map_selector[$encoding] : '';
2142
}
2143
2144
/**
2145
 * Appendix to "String comparison".
2146
 */
2147
2148
/**
2149
 * A reverse function from php-core function strnatcmp(),
2150
 * performs string comparison in reverse natural (alpha-numerical) order.
2151
 *
2152
 * @param string $string1 the first string
2153
 * @param string $string2 the second string
2154
 *
2155
 * @return int returns 0 if $string1 = $string2; >0 if $string1 < $string2; <0 if $string1 > $string2
2156
 */
2157
function _api_strnatrcmp($string1, $string2)
2158
{
2159
    return strnatcmp($string2, $string1);
2160
}
2161
2162
/**
2163
 * Sets/Gets internal character encoding of the common string functions within the PHP mbstring extension.
2164
 *
2165
 * @param string $encoding (optional)    When this parameter is given, the function sets the internal encoding
2166
 *
2167
 * @return string When $encoding parameter is not given, the function returns the internal encoding.
2168
 *                Note: This function is used in the global initialization script for setting the
2169
 *                internal encoding to the platform's character set.
2170
 *
2171
 * @see http://php.net/manual/en/function.mb-internal-encoding
2172
 */
2173
function _api_mb_internal_encoding($encoding = 'UTF-8')
2174
{
2175
    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...
2176
}
2177
2178
/**
2179
 * Checks whether the specified encoding is supported by the PHP mbstring extension.
2180
 *
2181
 * @param string $encoding the specified encoding
2182
 *
2183
 * @return bool returns TRUE when the specified encoding is supported, FALSE othewise
2184
 */
2185
function _api_mb_supports($encoding)
2186
{
2187
    static $supported = [];
2188
    if (!isset($supported[$encoding])) {
2189
        if (MBSTRING_INSTALLED) {
2190
            $supported[$encoding] = api_equal_encodings($encoding, mb_list_encodings(), true);
2191
        } else {
2192
            $supported[$encoding] = false;
2193
        }
2194
    }
2195
2196
    return $supported[$encoding];
2197
}
2198
2199
/**
2200
 * Checks whether the specified encoding is supported by the PHP iconv extension.
2201
 *
2202
 * @param string $encoding the specified encoding
2203
 *
2204
 * @return bool returns TRUE when the specified encoding is supported, FALSE othewise
2205
 */
2206
function _api_iconv_supports($encoding)
2207
{
2208
    static $supported = [];
2209
    if (!isset($supported[$encoding])) {
2210
        if (ICONV_INSTALLED) {
2211
            $enc = api_refine_encoding_id($encoding);
2212
            if ('HTML-ENTITIES' != $enc) {
2213
                $test_string = '';
2214
                for ($i = 32; $i < 128; $i++) {
2215
                    $test_string .= chr($i);
2216
                }
2217
                $supported[$encoding] = (@iconv_strlen($test_string, $enc)) ? true : false;
2218
            } else {
2219
                $supported[$encoding] = false;
2220
            }
2221
        } else {
2222
            $supported[$encoding] = false;
2223
        }
2224
    }
2225
2226
    return $supported[$encoding];
2227
}
2228
2229
// This function checks whether the function _api_convert_encoding() (the php-
2230
// implementation) is able to convert from/to a given encoding.
2231
function _api_convert_encoding_supports($encoding)
2232
{
2233
    static $supports = [];
2234
    if (!isset($supports[$encoding])) {
2235
        $supports[$encoding] = '' != _api_get_character_map_name(api_refine_encoding_id($encoding));
2236
    }
2237
2238
    return $supports[$encoding];
2239
}
2240
2241
/**
2242
 * Given a date object, return a human or ISO format, with or without h:m:s.
2243
 *
2244
 * @param object $date      The Date object
2245
 * @param bool   $showTime  Whether to show the time and date (true) or only the date (false)
2246
 * @param bool   $humanForm Whether to show day-month-year (true) or year-month-day (false)
2247
 *
2248
 * @return string Formatted date
2249
 */
2250
function api_get_human_date_time($date, $showTime = true, $humanForm = false)
2251
{
2252
    if ($showTime) {
2253
        if ($humanForm) {
2254
            return $date->format('j M Y H:i:s');
2255
        } else {
2256
            return $date->format('Y-m-d H:i:s');
2257
        }
2258
    } else {
2259
        if ($humanForm) {
2260
            return $date->format('j M Y');
2261
        } else {
2262
            return $date->format('Y-m-d');
2263
        }
2264
    }
2265
}
2266