Passed
Push — v3 ( 5141f6...ef9227 )
by Andrew
31:01 queued 18:03
created

MetaValue   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 299
Duplicated Lines 0 %

Test Coverage

Coverage 11.67%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 47
eloc 138
c 3
b 1
f 0
dl 0
loc 299
ccs 14
cts 120
cp 0.1167
rs 8.64

5 Methods

Rating   Name   Duplication   Size   Complexity  
A parseString() 0 20 5
B cache() 0 38 6
F parseArray() 0 49 14
A getSiteLanguage() 0 20 4
F parseMetaString() 0 79 18

How to fix   Complexity   

Complex Class

Complex classes like MetaValue often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MetaValue, and based on these observations, apply Extract Interface, too.

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

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
224
            // Use the SeoElement interface to get the refHandle
225
            $metaBundleSourceType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($element);
226
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($metaBundleSourceType);
227
            if ($seoElement) {
228
                $elementRefHandle = $seoElement::getElementRefHandle();
229
            }
230
            // Prefer $element::refHandle()
231
            $matchedElementType = $elementRefHandle ?? $refHandle ?? 'entry';
232
            if ($matchedElementType) {
233
                self::$templateObjectVars[$matchedElementType] = $element;
234
                self::$templatePreviewVars[$matchedElementType] = $element;
235
            }
236
        }
237
        self::$templatePreviewVars['object'] = self::$templateObjectVars;
238
        self::$templatePreviewVars['seomatic'] = Seomatic::$seomaticVariable;
239
240
        self::$view = Seomatic::$view;
241
    }
242
243
    // Protected Methods
244
    // =========================================================================
245
246
    /**
247
     * @param string|Asset $metaValue
248
     * @param bool $resolveAliases Whether @ aliases should be resolved
249
     *                                     in this string
250
     * @param bool $parseAsTwig Whether items should be parsed as a
251
     *                                     Twig template in this string
252
     *
253
     * @return null|string
254
     */
255 1
    protected static function parseMetaString($metaValue, bool $resolveAliases = true, bool $parseAsTwig = true)
256
    {
257
        // Handle being passed in a string
258 1
        if (is_string($metaValue)) {
259 1
            if ($resolveAliases) {
260
                // Resolve it as an alias
261 1
                if (Seomatic::$craft31) {
262
                    try {
263 1
                        $alias = Craft::parseEnv($metaValue);
264
                    } catch (\Exception $e) {
265 1
                        $alias = false;
266
                    }
267
                } else {
268
                    try {
269
                        $alias = Craft::getAlias($metaValue, false);
270
                    } catch (\Exception $e) {
271
                        $alias = false;
272
                    }
273
                }
274 1
                if (is_string($alias)) {
275 1
                    $metaValue = $alias;
276
                }
277
            }
278
            // Ensure we aren't passed in an absurdly large object template to parse
279 1
            if (strlen($metaValue) > self::MAX_TEMPLATE_LENGTH) {
280
                $metaValue = mb_substr($metaValue, 0, self::MAX_TEMPLATE_LENGTH);
281
            }
282
            // If there are no dynamic tags, just return the template
283 1
            if (!$parseAsTwig || !StringHelper::contains($metaValue, '{', false)) {
284 1
                return trim(html_entity_decode($metaValue, ENT_NOQUOTES, 'UTF-8'));
285
            }
286
            $oldTemplateMode = self::$view->getTemplateMode();
287
            try {
288
                // Render in site template mode so that we get globals injected
289
                if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
290
                    self::$view->setTemplateMode(self::$view::TEMPLATE_MODE_SITE);
291
                }
292
                // Render the template out
293
                $metaValue = trim(html_entity_decode(
294
                    self::$view->renderObjectTemplate($metaValue, self::$templateObjectVars, self::$templatePreviewVars),
295
                    ENT_NOQUOTES,
296
                    'UTF-8'
297
                ));
298
                // Restore the template mode
299
                if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
300
                    self::$view->setTemplateMode($oldTemplateMode);
301
                }
302
            } catch (Throwable $e) {
303
                $metaValue = Craft::t(
304
                    'seomatic',
305
                    'Error rendering `{template}` -> {error}',
306
                    ['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

306
                    ['template' => $metaValue, 'error' => $e->getMessage() . ' - ' . /** @scrutinizer ignore-type */ print_r($metaValue, true)]
Loading history...
307
                );
308
                Craft::error($metaValue, __METHOD__);
309
                Craft::$app->getErrorHandler()->logException($e);
310
                // Restore the template mode
311
                if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
312
                    try {
313
                        self::$view->setTemplateMode($oldTemplateMode);
314
                    } catch (Exception $e) {
315
                        Craft::error($e->getMessage(), __METHOD__);
316
                    }
317
                }
318
319
                return null;
320
            }
321
        }
322
        // Handle being passed in an object
323
        if (is_object($metaValue)) {
324
            if ($metaValue instanceof Markup) {
0 ignored issues
show
introduced by
$metaValue is never a sub-type of Twig\Markup.
Loading history...
325
                return trim(html_entity_decode((string)$metaValue, ENT_NOQUOTES, 'UTF-8'));
326
            }
327
            if ($metaValue instanceof Asset) {
0 ignored issues
show
introduced by
$metaValue is always a sub-type of craft\elements\Asset.
Loading history...
328
                /** @var Asset $metaValue */
329
                return $metaValue->uri;
330
            }
331
        }
332
333
        return $metaValue;
334
    }
335
}
336