MetaItem::renderAttributes()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS
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
    // Constants
35
    // =========================================================================
36
37
    public const ITEM_TYPE = 'Generic';
38
39
    // Traits
40
    // =========================================================================
41
42
    use MetaItemTrait;
43
44
    // Constants
45
    // =========================================================================
46
47
    public const ARRAY_PROPERTIES = [
48
    ];
49
50
    // Public Methods
51 1
    // =========================================================================
52
53 1
    /**
54
     * @inheritdoc
55 1
     */
56 1
    public function init(): void
57 1
    {
58
        parent::init();
59 1
        // Set any per-environment attributes
60
        if (!Seomatic::$previewingMetaContainers || Seomatic::$headlessRequest) {
61
            $attributes = [];
62 1
            $envVars = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $envVars is dead and can be removed.
Loading history...
63
            try {
64
                $envVars = ArrayHelper::getValue($this->environment, Seomatic::$environment);
65
            } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
66
            }
67 1
            if (is_array($envVars)) {
68
                foreach ($envVars as $key => $value) {
69
                    $attributes[$key] = $value;
70
                }
71
            }
72
            $this->setAttributes($attributes, false);
73
        }
74
    }
75
76
    /**
77
     * @inheritdoc
78
     */
79
    public function rules(): array
80
    {
81
        $rules = parent::rules();
82
        $rules = array_merge($rules, [
83
            [['key'], 'required'],
84
            [['include'], 'boolean'],
85
            [['key'], 'string'],
86
            [['environment'], 'safe'],
87
            [['dependencies'], 'safe'],
88
            [['tagAttrs'], 'safe'],
89
        ]);
90
91
        return $rules;
92
    }
93
94
    /**
95
     * @inheritdoc
96
     */
97
    public function fields(): array
98
    {
99
        $fields = parent::fields();
100
        switch ($this->scenario) {
101
            case 'render':
102
                $fields = array_diff_key(
103
                    $fields,
104
                    array_flip([
105
                        'include',
106
                        'key',
107
                        'environment',
108
                        'dependencies',
109
                        'tagAttrs',
110
                    ])
111
                );
112
                break;
113
        }
114
115
        return $fields;
116 1
    }
117
118 1
    /**
119 1
     * @inheritdoc
120
     */
121
    public function prepForRender(&$data): bool
122
    {
123
        if ($this->include) {
124
            return Dependency::validateDependencies($this->dependencies);
125
        }
126
127
        return false;
128
    }
129
130
    /**
131
     * @inheritdoc
132
     */
133
    public function render(array $params = []): string
134
    {
135
        return '';
136
    }
137
138
    /**
139
     * @inheritdoc
140
     */
141
    public function renderAttributes(array $params = []): array
142
    {
143
        return [];
144
    }
145
146
    /**
147
     * Add debug logging for the MetaItem
148
     *
149
     * @param string $errorLabel
150
     * @param array $scenarios
151
     */
152
    public function debugMetaItem(
153
        $errorLabel = 'Error: ',
154
        array $scenarios = ['default' => 'error'],
155
    ) {
156
        $isMetaJsonLdModel = false;
157
        if (is_subclass_of($this, MetaJsonLd::class)) {
158
            $isMetaJsonLdModel = true;
159
        }
160
        $modelScenarios = $this->scenarios();
161
        $scenarios = array_intersect_key($scenarios, $modelScenarios);
162
        foreach ($scenarios as $scenario => $logLevel) {
163
            $this->setScenario($scenario);
164
            if (!$this->validate()) {
165
                $extraInfo = '';
166
                // Add a URL to the schema.org type if this is a MetaJsonLD object
167
                if ($isMetaJsonLdModel) {
168
                    /** @var MetaJsonLd $this */
169
                    $extraInfo = ' for http://schema.org/' . $this->type;
170
                }
171
                $errorMsg =
172
                    Craft::t('seomatic', 'Scenario: "')
173
                    . $scenario
174
                    . '"'
175
                    . $extraInfo
176
                    . PHP_EOL
177
                    . 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

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

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