ObjectFormWidget::objData()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Charcoal\Admin\Widget;
4
5
use UnexpectedValueException;
6
use InvalidArgumentException;
7
8
// From Pimple
9
use Pimple\Container;
10
11
// From 'charcoal-ui'
12
use Charcoal\Ui\FormGroup\FormGroupInterface;
13
use Charcoal\Ui\Form\FormInterface;
14
15
// From 'charcoal-admin'
16
use Charcoal\Admin\Widget\FormWidget;
17
use Charcoal\Admin\Widget\FormPropertyWidget;
18
19
use Charcoal\Admin\Ui\ObjectContainerInterface;
20
use Charcoal\Admin\Ui\ObjectContainerTrait;
21
22
/**
23
 * Object Admin Form
24
 */
25
class ObjectFormWidget extends FormWidget implements
26
    ObjectContainerInterface
27
{
28
    use ObjectContainerTrait;
29
30
    /**
31
     * @var string
32
     */
33
    protected $formIdent;
34
35
    /**
36
     * @var array
37
     */
38
    protected $formData;
39
40
    /**
41
     * @return string
42
     */
43
    public function widgetType()
44
    {
45
        return 'charcoal/admin/widget/object-form';
46
    }
47
48
    /**
49
     * Retrieve the default label for the form submission button.
50
     *
51
     * @return Translation|string|null
0 ignored issues
show
Bug introduced by
The type Charcoal\Admin\Widget\Translation 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...
52
     */
53
    public function defaultSubmitLabel()
54
    {
55
        if ($this->objId()) {
56
            return $this->translator()->translation('Update');
57
        }
58
59
        return parent::defaultSubmitLabel();
60
    }
61
62
    /**
63
     * @param array $data The widget data.
64
     * @return ObjectForm Chainable
65
     */
66
    public function setData(array $data)
67
    {
68
        parent::setData($data);
69
70
        $this->mergeDataSources($data);
71
72
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Charcoal\Admin\Widget\ObjectFormWidget which is incompatible with the documented return type Charcoal\Admin\Widget\ObjectForm.
Loading history...
73
    }
74
75
    /**
76
     * Set the key for the form structure to use.
77
     *
78
     * @param  string $formIdent The form identifier.
79
     * @throws InvalidArgumentException If the identifier is not a string.
80
     * @return ObjectForm Chainable
81
     */
82
    public function setFormIdent($formIdent)
83
    {
84
        if (!is_string($formIdent)) {
0 ignored issues
show
introduced by
The condition is_string($formIdent) is always true.
Loading history...
85
            throw new InvalidArgumentException(
86
                'Form identifier must be a string'
87
            );
88
        }
89
90
        $this->formIdent = $formIdent;
91
92
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Charcoal\Admin\Widget\ObjectFormWidget which is incompatible with the documented return type Charcoal\Admin\Widget\ObjectForm.
Loading history...
93
    }
94
95
    /**
96
     * Retrieve a key for the form structure to use.
97
     *
98
     * If the form key is undefined, resolve a fallback.
99
     *
100
     * @return string
101
     */
102
    public function formIdentFallback()
103
    {
104
        $metadata = $this->obj()->metadata();
105
106
        if (isset($metadata['admin']['default_form'])) {
107
            return $metadata['admin']['default_form'];
108
        }
109
110
        return '';
111
    }
112
113
    /**
114
     * Retrieve the key for the form structure to use.
115
     *
116
     * @return string
117
     */
118
    public function formIdent()
119
    {
120
        return $this->formIdent;
121
    }
122
123
    /**
124
     * @param string $url The next URL.
125
     * @throws InvalidArgumentException If argument is not a string.
126
     * @return ActionInterface Chainable
0 ignored issues
show
Bug introduced by
The type Charcoal\Admin\Widget\ActionInterface 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...
127
     */
128
    public function setNextUrl($url)
129
    {
130
        if (!is_string($url)) {
0 ignored issues
show
introduced by
The condition is_string($url) is always true.
Loading history...
131
            throw new InvalidArgumentException(
132
                'URL needs to be a string'
133
            );
134
        }
135
136
        if (!$this->obj()) {
137
            $this->nextUrl = $url;
0 ignored issues
show
Bug Best Practice introduced by
The property nextUrl does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
138
139
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Charcoal\Admin\Widget\ObjectFormWidget which is incompatible with the documented return type Charcoal\Admin\Widget\ActionInterface.
Loading history...
140
        }
141
142
        $obj = $this->obj();
143
        if ($obj->view()) {
0 ignored issues
show
Bug introduced by
The method view() does not exist on Charcoal\Model\ModelInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Charcoal\Queue\QueueItemInterface or Charcoal\Object\ContentInterface or Charcoal\Object\UserDataInterface or Charcoal\User\UserInterface. 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

143
        if ($obj->/** @scrutinizer ignore-call */ view()) {
Loading history...
144
            $this->nextUrl = $obj->render($url);
0 ignored issues
show
Bug introduced by
The method render() does not exist on Charcoal\Model\ModelInterface. It seems like you code against a sub-type of said class. However, the method does not exist in Charcoal\Queue\QueueItemInterface or Charcoal\Object\ContentInterface or Charcoal\Object\UserDataInterface or Charcoal\User\UserInterface. 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

144
            /** @scrutinizer ignore-call */ 
145
            $this->nextUrl = $obj->render($url);
Loading history...
145
        }
146
147
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Charcoal\Admin\Widget\ObjectFormWidget which is incompatible with the documented return type Charcoal\Admin\Widget\ActionInterface.
Loading history...
148
    }
149
150
    /**
151
     * Form action (target URL)
152
     *
153
     * @return string Relative URL
154
     */
155
    public function action()
156
    {
157
        $action = parent::action();
158
        if (!$action) {
159
            $obj   = $this->obj();
160
            $objId = $obj->id();
161
            if ($objId) {
162
                return 'object/update';
163
            } else {
164
                return 'object/save';
165
            }
166
        } else {
167
            return $action;
168
        }
169
    }
170
171
    /**
172
     * Retrieve the object's properties as form controls.
173
     *
174
     * @param  array $group An optional group to use.
175
     * @throws UnexpectedValueException If a property data is invalid.
176
     * @return FormPropertyWidget[]|\Generator
177
     */
178
    public function formProperties(array $group = null)
179
    {
180
        $obj   = $this->obj();
181
        $props = $obj->metadata()->properties();
182
183
        // We need to sort form properties by form group property order if a group exists
184
        if (!empty($group)) {
185
            $props = array_merge(array_flip($group), $props);
186
        }
187
188
        foreach ($props as $propertyIdent => $propertyMetadata) {
189
            if (method_exists($obj, 'filterPropertyMetadata')) {
190
                $propertyMetadata = $obj->filterPropertyMetadata($propertyMetadata, $propertyIdent);
191
            }
192
193
            if (!is_array($propertyMetadata)) {
194
                throw new UnexpectedValueException(sprintf(
195
                    'Invalid property data for "%1$s", received %2$s',
196
                    $propertyIdent,
197
                    (is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata))
198
                ));
199
            }
200
201
            $formProperty = $this->getOrCreateFormProperty($propertyIdent, $propertyMetadata);
202
203
            if (!$formProperty->hidden()) {
204
                yield $propertyIdent => $formProperty;
205
            }
206
        }
207
    }
208
209
    /**
210
     * Retrieve an object property as a form control.
211
     *
212
     * @param  string $propertyIdent An optional group to use.
213
     * @throws InvalidArgumentException If the property identifier is not a string.
214
     * @throws UnexpectedValueException If a property data is invalid.
215
     * @return FormPropertyWidget
216
     */
217
    public function formProperty($propertyIdent)
218
    {
219
        if (!is_string($propertyIdent)) {
0 ignored issues
show
introduced by
The condition is_string($propertyIdent) is always true.
Loading history...
220
            throw new InvalidArgumentException(
221
                'Property ident must be a string'
222
            );
223
        }
224
225
        if (isset($this->formProperties[$propertyIdent])) {
226
            return $this->formProperties[$propertyIdent];
227
        }
228
229
        $propertyMetadata = $this->obj()->metadata()->property($propertyIdent);
230
231
        if (!is_array($propertyMetadata)) {
232
            throw new UnexpectedValueException(sprintf(
233
                'Invalid property data for "%1$s", received %2$s',
234
                $propertyIdent,
235
                (is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata))
236
            ));
237
        }
238
239
        $p = $this->getOrCreateFormProperty($propertyIdent, $propertyMetadata);
240
241
        return $p;
242
    }
243
244
    /**
245
     * Set the form's auxiliary data.
246
     *
247
     * This method is called via {@see self::setData()} if a "form_data" parameter
248
     * is present on the HTTP request.
249
     *
250
     * @param array $data Data.
251
     * @return ObjectFormWidget Chainable.
252
     */
253
    public function setFormData(array $data)
254
    {
255
        $objData = $this->objData();
256
        $merged  = array_replace_recursive($objData, $data);
257
258
        // Remove null values
259
        $merged = array_filter($merged, function ($val) {
260
            if ($val === null) {
261
                return false;
262
            }
263
264
            return true;
265
        });
266
267
        $this->formData = $merged;
268
        $this->obj()->setData($merged);
269
270
        return $this;
271
    }
272
273
    /**
274
     * Retrieve the form's auxiliary  data.
275
     *
276
     * @return array
277
     */
278
    public function formData()
279
    {
280
        if (!$this->formData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->formData 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...
281
            $this->formData = $this->objData();
282
        }
283
284
        return $this->formData;
285
    }
286
287
    /**
288
     * Object data.
289
     * @return array Object data.
290
     */
291
    public function objData()
292
    {
293
        return $this->obj()->data();
294
    }
295
296
    /**
297
     * Retrieve the widget's data options for JavaScript components.
298
     *
299
     * @return array
300
     */
301
    public function widgetDataForJs()
302
    {
303
        return [
304
            'obj_id'           => $this->objId(),
305
            'obj_type'         => $this->objType(),
306
            'template'          => $this->template(),
307
            'form_selector'    => '#'.$this->widgetId(),
308
            'tab'              => $this->isTabbable(),
309
            'group_conditions' => $this->groupsConditionalLogic(),
310
        ];
311
    }
312
313
    /**
314
     * Self recursive when a groups is an instance of FormInterface.
315
     *
316
     * @param array|null $groups Form groups to parse.
317
     * @return array
318
     */
319
    protected function groupsConditionalLogic(array $groups = null)
320
    {
321
        if (!$groups) {
322
            $groups = iterator_to_array($this->groups());
323
        }
324
325
        $conditions = [];
326
327
        foreach ($groups as $group) {
328
            if ($group instanceof FormInterface) {
329
                $groupGroups = iterator_to_array($group->groups());
330
                if (!empty($groupGroups)) {
331
                    $conditions = array_merge(
332
                        $conditions,
333
                        $this->groupsConditionalLogic($groupGroups)
334
                    );
335
                }
336
            }
337
338
            if ($group instanceof FormGroupInterface && $group->conditionalLogic()) {
339
                $conditions = array_merge($conditions, $group->conditionalLogic());
340
            }
341
        }
342
343
        return $conditions;
344
    }
345
346
    /**
347
     * @param Container $container The DI container.
348
     * @return void
349
     */
350
    protected function setDependencies(Container $container)
351
    {
352
        parent::setDependencies($container);
353
354
        // Fill ObjectContainerInterface dependencies
355
        $this->setModelFactory($container['model/factory']);
356
    }
357
358
    /**
359
     * Retrieve the default data sources (when setting data on an entity).
360
     *
361
     * @return string[]
362
     */
363
    protected function defaultDataSources()
364
    {
365
        return [static::DATA_SOURCE_REQUEST, static::DATA_SOURCE_OBJECT];
366
    }
367
368
    /**
369
     * Retrieve the default data source filters (when setting data on an entity).
370
     *
371
     * @return array
372
     */
373
    protected function defaultDataSourceFilters()
374
    {
375
        return [
376
            'request' => null,
377
            'object'  => 'array_replace_recursive'
378
        ];
379
    }
380
381
    /**
382
     * Retrieve the default data source filters (when setting data on an entity).
383
     *
384
     * Note: Adapted from {@see \Slim\CallableResolver}.
385
     *
386
     * @link   https://github.com/slimphp/Slim/blob/3.x/Slim/CallableResolver.php
387
     * @param  mixed $toResolve A callable used when merging data.
388
     * @return callable|null
389
     */
390
    protected function resolveDataSourceFilter($toResolve)
391
    {
392
        if (is_string($toResolve)) {
393
            $obj = $this->obj();
394
395
            $resolved = [$obj, $toResolve];
396
397
            // Sheck for Slim callable
398
            $callablePattern = '!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!';
399
            if (preg_match($callablePattern, $toResolve, $matches)) {
400
                $class  = $matches[1];
401
                $method = $matches[2];
402
403
                if ($class === 'parent') {
404
                    $resolved = [$obj, $class.'::'.$method];
405
                }
406
            }
407
408
            $toResolve = $resolved;
409
        }
410
411
        return parent::resolveDataSourceFilter($toResolve);
412
    }
413
414
    /**
415
     * Retrieve the accepted metadata from the current request.
416
     *
417
     * @return array
418
     */
419
    protected function acceptedRequestData()
420
    {
421
        return array_merge(
422
            ['obj_type', 'obj_id', 'template'],
423
            parent::acceptedRequestData()
424
        );
425
    }
426
427
    /**
428
     * Fetch metadata from the current object type.
429
     *
430
     * @return array
431
     */
432
    protected function dataFromObject()
433
    {
434
        $obj           = $this->obj();
435
        $objMetadata   = $obj->metadata();
436
        $adminMetadata = (isset($objMetadata['admin']) ? $objMetadata['admin'] : null);
437
438
439
        $formIdent = $this->formIdent();
440
        if (!$formIdent) {
441
            $formIdent = $this->formIdentFallback();
442
        }
443
444
445
        if ($formIdent && $obj->view()) {
446
            $formIdent = $obj->render($formIdent);
447
        }
448
449
        if (isset($adminMetadata['forms'][$formIdent])) {
450
            $objFormData = $adminMetadata['forms'][$formIdent];
451
        } else {
452
            $objFormData = [];
453
        }
454
455
        if (isset($objFormData['groups']) && isset($adminMetadata['form_groups'])) {
456
            $extraFormGroups = array_intersect(
457
                array_keys($adminMetadata['form_groups']),
458
                array_keys($objFormData['groups'])
459
            );
460
            foreach ($extraFormGroups as $groupIdent) {
461
                $objFormData['groups'][$groupIdent] = array_replace_recursive(
462
                    $adminMetadata['form_groups'][$groupIdent],
463
                    $objFormData['groups'][$groupIdent]
464
                );
465
            }
466
        }
467
468
        if (isset($objFormData['sidebars']) && isset($adminMetadata['form_sidebars'])) {
469
            $extraFormSidebars = array_intersect(
470
                array_keys($adminMetadata['form_sidebars']),
471
                array_keys($objFormData['sidebars'])
472
            );
473
            foreach ($extraFormSidebars as $sidebarIdent) {
474
                $objFormData['sidebars'][$sidebarIdent] = array_replace_recursive(
475
                    $adminMetadata['form_sidebars'][$sidebarIdent],
476
                    $objFormData['sidebars'][$sidebarIdent]
477
                );
478
            }
479
        }
480
481
        return $objFormData;
482
    }
483
484
    /**
485
     * Parse a form group.
486
     *
487
     * @param  string                   $groupIdent The group identifier.
488
     * @param  array|FormGroupInterface $group      The group object or structure.
489
     * @throws InvalidArgumentException If the identifier is not a string or the group is invalid.
490
     * @return FormGroupInterface
491
     */
492
    protected function parseFormGroup($groupIdent, $group)
493
    {
494
        $group = parent::parseFormGroup($groupIdent, $group);
495
496
        if (method_exists($this->obj(), 'filterAdminFormGroup')) {
497
            $group = $this->obj()->filterAdminFormGroup($group, $groupIdent);
498
        }
499
500
        return $group;
501
    }
502
503
    /**
504
     * Yield the form's property controls.
505
     *
506
     * @return array
507
     */
508
    public function parseFormProperties()
509
    {
510
        return $this->formProperties;
511
    }
512
513
    /**
514
     * Create a new form group widget.
515
     *
516
     * @see    \Charcoal\Ui\Form\FormTrait::createFormGroup()
517
     * @param  array|null $data Optional. The form group data to set.
518
     * @return FormGroupInterface
519
     */
520
    protected function createFormGroup(array $data = null)
521
    {
522
        if (isset($data['type'])) {
523
            $type = $data['type'];
524
        } else {
525
            $type = $this->defaultGroupType();
526
        }
527
528
        $group = $this->formGroupFactory()->create($type);
0 ignored issues
show
Bug introduced by
The method create() does not exist on Charcoal\Ui\Form\FormInterface. ( Ignorable by Annotation )

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

528
        $group = $this->formGroupFactory()->/** @scrutinizer ignore-call */ create($type);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
529
        $group->setForm($this->formWidget());
530
531
        if ($group instanceof ObjectContainerInterface) {
532
            if (empty($group->objType())) {
533
                $group->setObjType($this->objType());
534
            }
535
536
            if (empty($group->objId()) && !empty($this->objId())) {
537
                $group->setObjId($this->objId());
538
            }
539
        }
540
541
        if ($data !== null) {
542
            $group->setData($data);
543
        }
544
545
        return $group;
546
    }
547
548
    /**
549
     * Update the given form group widget.
550
     *
551
     * @see    \Charcoal\Ui\Form\FormTrait::updateFormGroup()
552
     * @param  FormGroupInterface $group      The form group to update.
553
     * @param  array|null         $groupData  Optional. The new group data to apply.
554
     * @param  string|null        $groupIdent Optional. The new group identifier.
555
     * @return FormGroupInterface
556
     */
557
    protected function updateFormGroup(
558
        FormGroupInterface $group,
559
        array $groupData = null,
560
        $groupIdent = null
561
    ) {
562
        $group->setForm($this);
563
564
        if ($groupIdent !== null) {
565
            $group->setIdent($groupIdent);
566
        }
567
568
        if ($group instanceof ObjectContainerInterface) {
569
            if (empty($group->objType())) {
570
                $group->setObjType($this->objType());
571
            }
572
573
            if (empty($group->objId()) && !empty($this->objId())) {
574
                $group->setObjId($this->objId());
575
            }
576
        }
577
578
        if ($groupData !== null) {
579
            $group->setData($groupData);
580
        }
581
582
        return $group;
583
    }
584
}
585