Completed
Branch BUG-10381-asset-loading (5e9d4d)
by
unknown
14:17
created

EE_Model_Field_Base::getSchemaDefault()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 16
nc 2
nop 0
dl 0
loc 23
rs 8.5906
c 0
b 0
f 0
1
<?php
2
use EventEspresso\core\entities\interfaces\HasSchemaInterface;
3
4
defined('EVENT_ESPRESSO_VERSION') || exit;
5
6
/**
7
 * EE_Model_Field_Base class
8
 * Base class for all EE_*_Field classes. These classes are for providing information and functions specific to each
9
 * field. They define the field's data type for insertion into the db (eg, if the value should be treated as an int,
10
 * float, or string), what values for the field are acceptable (eg, if setting EVT_ID to a float is acceptable), and
11
 * generally any functionality within EEM_Base or EE_Base_Class which depend on the field's type. (ie, you shouldn't
12
 * need any logic within your model or model object which are dependent on the field's type, ideally). For example,
13
 * EE_Serialized_Text_Field, specifies that any fields of this type should be serialized before insertion into the db
14
 * (prepare_for_insertion_into_db()), should be considered a string when inserting, updating, or using in a where
15
 * clause for any queries (get_wpdb_data_type()), should be unserialized when being retrieved from the db
16
 * (prepare_for_set_from_db()), and whatever else.
17
 *
18
 * @package               Event Espresso
19
 * @subpackage            /core/db_models/fields/EE_Model_Field_Base.php
20
 * @author                Michael Nelson
21
 */
22
abstract class EE_Model_Field_Base implements HasSchemaInterface
23
{
24
    /**
25
     * The alias for the table the column belongs to.
26
     * @var string
27
     */
28
    protected $_table_alias;
29
30
    /**
31
     * The actual db column name for the table
32
     * @var string
33
     */
34
    protected $_table_column;
35
36
37
    /**
38
     * The authoritative name for the table column (used by client code to reference the field).
39
     * @var string
40
     */
41
    protected $_name;
42
43
44
    /**
45
     * A description for the field.
46
     * @var string
47
     */
48
    protected $_nicename;
49
50
51
    /**
52
     * Whether the field is nullable or not
53
     * @var bool
54
     */
55
    protected $_nullable;
56
57
58
    /**
59
     * What the default value for the field should be.
60
     * @var mixed
61
     */
62
    protected $_default_value;
63
64
65
    /**
66
     * Other configuration for the field
67
     * @var mixed
68
     */
69
    protected $_other_config;
70
71
72
    /**
73
     * The name of the model this field is instantiated for.
74
     * @var string
75
     */
76
    protected $_model_name;
77
78
79
    /**
80
     * This should be a json-schema valid data type for the field.
81
     * @link http://json-schema.org/latest/json-schema-core.html#rfc.section.4.2
82
     * @var string
83
     */
84
    private $_schema_type = 'string';
85
86
87
    /**
88
     * If the schema has a defined format then it should be defined via this property.
89
     * @link http://json-schema.org/latest/json-schema-validation.html#rfc.section.7
90
     * @var string
91
     */
92
    private $_schema_format = '';
93
94
95
    /**
96
     * Indicates that the value of the field is managed exclusively by the server/model and not something
97
     * settable by client code.
98
     * @link http://json-schema.org/latest/json-schema-hypermedia.html#rfc.section.4.4
99
     * @var bool
100
     */
101
    private $_schema_readonly = false;
102
103
104
    /**
105
     * @param string $table_column
106
     * @param string $nicename
107
     * @param bool   $nullable
108
     * @param null   $default_value
109
     */
110
    public function __construct($table_column, $nicename, $nullable, $default_value = null)
111
    {
112
        $this->_table_column  = $table_column;
113
        $this->_nicename      = $nicename;
114
        $this->_nullable      = $nullable;
115
        $this->_default_value = $default_value;
116
    }
117
118
119
    /**
120
     * @param $table_alias
121
     * @param $name
122
     * @param $model_name
123
     */
124
    public function _construct_finalize($table_alias, $name, $model_name)
125
    {
126
        $this->_table_alias = $table_alias;
127
        $this->_name        = $name;
128
        $this->_model_name  = $model_name;
129
        /**
130
         * allow for changing the defaults
131
         */
132
        $this->_nicename      = apply_filters('FHEE__EE_Model_Field_Base___construct_finalize___nicename',
133
            $this->_nicename, $this);
134
        $this->_default_value = apply_filters('FHEE__EE_Model_Field_Base___construct_finalize___default_value',
135
            $this->_default_value, $this);
136
    }
137
138
    public function get_table_alias()
139
    {
140
        return $this->_table_alias;
141
    }
142
143
    public function get_table_column()
144
    {
145
        return $this->_table_column;
146
    }
147
148
    /**
149
     * Returns the name of the model this field is on. Eg 'Event' or 'Ticket_Datetime'
150
     *
151
     * @return string
152
     */
153
    public function get_model_name()
154
    {
155
        return $this->_model_name;
156
    }
157
158
    /**
159
     * @throws \EE_Error
160
     * @return string
161
     */
162 View Code Duplication
    public function get_name()
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...
163
    {
164
        if ($this->_name) {
165
            return $this->_name;
166
        } else {
167
            throw new EE_Error(sprintf(__("Model field '%s' has no name set. Did you make a model and forget to call the parent model constructor?",
168
                "event_espresso"), get_class($this)));
169
        }
170
    }
171
172
    public function get_nicename()
173
    {
174
        return $this->_nicename;
175
    }
176
177
    public function is_nullable()
178
    {
179
        return $this->_nullable;
180
    }
181
182
    /**
183
     * returns whether this field is an auto-increment field or not. If it is, then
184
     * on insertion it can be null. However, on updates it must be present.
185
     *
186
     * @return boolean
187
     */
188
    public function is_auto_increment()
189
    {
190
        return false;
191
    }
192
193
    /**
194
     * The default value in the model object's value domain. See lengthy comment about
195
     * value domains at the top of EEM_Base
196
     *
197
     * @return mixed
198
     */
199
    public function get_default_value()
200
    {
201
        return $this->_default_value;
202
    }
203
204
    /**
205
     * Returns the table alias joined to the table column, however this isn't the right
206
     * table alias if the aliased table is being joined to. In that case, you can use
207
     * EE_Model_Parser::extract_table_alias_model_relation_chain_prefix() to find the table's current alias
208
     * in the current query
209
     *
210
     * @return string
211
     */
212
    public function get_qualified_column()
213
    {
214
        return $this->get_table_alias() . "." . $this->get_table_column();
215
    }
216
217
    /**
218
     * When get() is called on a model object (eg EE_Event), before returning its value,
219
     * call this function on it, allowing us to customize the returned value based on
220
     * the field's type. Eg, we may want ot serialize it, strip tags, etc. By default,
221
     * we simply return it.
222
     *
223
     * @param mixed $value_of_field_on_model_object
224
     * @return mixed
225
     */
226
    public function prepare_for_get($value_of_field_on_model_object)
227
    {
228
        return $value_of_field_on_model_object;
229
    }
230
231
    /**
232
     * When inserting or updating a field on a model object, run this function on each
233
     * value to prepare it for insertion into the db. We may want to add slashes, serialize it, etc.
234
     * By default, we do nothing.
235
     *
236
     * @param mixed $value_of_field_on_model_object
237
     * @return mixed
238
     */
239
    public function prepare_for_use_in_db($value_of_field_on_model_object)
240
    {
241
        return $value_of_field_on_model_object;
242
    }
243
244
    /**
245
     * When creating a brand-new model object, or setting a particular value for one of its fields, this function
246
     * is called before setting it on the model object. We may want to strip slashes, unserialize the value, etc.
247
     * By default, we do nothing.
248
     *
249
     * @param mixed $value_inputted_for_field_on_model_object
250
     * @return mixed
251
     */
252
    public function prepare_for_set($value_inputted_for_field_on_model_object)
253
    {
254
        return $value_inputted_for_field_on_model_object;
255
    }
256
257
258
    /**
259
     * When instantiating a model object from DB results, this function is called before setting each field.
260
     * We may want to serialize the value, etc. By default, we return the value using prepare_for_set() method as that
261
     * is the one child classes will most often define.
262
     *
263
     * @param mixed $value_found_in_db_for_model_object
264
     * @return mixed
265
     */
266
    public function prepare_for_set_from_db($value_found_in_db_for_model_object)
267
    {
268
        return $this->prepare_for_set($value_found_in_db_for_model_object);
269
    }
270
271
    /**
272
     * When echoing a field's value on a model object, this function is run to prepare the value for presentation in a
273
     * webpage. For example, we may want to output floats with 2 decimal places by default, dates as "Monday Jan 12,
274
     * 2013, at 3:23pm" instead of
275
     * "8765678632", or any other modifications to how the value should be displayed, but not modified itself.
276
     *
277
     * @param mixed $value_on_field_to_be_outputted
278
     * @return mixed
279
     */
280
    public function prepare_for_pretty_echoing($value_on_field_to_be_outputted)
281
    {
282
        return $value_on_field_to_be_outputted;
283
    }
284
285
286
    /**
287
     * Returns whatever is set as the nicename for the object.
288
     * @return string
289
     */
290
    public function getSchemaDescription()
291
    {
292
        return $this->get_nicename();
293
    }
294
295
296
    /**
297
     * Returns whatever is set as the $_schema_type property for the object.
298
     * Note: this will automatically add 'null' to the schema if the object is_nullable()
299
     * @return string|array
300
     */
301
    public function getSchemaType()
302
    {
303
        if ($this->is_nullable()) {
304
            $this->_schema_type = (array) $this->_schema_type;
0 ignored issues
show
Documentation Bug introduced by
It seems like (array) $this->_schema_type of type array is incompatible with the declared type string of property $_schema_type.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
305
            if (! in_array('null', $this->_schema_type)) {
306
                $this->_schema_type[] = 'null';
307
            };
308
        }
309
        return $this->_schema_type;
310
    }
311
312
313
    /**
314
     * Sets the _schema_type property.  Child classes should call this in their constructors to override the default state
315
     * for this property.
316
     * @param string|array $type
317
     * @throws InvalidArgumentException
318
     */
319
    protected function setSchemaType($type)
320
    {
321
        $this->validateSchemaType($type);
322
        $this->_schema_type = $type;
0 ignored issues
show
Documentation Bug introduced by
It seems like $type can also be of type array. However, the property $_schema_type is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
323
    }
324
325
326
    /**
327
     * This is usually present when the $_schema_type property is 'object'.  Any child classes will need to override
328
     * this method and return the properties for the schema.
329
     *
330
     * The reason this is not a property on the class is because there may be filters set on the values for the property
331
     * that won't be exposed on construct.  For example enum type schemas may have the enum values filtered.
332
     *
333
     * @return array
334
     */
335
    public function getSchemaProperties()
336
    {
337
        return array();
338
    }
339
340
341
342
    /**
343
     * By default this returns the scalar default value that was sent in on the class prepped according to the class type
344
     * as the default.  However, when there are schema properties, then the default property is setup to mirror the
345
     * property keys and correctly prepare the default according to that expected property value.
346
     * The getSchema method validates whether the schema for default is setup correctly or not according to the schema type
347
     *
348
     * @return mixed
349
     */
350
    public function getSchemaDefault()
351
    {
352
        $default_value = $this->prepare_for_use_in_db($this->prepare_for_set($this->get_default_value()));
353
        $schema_properties = $this->getSchemaProperties();
354
355
        //if this schema has properties than shape the default value to match the properties shape.
356
        if ($schema_properties) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $schema_properties of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
357
            $value_to_return = array();
358
            foreach ($schema_properties as $property_key => $property_schema) {
359
                switch ($property_key) {
360
                    case 'pretty':
361
                    case 'rendered':
362
                        $value_to_return[$property_key] = $this->prepare_for_pretty_echoing($this->prepare_for_set($default_value));
363
                        break;
364
                    default:
365
                        $value_to_return[$property_key] = $default_value;
366
                        break;
367
                }
368
            }
369
            $default_value = $value_to_return;
370
        }
371
        return $default_value;
372
    }
373
374
375
376
377
    /**
378
     * If a child class has enum values, they should override this method and provide a simple array
379
     * of the enum values.
380
381
     * The reason this is not a property on the class is because there may be filterable enum values that
382
     * are set on the instantiated object that could be filtered after construct.
383
     *
384
     * @return array
385
     */
386
    public function getSchemaEnum()
387
    {
388
        return array();
389
    }
390
391
392
    /**
393
     * This returns the value of the $_schema_format property on the object.
394
     * @return string
395
     */
396
    public function getSchemaFormat()
397
    {
398
        return $this->_schema_format;
399
    }
400
401
402
    /**
403
     * Sets the schema format property.
404
     * @throws InvalidArgumentException
405
     * @param string $format
406
     */
407
    protected function setSchemaFormat($format)
408
    {
409
        $this->validateSchemaFormat($format);
410
        $this->_schema_format = $format;
411
    }
412
413
414
    /**
415
     * This returns the value of the $_schema_readonly property on the object.
416
     * @return bool
417
     */
418
    public function getSchemaReadonly()
419
    {
420
        return $this->_schema_readonly;
421
    }
422
423
424
    /**
425
     * This sets the value for the $_schema_readonly property.
426
     * @param bool $readonly  (only explicit boolean values are accepted)
427
     */
428
    protected function setSchemaReadOnly($readonly)
429
    {
430
        if (! is_bool($readonly)) {
431
            throw new InvalidArgumentException(
432
                sprintf(
433
                    esc_html__('The incoming argument (%s) must be a boolean.', 'event_espresso'),
434
                    print_r($readonly, true)
435
                )
436
            );
437
        }
438
439
        $this->_schema_readonly = $readonly;
440
    }
441
442
443
444
445
    /**
446
     * Return `%d`, `%s` or `%f` to indicate the data type for the field.
447
     * @uses _get_wpdb_data_type()
448
     *
449
     * @return string
450
     */
451
    public function get_wpdb_data_type()
452
    {
453
        return $this->_get_wpdb_data_type();
454
    }
455
456
457
    /**
458
     * Return `%d`, `%s` or `%f` to indicate the data type for the field that should be indicated in wpdb queries.
459
     * @param string $type  Included if a specific type is requested.
460
     * @uses get_schema_type()
461
     * @return string
462
     */
463
    protected function _get_wpdb_data_type($type='')
464
    {
465
        $type = empty($type) ? $this->getSchemaType() : $type;
466
467
        //if type is an array, then different parsing is required.
468
        if (is_array($type)) {
469
            return $this->_get_wpdb_data_type_for_type_array($type);
470
        }
471
472
        $wpdb_type = '%s';
473
        switch ($type) {
474
            case 'number':
475
                $wpdb_type = '%f';
476
                break;
477
            case 'integer':
478
            case 'boolean':
479
                $wpdb_type = '%d';
480
                break;
481
            case 'object':
482
                $properties = $this->getSchemaProperties();
483
                if (isset($properties['raw'], $properties['raw']['type'])) {
484
                    $wpdb_type = $this->_get_wpdb_data_type($properties['raw']['type']);
485
                }
486
                break; //leave at default
487
        }
488
        return $wpdb_type;
489
    }
490
491
492
493
    protected function _get_wpdb_data_type_for_type_array($type)
494
    {
495
        $type = (array) $type;
496
        //first let's flip because then we can do a faster key check
497
        $type = array_flip($type);
498
499
        //check for things that mean '%s'
500
        if (isset($type['string'],$type['object'],$type['array'])) {
501
            return '%s';
502
        }
503
504
        //if makes it past the above condition and there's float in the array
505
        //then the type is %f
506
        if (isset($type['number'])) {
507
            return '%f';
508
        }
509
510
        //if it makes it above the above conditions and there is an integer in the array
511
        //then the type is %d
512
        if (isset($type['integer'])) {
513
            return '%d';
514
        }
515
516
        //anything else is a string
517
        return '%s';
518
    }
519
520
521
    /**
522
     * This returns elements used to represent this field in the json schema.
523
     *
524
     * @link http://json-schema.org/
525
     * @return array
526
     */
527
    public function getSchema()
528
    {
529
        $schema = array(
530
            'description' => $this->getSchemaDescription(),
531
            'type' => $this->getSchemaType(),
532
            'readonly' => $this->getSchemaReadonly(),
533
            'default' => $this->getSchemaDefault()
534
        );
535
536
        //optional properties of the schema
537
        $enum = $this->getSchemaEnum();
538
        $properties = $this->getSchemaProperties();
539
        $format = $this->getSchemaFormat();
540
        if ($enum) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $enum of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
541
            $schema['enum'] = $enum;
542
        }
543
544
        if ($properties) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $properties of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
545
            $schema['properties'] = $properties;
546
        }
547
548
        if ($format) {
549
            $schema['format'] = $format;
550
        }
551
        return $schema;
552
    }
553
554
    /**
555
     * Some fields are in the database-only, (ie, used in queries etc), but shouldn't necessarily be part
556
     * of the model objects (ie, client code shouldn't care to ever see their value... if client code does
557
     * want to see their value, then they shouldn't be db-only fields!)
558
     * Eg, when doing events as custom post types, querying the post_type is essential, but
559
     * post_type is irrelevant for EE_Event objects (because they will ALL be of post_type 'esp_event').
560
     * By default, all fields aren't db-only.
561
     *
562
     * @return boolean
563
     */
564
    public function is_db_only_field()
565
    {
566
        return false;
567
    }
568
569
570
    /**
571
     * Validates the incoming string|array to ensure its an allowable type.
572
     * @throws InvalidArgumentException
573
     * @param string|array $type
574
     */
575
    private function validateSchemaType($type)
576
    {
577 View Code Duplication
        if (! (is_string($type) || is_array($type))) {
578
            throw new InvalidArgumentException(
579
                sprintf(
580
                    esc_html__('The incoming argument (%s) must be a string or an array.', 'event_espresso'),
581
                    print_r($type, true)
582
                )
583
            );
584
        }
585
586
        //validate allowable types.
587
        //@link http://json-schema.org/latest/json-schema-core.html#rfc.section.4.2
588
        $allowable_types = array_flip(
589
            array(
590
                'string',
591
                'number',
592
                'null',
593
                'object',
594
                'array',
595
                'boolean',
596
                'integer'
597
            )
598
        );
599
600
        if (is_array($type)) {
601
            foreach ($type as $item_in_type) {
602
                $this->validateSchemaType($item_in_type);
603
            }
604
            return;
605
        }
606
607 View Code Duplication
        if (! isset($allowable_types[$type])) {
608
            throw new InvalidArgumentException(
609
                sprintf(
610
                    esc_html__('The incoming argument (%1$s) must be one of the allowable types: %2$s', 'event_espresso'),
611
                    $type,
612
                    implode(',', array_flip($allowable_types))
613
                )
614
            );
615
        }
616
    }
617
618
619
    /**
620
     * Validates that the incoming format is an allowable string to use for the _schema_format property
621
     * @throws InvalidArgumentException
622
     * @param $format
623
     */
624
    private function validateSchemaFormat($format)
625
    {
626 View Code Duplication
        if (! is_string($format)) {
627
            throw new InvalidArgumentException(
628
                sprintf(
629
                    esc_html__('The incoming argument (%s) must be a string.', 'event_espresso'),
630
                    print_r($format, true)
631
                )
632
            );
633
        }
634
635
        //validate allowable format values
636
        //@link http://json-schema.org/latest/json-schema-validation.html#rfc.section.7
637
        $allowable_formats = array_flip(
638
            array(
639
                'date-time',
640
                'email',
641
                'hostname',
642
                'ipv4',
643
                'ipv6',
644
                'uri',
645
                'uriref'
646
            )
647
        );
648
649 View Code Duplication
        if (! isset($allowable_formats[$format])) {
650
            throw new InvalidArgumentException(
651
                sprintf(
652
                    esc_html__('The incoming argument (%1$s) must be one of the allowable formats: %2$s', 'event_espresso'),
653
                    $format,
654
                    implode(',', array_flip($allowable_formats))
655
                )
656
            );
657
        }
658
    }
659
}