Passed
Push — v3 ( c4eb1a...fabe49 )
by Andrew
33:23 queued 19:58
created

MetaValue::parseMetaString()   F

Complexity

Conditions 18
Paths 580

Size

Total Lines 79
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 151.5408

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 18
eloc 47
c 1
b 0
f 0
nc 580
nop 3
dl 0
loc 79
ccs 11
cts 43
cp 0.2558
crap 151.5408
rs 1.2833

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\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
     */
118
    public static function parseArray(array &$metaArray, bool $resolveAliases = true, bool $parseAsTwig = true)
119
    {
120
        // Do this here as well so that parseString() won't potentially be constantly switching modes
121
        // while parsing through the array
122
        $oldTemplateMode = self::$view->getTemplateMode();
123
        // Render in site template mode so that we get globals injected
124
        if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
125
            try {
126
                self::$view->setTemplateMode(self::$view::TEMPLATE_MODE_SITE);
127
            } catch (Exception $e) {
128
                Craft::error($e->getMessage(), __METHOD__);
129
            }
130
        }
131
        foreach ($metaArray as $key => $value) {
132
            $shouldParse = $parseAsTwig;
133
            $shouldAlias = $resolveAliases;
134
            $tries = self::MAX_PARSE_TRIES;
135
            if (in_array($key, self::NO_ALIASES, true)) {
136
                $shouldAlias = false;
137
            }
138
            if (in_array($key, self::NO_PARSING, true)) {
139
                $shouldParse = false;
140
            }
141
            if (in_array($key, self::PARSE_ONCE, true)) {
142
                $tries = 1;
143
                if (is_string($value) && $value[0] !== '{') {
144
                    $shouldParse = false;
145
                }
146
            }
147
            if ($value !== null) {
148
                $metaArray[$key] = self::parseString($value, $shouldAlias, $shouldParse, $tries);
149
            }
150
        }
151
        // Restore the template mode
152
        if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
153
            try {
154
                self::$view->setTemplateMode($oldTemplateMode);
155
            } catch (Exception $e) {
156
                Craft::error($e->getMessage(), __METHOD__);
157
            }
158
        }
159
160
        // Remove any empty values
161
        $metaArray = array_filter(
162
            $metaArray,
163
            [ArrayHelper::class, 'preserveNumerics']
164
        );
165
    }
166
167
    /**
168
     * Get the language from a siteId
169
     *
170
     * @param null|int $siteId
171
     *
172
     * @return string
173
     */
174
    public static function getSiteLanguage(int $siteId = null): string
175
    {
176
        if ($siteId === null) {
177
            try {
178
                $siteId = Craft::$app->getSites()->getCurrentSite()->id;
179
            } catch (SiteNotFoundException $e) {
180
                $siteId = 1;
181
                Craft::error($e->getMessage(), __METHOD__);
182
            }
183
        }
184
        $site = Craft::$app->getSites()->getSiteById($siteId);
185
        if ($site) {
186
            $language = $site->language;
187
        } else {
188
            $language = Craft::$app->language;
189
        }
190
        $language = strtolower($language);
191
        $language = str_replace('_', '-', $language);
192
193
        return $language;
194
    }
195
196
    /**
197
     * Cache frequently accessed properties locally
198
     */
199
    public static function cache()
200
    {
201
        self::$templateObjectVars = [
202
            'seomatic' => Seomatic::$seomaticVariable,
203
        ];
204
205
        $element = Seomatic::$matchedElement;
206
        /** @var Element $element */
207
        if ($element !== null) {
208
            $refHandle = null;
209
            // Get a fallback from the element's root class name
210
            try {
211
                $reflector = new ReflectionClass($element);
212
            } catch (ReflectionException $e) {
213
                $reflector = null;
214
                Craft::error($e->getMessage(), __METHOD__);
215
            }
216
            if ($reflector) {
217
                $refHandle = strtolower($reflector->getShortName());
218
            }
219
            $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...
220
            // Use the SeoElement interface to get the refHandle
221
            $metaBundleSourceType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($element);
222
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($metaBundleSourceType);
223
            if ($seoElement) {
224
                $elementRefHandle = $seoElement::getElementRefHandle();
225
            }
226
            // Prefer $element::refHandle()
227
            $matchedElementType = $elementRefHandle ?? $refHandle ?? 'entry';
228
            if ($matchedElementType) {
229
                self::$templateObjectVars[$matchedElementType] = $element;
230
                self::$templatePreviewVars[$matchedElementType] = $element;
231
            }
232
        }
233
        self::$templatePreviewVars['object'] = self::$templateObjectVars;
234
        self::$templatePreviewVars['seomatic'] = Seomatic::$seomaticVariable;
235
236
        self::$view = Seomatic::$view;
237
    }
238
239
    // Protected Methods
240
    // =========================================================================
241
242
    /**
243
     * @param string|Asset $metaValue
244
     * @param bool $resolveAliases Whether @ aliases should be resolved
245
     *                                     in this string
246
     * @param bool $parseAsTwig Whether items should be parsed as a
247
     *                                     Twig template in this string
248
     *
249
     * @return null|string
250
     */
251 1
    protected static function parseMetaString($metaValue, bool $resolveAliases = true, bool $parseAsTwig = true)
252
    {
253
        // Handle being passed in a string
254 1
        if (is_string($metaValue)) {
255 1
            if ($resolveAliases) {
256
                // Resolve it as an alias
257 1
                if (Seomatic::$craft31) {
258
                    try {
259 1
                        $alias = Craft::parseEnv($metaValue);
260
                    } catch (\Exception $e) {
261 1
                        $alias = false;
262
                    }
263
                } else {
264
                    try {
265
                        $alias = Craft::getAlias($metaValue, false);
266
                    } catch (\Exception $e) {
267
                        $alias = false;
268
                    }
269
                }
270 1
                if (is_string($alias)) {
271 1
                    $metaValue = $alias;
272
                }
273
            }
274
            // Ensure we aren't passed in an absurdly large object template to parse
275 1
            if (strlen($metaValue) > self::MAX_TEMPLATE_LENGTH) {
276
                $metaValue = mb_substr($metaValue, 0, self::MAX_TEMPLATE_LENGTH);
277
            }
278
            // If there are no dynamic tags, just return the template
279 1
            if (!$parseAsTwig || !StringHelper::contains($metaValue, '{', false)) {
280 1
                return trim(html_entity_decode($metaValue, ENT_NOQUOTES, 'UTF-8'));
281
            }
282
            $oldTemplateMode = self::$view->getTemplateMode();
283
            try {
284
                // Render in site template mode so that we get globals injected
285
                if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
286
                    self::$view->setTemplateMode(self::$view::TEMPLATE_MODE_SITE);
287
                }
288
                // Render the template out
289
                $metaValue = trim(html_entity_decode(
290
                    self::$view->renderObjectTemplate($metaValue, self::$templateObjectVars, self::$templatePreviewVars),
291
                    ENT_NOQUOTES,
292
                    'UTF-8'
293
                ));
294
                // Restore the template mode
295
                if ($oldTemplateMode !== self::$view::TEMPLATE_MODE_SITE) {
296
                    self::$view->setTemplateMode($oldTemplateMode);
297
                }
298
            } catch (Throwable $e) {
299
                $metaValue = Craft::t(
300
                    'seomatic',
301
                    'Error rendering `{template}` -> {error}',
302
                    ['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

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