EchoExtension::getEchoPath()   B
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.8657
c 0
b 0
f 0
cc 6
nc 4
nop 3
1
<?php
2
3
namespace Admingenerator\GeneratorBundle\Twig\Extension;
4
5
/**
6
 * @author Cedric LOMBARDOT
7
 * @author Piotr Gołębiewski <[email protected]>
8
 * @author Stéphane Escandell
9
 */
10
class EchoExtension extends \Twig_Extension
0 ignored issues
show
Deprecated Code introduced by
The class Twig_Extension has been deprecated with message: since Twig 2.7, use "Twig\Extension\AbstractExtension" instead

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
11
{
12
    /**
13
     * @var bool
14
     */
15
    private $useExpression;
16
17
    public function __construct($useExpression = false)
18
    {
19
        $this->useExpression = $useExpression;
20
    }
21
22
    /**
23
     * {@inheritdoc}
24
     */
25
    public function getFunctions()
26
    {
27
        return array(
28
            'echo_if_granted'     => new \Twig_SimpleFunction('echo_if_granted', array($this, 'getEchoIfGranted')),
0 ignored issues
show
Deprecated Code introduced by
The class Twig_SimpleFunction has been deprecated with message: since Twig 2.7, use "Twig\TwigFunction" instead

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
29
            'echo_path'           => new \Twig_SimpleFunction('echo_path', array($this, 'getEchoPath')),
0 ignored issues
show
Deprecated Code introduced by
The class Twig_SimpleFunction has been deprecated with message: since Twig 2.7, use "Twig\TwigFunction" instead

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
30
            'echo_trans'          => new \Twig_SimpleFunction('echo_trans', array($this, 'getEchoTrans')),
0 ignored issues
show
Deprecated Code introduced by
The class Twig_SimpleFunction has been deprecated with message: since Twig 2.7, use "Twig\TwigFunction" instead

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
31
            'echo_render'         => new \Twig_SimpleFunction('echo_render', array($this, 'getEchoRender'))
0 ignored issues
show
Deprecated Code introduced by
The class Twig_SimpleFunction has been deprecated with message: since Twig 2.7, use "Twig\TwigFunction" instead

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
32
        );
33
    }
34
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function getFilters()
39
    {
40
        return array(
41
            'convert_as_form' => new \Twig_SimpleFilter('convert_as_form', array($this, 'convertAsForm')),
0 ignored issues
show
Deprecated Code introduced by
The class Twig_SimpleFilter has been deprecated with message: since Twig 2.7, use "Twig\TwigFilter" instead

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
42
        );
43
    }
44
45
    /**
46
     * Try to convert options of form given as string from yaml to a good object
47
     *    > Transforms PHP call into PHP :
48
     *      addFormOptions:
49
     *          myOption: __php(MyStaticClass::myCustomFunction())
50
     *
51
     *    > Tranforms [query_builder|query] into valid Closure:
52
     *      addFormOptions:
53
     *          query_builder: function($er) { return $er->createMyCustomQueryBuilder(); }
54
     *
55
     *
56
     * @param string $options  the string as php
57
     * @param string $formType the form type
58
     *
59
     * @return string the new options
60
     */
61
    public function convertAsForm($options, $formType)
62
    {
63
        // Transforms PHP call into PHP (simple copy/paste)
64
        preg_match("/'__php\((.+?)\)'/i", stripslashes($options), $matches);
65
        if (count($matches)) {
66
            $options = preg_replace("/'__php\((.+?)\)'/i", $matches[1], $options);
67
        }
68
69
        // Query builder: remove quotes around closure
70
        // Should we really check formType or can we just
71
        // look for query_builder option?
72 View Code Duplication
        if (preg_match("/EntityType$/i", $formType)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
73
            preg_match("/'query_builder' => '(.+?)}',/i", $options, $matches);
74
75
            if (count($matches) > 0) {
76
              $options = str_replace("'query_builder' => '$matches[1]}'", "'query_builder' => ".stripslashes($matches[1]).'}', $options);
77
            }
78
            preg_match("/'query_builder' => '(.+?)',/i", $options, $matches);
79
80
            if (count($matches) > 0) {
81
                $options = str_replace("'query_builder' => '$matches[1]'", "'query_builder' => ".stripslashes($matches[1]), $options);
82
            }
83
        }
84
85
        // Same question here
86 View Code Duplication
        if (preg_match("/ModelType$/i", $formType)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
87
            preg_match("/'query' => '(.+?)}',/i", $options, $matches);
88
89
            if (count($matches) > 0) {
90
                $options = str_replace("'query' => '$matches[1]}'", "'query' => ".stripslashes($matches[1]).'}', $options);
91
            }
92
            preg_match("/'query' => '(.+?)',/i", $options, $matches);
93
94
            if (count($matches) > 0) {
95
                $options = str_replace("'query' => '$matches[1]'", "'query' => ".stripslashes($matches[1]), $options);
96
            }
97
        }
98
99
        return $options;
100
    }
101
102
    /**
103
     * Print "trans" tag for string $str with parameters $parameters
104
     * for catalog $catalog.
105
     *
106
     * @param $str
107
     * @param  array  $parameters
108
     * @param  string $catalog
109
     * @return string
110
     */
111
    public function getEchoTrans($str, array $parameters = array(), $catalog = 'Admingenerator', $escape = null)
112
    {
113
        $transParameters='{}';
114
        $bag_parameters=array();
115
116
        if ($parameterBag = $this->getParameterBag($str)) {
117
            $str = $parameterBag['string'];
118
            $bag_parameters = $parameterBag['params'];
119
        }
120
121
        if (!empty($parameters) || !empty($bag_parameters)) {
122
            $transParameters="{";
123
124
            foreach ($parameters as $key => $value) {
125
                $transParameters.= "'%".$key."%': '".str_replace("'", "\'", $value)."',";
126
            }
127
            foreach ($bag_parameters as $key => $value) {
128
                $transParameters.= "'%".$key."%': ".str_replace("'", "\'", $value).",";
129
            }
130
131
            $transParameters.="}";
132
        }
133
134
        return sprintf(
135
            '{{ "%s"|trans(%s, "%s")%s }}',
136
            str_replace('"', '\"', $str),
137
            $transParameters,
138
            $catalog,
139
            $escape ? sprintf('|escape("%s")', $escape) : '|raw'
140
        );
141
    }
142
143
    /**
144
     * Print "echo tag with path call" to the path $path with params $params.
145
     *
146
     * @param $path
147
     * @param  array        $params
148
     * @param  array|string $filters
149
     * @return string
150
     */
151
    public function getEchoPath($path, $params = null, $filters = null)
152
    {
153
        if (null === $params) {
154
            return (null === $filters)
155
                ? strtr('{{ path("%%path%%") }}', array('%%path%%' => $path))
156
                : strtr(
157
                    '{{ path("%%path%%")|%%filters%% }}',
158
                    array(
159
                        '%%path%%' => $path,
160
                        '%%filters%%' => (is_array($filters) ? implode('|', $filters) : $filters)
161
                    )
162
                );
163
        }
164
165
        $params = preg_replace('/\{\{\s+?([\w\.]+)\s+?\}\}/i', '$1', $params);
166
167
        return (null === $filters)
168
            ? strtr('{{ path("%%path%%", %%params%%) }}', array('%%path%%' => $path, '%%params%%' => $params))
169
            : strtr(
170
                '{{ path("%%path%%", %%params%%)|%%filters%% }}',
171
                array(
172
                    '%%path%%' => $path,
173
                    '%%params%%' => $params,
174
                    '%%filters%%' => (is_array($filters) ? implode('|', $filters) : $filters)
175
                )
176
            );
177
    }
178
179
    /**
180
     * Print "if" tag with condition to is_expr_granted('$credentials')
181
     * If $modelName is not null, append the $modelName to the function call.
182
     *
183
     * @param $credentials
184
     * @param  null   $modelName
185
     * @return string
186
     */
187
    public function getEchoIfGranted($credentials, $modelName = null)
188
    {
189
        if ('AdmingenAllowed' == $credentials) {
190
            return "{% if (true) %}";
191
        }
192
193
        return sprintf(
194
            "{%% if %s('%s'%s) %%}",
195
            $this->useExpression ? 'is_expr_granted' : 'is_granted',
196
            $credentials,
197
            $modelName ? ', '.$modelName.' is defined ? '.$modelName.' : null' : ''
198
        );
199
    }
200
201
    /**
202
     * Print "echo tag with render call" to the controller $controller
203
     * with $params parameters.
204
     *
205
     * @param $controller
206
     * @param  array  $params
207
     * @return string
208
     */
209
    public function getEchoRender($controller, array $params = array())
210
    {
211
        $params = $this->getTwigAssociativeArray($params);
212
213
        return '{{ render(controller("'.$controller.'", '.$params.')) }}';
214
    }
215
216
    /**
217
     * Reads parameters from subject and removes parameter bag from string.
218
     *
219
     * @return array
220
     *               [string] -> string for echo trans
221
     *               [params] -> parameters for echo trans
222
     *
223
     * @return false if subject did not match any of following patterns
224
     *
225
     * ##############################
226
     * Backwards compability pattern:
227
     *
228
     * replaces twig tags {{ parameter_name }} with parameters.
229
     *
230
     * example: You're editing {{ Book.title }} written by {{ Book.author.name }}!
231
     *
232
     * results in:
233
     *   string -> You're editing %Book.title% written by %Book.author.name%!
234
     *   params ->
235
     *     [Book.title] -> Book.title
236
     *     [Book.author.name] -> Book.author.name
237
     *
238
     * ###################################
239
     * Feature - key-value syntax pattern:
240
     * |{ %param_key%: param_value, %param_key2%: param_value2, %param_key3%: param_value3 }|
241
     *
242
     * where param_key and param_value consist of any number a-z, A-Z, 0-9 or . (dot) characters
243
     *
244
     * example: You're editing %book% written by %author%!|{ %book%: Book.title, %author%: Book.author.name }|
245
     * results in:
246
     *   string -> You're editing %book% written by %author%!
247
     *   params ->
248
     *     [book] -> Book.title
249
     *     [author] -> Book.author.name
250
     *
251
     * example: book.edit.title|{ %book%: Book.title, %author%: Book.author.name }|     *
252
     * results in:
253
     *   string -> book.edit.title
254
     *   params ->
255
     *     [book] -> Book.title
256
     *     [author] -> Book.author.name
257
     *
258
     * ###################################
259
     * Feature - abbreviated syntax pattern:
260
     * |{ param_value, param_value2, param_value3 }|
261
     *
262
     * where param_value consists of any number a-z, A-Z, 0-9 or . (dot) characters
263
     *
264
     * example: You're editing %Book.title% written by %Book.author.name%!|{ Book.title, Book.author.name }|
265
     * results in:
266
     *   string -> You're editing %Book.title% written by %Book.author.name%!
267
     *   params ->
268
     *     [Book.title] -> Book.title
269
     *     [Book.author.name] -> Book.author.name
270
     *
271
     * example: book.edit.title|{ Book.title, Book.author.name }|
272
     * results in:
273
     *   string -> book.edit.title
274
     *   params ->
275
     *     [Book.title] -> Book.title
276
     *     [Book.author.name] -> Book.author.name
277
     */
278
    private function getParameterBag($subject)
279
    {
280
        // Backwards compability - replace twig tags with parameters
281
        $pattern_bc = '/\{\{\s(?<param>[a-zA-Z0-9.]+)\s\}\}+/';
282
283
        if (preg_match_all($pattern_bc, $subject, $match_params)) {
284
            $string = preg_filter($pattern_bc, '%\1%', $subject);
285
286
            $param = array();
287
            foreach ($match_params['param'] as $value) {
288
                $param[$value] = $value;
289
            }
290
291
            return array(
292
                'string' => $string,
293
                'params' => $param
294
            );
295
        }
296
297
        # Feature - read key/value syntax parameters
298
        $pattern_string = '/^(?<string>[^|]+)(?<parameter_bag>\|\{(\s?%[a-zA-Z0-9.]+%:\s[a-zA-Z0-9.]+,?\s?)+\s?\}\|)\s*$/';
299
        $pattern_params = '/(?>(?<=(\|\{\s|.,\s))%(?<key>[a-zA-Z0-9.]+)%:\s(?<value>[a-zA-Z0-9.]+)(?=(,\s.|\s\}\|)))+/';
300
301 View Code Duplication
        if (preg_match($pattern_string, $subject, $match_string)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
302
            $string = $match_string['string'];
303
            $parameter_bag = $match_string['parameter_bag'];
304
305
            $param = array();
306
            preg_match_all($pattern_params, $parameter_bag, $match_params, PREG_SET_ORDER);
307
308
            foreach ($match_params as $match) {
0 ignored issues
show
Bug introduced by
The expression $match_params of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
309
                $param[$match['key']] = $match['value'];
310
            }
311
312
            return array(
313
                'string' => $string,
314
                'params' => $param
315
            );
316
        }
317
318
        # Feature - read abbreviated syntax parameters
319
        $abbreviated_pattern_string = '/^(?<string>[^|]+)(?<parameter_bag>\|\{(\s?[a-zA-Z0-9.]+,?\s?)+\s?\}\|)\s*$/';
320
        $abbreviated_pattern_params = '/(?>(?<=(\|\{\s|.,\s))(?<param>[a-zA-Z0-9.]+)(?=(,\s.|\s\}\|)))+?/';
321
322 View Code Duplication
        if (preg_match($abbreviated_pattern_string, $subject, $match_string)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
323
            $string = $match_string['string'];
324
            $parameter_bag = $match_string['parameter_bag'];
325
326
            $param = array();
327
            preg_match_all($abbreviated_pattern_params, $parameter_bag, $match_params);
328
329
            foreach ($match_params['param'] as $value) {
330
                $param[$value] = $value;
331
            }
332
333
            return array(
334
                'string' => $string,
335
                'params' => $param
336
            );
337
        }
338
339
        // If subject does not match any pattern, return false
340
        return false;
341
    }
342
343
    /**
344
     * Converts an assoc array to a twig array expression (string) .
345
     * Only in case a value contains '{{' and '}}' the value won't be
346
     * wrapped in quotes.
347
     *
348
     * An array like:
349
     * <code>
350
     * $array = array('a' => 'b', 'c' => 'd', 'e' => '{{f}}');
351
     * </code>
352
     *
353
     * Will be converted to:
354
     * <code>
355
     * "{ a: 'b', c: 'd', e: f }"
356
     * </code>
357
     *
358
     * @return string
359
     */
360
    private function getTwigAssociativeArray(array $hashmap)
361
    {
362
        $contents = array();
363
        foreach ($hashmap as $key => $value) {
364
            if (!strstr($value, '{{') || !strstr($value, '}}')) {
365
                $value = "'$value'";
366
            } else {
367
                $value = trim(str_replace(array('{{', '}}'), '', $value));
368
            }
369
370
            $contents[] = "$key: $value";
371
        }
372
373
        return '{ ' . implode(', ', $contents) . ' }';
374
    }
375
376
    /**
377
     * Returns the name of the extension.
378
     *
379
     * @return string The extension name
380
     */
381
    public function getName()
382
    {
383
        return 'admingenerator_echo';
384
    }
385
}
386