Passed
Push — master ( 5655bd...d18046 )
by
unknown
02:33
created

FormTrait::formWidget()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Charcoal\Ui\Form;
4
5
use Charcoal\Admin\Widget\FormWidget;
6
use Charcoal\Ui\ConditionalizableInterface;
7
use Exception;
8
use InvalidArgumentException;
9
10
// From 'charcoal-factory'
11
use Charcoal\Factory\FactoryInterface;
12
13
// From 'charcoal-ui'
14
use Charcoal\Ui\Form\FormInterface;
15
use Charcoal\Ui\FormGroup\FormGroupInterface;
16
use Charcoal\Ui\PrioritizableInterface;
17
18
/**
19
 * Provides an implementation of {@see FormInterface}.
20
 */
21
trait FormTrait
22
{
23
    /**
24
     * The URI of a program that processes the form information.
25
     *
26
     * @var string
27
     */
28
    private $action = '';
29
30
    /**
31
     * The HTTP method that the browser uses to submit the form.
32
     *
33
     * @var string
34
     */
35
    private $method = 'post';
36
37
    /**
38
     * The form's display mode for multilingual fields.
39
     *
40
     * @var string
41
     */
42
    private $l10nMode = 'loop_inputs';
43
44
    /**
45
     * The form's display mode for groups.
46
     *
47
     * @var string
48
     */
49
    protected $groupDisplayMode;
50
51
    /**
52
     * The form's field groups.
53
     *
54
     * @var FormGroupInterface[]
55
     */
56
    protected $groups = [];
57
58
    /**
59
     * The form's predefined data.
60
     *
61
     * @var array $formData
62
     */
63
    private $formData = [];
64
65
    /**
66
     * Store the form's metadata instance.
67
     *
68
     * @var MetadataInterface
69
     */
70
    private $metadata;
71
72
    /**
73
     * Store the form's group factory instance.
74
     *
75
     * @var FactoryInterface
76
     */
77
    protected $formGroupFactory;
78
79
    /**
80
     * Store the form's group callback.
81
     *
82
     * @var callable
83
     */
84
    private $groupCallback;
85
86
    /**
87
     * Store the tab ident received through REQUEST.
88
     *
89
     * @var string
90
     */
91
    private $tabIdent;
92
93
    /**
94
     * Store the current form group.
95
     *
96
     * @var string
97
     */
98
    private $selectedFormGroup;
99
100
    /**
101
     * Comparison function used by {@see uasort()}.
102
     *
103
     * @param  PrioritizableInterface $a Sortable entity A.
104
     * @param  PrioritizableInterface $b Sortable entity B.
105
     * @return integer Sorting value: -1 or 1.
106
     */
107
    abstract protected function sortItemsByPriority(
108
        PrioritizableInterface $a,
109
        PrioritizableInterface $b
110
    );
111
112
    /**
113
     * @param FactoryInterface $factory A factory, to create customized form gorup objects.
114
     * @return FormInterface Chainable
115
     */
116
    public function setFormGroupFactory(FactoryInterface $factory)
117
    {
118
        $this->formGroupFactory = $factory;
119
120
        return $this;
121
    }
122
123
    /**
124
     * @throws Exception If the form group factory object was not set / injected.
125
     * @return FormInterface Chainable
126
     */
127
    protected function formGroupFactory()
128
    {
129
        if ($this->formGroupFactory === null) {
130
            throw new Exception(
131
                'Form group factory was not set.'
132
            );
133
        }
134
135
        return $this->formGroupFactory;
136
    }
137
138
    /**
139
     * @param callable $cb The group callback.
140
     * @return FormInterface Chainable
141
     */
142
    public function setGroupCallback(callable $cb)
143
    {
144
        $this->groupCallback = $cb;
145
146
        return $this;
147
    }
148
149
    /**
150
     * @param string $action The "action" value, typically a URL.
151
     * @throws InvalidArgumentException If the action argument is not a string.
152
     * @return FormInterface Chainable
153
     */
154
    public function setAction($action)
155
    {
156
        if (!is_string($action)) {
157
            throw new InvalidArgumentException(
158
                'Action must be a string'
159
            );
160
        }
161
        $this->action = $action;
162
163
        return $this;
164
    }
165
166
    /**
167
     * @return string
168
     */
169
    public function action()
170
    {
171
        return $this->action;
172
    }
173
174
    /**
175
     * Set the method (forcing lowercase) to "post" or "get".
176
     *
177
     * @param string $method Either "post" or "get".
178
     * @throws InvalidArgumentException If the method is not post or get.
179
     * @return FormInterface Chainable
180
     */
181
    public function setMethod($method)
182
    {
183
        $method = strtolower($method);
184
        if (!in_array($method, ['post', 'get'])) {
185
            throw new InvalidArgumentException(
186
                'Method must be "post" or "get"'
187
            );
188
        }
189
        $this->method = $method;
190
191
        return $this;
192
    }
193
194
    /**
195
     * @return string Either "post" or "get".
196
     */
197
    public function method()
198
    {
199
        return $this->method;
200
    }
201
202
    /**
203
     * @param string $mode The l10n mode.
204
     * @return FormInterface Chainable
205
     */
206
    public function setL10nMode($mode)
207
    {
208
        $this->l10nMode = $mode;
209
210
        return $this;
211
    }
212
213
    /**
214
     * @return string
215
     */
216
    public function l10nMode()
217
    {
218
        return $this->l10nMode;
219
    }
220
221
    /**
222
     * Set the object's form groups.
223
     *
224
     * @param array $groups A collection of group structures.
225
     * @return FormInterface Chainable
226
     */
227
    public function setGroups(array $groups)
228
    {
229
        $this->groups = [];
230
231
        foreach ($groups as $groupIdent => $group) {
232
            $this->addGroup($groupIdent, $group);
233
        }
234
235
        return $this;
236
    }
237
238
    /**
239
     * @return mixed
240
     */
241
    public function tabIdent()
242
    {
243
        return $this->tabIdent;
244
    }
245
246
    /**
247
     * @param mixed $tabIdent Store the tab ident received through REQUEST.
248
     * @return self
249
     */
250
    public function setTabIdent($tabIdent)
251
    {
252
        $this->tabIdent = $tabIdent;
253
254
        return $this;
255
    }
256
257
    /**
258
     * @return string
259
     */
260
    public function selectedFormGroup()
261
    {
262
        return $this->selectedFormGroup;
263
    }
264
265
    /**
266
     * @param mixed $selectedFormGroup Store the current form group.
267
     * @return self
268
     */
269
    public function setSelectedFormGroup($selectedFormGroup)
270
    {
271
        $this->selectedFormGroup = $selectedFormGroup;
272
273
        return $this;
274
    }
275
276
    /**
277
     * Add a form group.
278
     *
279
     * @param  string                   $groupIdent The group identifier.
280
     * @param  array|FormGroupInterface $group      The group object or structure.
281
     * @throws InvalidArgumentException If the identifier is not a string or the group is invalid.
282
     * @return FormInterface Chainable
283
     */
284
    public function addGroup($groupIdent, $group)
285
    {
286
        if ($group === false || $group === null) {
287
            return $this;
288
        }
289
290
        $group = $this->parseFormGroup($groupIdent, $group);
291
292
        if (isset($group['ident'])) {
293
            $groupIdent = $group['ident'];
294
        }
295
296
        $this->groups[$groupIdent] = $group;
297
298
        return $this;
299
    }
300
301
    /**
302
     * Parse a form group.
303
     *
304
     * @param  string                   $groupIdent The group identifier.
305
     * @param  array|FormGroupInterface $group      The group object or structure.
306
     * @throws InvalidArgumentException If the identifier is not a string or the group is invalid.
307
     * @return FormGroupInterface
308
     */
309
    protected function parseFormGroup($groupIdent, $group)
310
    {
311
        if (!is_string($groupIdent)) {
312
            throw new InvalidArgumentException(
313
                'Group identifier must be a string'
314
            );
315
        }
316
317
        if ($group instanceof FormGroupInterface) {
318
            $group = $this->updateFormGroup($group, null, $groupIdent);
319
        } elseif (is_array($group)) {
320
            $data = $group;
321
322
            if (!isset($data['ident'])) {
323
                $data['ident'] = $groupIdent;
324
            }
325
326
            $group = $this->createFormGroup($data);
327
        } else {
328
            throw new InvalidArgumentException(sprintf(
329
                'Group must be an instance of %s or an array of form group options, received %s',
330
                'FormGroupInterface',
331
                (is_object($group) ? get_class($group) : gettype($group))
332
            ));
333
        }
334
335
        return $group;
336
    }
337
338
    /**
339
     * Create a new form group widget.
340
     *
341
     * @param  array|null $data Optional. The form group data to set.
342
     * @return FormGroupInterface
343
     */
344
    protected function createFormGroup(array $data = null)
345
    {
346
        if (isset($data['type'])) {
347
            $type = $data['type'];
348
        } else {
349
            $type = $this->defaultGroupType();
350
        }
351
352
        $group = $this->formGroupFactory()->create($type);
0 ignored issues
show
Bug introduced by
The method create() does not seem to exist on object<Charcoal\Ui\Form\FormInterface>.

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...
353
        $group->setForm($this->formWidget());
354
355
        if ($data !== null) {
356
            $group->setData($data);
357
        }
358
359
        return $group;
360
    }
361
362
    /**
363
     * Update the given form group widget.
364
     *
365
     * @param  FormGroupInterface $group      The form group to update.
366
     * @param  array|null         $groupData  Optional. The new group data to apply.
367
     * @param  string|null        $groupIdent Optional. The new group identifier.
368
     * @return FormGroupInterface
369
     */
370
    protected function updateFormGroup(
371
        FormGroupInterface $group,
372
        array $groupData = null,
373
        $groupIdent = null
374
    ) {
375
        $group->setForm($this->formWidget());
376
377
        if ($groupData !== null) {
378
            $group->setData($groupData);
379
        }
380
381
        if ($groupIdent !== null) {
382
            $group->setIdent($groupIdent);
383
        }
384
385
        return $group;
386
    }
387
388
    /**
389
     * Retrieve the default form group class name.
390
     *
391
     * @return string
392
     */
393
    public function defaultGroupType()
394
    {
395
        return 'charcoal/ui/form-group/generic';
396
    }
397
398
    /**
399
     * Retrieve the form groups.
400
     *
401
     * @param callable $groupCallback Optional callback applied to each form group.
402
     * @return FormGroupInterface[]|Generator
403
     */
404
    public function groups(callable $groupCallback = null)
0 ignored issues
show
Coding Style introduced by
groups uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
405
    {
406
        $groups = $this->groups;
407
        uasort($groups, [ $this, 'sortItemsByPriority' ]);
408
409
        $groupCallback = (isset($groupCallback) ? $groupCallback : $this->groupCallback);
410
411
        $groups = $this->finalizeFormGroups($groups);
412
413
        $i = 1;
414
        foreach ($groups as $group) {
415
            if ($groupCallback) {
416
                $groupCallback($group);
417
            }
418
419
            $GLOBALS['widget_template'] = $group->template();
420
421
            if (!$this->selectedFormGroup() && $this->isTabbable()) {
422
                $group->setIsHidden(false);
423
                if ($i > 1) {
424
                    $group->setIsHidden(true);
425
                }
426
            }
427
            $i++;
428
429
            yield $group;
430
431
            $GLOBALS['widget_template'] = '';
432
        }
433
    }
434
435
    /**
436
     * @param array|FormGroupInterface[] $groups Form groups to finalize.
437
     * @return array|FormGroupInterface[]
438
     */
439
    protected function finalizeFormGroups($groups)
440
    {
441
        $out = [];
442
443
        foreach ($groups as $group) {
444
            if (!$group->active()) {
445
                continue;
446
            }
447
448
            if ($group instanceof ConditionalizableInterface) {
449
                if (!$group->resolvedCondition()) {
450
                    continue;
451
                }
452
            }
453
454
            // Test formGroup vs. ACL roles
455
            if ($group->isAuthorized() === false) {
456
                continue;
457
            }
458
459
            if (!$group->l10nMode()) {
460
                $group->setL10nMode($this->l10nMode());
461
            }
462
463
            if ($this->isTabbable() && $this->tabIdent()) {
464
                $group->setIsHidden(true);
465
466
                if ($group->ident() === $this->tabIdent()) {
467
                    $group->setIsHidden(false);
468
                    $this->setSelectedFormGroup($group->ident());
469
                }
470
            }
471
472
            $out[] = $group;
473
        }
474
475
        return $out;
476
    }
477
478
    /**
479
     * Determine if the form has any groups.
480
     *
481
     * @return boolean
482
     */
483
    public function hasGroups()
484
    {
485
        return ($this->numGroups() > 0);
486
    }
487
488
    /**
489
     * Determine if the form has a given group.
490
     *
491
     * @param string $groupIdent The group identifier to look up.
492
     * @throws InvalidArgumentException If the group identifier is invalid.
493
     * @return boolean
494
     */
495
    public function hasGroup($groupIdent)
496
    {
497
        if (!is_string($groupIdent)) {
498
            throw new InvalidArgumentException(
499
                'Group identifier must be a string'
500
            );
501
        }
502
503
        return isset($this->groups[$groupIdent]);
504
    }
505
506
    /**
507
     * Count the number of form groups.
508
     *
509
     * @return integer
510
     */
511
    public function numGroups()
512
    {
513
        return count($this->groups);
514
    }
515
516
    /**
517
     * Set the widget's content group display mode.
518
     *
519
     * Currently only supports "tab".
520
     *
521
     * @param string $mode Group display mode.
522
     * @throws InvalidArgumentException If the display mode is not a string.
523
     * @return ObjectFormWidget Chainable.
524
     */
525
    public function setGroupDisplayMode($mode)
526
    {
527
        if (!is_string($mode)) {
528
            throw new InvalidArgumentException(
529
                'Display mode must be a string'
530
            );
531
        }
532
533
        if ($mode === 'tabs') {
534
            $mode = 'tab';
535
        }
536
537
        $this->groupDisplayMode = $mode;
538
539
        return $this;
540
    }
541
542
    /**
543
     * Retrieve the widget's content group display mode.
544
     *
545
     * @return string Group display mode.
546
     */
547
    public function groupDisplayMode()
548
    {
549
        return $this->groupDisplayMode;
550
    }
551
552
    /**
553
     * Determine if content groups are to be displayed as tabbable panes.
554
     *
555
     * @return boolean
556
     */
557
    public function isTabbable()
558
    {
559
        return ($this->groupDisplayMode() === 'tab');
560
    }
561
562
    /**
563
     * @param array $formData The (pre-populated) form data, as [$key=>$val] array.
564
     * @return FormInterface Chainable
565
     */
566
    public function setFormData(array $formData)
567
    {
568
        $this->formData = $formData;
569
570
        return $this;
571
    }
572
573
    /**
574
     * @param string $key The form data key, or poperty identifier.
575
     * @param mixed  $val The form data value, for a given key.
576
     * @throws InvalidArgumentException If the key argument is not a string.
577
     * @return FormInterface Chainable
578
     */
579 View Code Duplication
    public function addFormData($key, $val)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
580
    {
581
        if (!is_string($key)) {
582
            throw new InvalidArgumentException(
583
                'Can not add form data: Data key must be a string'
584
            );
585
        }
586
        $this->formData[$key] = $val;
587
588
        return $this;
589
    }
590
591
    /**
592
     * @return array
593
     */
594
    public function formData()
595
    {
596
        return $this->formData;
597
    }
598
599
    /**
600
     * @return FormWidget
601
     */
602
    protected function formWidget()
603
    {
604
        return $this;
605
    }
606
}
607