Test Setup Failed
Push — master ( 2e1da5...23a388 )
by Chauncey
49s queued 10s
created

src/Charcoal/Property/AbstractProperty.php (2 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
namespace Charcoal\Property;
4
5
use PDO;
6
use Exception;
7
use LogicException;
8
use RuntimeException;
9
use InvalidArgumentException;
10
11
// From PSR-3
12
use Psr\Log\LoggerAwareInterface;
13
use Psr\Log\LoggerAwareTrait;
14
use Psr\Log\NullLogger;
15
16
// From Pimple
17
use Pimple\Container;
18
19
// From 'charcoal-config'
20
use Charcoal\Config\AbstractEntity;
21
22
// From 'charcoal-core'
23
use Charcoal\Model\DescribableInterface;
24
use Charcoal\Model\DescribableTrait;
25
use Charcoal\Validator\ValidatableInterface;
26
use Charcoal\Validator\ValidatableTrait;
27
use Charcoal\Validator\ValidatorInterface;
28
29
// From 'charcoal-translator'
30
use Charcoal\Translator\Translation;
31
use Charcoal\Translator\TranslatorAwareTrait;
32
33
// From 'charcoal-property'
34
use Charcoal\Property\DescribablePropertyInterface;
35
use Charcoal\Property\DescribablePropertyTrait;
36
use Charcoal\Property\PropertyInterface;
37
use Charcoal\Property\PropertyValidator;
38
use Charcoal\Property\StorablePropertyInterface;
39
use Charcoal\Property\StorablePropertyTrait;
40
41
/**
42
 * An abstract class that implements the full `PropertyInterface`.
43
 */
44
abstract class AbstractProperty extends AbstractEntity implements
45
    PropertyInterface,
46
    DescribableInterface,
47
    DescribablePropertyInterface,
48
    LoggerAwareInterface,
49
    StorablePropertyInterface,
50
    ValidatableInterface
51
{
52
    use LoggerAwareTrait;
53
    use DescribableTrait;
54
    use DescribablePropertyTrait;
55
    use StorablePropertyTrait;
56
    use TranslatorAwareTrait;
57
    use ValidatableTrait;
58
59
    const DEFAULT_L10N = false;
60
    const DEFAULT_MULTIPLE = false;
61
    const DEFAULT_HIDDEN = false;
62
    const DEFAULT_UNIQUE = false;
63
    const DEFAULT_REQUIRED = false;
64
    const DEFAULT_ALLOW_NULL = true;
65
    const DEFAULT_STORABLE = true;
66
    const DEFAULT_VALIDATABLE = true;
67
    const DEFAULT_ACTIVE = true;
68
69
    /**
70
     * @var string
71
     */
72
    private $ident = '';
73
74
    /**
75
     * @var mixed
76
     */
77
    protected $val;
78
79
    /**
80
     * @var Translation|null
81
     */
82
    private $label;
83
84
    /**
85
     * @var boolean
86
     */
87
    private $l10n = self::DEFAULT_L10N;
88
89
    /**
90
     * @var boolean
91
     */
92
    private $multiple = self::DEFAULT_MULTIPLE;
93
94
    /**
95
     * Array of options for multiple properties
96
     * - `separator` (default=",") How the values will be separated in the storage (sql).
97
     * - `min` (default=null) The min number of values. If null, <0 or NaN, then this is not taken into consideration.
98
     * - `max` (default=null) The max number of values. If null, <0 or NaN, then there is not limit.
99
     * @var array|null
100
     */
101
    private $multipleOptions;
102
103
    /**
104
     * @var boolean
105
     */
106
    private $hidden = self::DEFAULT_HIDDEN;
107
108
    /**
109
     * If true, this property *must* have a value
110
     * @var boolean
111
     */
112
    private $required = self::DEFAULT_REQUIRED;
113
114
    /**
115
     * Unique properties should not share he same value across 2 objects
116
     * @var boolean
117
     */
118
    private $unique = self::DEFAULT_UNIQUE;
119
120
    /**
121
     * @var boolean $allowNull
122
     */
123
    private $allowNull = self::DEFAULT_ALLOW_NULL;
124
125
    /**
126
     * Only the storable properties should be saved in storage.
127
     * @var boolean
128
     */
129
    private $storable = self::DEFAULT_STORABLE;
130
131
    /**
132
     * Whether to validate the property.
133
     * @var boolean
134
     */
135
    private $validatable = self::DEFAULT_VALIDATABLE;
136
137
    /**
138
     * Inactive properties should be hidden everywhere / unused
139
     * @var boolean
140
     */
141
    private $active = self::DEFAULT_ACTIVE;
142
143
    /**
144
     * @var Translation|null
145
     */
146
    private $description;
147
148
    /**
149
     * @var Translation|null
150
     */
151
    private $notes;
152
153
    /**
154
     * @var array|null
155
     */
156
    protected $viewOptions;
157
158
    /**
159
     * @var string
160
     */
161
    protected $displayType;
162
163
    /**
164
     * @var PDO
165
     */
166
    protected $pdo;
167
168
    /**
169
     * Required dependencies:
170
     * - `logger` a PSR3-compliant logger.
171
     * - `pdo` a PDO database.
172
     * - `translator` a Charcoal Translator (based on Symfony's).
173
     *
174
     * @param array $data Optional. Class Dependencies.
175
     */
176
    public function __construct(array $data = null)
177
    {
178
        $this->setLogger($data['logger']);
179
        $this->setPdo($data['database']);
180
        $this->setTranslator($data['translator']);
181
182
        // Optional DescribableInterface dependencies
183
        if (isset($data['property_factory'])) {
184
            $this->setPropertyFactory($data['property_factory']);
185
        }
186
187
        if (isset($data['metadata_loader'])) {
188
            $this->setMetadataLoader($data['metadata_loader']);
189
        }
190
191
        // DI Container can optionally be set in property constructor.
192
        if (isset($data['container'])) {
193
            $this->setDependencies($data['container']);
194
        }
195
    }
196
197
    /**
198
     * @return string
199
     * @deprecated
200
     */
201
    public function __toString()
202
    {
203
        $val = $this->val();
204
        if (is_string($val)) {
205
            return $val;
206
        } else {
207
            if (is_object($val)) {
208
                return (string)$val;
209
            } else {
210
                return '';
211
            }
212
        }
213
    }
214
215
    /**
216
     * Get the "property type" string.
217
     *
218
     * ## Notes
219
     * - Type can not be set, so it must be explicitely provided by each implementing property classes.
220
     *
221
     * @return string
222
     */
223
    abstract public function type();
224
225
    /**
226
     * Set the property's identifier.
227
     *
228
     * @param  string $ident The property identifier.
229
     * @throws InvalidArgumentException  If the identifier is not a string.
230
     * @return self
231
     */
232
    public function setIdent($ident)
233
    {
234
        if (!is_string($ident)) {
235
            throw new InvalidArgumentException(
236
                'Ident needs to be string.'
237
            );
238
        }
239
        $this->ident = $ident;
240
241
        return $this;
242
    }
243
244
    /**
245
     * Retrieve the property's identifier.
246
     *
247
     * @return string
248
     */
249
    public function getIdent()
250
    {
251
        return $this->ident;
252
    }
253
254
    /**
255
     * Legacy support of ident() instead of getIdent().
256
     *
257
     * @return string
258
     */
259
    public function ident()
260
    {
261
        return $this->getIdent();
262
    }
263
264
    /**
265
     * Retrieve the property's localized identifier.
266
     *
267
     * @param  string|null $lang The language code to return the identifier with.
268
     * @throws LogicException If the property is not multilingual.
269
     * @throws RuntimeException If the property has no identifier.
270
     * @throws InvalidArgumentException If the language code is invalid.
271
     * @return string
272
     */
273
    public function l10nIdent($lang = null)
274
    {
275
        if ($this->ident === '') {
276
            throw new RuntimeException('Missing Property Identifier');
277
        }
278
279
        if (!$this['l10n']) {
280
            throw new LogicException(sprintf(
281
                'Property "%s" is not multilingual',
282
                $this->ident
283
            ));
284
        }
285
286 View Code Duplication
        if ($lang === null) {
287
            $lang = $this->translator()->getLocale();
288
        } elseif (!is_string($lang)) {
289
            throw new InvalidArgumentException(sprintf(
290
                'Language must be a string for Property "%s"',
291
                $this->ident
292
            ));
293
        }
294
295
        return sprintf('%1$s_%2$s', $this->ident, $lang);
296
    }
297
298
    /**
299
     * Set the property's value.
300
     *
301
     * @deprecated
302
     *
303
     * @param  mixed $val The property (raw) value.
304
     * @return self
305
     */
306
    final public function setVal($val)
307
    {
308
        $this->val = $this->parseVal($val);
309
310
        return $this;
311
    }
312
313
    /**
314
     * Clear the property's value.
315
     *
316
     * @deprecated
317
     *
318
     * @return self
319
     */
320
    final public function clearVal()
321
    {
322
        $this->val = null;
323
324
        return $this;
325
    }
326
327
    /**
328
     * Retrieve the property's value.
329
     *
330
     * @deprecated
331
     *
332
     * @return mixed
333
     */
334
    final public function val()
335
    {
336
        return $this->val;
337
    }
338
339
    /**
340
     * Parse the given value.
341
     *
342
     * > Note: the base method (defined here) returns the current value intact.
343
     * > Other properties can reimplement this method to parse their values,
344
     * > such as {@see \Charcoal\Property\ObjectProperty::parseVal()} who could parse objects into object IDs.
345
     *
346
     * @param  mixed $val The value to be parsed (normalized).
347
     * @throws InvalidArgumentException If the value does not match property settings.
348
     * @return mixed Returns the parsed value.
349
     */
350
    final public function parseVal($val)
351
    {
352
        if ($this['allowNull']) {
353
            if ($val === null || $val === '') {
354
                return null;
355
            }
356
        } elseif ($val === null) {
357
            throw new InvalidArgumentException(sprintf(
358
                'Property "%s" value can not be NULL (not allowed)',
359
                $this->ident()
360
            ));
361
        }
362
363
        if ($this['multiple']) {
364
            $val = $this->parseValAsMultiple($val);
365
366
            if (empty($val)) {
367
                if ($this['allowNull'] === false) {
368
                    throw new InvalidArgumentException(sprintf(
369
                        'Property "%s" value can not be NULL or empty (not allowed)',
370
                        $this->ident()
371
                    ));
372
                }
373
374
                return $val;
375
            }
376
377
            $val = array_map([ $this, 'parseOne' ], $val);
378
        } else {
379
            if ($this['l10n']) {
380
                $val = $this->parseValAsL10n($val);
381
382
                if ($val) {
383
                    $val->sanitize([ $this, 'parseOne' ]);
384
                }
385
            } else {
386
                $val = $this->parseOne($val);
387
            }
388
        }
389
390
        return $val;
391
    }
392
393
    /**
394
     * @param mixed $val A single value to parse.
395
     * @return mixed The parsed value.
396
     */
397
    public function parseOne($val)
398
    {
399
        return $val;
400
    }
401
402
    /**
403
     * @param   mixed $val     Optional. The value to to convert for input.
404
     * @param   array $options Optional input options.
405
     * @return  string
406
     */
407
    public function inputVal($val, array $options = [])
408
    {
409
        if ($val === null) {
410
            return '';
411
        }
412
413
        if (is_string($val)) {
414
            return $val;
415
        }
416
417
        /** Parse multilingual values */
418 View Code Duplication
        if ($this['l10n']) {
419
            $propertyValue = $this->l10nVal($val, $options);
420
            if ($propertyValue === null) {
421
                return '';
422
            }
423
        } elseif ($val instanceof Translation) {
424
            $propertyValue = (string)$val;
425
        } else {
426
            $propertyValue = $val;
427
        }
428
429
        /** Parse multiple values / ensure they are of array type. */
430
        if ($this['multiple']) {
431
            if (is_array($propertyValue)) {
432
                $propertyValue = implode($this->multipleSeparator(), $propertyValue);
433
            }
434
        }
435
436
        if (!is_scalar($propertyValue)) {
437
            $propertyValue = json_encode($propertyValue, JSON_PRETTY_PRINT);
438
        }
439
440
        return (string)$propertyValue;
441
    }
442
443
    /**
444
     * @param  mixed $val     The value to to convert for display.
445
     * @param  array $options Optional display options.
446
     * @return string
447
     */
448
    public function displayVal($val, array $options = [])
449
    {
450
        if ($val === null || $val === '') {
451
            return '';
452
        }
453
454
        /** Parse multilingual values */
455 View Code Duplication
        if ($this['l10n']) {
456
            $propertyValue = $this->l10nVal($val, $options);
457
            if ($propertyValue === null) {
458
                return '';
459
            }
460
        } elseif ($val instanceof Translation) {
461
            $propertyValue = (string)$val;
462
        } else {
463
            $propertyValue = $val;
464
        }
465
466
        /** Parse multiple values / ensure they are of array type. */
467
        if ($this['multiple']) {
468
            if (!is_array($propertyValue)) {
469
                $propertyValue = $this->parseValAsMultiple($propertyValue);
470
            }
471
        }
472
473
        if (is_array($propertyValue)) {
474
            $separator = $this->multipleSeparator();
475
            if ($separator === ',') {
476
                $separator = ', ';
477
            }
478
479
            $propertyValue = implode($separator, $propertyValue);
480
        }
481
482
        return (string)$propertyValue;
483
    }
484
485
    /**
486
     * @param mixed $label The property label.
487
     * @return self
488
     */
489
    public function setLabel($label)
490
    {
491
        $this->label = $this->translator()->translation($label);
492
493
        return $this;
494
    }
495
496
    /**
497
     * @return Translation
498
     */
499
    public function getLabel()
500
    {
501
        if ($this->label === null) {
502
            return ucwords(str_replace([ '.', '_' ], ' ', $this->ident()));
503
        }
504
505
        return $this->label;
506
    }
507
508
    /**
509
     * @param boolean $l10n The l10n, or "translatable" flag.
510
     * @return self
511
     */
512
    public function setL10n($l10n)
513
    {
514
        $this->l10n = !!$l10n;
515
516
        return $this;
517
    }
518
519
    /**
520
     * The l10n flag sets the property as being translatable, meaning the data is held for multple languages.
521
     *
522
     * @return boolean
523
     */
524
    public function getL10n()
525
    {
526
        return $this->l10n;
527
    }
528
529
    /**
530
     * @param  mixed $val A L10N variable.
531
     * @return Translation The translation value.
532
     */
533
    public function parseValAsL10n($val)
534
    {
535
        return $this->translator()->translation($val);
536
    }
537
538
    /**
539
     * @param boolean $hidden The hidden flag.
540
     * @return self
541
     */
542
    public function setHidden($hidden)
543
    {
544
        $this->hidden = !!$hidden;
545
546
        return $this;
547
    }
548
549
    /**
550
     * @return boolean
551
     */
552
    public function getHidden()
553
    {
554
        return $this->hidden;
555
    }
556
557
    /**
558
     * Set whether this property accepts multiple values or a single value.
559
     *
560
     * @param  boolean $multiple The multiple flag.
561
     * @return self
562
     */
563
    public function setMultiple($multiple)
564
    {
565
        if (!is_bool($multiple)) {
566
            if (is_array($multiple)) {
567
                $this->setMultipleOptions($multiple);
568
            } elseif (is_int($multiple)) {
569
                $this->setMultipleOptions([
570
                    'min' => $multiple,
571
                    'max' => $multiple
572
                ]);
573
            }
574
        }
575
576
        $this->multiple = !!$multiple;
577
578
        return $this;
579
    }
580
581
    /**
582
     * Determine if this property accepts multiple values or a single value.
583
     *
584
     * The multiple flag sets the property as being "repeatable", or allow to represent an array of multiple values.
585
     *
586
     * ## Notes
587
     * - The multiple flag can be forced to false (or true) in implementing property class.
588
     * - How a multiple behaves also depend on `multipleOptions`.
589
     *
590
     * @return boolean
591
     */
592
    public function getMultiple()
593
    {
594
        return $this->multiple;
595
    }
596
597
    /**
598
     * Set the multiple options / configuration, when property is `multiple`.
599
     *
600
     * ## Options structure
601
     * - `separator` (string) The separator charactor.
602
     * - `min` (integer) The minimum number of values. (0 = no limit).
603
     * - `max` (integer) The maximum number of values. (0 = no limit).
604
     *
605
     * @param array $multipleOptions The property multiple options.
606
     * @return self
607
     */
608
    public function setMultipleOptions(array $multipleOptions)
609
    {
610
        // The options are always merged with the defaults, to ensure minimum required array structure.
611
        $options = array_merge($this->defaultMultipleOptions(), $multipleOptions);
612
        $this->multipleOptions = $options;
613
614
        return $this;
615
    }
616
617
    /**
618
     * The options defining the property behavior when the multiple flag is set to true.
619
     *
620
     * @see    self::defaultMultipleOptions
621
     * @param  string|null $key Optional setting to retrieve from the options.
622
     * @return array|mixed|null
623
     */
624
    public function getMultipleOptions($key = null)
625
    {
626
        if ($this->multipleOptions === null) {
627
            $this->multipleOptions = $this->defaultMultipleOptions();
628
        }
629
630
        if (is_string($key)) {
631
            if (isset($this->multipleOptions[$key])) {
632
                return $this->multipleOptions[$key];
633
            } else {
634
                return null;
635
            }
636
        }
637
638
        return $this->multipleOptions;
639
    }
640
641
    /**
642
     * Output the property multiple options as json.
643
     *
644
     * @return string
645
     */
646
    public function multipleOptionsAsJson()
647
    {
648
        return json_encode($this->getMultipleOptions());
649
    }
650
651
    /**
652
     * Retrieve the default settings for a multi-value property.
653
     *
654
     * @return array
655
     */
656
    public function defaultMultipleOptions()
657
    {
658
        return [
659
            'separator' => ',',
660
            'min'       => 0,
661
            'max'       => 0
662
        ];
663
    }
664
665
    /**
666
     * Retrieve the value delimiter for a multi-value property.
667
     *
668
     * @return string
669
     */
670
    public function multipleSeparator()
671
    {
672
        return $this->getMultipleOptions('separator');
673
    }
674
675
    /**
676
     * @param  mixed $val A multi-value variable.
677
     * @return array The array of values.
678
     */
679
    public function parseValAsMultiple($val)
680
    {
681
        if (is_array($val)) {
682
            return $val;
683
        }
684
685
        if ($val === null || $val === '') {
686
            return [];
687
        }
688
689
        if (!is_string($val)) {
690
            return (array)$val;
691
        }
692
693
        return explode($this->multipleSeparator(), $val);
694
    }
695
696
    /**
697
     * @param boolean $allow The property allow null flag.
698
     * @return self
699
     */
700
    public function setAllowNull($allow)
701
    {
702
        $this->allowNull = !!$allow;
703
704
        return $this;
705
    }
706
707
    /**
708
     * The allow null flag sets the property as being able to be of a "null" value.
709
     *
710
     * ## Notes
711
     * - This flag typically modifies the storage database to also allow null values.
712
     *
713
     * @return boolean
714
     */
715
    public function getAllowNull()
716
    {
717
        return $this->allowNull;
718
    }
719
720
    /**
721
     * @param boolean $required The property required flag.
722
     * @return self
723
     */
724
    public function setRequired($required)
725
    {
726
        $this->required = !!$required;
727
728
        return $this;
729
    }
730
731
    /**
732
     * Required flag sets the property as being required, meaning not allowed to be null / empty.
733
     *
734
     * ## Notes
735
     * - The actual meaning of "required" might be different for implementing property class.
736
     *
737
     * @return boolean
738
     */
739
    public function getRequired()
740
    {
741
        return $this->required;
742
    }
743
744
    /**
745
     * @param boolean $unique The property unique flag.
746
     * @return self
747
     */
748
    public function setUnique($unique)
749
    {
750
        $this->unique = !!$unique;
751
752
        return $this;
753
    }
754
755
    /**
756
     * @return boolean
757
     */
758
    public function getUnique()
759
    {
760
        return $this->unique;
761
    }
762
763
    /**
764
     * @param boolean $active The property active flag. Inactive properties should have no effects.
765
     * @return self
766
     */
767
    public function setActive($active)
768
    {
769
        $this->active = !!$active;
770
771
        return $this;
772
    }
773
774
    /**
775
     * @return boolean
776
     */
777
    public function getActive()
778
    {
779
        return $this->active;
780
    }
781
782
    /**
783
     * Legacy support of active() instead of getActive().
784
     *
785
     * @return string
786
     */
787
    public function active()
788
    {
789
        return $this->getActive();
790
    }
791
792
    /**
793
     * @param  boolean $validatable The validatable flag.
794
     * @return self
795
     */
796
    public function setValidatable($validatable)
797
    {
798
        $this->validatable = !!$validatable;
799
800
        return $this;
801
    }
802
803
    /**
804
     * @return boolean
805
     */
806
    public function getValidatable()
807
    {
808
        return $this->validatable;
809
    }
810
811
    /**
812
     * @param boolean $storable The storable flag.
813
     * @return self
814
     */
815
    public function setStorable($storable)
816
    {
817
        $this->storable = !!$storable;
818
819
        return $this;
820
    }
821
822
    /**
823
     * @return boolean
824
     */
825
    public function getStorable()
826
    {
827
        return $this->storable;
828
    }
829
830
    /**
831
     * @param mixed $description The property description.
832
     * @return self
833
     */
834
    public function setDescription($description)
835
    {
836
        $this->description = $this->translator()->translation($description);
837
        return $this;
838
    }
839
840
    /**
841
     * @return Translation|null
842
     */
843
    public function getDescription()
844
    {
845
        return $this->description;
846
    }
847
848
    /**
849
     * @param mixed $notes The property notes.
850
     * @return self
851
     */
852
    public function setNotes($notes)
853
    {
854
        $this->notes = $this->translator()->translation($notes);
855
        return $this;
856
    }
857
858
    /**
859
     * @return Translation|null
860
     */
861
    public function getNotes()
862
    {
863
        return $this->notes;
864
    }
865
866
    /**
867
     * The property's default validation methods.
868
     *
869
     * - `required`
870
     * - `unique`
871
     * - `allowNull`
872
     *
873
     * ## Notes
874
     * - Those 3 base validation methods should always be merged, in implementing factory class.
875
     *
876
     * @return string[]
877
     */
878
    public function validationMethods()
879
    {
880
        return [
881
            'required',
882
            'unique',
883
            'allowNull',
884
        ];
885
    }
886
887
    /**
888
     * @return boolean
889
     */
890 View Code Duplication
    public function validateRequired()
891
    {
892
        $val = $this->val();
0 ignored issues
show
Deprecated Code introduced by
The method Charcoal\Property\AbstractProperty::val() has been deprecated.

This method has been deprecated.

Loading history...
893
        if ($this['required'] && empty($val) && !is_numeric($val)) {
894
            $this->validator()->error('Value is required.', 'required');
895
896
            return false;
897
        }
898
899
        return true;
900
    }
901
902
    /**
903
     * @return boolean
904
     */
905
    public function validateUnique()
906
    {
907
        if (!$this['unique']) {
908
            return true;
909
        }
910
911
        /** @todo Check in the model's storage if the value already exists. */
912
        return true;
913
    }
914
915
    /**
916
     * @return boolean
917
     */
918
    public function validateAllowNull()
919
    {
920
        $val = $this->val();
0 ignored issues
show
Deprecated Code introduced by
The method Charcoal\Property\AbstractProperty::val() has been deprecated.

This method has been deprecated.

Loading history...
921
        if (!$this['allowNull'] && $val === null) {
922
            $this->validator()->error('Value can not be null.', 'allowNull');
923
924
            return false;
925
        }
926
927
        return true;
928
    }
929
930
    /**
931
     * @param mixed $val The value, at time of saving.
932
     * @return mixed
933
     */
934
    public function save($val)
935
    {
936
        // By default, nothing to do
937
        return $this->parseVal($val);
938
    }
939
940
    /**
941
     * @param string $type The display type.
942
     * @return self
943
     */
944
    public function setDisplayType($type)
945
    {
946
        $this->displayType = $type;
947
948
        return $this;
949
    }
950
951
    /**
952
     * @return string
953
     */
954
    public function getDisplayType()
955
    {
956
        if (!$this->displayType) {
957
            $meta = $this->metadata();
958
959
            // This default would be defined in type-property.json (@see charcoal-property/metadata)
960
            if (isset($meta['admin']) && isset($meta['admin']['display_type'])) {
961
                $default = $meta['admin']['display_type'];
962
            } else {
963
                $default = 'charcoal/admin/property/display/text';
964
            }
965
            $this->setDisplayType($default);
966
        }
967
968
        return $this->displayType;
969
    }
970
971
    /**
972
     * View options.
973
     * @param string $ident The display ident (ex: charcoal/admin/property/display/text).
974
     * @return array Should ALWAYS be an array.
975
     */
976
    final public function viewOptions($ident = null)
977
    {
978
        // No options defined
979
        if (!$this->viewOptions) {
980
            return [];
981
        }
982
983
        // No ident defined
984
        if (!$ident) {
985
            return $this->viewOptions;
986
        }
987
988
        // Invalid ident
989
        if (!isset($this->viewOptions[$ident])) {
990
            return [];
991
        }
992
993
        // Success!
994
        return $this->viewOptions[$ident];
995
    }
996
997
    /**
998
     * Set view options for both display and input
999
     *
1000
     * @param array $viewOpts View options.
1001
     * @return self
1002
     */
1003
    final public function setViewOptions(array $viewOpts = [])
1004
    {
1005
        $this->viewOptions = $viewOpts;
1006
1007
        return $this;
1008
    }
1009
1010
    /**
1011
     * @param Container $container A Pimple DI container.
1012
     * @return void
1013
     */
1014
    protected function setDependencies(Container $container)
1015
    {
1016
        $this->setPropertyFactory($container['property/factory']);
1017
        $this->setMetadataLoader($container['metadata/loader']);
1018
    }
1019
1020
    /**
1021
     * Attempt to get the multilingual value in the requested language.
1022
     *
1023
     * @param  mixed $val  The multilingual value to lookup.
1024
     * @param  mixed $lang The language to return the value in.
1025
     * @return string|null
1026
     */
1027
    protected function l10nVal($val, $lang = null)
1028
    {
1029
        if (!is_string($lang)) {
1030
            if (is_array($lang) && isset($lang['lang'])) {
1031
                $lang = $lang['lang'];
1032
            } else {
1033
                $lang = $this->translator()->getLocale();
1034
            }
1035
        }
1036
1037
        if (isset($val[$lang])) {
1038
            return $val[$lang];
1039
        } else {
1040
            return null;
1041
        }
1042
    }
1043
1044
    /**
1045
     * Create a new metadata object.
1046
     *
1047
     * @param  array $data Optional metadata to merge on the object.
1048
     * @see DescribableTrait::createMetadata()
1049
     * @return PropertyMetadata
1050
     */
1051
    protected function createMetadata(array $data = null)
1052
    {
1053
        $class = $this->metadataClass();
1054
        return new $class($data);
1055
    }
1056
1057
    /**
1058
     * Retrieve the class name of the metadata object.
1059
     *
1060
     * @see DescribableTrait::metadataClass()
1061
     * @return string
1062
     */
1063
    protected function metadataClass()
1064
    {
1065
        return PropertyMetadata::class;
1066
    }
1067
1068
    /**
1069
     * Create a Validator object
1070
     *
1071
     * @see ValidatableTrait::createValidator()
1072
     * @return ValidatorInterface
1073
     */
1074
    protected function createValidator()
1075
    {
1076
        $validator = new PropertyValidator($this);
1077
1078
        return $validator;
1079
    }
1080
1081
    /**
1082
     * @param PDO $pdo The database connection (PDO) instance.
1083
     * @return void
1084
     */
1085
    private function setPdo(PDO $pdo)
1086
    {
1087
        $this->pdo = $pdo;
1088
    }
1089
}
1090