Meta::hasStatuses()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/meta/license
6
 * @link       https://www.flipboxfactory.com/software/meta/
7
 */
8
9
namespace flipbox\meta\elements;
10
11
use Craft;
12
use craft\base\Element;
13
use craft\base\ElementInterface;
14
use craft\base\FieldInterface;
15
use craft\elements\db\ElementQueryInterface;
16
use craft\fields\MissingField;
17
use craft\helpers\ArrayHelper;
18
use craft\validators\SiteIdValidator;
19
use flipbox\meta\elements\db\Meta as MetaQuery;
20
use flipbox\meta\fields\Meta as MetaField;
21
use flipbox\meta\helpers\Field as FieldHelper;
22
use flipbox\meta\Meta as MetaPlugin;
23
use flipbox\meta\records\Meta as MetaRecord;
24
use flipbox\spark\helpers\ElementHelper;
25
use yii\base\Exception;
26
27
/**
28
 * @author Flipbox Factory <[email protected]>
29
 * @since 1.0.0
30
 */
31
class Meta extends Element
32
{
33
    /**
34
     * @var int|null Field ID
35
     */
36
    public $fieldId;
37
38
    /**
39
     * @var int|null Owner ID
40
     */
41
    public $ownerId;
42
43
    /**
44
     * @var int|null Owner site ID
45
     */
46
    public $ownerSiteId;
47
48
    /**
49
     * @var int|null Sort order
50
     */
51
    public $sortOrder;
52
53
    /**
54
     * @var ElementInterface|false|null The owner element, or false if [[ownerId]] is invalid
55
     */
56
    private $owner;
57
58
    /**
59
     * @inheritdoc
60
     */
61
    public static function displayName(): string
62
    {
63
        return Craft::t('meta', 'Meta');
64
    }
65
66
    /**
67
     * @inheritdoc
68
     */
69
    public static function refHandle()
70
    {
71
        return 'meta';
72
    }
73
74
    /**
75
     * @inheritdoc
76
     */
77
    public static function hasStatuses(): bool
78
    {
79
        return true;
80
    }
81
82
    /**
83
     * @inheritdoc
84
     */
85
    public static function hasContent(): bool
86
    {
87
        return true;
88
    }
89
90
    /**
91
     * @inheritdoc
92
     */
93
    public static function isLocalized(): bool
94
    {
95
        return true;
96
    }
97
98
    /**
99
     * @inheritdoc
100
     * @return MetaQuery
101
     */
102
    public static function find(): ElementQueryInterface
103
    {
104
        return new MetaQuery(get_called_class());
105
    }
106
107
    /**
108
     * @inheritdoc
109
     */
110
    public static function eagerLoadingMap(array $sourceElements, string $handle)
111
    {
112
        $fieldId = $sourceElements[0]->fieldId;
113
114
        // Create field context (meta:{id})
115
        $fieldContext = FieldHelper::getContextById($fieldId);
116
117
        // Get all fields (by context)
118
        $fields = ArrayHelper::index(
119
            Craft::$app->getFields()->getAllFields($fieldContext),
120
            'handle'
121
        );
122
123
        // Does field exist?
124
        if (ArrayHelper::keyExists($handle, $fields)) {
125
            $contentService = Craft::$app->getContent();
126
127
            $originalFieldContext = $contentService->fieldContext;
128
            $contentService->fieldContext = $fieldContext;
129
130
            $map = parent::eagerLoadingMap($sourceElements, $handle);
131
132
            $contentService->fieldContext = $originalFieldContext;
133
134
            return $map;
135
        }
136
137
        return parent::eagerLoadingMap($sourceElements, $handle);
138
    }
139
140
    /**
141
     * @inheritdoc
142
     */
143
    public function rules()
144
    {
145
        return array_merge(
146
            parent::rules(),
147
            [
148
                [
149
                    [
150
                        'fieldId',
151
                        'ownerId',
152
                        'fieldId'
153
                    ],
154
                    'number',
155
                    'integerOnly' => true
156
                ],
157
                [
158
                    [
159
                        'ownerSiteId'
160
                    ],
161
                    SiteIdValidator::class
162
                ],
163
                [
164
                    [
165
                        'ownerId',
166
                        'ownerSiteId',
167
                        'fieldId',
168
                        'sortOrder'
169
                    ],
170
                    'safe',
171
                    'on' => [
172
                        ElementHelper::SCENARIO_DEFAULT
173
                    ]
174
                ]
175
            ]
176
        );
177
    }
178
179
    /**
180
     * @inheritdoc
181
     */
182
    public function getFieldLayout()
183
    {
184
        return $this->getField()->getFieldLayout();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface craft\base\FieldInterface as the method getFieldLayout() does only exist in the following implementations of said interface: flipbox\meta\fields\Meta.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
185
    }
186
187
    /**
188
     * @inheritdoc
189
     */
190
    public function getSupportedSites(): array
191
    {
192
        // If the field is translatable, than each individual block is tied to a single site, and thus aren't
193
        // translatable. Otherwise all elements belong to all sites, and their content is translatable.
194
195
        if ($this->ownerSiteId !== null) {
196
            return [$this->ownerSiteId];
197
        }
198
199
        $owner = $this->getOwner();
200
201
        if ($owner) {
202
            // Just send back an array of site IDs -- don't pass along enabledByDefault configs
203
            $siteIds = [];
204
205
            foreach (ElementHelper::supportedSitesForElement($owner) as $siteInfo) {
206
                $siteIds[] = $siteInfo['siteId'];
207
            }
208
209
            return $siteIds;
210
        }
211
212
        return [Craft::$app->getSites()->getPrimarySite()->id];
213
    }
214
215
    /**
216
     * Returns the owner.
217
     *
218
     * @return ElementInterface|null
219
     */
220
    public function getOwner()
221
    {
222
        if ($this->owner !== null) {
223
            return $this->owner !== false ? $this->owner : null;
224
        }
225
226
        if ($this->ownerId === null) {
227
            return null;
228
        }
229
230
        if (($this->owner = Craft::$app->getElements()->getElementById($this->ownerId, null, $this->siteId)) === null) {
231
            // Be forgiving of invalid ownerId's in this case, since the field
232
            // could be in the process of being saved to a new element/site
233
            $this->owner = false;
234
235
            return null;
236
        }
237
238
        return $this->owner;
239
    }
240
241
    /**
242
     * Sets the owner
243
     *
244
     * @param ElementInterface $owner
245
     */
246
    public function setOwner(ElementInterface $owner)
247
    {
248
        $this->owner = $owner;
249
    }
250
251
    /**
252
     * @inheritdoc
253
     */
254
    public function getContentTable(): string
255
    {
256
        return MetaPlugin::getInstance()->getField()->getContentTableName($this->getField());
257
    }
258
259
    /**
260
     * @inheritdoc
261
     */
262
    public function getFieldContext(): string
263
    {
264
        return FieldHelper::getContextById($this->fieldId);
265
    }
266
267
268
    /**
269
     * @inheritdoc
270
     */
271
    public static function getFieldsForElementsQuery(ElementQueryInterface $query)
272
    {
273
        if (isset($query->fieldId) and !empty($query->fieldId)) {
0 ignored issues
show
Bug introduced by
Accessing fieldId on the interface craft\elements\db\ElementQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
274
            // Get the field context
275
            $fieldContext = FieldHelper::getContextById($query->fieldId);
0 ignored issues
show
Bug introduced by
Accessing fieldId on the interface craft\elements\db\ElementQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
276
277
            // Get all fields (based on context);
278
            return Craft::$app->getFields()->getAllFields($fieldContext);
279
        }
280
281
        return [];
282
    }
283
284
    /**
285
     * @inheritdoc
286
     */
287
    public function getHasFreshContent(): bool
288
    {
289
        // Defer to the owner element
290
        $owner = $this->getOwner();
291
292
        return $owner ? $owner->getHasFreshContent() : false;
293
    }
294
295
    // Events
296
    // -------------------------------------------------------------------------
297
298
    /**
299
     * @inheritdoc
300
     * @throws Exception
301
     */
302
    public function afterSave(bool $isNew)
303
    {
304
        // Get the record
305
        if (!$isNew) {
306
            $record = MetaRecord::findOne($this->id);
307
308
            if (!$record) {
309
                throw new Exception('Invalid Meta Id: ' . $this->id);
310
            }
311
        } else {
312
            $record = new MetaRecord();
313
            $record->id = $this->id;
314
        }
315
316
        $record->fieldId = $this->fieldId;
317
        $record->ownerId = $this->ownerId;
318
        $record->ownerSiteId = $this->ownerSiteId;
319
        $record->sortOrder = $this->sortOrder;
320
        $record->save(false);
321
322
        parent::afterSave($isNew);
323
    }
324
325
    // Private Methods
326
    // =========================================================================
327
328
    /**
329
     * Returns the Meta field.
330
     *
331
     * @return FieldInterface|MetaField
332
     */
333
    private function getField()
334
    {
335
        /** @noinspection PhpIncompatibleReturnTypeInspection */
336
        if (!$this->fieldId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->fieldId of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
337
338
            /** @var MissingField $newField */
339
            $missingField = new MissingField();
340
341
            /** @var MetaField $fallbackField */
342
            $fallbackField = $missingField->createFallback(MetaField::class);
343
344
            return $fallbackField;
345
        }
346
347
        return Craft::$app->getFields()->getFieldById($this->fieldId);
348
    }
349
}
350