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

Sitemap::getElementListSitemap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

150
        /** @scrutinizer ignore-call */ 
151
        $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...
151
        $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

151
        /** @scrutinizer ignore-call */ 
152
        $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...
152
        // Use craft\db\Paginator to paginate the results so we don't exceed any memory limits
153
        // See batch() and each() discussion here: https://github.com/yiisoft/yii2/issues/8420
154
        // and here: https://github.com/craftcms/cms/issues/7338
155
156
        $elementQuery = $seoElement::sitemapElementsQuery($metaBundle);
157
        $sitemapPageSize = $metaBundle->metaSitemapVars->sitemapPageSize;
158
        $elementQuery->limit($metaBundle->metaSitemapVars->sitemapLimit ?? null);
159
160
        // If this is not a paged sitemap, go through full results
161
        if (is_null($sitemapPageSize)) {
162
            $pagedSitemap = false;
163
            $paginator = new Paginator($elementQuery, [
164
                'pageSize' => self::SITEMAP_QUERY_PAGE_SIZE,
165
            ]);
166
            $elements = $paginator->getPageResults();
167
        } else {
168
            $sitemapPage = empty($page) ? 1 : $page;
169
            $pagedSitemap = true;
170
            $elementQuery->limit($sitemapPageSize);
171
            $elementQuery->offset(($sitemapPage - 1) * $sitemapPageSize);
172
            $elements = $elementQuery->all();
173
            $totalElements = $sitemapPageSize;
174
            $paginator = new Paginator($elementQuery, [
175
                'pageSize' => $sitemapPageSize,
176
            ]);
177
        }
178
179
        $currentElement = 1;
180
181
        do {
182
            if (Craft::$app instanceof ConsoleApplication) {
183
                if ($pagedSitemap) {
184
                    $message = sprintf('Query %d elements', $sitemapPageSize);
185
                } else {
186
                    $message = sprintf('Query %d / %d - elements: %d',
187
                        $paginator->getCurrentPage(),
188
                        $paginator->getTotalPages(),
189
                        $paginator->getTotalResults());
190
                }
191
                echo $message . PHP_EOL;
192
            }
193
            /** @var Element $element */
194
            foreach ($elements as $element) {
195
                // Output some info if this is a console app
196
                if (Craft::$app instanceof ConsoleApplication) {
197
                    echo "Processing element {$currentElement}/{$totalElements} - {$element->title}" . PHP_EOL;
198
                }
199
200
                $metaBundle->metaSitemapVars->setAttributes($stashedSitemapAttrs, false);
201
                $metaBundle->metaGlobalVars->setAttributes($stashedGlobalVarsAttrs, false);
202
                // Combine in any per-entry type settings
203
                self::combineEntryTypeSettings($seoElement, $element, $metaBundle);
204
                // Make sure this entry isn't disabled
205
                self::combineFieldSettings($element, $metaBundle);
206
                // Special case for the __home__ URI
207
                $path = ($element->uri === '__home__') ? '' : $element->uri;
208
                // Check to see if robots is `none` or `no index`
209
                $robotsEnabled = true;
210
                if (!empty($metaBundle->metaGlobalVars->robots)) {
211
                    $robotsEnabled = $metaBundle->metaGlobalVars->robots !== 'none' &&
212
                        $metaBundle->metaGlobalVars->robots !== 'noindex';
213
                }
214
                $enabled = true;
215
                if (Seomatic::$craft34) {
216
                    $enabled = $element->getEnabledForSite($metaBundle->sourceSiteId);
217
                }
218
                $enabled = $enabled && $path !== null && $metaBundle->metaSitemapVars->sitemapUrls && $robotsEnabled;
219
                $event = new IncludeSitemapEntryEvent([
220
                    'include' => $enabled,
221
                    'element' => $element,
222
                    'metaBundle' => $metaBundle,
223
                ]);
224
                Event::trigger(self::class, self::EVENT_INCLUDE_SITEMAP_ENTRY, $event);
225
                // Only add in a sitemap entry if it meets our criteria
226
                if ($event->include) {
227
                    // Get the url and canonicalUrl
228
                    try {
229
                        $url = UrlHelper::siteUrl($path, null, null, $metaBundle->sourceSiteId);
230
                    } catch (Exception $e) {
231
                        $url = '';
232
                    }
233
                    $url = UrlHelper::absoluteUrlWithProtocol($url);
234
                    if (Seomatic::$settings->excludeNonCanonicalUrls) {
235
                        Seomatic::$matchedElement = $element;
236
                        MetaValue::cache();
237
                        $path = $metaBundle->metaGlobalVars->parsedValue('canonicalUrl');
238
                        try {
239
                            $canonicalUrl = UrlHelper::siteUrl($path, null, null, $metaBundle->sourceSiteId);
240
                        } catch (Exception $e) {
241
                            $canonicalUrl = '';
242
                        }
243
                        $canonicalUrl = UrlHelper::absoluteUrlWithProtocol($canonicalUrl);
244
                        if ($url !== $canonicalUrl) {
245
                            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

245
                            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...
246
                            continue;
247
                        }
248
                    }
249
                    $dateUpdated = $element->dateUpdated ?? $element->dateCreated ?? new DateTime();
250
                    $lines[] = '<url>';
251
                    // Standard sitemap key/values
252
                    $lines[] = '<loc>';
253
                    $lines[] = Html::encode($url);
254
                    $lines[] = '</loc>';
255
                    $lines[] = '<lastmod>';
256
                    $lines[] = $dateUpdated->format(DateTime::W3C);
257
                    $lines[] = '</lastmod>';
258
                    $lines[] = '<changefreq>';
259
                    $lines[] = $metaBundle->metaSitemapVars->sitemapChangeFreq;
260
                    $lines[] = '</changefreq>';
261
                    $lines[] = '<priority>';
262
                    $lines[] = $metaBundle->metaSitemapVars->sitemapPriority;
263
                    $lines[] = '</priority>';
264
                    // Handle alternate URLs if this is multi-site
265
                    if ($multiSite && $metaBundle->metaSitemapVars->sitemapAltLinks) {
266
                        $primarySiteId = Craft::$app->getSites()->getPrimarySite()->id;
267
                        foreach ($metaBundle->sourceAltSiteSettings as $altSiteSettings) {
268
                            if (in_array($altSiteSettings['siteId'], $groupSiteIds, false) && SiteHelper::siteEnabledWithUrls($altSiteSettings['siteId'])) {
269
                                $altElement = null;
270
                                if ($seoElement !== null) {
271
                                    /** @var Element $altElement */
272
                                    $altElement = $seoElement::sitemapAltElement(
273
                                        $metaBundle,
274
                                        $element->id,
275
                                        $altSiteSettings['siteId']
276
                                    );
277
                                }
278
                                // Make sure to only include the `hreflang` if the element exists,
279
                                // and sitemaps are on for it
280
                                if (Seomatic::$settings->addHrefLang && $altElement && $altElement->url) {
281
                                    list($altSourceId, $altSourceBundleType, $altSourceHandle, $altSourceSiteId, $altTypeId)
282
                                        = Seomatic::$plugin->metaBundles->getMetaSourceFromElement($altElement);
283
                                    $altMetaBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceId(
284
                                        $altSourceBundleType,
285
                                        $altSourceId,
286
                                        $altSourceSiteId
287
                                    );
288
                                    if ($altMetaBundle) {
289
                                        $altEnabled = true;
290
                                        if (Seomatic::$craft34) {
291
                                            $altEnabled = $altElement->getEnabledForSite($altMetaBundle->sourceSiteId);
292
                                        }
293
                                        // Make sure this entry isn't disabled
294
                                        self::combineFieldSettings($altElement, $altMetaBundle);
295
                                        if ($altEnabled && $altMetaBundle->metaSitemapVars->sitemapUrls) {
296
                                            try {
297
                                                $altUrl = UrlHelper::siteUrl($altElement->url, null, null, $altSourceId);
0 ignored issues
show
Bug introduced by
It seems like $altElement->url can also be of type craft\base\ElementInterface[]; however, parameter $path of nystudio107\seomatic\helpers\UrlHelper::siteUrl() does only seem to accept string, 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

297
                                                $altUrl = UrlHelper::siteUrl(/** @scrutinizer ignore-type */ $altElement->url, null, null, $altSourceId);
Loading history...
298
                                            } catch (Exception $e) {
299
                                                $altUrl = $altElement->url;
300
                                            }
301
                                            $altUrl = UrlHelper::absoluteUrlWithProtocol($altUrl);
0 ignored issues
show
Bug introduced by
It seems like $altUrl can also be of type craft\base\ElementInterface[]; however, parameter $url of nystudio107\seomatic\hel...soluteUrlWithProtocol() does only seem to accept string, 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

301
                                            $altUrl = UrlHelper::absoluteUrlWithProtocol(/** @scrutinizer ignore-type */ $altUrl);
Loading history...
302
                                            // If this is the primary site, add it as x-default, too
303
                                            if ($primarySiteId === $altSourceSiteId && Seomatic::$settings->addXDefaultHrefLang) {
304
                                                $lines[] = '<xhtml:link rel="alternate"'
305
                                                    . ' hreflang="x-default"'
306
                                                    . ' href="' . Html::encode($altUrl) . '"'
307
                                                    . ' />';
308
                                            }
309
                                            $lines[] = '<xhtml:link rel="alternate"'
310
                                                . ' hreflang="' . $altSiteSettings['language'] . '"'
311
                                                . ' href="' . Html::encode($altUrl) . '"'
312
                                                . ' />';
313
                                        }
314
                                    }
315
                                }
316
                            }
317
                        }
318
                    }
319
                    // Handle news sitemaps https://developers.google.com/search/docs/crawling-indexing/sitemaps/news-sitemap
320
                    if ((bool)$metaBundle->metaSitemapVars->newsSitemap) {
321
                        $now = new DateTime();
322
                        $interval = $now->diff($dateUpdated);
323
                        if ($interval->days <= 2) {
324
                            $language = strtolower($element->getLanguage());
325
                            if (!str_starts_with($language, 'zh')) {
326
                                $language = substr($language, 0, 2);
327
                            }
328
                            $lines[] = '<news:news>';
329
                            $lines[] = '<news:publication>';
330
                            $lines[] = '<news:name>' . $metaBundle->metaSitemapVars->newsPublicationName . '</news:name>';
331
                            $lines[] = '<news:language>' . $language . '</news:language>';
332
                            $lines[] = '</news:publication>';
333
                            $lines[] = '<news:publication_date>' . $dateUpdated->format(DateTime::W3C) . '</news:publication_date>';
334
                            $lines[] = '<news:title>' . $element->title . '</news:title>';
335
                            $lines[] = '</news:news>';
336
                        }
337
                    }
338
                    // Handle any Assets
339
                    if ($metaBundle->metaSitemapVars->sitemapAssets) {
340
                        // Regular Assets fields
341
                        $assetFields = FieldHelper::fieldsOfTypeFromElement(
342
                            $element,
343
                            FieldHelper::ASSET_FIELD_CLASS_KEY,
344
                            true
345
                        );
346
                        foreach ($assetFields as $assetField) {
347
                            $assets = $element[$assetField]->all();
348
                            /** @var Asset[] $assets */
349
                            foreach ($assets as $asset) {
350
                                self::assetSitemapItem($asset, $metaBundle, $lines);
351
                            }
352
                        }
353
                        // Assets embeded in Block fields
354
                        $blockFields = FieldHelper::fieldsOfTypeFromElement(
355
                            $element,
356
                            FieldHelper::BLOCK_FIELD_CLASS_KEY,
357
                            true
358
                        );
359
                        foreach ($blockFields as $blockField) {
360
                            $blocks = $element[$blockField]->all();
361
                            /** @var MatrixBlock[]|NeoBlock[]|SuperTableBlock[]|object[] $blocks */
362
                            foreach ($blocks as $block) {
363
                                $assetFields = [];
364
                                if ($block instanceof MatrixBlock) {
365
                                    $assetFields = FieldHelper::matrixFieldsOfType($block, AssetsField::class);
366
                                }
367
                                if ($block instanceof NeoBlock) {
368
                                    $assetFields = FieldHelper::neoFieldsOfType($block, AssetsField::class);
369
                                }
370
                                if ($block instanceof SuperTableBlock) {
371
                                    $assetFields = FieldHelper::superTableFieldsOfType($block, AssetsField::class);
372
                                }
373
                                foreach ($assetFields as $assetField) {
374
                                    foreach ($block[$assetField]->all() as $asset) {
375
                                        self::assetSitemapItem($asset, $metaBundle, $lines);
376
                                    }
377
                                }
378
                            }
379
                        }
380
                    }
381
                    $lines[] = '</url>';
382
                }
383
                // Include links to any known file types in the assets fields
384
                if ($metaBundle->metaSitemapVars->sitemapFiles) {
385
                    // Regular Assets fields
386
                    $assetFields = FieldHelper::fieldsOfTypeFromElement(
387
                        $element,
388
                        FieldHelper::ASSET_FIELD_CLASS_KEY,
389
                        true
390
                    );
391
                    foreach ($assetFields as $assetField) {
392
                        $assets = $element[$assetField]->all();
393
                        foreach ($assets as $asset) {
394
                            self::assetFilesSitemapLink($asset, $metaBundle, $lines);
395
                        }
396
                    }
397
                    // Assets embeded in Block fields
398
                    $blockFields = FieldHelper::fieldsOfTypeFromElement(
399
                        $element,
400
                        FieldHelper::BLOCK_FIELD_CLASS_KEY,
401
                        true
402
                    );
403
                    foreach ($blockFields as $blockField) {
404
                        $blocks = $element[$blockField]->all();
405
                        /** @var MatrixBlock[]|NeoBlock[]|SuperTableBlock[]|object[] $blocks */
406
                        foreach ($blocks as $block) {
407
                            $assetFields = [];
408
                            if ($block instanceof MatrixBlock) {
409
                                $assetFields = FieldHelper::matrixFieldsOfType($block, AssetsField::class);
410
                            }
411
                            if ($block instanceof SuperTableBlock) {
412
                                $assetFields = FieldHelper::superTableFieldsOfType($block, AssetsField::class);
413
                            }
414
                            if ($block instanceof NeoBlock) {
415
                                $assetFields = FieldHelper::neoFieldsOfType($block, AssetsField::class);
416
                            }
417
                            foreach ($assetFields as $assetField) {
418
                                foreach ($block[$assetField]->all() as $asset) {
419
                                    self::assetFilesSitemapLink($asset, $metaBundle, $lines);
420
                                }
421
                            }
422
                        }
423
                    }
424
                }
425
                $currentElement++;
426
            }
427
428
            if ($pagedSitemap) {
429
                break;
430
            }
431
432
            if ($paginator->getCurrentPage() == $paginator->getTotalPages()) {
433
                break;
434
            }
435
436
            $paginator->currentPage++;
437
            $elements = $paginator->getPageResults();
438
        } while (!empty($elements));
439
440
        // Sitemap closing tag
441
        $lines[] = '</urlset>';
442
443
        return implode('', $lines);
444
    }
445
446
447
    /**
448
     * Combine any per-entry type field settings from $element with the passed in
449
     * $metaBundle
450
     *
451
     * @param SeoElementInterface|string $seoElement
452
     * @param Element $element
453
     * @param MetaBundle $metaBundle
454
     */
455
    protected static function combineEntryTypeSettings($seoElement, Element $element, MetaBundle $metaBundle)
456
    {
457
        if (!empty($seoElement::typeMenuFromHandle($metaBundle->sourceHandle))) {
458
            list($sourceId, $sourceBundleType, $sourceHandle, $sourceSiteId, $typeId)
459
                = Seomatic::$plugin->metaBundles->getMetaSourceFromElement($element);
460
            $entryTypeBundle = Seomatic::$plugin->metaBundles->getMetaBundleBySourceId(
461
                $sourceBundleType,
462
                $sourceId,
463
                $sourceSiteId,
464
                $typeId
465
            );
466
            // Combine in any settings for this entry type
467
            if ($entryTypeBundle) {
468
                // Combine the meta sitemap vars
469
                $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

469
                /** @scrutinizer ignore-call */ 
470
                $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...
470
                $attributes = array_filter(
471
                    $attributes,
472
                    [ArrayHelper::class, 'preserveBools']
473
                );
474
                $metaBundle->metaSitemapVars->setAttributes($attributes, false);
475
476
                // Combine the meta global vars
477
                $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

477
                /** @scrutinizer ignore-call */ 
478
                $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...
478
                $attributes = array_filter(
479
                    $attributes,
480
                    [ArrayHelper::class, 'preserveBools']
481
                );
482
                $metaBundle->metaGlobalVars->setAttributes($attributes, false);
483
            }
484
        }
485
    }
486
487
    /**
488
     * Combine any SEO Settings field settings from $element with the passed in
489
     * $metaBundle
490
     *
491
     * @param Element $element
492
     * @param MetaBundle $metaBundle
493
     */
494
    protected static function combineFieldSettings(Element $element, MetaBundle $metaBundle)
495
    {
496
        $fieldHandles = FieldHelper::fieldsOfTypeFromElement(
497
            $element,
498
            FieldHelper::SEO_SETTINGS_CLASS_KEY,
499
            true
500
        );
501
        foreach ($fieldHandles as $fieldHandle) {
502
            if (!empty($element->$fieldHandle)) {
503
                /** @var SeoSettings $seoSettingsField */
504
                $seoSettingsField = Craft::$app->getFields()->getFieldByHandle($fieldHandle);
505
                /** @var MetaBundle $fieldMetaBundle */
506
                $fieldMetaBundle = $element->$fieldHandle;
507
                if ($seoSettingsField !== null) {
508
                    if ($seoSettingsField->sitemapTabEnabled) {
509
                        Seomatic::$plugin->metaBundles->pruneFieldMetaBundleSettings($fieldMetaBundle, $fieldHandle);
510
                        // Combine the meta sitemap vars
511
                        $attributes = $fieldMetaBundle->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

511
                        /** @scrutinizer ignore-call */ 
512
                        $attributes = $fieldMetaBundle->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...
512
513
                        // Get the explicitly inherited attributes
514
                        $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

514
                        $inherited = array_keys(/** @scrutinizer ignore-type */ ArrayHelper::remove($attributes, 'inherited', []));
Loading history...
515
516
                        $attributes = array_intersect_key(
517
                            $attributes,
518
                            array_flip($seoSettingsField->sitemapEnabledFields)
519
                        );
520
                        $attributes = array_filter(
521
                            $attributes,
522
                            [ArrayHelper::class, 'preserveBools']
523
                        );
524
525
                        foreach ($inherited as $inheritedAttribute) {
526
                            unset($attributes[$inheritedAttribute]);
527
                        }
528
529
                        $metaBundle->metaSitemapVars->setAttributes($attributes, false);
530
                    }
531
                    // Combine the meta global vars
532
                    $attributes = $fieldMetaBundle->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

532
                    /** @scrutinizer ignore-call */ 
533
                    $attributes = $fieldMetaBundle->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...
533
                    $attributes = array_filter(
534
                        $attributes,
535
                        [ArrayHelper::class, 'preserveBools']
536
                    );
537
                    $metaBundle->metaGlobalVars->setAttributes($attributes, false);
538
                }
539
            }
540
        }
541
    }
542
543
    /**
544
     * @param Asset $asset
545
     * @param MetaBundle $metaBundle
546
     * @param array $lines
547
     */
548
    protected static function assetSitemapItem(Asset $asset, MetaBundle $metaBundle, array &$lines)
549
    {
550
        if ((bool)$asset->enabledForSite && $asset->getUrl() !== null) {
551
            switch ($asset->kind) {
552
                case 'image':
553
                    $transform = Craft::$app->getAssetTransforms()->getTransformByHandle($metaBundle->metaSitemapVars->sitemapAssetTransform ?? '');
554
                    $lines[] = '<image:image>';
555
                    $lines[] = '<image:loc>';
556
                    $lines[] = Html::encode(UrlHelper::absoluteUrlWithProtocol($asset->getUrl($transform, true)));
557
                    $lines[] = '</image:loc>';
558
                    // Handle the dynamic field => property mappings
559
                    foreach ($metaBundle->metaSitemapVars->sitemapImageFieldMap as $row) {
560
                        $fieldName = $row['field'] ?? '';
561
                        $propName = $row['property'] ?? '';
562
                        if (!empty($fieldName) && !empty($asset[$fieldName]) && !empty($propName)) {
563
                            $lines[] = '<image:' . $propName . '>';
564
                            $lines[] = Html::encode($asset[$fieldName]);
565
                            $lines[] = '</image:' . $propName . '>';
566
                        }
567
                    }
568
                    $lines[] = '</image:image>';
569
                    break;
570
571
                case 'video':
572
                    $lines[] = '<video:video>';
573
                    $lines[] = '<video:content_loc>';
574
                    $lines[] = Html::encode(UrlHelper::absoluteUrlWithProtocol($asset->getUrl()));
575
                    $lines[] = '</video:content_loc>';
576
                    // Handle the dynamic field => property mappings
577
                    foreach ($metaBundle->metaSitemapVars->sitemapVideoFieldMap as $row) {
578
                        $fieldName = $row['field'] ?? '';
579
                        $propName = $row['property'] ?? '';
580
                        if (!empty($fieldName) && !empty($asset[$fieldName]) && !empty($propName)) {
581
                            $lines[] = '<video:' . $propName . '>';
582
                            $lines[] = Html::encode($asset[$fieldName]);
583
                            $lines[] = '</video:' . $propName . '>';
584
                        }
585
                    }
586
                    $lines[] = '</video:video>';
587
                    break;
588
            }
589
        }
590
    }
591
592
    /**
593
     * @param Asset $asset
594
     * @param MetaBundle $metaBundle
595
     * @param array $lines
596
     */
597
    protected static function assetFilesSitemapLink(Asset $asset, MetaBundle $metaBundle, array &$lines)
598
    {
599
        if ((bool)$asset->enabledForSite && $asset->getUrl() !== null) {
600
            if (in_array($asset->kind, SitemapTemplate::FILE_TYPES, false)) {
601
                $dateUpdated = $asset->dateUpdated ?? $asset->dateCreated ?? new DateTime();
602
                $lines[] = '<url>';
603
                $lines[] = '<loc>';
604
                $lines[] = Html::encode(UrlHelper::absoluteUrlWithProtocol($asset->getUrl()));
605
                $lines[] = '</loc>';
606
                $lines[] = '<lastmod>';
607
                $lines[] = $dateUpdated->format(DateTime::W3C);
608
                $lines[] = '</lastmod>';
609
                $lines[] = '<changefreq>';
610
                $lines[] = $metaBundle->metaSitemapVars->sitemapChangeFreq;
611
                $lines[] = '</changefreq>';
612
                $lines[] = '<priority>';
613
                $lines[] = $metaBundle->metaSitemapVars->sitemapPriority;
614
                $lines[] = '</priority>';
615
                $lines[] = '</url>';
616
            }
617
        }
618
    }
619
620
    protected static function getElementListSitemap(array $elements)
0 ignored issues
show
Unused Code introduced by
The parameter $elements 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

620
    protected static function getElementListSitemap(/** @scrutinizer ignore-unused */ array $elements)

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...
621
    {
622
    }
623
}
624