MetaValue::parseMetaString()   F
last analyzed

Complexity

Conditions 16
Paths 324

Size

Total Lines 70
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 41
dl 0
loc 70
rs 3.1833
c 0
b 0
f 0
cc 16
nc 324
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\helpers;
13
14
use Craft;
15
use craft\base\Element;
16
use craft\elements\Asset;
17
use craft\errors\SiteNotFoundException;
18
use craft\web\View;
19
use nystudio107\seomatic\Seomatic;
20
use ReflectionClass;
21
use Throwable;
22
use Twig\Markup;
23
use yii\base\Exception;
24
use function in_array;
25
use function is_object;
26
use function is_string;
27
28
/**
29
 * @author    nystudio107
30
 * @package   Seomatic
31
 * @since     3.0.0
32
 */
33
class MetaValue
34
{
35
    // Constants
36
    // =========================================================================
37
38
    public const MAX_TEMPLATE_LENGTH = 4096;
39
    public const MAX_PARSE_TRIES = 5;
40
    // Semicolon because that is the resolved config key when rendering tags,
41
    // kebab-case because that is the config keys as defined in the config files.
42
    public const NO_ALIASES = [
43
        'twitter:site',
44
        'twitter:creator',
45
        'twitterSite',
46
        'twitterCreator',
47
    ];
48
    public const NO_PARSING = [
49
        'siteLinksSearchTarget',
50
    ];
51
    public const PARSE_ONCE = [
52
        'target',
53
        'urlTemplate',
54
    ];
55
56
    // Static Properties
57
    // =========================================================================
58
59
    /**
60
     * @var array
61
     */
62
    public static $templateObjectVars;
63
64
    /**
65
     * @var array
66
     */
67
    public static $templatePreviewVars = [];
68
69
    /**
70
     * @var View
71
     */
72
    public static $view;
73
74
    // Static Methods
75
    // =========================================================================
76
77
    /**
78
     * @param string $metaValue
79
     * @param bool $resolveAliases Whether @ aliases should be resolved in
80
     *                               this string
81
     * @param bool $parseAsTwig Whether items should be parsed as a Twig
82
     *                               template in this string
83
     * @param int $tries The number of times to parse the string
84
     *
85
     * @return string
86
     */
87
    public static function parseString(
88
        $metaValue,
89
        bool $resolveAliases = true,
90
        bool $parseAsTwig = true,
91
        $tries = self::MAX_PARSE_TRIES,
92
    ) {
93
        // If it's a string, and there are no dynamic tags, just return the template
94
        if (is_string($metaValue) && !str_contains($metaValue, '{')) {
95
            return self::parseMetaString($metaValue, $resolveAliases, $parseAsTwig) ?? $metaValue;
96
        }
97
        // Parse it repeatedly until it doesn't change
98
        $value = '';
99
        while ($metaValue !== $value && $tries) {
100
            $tries--;
101
            $value = $metaValue;
102
            $metaValue = self::parseMetaString($value, $resolveAliases, $parseAsTwig) ?? $metaValue;
103
        }
104
105
        return $metaValue;
106
    }
107
108
    /**
109
     * @param array $metaArray
110
     * @param bool $resolveAliases Whether @ aliases should be resolved in
111
     *                              this array
112
     * @param bool $parseAsTwig Whether items should be parsed as a Twig
113
     *                              template in this array
114
     * @param bool $recursive Whether to recursively parse the array
115
     */
116
    public static function parseArray(array &$metaArray, bool $resolveAliases = true, bool $parseAsTwig = true, bool $recursive = false)
117
    {
118
        // Do this here as well so that parseString() won't potentially be constantly switching modes
119
        // while parsing through the array
120
        $oldTemplateMode = self::$view->getTemplateMode();
121
        // Render in site template mode so that we get globals injected
122
        if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
123
            try {
124
                self::$view->setTemplateMode(self::$view::TEMPLATE_MODE_SITE);
125
            } catch (Exception $e) {
126
                Craft::error($e->getMessage(), __METHOD__);
127
            }
128
        }
129
        foreach ($metaArray as $key => $value) {
130
            if ($recursive && is_array($value)) {
131
                self::parseArray($value, $resolveAliases, $parseAsTwig, $recursive);
132
            }
133
            $shouldParse = $parseAsTwig;
134
            $shouldAlias = $resolveAliases;
135
            $tries = self::MAX_PARSE_TRIES;
136
            if (in_array($key, self::NO_ALIASES, true)) {
137
                $shouldAlias = false;
138
            }
139
            if (in_array($key, self::NO_PARSING, true)) {
140
                $shouldParse = false;
141
            }
142
            if (in_array($key, self::PARSE_ONCE, true)) {
143
                $tries = 1;
144
                if (is_string($value) && $value[0] !== '{') {
145
                    $shouldParse = false;
146
                }
147
            }
148
            if ($value !== null) {
149
                $metaArray[$key] = self::parseString($value, $shouldAlias, $shouldParse, $tries);
150
            }
151
        }
152
        // Restore the template mode
153
        if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
154
            try {
155
                self::$view->setTemplateMode($oldTemplateMode);
156
            } catch (Exception $e) {
157
                Craft::error($e->getMessage(), __METHOD__);
158
            }
159
        }
160
161
        // Remove any empty values
162
        $metaArray = array_filter(
163
            $metaArray,
164
            [ArrayHelper::class, 'preserveNumerics']
165
        );
166
    }
167
168
    /**
169
     * Get the language from a siteId
170
     *
171
     * @param null|int $siteId
172
     *
173
     * @return string
174
     */
175
    public static function getSiteLanguage(int $siteId = null): string
176
    {
177
        if ($siteId === null) {
178
            try {
179
                $siteId = Craft::$app->getSites()->getCurrentSite()->id;
180
            } catch (SiteNotFoundException $e) {
181
                $siteId = 1;
182
                Craft::error($e->getMessage(), __METHOD__);
183
            }
184
        }
185
        $site = Craft::$app->getSites()->getSiteById($siteId);
186
        if ($site) {
187
            $language = $site->language;
188
        } else {
189
            $language = Craft::$app->language;
190
        }
191
        $language = strtolower($language);
192
        $language = str_replace('_', '-', $language);
193
194
        return $language;
195
    }
196
197
    /**
198
     * Cache frequently accessed properties locally
199
     */
200
    public static function cache()
201
    {
202
        self::$templateObjectVars = [
203
            'seomatic' => Seomatic::$seomaticVariable,
204
        ];
205
206
        $element = Seomatic::$matchedElement;
207
        /** @var Element $element */
208
        if ($element !== null) {
209
            $refHandle = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $refHandle is dead and can be removed.
Loading history...
210
            // Get a fallback from the element's root class name
211
            $reflector = new ReflectionClass($element);
212
            $refHandle = strtolower($reflector->getShortName());
213
            $elementRefHandle = $element::refHandle();
214
            // Use the SeoElement interface to get the refHandle
215
            $metaBundleSourceType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($element);
216
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($metaBundleSourceType);
217
            if ($seoElement) {
218
                $elementRefHandle = $seoElement::getElementRefHandle();
219
            }
220
            // Prefer $element::refHandle()
221
            $matchedElementType = $elementRefHandle ?? $refHandle;
222
            if ($matchedElementType) {
223
                self::$templateObjectVars[$matchedElementType] = $element;
224
                self::$templatePreviewVars[$matchedElementType] = $element;
225
            }
226
        }
227
        self::$templatePreviewVars['object'] = self::$templateObjectVars;
228
        self::$templatePreviewVars['seomatic'] = Seomatic::$seomaticVariable;
229
230
        self::$view = Seomatic::$view;
231
    }
232
233
    // Protected Methods
234
    // =========================================================================
235
236
    /**
237
     * @param string|object $metaValue
238
     * @param bool $resolveAliases Whether @ aliases should be resolved
239
     *                                     in this string
240
     * @param bool $parseAsTwig Whether items should be parsed as a
241
     *                                     Twig template in this string
242
     *
243
     * @return null|string
244
     */
245
    protected static function parseMetaString($metaValue, bool $resolveAliases = true, bool $parseAsTwig = true)
246
    {
247
        // Handle being passed in a string
248
        if (is_string($metaValue)) {
249
            if ($resolveAliases) {
250
                // Resolve it as an alias
251
                try {
252
                    $alias = Craft::parseEnv($metaValue);
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

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

252
                    $alias = /** @scrutinizer ignore-deprecated */ Craft::parseEnv($metaValue);

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

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

Loading history...
253
                } catch (\Exception $e) {
254
                    $alias = false;
255
                }
256
                if (is_string($alias)) {
257
                    $metaValue = $alias;
258
                }
259
            }
260
            // Ensure we aren't passed in an absurdly large object template to parse
261
            if (strlen($metaValue) > self::MAX_TEMPLATE_LENGTH) {
262
                $metaValue = mb_substr($metaValue, 0, self::MAX_TEMPLATE_LENGTH);
263
            }
264
            // If there are no dynamic tags, just return the template
265
            if (!$parseAsTwig || !str_contains($metaValue, '{')) {
266
                return trim(html_entity_decode($metaValue, ENT_NOQUOTES, 'UTF-8'));
267
            }
268
            $oldTemplateMode = self::$view->getTemplateMode();
269
            try {
270
                // Render in site template mode so that we get globals injected
271
                if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
272
                    self::$view->setTemplateMode(self::$view::TEMPLATE_MODE_SITE);
273
                }
274
                // Render the template out
275
                $metaValue = trim(html_entity_decode(
276
                    self::$view->renderObjectTemplate($metaValue, self::$templateObjectVars, self::$templatePreviewVars),
277
                    ENT_NOQUOTES,
278
                    'UTF-8'
279
                ));
280
                // Restore the template mode
281
                if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
282
                    self::$view->setTemplateMode($oldTemplateMode);
283
                }
284
            } catch (Throwable $e) {
285
                $metaValue = Craft::t(
286
                    'seomatic',
287
                    'Error rendering `{template}` -> {error}',
288
                    ['template' => $metaValue, 'error' => $e->getMessage() . ' - ' . print_r($metaValue, true)]
0 ignored issues
show
Bug introduced by
Are you sure print_r($metaValue, 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

288
                    ['template' => $metaValue, 'error' => $e->getMessage() . ' - ' . /** @scrutinizer ignore-type */ print_r($metaValue, true)]
Loading history...
289
                );
290
                Craft::error($metaValue, __METHOD__);
291
                Craft::$app->getErrorHandler()->logException($e);
292
                // Restore the template mode
293
                if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
294
                    try {
295
                        self::$view->setTemplateMode($oldTemplateMode);
296
                    } catch (Exception $e) {
297
                        Craft::error($e->getMessage(), __METHOD__);
298
                    }
299
                }
300
301
                return null;
302
            }
303
        }
304
        // Handle being passed in an object
305
        if (is_object($metaValue)) {
306
            if ($metaValue instanceof Markup) {
307
                return trim(html_entity_decode((string)$metaValue, ENT_NOQUOTES, 'UTF-8'));
308
            }
309
            if ($metaValue instanceof Asset) {
310
                return $metaValue->uri;
311
            }
312
        }
313
314
        return $metaValue;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $metaValue also could return the type object which is incompatible with the documented return type null|string.
Loading history...
315
    }
316
}
317