Test Failed
Push — v5 ( ec28e0...e66c40 )
by Andrew
54:30 queued 27:51
created

SitemapIndexTemplate   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 313
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 40
eloc 139
c 0
b 0
f 0
dl 0
loc 313
rs 9.2

8 Methods

Rating   Name   Duplication   Size   Complexity  
A fields() 0 3 1
A create() 0 11 1
A getFilename() 0 3 1
A rules() 0 7 1
A invalidateCache() 0 7 1
B addAdditionalSitemaps() 0 28 6
B addAdditionalSitemapUrls() 0 36 7
D render() 0 113 22

How to fix   Complexity   

Complex Class

Complex classes like SitemapIndexTemplate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SitemapIndexTemplate, and based on these observations, apply Extract Interface, too.

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\models\SiteGroup;
16
use DateTime;
17
use Exception;
18
use nystudio107\seomatic\base\FrontendTemplate;
19
use nystudio107\seomatic\base\SitemapInterface;
20
use nystudio107\seomatic\events\RegisterSitemapsEvent;
21
use nystudio107\seomatic\events\RegisterSitemapUrlsEvent;
22
use nystudio107\seomatic\helpers\MetaValue as MetaValueHelper;
23
use nystudio107\seomatic\Seomatic;
24
use yii\base\Event;
25
use yii\caching\TagDependency;
26
use yii\helpers\Html;
27
use yii\web\NotFoundHttpException;
28
use function in_array;
29
30
/**
31
 * @author    nystudio107
32
 * @package   Seomatic
33
 * @since     3.0.0
34
 */
35
class SitemapIndexTemplate extends FrontendTemplate implements SitemapInterface
36
{
37
    // Constants
38
    // =========================================================================
39
40
    /**
41
     * @event RegisterSitemapsEvent The event that is triggered when registering
42
     * additional sitemaps for the sitemap index.
43
     *
44
     * ---
45
     * ```php
46
     * use nystudio107\seomatic\events\RegisterSitemapsEvent;
47
     * use nystudio107\seomatic\models\SitemapIndexTemplate;
48
     * use yii\base\Event;
49
     * Event::on(SitemapIndexTemplate::class, SitemapIndexTemplate::EVENT_REGISTER_SITEMAPS, function(RegisterSitemapsEvent $e) {
50
     *     $e->sitemaps[] = [
51
     *         'loc' => $url,
52
     *         'lastmod' => $lastMod,
53
     *     ];
54
     * });
55
     * ```
56
     */
57
    public const EVENT_REGISTER_SITEMAPS = 'registerSitemaps';
58
59
    public const TEMPLATE_TYPE = 'SitemapIndexTemplate';
60
61
    public const CACHE_KEY = 'seomatic_sitemap_index';
62
63
    public const SITEMAP_INDEX_CACHE_TAG = 'seomatic_sitemap_index';
64
65
    // Static Methods
66
    // =========================================================================
67
68
    /**
69
     * @param array $config
70
     *
71
     * @return null|SitemapIndexTemplate
72
     */
73
    public static function create(array $config = [])
74
    {
75
        $defaults = [
76
            'path' => 'sitemaps-<groupId:\d+>-sitemap.xml',
77
            'template' => '',
78
            'controller' => 'sitemap',
79
            'action' => 'sitemap-index',
80
        ];
81
        $config = array_merge($config, $defaults);
82
83
        return new SitemapIndexTemplate($config);
84
    }
85
86
    // Public Properties
87
    // =========================================================================
88
89
    // Public Methods
90
    // =========================================================================
91
92
    /**
93
     * @inheritdoc
94
     */
95
    public function rules(): array
96
    {
97
        $rules = parent::rules();
98
        $rules = array_merge($rules, [
99
        ]);
100
101
        return $rules;
102
    }
103
104
    /**
105
     * @inheritdoc
106
     */
107
    public function fields(): array
108
    {
109
        return parent::fields();
110
    }
111
112
    /**
113
     * Get the filename of the sitemap index.
114
     *
115
     * @param int $groupId
116
     * @return string
117
     */
118
    public function getFilename(int $groupId): string
119
    {
120
        return 'sitemaps-' . $groupId . '-sitemap.xml';
121
    }
122
123
    /**
124
     * @inheritdoc
125
     *
126
     * @throws NotFoundHttpException if the sitemap.xml doesn't exist
127
     */
128
    public function render(array $params = []): string
129
    {
130
        $cache = Craft::$app->getCache();
131
        $groupId = $params['groupId'];
132
        $siteId = $params['siteId'];
133
        if (Seomatic::$settings->siteGroupsSeparate) {
134
            /** @var SiteGroup|null $siteGroup */
135
            $siteGroup = Craft::$app->getSites()->getGroupById($groupId);
136
            if ($siteGroup === null) {
137
                throw new NotFoundHttpException(Craft::t('seomatic', 'Sitemap.xml not found for groupId {groupId}', [
138
                    'groupId' => $groupId,
139
                ]));
140
            }
141
            $groupSiteIds = $siteGroup->getSiteIds();
142
        } else {
143
            $groupSiteIds = Craft::$app->getSites()->allSiteIds;
144
        }
145
146
        $dependency = new TagDependency([
147
            'tags' => [
148
                self::GLOBAL_SITEMAP_CACHE_TAG,
149
                self::SITEMAP_INDEX_CACHE_TAG,
150
            ],
151
        ]);
152
153
        return $cache->getOrSet(self::CACHE_KEY . $groupId . '.' . $siteId, function() use ($groupSiteIds, $siteId) {
154
            Craft::info(
155
                'Sitemap index cache miss',
156
                __METHOD__
157
            );
158
            $lines = [];
159
            // Sitemap index XML header and opening tag
160
            $lines[] = '<?xml version="1.0" encoding="UTF-8"?>';
161
            $lines[] = '<?xml-stylesheet type="text/xsl" href="sitemap.xsl"?>';
162
            $lines[] = '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
163
            // One sitemap entry for each MeteBundle
164
            $metaBundles = Seomatic::$plugin->metaBundles->getContentMetaBundlesForSiteId($siteId);
165
            Seomatic::$plugin->metaBundles->pruneVestigialMetaBundles($metaBundles);
166
            /** @var MetaBundle $metaBundle */
167
            foreach ($metaBundles as $metaBundle) {
168
                $sitemapUrls = $metaBundle->metaSitemapVars->sitemapUrls;
169
                // Check to see if robots is `none` or `no index`
170
                $robotsEnabled = true;
171
                if (!empty($metaBundle->metaGlobalVars->robots)) {
172
                    $robotsEnabled = $metaBundle->metaGlobalVars->robots !== 'none' &&
173
                        $metaBundle->metaGlobalVars->robots !== 'noindex';
174
                }
175
                if (Seomatic::$plugin->sitemaps->anyEntryTypeHasSitemapUrls($metaBundle)) {
176
                    $robotsEnabled = true;
177
                    $sitemapUrls = true;
178
                }
179
                // Only add in a sitemap entry if it meets our criteria
180
                if (in_array($metaBundle->sourceSiteId, $groupSiteIds, false)
181
                    && $sitemapUrls
182
                    && $robotsEnabled) {
183
                    // Get all of the elements for this meta bundle type
184
                    $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($metaBundle->sourceBundleType);
185
                    $totalElements = 0;
186
                    $pageCount = 0;
187
188
                    if ($seoElement !== null) {
189
                        // Ensure `null` so that the resulting element query is correct
190
                        if (empty($metaBundle->metaSitemapVars->sitemapLimit)) {
191
                            $metaBundle->metaSitemapVars->sitemapLimit = null;
192
                        }
193
194
                        $totalElements = $seoElement::sitemapElementsQuery($metaBundle)->count();
195
196
                        if ($metaBundle->metaSitemapVars->sitemapLimit && ($totalElements > $metaBundle->metaSitemapVars->sitemapLimit)) {
197
                            $totalElements = $metaBundle->metaSitemapVars->sitemapLimit;
198
                        }
199
200
                        $pageSize = $metaBundle->metaSitemapVars->sitemapPageSize;
201
                        $pageCount = (!empty($pageSize) && $pageSize > 0) ? ceil($totalElements / $pageSize) : 1;
202
                    }
203
204
                    // Only add a sitemap to the sitemap index if there's at least 1 element in the resulting sitemap
205
                    if ($totalElements > 0 && $pageCount > 0) {
206
                        for ($page = 1; $page <= $pageCount; $page++) {
207
                            $sitemapUrl = Seomatic::$plugin->sitemaps->sitemapUrlForBundle(
208
                                $metaBundle->sourceBundleType,
209
                                $metaBundle->sourceHandle,
210
                                $metaBundle->sourceSiteId,
211
                                $pageCount > 1 ? $page : 0 // No paging, if only one page
212
                            );
213
214
                            $lines[] = '<sitemap>';
215
                            $lines[] = '<loc>';
216
                            $lines[] = Html::encode($sitemapUrl);
217
                            $lines[] = '</loc>';
218
219
                            if ($metaBundle->sourceDateUpdated !== null) {
220
                                $lines[] = '<lastmod>';
221
                                $lines[] = $metaBundle->sourceDateUpdated->format(DateTime::W3C);
222
                                $lines[] = '</lastmod>';
223
                            }
224
                            
225
                            $lines[] = '</sitemap>';
226
                        }
227
                    }
228
                }
229
            }
230
            // Custom sitemap entries
231
            $metaBundle = Seomatic::$plugin->metaBundles->getGlobalMetaBundle($siteId, false);
232
            if ($metaBundle !== null) {
233
                $this->addAdditionalSitemapUrls($metaBundle, $siteId, $lines);
234
                $this->addAdditionalSitemaps($metaBundle, $siteId, $lines);
235
            }
236
            // Sitemap index closing tag
237
            $lines[] = '</sitemapindex>';
238
239
            return implode('', $lines);
240
        }, Seomatic::$cacheDuration, $dependency);
241
    }
242
243
    /**
244
     * Invalidate the sitemap index cache
245
     */
246
    public function invalidateCache()
247
    {
248
        $cache = Craft::$app->getCache();
249
        TagDependency::invalidate($cache, self::SITEMAP_INDEX_CACHE_TAG);
250
        Craft::info(
251
            'Sitemap index cache cleared',
252
            __METHOD__
253
        );
254
    }
255
256
    // Protected Methods
257
    // =========================================================================
258
259
    /**
260
     * Add an additional sitemap to the sitemap index, coming from the global
261
     * meta bundle metaSiteVars->additionalSitemaps
262
     *
263
     * @param MetaBundle $metaBundle
264
     * @param int $groupSiteId
265
     * @param array $lines
266
     *
267
     * @throws Exception
268
     */
269
    protected function addAdditionalSitemaps(MetaBundle $metaBundle, int $groupSiteId, array &$lines)
270
    {
271
        $additionalSitemaps = $metaBundle->metaSiteVars->additionalSitemaps;
272
        $additionalSitemaps = empty($additionalSitemaps) ? [] : $additionalSitemaps;
273
        // Allow plugins/modules to add custom URLs
274
        $event = new RegisterSitemapsEvent([
275
            'sitemaps' => $additionalSitemaps,
276
            'siteId' => $groupSiteId,
277
        ]);
278
        Event::trigger(SitemapIndexTemplate::class, SitemapIndexTemplate::EVENT_REGISTER_SITEMAPS, $event);
279
        $additionalSitemaps = array_filter($event->sitemaps);
280
        // Output the sitemap index
281
        if (!empty($additionalSitemaps)) {
282
            foreach ($additionalSitemaps as $additionalSitemap) {
283
                if (!empty($additionalSitemap['loc'])) {
284
                    $loc = MetaValueHelper::parseString($additionalSitemap['loc']);
285
                    $lines[] = '<sitemap>';
286
                    $lines[] = '<loc>';
287
                    $lines[] = Html::encode($loc);
288
                    $lines[] = '</loc>';
289
                    // Find the most recent date
290
                    $dateUpdated = !empty($additionalSitemap['lastmod'])
291
                        ? $additionalSitemap['lastmod']
292
                        : new DateTime();
293
                    $lines[] = '<lastmod>';
294
                    $lines[] = $dateUpdated->format(DateTime::W3C);
295
                    $lines[] = '</lastmod>';
296
                    $lines[] = '</sitemap>';
297
                }
298
            }
299
        }
300
    }
301
302
    /**
303
     * Add an additional "custom" sitemap to the sitemap index, with URLs coming from
304
     * the global meta bundle metaSiteVars->additionalSitemapUrls
305
     *
306
     * @param MetaBundle $metaBundle
307
     * @param int $groupSiteId
308
     * @param array $lines
309
     *
310
     * @throws Exception
311
     */
312
    protected function addAdditionalSitemapUrls(MetaBundle $metaBundle, int $groupSiteId, array &$lines)
313
    {
314
        $additionalSitemapUrls = $metaBundle->metaSiteVars->additionalSitemapUrls;
315
        $additionalSitemapUrls = empty($additionalSitemapUrls) ? [] : $additionalSitemapUrls;
316
        // Allow plugins/modules to add custom URLs
317
        $event = new RegisterSitemapUrlsEvent([
318
            'sitemaps' => $additionalSitemapUrls,
319
            'siteId' => $groupSiteId,
320
        ]);
321
        Event::trigger(SitemapCustomTemplate::class, SitemapCustomTemplate::EVENT_REGISTER_SITEMAP_URLS, $event);
322
        $additionalSitemapUrls = array_filter($event->sitemaps);
323
        // Output the sitemap index
324
        if (!empty($additionalSitemapUrls)) {
325
            $sitemapUrl = Seomatic::$plugin->sitemaps->sitemapCustomUrlForSiteId(
326
                $groupSiteId
327
            );
328
            $lines[] = '<sitemap>';
329
            $lines[] = '<loc>';
330
            $lines[] = Html::encode($sitemapUrl);
331
            $lines[] = '</loc>';
332
            // Find the most recent date
333
            $dateUpdated = $metaBundle->metaSiteVars->additionalSitemapUrlsDateUpdated
334
                ?? new DateTime();
335
            foreach ($additionalSitemapUrls as $additionalSitemapUrl) {
336
                if (!empty($additionalSitemapUrl['lastmod'])) {
337
                    if ($additionalSitemapUrl['lastmod'] > $dateUpdated) {
338
                        $dateUpdated = $additionalSitemapUrl['lastmod'];
339
                    }
340
                }
341
            }
342
            if ($dateUpdated !== null) {
343
                $lines[] = '<lastmod>';
344
                $lines[] = $dateUpdated->format(DateTime::W3C);
345
                $lines[] = '</lastmod>';
346
            }
347
            $lines[] = '</sitemap>';
348
        }
349
    }
350
}
351