Issues (257)

src/models/MetaScriptContainer.php (2 issues)

1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS
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\models;
13
14
use Craft;
15
use craft\helpers\Html;
16
use nystudio107\seomatic\base\NonceContainer;
17
use nystudio107\seomatic\helpers\ImageTransform as ImageTransformHelper;
18
use nystudio107\seomatic\Seomatic;
19
use yii\caching\TagDependency;
20
use yii\web\View;
21
22
/**
23
 * @author    nystudio107
24
 * @package   Seomatic
25
 * @since     3.0.0
26
 */
27
class MetaScriptContainer extends NonceContainer
28
{
29
    // Constants
30
    // =========================================================================
31
32
    public const CONTAINER_TYPE = 'MetaScriptContainer';
33
34
    // Public Properties
35
    // =========================================================================
36
37
    /**
38
     * The data in this container
39
     *
40
     * @var MetaScript[] $data
41
     */
42
    public $data = [];
43
44
    /**
45
     * @var int
46
     */
47
    public $position = View::POS_HEAD;
48
49
    // Public Methods
50
    // =========================================================================
51
52
    /**
53
     * @inheritdoc
54
     */
55
    public function includeMetaData($dependency)
56
    {
57
        Craft::beginProfile('MetaScriptContainer::includeMetaData', __METHOD__);
58
        $uniqueKey = $this->handle . $dependency->tags[3] . $this->dataLayerHash();
59
        $cache = Craft::$app->getCache();
60
        if ($this->clearCache) {
61
            TagDependency::invalidate($cache, $dependency->tags[3]);
62
        }
63
        $tagData = $cache->getOrSet(
64
            self::CONTAINER_TYPE . $uniqueKey,
65
            function() use ($uniqueKey) {
66
                Craft::info(
67
                    self::CONTAINER_TYPE . ' cache miss: ' . $uniqueKey,
68
                    __METHOD__
69
                );
70
71
                return $this->renderInternal();
72
            },
73
            Seomatic::$cacheDuration,
74
            $dependency
75
        );
76
        // Invalidate the cache we just created if there were pending image transforms in it
77
        // or we were asked to clear the cache for this container (because it's a preview request, etc.)
78
        if ($this->clearCache || ImageTransformHelper::$pendingImageTransforms) {
79
            TagDependency::invalidate($cache, $dependency->tags[3]);
80
        }
81
        // Register the tags
82
        foreach ($tagData as $config) {
83
            // Register the tags
84
            $attrs = $config['tagAttrs'] ?? [];
85
            if (!empty($config['nonce'])) {
86
                /** @noinspection SlowArrayOperationsInLoopInspection */
87
                $attrs = array_merge($attrs, [
88
                    'nonce' => $config['nonce'],
89
                ]);
90
            }
91
            $bodyJs = $config['bodyJs'] ?? false;
92
            if ($bodyJs) {
93
                Seomatic::$view->registerHtml(
0 ignored issues
show
The method registerHtml() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

93
                Seomatic::$view->/** @scrutinizer ignore-call */ 
94
                                 registerHtml(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
94
                    $config['js'],
95
                    $config['position'],
96
                );
97
            } else {
98
                Seomatic::$view->registerScript(
99
                    $config['js'],
100
                    $config['position'],
101
                    $attrs
102
                );
103
            }
104
        }
105
106
        Craft::endProfile('MetaScriptContainer::includeMetaData', __METHOD__);
107
    }
108
109
    /**
110
     * @inheritdoc
111
     */
112
    public function render(array $params = [
113
        'renderScriptTags' => true,
114
    ]): string
115
    {
116
        $html = '';
117
        $linebreak = '';
118
        // If `devMode` is enabled, make the scripts human-readable
119
        if (Seomatic::$devMode) {
120
            $linebreak = PHP_EOL;
121
        }
122
        $tagData = $this->renderInternal();
123
        // Register the tags
124
        foreach ($tagData as $config) {
125
            // Register the tags
126
            $attrs = $config['tagAttrs'] ?? [];
127
            if (!empty($config['nonce'])) {
128
                /** @noinspection SlowArrayOperationsInLoopInspection */
129
                $attrs = array_merge($attrs, [
130
                    'nonce' => $config['nonce'],
131
                ]);
132
            }
133
            $bodyJs = $config['bodyJs'] ?? false;
134
            // If `devMode` is enabled, add some positional information
135
            if (Seomatic::$devMode) {
136
                $positionNames = [
137
                    '<head>',
138
                    '<body>',
139
                    '</body>',
140
                    'jQuery(document).ready()',
141
                    'jQuery(window).load()',
142
                ];
143
                $position = $positionNames[$config['position'] - 1] ?? 'unknown';
144
                $html .= "<!-- Position: {$position} -->" . PHP_EOL;
145
            }
146
            if ($bodyJs || !$params['renderScriptTags']) {
147
                $html .= $config['js'];
148
            } else {
149
                $html .= Html::script($config['js'], $attrs);
150
            }
151
            $html .= $linebreak;
152
        }
153
154
        return trim($html);
155
    }
156
157
    /**
158
     * @inheritdoc
159
     */
160
    public function normalizeContainerData()
161
    {
162
        parent::normalizeContainerData();
163
164
        /** @var array $config */
165
        foreach ($this->data as $key => $config) {
166
            $config['key'] = $key;
167
            $this->data[$key] = MetaScript::create($config);
0 ignored issues
show
$config of type nystudio107\seomatic\models\MetaScript is incompatible with the type array expected by parameter $config of nystudio107\seomatic\models\MetaScript::create(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

167
            $this->data[$key] = MetaScript::create(/** @scrutinizer ignore-type */ $config);
Loading history...
168
        }
169
    }
170
171
    // Protected Methods
172
    // =========================================================================
173
174
    /**
175
     * Render all of the scripts out into tagData
176
     *
177
     * @return array
178
     */
179
    protected function renderInternal(): array
180
    {
181
        $tagData = [];
182
        if ($this->prepForInclusion()) {
183
            foreach ($this->data as $metaScriptModel) {
184
                if ($metaScriptModel->include) {
185
                    // The regular script JS
186
                    $js = $metaScriptModel->render();
187
                    if (!empty($js)) {
188
                        $scenario = $this->scenario;
189
                        $metaScriptModel->setScenario('render');
190
                        $options = $metaScriptModel->tagAttributes();
191
                        $metaScriptModel->setScenario($scenario);
192
                        $tagData[] = [
193
                            'js' => $js,
194
                            'position' => $metaScriptModel->position,
195
                            'nonce' => $metaScriptModel->nonce ?? null,
196
                            'tagAttrs' => $options,
197
                            'bodyJs' => false,
198
                        ];
199
                        // If `devMode` is enabled, validate the Meta Script and output any model errors
200
                        if (Seomatic::$devMode) {
201
                            $metaScriptModel->debugMetaItem(
202
                                'Script attribute: '
203
                            );
204
                        }
205
                    }
206
                    // The body script JS (has no wrapping <script></script> tags, as it can be arbitrary HTML)
207
                    $bodyJs = $metaScriptModel->renderBodyHtml();
208
                    if (!empty($bodyJs)) {
209
                        $scenario = $this->scenario;
210
                        $metaScriptModel->setScenario('render');
211
                        $options = $metaScriptModel->tagAttributes();
212
                        $metaScriptModel->setScenario($scenario);
213
                        $tagData[] = [
214
                            'js' => $bodyJs,
215
                            'position' => $metaScriptModel->bodyPosition,
216
                            'nonce' => $metaScriptModel->nonce ?? null,
217
                            'tagAttrs' => $options,
218
                            'bodyJs' => true,
219
                        ];
220
                        // If `devMode` is enabled, validate the Meta Script and output any model errors
221
                        if (Seomatic::$devMode) {
222
                            $metaScriptModel->debugMetaItem(
223
                                'Script attribute: '
224
                            );
225
                        }
226
                    }
227
                }
228
            }
229
        }
230
231
        return $tagData;
232
    }
233
234
    protected function dataLayerHash(): string
235
    {
236
        $data = '';
237
        foreach ($this->data as $metaScriptModel) {
238
            $data .= serialize($metaScriptModel->dataLayer);
239
        }
240
241
        return md5($data);
242
    }
243
}
244