Issues (92)

Bundle/FrameworkExtraBundle/Twig/Extension.php (20 issues)

1
<?php
2
/**
3
 * @author Gerard van Helden <[email protected]>
4
 * @copyright Zicht Online <http://zicht.nl>
5
 */
6
7
namespace Zicht\Bundle\FrameworkExtraBundle\Twig;
8
9
use Doctrine\Common\Collections\ArrayCollection;
10
use Doctrine\Common\Collections\Collection;
11
use Doctrine\Common\Collections\Criteria;
12
use Doctrine\Common\Collections\ExpressionBuilder;
13
use Doctrine\Common\Util\Debug as DoctrineUtilDebug;
14
use Doctrine\ORM\PersistentCollection;
15
use DOMDocument;
16
use SimpleXMLElement;
17
use Symfony\Component\Form\FormError;
18
use Symfony\Component\Form\FormView;
19
use Symfony\Component\Security\Acl\Voter\FieldVote;
20
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
21
use Symfony\Component\Translation\TranslatorInterface;
22
use Traversable;
23
use Twig_Extension;
24
use Twig_SimpleFilter;
25
use Twig_SimpleFunction;
26
use Zicht\Bundle\FrameworkExtraBundle\Helper\AnnotationRegistry;
27
use Zicht\Bundle\FrameworkExtraBundle\Helper\EmbedHelper;
28
use Zicht\Itertools as iter;
29
use Zicht\Util\Debug;
30
use Zicht\Util\Str as StrUtil;
31
32
/**
33
 * Class Extension
34
 *
35
 * @package Zicht\Bundle\FrameworkExtraBundle\Twig
36
 */
37
class Extension extends Twig_Extension implements \Twig_Extension_GlobalsInterface
38
{
39
    public static $RELATIVE_DATE_PART_MAP = array(
40
        'y' => array('year', 'years'),
41
        'm' => array('month', 'months'),
42
        'd' => array('day', 'days'),
43
        'w' => array('week', 'weeks'),
44
        'h' => array('hour', 'hours'),
45
        'i' => array('minute', 'minutes'),
46
        's' => array('second', 'seconds')
47
    );
48
49
    /**
50
     * @var EmbedHelper
51
     */
52
    protected $embedHelper;
53
54
    /**
55
     * @var array
56
     */
57
    protected $globals;
58
59
    /**
60
     * @var AnnotationRegistry
61
     */
62
    protected $annotationRegistry;
63
64
    /**
65
     * @var TranslatorInterface
66
     */
67
    protected $translator;
68
69
    /**
70
     * @var AuthorizationCheckerInterface
71
     */
72
    protected $authChecker;
73
74
    /**
75
     * Extension constructor.
76
     *
77
     * @param EmbedHelper $embedHelper
78
     * @param AnnotationRegistry $annotationRegistry
79
     * @param TranslatorInterface|null $translator
80
     * @param AuthorizationCheckerInterface|null $authChecker
81
     */
82
    public function __construct(
83
        EmbedHelper $embedHelper,
84
        AnnotationRegistry $annotationRegistry,
85
        TranslatorInterface $translator = null,
86
        AuthorizationCheckerInterface $authChecker = null
87
    )
88
    {
89
        $this->globals = [];
90
        $this->embedHelper = $embedHelper;
91
        $this->annotationRegistry = $annotationRegistry;
92
        $this->translator = $translator;
93
        $this->authChecker = $authChecker;
94
    }
95
96
    /**
97
     * Set global
98
     *
99
     * @param string $name
100
     * @param mixed $value
101
     */
102
    public function setGlobal($name, $value)
103
    {
104
        $this->globals[$name] = $value;
105
    }
106
107
    /**
108
     * @return array
109
     */
110
    public function getGlobals()
111
    {
112
        return $this->globals;
113
    }
114
115
    /**
116
     * @return array
117
     */
118
    public function getFilters()
119
    {
120
        return array(
121
            new Twig_SimpleFilter('dump', array($this, 'dump'), array('is_safe' => array('html'))),
122
            new Twig_SimpleFilter('xml', array($this, 'xml')),
123
            new Twig_SimpleFilter('regex_replace', array($this, 'regexReplace')),
124
            new Twig_SimpleFilter('re_replace', array($this, 'regexReplace')),
125
            new Twig_SimpleFilter('str_uscore', array($this, 'strUscore')),
126
            new Twig_SimpleFilter('str_dash', array($this, 'strDash')),
127
            new Twig_SimpleFilter('str_camel', array($this, 'strCamel')),
128
            new Twig_SimpleFilter('str_humanize', array($this, 'strHumanize')),
129
            new Twig_SimpleFilter('date_format', array($this, 'dateFormat')),
130
            new Twig_SimpleFilter('relative_date', array($this, 'relativeDate')),
131
            new Twig_SimpleFilter('ga_trackevent', array($this, 'gaTrackEvent')),
132
133
            new Twig_SimpleFilter('prefix_multiple', array($this, 'prefixMultiple')),
134
            new Twig_SimpleFilter('trans_multiple', array($this, 'transMultiple')),
135
            new Twig_SimpleFilter('truncate_html', array($this, 'truncateHtml')),
136
137
            new Twig_SimpleFilter('with', array($this, 'with')),
138
            new Twig_SimpleFilter('without', array($this, 'without')),
139
140
            new Twig_SimpleFilter('where', array($this, 'where')),
141
            new Twig_SimpleFilter('not_where', array($this, 'notWhere')),
142
            new Twig_SimpleFilter('where_split', array($this, 'whereSplit')),
143
            new Twig_SimpleFilter('url_to_form_params', array($this, 'urlToFormParameters')),
144
            new Twig_SimpleFilter('url_strip_query', array($this, 'urlStripQuery')),
145
146
            new Twig_SimpleFilter('ceil', 'ceil'),
147
            new Twig_SimpleFilter('floor', 'floor'),
148
            new Twig_SimpleFilter('groups', array($this, 'groups')),
149
            new Twig_SimpleFilter('sort_by_type', array($this, 'sortByType')),
150
            new Twig_SimpleFilter('html2text', array($this, 'htmlToText')),
151
            new Twig_SimpleFilter('replace_recursive', 'array_replace_recursive'),
152
            new Twig_SimpleFilter('json_decode', array($this, 'jsonDecode')),
153
            new Twig_SimpleFilter('sha1', array($this, 'shaOne')),
154
155
            new Twig_SimpleFilter('form_root', array($this, 'formRoot')),
156
            new Twig_SimpleFilter('form_has_errors', array($this, 'formHasErrors')),
157
        );
158
    }
159
160
    /**
161
     * Prefix multiple
162
     *
163
     * @param array $values
164
     * @param string $prefix
165
     * @return iter\lib\MapIterator
166
     */
167
    public function prefixMultiple($values, $prefix)
168
    {
169
        return iter\map(
0 ignored issues
show
The function map was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

169
        return /** @scrutinizer ignore-call */ iter\map(
Loading history...
170
            function ($value) use ($prefix) {
171
                return sprintf('%s%s', $prefix, $value);
172
            },
173
            $values
174
        );
175
    }
176
177
    /**
178
     * Translate multiple strings
179
     *
180
     * @param array $messages
181
     * @param array $parameters
182
     * @param null $domain
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $domain is correct as it would always require null to be passed?
Loading history...
183
     * @param null $locale
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $locale is correct as it would always require null to be passed?
Loading history...
184
     * @return iter\lib\MapIterator
185
     */
186
    public function transMultiple($messages, $parameters = [], $domain = null, $locale = null)
187
    {
188
        $translator = $this->translator;
189
        return iter\map(
0 ignored issues
show
The function map was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

189
        return /** @scrutinizer ignore-call */ iter\map(
Loading history...
190
            function ($message) use ($translator, $parameters, $domain, $locale) {
0 ignored issues
show
The import $translator is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
191
                return $this->translator->trans($message, $parameters, $domain, $locale);
192
            },
193
            $messages
194
        );
195
    }
196
197
    /**
198
     * Returns the root of the form
199
     *
200
     * @param FormView $formView
201
     * @return FormView
202
     */
203
    public function formRoot(FormView $formView)
204
    {
205
        return EmbedHelper::getFormRoot($formView);
206
    }
207
208
    /**
209
     * Returns true when the form, or any of its children, has one or more errors.
210
     *
211
     * @param FormView $form
212
     *
213
     * @return bool
214
     */
215
    public function formHasErrors(FormView $form)
216
    {
217
        if ($form->vars['errors']->count()) {
218
            return true;
219
        }
220
221
        foreach ($form->children as $child) {
222
            if ($this->formHasErrors($child)) {
223
                return true;
224
            }
225
        }
226
227
        return false;
228
    }
229
230
    /**
231
     * Call the php json_decode
232
     *
233
     * @param string $string
234
     * @param bool $assoc
235
     * @return mixed|null
236
     */
237
    public function jsonDecode($string, $assoc = false)
238
    {
239
        if (is_string($string)) {
0 ignored issues
show
The condition is_string($string) is always true.
Loading history...
240
            return json_decode($string, $assoc);
241
        }
242
        return null;
243
    }
244
245
    /**
246
     * Returns a 40 byte string representing the sha1 digest of the input string.
247
     *
248
     * @param string|\Twig_Markup $string
249
     * @return string
250
     */
251
    public function shaOne($string)
252
    {
253
254
        if (is_string($string) || $string instanceof \Twig_Markup) {
0 ignored issues
show
$string is always a sub-type of Twig_Markup.
Loading history...
255
            return sha1($string);
256
        }
257
        return '';
258
    }
259
260
    /**
261
     * Filter a collection based on properties of the collection's items
262
     *
263
     * @param array|Collection $items
264
     * @param array $keyValuePairs
265
     * @param string $comparator
266
     * @param string $booleanOperator
267
     * @return \Doctrine\Common\Collections\Collection
268
     */
269
    public function where($items, array $keyValuePairs, $comparator = 'eq', $booleanOperator = 'and')
270
    {
271
        if (is_array($items)) {
272
            $items = new ArrayCollection($items);
273
        }
274
        if ($items instanceof PersistentCollection && !$items->isInitialized()) {
275
            $items->initialize();
276
        }
277
278
        $whereMethod = $booleanOperator . 'Where';
279
280
        $eb = new ExpressionBuilder();
281
        $criteria = new Criteria();
282
        foreach ($keyValuePairs as $key => $value) {
283
            if (is_array($value)) {
284
                if ($comparator === 'eq') {
285
                    $criteria->$whereMethod($eb->in($key, $value));
286
                    continue;
287
                } elseif ($comparator === 'neq') {
288
                    $criteria->$whereMethod($eb->notIn($key, $value));
289
                    continue;
290
                }
291
            }
292
            $criteria->$whereMethod($eb->$comparator($key, $value));
293
        }
294
295
        return $items->matching($criteria);
0 ignored issues
show
The method matching() does not exist on Doctrine\Common\Collections\Collection. It seems like you code against a sub-type of said class. However, the method does not exist in Doctrine\Common\Collections\AbstractLazyCollection. Are you sure you never get one of those? ( Ignorable by Annotation )

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

295
        return $items->/** @scrutinizer ignore-call */ matching($criteria);
Loading history...
296
    }
297
298
    /**
299
     * Inverse of where, i.e. get all items that are NOT matching the criteria.
300
     *
301
     * @param array|Collection $items
302
     * @param array $keyValuePairs
303
     * @return Collection
304
     */
305
    public function notWhere($items, $keyValuePairs)
306
    {
307
        return $this->where($items, $keyValuePairs, 'neq');
308
    }
309
310
    /**
311
     * Splits a list in two collections, one matching the criteria, and the rest
312
     *
313
     * @param array|Collection $items
314
     * @param array $keyValuePairs
315
     * @return array
316
     */
317
    public function whereSplit($items, $keyValuePairs)
318
    {
319
        return array(
320
            $this->notWhere($items, $keyValuePairs),
321
            $this->where($items, $keyValuePairs)
322
        );
323
    }
324
325
    /**
326
     * Removes the query string from a form.
327
     *
328
     * Used as follows in conjunction with url_parse_query()
329
     *
330
     * <form method="get" action="{{ url|url_strip_query }}">
331
     *     {% for k, value in url|url_to_form_params %}
332
     *          <input type="hidden" name="{{ k }}" value="{{ value }}">
333
     *     {% endfor %}
334
     * </form>
335
     *
336
     * @param string $url
337
     * @return array
338
     */
339
    public function urlStripQuery($url)
340
    {
341
        $query = parse_url($url, PHP_URL_QUERY);
342
        return str_replace('?' . $query, '', $url);
0 ignored issues
show
Bug Best Practice introduced by
The expression return str_replace('?' . $query, '', $url) returns the type string which is incompatible with the documented return type array.
Loading history...
343
    }
344
345
    /**
346
     * Url to form parameters
347
     *
348
     * @param string $url
349
     * @return array
350
     */
351
    public function urlToFormParameters($url)
352
    {
353
        $query = parse_url($url, PHP_URL_QUERY);
354
        $vars = array();
355
        parse_str($query, $vars);
356
        return $this->valuesToFormParameters($vars, null);
357
    }
358
359
    /**
360
     * Prepares a nested array for use in form fields.
361
     *
362
     * @param mixed[] $values
363
     * @param string $parent
364
     * @return array
365
     */
366
    private function valuesToFormParameters($values, $parent)
367
    {
368
        $ret = array();
369
        foreach ($values as $key => $value) {
370
            if (null !== $parent) {
371
                $keyName = sprintf('%s[%s]', $parent, $key);
372
            } else {
373
                $keyName = $key;
374
            }
375
            if (is_scalar($value)) {
376
                $ret[$keyName] = $value;
377
            } else {
378
                $ret = array_merge($ret, $this->valuesToFormParameters($value, $keyName));
379
            }
380
        }
381
        return $ret;
382
    }
383
384
    /**
385
     * @return array
386
     */
387
    public function getFunctions()
388
    {
389
        return array(
390
            'trans_form_errors' => new Twig_SimpleFunction('trans_form_errors', [$this, 'transFormErrors']),
391
            'embed_params' => new Twig_SimpleFunction('embed_params', [$this, 'getEmbedParams']),
392
            'defaults' => new Twig_SimpleFunction('defaults', [$this, 'getDefaultOf']),
393
            'embed' => new Twig_SimpleFunction('embed', [$this, 'embed']),
394
            'is_granted' => new Twig_SimpleFunction('is_granted', [$this, 'isGranted']),
395
            'embedded_image' => new Twig_SimpleFunction('embedded_image', [$this, 'embeddedImage']),
396
        );
397
    }
398
399
    /**
400
     * Given an existing file, returns an embedded data stream, or null when the file does not exist
401
     *
402
     * For example
403
     * {{ embedded_image('foo.jpg') }}
404
     *  --> "data:image/jpg;base64,BLABLABLA"
405
     *
406
     * @param string $filename
407
     * @return null|string
408
     */
409
    public function embeddedImage($filename)
410
    {
411
        if (is_file($filename) && preg_match('/[.](?P<extension>[a-zA-Z0-9]+)$/', $filename, $matches)) {
412
            return sprintf('data:image/%s;base64,%s', $matches['extension'], base64_encode(file_get_contents($filename)));
413
        }
414
415
        return null;
416
    }
417
418
    /**
419
     * The template may assume that the role will be denied when there is no security context, therefore we override
420
     * the default behaviour here.
421
     *
422
     * @param string $role
423
     * @param mixed $object
424
     * @param mixed $field
425
     * @return bool
426
     */
427
    public function isGranted($role, $object = null, $field = null)
428
    {
429
        if (null !== $field) {
430
            $object = new FieldVote($object, $field);
431
        }
432
        return $this->authChecker->isGranted($role, $object);
433
    }
434
435
    /**
436
     * Groups
437
     *
438
     * @param array|object $list
439
     * @param int $numGroups
440
     * @return array
441
     */
442
    public function groups($list, $numGroups)
443
    {
444
        $items = ($list instanceof Traversable ? iterator_to_array($list) : $list);
445
446
        $groups = array();
447
        $i = 0;
448
        foreach ($items as $item) {
449
            $groups[$i++ % $numGroups][] = $item;
450
        }
451
        return $groups;
452
    }
453
454
    /**
455
     * Formats some values as an 'onclick' attribute for Google Analytics
456
     *
457
     * @param null $values
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $values is correct as it would always require null to be passed?
Loading history...
458
     * @return string
459
     */
460
    public function gaTrackEvent($values = null)
461
    {
462
        $values = func_get_args();
463
        array_unshift($values, '_trackEvent');
464
465
        return sprintf(
466
            ' onclick="_gaq.push(%s);"',
467
            htmlspecialchars(json_encode(array_values($values)))
468
        );
469
    }
470
471
    /**
472
     * Sort by type
473
     *
474
     * @param array|object $collection
475
     * @param array $types
476
     * @return array
477
     */
478
    public function sortByType($collection, $types)
479
    {
480
        if ($collection instanceof Traversable) {
481
            $collection = iterator_to_array($collection);
482
        }
483
484
        // store a map of the original sorting, so the usort can use this to keep the original sorting if the types are
485
        // equal
486
        $idToIndexMap = array();
487
        foreach ($collection as $index => $item) {
488
            $idToIndexMap[$item->getId()] = $index;
489
        }
490
491
        $numTypes = count($types);
492
493
        usort(
494
            $collection,
0 ignored issues
show
It seems like $collection can also be of type object; however, parameter $array of usort() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

494
            /** @scrutinizer ignore-type */ $collection,
Loading history...
495
            function ($left, $right) use ($types, $idToIndexMap, $numTypes) {
496
                $localClassNameLeft = Str::classname(get_class($left));
0 ignored issues
show
The type Zicht\Bundle\FrameworkExtraBundle\Twig\Str was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
497
                $localClassNameRight = Str::classname(get_class($right));
498
499
                // if same type, use original sorting
500
                if ($localClassNameRight === $localClassNameLeft) {
501
                    $indexLeft = $idToIndexMap[$left->getId()];
502
                    $indexRight = $idToIndexMap[$right->getId()];
503
                } else {
504
                    $indexLeft = array_search($localClassNameLeft, $types);
505
                    $indexRight = array_search($localClassNameRight, $types);
506
507
                    // assume that types that aren't defined, should come last.
508
                    if (false === $indexLeft) {
509
                        $indexLeft = $numTypes + 1;
510
                    }
511
                    if (false === $indexRight) {
512
                        $indexRight = $numTypes + 1;
513
                    }
514
                }
515
516
                if ($indexLeft < $indexRight) {
517
                    return -1;
518
                }
519
                if ($indexLeft > $indexRight) {
520
                    return 1;
521
                }
522
                return 0;
523
            }
524
        );
525
526
        return $collection;
527
    }
528
529
    /**
530
     * Dash to CamelCase
531
     *
532
     * @param string $str
533
     * @param bool $camelFirst
534
     * @return string
535
     */
536
    public function strDash($str, $camelFirst = true)
537
    {
538
        if ($camelFirst) {
539
            $str = StrUtil::camel($str);
540
        }
541
        return StrUtil::dash($str);
542
    }
543
544
    /**
545
     * CamelCased to under_score
546
     *
547
     * @param string $str
548
     * @param bool $camelFirst
549
     * @return string
550
     */
551
    public function strUscore($str, $camelFirst = true)
552
    {
553
        if ($camelFirst) {
554
            $str = StrUtil::camel($str);
555
        }
556
        return StrUtil::uscore($str);
557
    }
558
559
    /**
560
     * Camelcase
561
     *
562
     * @param String $str
563
     * @return string
564
     */
565
    public function strCamel($str)
566
    {
567
        return StrUtil::camel($str);
568
    }
569
570
    /**
571
     * Humanize
572
     *
573
     * @param string $str
574
     * @return string
575
     */
576
    public function strHumanize($str)
577
    {
578
        return StrUtil::humanize($str);
579
    }
580
581
    /**
582
     * Format Date
583
     *
584
     * @param string|object|int $date
585
     * @param string $format
586
     * @return string
587
     */
588
    public function dateFormat($date, $format = '%e %b %Y')
589
    {
590
        if ($date instanceof \DateTime) {
591
            $ts = $date->getTimestamp();
592
        } elseif (is_numeric($date)) {
593
            $ts = $date;
594
        } elseif (preg_match('/^[0-9]{4,4}-[0-9]{2,2}-[0-9]{2,2}T[0-9]{2,2}:[0-9]{2,2}.*/', $date)) {
0 ignored issues
show
It seems like $date can also be of type object; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

594
        } elseif (preg_match('/^[0-9]{4,4}-[0-9]{2,2}-[0-9]{2,2}T[0-9]{2,2}:[0-9]{2,2}.*/', /** @scrutinizer ignore-type */ $date)) {
Loading history...
595
            // timestamp format 2013-01-01T00:00:00
596
            $ts = new \DateTime($date);
0 ignored issues
show
It seems like $date can also be of type object; however, parameter $time of DateTime::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

596
            $ts = new \DateTime(/** @scrutinizer ignore-type */ $date);
Loading history...
597
            $ts = $ts->getTimestamp();
598
        } else {
599
            throw new \InvalidArgumentException(sprintf("Cannot format %s as a date", $date));
0 ignored issues
show
It seems like $date can also be of type object; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

599
            throw new \InvalidArgumentException(sprintf("Cannot format %s as a date", /** @scrutinizer ignore-type */ $date));
Loading history...
600
        }
601
602
        return strftime($format, $ts);
0 ignored issues
show
It seems like $ts can also be of type string; however, parameter $timestamp of strftime() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

602
        return strftime($format, /** @scrutinizer ignore-type */ $ts);
Loading history...
603
    }
604
605
    /**
606
     * Relative date
607
     *
608
     * @param string|object $date
609
     * @return string
610
     */
611
    public function relativeDate($date)
612
    {
613
        $now = new \DateTime();
614
        $diff = $date->diff($now);
615
        // natively, diff doesn't contain 'weeks'.
616
        $diff->w = round($diff->d / 7);
617
        $message = '';
618
        foreach (array('y', 'm', 'w', 'd', 'h', 'i', 's') as $part) {
619
            if ($diff->$part > 0) {
620
                list($singular, $plural) = self::$RELATIVE_DATE_PART_MAP[$part];
621
                if ($diff->$part > 1) {
622
                    $denominator = $plural;
623
                } else {
624
                    $denominator = $singular;
625
                }
626
627
                if (null !== $this->translator) {
628
                    $message = $this->translator->trans(
629
                        '%count% ' . $denominator . ' ago',
630
                        array('%count%' => $diff->$part)
631
                    );
632
                } else {
633
                    $message = sprintf('%d %s ago', $diff->$part, $denominator);
634
                }
635
                break;
636
            }
637
        }
638
639
        return $message;
640
    }
641
642
    /**
643
     * Filter implementation for regular expression replacement
644
     *
645
     * @param string $subject
646
     * @param string $pattern
647
     * @param string $replacement
648
     * @return string
649
     */
650
    public function regexReplace($subject, $pattern, $replacement)
651
    {
652
        return preg_replace($pattern, $replacement, $subject);
653
    }
654
655
    /**
656
     * Truncate text at a maximum length, splitting by words, and add an ellipsis "...".
657
     *
658
     * @param string $str
659
     * @param int $length
660
     * @param string $ellipsis
661
     * @return string
662
     */
663
    public function truncate($str, $length, $ellipsis = '...')
664
    {
665
        $result = '';
666
        foreach (preg_split('/\b/U', $str) as $part) {
667
            if (strlen($result . $part) > $length) {
668
                $result = rtrim($result) . $ellipsis;
669
                break;
670
            } else {
671
                $result .= $part;
672
            }
673
        }
674
675
        return $result;
676
    }
677
678
    /**
679
     * Truncates html as text
680
     *
681
     * @param string $html
682
     * @param int $length
683
     * @param string $ellipsis
684
     * @return string
685
     */
686
    public function truncateHtml($html, $length, $ellipsis = '...')
687
    {
688
        return $this->truncate(html_entity_decode(strip_tags($html), null, 'UTF-8'), $length, $ellipsis);
689
    }
690
691
    /**
692
     * @return array|\Twig_NodeVisitorInterface[]
693
     */
694
    public function getNodeVisitors()
695
    {
696
        return array(
697
            'zicht_render_add_embed_params' => new RenderAddEmbedParamsNodeVisitor()
698
        );
699
    }
700
701
    /**
702
     * @return array
703
     */
704
    public function getTokenParsers()
705
    {
706
        return array(
707
            'zicht_switch' => new ControlStructures\SwitchTokenParser(),
708
            'zicht_strict' => new ControlStructures\StrictTokenParser(),
709
            'zicht_meta_annotate' => new Meta\AnnotateTokenParser(),
710
            'zicht_meta_annotations' => new Meta\AnnotationsTokenParser()
711
        );
712
    }
713
714
    /**
715
     * @return AnnotationRegistry
716
     */
717
    public function getAnnotationRegistry()
718
    {
719
        return $this->annotationRegistry;
720
    }
721
722
    /**
723
     * Embed
724
     *
725
     * @param array|string $urlOrArray
726
     * @return array|mixed|string
727
     */
728
    public function embed($urlOrArray)
729
    {
730
        $embedParams = array_filter($this->embedHelper->getEmbedParams());
731
        if (!$embedParams) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $embedParams of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
732
            return $urlOrArray;
733
        }
734
        if (is_array($urlOrArray)) {
735
            return $urlOrArray + $embedParams;
736
        } elseif (is_string($urlOrArray)) {
0 ignored issues
show
The condition is_string($urlOrArray) is always true.
Loading history...
737
            $query = parse_url($urlOrArray, PHP_URL_QUERY);
738
            $urlOrArray = str_replace($query, '', $urlOrArray);
739
            $currentParams = array();
740
            parse_str($query, $currentParams);
741
            $currentParams += $embedParams;
742
743
            return preg_replace('/\?$/', '', $urlOrArray) . '?' . http_build_query($currentParams);
744
        } else {
745
            throw new \InvalidArgumentException("Only supports arrays or strings");
746
        }
747
    }
748
749
    /**
750
     * @return array
751
     */
752
    public function getEmbedParams()
753
    {
754
        return array_filter($this->embedHelper->getEmbedParams());
755
    }
756
757
    /**
758
     * @return string
759
     */
760
    public function getName()
761
    {
762
        return 'zicht_framework_extra';
763
    }
764
765
    /**
766
     * @return mixed|null
767
     */
768
    public function getDefaultOf()
769
    {
770
        $items = func_get_args();
771
        $items = array_filter($items);
772
        if (count($items)) {
773
            return current($items);
774
        }
775
776
        return null;
777
    }
778
779
    /**
780
     * Dumps given variable in styled format
781
     *
782
     * @param mixed $var
783
     * @param string $mode
784
     * @return mixed
785
     */
786
    public function dump($var, $mode = null)
787
    {
788
        if (null === $mode && class_exists('Zicht\Util\Debug')) {
789
            return htmlspecialchars(Debug::dump($var));
790
        } else {
791
            switch ($mode) {
792
                case 'export':
793
                    DoctrineUtilDebug::dump($var, 5);
794
                    break;
795
                default:
796
                    var_dump($var);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($var) looks like debug code. Are you sure you do not want to remove it?
Loading history...
797
                    break;
798
            }
799
        }
800
        return null;
801
    }
802
803
    /**
804
     * XML
805
     *
806
     * @param object $data
807
     * @return string
808
     */
809
    public function xml($data)
810
    {
811
        if ($data instanceof SimpleXMLElement) {
812
            $data = $data->saveXML();
813
        }
814
        $dom = new DOMDocument();
815
        $dom->loadXml($data);
0 ignored issues
show
It seems like $data can also be of type object; however, parameter $source of DOMDocument::loadXML() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

815
        $dom->loadXml(/** @scrutinizer ignore-type */ $data);
Loading history...
816
        $dom->formatOutput = true;
817
        return $dom->saveXML();
818
    }
819
820
    /**
821
     * Strips tags
822
     *
823
     * @param string $html
824
     * @return string
825
     */
826
    public function htmlToText($html)
827
    {
828
        return strip_tags($html);
829
    }
830
831
    /**
832
     * Transforms errors
833
     *
834
     * @param FormError[] $formErrorList
835
     * @param string $translationDomain
836
     * @return array
837
     */
838
    public function transFormErrors($formErrorList, $translationDomain)
839
    {
840
        $ret = [];
841
        foreach ($formErrorList as $k => $formError) {
842
            $ret[$k] = $this->translator->trans($formError->getMessage(), $formError->getMessageParameters(), $translationDomain);
843
        }
844
        return $ret;
845
    }
846
}
847