SitemapIndexTemplate::invalidateCache()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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