Sitemaps::submitSitemapIndex()   B
last analyzed

Complexity

Conditions 9
Paths 8

Size

Total Lines 29
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
eloc 21
dl 0
loc 29
ccs 0
cts 21
cp 0
rs 8.0555
c 0
b 0
f 0
cc 9
nc 8
nop 0
crap 90
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\services;
13
14
use Craft;
15
use craft\base\Component;
16
use craft\base\Element;
17
use craft\base\ElementInterface;
18
use craft\errors\SiteNotFoundException;
19
use craft\events\RegisterUrlRulesEvent;
20
use craft\models\Section;
21
use craft\web\UrlManager;
22
use nystudio107\seomatic\base\FrontendTemplate;
23
use nystudio107\seomatic\base\SitemapInterface;
24
use nystudio107\seomatic\helpers\UrlHelper;
25
use nystudio107\seomatic\models\FrontendTemplateContainer;
26
use nystudio107\seomatic\models\MetaBundle;
27
use nystudio107\seomatic\models\SitemapCustomTemplate;
28
use nystudio107\seomatic\models\SitemapIndexTemplate;
29
use nystudio107\seomatic\models\SitemapTemplate;
30
use nystudio107\seomatic\Seomatic;
31
use yii\base\Event;
32
use yii\base\Exception;
33
use yii\base\InvalidConfigException;
34
use yii\caching\TagDependency;
35
36
/**
37
 * @author    nystudio107
38
 * @package   Seomatic
39
 * @since     3.0.0
40
 */
41
class Sitemaps extends Component implements SitemapInterface
42
{
43
    // Constants
44
    // =========================================================================
45
46
    const SEOMATIC_SITEMAPINDEX_CONTAINER = Seomatic::SEOMATIC_HANDLE . SitemapIndexTemplate::TEMPLATE_TYPE;
47
48
    const SEOMATIC_SITEMAP_CONTAINER = Seomatic::SEOMATIC_HANDLE . SitemapTemplate::TEMPLATE_TYPE;
49
50
    const SEOMATIC_SITEMAPCUSTOM_CONTAINER = Seomatic::SEOMATIC_HANDLE . SitemapCustomTemplate::TEMPLATE_TYPE;
51
52
    const SEARCH_ENGINE_SUBMISSION_URLS = [
53
    ];
54
55
    // Protected Properties
56
    // =========================================================================
57
58
    /**
59
     * @var FrontendTemplateContainer
60
     */
61
    protected $sitemapTemplateContainer;
62
63
    // Public Methods
64
    // =========================================================================
65
66
    /**
67
     *
68
     */
69
    public function init()
70
    {
71
        parent::init();
72
    }
73
74
    /**
75
     * Load in the sitemap frontend template containers
76
     */
77
    public function loadSitemapContainers()
78
    {
79
        if (Seomatic::$settings->sitemapsEnabled) {
80
            $this->sitemapTemplateContainer = FrontendTemplateContainer::create();
81
            // The Sitemap Index
82
            $sitemapIndexTemplate = SitemapIndexTemplate::create();
83
            $this->sitemapTemplateContainer->addData($sitemapIndexTemplate, self::SEOMATIC_SITEMAPINDEX_CONTAINER);
84
            // A custom sitemap
85
            $sitemapCustomTemplate = SitemapCustomTemplate::create();
86
            $this->sitemapTemplateContainer->addData($sitemapCustomTemplate, self::SEOMATIC_SITEMAPCUSTOM_CONTAINER);
87
            // A generic sitemap
88
            $sitemapTemplate = SitemapTemplate::create();
89
            $this->sitemapTemplateContainer->addData($sitemapTemplate, self::SEOMATIC_SITEMAP_CONTAINER);
90
            // Handler: UrlManager::EVENT_REGISTER_SITE_URL_RULES
91
            Event::on(
92
                UrlManager::class,
93
                UrlManager::EVENT_REGISTER_SITE_URL_RULES,
94
                function(RegisterUrlRulesEvent $event) {
95
                    Craft::debug(
96
                        'UrlManager::EVENT_REGISTER_SITE_URL_RULES',
97
                        __METHOD__
98
                    );
99
                    // Register our sitemap routes
100
                    $event->rules = array_merge(
101
                        $event->rules,
102
                        $this->sitemapRouteRules()
103
                    );
104
                }
105
            );
106
        }
107
    }
108
109
    /**
110
     * @return array
111
     */
112
    public function sitemapRouteRules(): array
113
    {
114
        $rules = [];
115
        $groups = Craft::$app->getSites()->getAllGroups();
116
        $groupId = $groups[0]->id;
0 ignored issues
show
Unused Code introduced by
The assignment to $groupId is dead and can be removed.
Loading history...
117
        $currentSite = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $currentSite is dead and can be removed.
Loading history...
118
        try {
119
            $currentSite = Craft::$app->getSites()->getCurrentSite();
120
        } catch (SiteNotFoundException $e) {
121
            Craft::error($e->getMessage(), __METHOD__);
122
        }
123
        if ($currentSite) {
124
            try {
125
                $groupId = $currentSite->getGroup()->id;
126
            } catch (InvalidConfigException $e) {
127
                Craft::error($e->getMessage(), __METHOD__);
128
            }
129
        }
130
        // Add the route to redirect sitemap.xml to the actual sitemap
131
        $route =
132
            Seomatic::$plugin->handle
133
            . '/'
134
            . 'sitemap'
135
            . '/'
136
            . 'sitemap-index-redirect';
137
        $rules['sitemap.xml'] = [
138
            'route' => $route,
139
        ];
140
        // Add the route for the sitemap.xsl styles
141
        $route =
142
            Seomatic::$plugin->handle
143
            . '/'
144
            . 'sitemap'
145
            . '/'
146
            . 'sitemap-styles';
147
        $rules['sitemap.xsl'] = [
148
            'route' => $route,
149
        ];
150
        // Add the route for the sitemap-empty.xsl styles
151
        $route =
152
            Seomatic::$plugin->handle
153
            . '/'
154
            . 'sitemap'
155
            . '/'
156
            . 'sitemap-empty-styles';
157
        $rules['sitemap-empty.xsl'] = [
158
            'route' => $route,
159
        ];
160
        // Add all of the frontend container routes
161
        foreach ($this->sitemapTemplateContainer->data as $sitemapTemplate) {
162
            /** @var FrontendTemplate $sitemapTemplate */
163
            $rules = array_merge(
164
                $rules,
165
                $sitemapTemplate->routeRules()
166
            );
167
        }
168
169
        return $rules;
170
    }
171
172
    /**
173
     * See if any of the entry types have robots enable and sitemap urls enabled
174
     *
175
     * @param MetaBundle $metaBundle
176
     * @return bool
177
     */
178
    public function anyEntryTypeHasSitemapUrls(MetaBundle $metaBundle): bool
179
    {
180
        $result = false;
181
        $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($metaBundle->sourceBundleType);
182
        if ($seoElement) {
183
            if (!empty($seoElement::typeMenuFromHandle($metaBundle->sourceHandle))) {
184
                /** @var Section|null $section */
185
                $section = $seoElement::sourceModelFromHandle($metaBundle->sourceHandle);
186
                if ($section !== null) {
187
                    $entryTypes = $section->getEntryTypes();
188
                    // Fetch each meta bundle for each entry type to see if _any_ of them have sitemap URLs
189
                    foreach ($entryTypes as $entryType) {
190
                        $entryTypeBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceId(
191
                            $metaBundle->sourceBundleType,
192
                            $metaBundle->sourceId,
193
                            $metaBundle->sourceSiteId,
194
                            $entryType->id
195
                        );
196
                        if ($entryTypeBundle) {
197
                            $robotsEnabled = true;
198
                            if (!empty($entryTypeBundle->metaGlobalVars->robots)) {
199
                                $robotsEnabled = $entryTypeBundle->metaGlobalVars->robots !== 'none' &&
200
                                    $entryTypeBundle->metaGlobalVars->robots !== 'noindex';
201
                            }
202
                            if ($entryTypeBundle->metaSitemapVars->sitemapUrls && $robotsEnabled) {
203
                                $result = true;
204
                            }
205
                        }
206
                    }
207
                }
208
            }
209
        }
210
211
        return $result;
212
    }
213
214
    /**
215
     * @param string $template
216
     * @param array $params
217
     *
218
     * @return string
219
     */
220
    public function renderTemplate(string $template, array $params = []): string
221
    {
222
        $html = '';
223
        if (!empty($this->sitemapTemplateContainer->data[$template])) {
224
            /** @var FrontendTemplate $sitemapTemplate */
225
            $sitemapTemplate = $this->sitemapTemplateContainer->data[$template];
226
            $html = $sitemapTemplate->render($params);
227
        }
228
229
        return $html;
230
    }
231
232
    /**
233
     * Submit the sitemap index to the search engine services
234
     */
235
    public function submitSitemapIndex()
236
    {
237
        if (Seomatic::$settings->sitemapsEnabled && Seomatic::$environment === 'live' && Seomatic::$settings->submitSitemaps) {
238
            // Submit the sitemap to each search engine
239
            $searchEngineUrls = self::SEARCH_ENGINE_SUBMISSION_URLS;
240
            // Array is currently empty, but leave the code in place in case submission urls return
241
            /** @phpstan-ignore-next-line */
242
            foreach ($searchEngineUrls as &$url) {
243
                $groups = Craft::$app->getSites()->getAllGroups();
244
                foreach ($groups as $group) {
245
                    $groupSiteIds = $group->getSiteIds();
246
                    if (!empty($groupSiteIds)) {
247
                        $siteId = $groupSiteIds[0];
248
                        $sitemapIndexUrl = $this->sitemapIndexUrlForSiteId($siteId);
249
                        if (!empty($sitemapIndexUrl)) {
250
                            $submissionUrl = $url . urlencode($sitemapIndexUrl);
251
                            // create new guzzle client
252
                            $guzzleClient = Craft::createGuzzleClient(['timeout' => 5, 'connect_timeout' => 5]);
253
                            // Submit the sitemap index to each search engine
254
                            try {
255
                                $guzzleClient->post($submissionUrl);
256
                                Craft::info(
257
                                    'Sitemap index submitted to: ' . $submissionUrl,
258
                                    __METHOD__
259
                                );
260
                            } catch (\Exception $e) {
261
                                Craft::error(
262
                                    'Error submitting sitemap index to: ' . $submissionUrl . ' - ' . $e->getMessage(),
263
                                    __METHOD__
264
                                );
265
                            }
266
                        }
267
                    }
268
                }
269
            }
270
        }
271
    }
272
273
    /**
274
     * Get the URL to the $siteId's sitemap index
275
     *
276
     * @param int|null $siteId
277
     *
278
     * @return string
279
     */
280
    public function sitemapIndexUrlForSiteId(int $siteId = null): string
281
    {
282
        $url = '';
283
        $sites = Craft::$app->getSites();
284
        if ($siteId === null) {
285
            $siteId = $sites->currentSite->id ?? 1;
286
        }
287
        $site = $sites->getSiteById($siteId);
288
        if ($site !== null) {
289
            try {
290
                $url = UrlHelper::siteUrl(
291
                    '/sitemaps-'
292
                    . $site->groupId
293
                    . '-sitemap.xml',
294
                    null,
295
                    null,
296
                    $siteId
297
                );
298
            } catch (Exception $e) {
299
                Craft::error($e->getMessage(), __METHOD__);
300
            }
301
        }
302
303
        return $url;
304
    }
305
306
    /**
307
     * Submit the bundle sitemap to the search engine services
308
     *
309
     * @param ElementInterface $element
310
     */
311
    public function submitSitemapForElement(ElementInterface $element)
312
    {
313
        if (Seomatic::$settings->sitemapsEnabled && Seomatic::$environment === 'live' && Seomatic::$settings->submitSitemaps) {
314
            /** @var Element $element */
315
            list($sourceId, $sourceBundleType, $sourceHandle, $sourceSiteId, $typeId)
316
                = Seomatic::$plugin->metaBundles->getMetaSourceFromElement($element);
317
            // Submit the sitemap to each search engine
318
            $searchEngineUrls = self::SEARCH_ENGINE_SUBMISSION_URLS;
319
            // Array is currently empty, but leave the code in place in case submission urls return
320
            /** @phpstan-ignore-next-line */
321
            foreach ($searchEngineUrls as &$url) {
322
                $sitemapUrl = $this->sitemapUrlForBundle($sourceBundleType, $sourceHandle, $sourceSiteId);
323
                if (!empty($sitemapUrl)) {
324
                    $submissionUrl = $url . urlencode($sitemapUrl);
325
                    // create new guzzle client
326
                    $guzzleClient = Craft::createGuzzleClient(['timeout' => 5, 'connect_timeout' => 5]);
327
                    // Submit the sitemap index to each search engine
328
                    try {
329
                        $guzzleClient->post($submissionUrl);
330
                        Craft::info(
331
                            'Sitemap index submitted to: ' . $submissionUrl,
332
                            __METHOD__
333
                        );
334
                    } catch (\Exception $e) {
335
                        Craft::error(
336
                            'Error submitting sitemap index to: ' . $submissionUrl . ' - ' . $e->getMessage(),
337
                            __METHOD__
338
                        );
339
                    }
340
                }
341
            }
342
        }
343
    }
344
345
    /**
346
     * @param string $sourceBundleType
347
     * @param string $sourceHandle
348
     * @param int|null $siteId
349
     *
350
     * @return string
351
     */
352
    public function sitemapUrlForBundle(string $sourceBundleType, string $sourceHandle, int $siteId = null, int $page = null): string
353
    {
354
        $url = '';
355
        $sites = Craft::$app->getSites();
356
        if ($siteId === null) {
357
            $siteId = $sites->currentSite->id ?? 1;
358
        }
359
        $site = $sites->getSiteById($siteId);
360
        $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle(
361
            $sourceBundleType,
362
            $sourceHandle,
363
            $siteId
364
        );
365
        if ($site && $metaBundle && $metaBundle->metaSitemapVars->sitemapUrls) {
366
            try {
367
                $url = UrlHelper::siteUrl(
368
                    '/sitemaps-'
369
                    . $site->groupId
370
                    . '-'
371
                    . $metaBundle->sourceBundleType
372
                    . '-'
373
                    . $metaBundle->sourceHandle
374
                    . '-'
375
                    . $metaBundle->sourceSiteId
376
                    . '-sitemap'
377
                    . (!empty($page) ? '-p' . $page : '')
378
                    . '.xml',
379
                    null,
380
                    null,
381
                    $siteId
382
                );
383
            } catch (Exception $e) {
384
                Craft::error($e->getMessage(), __METHOD__);
385
            }
386
        }
387
388
        return $url;
389
    }
390
391
    /**
392
     * Submit the bundle sitemap to the search engine services
393
     *
394
     * @param int $siteId
395
     */
396
    public function submitCustomSitemap(int $siteId)
397
    {
398
        if (Seomatic::$settings->sitemapsEnabled && Seomatic::$environment === 'live' && Seomatic::$settings->submitSitemaps) {
399
            // Submit the sitemap to each search engine
400
            $searchEngineUrls = self::SEARCH_ENGINE_SUBMISSION_URLS;
401
            // Array is currently empty, but leave the code in place in case submission urls return
402
            /** @phpstan-ignore-next-line */
403
            foreach ($searchEngineUrls as &$url) {
404
                $sitemapUrl = $this->sitemapCustomUrlForSiteId($siteId);
405
                if (!empty($sitemapUrl)) {
406
                    $submissionUrl = $url . urlencode($sitemapUrl);
407
                    // create new guzzle client
408
                    $guzzleClient = Craft::createGuzzleClient(['timeout' => 5, 'connect_timeout' => 5]);
409
                    // Submit the sitemap index to each search engine
410
                    try {
411
                        $guzzleClient->post($submissionUrl);
412
                        Craft::info(
413
                            'Sitemap Custom submitted to: ' . $submissionUrl,
414
                            __METHOD__
415
                        );
416
                    } catch (\Exception $e) {
417
                        Craft::error(
418
                            'Error submitting sitemap index to: ' . $submissionUrl . ' - ' . $e->getMessage(),
419
                            __METHOD__
420
                        );
421
                    }
422
                }
423
            }
424
        }
425
    }
426
427
    /**
428
     * @param int|null $siteId
429
     *
430
     * @return string
431
     */
432
    public function sitemapCustomUrlForSiteId(int $siteId = null)
433
    {
434
        $url = '';
435
        $sites = Craft::$app->getSites();
436
        if ($siteId === null) {
437
            $siteId = $sites->currentSite->id ?? 1;
438
        }
439
        $site = $sites->getSiteById($siteId);
440
        if ($site) {
441
            try {
442
                $url = UrlHelper::siteUrl(
443
                    '/sitemaps-'
444
                    . $site->groupId
445
                    . '-'
446
                    . SitemapCustomTemplate::CUSTOM_SCOPE
447
                    . '-'
448
                    . SitemapCustomTemplate::CUSTOM_HANDLE
449
                    . '-'
450
                    . $siteId
451
                    . '-sitemap.xml',
452
                    null,
453
                    null,
454
                    $siteId
455
                );
456
            } catch (Exception $e) {
457
                Craft::error($e->getMessage(), __METHOD__);
458
            }
459
        }
460
461
        return $url;
462
    }
463
464
    /**
465
     * Return all of the sitemap indexes the current group of sites
466
     *
467
     * @return string
468
     */
469
    public function sitemapIndex(): string
470
    {
471
        $result = '';
472
        $sites = [];
473
        // If sitemaps aren't enabled globally, return nothing for the sitemap index
474
        if (!Seomatic::$settings->sitemapsEnabled) {
475
            return '';
476
        }
477
        if (Seomatic::$settings->siteGroupsSeparate) {
478
            // Get only the sites that are in the current site's group
479
            try {
480
                $siteGroup = Craft::$app->getSites()->getCurrentSite()->getGroup();
481
            } catch (InvalidConfigException $e) {
482
                $siteGroup = null;
483
                Craft::error($e->getMessage(), __METHOD__);
484
            }
485
            // If we can't get a group, just use the current site
486
            if ($siteGroup === null) {
487
                $sites = [Craft::$app->getSites()->getCurrentSite()];
488
            } else {
489
                $sites = $siteGroup->getSites();
490
            }
491
        } else {
492
            $sites = Craft::$app->getSites()->getAllSites();
493
        }
494
495
        foreach ($sites as $site) {
496
            $result .= 'sitemap: ' . $this->sitemapIndexUrlForSiteId($site->id) . PHP_EOL;
497
        }
498
499
        return rtrim($result, PHP_EOL);
500
    }
501
502
    /**
503
     * Invalidate all of the sitemap caches
504
     */
505
    public function invalidateCaches()
506
    {
507
        $cache = Craft::$app->getCache();
508
        TagDependency::invalidate($cache, self::GLOBAL_SITEMAP_CACHE_TAG);
509
        Craft::info(
510
            'All sitemap caches cleared',
511
            __METHOD__
512
        );
513
    }
514
515
    /**
516
     * Invalidate the sitemap cache passed in $handle
517
     *
518
     * @param string $handle
519
     * @param int|null $siteId
520
     * @param string $type
521
     * @param bool $invalidateCache
522
     */
523
    public function invalidateSitemapCache(string $handle, ?int $siteId, string $type, bool $invalidateCache = true)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

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

523
    public function invalidateSitemapCache(string $handle, ?int $siteId, /** @scrutinizer ignore-unused */ string $type, bool $invalidateCache = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $invalidateCache is not used and could be removed. ( Ignorable by Annotation )

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

523
    public function invalidateSitemapCache(string $handle, ?int $siteId, string $type, /** @scrutinizer ignore-unused */ bool $invalidateCache = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
524
    {
525
        // Always just invalidate the sitemap cache now, since we're doing paginated sitemaps
526
        $cache = Craft::$app->getCache();
527
        TagDependency::invalidate($cache, SitemapTemplate::SITEMAP_CACHE_TAG . $handle . $siteId);
528
        Craft::info(
529
            'Sitemap cache cleared: ' . $handle,
530
            __METHOD__
531
        );
532
    }
533
534
    /**
535
     * Invalidate the sitemap index cache
536
     */
537
    public function invalidateSitemapIndexCache()
538
    {
539
        $cache = Craft::$app->getCache();
540
        TagDependency::invalidate($cache, SitemapIndexTemplate::SITEMAP_INDEX_CACHE_TAG);
541
        Craft::info(
542
            'Sitemap index cache cleared',
543
            __METHOD__
544
        );
545
    }
546
}
547