• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

timber / timber / 5690057835

pending completion
5690057835

push

github

nlemoine
Merge branch '2.x' of github.com:timber/timber into 2.x-refactor-file-models

# Conflicts:
#	src/Attachment.php
#	src/ExternalImage.php
#	src/FileSize.php
#	src/URLHelper.php

1134 of 1134 new or added lines in 55 files covered. (100.0%)

3923 of 4430 relevant lines covered (88.56%)

59.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.25
/src/Twig.php
1
<?php
2

3
namespace Timber;
4

5
use DateInterval;
6
use DateTime;
7
use DateTimeInterface;
8
use Exception;
9

10
use Timber\Factory\PostFactory;
11
use Timber\Factory\TermFactory;
12
use Twig\Environment;
13
use Twig\Extension\CoreExtension;
14
use Twig\TwigFilter;
15
use Twig\TwigFunction;
16

17
/**
18
 * Class Twig
19
 */
20
class Twig
21
{
22
    public static $dir_name;
23

24
    /**
25
     * @codeCoverageIgnore
26
     */
27
    public static function init()
28
    {
29
        $self = new self();
30

31
        \add_filter('timber/twig', [$self, 'add_timber_functions']);
32
        \add_filter('timber/twig', [$self, 'add_timber_filters']);
33
        \add_filter('timber/twig', [$self, 'add_timber_escapers']);
34

35
        \add_filter('timber/loader/twig', [$self, 'set_defaults']);
36
    }
37

38
    /**
39
     * Get Timber default functions
40
     *
41
     * @return array Default Timber functions
42
     */
43
    public function get_timber_functions()
44
    {
45
        $post_factory = new PostFactory();
405✔
46
        $termFactory = new TermFactory();
405✔
47

48
        $functions = [
405✔
49
            'action' => [
405✔
50
                'callable' => function ($action_name, ...$args) {
405✔
51
                    \do_action_ref_array($action_name, $args);
1✔
52
                },
405✔
53
            ],
405✔
54
            'function' => [
405✔
55
                'callable' => [$this, 'exec_function'],
405✔
56
            ],
405✔
57
            'fn' => [
405✔
58
                'callable' => [$this, 'exec_function'],
405✔
59
            ],
405✔
60
            'get_post' => [
405✔
61
                'callable' => [Timber::class, 'get_post'],
405✔
62
            ],
405✔
63
            'get_image' => [
405✔
64
                'callable' => [Timber::class, 'get_image'],
405✔
65
            ],
405✔
66
            'get_external_image' => [
405✔
67
                'callable' => [Timber::class, 'get_external_image'],
405✔
68
            ],
405✔
69
            'get_attachment' => [
405✔
70
                'callable' => [Timber::class, 'get_attachment'],
405✔
71
            ],
405✔
72
            'get_posts' => [
405✔
73
                'callable' => [Timber::class, 'get_posts'],
405✔
74
            ],
405✔
75
            'get_attachment_by' => [
405✔
76
                'callable' => [Timber::class, 'get_attachment_by'],
405✔
77
            ],
405✔
78
            'get_term' => [
405✔
79
                'callable' => [Timber::class, 'get_term'],
405✔
80
            ],
405✔
81
            'get_terms' => [
405✔
82
                'callable' => [Timber::class, 'get_terms'],
405✔
83
            ],
405✔
84
            'get_user' => [
405✔
85
                'callable' => [Timber::class, 'get_user'],
405✔
86
            ],
405✔
87
            'get_users' => [
405✔
88
                'callable' => [Timber::class, 'get_users'],
405✔
89
            ],
405✔
90
            'get_comment' => [
405✔
91
                'callable' => [Timber::class, 'get_comment'],
405✔
92
            ],
405✔
93
            'get_comments' => [
405✔
94
                'callable' => [Timber::class, 'get_comments'],
405✔
95
            ],
405✔
96
            'Post' => [
405✔
97
                'callable' => function ($post_id) use ($post_factory) {
405✔
98
                    Helper::deprecated('{{ Post() }}', '{{ get_post() }} or {{ get_posts() }}', '2.0.0');
2✔
99
                    return $post_factory->from($post_id);
2✔
100
                },
405✔
101
                'options' => [
405✔
102
                    'deprecated' => true,
405✔
103
                ],
405✔
104
            ],
405✔
105
            'TimberPost' => [
405✔
106
                'callable' => function ($post_id) use ($post_factory) {
405✔
107
                    Helper::deprecated('{{ TimberPost() }}', '{{ get_post() }} or {{ get_posts() }}', '2.0.0');
2✔
108
                    return $post_factory->from($post_id);
2✔
109
                },
405✔
110
                'options' => [
405✔
111
                    'deprecated' => true,
405✔
112
                ],
405✔
113
            ],
405✔
114
            'Image' => [
405✔
115
                'callable' => function ($post_id) use ($post_factory) {
405✔
116
                    Helper::deprecated('{{ Image() }}', '{{ get_image() }}, {{ get_post() }}, {{ get_posts() }}, {{ get_attachment() }} or {{ get_attachment_by() }}', '2.0.0');
2✔
117
                    return $post_factory->from($post_id);
2✔
118
                },
405✔
119
                'options' => [
405✔
120
                    'deprecated' => true,
405✔
121
                ],
405✔
122
            ],
405✔
123
            'TimberImage' => [
405✔
124
                'callable' => function ($post_id) use ($post_factory) {
405✔
125
                    Helper::deprecated('{{ TimberImage() }}', '{{ get_image() }}, {{ get_post() }}, {{ get_posts() }}, {{ get_attachment() }} or {{ get_attachment_by() }}', '2.0.0');
2✔
126
                    return $post_factory->from($post_id);
2✔
127
                },
405✔
128
                'options' => [
405✔
129
                    'deprecated' => true,
405✔
130
                ],
405✔
131
            ],
405✔
132
            'Term' => [
405✔
133
                'callable' => function ($term_id) use ($termFactory) {
405✔
134
                    Helper::deprecated('{{ Term() }}', '{{ get_term() }} or {{ get_terms() }}', '2.0.0');
2✔
135
                    return $termFactory->from($term_id);
2✔
136
                },
405✔
137
                'options' => [
405✔
138
                    'deprecated' => true,
405✔
139
                ],
405✔
140
            ],
405✔
141
            'TimberTerm' => [
405✔
142
                'callable' => function ($term_id) use ($termFactory) {
405✔
143
                    Helper::deprecated('{{ TimberTerm() }}', '{{ get_term() }} or {{ get_terms() }}', '2.0.0');
2✔
144
                    return $termFactory->from($term_id);
2✔
145
                },
405✔
146
                'options' => [
405✔
147
                    'deprecated' => true,
405✔
148
                ],
405✔
149
            ],
405✔
150
            'User' => [
405✔
151
                'callable' => function ($user_id) {
405✔
152
                    Helper::deprecated('{{ User() }}', '{{ get_user() }} or {{ get_users() }}', '2.0.0');
2✔
153
                    return Timber::get_user($user_id);
2✔
154
                },
405✔
155
                'options' => [
405✔
156
                    'deprecated' => true,
405✔
157
                ],
405✔
158
            ],
405✔
159
            'TimberUser' => [
405✔
160
                'callable' => function ($user_id) {
405✔
161
                    Helper::deprecated('{{ TimberUser() }}', '{{ get_user() }} or {{ get_users() }}', '2.0.0');
2✔
162
                    return Timber::get_user($user_id);
2✔
163
                },
405✔
164
                'options' => [
405✔
165
                    'deprecated' => true,
405✔
166
                ],
405✔
167
            ],
405✔
168
            'shortcode' => [
405✔
169
                'callable' => 'do_shortcode',
405✔
170
            ],
405✔
171
            'bloginfo' => [
405✔
172
                'callable' => 'bloginfo',
405✔
173
            ],
405✔
174

175
            // Translation functions.
176
            '__' => [
405✔
177
                'callable' => '__',
405✔
178
            ],
405✔
179
            'translate' => [
405✔
180
                'callable' => 'translate',
405✔
181
            ],
405✔
182
            '_e' => [
405✔
183
                'callable' => '_e',
405✔
184
            ],
405✔
185
            '_n' => [
405✔
186
                'callable' => '_n',
405✔
187
            ],
405✔
188
            '_x' => [
405✔
189
                'callable' => '_x',
405✔
190
            ],
405✔
191
            '_ex' => [
405✔
192
                'callable' => '_ex',
405✔
193
            ],
405✔
194
            '_nx' => [
405✔
195
                'callable' => '_nx',
405✔
196
            ],
405✔
197
            '_n_noop' => [
405✔
198
                'callable' => '_n_noop',
405✔
199
            ],
405✔
200
            '_nx_noop' => [
405✔
201
                'callable' => '_nx_noop',
405✔
202
            ],
405✔
203
            'translate_nooped_plural' => [
405✔
204
                'callable' => 'translate_nooped_plural',
405✔
205
            ],
405✔
206
        ];
405✔
207

208
        /**
209
         * Filters the functions that are added to Twig.
210
         *
211
         * The `$functions` array is an associative array with the filter name as a key and an
212
         * arguments array as the value. In the arguments array, you pass the function to call with
213
         * a `callable` entry.
214
         *
215
         * This is an alternative filter that you can use instead of adding your function in the
216
         * `timber/twig` filter.
217
         *
218
         * @api
219
         * @since 2.0.0
220
         * @example
221
         * ```php
222
         * add_filter( 'timber/twig/functions', function( $functions ) {
223
         *     // Add your own function.
224
         *     $functions['url_to_domain'] = [
225
         *         'callable' => 'url_to_domain',
226
         *     ];
227
         *
228
         *     // Replace a function.
229
         *     $functions['get_image'] = [
230
         *         'callable' => 'custom_image_get',
231
         *     ];
232
         *
233
         *     // Remove a function.
234
         *     unset( $functions['bloginfo'] );
235
         *
236
         *     return $functions;
237
         * } );
238
         * ```
239
         *
240
         * @param array $functions
241
         */
242
        $functions = \apply_filters('timber/twig/functions', $functions);
405✔
243

244
        return $functions;
405✔
245
    }
246

247
    /**
248
     * Adds Timber-specific functions to Twig.
249
     *
250
     * @param \Twig\Environment $twig The Twig Environment.
251
     *
252
     * @return \Twig\Environment
253
     */
254
    public function add_timber_functions($twig)
255
    {
256
        foreach ($this->get_timber_functions() as $name => $function) {
405✔
257
            $twig->addFunction(
405✔
258
                new TwigFunction(
405✔
259
                    $name,
405✔
260
                    $function['callable'],
405✔
261
                    $function['options'] ?? []
405✔
262
                )
405✔
263
            );
405✔
264
        }
265

266
        return $twig;
405✔
267
    }
268

269
    /**
270
     * Get Timber default filters
271
     *
272
     * @return array Default Timber filters
273
     */
274
    public function get_timber_filters()
275
    {
276
        $filters = [
405✔
277
            /* image filters */
278
            'resize' => [
405✔
279
                'callable' => ['Timber\ImageHelper', 'resize'],
405✔
280
            ],
405✔
281
            'retina' => [
405✔
282
                'callable' => ['Timber\ImageHelper', 'retina_resize'],
405✔
283
            ],
405✔
284
            'letterbox' => [
405✔
285
                'callable' => ['Timber\ImageHelper', 'letterbox'],
405✔
286
            ],
405✔
287
            'tojpg' => [
405✔
288
                'callable' => ['Timber\ImageHelper', 'img_to_jpg'],
405✔
289
            ],
405✔
290
            'towebp' => [
405✔
291
                'callable' => ['Timber\ImageHelper', 'img_to_webp'],
405✔
292
            ],
405✔
293

294
            // Debugging filters.
295
            'get_class' => [
405✔
296
                'callable' => function ($obj) {
405✔
297
                    Helper::deprecated('{{ my_object | get_class }}', "{{ function('get_class', my_object) }}", '2.0.0');
1✔
298
                    return \get_class($obj);
1✔
299
                },
405✔
300
                'options' => [
405✔
301
                    'deprecated' => true,
405✔
302
                ],
405✔
303
            ],
405✔
304
            'print_r' => [
405✔
305
                'callable' => function ($arr) {
405✔
306
                    Helper::deprecated('{{ my_object | print_r }}', '{{ dump(my_object) }}', '2.0.0');
×
307
                    return \print_r($arr, true);
×
308
                },
405✔
309
                'options' => [
405✔
310
                    'deprecated' => true,
405✔
311
                ],
405✔
312
            ],
405✔
313

314
            // Other filters.
315
            'stripshortcodes' => [
405✔
316
                'callable' => 'strip_shortcodes',
405✔
317
            ],
405✔
318
            'array' => [
405✔
319
                'callable' => [$this, 'to_array'],
405✔
320
            ],
405✔
321
            'excerpt' => [
405✔
322
                'callable' => 'wp_trim_words',
405✔
323
            ],
405✔
324
            'excerpt_chars' => [
405✔
325
                'callable' => ['Timber\TextHelper', 'trim_characters'],
405✔
326
            ],
405✔
327
            'function' => [
405✔
328
                'callable' => [$this, 'exec_function'],
405✔
329
            ],
405✔
330
            'pretags' => [
405✔
331
                'callable' => [$this, 'twig_pretags'],
405✔
332
            ],
405✔
333
            'sanitize' => [
405✔
334
                'callable' => 'sanitize_title',
405✔
335
            ],
405✔
336
            'shortcodes' => [
405✔
337
                'callable' => 'do_shortcode',
405✔
338
            ],
405✔
339
            'wpautop' => [
405✔
340
                'callable' => 'wpautop',
405✔
341
            ],
405✔
342
            'list' => [
405✔
343
                'callable' => [$this, 'add_list_separators'],
405✔
344
            ],
405✔
345
            'pluck' => [
405✔
346
                'callable' => ['Timber\Helper', 'pluck'],
405✔
347
            ],
405✔
348
            'wp_list_filter' => [
405✔
349
                'callable' => ['Timber\Helper', 'wp_list_filter'],
405✔
350
            ],
405✔
351

352
            'relative' => [
405✔
353
                'callable' => function ($link) {
405✔
354
                    return URLHelper::get_rel_url($link, true);
1✔
355
                },
405✔
356
            ],
405✔
357

358
            /**
359
             * Date and Time filters.
360
             */
361
            'date' => [
405✔
362
                'callable' => [$this, 'twig_date_format_filter'],
405✔
363
                'options' => [
405✔
364
                    'needs_environment' => true,
405✔
365
                ],
405✔
366
            ],
405✔
367
            'time_ago' => [
405✔
368
                'callable' => ['Timber\DateTimeHelper', 'time_ago'],
405✔
369
            ],
405✔
370
            'truncate' => [
405✔
371
                'callable' => function ($text, $len) {
405✔
372
                    return TextHelper::trim_words($text, $len);
3✔
373
                },
405✔
374
            ],
405✔
375

376
            // Numbers filters
377
            'size_format' => [
405✔
378
                'callable' => 'size_format',
405✔
379
            ],
405✔
380

381
            // Actions and filters.
382
            'apply_filters' => [
405✔
383
                'callable' => function () {
405✔
384
                    $args = \func_get_args();
1✔
385
                    $tag = \current(\array_splice($args, 1, 1));
1✔
386

387
                    return \apply_filters_ref_array($tag, $args);
1✔
388
                },
405✔
389
            ],
405✔
390
        ];
405✔
391

392
        /**
393
         * Filters the filters that are added to Twig.
394
         *
395
         * The `$filters` array is an associative array with the filter name as a key and an
396
         * arguments array as the value. In the arguments array, you pass the function to call with
397
         * a `callable` entry.
398
         *
399
         * This is an alternative filter that you can use instead of adding your filter in the
400
         * `timber/twig` filter.
401
         *
402
         * @api
403
         * @since 2.0.0
404
         * @example
405
         * ```php
406
         * add_filter( 'timber/twig/default_filters', function( $filters ) {
407
         *     // Add your own filter.
408
         *     $filters['price'] = [
409
         *         'callable' => 'format_price',
410
         *     ];
411
         *
412
         *     // Replace a filter.
413
         *     $filters['list'] = [
414
         *         'callable' => 'custom_list_filter',
415
         *     ];
416
         *
417
         *     // Remove a filter.
418
         *     unset( $filters['list'] );
419
         *
420
         *     return $filters;
421
         * } );
422
         * ```
423
         *
424
         * @param array $filters
425
         */
426
        $filters = \apply_filters('timber/twig/filters', $filters);
405✔
427

428
        return $filters;
405✔
429
    }
430

431
    /**
432
     * Adds filters to Twig.
433
     *
434
     * @param \Twig\Environment $twig The Twig Environment.
435
     *
436
     * @return \Twig\Environment
437
     */
438
    public function add_timber_filters($twig)
439
    {
440
        foreach ($this->get_timber_filters() as $name => $function) {
405✔
441
            $twig->addFilter(
405✔
442
                new TwigFilter(
405✔
443
                    $name,
405✔
444
                    $function['callable'],
405✔
445
                    $function['options'] ?? []
405✔
446
                )
405✔
447
            );
405✔
448
        }
449

450
        return $twig;
405✔
451
    }
452

453
    /**
454
     * Adds escapers.
455
     *
456
     * @param \Twig\Environment $twig The Twig Environment.
457
     * @return \Twig\Environment
458
     */
459
    public function add_timber_escapers($twig)
460
    {
461
        $esc_url = function (Environment $env, $string) {
405✔
462
            return \esc_url($string);
2✔
463
        };
405✔
464

465
        $wp_kses_post = function (Environment $env, $string) {
405✔
466
            return \wp_kses_post($string);
1✔
467
        };
405✔
468

469
        $esc_html = function (Environment $env, $string) {
405✔
470
            return \esc_html($string);
2✔
471
        };
405✔
472

473
        $esc_js = function (Environment $env, $string) {
405✔
474
            return \esc_js($string);
1✔
475
        };
405✔
476

477
        if (\class_exists('Twig\Extension\EscaperExtension')) {
405✔
478
            $escaper_extension = $twig->getExtension('Twig\Extension\EscaperExtension');
405✔
479
            $escaper_extension->setEscaper('esc_url', $esc_url);
405✔
480
            $escaper_extension->setEscaper('wp_kses_post', $wp_kses_post);
405✔
481
            $escaper_extension->setEscaper('esc_html', $esc_html);
405✔
482
            $escaper_extension->setEscaper('esc_js', $esc_js);
405✔
483
        }
484
        return $twig;
405✔
485
    }
486

487
    /**
488
     * Overwrite Twig defaults.
489
     *
490
     * Makes Twig compatible with how WordPress handles dates, timezones, numbers and perhaps other items in
491
     * the future
492
     *
493
     * @since 2.0.0
494
     *
495
     * @throws \Twig\Error\RuntimeError
496
     * @param \Twig\Environment $twig Twig Environment.
497
     *
498
     * @return \Twig\Environment
499
     */
500
    public function set_defaults(Environment $twig)
501
    {
502
        $twig->getExtension(CoreExtension::class)->setDateFormat(\get_option('date_format'), '%d days');
405✔
503
        $twig->getExtension(CoreExtension::class)->setTimezone(\wp_timezone_string());
405✔
504

505
        /** @see https://developer.wordpress.org/reference/functions/number_format_i18n/ */
506
        global $wp_locale;
507
        if (isset($wp_locale)) {
405✔
508
            $twig->getExtension(CoreExtension::class)->setNumberFormat(0, $wp_locale->number_format['decimal_point'], $wp_locale->number_format['thousands_sep']);
405✔
509
        }
510

511
        return $twig;
405✔
512
    }
513

514
    /**
515
     * Converts a date to the given format.
516
     *
517
     * @internal
518
     * @since 2.0.0
519
     * @see  twig_date_format_filter()
520
     * @link https://twig.symfony.com/doc/2.x/filters/date.html
521
     *
522
     * @throws Exception
523
     *
524
     * @param \Twig\Environment         $env      Twig Environment.
525
     * @param null|string|int|DateTime $date     A date.
526
     * @param null|string               $format   Optional. PHP date format. Will return the
527
     *                                            current date as a DateTimeImmutable object by
528
     *                                            default.
529
     * @param null                      $timezone Optional. The target timezone. Use `null` to use
530
     *                                            the default or
531
     *                                            `false` to leave the timezone unchanged.
532
     *
533
     * @return false|string A formatted date.
534
     */
535
    public function twig_date_format_filter(Environment $env, $date = null, $format = null, $timezone = null)
536
    {
537
        // Support for DateInterval.
538
        if ($date instanceof DateInterval) {
68✔
539
            if (null === $format) {
5✔
540
                $format = $env->getExtension(CoreExtension::class)->getDateFormat()[1];
2✔
541
            }
542

543
            return $date->format($format);
5✔
544
        }
545

546
        if (null === $date || 'now' === $date) {
63✔
547
            return DateTimeHelper::wp_date($format, null);
2✔
548
        }
549

550
        /**
551
         * If a string is given and it’s not a timestamp (e.g. "2010-01-28T15:00:00+04:00", try creating a DateTime
552
         * object and read the timezone from that string.
553
         */
554
        if (\is_string($date) && !\ctype_digit($date)) {
61✔
555
            $date_obj = \date_create($date);
15✔
556

557
            if ($date_obj) {
15✔
558
                $date = $date_obj;
15✔
559
            }
560
        }
561

562
        /**
563
         * Check for `false` parameter in |date filter in Twig
564
         *
565
         * @link https://twig.symfony.com/doc/2.x/filters/date.html#timezone
566
         */
567
        if (false === $timezone && $date instanceof DateTimeInterface) {
61✔
568
            $timezone = $date->getTimezone();
7✔
569
        }
570

571
        return DateTimeHelper::wp_date($format, $date, $timezone);
61✔
572
    }
573

574
    /**
575
     *
576
     *
577
     * @param mixed   $arr
578
     * @return array
579
     */
580
    public function to_array($arr)
581
    {
582
        if (\is_array($arr)) {
2✔
583
            return $arr;
1✔
584
        }
585
        $arr = [$arr];
1✔
586
        return $arr;
1✔
587
    }
588

589
    /**
590
     *
591
     *
592
     * @param string  $function_name
593
     * @return mixed
594
     */
595
    public function exec_function($function_name)
596
    {
597
        $args = \func_get_args();
8✔
598
        \array_shift($args);
8✔
599
        if (\is_string($function_name)) {
8✔
600
            $function_name = \trim($function_name);
8✔
601
        }
602
        return \call_user_func_array($function_name, ($args));
8✔
603
    }
604

605
    /**
606
     *
607
     *
608
     * @param string  $content
609
     * @return string
610
     */
611
    public function twig_pretags($content)
612
    {
613
        return \preg_replace_callback('|<pre.*>(.*)</pre|isU', [&$this, 'convert_pre_entities'], $content);
1✔
614
    }
615

616
    /**
617
     *
618
     *
619
     * @param array   $matches
620
     * @return string
621
     */
622
    public function convert_pre_entities($matches)
623
    {
624
        return \str_replace($matches[1], \htmlentities($matches[1]), $matches[0]);
1✔
625
    }
626

627
    /**
628
     * Formats a date.
629
     *
630
     * @deprecated 2.0.0
631
     *
632
     * @param null|string|false    $format Optional. PHP date format. Will use the `date_format`
633
     *                                     option as a default.
634
     * @param string|int|DateTime $date   A date.
635
     *
636
     * @return string
637
     */
638
    public function intl_date($date, $format = null)
639
    {
640
        Helper::deprecated('intl_date', 'DateTimeHelper::wp_date', '2.0.0');
×
641

642
        return DateTimeHelper::wp_date($format, $date);
×
643
    }
644

645
    /**
646
     *
647
     * @deprecated 2.0.0
648
     *
649
     * Returns the difference between two times in a human readable format.
650
     *
651
     * Differentiates between past and future dates.
652
     *
653
     * @see \human_time_diff()
654
     *
655
     * @param int|string $from          Base date as a timestamp or a date string.
656
     * @param int|string $to            Optional. Date to calculate difference to as a timestamp or
657
     *                                  a date string. Default to current time.
658
     * @param string     $format_past   Optional. String to use for past dates. To be used with
659
     *                                  `sprintf()`. Default `%s ago`.
660
     * @param string     $format_future Optional. String to use for future dates. To be used with
661
     *                                  `sprintf()`. Default `%s from now`.
662
     *
663
     * @return string
664
     */
665
    public static function time_ago($from, $to = null, $format_past = null, $format_future = null)
666
    {
667
        Helper::deprecated('time_ago', 'DateTimeHelper::time_ago', '2.0.0');
×
668

669
        return DateTimeHelper::time_ago($from, $to, $format_past, $format_future);
×
670
    }
671

672
    /**
673
     * @param array $arr
674
     * @param string $first_delimiter
675
     * @param string $second_delimiter
676
     * @return string
677
     */
678
    public function add_list_separators($arr, $first_delimiter = ',', $second_delimiter = ' and')
679
    {
680
        $length = \count($arr);
2✔
681
        $list = '';
2✔
682
        foreach ($arr as $index => $item) {
2✔
683
            if ($index < $length - 2) {
2✔
684
                $delimiter = $first_delimiter . ' ';
2✔
685
            } elseif ($index == $length - 2) {
2✔
686
                $delimiter = $second_delimiter . ' ';
2✔
687
            } else {
688
                $delimiter = '';
2✔
689
            }
690
            $list = $list . $item . $delimiter;
2✔
691
        }
692
        return $list;
2✔
693
    }
694
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc