Issues (689)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Admin/Traits/Mapper.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the Blast Project package.
5
 *
6
 * Copyright (C) 2015-2017 Libre Informatique
7
 *
8
 * This file is licenced under the GNU LGPL v3.
9
 * For the full copyright and license information, please view the LICENSE.md
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Blast\CoreBundle\Admin\Traits;
14
15
use Sonata\AdminBundle\Datagrid\ListMapper;
16
use Sonata\AdminBundle\Form\FormMapper;
17
use Sonata\AdminBundle\Mapper\BaseGroupedMapper;
18
use Sonata\AdminBundle\Mapper\BaseMapper;
19
use Sonata\AdminBundle\Show\ShowMapper;
20
use Symfony\Component\Validator\Constraints\NotBlank;
21
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
22
use Exception;
23
24
trait Mapper
25
{
26
    /**
27
     * Force tabulations on Show views.
28
     *
29
     * @var bool
30
     */
31
    protected $forceTabs = false;
32
33
    /**
34
     * Links in the view navbar.
35
     *
36
     * @var array
37
     */
38
    protected $helperLinks = [];
39
40
    /**
41
     * Admin titles (for list, show, edit and create).
42
     *
43
     * @var string
44
     */
45
    public $titles = [];
46
47
    /**
48
     * Admin title templates (for list, show, edit and create).
49
     *
50
     * @var array
51
     */
52
    public $titleTemplates = [];
53
54
    protected function configureMapper(BaseMapper $mapper)
55
    {
56
        $classes = $this->getCurrentComposition();
0 ignored issues
show
It seems like getCurrentComposition() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
57
        $blast = $this
58
            ->getConfigurationPool()
59
            ->getContainer()
60
            ->getParameter('blast')
61
        ;
62
        $this
63
            ->getConfigurationPool()
64
            ->getContainer()
65
            ->get('logger')
66
            ->debug(sprintf(
67
                '[BlastCoreBundle] Processing the configuration in this order: %s',
68
                 implode(', ', $classes)
69
            ))
70
        ;
71
72
        $fcts = [
73
            'tabs' => $mapper instanceof ShowMapper ?
74
                ['getter' => 'getShowTabs', 'setter' => 'setShowTabs'] :
75
                ['getter' => 'getFormTabs', 'setter' => 'setFormTabs'],
76
            'groups' => $mapper instanceof ShowMapper ?
77
                ['getter' => 'getShowGroups', 'setter' => 'setShowGroups'] :
78
                ['getter' => 'getFormGroups', 'setter' => 'setFormGroups'],
79
        ];
80
81
        // Figure out if we have to display tabs on the Show view
82
        $this->forceTabs = false;
83
        if ($mapper instanceof ShowMapper) {
84
            foreach ($classes as $class) {
85
                if (isset($blast[$class])) {
86
                    foreach (array_reverse($list = array_merge([get_class($mapper)], array_values(class_parents($mapper)))) as $mapper_class) {
87
                        if (!empty($blast[$class][$mapper_class]['forceTabs'])) {
88
                            $this->forceTabs = true;
89
                        }
90
                    }
91
                }
92
            }
93
        }
94
95
        // builds the configuration, based on the Mapper class
96
        foreach ($classes as $class) {
97
            if (!isset($blast[$class])) {
98
                continue;
99
            }
100
101
            // copy stuff from elsewhere
102
            foreach (array_reverse($list = array_merge([get_class($mapper)], array_values(class_parents($mapper)))) as $mapper_class) {
103
                if (isset($blast[$class][$mapper_class]) && !empty($blast[$class][$mapper_class]['_copy'])) {
104
                    if (!is_array($blast[$class][$mapper_class]['_copy'])) {
105
                        $blast[$class][$mapper_class]['_copy'] = [$blast[$class][$mapper_class]['_copy']];
106
                    }
107
                    foreach ($blast[$class][$mapper_class]['_copy'] as $copy) {
108
                        $list = array_merge(
109
                                $list, array_merge([$copy], array_values(class_parents($copy)))
110
                        );
111
                    }
112
                }
113
            }
114
115
            $specialKeys = ['_actions', '_list_actions', '_batch_actions', '_export_formats', '_extra_templates', '_helper_links'];
116
117
            // process data...
118
            foreach (array_reverse($list) as $mapper_class) {
119
                if (!isset($blast[$class][$mapper_class])) {
120
                    continue;
121
                }
122
123
                // remove fields
124
                if (isset($blast[$class][$mapper_class]['remove'])) {
125 View Code Duplication
                    if (isset($blast['all'][$mapper_class]['remove'])) {
126
                        $blast[$class][$mapper_class]['remove'] = array_merge_recursive(
127
                            $blast[$class][$mapper_class]['remove'],
128
                            $blast['all'][$mapper_class]['remove']
129
                        );
130
                    }
131
132
                    foreach ($blast[$class][$mapper_class]['remove'] as $key => $field) {
133
                        if (in_array($key, $specialKeys)) {
134
                            continue;
135
                        }
136
137
                        if ($mapper->has($key)) {
138
                            $mapper->remove($key);
139
140
                            // compensating the partial removal in Sonata Admin, that does not touch the groups when removing a field
141 View Code Duplication
                            if ($mapper instanceof BaseGroupedMapper) {
142
                                foreach ($groups = $this->{$fcts['groups']['getter']}() as $groupkey => $group) {
143
                                    if (isset($group['fields'][$key])) {
144
                                        unset($groups[$groupkey]['fields'][$key]);
145
                                        if (!$groups[$groupkey]['fields']) {
146
                                            unset($groups[$groupkey]);
147
                                        }
148
                                        $this->{$fcts['groups']['setter']}($groups);
149
                                    }
150
                                }
151
                            }
152
                        }
153
                    }
154
                }
155
156
                // add fields & more
157
                if (isset($blast[$class][$mapper_class]['add'])) {
158 View Code Duplication
                    if (isset($blast['all'][$mapper_class]['add'])) {
159
                        $blast[$class][$mapper_class]['add'] = array_merge(
160
                                    $blast[$class][$mapper_class]['add'],
161
                                    $blast['all'][$mapper_class]['add']
162
                                );
163
                    }
164
165
                    // do not parse _batch_actions & co
166
                    foreach ($specialKeys as $sk) {
167 View Code Duplication
                        if (isset($blast[$class][$mapper_class]['add'][$sk])) {
168
                            unset($blast[$class][$mapper_class]['add'][$sk]);
169
                        }
170
                    }
171
172
                    $this->addContent($mapper, $blast[$class][$mapper_class]['add']);
173
                }
174
175
                // set Admin titles
176
                $titleTemplate = isset($blast[$class][$mapper_class]['titleTemplate']) ? $blast[$class][$mapper_class]['titleTemplate'] : null;
177
                $title = isset($blast[$class][$mapper_class]['title']) ? $blast[$class][$mapper_class]['title'] : null;
178
                $this->setTitles($mapper, $titleTemplate, $title);
179
            }
180
        }
181
182
        if ($mapper instanceof BaseGroupedMapper) { // ShowMapper and FormMapper
183
            // removing empty groups
184
            $groups = $this->{$fcts['groups']['getter']}();
185
            if (is_array($groups)) {
186
                foreach ($groups as $groupkey => $group) {
187
                    if (!$group['fields']) {
188
                        unset($groups[$groupkey]);
189
                    }
190
                }
191
                $this->{$fcts['groups']['setter']}($groups);
192
            }
193
194
            // removing empty tabs
195
            $tabs = $this->{$fcts['tabs']['getter']}();
196 View Code Duplication
            if (is_array($tabs)) {
197
                foreach ($tabs as $tabkey => $tab) {
198
                    foreach ($tab['groups'] as $groupkey => $group) {
199
                        if (!isset($this->{$fcts['groups']['getter']}()[$group])) {
200
                            unset($tabs[$tabkey]['groups'][$groupkey]);
201
                        }
202
                    }
203
204
                    if (!$tabs[$tabkey]['groups']) {
205
                        unset($tabs[$tabkey]);
206
                    }
207
                }
208
                $this->{$fcts['tabs']['setter']}($tabs);
209
            }
210
        }
211
212
        $this->fixTemplates($mapper);
213
214
        if (!$mapper instanceof FormMapper) {
215
            $this->fixShowRoutes($mapper);
216
        }
217
218
        // Debug profiler
219
        $this->getConfigurationPool()->getContainer()->get('blast_core.profiler.collector')
220
            ->collectOnce('Mapper', $mapper)
221
            ->collectOnce('Managed classes', $classes);
222
223
        return $this;
224
    }
225
226
    /**
227
     * @param BaseMapper $mapper
228
     * @param array      $group
229
     *
230
     * @return BaseMapper
231
     */
232
    protected function addContent(BaseMapper $mapper, $group)
233
    {
234
        // helper links
235
        $this->parseHelperLinks();
236
237
        // flat organization (DatagridMapper / ListMapper...)
238
        if (!$mapper instanceof BaseGroupedMapper) {
239
            //list actions
240
            $this->addActions();
241
            $this->removeActions();
242
243
            // options pre-treatment
244
            $options = [];
245
            if (isset($group['_options'])) {
246
                $options = $group['_options'];
247
                unset($group['_options']);
248
            }
249
250
            // content
251
            foreach ($group as $add => $opts) {
252
                $this->addField($mapper, $add, $opts);
253
            }
254
255
            // options
256
            if (isset($options['fieldsOrder'])) {
257
                $mapper->reorder($options['fieldsOrder']);
258
            }
259
260
            // extra templates
261
            $this->parseExtraTemplates();
262
263
            return $mapper;
264
        }
265
266
        $fcts = [
267
            'tabs' => $mapper instanceof ShowMapper ?
268
            ['getter' => 'getShowTabs', 'setter' => 'setShowTabs'] :
269
            ['getter' => 'getFormTabs', 'setter' => 'setFormTabs'],
270
            'groups' => $mapper instanceof ShowMapper ?
271
            ['getter' => 'getShowGroups', 'setter' => 'setShowGroups'] :
272
            ['getter' => 'getFormGroups', 'setter' => 'setFormGroups'],
273
        ];
274
275
        // if a grouped organization can be shapped
276
        // options
277
        $tabsOptions = null;
278
        if (isset($group['_options'])) {
279
            $tabsOptions = $group['_options'];
280
            unset($group['_options']);
281
        }
282
283
        // content
284
        foreach ($group as $tab => $tabcontent) { // loop on content...
285
            if (self::arrayDepth($tabcontent) < 1) {
286
                // direct add
287
                $this->addField($mapper, $tab, $tabcontent);
288
                $mapper->end()->end();
289
            } else {
290
                // groups/withs order
291
                $groupsOrder = null;
292
                if (isset($tabcontent['_options']['groupsOrder'])) {
293
                    $groupsOrder = $tabcontent['_options']['groupsOrder'];
294
                    unset($tabcontent['_options']['groupsOrder']);
295
                }
296
297
                $endgroup = $endtab = false;
298
299
                // tab
300
                if (!empty($tabcontent['_options']['hideTitle']) || $mapper instanceof ShowMapper && !$this->forceTabs) {
301
                    // display tabs as groups
302
                    $tabs = $this->{$fcts['tabs']['getter']}();
303
                    $groups = $this->{$fcts['groups']['getter']}();
304
                    if (isset($tabs[$tab])) {
305
                        $tabs[$tab]['auto_created'] = true;
306
                        $this->{$fcts['tabs']['setter']}($tabs);
307
308
                        foreach ($groups as $groupkey => $group) {
309
                            if (!isset($groups[$group['name']])) {
310
                                $groups[$group['name']] = $group;
311
                                unset($groups[$groupkey]);
312
                            }
313
                        }
314
                        $this->{$fcts['groups']['setter']}($groups);
315
                    }
316
                } else {
317
                    $mapper->tab($tab, isset($tabcontent['_options']) ? $tabcontent['_options'] : []);
318
                    $endtab = true;
319
                }
320
321
                // adding count of collections items in tab
322
                if (isset($tabcontent['_options']['countChildItems']) && is_array($tabcontent['_options']['countChildItems'])) {
323
                    $tabs = $this->{$fcts['tabs']['getter']}();
324
                    if (strpos($tabs[$tab]['class'], 'countable-tab') === false) {
325
                        $tabs[$tab]['class'] .= ' countable-tab';
326
327
                        foreach ($tabcontent['_options']['countChildItems'] as $fieldToCount) {
328
                            if (strpos($tabs[$tab]['class'], 'count-' . $fieldToCount) === false) {
329
                                $tabs[$tab]['class'] .= ' count-' . $fieldToCount;
330
                            }
331
                        }
332
333
                        $this->{$fcts['tabs']['setter']}($tabs);
334
                    }
335
                }
336
337
                // clearing tabcontent options
338
                if (isset($tabcontent['_options'])) {
339
                    unset($tabcontent['_options']);
340
                }
341
342
                $finalOrder = null;
343
344
                // with
345
                if (self::arrayDepth($tabcontent) > 0) {
346
                    foreach ($tabcontent as $with => $withcontent) {
347
                        $opt = isset($withcontent['_options']) ? $withcontent['_options'] : [];
348
                        $finalOrder = (isset($opt['fieldsOrder']) ? $opt['fieldsOrder'] : null);
349
350
                        if (empty($opt['hideTitle'])) {
351
                            $endtab = true;
352
                            $endgroup = true;
353
                            $mapper->with($with, $opt);
354
                        }
355
                        if (isset($withcontent['_options'])) {
356
                            unset($withcontent['_options']);
357
                        }
358
359
                        // final adds
360
                        if (self::arrayDepth($withcontent) > 0) {
361
                            foreach ($withcontent as $name => $options) {
362
                                $fieldDescriptionOptions = [];
363
                                if (isset($options['_options'])) {
364
                                    $fieldDescriptionOptions = $options['_options'];
365
                                    unset($options['_options']);
366
                                }
367
                                $this->addField($mapper, $name, $options, $fieldDescriptionOptions);
368
                                $endgroup = $endtab = true;
369
                            }
370
                        }
371
372
                        if ($finalOrder != null) {
373
                            $mapper->reorder($finalOrder);
374
                        }
375
376
                        if ($endgroup) {
377
                            $mapper->end();
378
                        }
379
                    }
380
                }
381
382
                // order groups / withs (using tabs, because they are prioritary at the end)
383
                if (isset($groupsOrder)) {
384
                    // preparing
385
                    $otabs = $mapper->getAdmin()->{$fcts['tabs']['getter']}();
386
                    $groups = $mapper->getAdmin()->{$fcts['groups']['getter']}();
387
388
                    // pre-ordering
389
                    $newgroups = [];
390
                    $buf = empty($otabs[$tab]['auto_created']) ? "$tab." : '';
391
                    foreach ($groupsOrder as $groupname) {
392
                        if (isset($otabs[$tab]) && in_array("$buf$groupname", $otabs[$tab]['groups'])) {
393
                            $newgroups[] = "$buf$groupname";
394
                        }
395
                    }
396
397
                    // ordering tabs
398
                    foreach (empty($otabs[$tab]['groups']) ? [] : $otabs[$tab]['groups'] as $groupname) {
399
                        if (!in_array($groupname, $newgroups)) {
400
                            $newgroups[] = $groupname;
401
                        }
402
                    }
403
                    $otabs[$tab]['groups'] = $newgroups;
404
405
                    // "persisting"
406
                    $mapper->getAdmin()->{$fcts['tabs']['setter']}($otabs);
407
                }
408
409
                if ($endtab) {
410
                    $mapper->end();
411
                }
412
            }
413
        }
414
415
        // ordering tabs
416
        if (isset($tabsOptions['tabsOrder']) && $tabs = $this->{$fcts['tabs']['getter']}()) {
417
            $newtabs = [];
418
            foreach ($tabsOptions['tabsOrder'] as $tabname) {
419
                if (isset($tabs[$tabname])) {
420
                    $newtabs[$tabname] = $tabs[$tabname];
421
                }
422
            }
423
            foreach ($tabs as $tabname => $tab) {
424
                if (!isset($newtabs[$tabname])) {
425
                    $newtabs[$tabname] = $tab;
426
                }
427
            }
428
            $this->{$fcts['tabs']['setter']}($newtabs);
429
        }
430
431
        // ordering the ShowMapper
432
        if ($mapper instanceof ShowMapper) {
433
            foreach ($group as $tabName => $tabContent) {
434
                $tabOptions = null;
435
                if (isset($tabContent['_options'])) {
436
                    $tabOptions = $tabContent['_options'];
437
                    unset($tabContent['_options']);
438
                }
439
440
                if (isset($tabOptions['groupsOrder'])) {
441
                    $tabs = $this->{$fcts['tabs']['getter']}();
442
                    $groups = $this->{$fcts['groups']['getter']}();
443
444
                    $groupOrder = $tabOptions['groupsOrder'];
445
446
                    $properOrderedArray = array_merge(array_flip($groupOrder), $groups);
447
448
                    $this->{$fcts['groups']['setter']}($properOrderedArray);
449
                    $this->{$fcts['tabs']['setter']}($tabs);
450
                }
451
            }
452
        }
453
454
        return $mapper;
455
    }
456
457
    protected function addField(BaseMapper $mapper, $name, $options = [], $fieldDescriptionOptions = [])
458
    {
459
        // avoid duplicates
460
        if ($mapper->has($name)) {
461
            $mapper->remove($name);
462
        }
463
464
        if (!is_array($options)) {
465
            $options = [];
466
        }
467
468 View Code Duplication
        if (isset($options['only_new'])) {
469
            if ($options['only_new'] && $this->subject && !$this->subject->isNew()) {
470
                return $mapper;
471
            }
472
            unset($options['only_new']);
473
        }
474
475 View Code Duplication
        if (isset($options['only_not_new'])) {
476
            if ($options['only_not_new'] && (!$this->subject || $this->subject->isNew())) {
477
                return $mapper;
478
            }
479
            unset($options['only_not_new']);
480
        }
481
482
        $type = null;
483
        if (isset($options['type'])) {
484
            $type = $options['type'];
485
            unset($options['type']);
486
        }
487
488
        if (isset($options['constraints'])) {
489
            foreach ($options['constraints'] as $k => $constraint) {
490
                $options['constraints'][$k] = new $constraint();
491
            }
492
        }
493
494
        if (isset($options['required']) && $options['required'] === true) {
495
            $options['constraints'] = [new NotBlank()];
496
        }
497
498
        if (isset($options['query'])) {
499
            $this->manageQueryCallback($mapper, $options);
500
        }
501
502
        if (isset($options['choicesCallback'])) {
503
            $this->manageChoicesCallback($mapper, $options);
504
        }
505
506
        if (isset($options['serviceCallback'])) {
507
            $this->manageServiceCallback($mapper, $options);
508
        }
509
510
        // save-and-remove CoreBundle-specific options
511
        $extras = [];
512
        foreach ([
513
            'template' => 'setTemplate',
514
            'initializeAssociationAdmin' => null,
515
        ] as $extra => $method) {
516
            if (isset($fieldDescriptionOptions[$extra])) {
517
                $extras[$extra] = [$method, $fieldDescriptionOptions[$extra]];
518
                unset($fieldDescriptionOptions[$extra]);
519
            }
520
        }
521
522
        $mapper->add($name, $type, $options, $fieldDescriptionOptions);
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class Sonata\AdminBundle\Mapper\BaseMapper as the method add() does only exist in the following sub-classes of Sonata\AdminBundle\Mapper\BaseMapper: Sonata\AdminBundle\Datagrid\DatagridMapper, Sonata\AdminBundle\Datagrid\ListMapper, Sonata\AdminBundle\Form\FormMapper, Sonata\AdminBundle\Show\ShowMapper. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
523
524
        // apply extra options
525
        foreach ($extras as $extra => $call) {
526
            if ($call[0]) {
527
                $mapper->get($name)->{$call[0]}($call[1]);
528
            } else {
529
                switch ($extra) {
530
                    case 'initializeAssociationAdmin':
531
                        // only if "true"
532
                        if (!$call[1]) {
533
                            break;
534
                        }
535
536
                        // initialize the association-admin
537
                        $mapper->get($name)->getAssociationAdmin()->configureShowFields(new ShowMapper(
538
                                $mapper->get($name)->getAssociationAdmin()->getShowBuilder(), $mapper->get($name)->getAssociationAdmin()->getShow(), $mapper->get($name)->getAssociationAdmin()
539
                        ));
540
541
                        // set the efficient template
542
                        if (!isset($extras['template'])) {
543
                            $mapper->get($name)->setTemplate('BlastCoreBundle:CRUD:show_association_admin.html.twig');
544
                        }
545
                        break;
546
                }
547
            }
548
        }
549
550
        return $mapper;
551
    }
552
553
    protected function configureFields($function, BaseMapper $mapper, $class = null)
554
    {
555
        if (!$class) {
556
            $class = $this->getOriginalClass();
0 ignored issues
show
It seems like getOriginalClass() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
557
        }
558
559
        return $class::$function($mapper);
560
    }
561
562
    /**
563
     * @param array $actions
564
     * */
565
    protected function handleBatchActions(array $actions = [])
566
    {
567
        $blast = $this->getConfigurationPool()->getContainer()->getParameter('blast');
568
        $mapperClass = ListMapper::class;
569
        $actionKey = '_batch_actions';
570
571
        foreach ($this->getCurrentComposition() as $class) {
572
            if (isset($blast[$class][$mapperClass])) {
573
                $config = $blast[$class][$mapperClass];
574
575 View Code Duplication
                if (isset($blast['all'][$mapperClass])) {
576
                    $config = array_merge_recursive(
577
                        $config,
578
                        $blast['all'][$mapperClass]
579
                    );
580
                }
581
582
                // remove / reset
583
                if (isset($config['remove'][$actionKey])) {
584
                    $actions = parent::getBatchActions();
585
586
                    foreach ($config['remove'][$actionKey] as $action) {
587
                        if (isset($actions[$action])) {
588
                            unset($actions[$action]);
589
                        }
590
                    }
591
                }
592
593
                // add
594
                if (isset($config['add'][$actionKey])) {
595
                    $buf = $config['add'][$actionKey];
596
597
                    foreach ($buf as $action => $props) {
598
                        $name = 'batch_action_' . $action;
599
600
                        foreach ([
601
                            'label' => $name,
602
                            'params' => [],
603
                            'translation_domain' => $this->getTranslationDomain(),
604
                            'action' => $name,
605
                            'route' => 'batch_' . $action,
606
                        ] as $field => $value) {
607
                            if (empty($props[$field])) {
608
                                $props[$field] = $value;
609
                            }
610
                        }
611
612
                        $actions[$action] = $props;
613
                    }
614
                }
615
            }
616
        }
617
618
        return $actions;
619
    }
620
621
    /**
622
     * @param array $actions
623
     * */
624
    protected function handleListActions(array $actions = [])
625
    {
626
        $this->_listActionLoaded = true;
0 ignored issues
show
The property _listActionLoaded does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
627
        $blast = $this->getConfigurationPool()->getContainer()->getParameter('blast');
628
629
        foreach ($this->getCurrentComposition() as $class) {
630
            // remove / reset
631 View Code Duplication
            if (isset($blast[$class][ListMapper::class]['remove']['_list_actions'])) {
632
                foreach ($blast[$class][ListMapper::class]['remove']['_list_actions'] as $action) {
633
                    $this->removeListAction($action);
634
                }
635
            }
636
637
            // add
638
            if (isset($blast[$class][ListMapper::class]['add']['_list_actions'])) {
639
                foreach ($blast[$class][ListMapper::class]['add']['_list_actions'] as $action => $props) {
640
                    $props['translation_domain'] = isset($props['translation_domain']) ? $props['translation_domain'] : $this->getTranslationDomain();
641
                    $this->addListAction($action, $props);
642
                }
643
            }
644
        }
645
646
        return $this->getListActions();
0 ignored issues
show
It seems like getListActions() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
647
    }
648
649
    /**
650
     * @param array $formats
651
     * */
652
    protected function addPresetExportFormats(array $formats = [])
653
    {
654
        $blast = $this->getConfigurationPool()->getContainer()->getParameter('blast');
655
        $this->exportFields = $formats;
0 ignored issues
show
The property exportFields does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
656
657
        foreach ($this->getCurrentComposition() as $class) {
658
            // remove / reset
659
            if (isset($blast[$class][ListMapper::class]['remove']['_export_format'])) {
660
                $this->exportFields = [];
661
            }
662
663
            // add
664
            if (isset($blast[$class][ListMapper::class]['add']['_export_format'])) {
665
                foreach ($blast[$class][ListMapper::class]['add']['_export_format'] as $format => $fields) {
666
                    // if no fields are defined (not an associative array)
667
                    if (intval($format) . '' == '' . $format && !is_array($fields)) {
668
                        $format = $fields;
669
                        $this->exportFields[$format] = $fields = [];
670
                    }
671
672
                    // if a copy of an other format is requested
673
                    if (!is_array($fields) && isset($blast[$class][ListMapper::class]['add']['_export_format'][$fields])) {
674
                        $blast[$class][ListMapper::class]['add']['_export_format'][$format] = // the global fields array
675
                                $fields = // the local  fields array
676
                                $blast[$class][ListMapper::class]['add']['_export_format'][$fields];  // the source fields array
677
                    }
678
679
                    // removes a specific format
680
                    if (substr($format, 0, 1) == '-') {
681
                        unset($this->exportFields[substr($format, 1)]);
682
                        continue;
683
                    }
684
685
                    // if an order is defined, use it to order the extracted fields
686
                    if (!$fields && isset($blast[$class][ListMapper::class]['add']['_options']['fieldsOrder'])) {
687
                        // get back default fields
688
                        $tmp = parent::getExportFields();
689
                        $fields = [];
690
691
                        // takes the ordered fields
692 View Code Duplication
                        foreach ($blast[$class][ListMapper::class]['add']['_options']['fieldsOrder'] as $field) {
693
                            if (in_array($field, $tmp)) {
694
                                $fields[] = $field;
695
                            }
696
                        }
697
698
                        // then the forgotten fields as they come
699 View Code Duplication
                        foreach ($tmp as $field) {
700
                            if (!in_array($field, $blast[$class][ListMapper::class]['add']['_options']['fieldsOrder'])) {
701
                                $fields[] = $field;
702
                            }
703
                        }
704
                    }
705
                    $this->exportFields[$format] = $fields;
706
                }
707
            }
708
        }
709
710
        return $this->exportFields;
711
    }
712
713
    /**
714
     * @todo parse ShowMapper and FormMapper
715
     */
716
    protected function parseExtraTemplates()
717
    {
718
        $blast = $this->getConfigurationPool()->getContainer()->getParameter('blast');
719
720
        foreach ($this->getCurrentComposition() as $class) {
721
            // remove / reset
722
            if (isset($blast[$class][ListMapper::class]['remove']['_extra_templates'])) {
723
                // TODO
724
            }
725
726
            // add
727 View Code Duplication
            if (isset($blast[$class][ListMapper::class]['add']['_extra_templates'])) {
728
                foreach ($blast[$class][ListMapper::class]['add']['_extra_templates'] as $template) {
729
                    $this->addExtraTemplate('list', $template);
730
                }
731
            }
732
        }
733
    }
734
735
    protected function parseHelperLinks()
736
    {
737
        $blast = $this->getConfigurationPool()->getContainer()->getParameter('blast');
738
        $mappers = [
739
            'list' => ListMapper::class,
740
            'show' => ShowMapper::class,
741
            'form' => FormMapper::class,
742
        ];
743
744
        foreach ($this->getCurrentComposition() as $class) {
745
            foreach ($mappers as $mapper => $mapper_class) {
746
                // remove / reset
747
                if (isset($blast[$class][$mapper_class]['remove']['_helper_links'])) {
748
                    // TODO
749
                }
750
751
                // add
752 View Code Duplication
                if (isset($blast[$class][$mapper_class]['add']['_helper_links'])) {
753
                    foreach ($blast[$class][$mapper_class]['add']['_helper_links'] as $link) {
754
                        $this->addHelperLink($mapper, $link);
755
                    }
756
                }
757
            }
758
        }
759
    }
760
761
    protected function setTitles(BaseMapper $mapper, $titleTemplate, $title)
762
    {
763
        $contexts = [
764
            ListMapper::class => 'list',
765
            ShowMapper::class => 'show',
766
            FormMapper::class => 'form',
767
        ];
768
        if (!isset($contexts[get_class($mapper)])) {
769
            return;
770
        }
771
772
        $context = $contexts[get_class($mapper)];
773
        if ($titleTemplate) {
774
            $this->titleTemplates[$context] = $titleTemplate;
775
        }
776
        if ($title) {
777
            $this->titles[$context] = $title;
778
        }
779
    }
780
781
    protected function getFormThemeMapping()
782
    {
783
        $theme = [];
784
        $blast = $this->getConfigurationPool()->getContainer()->getParameter('blast');
785
786
        foreach ($this->getCurrentComposition() as $class) {
787
            if (isset($blast[$class])) {
788
                if (isset($blast[$class]['form_theme'])) {
789
                    $theme = array_merge($theme, $blast[$class]['form_theme']);
790
                }
791
            }
792
        }
793
794
        return $theme;
795
    }
796
797
    protected function getBaseRouteMapping()
798
    {
799
        $baseRoute = [];
800
        $blast = $this->getConfigurationPool()->getContainer()->getParameter('blast');
801
802
        foreach ($this->getCurrentComposition() as $class) {
803
            if (isset($blast[$class]) && isset($blast[$class]['baseRoute'])) {
804
                $reflexionClass = new \ReflectionClass($class);
805
                if (!$reflexionClass->isTrait()) {
806
                    $baseRoute = array_merge($baseRoute, $blast[$class]['baseRoute']);
807
                }
808
            }
809
        }
810
811
        return $baseRoute;
812
    }
813
814
    protected function manageCallback($mapper, &$options, $callbackType)
815
    {
816
        $option = $options[$callbackType];
817
818
        $entityClass = isset($options['class']) ? $options['class'] : $this->getClass();
819
820
        if (!is_array($option)) {
821
            throw new Exception('« $callbackType » option must be an array : ["FQDN"=>"static method name"]');
822
        }
823
824
        list($serviceNameOrClass, $methodName) = $option;
825
826
        $targetOptions = (isset($option[2]) ? $option[2] : null);
827
828
        if ($this->getConfigurationPool()->getContainer()->has($serviceNameOrClass)) {
829
            $callBackFunction = [$this->getConfigurationPool()->getContainer()->get($serviceNameOrClass), $methodName];
830
        } else {
831
            $callBackFunction = call_user_func($serviceNameOrClass . '::' . $methodName, $this->getModelManager(), $entityClass);
832
        }
833
834
        if ($targetOptions !== null) {
835
            $options[$targetOptions] = $callBackFunction;
836
            unset($options[$callbackType]);
837
        }
838
839
        return $callBackFunction;
840
    }
841
842
    protected function manageQueryCallback($mapper, &$options)
843
    {
844
        $callback = $this->manageCallback($mapper, $options, 'query');
845
        $options['query'] = $callback;
846
    }
847
848
    protected function manageChoicesCallback($mapper, &$options)
849
    {
850
        $callback = $this->manageCallback($mapper, $options, 'choicesCallback');
851
852
        $options['choices'] = $callback;
853
        $options['choice_loader'] = new CallbackChoiceLoader(function () use ($options) {
854
            return $options['choices'];
855
        });
856
        unset($options['choicesCallback']);
857
    }
858
859
    public function manageServiceCallback($mapper, &$options)
860
    {
861
        $this->manageCallback($mapper, $options, 'serviceCallback');
862
    }
863
}
864