MetaItem::tagAttributes()   A
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 20
ccs 0
cts 14
cp 0
rs 9.2222
c 0
b 0
f 0
cc 6
nc 8
nop 0
crap 42
1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS 3.x
4
 *
5
 * A turnkey SEO implementation for Craft CMS that is comprehensive, powerful,
6
 * and flexible
7
 *
8
 * @link      https://nystudio107.com
9
 * @copyright Copyright (c) 2017 nystudio107
10
 */
11
12
namespace nystudio107\seomatic\base;
13
14
use Craft;
15
use Exception;
16
use nystudio107\seomatic\behaviors\MetaItemAttributeParserBehavior;
17
use nystudio107\seomatic\helpers\ArrayHelper;
18
use nystudio107\seomatic\helpers\Dependency;
19
use nystudio107\seomatic\models\MetaJsonLd;
20
use nystudio107\seomatic\Seomatic;
21
use yii\helpers\Inflector;
22
use function count;
23
use function get_class;
24
use function is_array;
25
use function is_string;
26
27
/**
28
 * @author    nystudio107
29
 * @package   Seomatic
30
 * @since     3.0.0
31
 */
32
abstract class MetaItem extends FluentModel implements MetaItemInterface
33
{
34
    // Traits
35
    // =========================================================================
36
37
    use MetaItemTrait;
38
39
    // Constants
40
    // =========================================================================
41
42
    const ARRAY_PROPERTIES = [
43
    ];
44
    const ITEM_TYPE = 'Generic';
45
46
    // Public Methods
47
    // =========================================================================
48
49
    /**
50
     * @inheritdoc
51
     */
52 1
    public function init()
53
    {
54 1
        parent::init();
55
        // Set any per-environment attributes
56 1
        if (!Seomatic::$previewingMetaContainers || Seomatic::$headlessRequest) {
57 1
            $attributes = [];
58 1
            $envVars = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $envVars is dead and can be removed.
Loading history...
59
            try {
60 1
                $envVars = ArrayHelper::getValue($this->environment, Seomatic::$environment);
61
            } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
62
            }
63 1
            if (is_array($envVars)) {
64
                foreach ($envVars as $key => $value) {
65
                    $attributes[$key] = $value;
66
                }
67
            }
68 1
            $this->setAttributes($attributes, false);
69
        }
70 1
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75
    public function rules()
76
    {
77
        $rules = parent::rules();
78
        $rules = array_merge($rules, [
79
            [['key'], 'required'],
80
            [['include'], 'boolean'],
81
            [['key'], 'string'],
82
            [['environment'], 'safe'],
83
            [['dependencies'], 'safe'],
84
            [['tagAttrs'], 'safe'],
85
        ]);
86
87
        return $rules;
88
    }
89
90
    /**
91
     * @inheritdoc
92
     */
93
    public function fields()
94
    {
95
        $fields = parent::fields();
96
        switch ($this->scenario) {
97
            case 'render':
98
                $fields = array_diff_key(
99
                    $fields,
100
                    array_flip([
101
                        'include',
102
                        'key',
103
                        'environment',
104
                        'dependencies',
105
                        'tagAttrs',
106
                    ])
107
                );
108
                break;
109
        }
110
111
        return $fields;
112
    }
113
114
    /**
115
     * @inheritdoc
116
     */
117 1
    public function prepForRender(&$data): bool
118
    {
119 1
        if ($this->include) {
120 1
            return Dependency::validateDependencies($this->dependencies);
121
        }
122
123
        return false;
124
    }
125
126
    /**
127
     * @inheritdoc
128
     */
129
    public function render(array $params = []): string
130
    {
131
        return '';
132
    }
133
134
    /**
135
     * @inheritdoc
136
     */
137
    public function renderAttributes(array $params = []): array
138
    {
139
        return [];
140
    }
141
142
    /**
143
     * Add debug logging for the MetaItem
144
     *
145
     * @param string $errorLabel
146
     * @param array $scenarios
147
     */
148
    public function debugMetaItem(
149
        $errorLabel = 'Error: ',
150
        array $scenarios = ['default' => 'error']
151
    ) {
152
        $isMetaJsonLdModel = false;
153
        if (is_subclass_of($this, MetaJsonLd::class)) {
154
            $isMetaJsonLdModel = true;
155
        }
156
        $modelScenarios = $this->scenarios();
157
        $scenarios = array_intersect_key($scenarios, $modelScenarios);
158
        foreach ($scenarios as $scenario => $logLevel) {
159
            $this->setScenario($scenario);
160
            if (!$this->validate()) {
161
                $extraInfo = '';
162
                // Add a URL to the schema.org type if this is a MetaJsonLD object
163
                if ($isMetaJsonLdModel) {
164
                    /** @var MetaJsonLd $this */
165
                    $extraInfo = ' for http://schema.org/' . $this->type;
166
                }
167
                $errorMsg =
168
                    Craft::t('seomatic', 'Scenario: "')
169
                    . $scenario
170
                    . '"'
171
                    . $extraInfo
172
                    . PHP_EOL
173
                    . print_r($this->render(), true);
0 ignored issues
show
Bug introduced by
Are you sure print_r($this->render(), true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

173
                    . /** @scrutinizer ignore-type */ print_r($this->render(), true);
Loading history...
174
                Craft::info($errorMsg, __METHOD__);
175
                foreach ($this->errors as $param => $errors) {
176
                    $errorMsg = Craft::t('seomatic', $errorLabel) . $param;
177
                    /** @var array $errors */
178
                    foreach ($errors as $error) {
179
                        $errorMsg .= ' -> ' . $error;
180
                        // Change the error level depending on the error message if this is a MetaJsonLD object
181
                        if ($isMetaJsonLdModel) {
182
                            if (strpos($error, 'recommended') !== false) {
183
                                $logLevel = 'warning';
184
                            }
185
                            if (strpos($error, 'required') !== false
186
                                || strpos($error, 'Must be') !== false
187
                            ) {
188
                                $logLevel = 'error';
189
                            }
190
                        }
191
                    }
192
                    Craft::info(strtoupper($logLevel) . ' - ' . $errorMsg, __METHOD__);
193
                    // Extra debugging info for MetaJsonLd objects
194
                    if ($isMetaJsonLdModel) {
195
                        /** @var MetaJsonLd $className */
196
                        $className = get_class($this);
197
                        if (!empty($className->schemaPropertyDescriptions[$param])) {
198
                            $errorMsg = Craft::t('seomatic', $errorLabel) . $param;
199
                            $errorMsg .= ' -> ' . $className->schemaPropertyDescriptions[$param];
200
                            Craft::info($errorMsg, __METHOD__);
201
                        }
202
                    }
203
                }
204
            }
205
        }
206
    }
207
208
    /**
209
     * Return an array of tag attributes, normalizing the keys
210
     *
211
     * @return array
212
     */
213
    public function tagAttributes(): array
214
    {
215
        $attrs = $this->tagAttrs;
216
        if (!is_array($attrs)) {
0 ignored issues
show
introduced by
The condition is_array($attrs) is always true.
Loading history...
217
            $attrs = [];
218
        }
219
        if (!empty($attrs) && ArrayHelper::isIndexed($attrs, true)) {
220
            $attrs = [];
221
            foreach ($this->tagAttrs as $attr) {
222
                $attrs[$attr['name']] = $attr['value'];
223
            }
224
        }
225
        $tagAttributes = array_merge($this->toArray(), $attrs);
226
        $tagAttributes = array_filter($tagAttributes);
227
        foreach ($tagAttributes as $key => $value) {
228
            ArrayHelper::rename($tagAttributes, $key, Inflector::slug(Inflector::titleize($key)));
229
        }
230
        ksort($tagAttributes);
231
232
        return $tagAttributes;
233
    }
234
235
    /**
236
     * Return an array of arrays that contain the meta tag attributes
237
     *
238
     * @return array
239
     */
240
    public function tagAttributesArray(): array
241
    {
242
        $result = [];
243
        $optionsCount = 1;
244
        $scenario = $this->scenario;
245
        $this->setScenario('render');
246
        $options = $this->tagAttributes();
247
        $this->setScenario($scenario);
248
249
        // See if any of the potentially array properties actually are
250
        foreach (static::ARRAY_PROPERTIES as $arrayProperty) {
251
            if (!empty($options[$arrayProperty]) && is_array($options[$arrayProperty])) {
252
                $optionsCount = count($options[$arrayProperty]) > $optionsCount
253
                    ? count($options[$arrayProperty]) : $optionsCount;
254
            }
255
        }
256
        // Return an array of resulting options
257
        while ($optionsCount--) {
258
            $resultOptions = $options;
259
            foreach ($resultOptions as $key => $value) {
260
                $resultOptions[$key] = (is_array($value) && isset($value[$optionsCount]))
261
                    ? $value[$optionsCount] : $value;
262
            }
263
            $result[] = $resultOptions;
264
        }
265
266
        return $result;
267
    }
268
269
    /**
270
     * Validate the passed in $attribute as either an array or a string
271
     *
272
     * @param mixed $attribute the attribute currently being validated
273
     * @param mixed $params the value of the "params" given in the rule
274
     */
275
    public function validateStringOrArray(
276
        $attribute,
277
        $params
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

277
        /** @scrutinizer ignore-unused */ $params

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
278
    ) {
279
        $validated = false;
280
        if (is_string($attribute)) {
281
            $validated = true;
282
        }
283
        if (is_array($attribute)) {
284
            $validated = true;
285
        }
286
        if (!$validated) {
287
            $this->addError($attribute, 'Must be either a string or an array');
288
        }
289
    }
290
291
    /**
292
     * @inheritdoc
293
     */
294
    protected function defineBehaviors(): array
295
    {
296
        return [
297
            'parser' => [
298
                'class' => MetaItemAttributeParserBehavior::class,
299
                'attributes' => [
300
                ],
301
            ],
302
        ];
303
    }
304
}
305