Completed
Push — master ( 0949ca...32e3be )
by
unknown
07:59
created

FormTrait::selectedFormGroup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Charcoal\Ui\Form;
4
5
use Charcoal\Ui\ConditionalizableInterface;
6
use Exception;
7
use InvalidArgumentException;
8
9
// From 'charcoal-factory'
10
use Charcoal\Factory\FactoryInterface;
11
12
// From 'charcoal-ui'
13
use Charcoal\Ui\Form\FormInterface;
14
use Charcoal\Ui\FormGroup\FormGroupInterface;
15
use Charcoal\Ui\PrioritizableInterface;
16
17
/**
18
 * Provides an implementation of {@see FormInterface}.
19
 */
20
trait FormTrait
21
{
22
    /**
23
     * The URI of a program that processes the form information.
24
     *
25
     * @var string
26
     */
27
    private $action = '';
28
29
    /**
30
     * The HTTP method that the browser uses to submit the form.
31
     *
32
     * @var string
33
     */
34
    private $method = 'post';
35
36
    /**
37
     * The form's display mode for multilingual fields.
38
     *
39
     * @var string
40
     */
41
    private $l10nMode = 'loop_inputs';
42
43
    /**
44
     * The form's display mode for groups.
45
     *
46
     * @var string
47
     */
48
    protected $groupDisplayMode;
49
50
    /**
51
     * The form's field groups.
52
     *
53
     * @var FormGroupInterface[]
54
     */
55
    protected $groups = [];
56
57
    /**
58
     * The form's predefined data.
59
     *
60
     * @var array $formData
61
     */
62
    private $formData = [];
63
64
    /**
65
     * Store the form's metadata instance.
66
     *
67
     * @var MetadataInterface
68
     */
69
    private $metadata;
70
71
    /**
72
     * Store the form's group factory instance.
73
     *
74
     * @var FactoryInterface
75
     */
76
    protected $formGroupFactory;
77
78
    /**
79
     * Store the form's group callback.
80
     *
81
     * @var callable
82
     */
83
    private $groupCallback;
84
85
    /**
86
     * Store the tab ident received through REQUEST.
87
     *
88
     * @var string
89
     */
90
    private $tabIdent;
91
92
    /**
93
     * Store the current form group.
94
     *
95
     * @var string
96
     */
97
    private $selectedFormGroup;
98
99
    /**
100
     * Comparison function used by {@see uasort()}.
101
     *
102
     * @param  PrioritizableInterface $a Sortable entity A.
103
     * @param  PrioritizableInterface $b Sortable entity B.
104
     * @return integer Sorting value: -1 or 1.
105
     */
106
    abstract protected function sortItemsByPriority(
107
        PrioritizableInterface $a,
108
        PrioritizableInterface $b
109
    );
110
111
    /**
112
     * @param FactoryInterface $factory A factory, to create customized form gorup objects.
113
     * @return FormInterface Chainable
114
     */
115
    public function setFormGroupFactory(FactoryInterface $factory)
116
    {
117
        $this->formGroupFactory = $factory;
118
119
        return $this;
120
    }
121
122
    /**
123
     * @throws Exception If the form group factory object was not set / injected.
124
     * @return FormInterface Chainable
125
     */
126
    protected function formGroupFactory()
127
    {
128
        if ($this->formGroupFactory === null) {
129
            throw new Exception(
130
                'Form group factory was not set.'
131
            );
132
        }
133
134
        return $this->formGroupFactory;
135
    }
136
137
    /**
138
     * @param callable $cb The group callback.
139
     * @return FormInterface Chainable
140
     */
141
    public function setGroupCallback(callable $cb)
142
    {
143
        $this->groupCallback = $cb;
144
145
        return $this;
146
    }
147
148
    /**
149
     * @param string $action The "action" value, typically a URL.
150
     * @throws InvalidArgumentException If the action argument is not a string.
151
     * @return FormInterface Chainable
152
     */
153
    public function setAction($action)
154
    {
155
        if (!is_string($action)) {
156
            throw new InvalidArgumentException(
157
                'Action must be a string'
158
            );
159
        }
160
        $this->action = $action;
161
162
        return $this;
163
    }
164
165
    /**
166
     * @return string
167
     */
168
    public function action()
169
    {
170
        return $this->action;
171
    }
172
173
    /**
174
     * Set the method (forcing lowercase) to "post" or "get".
175
     *
176
     * @param string $method Either "post" or "get".
177
     * @throws InvalidArgumentException If the method is not post or get.
178
     * @return FormInterface Chainable
179
     */
180
    public function setMethod($method)
181
    {
182
        $method = strtolower($method);
183
        if (!in_array($method, ['post', 'get'])) {
184
            throw new InvalidArgumentException(
185
                'Method must be "post" or "get"'
186
            );
187
        }
188
        $this->method = $method;
189
190
        return $this;
191
    }
192
193
    /**
194
     * @return string Either "post" or "get".
195
     */
196
    public function method()
197
    {
198
        return $this->method;
199
    }
200
201
    /**
202
     * @param string $mode The l10n mode.
203
     * @return FormInterface Chainable
204
     */
205
    public function setL10nMode($mode)
206
    {
207
        $this->l10nMode = $mode;
208
209
        return $this;
210
    }
211
212
    /**
213
     * @return string
214
     */
215
    public function l10nMode()
216
    {
217
        return $this->l10nMode;
218
    }
219
220
    /**
221
     * Set the object's form groups.
222
     *
223
     * @param array $groups A collection of group structures.
224
     * @return FormInterface Chainable
225
     */
226
    public function setGroups(array $groups)
227
    {
228
        $this->groups = [];
229
230
        foreach ($groups as $groupIdent => $group) {
231
            $this->addGroup($groupIdent, $group);
232
        }
233
234
        return $this;
235
    }
236
237
    /**
238
     * @return mixed
239
     */
240
    public function tabIdent()
241
    {
242
        return $this->tabIdent;
243
    }
244
245
    /**
246
     * @param mixed $tabIdent Store the tab ident received through REQUEST.
247
     * @return self
248
     */
249
    public function setTabIdent($tabIdent)
250
    {
251
        $this->tabIdent = $tabIdent;
252
253
        return $this;
254
    }
255
256
    /**
257
     * @return string
258
     */
259
    public function selectedFormGroup()
260
    {
261
        return $this->selectedFormGroup;
262
    }
263
264
    /**
265
     * @param mixed $selectedFormGroup Store the current form group.
266
     * @return self
267
     */
268
    public function setSelectedFormGroup($selectedFormGroup)
269
    {
270
        $this->selectedFormGroup = $selectedFormGroup;
271
272
        return $this;
273
    }
274
275
    /**
276
     * Add a form group.
277
     *
278
     * @param  string                   $groupIdent The group identifier.
279
     * @param  array|FormGroupInterface $group      The group object or structure.
280
     * @throws InvalidArgumentException If the identifier is not a string or the group is invalid.
281
     * @return FormInterface Chainable
282
     */
283
    public function addGroup($groupIdent, $group)
284
    {
285
        if ($group === false || $group === null) {
286
            return $this;
287
        }
288
289
        $group = $this->parseFormGroup($groupIdent, $group);
290
291
        if (isset($group['ident'])) {
292
            $groupIdent = $group['ident'];
293
        }
294
295
        $this->groups[$groupIdent] = $group;
296
297
        return $this;
298
    }
299
300
    /**
301
     * Parse a form group.
302
     *
303
     * @param  string                   $groupIdent The group identifier.
304
     * @param  array|FormGroupInterface $group      The group object or structure.
305
     * @throws InvalidArgumentException If the identifier is not a string or the group is invalid.
306
     * @return FormGroupInterface
307
     */
308
    protected function parseFormGroup($groupIdent, $group)
309
    {
310
        if (!is_string($groupIdent)) {
311
            throw new InvalidArgumentException(
312
                'Group identifier must be a string'
313
            );
314
        }
315
316
        if ($group instanceof FormGroupInterface) {
317
            $group = $this->updateFormGroup($group, null, $groupIdent);
318
        } elseif (is_array($group)) {
319
            $data = $group;
320
321
            if (!isset($data['ident'])) {
322
                $data['ident'] = $groupIdent;
323
            }
324
325
            $group = $this->createFormGroup($data);
326
        } else {
327
            throw new InvalidArgumentException(sprintf(
328
                'Group must be an instance of %s or an array of form group options, received %s',
329
                'FormGroupInterface',
330
                (is_object($group) ? get_class($group) : gettype($group))
331
            ));
332
        }
333
334
        return $group;
335
    }
336
337
    /**
338
     * Create a new form group widget.
339
     *
340
     * @param  array|null $data Optional. The form group data to set.
341
     * @return FormGroupInterface
342
     */
343
    protected function createFormGroup(array $data = null)
344
    {
345
        if (isset($data['type'])) {
346
            $type = $data['type'];
347
        } else {
348
            $type = $this->defaultGroupType();
349
        }
350
351
        $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...
352
        $group->setForm($this);
353
354
        if ($data !== null) {
355
            $group->setData($data);
356
        }
357
358
        return $group;
359
    }
360
361
    /**
362
     * Update the given form group widget.
363
     *
364
     * @param  FormGroupInterface $group      The form group to update.
365
     * @param  array|null         $groupData  Optional. The new group data to apply.
366
     * @param  string|null        $groupIdent Optional. The new group identifier.
367
     * @return FormGroupInterface
368
     */
369
    protected function updateFormGroup(
370
        FormGroupInterface $group,
371
        array $groupData = null,
372
        $groupIdent = null
373
    ) {
374
        $group->setForm($this);
375
376
        if ($groupData !== null) {
377
            $group->setData($groupData);
378
        }
379
380
        if ($groupIdent !== null) {
381
            $group->setIdent($groupIdent);
382
        }
383
384
        return $group;
385
    }
386
387
    /**
388
     * Retrieve the default form group class name.
389
     *
390
     * @return string
391
     */
392
    public function defaultGroupType()
393
    {
394
        return 'charcoal/ui/form-group/generic';
395
    }
396
397
    /**
398
     * Retrieve the form groups.
399
     *
400
     * @param callable $groupCallback Optional callback applied to each form group.
401
     * @return FormGroupInterface[]|Generator
402
     */
403
    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...
404
    {
405
        $groups = $this->groups;
406
        uasort($groups, [ $this, 'sortItemsByPriority' ]);
407
408
        $groupCallback = (isset($groupCallback) ? $groupCallback : $this->groupCallback);
409
410
        $groups = $this->finalizeFormGroups($groups);
411
412
        $i = 1;
413
        foreach ($groups as $group) {
414
            if ($groupCallback) {
415
                $groupCallback($group);
416
            }
417
418
            $GLOBALS['widget_template'] = $group->template();
419
420
            if (!$this->selectedFormGroup() && $this->isTabbable()) {
421
                $group->setIsHidden(false);
422
                if ($i > 1) {
423
                    $group->setIsHidden(true);
424
                }
425
            }
426
            $i++;
427
428
            yield $group;
429
430
            $GLOBALS['widget_template'] = '';
431
        }
432
    }
433
434
    /**
435
     * @param array|FormGroupInterface[] $groups Form groups to finalize.
436
     * @return array|FormGroupInterface[]
437
     */
438
    protected function finalizeFormGroups($groups)
439
    {
440
        $out = [];
441
442
        foreach ($groups as $group) {
443
            if (!$group->active()) {
444
                continue;
445
            }
446
447
            if ($group instanceof ConditionalizableInterface) {
448
                if (!$group->resolvedCondition()) {
449
                    continue;
450
                }
451
            }
452
453
            // Test formGroup vs. ACL roles
454
            if ($group->isAuthorized() === false) {
455
                continue;
456
            }
457
458
            if (!$group->l10nMode()) {
459
                $group->setL10nMode($this->l10nMode());
460
            }
461
462
            if ($this->isTabbable() && $this->tabIdent()) {
463
                $group->setIsHidden(true);
464
465
                if ($group->ident() === $this->tabIdent()) {
466
                    $group->setIsHidden(false);
467
                    $this->setSelectedFormGroup($group->ident());
468
                }
469
            }
470
471
            $out[] = $group;
472
        }
473
474
        return $out;
475
    }
476
477
    /**
478
     * Determine if the form has any groups.
479
     *
480
     * @return boolean
481
     */
482
    public function hasGroups()
483
    {
484
        return ($this->numGroups() > 0);
485
    }
486
487
    /**
488
     * Determine if the form has a given group.
489
     *
490
     * @param string $groupIdent The group identifier to look up.
491
     * @throws InvalidArgumentException If the group identifier is invalid.
492
     * @return boolean
493
     */
494
    public function hasGroup($groupIdent)
495
    {
496
        if (!is_string($groupIdent)) {
497
            throw new InvalidArgumentException(
498
                'Group identifier must be a string'
499
            );
500
        }
501
502
        return isset($this->groups[$groupIdent]);
503
    }
504
505
    /**
506
     * Count the number of form groups.
507
     *
508
     * @return integer
509
     */
510
    public function numGroups()
511
    {
512
        return count($this->groups);
513
    }
514
515
    /**
516
     * Set the widget's content group display mode.
517
     *
518
     * Currently only supports "tab".
519
     *
520
     * @param string $mode Group display mode.
521
     * @throws InvalidArgumentException If the display mode is not a string.
522
     * @return ObjectFormWidget Chainable.
523
     */
524
    public function setGroupDisplayMode($mode)
525
    {
526
        if (!is_string($mode)) {
527
            throw new InvalidArgumentException(
528
                'Display mode must be a string'
529
            );
530
        }
531
532
        if ($mode === 'tabs') {
533
            $mode = 'tab';
534
        }
535
536
        $this->groupDisplayMode = $mode;
537
538
        return $this;
539
    }
540
541
    /**
542
     * Retrieve the widget's content group display mode.
543
     *
544
     * @return string Group display mode.
545
     */
546
    public function groupDisplayMode()
547
    {
548
        return $this->groupDisplayMode;
549
    }
550
551
    /**
552
     * Determine if content groups are to be displayed as tabbable panes.
553
     *
554
     * @return boolean
555
     */
556
    public function isTabbable()
557
    {
558
        return ($this->groupDisplayMode() === 'tab');
559
    }
560
561
    /**
562
     * @param array $formData The (pre-populated) form data, as [$key=>$val] array.
563
     * @return FormInterface Chainable
564
     */
565
    public function setFormData(array $formData)
566
    {
567
        $this->formData = $formData;
568
569
        return $this;
570
    }
571
572
    /**
573
     * @param string $key The form data key, or poperty identifier.
574
     * @param mixed  $val The form data value, for a given key.
575
     * @throws InvalidArgumentException If the key argument is not a string.
576
     * @return FormInterface Chainable
577
     */
578 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...
579
    {
580
        if (!is_string($key)) {
581
            throw new InvalidArgumentException(
582
                'Can not add form data: Data key must be a string'
583
            );
584
        }
585
        $this->formData[$key] = $val;
586
587
        return $this;
588
    }
589
590
    /**
591
     * @return array
592
     */
593
    public function formData()
594
    {
595
        return $this->formData;
596
    }
597
}
598