Passed
Push — v3 ( db5cc8...c32422 )
by Andrew
20:34 queued 30s
created

SitemapIndexTemplate::render()   D

Complexity

Conditions 22
Paths 3

Size

Total Lines 112
Code Lines 71

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 506

Importance

Changes 0
Metric Value
eloc 71
c 0
b 0
f 0
dl 0
loc 112
ccs 0
cts 74
cp 0
rs 4.1666
cc 22
nc 3
nop 1
crap 506

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