Completed
Push — 2.0 ( 42d51e...20eff8 )
by Christopher
02:57
created

FieldableBehavior::beforeFind()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 6
nop 4
dl 0
loc 13
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * Licensed under The GPL-3.0 License
4
 * For full copyright and license information, please see the LICENSE.txt
5
 * Redistributions of files must retain the above copyright notice.
6
 *
7
 * @since    2.0.0
8
 * @author   Christopher Castro <[email protected]>
9
 * @link     http://www.quickappscms.org
10
 * @license  http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License
11
 */
12
namespace Field\Model\Behavior;
13
14
use Cake\Collection\CollectionInterface;
15
use Cake\Datasource\EntityInterface;
16
use Cake\Error\FatalErrorException;
17
use Cake\Event\Event;
18
use Cake\ORM\Behavior;
19
use Cake\ORM\Entity;
20
use Cake\ORM\Query;
21
use Cake\ORM\Table;
22
use Cake\ORM\TableRegistry;
23
use Cake\Validation\Validator;
24
use Eav\Model\Behavior\EavBehavior;
25
use Field\Collection\FieldCollection;
26
use Field\Model\Entity\Field;
27
use \ArrayObject;
28
29
/**
30
 * Fieldable Behavior.
31
 *
32
 * A more flexible EAV approach. Allows additional fields to be attached to Tables.
33
 * Any Table (Contents, Users, etc.) can use this behavior to make itself `fieldable`
34
 * and thus allow fields to be attached to it.
35
 *
36
 * The Field API defines two primary data structures, FieldInstance and FieldValue:
37
 *
38
 * - FieldInstance: is a Field attached to a single Table. (Schema equivalent: column)
39
 * - FieldValue: is the stored data for a particular [FieldInstance, Entity]
40
 *   tuple of your Table. (Schema equivalent: cell value)
41
 *
42
 * **This behavior allows you to add _virtual columns_ to your table schema.**
43
 *
44
 * @link https://github.com/quickapps/docs/blob/2.x/en/developers/field-api.rst
45
 */
46
class FieldableBehavior extends EavBehavior
47
{
48
49
    /**
50
     * Used for reduce BD queries and allow inter-method communication.
51
     * Example, it allows to pass some information from beforeDelete() to
52
     * afterDelete().
53
     *
54
     * @var array
55
     */
56
    protected $_cache = [];
57
58
    /**
59
     * Default configuration.
60
     *
61
     * These are merged with user-provided configuration when the behavior is used.
62
     * Available options are:
63
     *
64
     * - `bundle`: Bundle within this the table. Can be a string or a callable
65
     *   method that must return a string to use as bundle. Default null. If set to
66
     *   a callable function, it will receive the entity being saved as first
67
     *   argument, so you can calculate a bundle name for each particular entity.
68
     *
69
     * - `enabled`: True enables this behavior or false for disable. Default to
70
     *   true.
71
     *
72
     * - `cache`: Column-based cache. See EAV plugin's documentation.
73
     *
74
     * Bundles are usually set to dynamic values. For example, for the "contents"
75
     * table we have "content" entities, but we may have "article contents", "page
76
     * contents", etc. depending on the "type of content" they are; is said that
77
     * "article" and "page" **are bundles** of "contents" table.
78
     *
79
     * @var array
80
     */
81
    protected $_fieldableDefaultConfig = [
82
        'bundle' => null,
83
        'implementedMethods' => [
84
            'attachFields' => 'attachEntityFields',
85
            'fieldable' => 'fieldable',
86
        ],
87
    ];
88
89
    /**
90
     * Instance of EavAttributes table.
91
     *
92
     * @var \Eav\Model\Table\EavAttributesTable
93
     */
94
    public $Attributes = null;
95
96
    /**
97
     * Constructor.
98
     *
99
     * @param \Cake\ORM\Table $table The table this behavior is attached to
100
     * @param array $config Configuration array for this behavior
101
     */
102
    public function __construct(Table $table, array $config = [])
103
    {
104
        $this->_defaultConfig = array_merge($this->_defaultConfig, $this->_fieldableDefaultConfig);
105
        $this->Attributes = TableRegistry::get('Eav.EavAttributes');
106
        $this->Attributes->hasOne('Instance', [
107
            'className' => 'Field.FieldInstances',
108
            'foreignKey' => 'eav_attribute_id',
109
            'propertyName' => 'instance',
110
        ]);
111
        parent::__construct($table, $config);
112
    }
113
114
    /**
115
     * Returns a list of events this class is implementing. When the class is
116
     * registered in an event manager, each individual method will be associated
117
     * with the respective event.
118
     *
119
     * @return void
120
     */
121
    public function implementedEvents()
122
    {
123
        $events = [
124
            'Model.beforeFind' => ['callable' => 'beforeFind', 'priority' => 15],
125
            'Model.beforeSave' => ['callable' => 'beforeSave', 'priority' => 15],
126
            'Model.afterSave' => ['callable' => 'afterSave', 'priority' => 15],
127
            'Model.beforeDelete' => ['callable' => 'beforeDelete', 'priority' => 15],
128
            'Model.afterDelete' => ['callable' => 'afterDelete', 'priority' => 15],
129
        ];
130
131
        return $events;
132
    }
133
134
    /**
135
     * Modifies the query object in order to merge custom fields records into each
136
     * entity under the `_fields` property.
137
     *
138
     * You can enable or disable this behavior for a single `find()` or `get()`
139
     * operation by setting `fieldable` or `eav` to false in the options array for
140
     * find method. e.g.:
141
     *
142
     * ```php
143
     * $contents = $this->Contents->find('all', ['fieldable' => false]);
144
     * $content = $this->Contents->get($id, ['fieldable' => false]);
145
     * ```
146
     *
147
     * It also looks for custom fields in WHERE clause. This will search entities in
148
     * all bundles this table may have, if you need to restrict the search to an
149
     * specific bundle you must use the `bundle` key in find()'s options:
150
     *
151
     * ```php
152
     * $this->Contents
153
     *     ->find('all', ['bundle' => 'articles'])
154
     *     ->where(['article-title' => 'My first article!']);
155
     * ```
156
     *
157
     * The `bundle` option has no effects if no custom fields are given in the
158
     * WHERE clause.
159
     *
160
     * @param \Cake\Event\Event $event The beforeFind event that was triggered
161
     * @param \Cake\ORM\Query $query The original query to modify
162
     * @param \ArrayObject $options Additional options given as an array
163
     * @param bool $primary Whether this find is a primary query or not
164
     * @return void
165
     */
166
    public function beforeFind(Event $event, Query $query, ArrayObject $options, $primary)
167
    {
168
        $status = array_key_exists('fieldable', $options) ? $options['fieldable'] : $this->config('status');
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
169
        if ($status) {
170
            return;
171
        }
172
173
        if (array_key_exists('eav', $options)) {
174
            unset($options['eav']);
175
        }
176
177
        return parent::beforeFind($event, $query, $options, $primary);
178
    }
179
180
    /**
181
     * {@inheritDoc}
182
     */
183
    protected function _hydrateEntities(CollectionInterface $entities, array $args)
184
    {
185
        return $entities->map(function ($entity) use ($args) {
186
            if ($entity instanceof EntityInterface) {
187
                $entity = $this->_prepareCachedColumns($entity);
188
                $entity = $this->_attachEntityFields($entity, $args);
189
190
                if ($entity === null) {
191
                    return self::NULL_ENTITY;
192
                }
193
            }
194
195
            return $entity;
196
        })
197
        ->filter(function ($entity) {
198
            return $entity !== self::NULL_ENTITY;
199
        });
200
    }
201
202
    /**
203
     * Attaches entity's field under the `_fields` property, this method is invoked
204
     * by `beforeFind()` when iterating results sets.
205
     *
206
     * @param \Cake\Datasource\EntityInterface $entity The entity being altered
207
     * @param array $args Arguments given to the originating `beforeFind()`
208
     */
209
    protected function _attachEntityFields(EntityInterface $entity, array $args)
210
    {
211
        $entity = $this->attachEntityFields($entity);
212
        foreach ($entity->get('_fields') as $field) {
213
            $result = $field->beforeFind((array)$args['options'], $args['primary']);
214
            if ($result === null) {
215
                return null; // remove entity from collection
216
            }
217
        }
218
219
        return $entity;
220
    }
221
222
    /**
223
     * Before an entity is saved.
224
     *
225
     * Here is where we dispatch each custom field's `$_POST` information to its
226
     * corresponding Field Handler, so they can operate over their values.
227
     *
228
     * Fields Handler's `beforeSave()` method is automatically invoked for each
229
     * attached field for the entity being processed, your field handler should look
230
     * as follow:
231
     *
232
     * ```php
233
     * use Field\Handler;
234
     *
235
     * class TextField extends Handler
236
     * {
237
     *     public function beforeSave(Field $field, $post)
238
     *     {
239
     *          // alter $field, and do nifty things with $post
240
     *          // return FALSE; will halt the operation
241
     *     }
242
     * }
243
     * ```
244
     *
245
     * Field Handlers should **alter** `$field->value` and `$field->extra` according
246
     * to its needs using the provided **$post** argument.
247
     *
248
     * **NOTE:** Returning boolean FALSE will halt the whole Entity's save operation.
249
     *
250
     * @param \Cake\Event\Event $event The event that was triggered
251
     * @param \Cake\Datasource\EntityInterface $entity The entity being saved
252
     * @param \ArrayObject $options Additional options given as an array
253
     * @throws \Cake\Error\FatalErrorException When using this behavior in non-atomic mode
254
     * @return bool True if save operation should continue
255
     */
256
    public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
257
    {
258
        if (!$this->config('status')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
259
            return true;
260
        }
261
262
        if (!$options['atomic']) {
263
            throw new FatalErrorException(__d('field', 'Entities in fieldable tables can only be saved using transaction. Set [atomic = true]'));
264
        }
265
266
        if (!$this->_validation($entity)) {
267
            return false;
268
        }
269
270
        $this->_cache['createValues'] = [];
271
        foreach ($this->_attributesForEntity($entity) as $attr) {
272
            if (!$this->_toolbox->propertyExists($entity, $attr->get('name'))) {
273
                continue;
274
            }
275
276
            $field = $this->_prepareMockField($entity, $attr);
277
            $result = $field->beforeSave($this->_fetchPost($field));
278
279
            if ($result === false) {
280
                $this->attachEntityFields($entity);
281
282
                return false;
283
            }
284
285
            $data = [
286
                'eav_attribute_id' => $field->get('metadata')->get('attribute_id'),
287
                'entity_id' => $this->_toolbox->getEntityId($entity),
288
                "value_{$field->metadata['type']}" => $field->get('value'),
289
                'extra' => $field->get('extra'),
290
            ];
291
292
            if ($field->get('metadata')->get('value_id')) {
293
                $valueEntity = TableRegistry::get('Eav.EavValues')->get($field->get('metadata')->get('value_id'));
294
                $valueEntity = TableRegistry::get('Eav.EavValues')->patchEntity($valueEntity, $data, ['validate' => false]);
295
            } else {
296
                $valueEntity = TableRegistry::get('Eav.EavValues')->newEntity($data, ['validate' => false]);
297
            }
298
299
            if ($entity->isNew() || $valueEntity->isNew()) {
300
                $this->_cache['createValues'][] = $valueEntity;
301
            } elseif (!TableRegistry::get('Eav.EavValues')->save($valueEntity)) {
302
                $this->attachEntityFields($entity);
303
                $event->stopPropagation();
304
305
                return false;
306
            }
307
        }
308
309
        $this->attachEntityFields($entity);
310
311
        return true;
312
    }
313
314
    /**
315
     * After an entity is saved.
316
     *
317
     * @param \Cake\Event\Event $event The event that was triggered
318
     * @param \Cake\Datasource\EntityInterface $entity The entity that was saved
319
     * @param \ArrayObject $options Additional options given as an array
320
     * @return bool True always
321
     */
322
    public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options)
323
    {
324
        if (!$this->config('status')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
325
            return true;
326
        }
327
328
        // as we don't know entity's ID on beforeSave, we must delay values storage;
329
        // all this occurs inside a transaction so we are safe
330
        if (!empty($this->_cache['createValues'])) {
331
            foreach ($this->_cache['createValues'] as $valueEntity) {
332
                $valueEntity->set('entity_id', $this->_toolbox->getEntityId($entity));
333
                $valueEntity->unsetProperty('id');
334
                TableRegistry::get('Eav.EavValues')->save($valueEntity);
335
            }
336
            $this->_cache['createValues'] = [];
337
        }
338
339
        foreach ($this->_attributesForEntity($entity) as $attr) {
340
            $field = $this->_prepareMockField($entity, $attr);
341
            $field->afterSave();
342
        }
343
344
        if ($this->config('cacheMap')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
345
            $this->updateEavCache($entity);
346
        }
347
348
        return true;
349
    }
350
351
    /**
352
     * Deletes an entity from a fieldable table.
353
     *
354
     * @param \Cake\Event\Event $event The event that was triggered
355
     * @param \Cake\Datasource\EntityInterface $entity The entity being deleted
356
     * @param \ArrayObject $options Additional options given as an array
357
     * @return bool
358
     * @throws \Cake\Error\FatalErrorException When using this behavior in non-atomic mode
359
     */
360
    public function beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options)
361
    {
362
        if (!$this->config('status')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
363
            return true;
364
        }
365
366
        if (!$options['atomic']) {
367
            throw new FatalErrorException(__d('field', 'Entities in fieldable tables can only be deleted using transaction. Set [atomic = true]'));
368
        }
369
370
        foreach ($this->_attributesForEntity($entity) as $attr) {
371
            $field = $this->_prepareMockField($entity, $attr);
372
            $result = $field->beforeDelete();
373
374
            if ($result === false) {
375
                $event->stopPropagation();
376
377
                return false;
378
            }
379
380
            // holds in cache field mocks, so we can catch them on afterDelete
381
            $this->_cache['afterDelete'][] = $field;
382
        }
383
384
        return true;
385
    }
386
387
    /**
388
     * After an entity was removed from database.
389
     *
390
     * **NOTE:** This method automatically removes all field values from
391
     * `eav_values` database table for each entity.
392
     *
393
     * @param \Cake\Event\Event $event The event that was triggered
394
     * @param \Cake\Datasource\EntityInterface $entity The entity that was deleted
395
     * @param \ArrayObject $options Additional options given as an array
396
     * @throws \Cake\Error\FatalErrorException When using this behavior in non-atomic mode
397
     * @return void
398
     */
399
    public function afterDelete(Event $event, EntityInterface $entity, ArrayObject $options)
400
    {
401
        if (!$this->config('status')) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
402
            return;
403
        }
404
405
        if (!$options['atomic']) {
406
            throw new FatalErrorException(__d('field', 'Entities in fieldable tables can only be deleted using transactions. Set [atomic = true]'));
407
        }
408
409
        if (!empty($this->_cache['afterDelete'])) {
410
            foreach ((array)$this->_cache['afterDelete'] as $field) {
411
                $field->afterDelete();
412
            }
413
            $this->_cache['afterDelete'] = [];
414
        }
415
416
        parent::afterDelete($event, $entity, $options);
417
    }
418
419
    /**
420
     * Gets/sets fieldable behavior status.
421
     *
422
     * @param array|bool|null $status If set to a boolean value then turns on/off
423
     *  this behavior
424
     * @return bool|void
425
     */
426
    public function fieldable($status = null)
427
    {
428
        return $this->eav($status);
0 ignored issues
show
Bug introduced by
It seems like $status defined by parameter $status on line 426 can also be of type array; however, Eav\Model\Behavior\EavBehavior::eav() does only seem to accept boolean|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
429
    }
430
431
    /**
432
     * The method which actually fetches custom fields.
433
     *
434
     * Fetches all Entity's fields under the `_fields` property.
435
     *
436
     * @param \Cake\Datasource\EntityInterface $entity The entity where to fetch fields
437
     * @return \Cake\Datasource\EntityInterface
438
     */
439
    public function attachEntityFields(EntityInterface $entity)
440
    {
441
        $_fields = [];
442
        foreach ($this->_attributesForEntity($entity) as $attr) {
443
            $field = $this->_prepareMockField($entity, $attr);
444
            if ($entity->has($field->get('name'))) {
445
                $this->_fetchPost($field);
446
            }
447
448
            $field->fieldAttached();
449
            $_fields[] = $field;
450
        }
451
452
        $entity->set('_fields', new FieldCollection($_fields));
453
454
        return $entity;
455
    }
456
457
    /**
458
     * Triggers before/after validate events.
459
     *
460
     * @param \Cake\Datasource\EntityInterface $entity The entity being validated
461
     * @return bool True if save operation should continue, false otherwise
462
     */
463
    protected function _validation(EntityInterface $entity)
464
    {
465
        $validator = new Validator();
466
        $hasErrors = false;
467
468
        foreach ($this->_attributesForEntity($entity) as $attr) {
469
            $field = $this->_prepareMockField($entity, $attr);
470
            $result = $field->validate($validator);
471
472
            if ($result === false) {
473
                $this->attachEntityFields($entity);
474
475
                return false;
476
            }
477
478
            $errors = $validator->errors($entity->toArray(), $entity->isNew());
479
            $entity->errors($errors);
480
481
            if (!empty($errors)) {
482
                $hasErrors = true;
483
                if ($entity->has('_fields')) {
484
                    $entityErrors = $entity->errors();
485
                    foreach ($entity->get('_fields') as $field) {
486
                        $postData = $entity->get($field->name);
487
                        if (!empty($entityErrors[$field->name])) {
488
                            $field->set('value', $postData);
489
                            $field->metadata->set('errors', (array)$entityErrors[$field->name]);
490
                        }
491
                    }
492
                }
493
            }
494
        }
495
496
        return !$hasErrors;
497
    }
498
499
    /**
500
     * Alters the given $field and fetches incoming POST data, both "value" and
501
     * "extra" property will be automatically filled for the given $field entity.
502
     *
503
     * @param \Field\Model\Entity\Field $field The field entity for which
504
     *  fetch POST information
505
     * @return mixed Raw POST information
506
     */
507
    protected function _fetchPost(Field $field)
508
    {
509
        $post = $field
510
            ->get('metadata')
511
            ->get('entity')
512
            ->get($field->get('name'));
513
514
        // auto-magic
515
        if (is_array($post)) {
516
            $field->set('extra', $post);
517
            $field->set('value', null);
518
        } else {
519
            $field->set('extra', null);
520
            $field->set('value', $post);
521
        }
522
523
        return $post;
524
    }
525
526
    /**
527
     * Gets all attributes that should be attached to the given entity, this entity
528
     * will be used as context to calculate the proper bundle.
529
     *
530
     * @param \Cake\Datasource\EntityInterface $entity Entity context
531
     * @return array
532
     */
533
    protected function _attributesForEntity(EntityInterface $entity)
534
    {
535
        $bundle = $this->_resolveBundle($entity);
536
        $attrs = $this->_toolbox->attributes($bundle);
537
        $attrByIds = []; // attrs indexed by id
538
        $attrByNames = []; // attrs indexed by name
539
540
        foreach ($attrs as $name => $attr) {
541
            $attrByNames[$name] = $attr;
542
            $attrByIds[$attr->get('id')] = $attr;
543
            $attr->set(':value', null);
544
        }
545
546
        if (!empty($attrByIds)) {
547
            $instances = $this->Attributes->Instance
548
                ->find()
549
                ->where(['eav_attribute_id IN' => array_keys($attrByIds)])
550
                ->all();
551
            foreach ($instances as $instance) {
552
                if (!empty($attrByIds[$instance->get('eav_attribute_id')])) {
553
                    $attr = $attrByIds[$instance->get('eav_attribute_id')];
554
                    if (!$attr->has('instance')) {
555
                        $attr->set('instance', $instance);
556
                    }
557
                }
558
            }
559
        }
560
561
        $values = $this->_fetchValues($entity, array_keys($attrByNames));
562
        foreach ($values as $value) {
563
            if (!empty($attrByNames[$value->get('eav_attribute')->get('name')])) {
564
                $attrByNames[$value->get('eav_attribute')->get('name')]->set(':value', $value);
565
            }
566
        }
567
568
        return $this->_toolbox->attributes($bundle);
569
    }
570
571
    /**
572
     * Retrives stored values for all virtual properties by name. This gets all
573
     * values at once.
574
     *
575
     * This method is used to reduce the number of SQl queries, so we get all
576
     * values at once in a single Select instead of creating a select for every
577
     * field attached to the given entity.
578
     *
579
     * @param \Cake\Datasource\EntityInterface $entity The entuity for which
580
     *  get related values
581
     * @param array $attrNames List of attribute names for which get their
582
     *  values
583
     * @return \Cake\Datasource\ResultSetInterface
584
     */
585
    protected function _fetchValues(EntityInterface $entity, array $attrNames = [])
586
    {
587
        $bundle = $this->_resolveBundle($entity);
588
        $conditions = [
589
            'EavAttribute.table_alias' => $this->_table->table(),
0 ignored issues
show
Deprecated Code introduced by
The method Cake\ORM\Table::table() has been deprecated with message: 3.4.0 Use setTable()/getTable() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
590
            'EavValues.entity_id' => $entity->get((string)$this->_table->primaryKey()),
0 ignored issues
show
Deprecated Code introduced by
The method Cake\ORM\Table::primaryKey() has been deprecated with message: 3.4.0 Use setPrimaryKey()/getPrimaryKey() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
591
        ];
592
593
        if ($bundle) {
594
            $conditions['EavAttribute.bundle'] = $bundle;
595
        }
596
597
        if (!empty($attrNames)) {
598
            $conditions['EavAttribute.name IN'] = $attrNames;
599
        }
600
601
        $storedValues = TableRegistry::get('Eav.EavValues')
602
            ->find()
603
            ->contain(['EavAttribute'])
604
            ->where($conditions)
605
            ->all();
606
607
        return $storedValues;
608
    }
609
610
    /**
611
     * Creates a new Virtual "Field" to be attached to the given entity.
612
     *
613
     * This mock Field represents a new property (table column) of the entity.
614
     *
615
     * @param \Cake\Datasource\EntityInterface $entity The entity where the
616
     *  generated virtual field will be attached
617
     * @param \Cake\Datasource\EntityInterface $attribute The attribute where to get
618
     *  the information when creating the mock field.
619
     * @return \Field\Model\Entity\Field
620
     */
621
    protected function _prepareMockField(EntityInterface $entity, EntityInterface $attribute)
622
    {
623
        $type = $this->_toolbox->mapType($attribute->get('type'));
624
        if (!$attribute->has(':value')) {
625
            $bundle = $this->_resolveBundle($entity);
626
            $conditions = [
627
                'EavAttribute.table_alias' => $this->_table->table(),
0 ignored issues
show
Deprecated Code introduced by
The method Cake\ORM\Table::table() has been deprecated with message: 3.4.0 Use setTable()/getTable() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
628
                'EavAttribute.name' => $attribute->get('name'),
629
                'EavValues.entity_id' => $entity->get((string)$this->_table->primaryKey()),
0 ignored issues
show
Deprecated Code introduced by
The method Cake\ORM\Table::primaryKey() has been deprecated with message: 3.4.0 Use setPrimaryKey()/getPrimaryKey() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
630
            ];
631
632
            if ($bundle) {
633
                $conditions['EavAttribute.bundle'] = $bundle;
634
            }
635
636
            $storedValue = TableRegistry::get('Eav.EavValues')
637
                ->find()
638
                ->contain(['EavAttribute'])
639
                ->select(['id', "value_{$type}", 'extra'])
640
                ->where($conditions)
641
                ->limit(1)
642
                ->first();
643
        } else {
644
            $storedValue = $attribute->get(':value');
645
        }
646
647
        $mockField = new Field([
648
            'name' => $attribute->get('name'),
649
            'label' => $attribute->get('instance')->get('label'),
650
            'value' => null,
651
            'extra' => null,
652
            'metadata' => new Entity([
653
                'value_id' => null,
654
                'instance_id' => $attribute->get('instance')->get('id'),
655
                'attribute_id' => $attribute->get('id'),
656
                'entity_id' => $this->_toolbox->getEntityId($entity),
657
                'table_alias' => $attribute->get('table_alias'),
658
                'type' => $type,
659
                'bundle' => $attribute->get('bundle'),
660
                'handler' => $attribute->get('instance')->get('handler'),
661
                'required' => $attribute->get('instance')->required,
662
                'description' => $attribute->get('instance')->description,
663
                'settings' => $attribute->get('instance')->settings,
664
                'view_modes' => $attribute->get('instance')->view_modes,
665
                'entity' => $entity,
666
                'errors' => [],
667
            ]),
668
        ]);
669
670
        if ($storedValue) {
671
            $mockField->set('value', $this->_toolbox->marshal($storedValue->get("value_{$type}"), $type));
672
            $mockField->set('extra', $storedValue->get('extra'));
673
            $mockField->metadata->set('value_id', $storedValue->id);
674
        }
675
676
        $mockField->isNew($entity->isNew());
677
678
        return $mockField;
679
    }
680
681
    /**
682
     * Resolves `bundle` name using $entity as context.
683
     *
684
     * @param \Cake\Datasource\EntityInterface $entity Entity to use as context when
685
     *  resolving bundle
686
     * @return string Bundle name as string value, it may be an empty string if no
687
     *  bundle should be applied
688
     */
689
    protected function _resolveBundle(EntityInterface $entity)
690
    {
691
        $bundle = $this->config('bundle');
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
692
        if (is_callable($bundle)) {
693
            $callable = $this->config('bundle');
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Core\InstanceConfigTrait::config() has been deprecated with message: 3.4.0 use setConfig()/getConfig() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
694
            $bundle = $callable($entity);
695
        }
696
697
        return (string)$bundle;
698
    }
699
}
700