Passed
Push — v3 ( 5141f6...ef9227 )
by Andrew
31:01 queued 18:03
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
     * @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