functions.php ➔ snapshot()   F
last analyzed

Complexity

Conditions 29
Paths > 20000

Size

Total Lines 205
Code Lines 143

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 29
eloc 143
nc 128744
nop 0
dl 0
loc 205
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Licensed under The GPL-3.0 License
4
 * For full copyright and license information, please see the LICENSE.txt
5
 * Redistributions of files must retain the above copyright notice.
6
 *
7
 * @since    2.0.0
8
 * @author   Christopher Castro <[email protected]>
9
 * @link     http://www.quickappscms.org
10
 * @license  http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License
11
 */
12
13
use Cake\Cache\Cache;
14
use Cake\Core\Configure;
15
use Cake\Datasource\ConnectionManager;
16
use Cake\Error\Debugger;
17
use Cake\Error\FatalErrorException;
18
use Cake\Event\EventManager;
19
use Cake\Filesystem\File;
20
use Cake\Filesystem\Folder;
21
use Cake\I18n\I18n;
22
use Cake\ORM\Entity;
23
use Cake\ORM\TableRegistry;
24
use Cake\Routing\Router;
25
use Cake\Utility\Inflector;
26
use CMS\Core\Plugin;
27
28
if (!function_exists('snapshot')) {
29
    /**
30
     * Stores some bootstrap-handy information into a persistent file.
31
     *
32
     * Information is stored in `TMP/snapshot.php` file, it contains
33
     * useful information such as enabled languages, content types slugs, installed
34
     * plugins, etc.
35
     *
36
     * You can read this information using `Configure::read()` as follow:
37
     *
38
     * ```php
39
     * Configure::read('QuickApps.<option>');
40
     * ```
41
     *
42
     * Or using the `quickapps()` global function:
43
     *
44
     * ```php
45
     * quickapps('<option>');
46
     * ```
47
     *
48
     * @return void
49
     */
50
    function snapshot()
51
    {
52
        if (Cache::config('default')) {
53
            Cache::clear(false, 'default');
54
        }
55
56
        if (Cache::config('_cake_core_')) {
57
            Cache::clear(false, '_cake_core_');
58
        }
59
60
        if (Cache::config('_cake_model_')) {
61
            Cache::clear(false, '_cake_model_');
62
        }
63
64
        $versionPath = QUICKAPPS_CORE . 'VERSION.txt';
65
        $snapshot = [
66
            'version' => null,
67
            'content_types' => [],
68
            'plugins' => [],
69
            'options' => [],
70
            'languages' => [],
71
            'aspects' => [],
72
        ];
73
74
        if (is_readable($versionPath)) {
75
            $versionFile = file($versionPath);
76
            $snapshot['version'] = trim(array_pop($versionFile));
77
        } else {
78
            die(sprintf('Missing file: %s', $versionPath));
79
        }
80
81
        if (ConnectionManager::config('default')) {
82
            if (!TableRegistry::exists('SnapshotPlugins')) {
83
                $PluginTable = TableRegistry::get('SnapshotPlugins', ['table' => 'plugins']);
84
            } else {
85
                $PluginTable = TableRegistry::get('SnapshotPlugins');
86
            }
87
88
            if (!TableRegistry::exists('SnapshotContentTypes')) {
89
                $ContentTypesTable = TableRegistry::get('SnapshotContentTypes', ['table' => 'content_types']);
90
            } else {
91
                $ContentTypesTable = TableRegistry::get('SnapshotContentTypes');
92
            }
93
94
            if (!TableRegistry::exists('SnapshotLanguages')) {
95
                $LanguagesTable = TableRegistry::get('SnapshotLanguages', ['table' => 'languages']);
96
            } else {
97
                $LanguagesTable = TableRegistry::get('SnapshotLanguages');
98
            }
99
100
            if (!TableRegistry::exists('SnapshotOptions')) {
101
                $OptionsTable = TableRegistry::get('SnapshotOptions', ['table' => 'options']);
102
            } else {
103
                $OptionsTable = TableRegistry::get('SnapshotOptions');
104
            }
105
106
            $PluginTable->schema(['value' => 'serialized']);
107
            $OptionsTable->schema(['value' => 'serialized']);
108
109
            $plugins = $PluginTable->find()
110
                ->select(['name', 'package', 'status'])
111
                ->order([
112
                    'ordering' => 'ASC',
113
                    'name' => 'ASC',
114
                ])
115
                ->all();
116
            $contentTypes = $ContentTypesTable->find()
117
                ->select(['slug'])
118
                ->all();
119
            $languages = $LanguagesTable->find()
120
                ->where(['status' => 1])
121
                ->order(['ordering' => 'ASC'])
122
                ->all();
123
            $options = $OptionsTable->find()
124
                ->select(['name', 'value'])
125
                ->where(['autoload' => 1])
126
                ->all();
127
128
            foreach ($contentTypes as $contentType) {
129
                $snapshot['content_types'][] = $contentType->slug;
130
            }
131
132
            foreach ($options as $option) {
133
                $snapshot['options'][$option->name] = $option->value;
134
            }
135
136
            foreach ($languages as $language) {
137
                list($languageCode, $countryCode) = localeSplit($language->code);
138
                $snapshot['languages'][$language->code] = [
139
                    'name' => $language->name,
140
                    'locale' => $language->code,
141
                    'code' => $languageCode,
142
                    'country' => $countryCode,
143
                    'direction' => $language->direction,
144
                    'icon' => $language->icon,
145
                ];
146
            }
147
        } else {
148
            $plugins = [];
149
            foreach (Plugin::scan() as $plugin => $path) {
150
                $plugins[] = new Entity([
151
                    'name' => $plugin,
152
                    'status' => true,
153
                    'package' => 'quickapps-plugins',
154
                ]);
155
            }
156
        }
157
158
        $folder = new Folder(QUICKAPPS_CORE . 'src/Aspect/');
159
        foreach ($folder->read(false, false, true)[1] as $classFile) {
160
            $className = basename(preg_replace('/\.php$/', '', $classFile));
161
            if (!in_array($className, ['AppAspect', 'Aspect'])) {
162
                $snapshot['aspects'][] = "CMS\\Aspect\\{$className}";
163
            }
164
        }
165
166
        foreach ($plugins as $plugin) {
167
            $pluginPath = false;
168
169
            if (isset(Plugin::scan()[$plugin->name])) {
170
                $pluginPath = Plugin::scan()[$plugin->name];
171
            }
172
173
            if ($pluginPath === false) {
174
                Debugger::log(sprintf('Plugin "%s" was found in DB but QuickAppsCMS was unable to locate its root directory.', $plugin->name));
175
                continue;
176
            }
177
178
            if (!Plugin::validateJson("{$pluginPath}/composer.json")) {
179
                Debugger::log(sprintf('Plugin "%s" has a corrupt "composer.json" file (%s).', $plugin->name, "{$pluginPath}/composer.json"));
180
                continue;
181
            }
182
183
            $aspectsPath = "{$pluginPath}/src/Aspect/";
184
            $eventsPath = "{$pluginPath}/src/Event/";
185
            $fieldsPath = "{$pluginPath}/src/Field/";
186
            $helpFiles = glob($pluginPath . '/src/Template/Element/Help/help*.ctp');
187
            $isTheme = str_ends_with($plugin->name, 'Theme');
188
            $status = (bool)$plugin->status;
189
            $humanName = '';
190
            $aspects = [];
191
            $eventListeners = [];
192
            $fields = [];
193
194
            $subspaces = [
195
                $aspectsPath => 'Aspect',
196
                $eventsPath => 'Event',
197
                $fieldsPath => 'Field',
198
            ];
199
            $varnames = [
200
                $aspectsPath => 'aspects',
201
                $eventsPath => 'eventListeners',
202
                $fieldsPath => 'fields',
203
            ];
204
            foreach ([$aspectsPath, $eventsPath, $fieldsPath] as $path) {
205
                if (is_dir($path)) {
206
                    $Folder = new Folder($path);
207
                    foreach ($Folder->read(false, false, true)[1] as $classFile) {
208
                        $className = basename(preg_replace('/\.php$/', '', $classFile));
209
                        $subspace = $subspaces[$path];
210
                        $varname = $varnames[$path];
211
                        $namespace = "{$plugin->name}\\{$subspace}\\";
212
                        ${$varname}[] = $namespace . $className;
213
                    }
214
                }
215
            }
216
217
            if (is_readable("{$pluginPath}composer.json")) {
218
                $json = (array)json_decode(file_get_contents("{$pluginPath}composer.json"), true);
219
                if (!empty($json['extra']['human-name'])) {
220
                    $humanName = $json['extra']['human-name'];
221
                }
222
            }
223
224
            if (empty($humanName)) {
225
                $humanName = (string)Inflector::humanize((string)Inflector::underscore($plugin->name));
226
                if ($isTheme) {
227
                    $humanName = trim(str_replace_last('Theme', '', $humanName));
228
                }
229
            }
230
231
            $snapshot['plugins'][$plugin->name] = [
232
                'name' => $plugin->name,
233
                'humanName' => $humanName,
234
                'package' => $plugin->package,
235
                'isTheme' => $isTheme,
236
                'hasHelp' => !empty($helpFiles),
237
                'hasSettings' => is_readable($pluginPath . '/src/Template/Element/settings.ctp'),
238
                'aspects' => $aspects,
239
                'eventListeners' => $eventListeners,
240
                'fields' => $fields,
241
                'status' => $status,
242
                'path' => $pluginPath,
243
            ];
244
245
            if ($status) {
246
                $snapshot['aspects'] = array_merge($snapshot['aspects'], $aspects);
247
            }
248
        }
249
250
        Configure::write('QuickApps', $snapshot);
251
        if (!Configure::dump('snapshot', 'QuickApps', ['QuickApps'])) {
252
            die('QuickAppsCMS was unable to create a snapshot file, check that PHP have permission to write to the "/tmp" directory.');
253
        }
254
    }
255
}
256
257
if (!function_exists('normalizePath')) {
258
    /**
259
     * Normalizes the given file system path, makes sure that all DIRECTORY_SEPARATOR
260
     * are the same according to current OS, so you won't get a mix of "/" and "\" in
261
     * your paths.
262
     *
263
     * ### Example:
264
     *
265
     * ```php
266
     * normalizePath('/path\to/filename\with\backslash.zip');
267
     * // output LINUX: /path/to/filename\with\backslashes.zip
268
     * // output WINDOWS: /path/to/filename/with/backslashes.zip
269
     * ```
270
     *
271
     * You can indicate which "directory separator" symbol to use using the second
272
     * argument:
273
     *
274
     * ```php
275
     * normalizePath('/path\to/filename\with\backslash.zip', '\');
276
     * // output LINUX & WIDNOWS: \path\to\filename\with\backslash.zip
277
     * ```
278
     *
279
     * By defaults uses DIRECTORY_SEPARATOR as symbol.
280
     *
281
     * @param string $path The path to normalize
282
     * @param string $ds Directory separator character, defaults to DIRECTORY_SEPARATOR
283
     * @return string Normalized $path
284
     */
285
    function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
286
    {
287
        $tail = '';
288
        $base = $path;
289
290
        if (DIRECTORY_SEPARATOR === '/') {
291
            $lastDS = strrpos($path, $ds);
292
            $tail = $lastDS !== false && $lastDS !== strlen($path) - 1 ? substr($path, $lastDS + 1) : '';
293
            $base = $tail ? substr($path, 0, $lastDS + 1) : $path;
294
        }
295
296
        $path = str_replace(['/', '\\', "{$ds}{$ds}"], $ds, $base);
297
        $path = str_replace("{$ds}{$ds}", $ds, $path);
298
        $path .= $tail;
299
300
        return $path;
301
    }
302
}
303
304
if (!function_exists('quickapps')) {
305
    /**
306
     * Shortcut for reading QuickApps's snapshot configuration.
307
     *
308
     * For example, `quickapps('variables');` maps to
309
     * `Configure::read('QuickApps.variables');`. If this function is used with
310
     * no arguments, `quickapps()`, the entire snapshot will be returned.
311
     *
312
     * @param string $key The key to read from snapshot, or null to read the whole
313
     *  snapshot's info
314
     * @return mixed
315
     */
316
    function quickapps($key = null)
317
    {
318
        if ($key !== null) {
319
            return Configure::read("QuickApps.{$key}");
320
        }
321
322
        return Configure::read('QuickApps');
323
    }
324
}
325
326
if (!function_exists('option')) {
327
    /**
328
     * Shortcut for getting an option value from "options" DB table.
329
     *
330
     * The second arguments, $default,  is used as default value to return if no
331
     * value is found. If not value is found and not default values was given this
332
     * function will return `false`.
333
     *
334
     * ### Example:
335
     *
336
     * ```php
337
     * option('site_slogan');
338
     * ```
339
     *
340
     * @param string $name Name of the option to retrieve. e.g. `front_theme`,
341
     *  `default_language`, `site_slogan`, etc
342
     * @param mixed $default The default value to return if no value is found
343
     * @return mixed Current value for the specified option. If the specified option
344
     *  does not exist, returns boolean FALSE
345
     */
346
    function option($name, $default = false)
347
    {
348
        if (Configure::check("QuickApps.options.{$name}")) {
349
            return Configure::read("QuickApps.options.{$name}");
350
        }
351
352
        if (ConnectionManager::config('default')) {
353
            $option = TableRegistry::get('Options')
354
                ->find()
355
                ->where(['Options.name' => $name])
356
                ->first();
357
            if ($option) {
358
                return $option->value;
359
            }
360
        }
361
362
        return $default;
363
    }
364
}
365
366
if (!function_exists('plugin')) {
367
    /**
368
     * Shortcut for "Plugin::get()".
369
     *
370
     * ### Example:
371
     *
372
     * ```php
373
     * $specialSetting = plugin('MyPlugin')->settings['special_setting'];
374
     * ```
375
     *
376
     * @param string $plugin Plugin name to get, or null to get a collection of
377
     *  all plugin objects
378
     * @return \CMS\Core\Package\PluginPackage|\Cake\Collection\Collection
379
     * @throws \Cake\Error\FatalErrorException When requested plugin was not found
380
     * @see \CMS\Core\Plugin::get()
381
     */
382
    function plugin($plugin = null)
383
    {
384
        return Plugin::get($plugin);
385
    }
386
}
387
388
if (!function_exists('theme')) {
389
    /**
390
     * Gets the given (or in use) theme as a package object.
391
     *
392
     * ### Example:
393
     *
394
     * ```php
395
     * // current theme
396
     * $bgColor = theme()->settings['background_color'];
397
     *
398
     * // specific theme
399
     * $bgColor = theme('BlueTheme')->settings['background_color'];
400
     * ```
401
     *
402
     * @param string|null $name Name of the theme to get, or null to get the theme
403
     *  being used in current request
404
     * @return \CMS\Core\Package\PluginPackage
405
     * @throws \Cake\Error\FatalErrorException When theme could not be found
406
     */
407
    function theme($name = null)
408
    {
409
        if ($name === null) {
410
            $option = Router::getRequest()->isAdmin() ? 'back_theme' : 'front_theme';
411
            $name = option($option);
412
        }
413
414
        $theme = Plugin::get()
415
            ->filter(function ($plugin) use ($name) {
416
                return $plugin->isTheme && $plugin->name == $name;
417
            })
418
            ->first();
419
420
        if ($theme) {
421
            return $theme;
422
        }
423
424
        throw new FatalErrorException(__d('cms', 'Theme "{0}" was not found', $name));
425
    }
426
}
427
428
if (!function_exists('listeners')) {
429
    /**
430
     * Returns a list of all registered event listeners within the provided event
431
     * manager, or within the global manager if not provided.
432
     *
433
     * @param \Cake\Event\EventManager\null $manager Event manager instance, or null
434
     *  to use global manager instance.
435
     * @return array
436
     */
437
    function listeners(EventManager $manager = null)
438
    {
439
        if ($manager === null) {
440
            $manager = EventManager::instance();
441
        }
442
        $class = new \ReflectionClass($manager);
443
        $property = $class->getProperty('_listeners');
444
        $property->setAccessible(true);
445
        $listeners = array_keys($property->getValue($manager));
446
447
        return $listeners;
448
    }
449
}
450
451
if (!function_exists('packageSplit')) {
452
    /**
453
     * Splits a composer package syntax into its vendor and package name.
454
     *
455
     * Commonly used like `list($vendor, $package) = packageSplit($name);`
456
     *
457
     * ### Example:
458
     *
459
     * ```php
460
     * list($vendor, $package) = packageSplit('some-vendor/this-package', true);
461
     * echo "{$vendor} : {$package}";
462
     * // prints: SomeVendor : ThisPackage
463
     * ```
464
     *
465
     * @param string $name Package name. e.g. author-name/package-name
466
     * @param bool $camelize Set to true to Camelize each part
467
     * @return array Array with 2 indexes. 0 => vendor name, 1 => package name.
468
     */
469
    function packageSplit($name, $camelize = false)
470
    {
471
        $pos = strrpos($name, '/');
472
        if ($pos === false) {
473
            $parts = ['', $name];
474
        } else {
475
            $parts = [substr($name, 0, $pos), substr($name, $pos + 1)];
476
        }
477
        if ($camelize) {
478
            $parts[0] = Inflector::camelize(str_replace('-', '_', $parts[0]));
479
            if (!empty($parts[1])) {
480
                $parts[1] = Inflector::camelize(str_replace('-', '_', $parts[1]));
481
            }
482
        }
483
484
        return $parts;
485
    }
486
}
487
488
if (!function_exists('normalizeLocale')) {
489
    /**
490
     * Normalizes the given locale code.
491
     *
492
     * @param string $locale The locale code to normalize. e.g. `en-US`
493
     * @return string Normalized code. e.g. `en_US`
494
     */
495
    function normalizeLocale($locale)
496
    {
497
        list($language, $region) = localeSplit($locale);
498
499
        return !empty($region) ? "{$language}_{$region}" : $language;
500
    }
501
}
502
503
if (!function_exists('aspects')) {
504
    /**
505
     * Gets a list of all active aspect classes.
506
     *
507
     * @return array
508
     */
509
    function aspects()
510
    {
511
        return quickapps('aspects');
512
    }
513
}
514
515
if (!function_exists('localeSplit')) {
516
    /**
517
     * Parses and splits the given locale code and returns its parts: language and
518
     * regional codes.
519
     *
520
     * ### Example:
521
     *
522
     * ```php
523
     * list($language, $region) = localeSplit('en_NZ');
524
     * ```
525
     *
526
     * IMPORTANT: Note that region code may be an empty string.
527
     *
528
     * @param string $localeId Locale code. e.g. "en_NZ" (or "en-NZ") for
529
     *  "English New Zealand"
530
     * @return array Array with 2 indexes. 0 => language code, 1 => country code.
531
     */
532
    function localeSplit($localeId)
533
    {
534
        $localeId = str_replace('-', '_', $localeId);
535
        $parts = explode('_', $localeId);
536
        $country = isset($parts[1]) ? strtoupper($parts[1]) : '';
537
        $language = strtolower($parts[0]);
538
539
        return [$language, $country];
540
    }
541
}
542
543
if (!function_exists('array_move')) {
544
    /**
545
     * Moves up or down the given element by index from a list array of elements.
546
     *
547
     * If item could not be moved, the original list will be returned. Valid values
548
     * for $direction are `up` or `down`.
549
     *
550
     * ### Example:
551
     *
552
     * ```php
553
     * array_move(['a', 'b', 'c'], 1, 'up');
554
     * // returns: ['a', 'c', 'b']
555
     * ```
556
     *
557
     * @param array $list Numeric indexed array list of elements
558
     * @param int $index The index position of the element you want to move
559
     * @param string $direction Direction, 'up' or 'down'
560
     * @return array Reordered original list.
561
     */
562
    function array_move(array $list, $index, $direction)
563
    {
564
        $maxIndex = count($list) - 1;
565
        if ($direction == 'down') {
566
            if (0 < $index && $index <= $maxIndex) {
567
                $item = $list[$index];
568
                $list[$index] = $list[$index - 1];
569
                $list[$index - 1] = $item;
570
            }
571
        } elseif ($direction == 'up') {
572
            if ($index >= 0 && $maxIndex > $index) {
573
                $item = $list[$index];
574
                $list[$index] = $list[$index + 1];
575
                $list[$index + 1] = $item;
576
577
                return $list;
578
            }
579
        }
580
581
        return $list;
582
    }
583
}
584
585
if (!function_exists('php_eval')) {
586
    /**
587
     * Evaluate a string of PHP code.
588
     *
589
     * This is a wrapper around PHP's eval(). It uses output buffering to capture both
590
     * returned and printed text. Unlike eval(), we require code to be surrounded by
591
     * <?php ?> tags; in other words, we evaluate the code as if it were a stand-alone
592
     * PHP file.
593
     *
594
     * Using this wrapper also ensures that the PHP code which is evaluated can not
595
     * overwrite any variables in the calling code, unlike a regular eval() call.
596
     *
597
     * ### Usage:
598
     *
599
     * ```php
600
     * echo php_eval('<?php return "Hello {$world}!"; ?>', ['world' => 'WORLD']);
601
     * // output: Hello WORLD
602
     * ```
603
     *
604
     * @param string $code The code to evaluate
605
     * @param array $args Array of arguments as `key` => `value` pairs, evaluated
606
     *  code can access this variables
607
     * @return string
608
     */
609
    function php_eval($code, $args = [])
610
    {
611
        ob_start();
612
        extract($args);
613
        print eval('?>' . $code);
614
        $output = ob_get_contents();
615
        ob_end_clean();
616
617
        return $output;
618
    }
619
}
620
621
if (!function_exists('get_this_class_methods')) {
622
    /**
623
     * Return only the methods for the given object. It will strip out inherited
624
     * methods.
625
     *
626
     * @param string $class Class name
627
     * @return array List of methods
628
     */
629
    function get_this_class_methods($class)
630
    {
631
        $primary = get_class_methods($class);
632
633
        if ($parent = get_parent_class($class)) {
634
            $secondary = get_class_methods($parent);
635
            $methods = array_diff($primary, $secondary);
636
        } else {
637
            $methods = $primary;
638
        }
639
640
        return $methods;
641
    }
642
}
643
644 View Code Duplication
if (!function_exists('str_replace_once')) {
645
    /**
646
     * Replace the first occurrence only.
647
     *
648
     * ### Example:
649
     *
650
     * ```php
651
     * echo str_replace_once('A', 'a', 'AAABBBCCC');
652
     * // out: aAABBBCCC
653
     * ```
654
     *
655
     * @param string|array $search The value being searched for
656
     * @param string $replace The replacement value that replaces found search value
657
     * @param string $subject The string being searched and replaced on
658
     * @return string A string with the replaced value
659
     */
660
    function str_replace_once($search, $replace, $subject)
661
    {
662
        if (!is_array($search)) {
663
            $search = [$search];
664
        }
665
666
        foreach ($search as $s) {
667
            if ($s !== '' && strpos($subject, $s) !== false) {
668
                return substr_replace($subject, $replace, strpos($subject, $s), strlen($s));
669
            }
670
        }
671
672
        return $subject;
673
    }
674
}
675
676 View Code Duplication
if (!function_exists('str_replace_last')) {
677
    /**
678
     * Replace the last occurrence only.
679
     *
680
     * ### Example:
681
     *
682
     * ```php
683
     * echo str_replace_once('A', 'a', 'AAABBBCCC');
684
     * // out: AAaBBBCCC
685
     * ```
686
     *
687
     * @param string|array $search The value being searched for
688
     * @param string $replace The replacement value that replaces found search value
689
     * @param string $subject The string being searched and replaced on
690
     * @return string A string with the replaced value
691
     */
692
    function str_replace_last($search, $replace, $subject)
693
    {
694
        if (!is_array($search)) {
695
            $search = [$search];
696
        }
697
698
        foreach ($search as $s) {
699
            if ($s !== '' && strrpos($subject, $s) !== false) {
700
                $subject = substr_replace($subject, $replace, strrpos($subject, $s), strlen($s));
701
            }
702
        }
703
704
        return $subject;
705
    }
706
}
707
708
if (!function_exists('str_starts_with')) {
709
    /**
710
     * Check if $haystack string starts with $needle string.
711
     *
712
     * ### Example:
713
     *
714
     * ```php
715
     * str_starts_with('lorem ipsum', 'lo'); // true
716
     * str_starts_with('lorem ipsum', 'ipsum'); // false
717
     * ```
718
     *
719
     * @param string $haystack The string to search in
720
     * @param string $needle The string to look for
721
     * @return bool
722
     */
723
    function str_starts_with($haystack, $needle)
724
    {
725
        return
726
            $needle === '' ||
727
            strpos($haystack, $needle) === 0;
728
    }
729
}
730
731
if (!function_exists('str_ends_with')) {
732
    /**
733
     * Check if $haystack string ends with $needle string.
734
     *
735
     * ### Example:
736
     *
737
     * ```php
738
     * str_ends_with('lorem ipsum', 'm'); // true
739
     * str_ends_with('dolorem sit amet', 'at'); // false
740
     * ```
741
     *
742
     * @param string $haystack The string to search in
743
     * @param string $needle The string to look for
744
     * @return bool
745
     */
746
    function str_ends_with($haystack, $needle)
747
    {
748
        return
749
            $needle === '' ||
750
            substr($haystack, - strlen($needle)) === $needle;
751
    }
752
}
753