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

MetaValue::cache()   B

Complexity

Conditions 6
Paths 17

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 24
c 2
b 0
f 0
dl 0
loc 38
ccs 0
cts 24
cp 0
rs 8.9137
cc 6
nc 17
nop 0
crap 42
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