Passed
Push — develop-v4 ( e57591...a35127 )
by Andrew
21:48 queued 10:23
created

MetaValue::parseMetaString()   F

Complexity

Conditions 16
Paths 324

Size

Total Lines 71
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 137.719

Importance

Changes 0
Metric Value
eloc 41
dl 0
loc 71
ccs 9
cts 41
cp 0.2195
rs 3.1833
c 0
b 0
f 0
cc 16
nc 324
nop 3
crap 137.719

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

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

293
                    ['template' => $metaValue, 'error' => $e->getMessage() . ' - ' . /** @scrutinizer ignore-type */ print_r($metaValue, true)]
Loading history...
294
                );
295
                Craft::error($metaValue, __METHOD__);
296
                Craft::$app->getErrorHandler()->logException($e);
297
                // Restore the template mode
298
                if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
299
                    try {
300
                        self::$view->setTemplateMode($oldTemplateMode);
301
                    } catch (Exception $e) {
302
                        Craft::error($e->getMessage(), __METHOD__);
303
                    }
304
                }
305
306
                return null;
307
            }
308
        }
309
        // Handle being passed in an object
310
        if (is_object($metaValue)) {
311
            if ($metaValue instanceof Markup) {
312
                return trim(html_entity_decode((string)$metaValue, ENT_NOQUOTES, 'UTF-8'));
313
            }
314
            if ($metaValue instanceof Asset) {
0 ignored issues
show
introduced by
$metaValue is always a sub-type of craft\elements\Asset.
Loading history...
315
                /** @var Asset $metaValue */
316
                return $metaValue->uri;
317
            }
318
        }
319
320
        return $metaValue;
321
    }
322
}
323