Issues (260)

src/base/MetaItem.php (1 issue)

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
    // Traits
35
    // =========================================================================
36
37
    use MetaItemTrait;
38
39
    // Constants
40
    // =========================================================================
41
42
    public const ARRAY_PROPERTIES = [
43
    ];
44
45
    // Public Methods
46
    // =========================================================================
47
48
    /**
49
     * @inheritdoc
50
     */
51
    public function init(): void
52
    {
53
        parent::init();
54
        // Set any per-environment attributes
55
        if (!Seomatic::$previewingMetaContainers || Seomatic::$headlessRequest) {
56
            $attributes = [];
57
            $envVars = null;
58
            try {
59
                $envVars = ArrayHelper::getValue($this->environment, Seomatic::$environment);
60
            } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
61
            }
62
            if (is_array($envVars)) {
63
                foreach ($envVars as $key => $value) {
64
                    $attributes[$key] = $value;
65
                }
66
            }
67
            $this->setAttributes($attributes, false);
68
        }
69
    }
70
71
    /**
72
     * @inheritdoc
73
     */
74
    public function rules(): array
75
    {
76
        $rules = parent::rules();
77
        $rules = array_merge($rules, [
78
            [['key'], 'required'],
79
            [['include'], 'boolean'],
80
            [['key'], 'string'],
81
            [['environment'], 'safe'],
82
            [['dependencies'], 'safe'],
83
            [['tagAttrs'], 'safe'],
84
        ]);
85
86
        return $rules;
87
    }
88
89
    /**
90
     * @inheritdoc
91
     */
92
    public function fields(): array
93
    {
94
        $fields = parent::fields();
95
        switch ($this->scenario) {
96
            case 'render':
97
                $fields = array_diff_key(
98
                    $fields,
99
                    array_flip([
100
                        'include',
101
                        'key',
102
                        'environment',
103
                        'dependencies',
104
                        'tagAttrs',
105
                    ])
106
                );
107
                break;
108
        }
109
110
        return $fields;
111
    }
112
113
    /**
114
     * @inheritdoc
115
     */
116
    public function prepForRender(&$data): bool
117
    {
118
        if ($this->include) {
119
            return Dependency::validateDependencies($this->dependencies);
120
        }
121
122
        return false;
123
    }
124
125
    /**
126
     * @inheritdoc
127
     */
128
    public function render(array $params = []): string
129
    {
130
        return '';
131
    }
132
133
    /**
134
     * @inheritdoc
135
     */
136
    public function renderAttributes(array $params = []): array
137
    {
138
        return [];
139
    }
140
141
    /**
142
     * Add debug logging for the MetaItem
143
     *
144
     * @param string $errorLabel
145
     * @param array $scenarios
146
     */
147
    public function debugMetaItem(
148
        $errorLabel = 'Error: ',
149
        array $scenarios = ['default' => 'error'],
150
    ) {
151
        $isMetaJsonLdModel = false;
152
        if (is_subclass_of($this, MetaJsonLd::class)) {
153
            $isMetaJsonLdModel = true;
154
        }
155
        $modelScenarios = $this->scenarios();
156
        $scenarios = array_intersect_key($scenarios, $modelScenarios);
157
        foreach ($scenarios as $scenario => $logLevel) {
158
            $this->setScenario($scenario);
159
            if (!$this->validate()) {
160
                $extraInfo = '';
161
                // Add a URL to the schema.org type if this is a MetaJsonLD object
162
                if ($isMetaJsonLdModel) {
163
                    /** @var MetaJsonLd $this */
164
                    $extraInfo = ' for http://schema.org/' . $this->type;
165
                }
166
                $errorMsg =
167
                    Craft::t('seomatic', 'Scenario: "')
168
                    . $scenario
169
                    . '"'
170
                    . $extraInfo
171
                    . PHP_EOL
172
                    . print_r($this->render(), true);
173
                Craft::info($errorMsg, __METHOD__);
174
                foreach ($this->errors as $param => $errors) {
175
                    $errorMsg = Craft::t('seomatic', $errorLabel) . $param;
176
                    /** @var array $errors */
177
                    foreach ($errors as $error) {
178
                        $errorMsg .= ' -> ' . $error;
179
                        // Change the error level depending on the error message if this is a MetaJsonLD object
180
                        if ($isMetaJsonLdModel) {
181
                            if (strpos($error, 'recommended') !== false) {
182
                                $logLevel = 'warning';
183
                            }
184
                            if (strpos($error, 'required') !== false
185
                                || strpos($error, 'Must be') !== false
186
                            ) {
187
                                $logLevel = 'error';
188
                            }
189
                        }
190
                    }
191
                    Craft::info(strtoupper($logLevel) . ' - ' . $errorMsg, __METHOD__);
192
                    // Extra debugging info for MetaJsonLd objects
193
                    if ($isMetaJsonLdModel) {
194
                        /** @var MetaJsonLd $className */
195
                        $className = get_class($this);
196
                        if (!empty($className->schemaPropertyDescriptions[$param])) {
197
                            $errorMsg = Craft::t('seomatic', $errorLabel) . $param;
198
                            $errorMsg .= ' -> ' . $className->schemaPropertyDescriptions[$param];
199
                            Craft::info($errorMsg, __METHOD__);
200
                        }
201
                    }
202
                }
203
            }
204
        }
205
    }
206
207
    /**
208
     * Return an array of tag attributes, normalizing the keys
209
     *
210
     * @return array
211
     */
212
    public function tagAttributes(): array
213
    {
214
        $attrs = $this->tagAttrs;
215
        if (!is_array($attrs)) {
216
            $attrs = [];
217
        }
218
        if (!empty($attrs) && ArrayHelper::isIndexed($attrs, true)) {
219
            $attrs = [];
220
            foreach ($this->tagAttrs as $attr) {
221
                $attrs[$attr['name']] = $attr['value'];
222
            }
223
        }
224
        $tagAttributes = array_merge($this->toArray(), $attrs);
225
        $tagAttributes = array_filter($tagAttributes);
226
        foreach ($tagAttributes as $key => $value) {
227
            ArrayHelper::rename($tagAttributes, $key, Inflector::slug(Inflector::titleize($key)));
228
        }
229
        ksort($tagAttributes);
230
231
        return $tagAttributes;
232
    }
233
234
    /**
235
     * Return an array of arrays that contain the meta tag attributes
236
     *
237
     * @return array
238
     */
239
    public function tagAttributesArray(): array
240
    {
241
        $result = [];
242
        $optionsCount = 1;
243
        $scenario = $this->scenario;
244
        $this->setScenario('render');
245
        $options = $this->tagAttributes();
246
        $this->setScenario($scenario);
247
248
        // See if any of the potentially array properties actually are
249
        foreach (static::ARRAY_PROPERTIES as $arrayProperty) {
250
            if (!empty($options[$arrayProperty]) && is_array($options[$arrayProperty])) {
251
                $optionsCount = count($options[$arrayProperty]) > $optionsCount
252
                    ? count($options[$arrayProperty]) : $optionsCount;
253
            }
254
        }
255
        // Return an array of resulting options
256
        while ($optionsCount--) {
257
            $resultOptions = $options;
258
            foreach ($resultOptions as $key => $value) {
259
                $resultOptions[$key] = (is_array($value) && isset($value[$optionsCount]))
260
                    ? $value[$optionsCount] : $value;
261
            }
262
            $result[] = $resultOptions;
263
        }
264
265
        return $result;
266
    }
267
268
    /**
269
     * Validate the passed in $attribute as either an array or a string
270
     *
271
     * @param mixed $attribute the attribute currently being validated
272
     * @param mixed $params the value of the "params" given in the rule
273
     */
274
    public function validateStringOrArray(
275
        $attribute,
276
        $params,
277
    ) {
278
        $validated = false;
279
        if (is_string($attribute)) {
280
            $validated = true;
281
        }
282
        if (is_array($attribute)) {
283
            $validated = true;
284
        }
285
        if (!$validated) {
286
            $this->addError($attribute, 'Must be either a string or an array');
287
        }
288
    }
289
290
    /**
291
     * @inheritdoc
292
     */
293
    protected function defineBehaviors(): array
294
    {
295
        return [
296
            'parser' => [
297
                'class' => MetaItemAttributeParserBehavior::class,
298
                'attributes' => [
299
                ],
300
            ],
301
        ];
302
    }
303
}
304