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

MetaScriptContainer   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 25
eloc 105
c 0
b 0
f 0
dl 0
loc 214
ccs 0
cts 97
cp 0
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A normalizeContainerData() 0 7 2
A dataLayerHash() 0 8 2
B includeMetaData() 0 51 6
B renderInternal() 0 54 8
B render() 0 43 7
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\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
    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
        if (ImageTransformHelper::$pendingImageTransforms) {
78
            TagDependency::invalidate($cache, $dependency->tags[3]);
79
        }
80
        // Register the tags
81
        foreach ($tagData as $config) {
82
            // Register the tags
83
            $attrs = $config['tagAttrs'] ?? [];
84
            if (!empty($config['nonce'])) {
85
                /** @noinspection SlowArrayOperationsInLoopInspection */
86
                $attrs = array_merge($attrs, [
87
                    'nonce' => $config['nonce'],
88
                ]);
89
            }
90
            $bodyJs = $config['bodyJs'] ?? false;
91
            if ($bodyJs) {
92
                Seomatic::$view->registerHtml(
93
                    $config['js'],
94
                    $config['position']
95
                );
96
            } else {
97
                Seomatic::$view->registerScript(
98
                    $config['js'],
99
                    $config['position'],
100
                    $attrs
101
                );
102
            }
103
        }
104
105
        Craft::endProfile('MetaScriptContainer::includeMetaData', __METHOD__);
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111
    public function render(array $params = [
112
        'renderScriptTags' => true,
113
    ]): string
114
    {
115
        $html = '';
116
        $linebreak = '';
117
        // If `devMode` is enabled, make the scripts human-readable
118
        if (Seomatic::$devMode) {
119
            $linebreak = PHP_EOL;
120
        }
121
        $tagData = $this->renderInternal();
122
        // Register the tags
123
        foreach ($tagData as $config) {
124
            // Register the tags
125
            $attrs = $config['tagAttrs'] ?? [];
126
            if (!empty($config['nonce'])) {
127
                /** @noinspection SlowArrayOperationsInLoopInspection */
128
                $attrs = array_merge($attrs, [
129
                    'nonce' => $config['nonce'],
130
                ]);
131
            }
132
            $bodyJs = $config['bodyJs'] ?? false;
133
            // If `devMode` is enabled, add some positional information
134
            if (Seomatic::$devMode) {
135
                $positionNames = [
136
                    '<head>',
137
                    '<body>',
138
                    '</body>',
139
                    'jQuery(document).ready()',
140
                    'jQuery(window).load()',
141
                ];
142
                $position = $positionNames[$config['position'] - 1] ?? 'unknown';
143
                $html .= "<!-- Position: {$position} -->" . PHP_EOL;
144
            }
145
            if ($bodyJs || !$params['renderScriptTags']) {
146
                $html .= $config['js'];
147
            } else {
148
                $html .= Html::script($config['js'], $attrs);
149
            }
150
            $html .= $linebreak;
151
        }
152
153
        return trim($html);
154
    }
155
156
    /**
157
     * @inheritdoc
158
     */
159
    public function normalizeContainerData()
160
    {
161
        parent::normalizeContainerData();
162
163
        foreach ($this->data as $key => $config) {
164
            $config['key'] = $key;
165
            $this->data[$key] = MetaScript::create($config);
0 ignored issues
show
Bug introduced by
$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

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