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

src/Charcoal/Property/AbstractProperty.php (3 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()
0 ignored issues
show
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...
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