SeoEntry::sitemapElementsQuery()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 12
rs 10
c 0
b 0
f 0
cc 3
nc 2
nop 1
1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS
4
 *
5
 * A turnkey SEO implementation for Craft CMS that is comprehensive, powerful,
6
 * and flexible
7
 *
8
 * @link      https://nystudio107.com
9
 * @copyright Copyright (c) 2019 nystudio107
10
 */
11
12
namespace nystudio107\seomatic\seoelements;
13
14
use Craft;
15
use craft\base\ElementInterface;
0 ignored issues
show
Bug introduced by
The type craft\base\ElementInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use craft\base\Model;
17
use craft\elements\db\ElementQueryInterface;
18
use craft\elements\Entry;
19
use craft\events\DefineHtmlEvent;
20
use craft\events\SectionEvent;
21
use craft\gql\interfaces\elements\Entry as EntryInterface;
22
use craft\helpers\ElementHelper;
23
use craft\models\Section;
24
use craft\models\Site;
25
use craft\services\Entries;
0 ignored issues
show
Bug introduced by
The type craft\services\Entries was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use nystudio107\seomatic\assetbundles\seomatic\SeomaticAsset;
27
use nystudio107\seomatic\base\GqlSeoElementInterface;
28
use nystudio107\seomatic\base\SeoElementInterface;
29
use nystudio107\seomatic\helpers\ArrayHelper;
30
use nystudio107\seomatic\helpers\Config as ConfigHelper;
31
use nystudio107\seomatic\helpers\PluginTemplate;
32
use nystudio107\seomatic\models\MetaBundle;
33
use nystudio107\seomatic\Seomatic;
34
use yii\base\Event;
35
use yii\base\InvalidConfigException;
36
37
/**
38
 * @author    nystudio107
39
 * @package   Seomatic
40
 * @since     3.2.0
41
 */
42
class SeoEntry implements SeoElementInterface, GqlSeoElementInterface
43
{
44
    // Constants
45
    // =========================================================================
46
47
    public const META_BUNDLE_TYPE = 'section';
48
    public const ELEMENT_CLASSES = [
49
        Entry::class,
50
    ];
51
    public const REQUIRED_PLUGIN_HANDLE = null;
52
    public const CONFIG_FILE_PATH = 'entrymeta/Bundle';
53
54
    // Public Static Methods
55
    // =========================================================================
56
57
    /**
58
     * Return the sourceBundleType for that this SeoElement handles
59
     *
60
     * @return string
61
     */
62
    public static function getMetaBundleType(): string
63
    {
64
        return self::META_BUNDLE_TYPE;
65
    }
66
67
    /**
68
     * Returns an array of the element classes that are handled by this SeoElement
69
     *
70
     * @return array
71
     */
72
    public static function getElementClasses(): array
73
    {
74
        return self::ELEMENT_CLASSES;
75
    }
76
77
    /**
78
     * Return the refHandle (e.g.: `entry` or `category`) for the SeoElement
79
     *
80
     * @return string
81
     */
82
    public static function getElementRefHandle(): string
83
    {
84
        return Entry::refHandle() ?? 'entry';
85
    }
86
87
    /**
88
     * Return the handle to a required plugin for this SeoElement type
89
     *
90
     * @return null|string
91
     */
92
    public static function getRequiredPluginHandle()
93
    {
94
        return self::REQUIRED_PLUGIN_HANDLE;
95
    }
96
97
    /**
98
     * Install any event handlers for this SeoElement type
99
     */
100
    public static function installEventHandlers()
101
    {
102
        $request = Craft::$app->getRequest();
103
104
        // Install for all requests
105
        Event::on(
106
            Entries::class,
107
            Entries::EVENT_AFTER_SAVE_SECTION,
108
            function(SectionEvent $event) {
0 ignored issues
show
Unused Code introduced by
The parameter $event 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

108
            function(/** @scrutinizer ignore-unused */ SectionEvent $event) {

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...
109
                Craft::debug(
110
                    'Entries::EVENT_AFTER_SAVE_SECTION',
111
                    __METHOD__
112
                );
113
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
114
            }
115
        );
116
        Event::on(
117
            Entries::class,
118
            Entries::EVENT_AFTER_DELETE_SECTION,
119
            function(SectionEvent $event) {
0 ignored issues
show
Unused Code introduced by
The parameter $event 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

119
            function(/** @scrutinizer ignore-unused */ SectionEvent $event) {

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...
120
                Craft::debug(
121
                    'Entries::EVENT_AFTER_DELETE_SECTION',
122
                    __METHOD__
123
                );
124
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
125
            }
126
        );
127
128
        // Install for all non-console requests
129
        if (!$request->getIsConsoleRequest()) {
130
            // Handler: Entries::EVENT_AFTER_SAVE_SECTION
131
            Event::on(
132
                Entries::class,
133
                Entries::EVENT_AFTER_SAVE_SECTION,
134
                function(SectionEvent $event) {
135
                    Craft::debug(
136
                        'Entries::EVENT_AFTER_SAVE_SECTION',
137
                        __METHOD__
138
                    );
139
                    if ($event->section !== null && $event->section->id !== null) {
140
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
141
                            SeoEntry::getMetaBundleType(),
142
                            $event->section->id,
143
                            $event->isNew
144
                        );
145
                        // Create the meta bundles for this section if it's new
146
                        if ($event->isNew) {
147
                            SeoEntry::createContentMetaBundle($event->section);
148
                            Seomatic::$plugin->sitemaps->submitSitemapIndex();
149
                        }
150
                    }
151
                }
152
            );
153
            // Handler: Entries::EVENT_AFTER_DELETE_SECTION
154
            Event::on(
155
                Entries::class,
156
                Entries::EVENT_AFTER_DELETE_SECTION,
157
                function(SectionEvent $event) {
158
                    Craft::debug(
159
                        'Entries::EVENT_AFTER_DELETE_SECTION',
160
                        __METHOD__
161
                    );
162
                    if ($event->section !== null && $event->section->id !== null) {
163
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
164
                            SeoEntry::getMetaBundleType(),
165
                            $event->section->id,
166
                            false
167
                        );
168
                        // Delete the meta bundles for this section
169
                        Seomatic::$plugin->metaBundles->deleteMetaBundleBySourceId(
170
                            SeoEntry::getMetaBundleType(),
171
                            $event->section->id
172
                        );
173
                    }
174
                }
175
            );
176
        }
177
178
        // Install only for non-console site requests
179
        if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
180
        }
181
182
        // Handler: Entry::EVENT_DEFINE_SIDEBAR_HTML
183
        Event::on(
184
            Entry::class,
185
            Entry::EVENT_DEFINE_SIDEBAR_HTML,
186
            static function(DefineHtmlEvent $event) {
187
                Craft::debug(
188
                    'Entry::EVENT_DEFINE_SIDEBAR_HTML',
189
                    __METHOD__
190
                );
191
                $html = '';
192
                Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
0 ignored issues
show
Bug introduced by
The method registerAssetBundle() 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

192
                Seomatic::$view->/** @scrutinizer ignore-call */ 
193
                                 registerAssetBundle(SeomaticAsset::class);

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...
193
                /** @var Entry $entry */
194
                $entry = $event->sender ?? null;
195
                if ($entry !== null && $entry->uri !== null) {
196
                    Seomatic::$plugin->metaContainers->previewMetaContainers($entry->uri, $entry->siteId, true, true, $entry);
197
                    // Render our preview sidebar template
198
                    if (Seomatic::$settings->displayPreviewSidebar && Seomatic::$matchedElement) {
199
                        $html .= PluginTemplate::renderPluginTemplate('_sidebars/entry-preview.twig');
200
                    }
201
                    // Render our analysis sidebar template
202
// @TODO: This will be added an upcoming 'pro' edition
203
//                if (Seomatic::$settings->displayAnalysisSidebar && Seomatic::$matchedElement) {
204
//                    $html .= PluginTemplate::renderPluginTemplate('_sidebars/entry-analysis.twig');
205
//                }
206
                }
207
                $event->html .= $html;
208
            }
209
        );
210
    }
211
212
    /**
213
     * Return an ElementQuery for the sitemap elements for the given MetaBundle
214
     *
215
     * @param MetaBundle $metaBundle
216
     *
217
     * @return ElementQueryInterface
218
     */
219
    public static function sitemapElementsQuery(MetaBundle $metaBundle): ElementQueryInterface
220
    {
221
        $query = Entry::find()
222
            ->section($metaBundle->sourceHandle)
223
            ->siteId($metaBundle->sourceSiteId)
224
            ->limit($metaBundle->metaSitemapVars->sitemapLimit);
225
        if ($metaBundle->sourceType === 'structure'
226
            && !empty($metaBundle->metaSitemapVars->structureDepth)) {
227
            $query->level('<=' . $metaBundle->metaSitemapVars->structureDepth);
228
        }
229
230
        return $query;
231
    }
232
233
    /**
234
     * Return an ElementInterface for the sitemap alt element for the given MetaBundle
235
     * and Element ID
236
     *
237
     * @param MetaBundle $metaBundle
238
     * @param int $elementId
239
     * @param int $siteId
240
     *
241
     * @return null|ElementInterface
242
     */
243
    public static function sitemapAltElement(
244
        MetaBundle $metaBundle,
245
        int        $elementId,
246
        int        $siteId,
247
    ) {
248
        return Entry::find()
249
            ->section($metaBundle->sourceHandle)
250
            ->id($elementId)
251
            ->siteId($siteId)
252
            ->limit(1)
253
            ->one();
254
    }
255
256
    /**
257
     * Return a preview URI for a given $sourceHandle and $siteId
258
     * This just returns the first element
259
     *
260
     * @param string $sourceHandle
261
     * @param int|null $siteId
262
     * @param int|string|null $typeId
263
     *
264
     * @return ?string
265
     */
266
    public static function previewUri(string $sourceHandle, $siteId, $typeId = null): ?string
267
    {
268
        $uri = null;
269
        $query = Entry::find()
270
            ->section($sourceHandle)
271
            ->siteId($siteId);
272
        if (!empty($typeId)) {
273
            $query
274
                ->andWhere([
275
                    'typeId' => (int)$typeId,
276
                ]);
277
        }
278
        $element = $query->one();
279
        if ($element) {
280
            $uri = $element->uri;
281
        }
282
283
        return $uri;
284
    }
285
286
    /**
287
     * Return an array of FieldLayouts from the $sourceHandle
288
     *
289
     * @param string $sourceHandle
290
     * @param int|string|null $typeId
291
     *
292
     * @return array
293
     */
294
    public static function fieldLayouts(string $sourceHandle, $typeId = null): array
295
    {
296
        $layouts = [];
297
        $section = Craft::$app->getEntries()->getSectionByHandle($sourceHandle);
298
        if ($section) {
299
            $entryTypes = $section->getEntryTypes();
300
            foreach ($entryTypes as $entryType) {
301
                if ($entryType->fieldLayoutId && ($entryType->id == $typeId || empty($typeId))) {
302
                    $layouts[] = Craft::$app->getFields()->getLayoutById($entryType->fieldLayoutId);
303
                }
304
            }
305
        }
306
307
        return $layouts;
308
    }
309
310
    /**
311
     * Return the (entry) type menu as a $id => $name associative array
312
     *
313
     * @param string $sourceHandle
314
     *
315
     * @return array
316
     */
317
    public static function typeMenuFromHandle(string $sourceHandle): array
318
    {
319
        $typeMenu = [];
320
321
        $section = self::sourceModelFromHandle($sourceHandle);
322
        if ($section !== null) {
323
            $entryTypes = $section->getEntryTypes();
324
            foreach ($entryTypes as $entryType) {
325
                $typeMenu[$entryType->id] = $entryType->name;
326
            }
327
        }
328
329
        return $typeMenu;
330
    }
331
332
    /**
333
     * Return the source model of the given $sourceId
334
     *
335
     * @param int $sourceId
336
     *
337
     * @return Section|null
338
     */
339
    public static function sourceModelFromId(int $sourceId)
340
    {
341
        return Craft::$app->getEntries()->getSectionById($sourceId);
342
    }
343
344
    /**
345
     * Return the source model of the given $sourceId
346
     *
347
     * @param string $sourceHandle
348
     *
349
     * @return Section|null
350
     */
351
    public static function sourceModelFromHandle(string $sourceHandle)
352
    {
353
        return Craft::$app->getEntries()->getSectionByHandle($sourceHandle);
354
    }
355
356
    /**
357
     * Return the most recently updated Element from a given source model
358
     *
359
     * @param Model $sourceModel
360
     * @param int $sourceSiteId
361
     *
362
     * @return null|ElementInterface
363
     */
364
    public static function mostRecentElement(Model $sourceModel, int $sourceSiteId)
365
    {
366
        /** @var Section $sourceModel */
367
        return Entry::find()
368
            ->section($sourceModel->handle)
369
            ->siteId($sourceSiteId)
370
            ->limit(1)
371
            ->orderBy(['elements.dateUpdated' => SORT_DESC])
372
            ->one();
373
    }
374
375
    /**
376
     * Return the path to the config file directory
377
     *
378
     * @return string
379
     */
380
    public static function configFilePath(): string
381
    {
382
        return self::CONFIG_FILE_PATH;
383
    }
384
385
    /**
386
     * Return a meta bundle config array for the given $sourceModel
387
     *
388
     * @param Model $sourceModel
389
     *
390
     * @return array
391
     */
392
    public static function metaBundleConfig(Model $sourceModel): array
393
    {
394
        /** @var Section $sourceModel */
395
        return ArrayHelper::merge(
396
            ConfigHelper::getConfigFromFile(self::configFilePath()),
397
            [
398
                'sourceId' => $sourceModel->id,
399
                'sourceName' => (string)$sourceModel->name,
400
                'sourceHandle' => $sourceModel->handle,
401
                'sourceType' => $sourceModel->type,
402
            ]
403
        );
404
    }
405
406
    /**
407
     * Return the source id from the $element
408
     *
409
     * @param ElementInterface $element
410
     *
411
     * @return int|null
412
     */
413
    public static function sourceIdFromElement(ElementInterface $element)
414
    {
415
        // Get the root element so we handle nested matrix entries
416
        $rootElement = ElementHelper::rootElement($element);
0 ignored issues
show
Deprecated Code introduced by
The function craft\helpers\ElementHelper::rootElement() has been deprecated: in 5.4.0. Use [[ElementInterface::getRootOwner()]] instead. ( Ignorable by Annotation )

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

416
        $rootElement = /** @scrutinizer ignore-deprecated */ ElementHelper::rootElement($element);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
417
        if ($rootElement instanceof Entry) {
418
            return $rootElement->sectionId;
419
        }
420
        // If the root element isn't an entry, handle that case too
421
        $sourceBundleType = Seomatic::$plugin->seoElements->getMetaBundleTypeFromElement($rootElement);
422
        if ($sourceBundleType !== null) {
423
            $seoElement = Seomatic::$plugin->seoElements->getSeoElementByMetaBundleType($sourceBundleType);
424
            if ($seoElement !== null) {
425
                return $seoElement::sourceIdFromElement($rootElement);
426
            }
427
        }
428
429
        return null;
430
    }
431
432
    /**
433
     * Return the (entry) type id from the $element
434
     *
435
     * @param ElementInterface $element
436
     *
437
     * @return int|null
438
     */
439
    public static function typeIdFromElement(ElementInterface $element)
440
    {
441
        /** @var Entry $element */
442
        return $element->typeId;
443
    }
444
445
    /**
446
     * Return the source handle from the $element
447
     *
448
     * @param ElementInterface $element
449
     *
450
     * @return string|null
451
     */
452
    public static function sourceHandleFromElement(ElementInterface $element)
453
    {
454
        $sourceHandle = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $sourceHandle is dead and can be removed.
Loading history...
455
        /** @var Entry $element */
456
        try {
457
            $sourceHandle = $element->getSection()?->handle;
458
        } catch (InvalidConfigException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
459
        }
460
461
        return $sourceHandle;
462
    }
463
464
    /**
465
     * Create a MetaBundle in the db for each site, from the passed in $sourceModel
466
     *
467
     * @param Model $sourceModel
468
     */
469
    public static function createContentMetaBundle(Model $sourceModel)
470
    {
471
        /** @var Section $sourceModel */
472
        $sites = Craft::$app->getSites()->getAllSites();
473
        /** @var Site $site */
474
        foreach ($sites as $site) {
475
            $seoElement = self::class;
476
            Seomatic::$plugin->metaBundles->createMetaBundleFromSeoElement($seoElement, $sourceModel, $site->id, null, true);
477
        }
478
    }
479
480
    /**
481
     * Create all the MetaBundles in the db for this Seo Element
482
     */
483
    public static function createAllContentMetaBundles()
484
    {
485
        // Get all of the sections with URLs
486
        $sections = Craft::$app->getEntries()->getAllSections();
487
        foreach ($sections as $section) {
488
            self::createContentMetaBundle($section);
489
        }
490
    }
491
492
    /**
493
     * @inheritdoc
494
     */
495
    public static function getGqlInterfaceTypeName()
496
    {
497
        return EntryInterface::getName();
498
    }
499
}
500