Sitemap::combineFieldSettings()   B
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 44
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 27
c 0
b 0
f 0
dl 0
loc 44
rs 8.8657
cc 6
nc 5
nop 2
1
<?php
2
3
namespace nystudio107\seomatic\helpers;
4
5
use benf\neo\elements\Block as NeoBlock;
6
use Craft;
7
use craft\base\Element;
8
use craft\base\Event;
9
use craft\console\Application as ConsoleApplication;
10
use craft\db\Paginator;
11
use craft\elements\Asset;
12
use craft\elements\Entry;
13
use craft\errors\SiteNotFoundException;
14
use craft\fields\Assets as AssetsField;
15
use DateTime;
16
use nystudio107\seomatic\base\SeoElementInterface;
17
use nystudio107\seomatic\events\IncludeSitemapEntryEvent;
18
use nystudio107\seomatic\events\ModifySitemapQueryEvent;
19
use nystudio107\seomatic\fields\SeoSettings;
20
use nystudio107\seomatic\helpers\EagerLoad as EagerLoadHelper;
21
use nystudio107\seomatic\helpers\Field as FieldHelper;
22
use nystudio107\seomatic\models\MetaBundle;
23
use nystudio107\seomatic\models\SitemapTemplate;
24
use nystudio107\seomatic\Seomatic;
25
use Throwable;
26
use yii\base\Exception;
27
use yii\helpers\Html;
28
use function array_intersect_key;
29
use function count;
30
use function in_array;
31
32
/**
33
 * @author    nystudio107
34
 * @package   Seomatic
35
 * @since     3.4.18
36
 */
37
class Sitemap
38
{
39
    /**
40
     * @event IncludeSitemapEntryEvent The event that is triggered when an entry is
41
     * about to be included in a sitemap
42
     *
43
     * ---
44
     * ```php
45
     * use nystudio107\seomatic\events\IncludeSitemapEntryEvent;
46
     * use nystudio107\seomatic\helpers\Sitemap;
47
     * use yii\base\Event;
48
     * Event::on(Sitemap::class, Sitemap::EVENT_INCLUDE_SITEMAP_ENTRY, function(IncludeSitemapEntryEvent $e) {
49
     *     $e->include = false;
50
     * });
51
     * ```
52
     */
53
    public const EVENT_INCLUDE_SITEMAP_ENTRY = 'includeSitemapEntry';
54
55
    /**
56
     * @event ModifySitemapQueryEvent Allows the modification of the element query used to generate a sitemap
57
     *
58
     * ---
59
     * ```php
60
     * use nystudio107\seomatic\events\ModifySitemapQueryEvent;
61
     * use nystudio107\seomatic\helpers\Sitemap;
62
     * use yii\base\Event;
63
     * Event::on(Sitemap::class, Sitemap::EVENT_MODIFY_SITEMAP_QUERY, function(ModifySitemapQueryEvent $e) {
64
     *     $e->query->limit(10);
65
     * });
66
     * ```
67
     */
68
    public const EVENT_MODIFY_SITEMAP_QUERY = 'modifySitemapQuery';
69
70
    /**
71
     * @const The number of assets to return in a single paginated query
72
     */
73
    public const SITEMAP_QUERY_PAGE_SIZE = 100;
74
75
    /**
76
     * Generate a sitemap with the passed in $params
77
     *
78
     * @param array $params
79
     * @return string
80
     * @throws SiteNotFoundException
81
     */
82
    public static function generateSitemap(array $params): ?string
83
    {
84
        $groupId = $params['groupId'];
85
        $type = $params['type'];
86
        $handle = $params['handle'];
87
        $siteId = $params['siteId'];
88
        $page = $params['page'];
89
90
        // Get an array of site ids for this site group
91
        $groupSiteIds = [];
92
93
        if (Seomatic::$settings->siteGroupsSeparate) {
94
            if (empty($groupId)) {
95
                try {
96
                    $thisSite = Craft::$app->getSites()->getSiteById($siteId);
97
                    if ($thisSite !== null) {
98
                        $group = $thisSite->getGroup();
99
                        $groupId = $group->id;
100
                    }
101
                } catch (Throwable $e) {
102
                    Craft::error($e->getMessage(), __METHOD__);
103
                }
104
            }
105
            $siteGroup = Craft::$app->getSites()->getGroupById($groupId);
106
            if ($siteGroup !== null) {
107
                $groupSiteIds = $siteGroup->getSiteIds();
108
            }
109
        }
110
111
        if (empty($groupSiteIds)) {
112
            $groupSiteIds = Craft::$app->getSites()->allSiteIds;
113
        }
114
115
        $lines = [];
116
        // Sitemap index XML header and opening tag
117
        $lines[] = '<?xml version="1.0" encoding="UTF-8"?>';
118
        $lines[] = '<?xml-stylesheet type="text/xsl" href="sitemap.xsl"?>';
119
        // One sitemap entry for each element
120
        $metaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceHandle(
121
            $type,
122
            $handle,
123
            $siteId
124
        );
125
        // If it doesn't exist, exit
126
        if ($metaBundle === null) {
127
            return null;
128
        }
129
        $multiSite = count($metaBundle->sourceAltSiteSettings) > 1;
130
        $totalElements = null;
131
        $urlsetLine = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
132
        if ($metaBundle->metaSitemapVars->sitemapAssets) {
133
            $urlsetLine .= ' xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"';
134
            $urlsetLine .= ' xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"';
135
        }
136
        if ($multiSite) {
137
            $urlsetLine .= ' xmlns:xhtml="http://www.w3.org/1999/xhtml"';
138
        }
139
        if ((bool)$metaBundle->metaSitemapVars->newsSitemap) {
140
            $urlsetLine .= ' xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"';
141
        }
142
        $urlsetLine .= '>';
143
        $lines[] = $urlsetLine;
144
145
        // Get all of the elements for this meta bundle type
146
        $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($metaBundle->sourceBundleType);
147
148
        if ($seoElement !== null) {
149
            // Ensure `null` so that the resulting element query is correct
150
            if (empty($metaBundle->metaSitemapVars->sitemapLimit)) {
151
                $metaBundle->metaSitemapVars->sitemapLimit = null;
152
            }
153
            $totalElements = self::getTotalElementsInSitemap($seoElement, $metaBundle);
154
        }
155
156
        // If no elements exist, just exit
157
        if (!$totalElements) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $totalElements of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
158
            return null;
159
        }
160
161
        // Stash the sitemap attributes so they can be modified on a per-entry basis
162
        $stashedSitemapAttrs = $metaBundle->metaSitemapVars->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

162
        /** @scrutinizer ignore-call */ 
163
        $stashedSitemapAttrs = $metaBundle->metaSitemapVars->getAttributes();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
163
        $stashedGlobalVarsAttrs = $metaBundle->metaGlobalVars->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

163
        /** @scrutinizer ignore-call */ 
164
        $stashedGlobalVarsAttrs = $metaBundle->metaGlobalVars->getAttributes();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
164
        // Use craft\db\Paginator to paginate the results so we don't exceed any memory limits
165
        // See batch() and each() discussion here: https://github.com/yiisoft/yii2/issues/8420
166
        // and here: https://github.com/craftcms/cms/issues/7338
167
168
        // Allow listeners to modify the query before we use it
169
        $elementQuery = $seoElement::sitemapElementsQuery($metaBundle);
170
        $event = new ModifySitemapQueryEvent([
171
            'query' => $elementQuery,
172
            'metaBundle' => $metaBundle,
173
        ]);
174
        Event::trigger(self::class, self::EVENT_MODIFY_SITEMAP_QUERY, $event);
175
176
        $sitemapPageSize = $metaBundle->metaSitemapVars->sitemapPageSize;
177
        $elementQuery->limit($metaBundle->metaSitemapVars->sitemapLimit ?? null);
178
179
        // Eager load assets & relations
180
        if ($metaBundle->metaSitemapVars->sitemapAssets || $metaBundle->metaSitemapVars->sitemapFiles) {
181
            $elementQuery->with(EagerLoadHelper::sitemapEagerLoadMap($metaBundle));
182
        }
183
184
        // If this is not a paged sitemap, go through full results
185
        if (empty($sitemapPageSize)) {
186
            $pagedSitemap = false;
187
            $paginator = new Paginator($elementQuery, [
188
                'pageSize' => self::SITEMAP_QUERY_PAGE_SIZE,
189
            ]);
190
            $elements = $paginator->getPageResults();
191
        } else {
192
            $sitemapPage = empty($page) ? 1 : $page;
193
            $pagedSitemap = true;
194
            $elementQuery->limit($sitemapPageSize);
195
            $elementQuery->offset(($sitemapPage - 1) * $sitemapPageSize);
196
            $elements = $elementQuery->all();
197
            $totalElements = $sitemapPageSize;
198
            $paginator = new Paginator($elementQuery, [
199
                'pageSize' => $sitemapPageSize,
200
            ]);
201
        }
202
203
        $currentElement = 1;
204
205
        do {
206
            if (Craft::$app instanceof ConsoleApplication) {
207
                if ($pagedSitemap) {
208
                    $message = sprintf('Query %d elements', $sitemapPageSize);
209
                } else {
210
                    $message = sprintf('Query %d / %d - elements: %d',
211
                        $paginator->getCurrentPage(),
212
                        $paginator->getTotalPages(),
213
                        $paginator->getTotalResults());
214
                }
215
                echo $message . PHP_EOL;
216
            }
217
            /** @var Element $element */
218
            foreach ($elements as $element) {
219
                // Output some info if this is a console app
220
                if (Craft::$app instanceof ConsoleApplication) {
221
                    echo "Processing element {$currentElement}/{$totalElements} - {$element->title}" . PHP_EOL;
222
                }
223
224
                $metaBundle->metaSitemapVars->setAttributes($stashedSitemapAttrs, false);
225
                $metaBundle->metaGlobalVars->setAttributes($stashedGlobalVarsAttrs, false);
226
                // Combine in any per-entry type settings
227
                self::combineEntryTypeSettings($seoElement, $element, $metaBundle);
228
                // Make sure this entry isn't disabled
229
                self::combineFieldSettings($element, $metaBundle);
230
                // Special case for the __home__ URI
231
                $path = ($element->uri === '__home__') ? '' : $element->uri;
232
                // Check to see if robots is `none` or `no index`
233
                $robotsEnabled = true;
234
                if (!empty($metaBundle->metaGlobalVars->robots)) {
235
                    $robotsEnabled = $metaBundle->metaGlobalVars->robots !== 'none' &&
236
                        $metaBundle->metaGlobalVars->robots !== 'noindex';
237
                }
238
                $enabled = $element->getEnabledForSite($metaBundle->sourceSiteId);
239
                $enabled = $enabled && $path !== null && $metaBundle->metaSitemapVars->sitemapUrls && $robotsEnabled;
240
                $event = new IncludeSitemapEntryEvent([
241
                    'include' => $enabled,
242
                    'element' => $element,
243
                    'metaBundle' => $metaBundle,
244
                ]);
245
                Event::trigger(self::class, self::EVENT_INCLUDE_SITEMAP_ENTRY, $event);
246
                // Only add in a sitemap entry if it meets our criteria
247
                if ($event->include) {
248
                    // Get the url and canonicalUrl
249
                    try {
250
                        $url = UrlHelper::siteUrl($path, null, null, $metaBundle->sourceSiteId);
251
                    } catch (Exception $e) {
252
                        $url = '';
253
                    }
254
                    $url = UrlHelper::absoluteUrlWithProtocol($url);
255
                    if (Seomatic::$settings->excludeNonCanonicalUrls) {
256
                        Seomatic::$matchedElement = $element;
257
                        MetaValue::cache();
258
                        $path = $metaBundle->metaGlobalVars->parsedValue('canonicalUrl');
259
                        try {
260
                            $canonicalUrl = UrlHelper::siteUrl($path, null, null, $metaBundle->sourceSiteId);
261
                        } catch (Exception $e) {
262
                            $canonicalUrl = '';
263
                        }
264
                        $canonicalUrl = UrlHelper::absoluteUrlWithProtocol($canonicalUrl);
265
                        if ($url !== $canonicalUrl) {
266
                            Craft::info("Excluding URL: {$url} from the sitemap because it does not match the Canonical URL: {$canonicalUrl} - " . $metaBundle->metaGlobalVars->canonicalUrl . " - " . $element->uri);
0 ignored issues
show
Bug introduced by
Are you sure $metaBundle->metaGlobalVars->canonicalUrl of type object|string can be used in concatenation? ( Ignorable by Annotation )

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

266
                            Craft::info("Excluding URL: {$url} from the sitemap because it does not match the Canonical URL: {$canonicalUrl} - " . /** @scrutinizer ignore-type */ $metaBundle->metaGlobalVars->canonicalUrl . " - " . $element->uri);
Loading history...
267
                            continue;
268
                        }
269
                    }
270
                    $dateUpdated = $element->dateUpdated ?? $element->dateCreated ?? new DateTime();
271
                    $lines[] = '<url>';
272
                    // Standard sitemap key/values
273
                    $lines[] = '<loc>';
274
                    $lines[] = self::encodeSitemapEntity($url);
275
                    $lines[] = '</loc>';
276
                    $lines[] = '<lastmod>';
277
                    $lines[] = $dateUpdated->format(DateTime::W3C);
278
                    $lines[] = '</lastmod>';
279
                    $lines[] = '<changefreq>';
280
                    $lines[] = $metaBundle->metaSitemapVars->sitemapChangeFreq;
281
                    $lines[] = '</changefreq>';
282
                    $lines[] = '<priority>';
283
                    $lines[] = $metaBundle->metaSitemapVars->sitemapPriority;
284
                    $lines[] = '</priority>';
285
                    // Handle alternate URLs if this is multi-site
286
                    if ($multiSite && $metaBundle->metaSitemapVars->sitemapAltLinks) {
287
                        $primarySiteId = Craft::$app->getSites()->getPrimarySite()->id;
288
                        foreach ($metaBundle->sourceAltSiteSettings as $altSiteSettings) {
289
                            if (in_array($altSiteSettings['siteId'], $groupSiteIds, false) && SiteHelper::siteEnabledWithUrls($altSiteSettings['siteId'])) {
290
                                $altElement = null;
291
                                if ($seoElement !== null) {
292
                                    /** @var Element $altElement */
293
                                    $altElement = $seoElement::sitemapAltElement(
294
                                        $metaBundle,
295
                                        $element->id,
296
                                        $altSiteSettings['siteId']
297
                                    );
298
                                }
299
                                // Make sure to only include the `hreflang` if the element exists,
300
                                // and sitemaps are on for it
301
                                if (Seomatic::$settings->addHrefLang && $altElement && $altElement->url) {
302
                                    list($altSourceId, $altSourceBundleType, $altSourceHandle, $altSourceSiteId, $altTypeId)
303
                                        = Seomatic::$plugin->metaBundles->getMetaSourceFromElement($altElement);
304
                                    $altMetaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceId(
305
                                        $altSourceBundleType,
306
                                        $altSourceId,
307
                                        $altSourceSiteId
308
                                    );
309
                                    if ($altMetaBundle) {
310
                                        $altEnabled = $altElement->getEnabledForSite($altMetaBundle->sourceSiteId);
311
                                        // Make sure this entry isn't disabled
312
                                        self::combineFieldSettings($altElement, $altMetaBundle);
313
                                        if ($altEnabled && $altMetaBundle->metaSitemapVars->sitemapUrls) {
314
                                            try {
315
                                                $altUrl = UrlHelper::siteUrl($altElement->url, null, null, $altSourceId);
316
                                            } catch (Exception $e) {
317
                                                $altUrl = $altElement->url;
318
                                            }
319
                                            $altUrl = UrlHelper::absoluteUrlWithProtocol($altUrl);
320
                                            // If this is the primary site, add it as x-default, too
321
                                            if ($primarySiteId === $altSourceSiteId && Seomatic::$settings->addXDefaultHrefLang) {
322
                                                $lines[] = '<xhtml:link rel="alternate"'
323
                                                    . ' hreflang="x-default"'
324
                                                    . ' href="' . self::encodeSitemapEntity($altUrl) . '"'
325
                                                    . ' />';
326
                                            }
327
                                            $lines[] = '<xhtml:link rel="alternate"'
328
                                                . ' hreflang="' . $altSiteSettings['language'] . '"'
329
                                                . ' href="' . self::encodeSitemapEntity($altUrl) . '"'
330
                                                . ' />';
331
                                        }
332
                                    }
333
                                }
334
                            }
335
                        }
336
                    }
337
                    // Handle news sitemaps https://developers.google.com/search/docs/crawling-indexing/sitemaps/news-sitemap
338
                    if ((bool)$metaBundle->metaSitemapVars->newsSitemap) {
339
                        $now = new DateTime();
340
                        $interval = $now->diff($dateUpdated);
341
                        if ($interval->days <= 2) {
342
                            $language = strtolower($element->getLanguage());
343
                            if (!str_starts_with($language, 'zh')) {
344
                                $language = substr($language, 0, 2);
345
                            }
346
                            $lines[] = '<news:news>';
347
                            $lines[] = '<news:publication>';
348
                            $lines[] = '<news:name>' . self::encodeSitemapEntity($metaBundle->metaSitemapVars->newsPublicationName) . '</news:name>';
349
                            $lines[] = '<news:language>' . $language . '</news:language>';
350
                            $lines[] = '</news:publication>';
351
                            $lines[] = '<news:publication_date>' . $dateUpdated->format(DateTime::W3C) . '</news:publication_date>';
352
                            $lines[] = '<news:title>' . self::encodeSitemapEntity($element->title) . '</news:title>';
353
                            $lines[] = '</news:news>';
354
                        }
355
                    }
356
                    // Handle any Assets
357
                    if ($metaBundle->metaSitemapVars->sitemapAssets) {
358
                        // Regular Assets fields
359
                        $assetFields = FieldHelper::fieldsOfTypeFromElement(
360
                            $element,
361
                            FieldHelper::ASSET_FIELD_CLASS_KEY,
362
                            true
363
                        );
364
                        foreach ($assetFields as $assetField) {
365
                            $assets = $element[$assetField]->all();
366
                            /** @var Asset[] $assets */
367
                            foreach ($assets as $asset) {
368
                                self::assetSitemapItem($asset, $metaBundle, $lines);
369
                            }
370
                        }
371
                        // Assets embeded in Block fields
372
                        $blockFields = FieldHelper::fieldsOfTypeFromElement(
373
                            $element,
374
                            FieldHelper::BLOCK_FIELD_CLASS_KEY,
375
                            true
376
                        );
377
                        foreach ($blockFields as $blockField) {
378
                            $blocks = $element[$blockField]->all();
379
                            /** @var Entry[]|NeoBlock[]|object[] $blocks */
380
                            foreach ($blocks as $block) {
381
                                $assetFields = [];
382
                                if ($block instanceof Entry) {
383
                                    $assetFields = FieldHelper::matrixFieldsOfType($block, AssetsField::class);
384
                                }
385
                                if ($block instanceof NeoBlock) {
386
                                    $assetFields = FieldHelper::neoFieldsOfType($block, AssetsField::class);
387
                                }
388
                                foreach ($assetFields as $assetField) {
389
                                    foreach ($block[$assetField]->all() as $asset) {
390
                                        self::assetSitemapItem($asset, $metaBundle, $lines);
391
                                    }
392
                                }
393
                            }
394
                        }
395
                    }
396
                    $lines[] = '</url>';
397
                }
398
                // Include links to any known file types in the assets fields
399
                if ($metaBundle->metaSitemapVars->sitemapFiles) {
400
                    // Regular Assets fields
401
                    $assetFields = FieldHelper::fieldsOfTypeFromElement(
402
                        $element,
403
                        FieldHelper::ASSET_FIELD_CLASS_KEY,
404
                        true
405
                    );
406
                    foreach ($assetFields as $assetField) {
407
                        $assets = $element[$assetField]->all();
408
                        foreach ($assets as $asset) {
409
                            self::assetFilesSitemapLink($asset, $metaBundle, $lines);
410
                        }
411
                    }
412
                    // Assets embeded in Block fields
413
                    $blockFields = FieldHelper::fieldsOfTypeFromElement(
414
                        $element,
415
                        FieldHelper::BLOCK_FIELD_CLASS_KEY,
416
                        true
417
                    );
418
                    foreach ($blockFields as $blockField) {
419
                        $blocks = $element[$blockField]->all();
420
                        /** @var Entry[]|NeoBlock[]|object[] $blocks */
421
                        foreach ($blocks as $block) {
422
                            $assetFields = [];
423
                            if ($block instanceof Entry) {
424
                                $assetFields = FieldHelper::matrixFieldsOfType($block, AssetsField::class);
425
                            }
426
                            if ($block instanceof NeoBlock) {
427
                                $assetFields = FieldHelper::neoFieldsOfType($block, AssetsField::class);
428
                            }
429
                            foreach ($assetFields as $assetField) {
430
                                foreach ($block[$assetField]->all() as $asset) {
431
                                    self::assetFilesSitemapLink($asset, $metaBundle, $lines);
432
                                }
433
                            }
434
                        }
435
                    }
436
                }
437
                $currentElement++;
438
            }
439
440
            if ($pagedSitemap) {
441
                break;
442
            }
443
444
            if ($paginator->getCurrentPage() == $paginator->getTotalPages()) {
445
                break;
446
            }
447
448
            $paginator->currentPage++;
449
            $elements = $paginator->getPageResults();
450
        } while (!empty($elements));
451
452
        // Sitemap closing tag
453
        $lines[] = '</urlset>';
454
455
        return implode('', $lines);
456
    }
457
458
    /**
459
     * Encode sitemap entities to make sure they follow the RFC-3986 standard for URIs, the RFC-3987 standard for IRIs
460
     * and the XML standard.
461
     * ref: https://sitemaps.org/protocol.html#escaping
462
     *
463
     * @param string $text
464
     * @return string
465
     */
466
    public static function encodeSitemapEntity(string $text): string
467
    {
468
        return Html::encode(UrlHelper::encodeUrl($text));
469
    }
470
471
    /**
472
     * Return the total number of elements in a sitemap, respecting metabundle settings.
473
     *
474
     * @param class-string<SeoElementInterface> $seoElement
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<SeoElementInterface> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<SeoElementInterface>.
Loading history...
475
     * @param MetaBundle $metaBundle
476
     * @return int|null
477
     */
478
    public static function getTotalElementsInSitemap(string $seoElement, MetaBundle $metaBundle): ?int
479
    {
480
        // Allow listeners to modify the query before we use it
481
        $query = $seoElement::sitemapElementsQuery($metaBundle);
482
        $event = new ModifySitemapQueryEvent([
483
            'query' => $query,
484
            'metaBundle' => $metaBundle,
485
        ]);
486
        Event::trigger(self::class, self::EVENT_MODIFY_SITEMAP_QUERY, $event);
487
        $totalElements = $query->count();
488
489
        if ($metaBundle->metaSitemapVars->sitemapLimit && ($totalElements > $metaBundle->metaSitemapVars->sitemapLimit)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metaBundle->metaSitemapVars->sitemapLimit of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
490
            $totalElements = $metaBundle->metaSitemapVars->sitemapLimit;
491
        }
492
493
        return $totalElements;
494
    }
495
496
    /**
497
     * Combine any per-entry type field settings from $element with the passed in
498
     * $metaBundle
499
     *
500
     * @param SeoElementInterface|string $seoElement
501
     * @param Element $element
502
     * @param MetaBundle $metaBundle
503
     */
504
    protected static function combineEntryTypeSettings($seoElement, Element $element, MetaBundle $metaBundle)
505
    {
506
        if (!empty($seoElement::typeMenuFromHandle($metaBundle->sourceHandle))) {
507
            list($sourceId, $sourceBundleType, $sourceHandle, $sourceSiteId, $typeId)
508
                = Seomatic::$plugin->metaBundles->getMetaSourceFromElement($element);
509
            $entryTypeBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceId(
510
                $sourceBundleType,
511
                $sourceId,
512
                $sourceSiteId,
513
                $typeId
514
            );
515
            // Combine in any settings for this entry type
516
            if ($entryTypeBundle) {
517
                // Combine the meta sitemap vars
518
                $attributes = $entryTypeBundle->metaSitemapVars->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

518
                /** @scrutinizer ignore-call */ 
519
                $attributes = $entryTypeBundle->metaSitemapVars->getAttributes();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
519
                $attributes = array_filter(
520
                    $attributes,
521
                    [ArrayHelper::class, 'preserveBools']
522
                );
523
                $metaBundle->metaSitemapVars->setAttributes($attributes, false);
524
525
                // Combine the meta global vars
526
                $attributes = $entryTypeBundle->metaGlobalVars->getAttributes();
0 ignored issues
show
Bug introduced by
The method getAttributes() does not exist on null. ( Ignorable by Annotation )

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

526
                /** @scrutinizer ignore-call */ 
527
                $attributes = $entryTypeBundle->metaGlobalVars->getAttributes();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
527
                $attributes = array_filter(
528
                    $attributes,
529
                    [ArrayHelper::class, 'preserveBools']
530
                );
531
                $metaBundle->metaGlobalVars->setAttributes($attributes, false);
532
            }
533
        }
534
    }
535
536
    /**
537
     * Combine any SEO Settings field settings from $element with the passed in
538
     * $metaBundle
539
     *
540
     * @param Element $element
541
     * @param MetaBundle $metaBundle
542
     */
543
    protected static function combineFieldSettings(Element $element, MetaBundle $metaBundle)
544
    {
545
        $fieldHandles = FieldHelper::fieldsOfTypeFromElement(
546
            $element,
547
            FieldHelper::SEO_SETTINGS_CLASS_KEY,
548
            true
549
        );
550
        foreach ($fieldHandles as $fieldHandle) {
551
            if (!empty($element->$fieldHandle)) {
552
                /** @var SeoSettings $seoSettingsField */
553
                $seoSettingsField = Craft::$app->getFields()->getFieldByHandle($fieldHandle);
554
                /** @var MetaBundle $metaBundle */
555
                $fieldMetaBundle = $element->$fieldHandle;
556
                if ($seoSettingsField !== null) {
557
                    if ($seoSettingsField->sitemapTabEnabled) {
558
                        Seomatic::$plugin->metaBundles->pruneFieldMetaBundleSettings($fieldMetaBundle, $fieldHandle);
559
                        // Combine the meta sitemap vars
560
                        $attributes = $fieldMetaBundle->metaSitemapVars->getAttributes();
561
562
                        // Get the explicitly inherited attributes
563
                        $inherited = array_keys(ArrayHelper::remove($attributes, 'inherited', []));
0 ignored issues
show
Bug introduced by
It seems like nystudio107\seomatic\hel..., 'inherited', array()) can also be of type null; however, parameter $array of array_keys() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

563
                        $inherited = array_keys(/** @scrutinizer ignore-type */ ArrayHelper::remove($attributes, 'inherited', []));
Loading history...
564
565
                        $attributes = array_intersect_key(
566
                            $attributes,
567
                            array_flip((array)$seoSettingsField->sitemapEnabledFields)
568
                        );
569
                        $attributes = array_filter(
570
                            $attributes,
571
                            [ArrayHelper::class, 'preserveBools']
572
                        );
573
574
                        foreach ($inherited as $inheritedAttribute) {
575
                            unset($attributes[$inheritedAttribute]);
576
                        }
577
578
                        $metaBundle->metaSitemapVars->setAttributes($attributes, false);
579
                    }
580
                    // Combine the meta global vars
581
                    $attributes = $fieldMetaBundle->metaGlobalVars->getAttributes();
582
                    $attributes = array_filter(
583
                        $attributes,
584
                        [ArrayHelper::class, 'preserveBools']
585
                    );
586
                    $metaBundle->metaGlobalVars->setAttributes($attributes, false);
587
                }
588
            }
589
        }
590
    }
591
592
    /**
593
     * @param Asset $asset
594
     * @param MetaBundle $metaBundle
595
     * @param array $lines
596
     */
597
    protected static function assetSitemapItem(Asset $asset, MetaBundle $metaBundle, array &$lines)
598
    {
599
        if ((bool)$asset->enabledForSite && $asset->getUrl() !== null) {
600
            switch ($asset->kind) {
601
                case 'image':
602
                    $transform = Craft::$app->getImageTransforms()->getTransformByHandle($metaBundle->metaSitemapVars->sitemapAssetTransform ?? '');
603
                    $lines[] = '<image:image>';
604
                    $lines[] = '<image:loc>';
605
                    $lines[] = self::encodeSitemapEntity(UrlHelper::absoluteUrlWithProtocol($asset->getUrl($transform, true)));
606
                    $lines[] = '</image:loc>';
607
                    // Handle the dynamic field => property mappings
608
                    foreach ($metaBundle->metaSitemapVars->sitemapImageFieldMap as $row) {
609
                        $fieldName = $row['field'] ?? '';
610
                        $propName = $row['property'] ?? '';
611
                        if (!empty($fieldName) && !empty($asset[$fieldName]) && !empty($propName)) {
612
                            $lines[] = '<image:' . $propName . '>';
613
                            $lines[] = self::encodeSitemapEntity($asset[$fieldName]);
614
                            $lines[] = '</image:' . $propName . '>';
615
                        }
616
                    }
617
                    $lines[] = '</image:image>';
618
                    break;
619
620
                case 'video':
621
                    $lines[] = '<video:video>';
622
                    $lines[] = '<video:content_loc>';
623
                    $lines[] = self::encodeSitemapEntity(UrlHelper::absoluteUrlWithProtocol($asset->getUrl()));
624
                    $lines[] = '</video:content_loc>';
625
                    // Handle the dynamic field => property mappings
626
                    foreach ($metaBundle->metaSitemapVars->sitemapVideoFieldMap as $row) {
627
                        $fieldName = $row['field'] ?? '';
628
                        $propName = $row['property'] ?? '';
629
                        if (!empty($fieldName) && !empty($asset[$fieldName]) && !empty($propName)) {
630
                            $lines[] = '<video:' . $propName . '>';
631
                            $lines[] = self::encodeSitemapEntity($asset[$fieldName]);
632
                            $lines[] = '</video:' . $propName . '>';
633
                        }
634
                    }
635
                    $lines[] = '</video:video>';
636
                    break;
637
            }
638
        }
639
    }
640
641
    /**
642
     * @param Asset $asset
643
     * @param MetaBundle $metaBundle
644
     * @param array $lines
645
     */
646
    protected static function assetFilesSitemapLink(Asset $asset, MetaBundle $metaBundle, array &$lines)
647
    {
648
        if ((bool)$asset->enabledForSite && $asset->getUrl() !== null) {
649
            if (in_array($asset->kind, SitemapTemplate::FILE_TYPES, false)) {
650
                $dateUpdated = $asset->dateUpdated ?? $asset->dateCreated ?? new DateTime();
651
                $lines[] = '<url>';
652
                $lines[] = '<loc>';
653
                $lines[] = self::encodeSitemapEntity(UrlHelper::absoluteUrlWithProtocol($asset->getUrl()));
654
                $lines[] = '</loc>';
655
                $lines[] = '<lastmod>';
656
                $lines[] = $dateUpdated->format(DateTime::W3C);
657
                $lines[] = '</lastmod>';
658
                $lines[] = '<changefreq>';
659
                $lines[] = $metaBundle->metaSitemapVars->sitemapChangeFreq;
660
                $lines[] = '</changefreq>';
661
                $lines[] = '<priority>';
662
                $lines[] = $metaBundle->metaSitemapVars->sitemapPriority;
663
                $lines[] = '</priority>';
664
                $lines[] = '</url>';
665
            }
666
        }
667
    }
668
}
669