SeoEntry::typeIdFromElement()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * SEOmatic plugin for Craft CMS 3.x
4
 *
5
 * A turnkey SEO implementation for Craft CMS that is comprehensive, powerful,
6
 * and flexible
7
 *
8
 * @link      https://nystudio107.com
9
 * @copyright Copyright (c) 2019 nystudio107
10
 */
11
12
namespace nystudio107\seomatic\seoelements;
13
14
use Craft;
15
use craft\base\ElementInterface;
16
use craft\base\Model;
17
use craft\elements\db\ElementQueryInterface;
18
use craft\elements\Entry;
19
use craft\events\SectionEvent;
20
use craft\gql\interfaces\elements\Entry as EntryInterface;
21
use craft\models\EntryDraft;
22
use craft\models\EntryVersion;
23
use craft\models\Section;
24
use craft\models\Site;
25
use craft\services\Sections;
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
    const META_BUNDLE_TYPE = 'section';
48
    const ELEMENT_CLASSES = [
49
        Entry::class,
50
        EntryDraft::class,
51
        EntryVersion::class,
52
    ];
53
    const REQUIRED_PLUGIN_HANDLE = null;
54
    const CONFIG_FILE_PATH = 'entrymeta/Bundle';
55
56
    // Public Static Methods
57
    // =========================================================================
58
59
    /**
60
     * Return the sourceBundleType for that this SeoElement handles
61
     *
62
     * @return string
63
     */
64
    public static function getMetaBundleType(): string
65
    {
66
        return self::META_BUNDLE_TYPE;
67
    }
68
69
    /**
70
     * Returns an array of the element classes that are handled by this SeoElement
71
     *
72
     * @return array
73
     */
74
    public static function getElementClasses(): array
75
    {
76
        return self::ELEMENT_CLASSES;
77
    }
78
79
    /**
80
     * Return the refHandle (e.g.: `entry` or `category`) for the SeoElement
81
     *
82
     * @return string
83
     */
84
    public static function getElementRefHandle(): string
85
    {
86
        return Entry::refHandle() ?? 'entry';
87
    }
88
89
    /**
90
     * Return the handle to a required plugin for this SeoElement type
91
     *
92
     * @return null|string
93
     */
94
    public static function getRequiredPluginHandle()
95
    {
96
        return self::REQUIRED_PLUGIN_HANDLE;
97
    }
98
99
    /**
100
     * Install any event handlers for this SeoElement type
101
     */
102
    public static function installEventHandlers()
103
    {
104
        $request = Craft::$app->getRequest();
105
106
        // Install for all requests
107
        Event::on(
108
            Sections::class,
109
            Sections::EVENT_AFTER_SAVE_SECTION,
110
            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

110
            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...
111
                Craft::debug(
112
                    'Sections::EVENT_AFTER_SAVE_SECTION',
113
                    __METHOD__
114
                );
115
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
116
            }
117
        );
118
        Event::on(
119
            Sections::class,
120
            Sections::EVENT_AFTER_DELETE_SECTION,
121
            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

121
            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...
122
                Craft::debug(
123
                    'Sections::EVENT_AFTER_DELETE_SECTION',
124
                    __METHOD__
125
                );
126
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
127
            }
128
        );
129
130
        // Install for all non-console requests
131
        if (!$request->getIsConsoleRequest()) {
132
            // Handler: Sections::EVENT_AFTER_SAVE_SECTION
133
            Event::on(
134
                Sections::class,
135
                Sections::EVENT_AFTER_SAVE_SECTION,
136
                function(SectionEvent $event) {
137
                    Craft::debug(
138
                        'Sections::EVENT_AFTER_SAVE_SECTION',
139
                        __METHOD__
140
                    );
141
                    if ($event->section !== null && $event->section->id !== null) {
142
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
143
                            SeoEntry::getMetaBundleType(),
144
                            $event->section->id,
145
                            $event->isNew
146
                        );
147
                        // Create the meta bundles for this section if it's new
148
                        if ($event->isNew) {
149
                            SeoEntry::createContentMetaBundle($event->section);
150
                            Seomatic::$plugin->sitemaps->submitSitemapIndex();
151
                        }
152
                    }
153
                }
154
            );
155
            // Handler: Sections::EVENT_AFTER_DELETE_SECTION
156
            Event::on(
157
                Sections::class,
158
                Sections::EVENT_AFTER_DELETE_SECTION,
159
                function(SectionEvent $event) {
160
                    Craft::debug(
161
                        'Sections::EVENT_AFTER_DELETE_SECTION',
162
                        __METHOD__
163
                    );
164
                    if ($event->section !== null && $event->section->id !== null) {
165
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
166
                            SeoEntry::getMetaBundleType(),
167
                            $event->section->id,
168
                            false
169
                        );
170
                        // Delete the meta bundles for this section
171
                        Seomatic::$plugin->metaBundles->deleteMetaBundleBySourceId(
172
                            SeoEntry::getMetaBundleType(),
173
                            $event->section->id
174
                        );
175
                    }
176
                }
177
            );
178
        }
179
180
        // Install only for non-console site requests
181
        if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
182
        }
183
184
        // Install only for non-console Control Panel requests
185
        if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
186
            // Entries sidebar
187
            Seomatic::$view->hook('cp.entries.edit.details', function(&$context) {
188
                $html = '';
189
                Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
190
                /** @var Entry $entry */
191
                $entry = $context[self::getElementRefHandle()] ?? null;
192
                if ($entry !== null && $entry->uri !== null) {
193
                    Seomatic::$plugin->metaContainers->previewMetaContainers($entry->uri, $entry->siteId, true, true, $entry);
194
                    // Render our preview sidebar template
195
                    if (Seomatic::$settings->displayPreviewSidebar && Seomatic::$matchedElement) {
196
                        $html .= PluginTemplate::renderPluginTemplate('_sidebars/entry-preview.twig');
197
                    }
198
                    // Render our analysis sidebar template
199
// @TODO: This will be added an upcoming 'pro' edition
200
//                if (Seomatic::$settings->displayAnalysisSidebar && Seomatic::$matchedElement) {
201
//                    $html .= PluginTemplate::renderPluginTemplate('_sidebars/entry-analysis.twig');
202
//                }
203
                }
204
205
                return $html;
206
            });
207
        }
208
    }
209
210
    /**
211
     * Return an ElementQuery for the sitemap elements for the given MetaBundle
212
     *
213
     * @param MetaBundle $metaBundle
214
     *
215
     * @return ElementQueryInterface
216
     */
217
    public static function sitemapElementsQuery(MetaBundle $metaBundle): ElementQueryInterface
218
    {
219
        $query = Entry::find()
220
            ->section($metaBundle->sourceHandle)
221
            ->siteId($metaBundle->sourceSiteId)
222
            ->limit($metaBundle->metaSitemapVars->sitemapLimit);
223
        if ($metaBundle->sourceType === 'structure'
224
            && !empty($metaBundle->metaSitemapVars->structureDepth)) {
225
            $query->level($metaBundle->metaSitemapVars->structureDepth . '<=');
226
        }
227
228
        return $query;
229
    }
230
231
    /**
232
     * Return an ElementInterface for the sitemap alt element for the given MetaBundle
233
     * and Element ID
234
     *
235
     * @param MetaBundle $metaBundle
236
     * @param int $elementId
237
     * @param int $siteId
238
     *
239
     * @return null|ElementInterface
240
     */
241
    public static function sitemapAltElement(
242
        MetaBundle $metaBundle,
243
        int        $elementId,
244
        int        $siteId
245
    ) {
246
        return Entry::find()
0 ignored issues
show
Bug Best Practice introduced by
The expression return craft\elements\En...iteId)->limit(1)->one() also could return the type array which is incompatible with the documented return type craft\base\ElementInterface|null.
Loading history...
247
            ->section($metaBundle->sourceHandle)
248
            ->id($elementId)
249
            ->siteId($siteId)
250
            ->limit(1)
251
            ->one();
252
    }
253
254
    /**
255
     * Return a preview URI for a given $sourceHandle and $siteId
256
     * This just returns the first element
257
     *
258
     * @param string $sourceHandle
259
     * @param int|null $siteId
260
     * @param int|string|null $typeId
261
     *
262
     * @return ?string
263
     */
264
    public static function previewUri(string $sourceHandle, $siteId, $typeId = null)
265
    {
266
        $uri = null;
267
        $query = Entry::find()
268
            ->section($sourceHandle)
269
            ->siteId($siteId);
270
        if (!empty($typeId)) {
271
            $query
272
                ->andWhere([
273
                    'typeId' => (int)$typeId,
274
                ]);
275
        }
276
        $element = $query->one();
277
        if ($element) {
278
            $uri = $element->uri;
279
        }
280
281
        return $uri;
282
    }
283
284
    /**
285
     * Return an array of FieldLayouts from the $sourceHandle
286
     *
287
     * @param string $sourceHandle
288
     * @param int|string|null $typeId
289
     *
290
     * @return array
291
     */
292
    public static function fieldLayouts(string $sourceHandle, $typeId = null): array
293
    {
294
        $layouts = [];
295
        $section = Craft::$app->getSections()->getSectionByHandle($sourceHandle);
296
        if ($section) {
297
            $entryTypes = $section->getEntryTypes();
298
            foreach ($entryTypes as $entryType) {
299
                if ($entryType->fieldLayoutId && ($entryType->id == $typeId || empty($typeId))) {
300
                    $layouts[] = Craft::$app->getFields()->getLayoutById($entryType->fieldLayoutId);
301
                }
302
            }
303
        }
304
305
        return $layouts;
306
    }
307
308
    /**
309
     * Return the (entry) type menu as a $id => $name associative array
310
     *
311
     * @param string $sourceHandle
312
     *
313
     * @return array
314
     */
315
    public static function typeMenuFromHandle(string $sourceHandle): array
316
    {
317
        $typeMenu = [];
318
319
        $section = self::sourceModelFromHandle($sourceHandle);
320
        if ($section !== null) {
321
            $entryTypes = $section->getEntryTypes();
322
            foreach ($entryTypes as $entryType) {
323
                $typeMenu[$entryType->id] = $entryType->name;
324
            }
325
        }
326
327
        return $typeMenu;
328
    }
329
330
    /**
331
     * Return the source model of the given $sourceId
332
     *
333
     * @param int $sourceId
334
     *
335
     * @return Section|null
336
     */
337
    public static function sourceModelFromId(int $sourceId)
338
    {
339
        return Craft::$app->getSections()->getSectionById($sourceId);
340
    }
341
342
    /**
343
     * Return the source model of the given $sourceId
344
     *
345
     * @param string $sourceHandle
346
     *
347
     * @return Section|null
348
     */
349
    public static function sourceModelFromHandle(string $sourceHandle)
350
    {
351
        return Craft::$app->getSections()->getSectionByHandle($sourceHandle);
352
    }
353
354
    /**
355
     * Return the most recently updated Element from a given source model
356
     *
357
     * @param Model $sourceModel
358
     * @param int $sourceSiteId
359
     *
360
     * @return null|ElementInterface
361
     */
362
    public static function mostRecentElement(Model $sourceModel, int $sourceSiteId)
363
    {
364
        /** @var Section $sourceModel */
365
        return Entry::find()
0 ignored issues
show
Bug Best Practice introduced by
The expression return craft\elements\En...ents\SORT_DESC))->one() also could return the type array which is incompatible with the documented return type craft\base\ElementInterface|null.
Loading history...
366
            ->section($sourceModel->handle)
367
            ->siteId($sourceSiteId)
368
            ->limit(1)
369
            ->orderBy(['elements.dateUpdated' => SORT_DESC])
370
            ->one();
371
    }
372
373
    /**
374
     * Return the path to the config file directory
375
     *
376
     * @return string
377
     */
378
    public static function configFilePath(): string
379
    {
380
        return self::CONFIG_FILE_PATH;
381
    }
382
383
    /**
384
     * Return a meta bundle config array for the given $sourceModel
385
     *
386
     * @param Model $sourceModel
387
     *
388
     * @return array
389
     */
390
    public static function metaBundleConfig(Model $sourceModel): array
391
    {
392
        /** @var Section $sourceModel */
393
        return ArrayHelper::merge(
394
            ConfigHelper::getConfigFromFile(self::configFilePath()),
395
            [
396
                'sourceId' => $sourceModel->id,
397
                'sourceName' => (string)$sourceModel->name,
398
                'sourceHandle' => $sourceModel->handle,
399
                'sourceType' => $sourceModel->type,
400
            ]
401
        );
402
    }
403
404
    /**
405
     * Return the source id from the $element
406
     *
407
     * @param ElementInterface $element
408
     *
409
     * @return int|null
410
     */
411
    public static function sourceIdFromElement(ElementInterface $element)
412
    {
413
        /** @var Entry $element */
414
        return $element->sectionId;
415
    }
416
417
    /**
418
     * Return the (entry) type id from the $element
419
     *
420
     * @param ElementInterface $element
421
     *
422
     * @return int|null
423
     */
424
    public static function typeIdFromElement(ElementInterface $element)
425
    {
426
        /** @var Entry $element */
427
        return $element->typeId;
428
    }
429
430
    /**
431
     * Return the source handle from the $element
432
     *
433
     * @param ElementInterface $element
434
     *
435
     * @return string|null
436
     */
437
    public static function sourceHandleFromElement(ElementInterface $element)
438
    {
439
        $sourceHandle = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $sourceHandle is dead and can be removed.
Loading history...
440
        /** @var Entry $element */
441
        try {
442
            $sourceHandle = $element->getSection()->handle;
443
        } catch (InvalidConfigException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
444
        }
445
446
        return $sourceHandle;
447
    }
448
449
    /**
450
     * Create a MetaBundle in the db for each site, from the passed in $sourceModel
451
     *
452
     * @param Model $sourceModel
453
     */
454
    public static function createContentMetaBundle(Model $sourceModel)
455
    {
456
        /** @var Section $sourceModel */
457
        $sites = Craft::$app->getSites()->getAllSites();
458
        /** @var Site $site */
459
        foreach ($sites as $site) {
460
            $seoElement = self::class;
461
            Seomatic::$plugin->metaBundles->createMetaBundleFromSeoElement($seoElement, $sourceModel, $site->id, null, true);
462
        }
463
    }
464
465
    /**
466
     * Create all the MetaBundles in the db for this Seo Element
467
     */
468
    public static function createAllContentMetaBundles()
469
    {
470
        // Get all of the sections with URLs
471
        $sections = Craft::$app->getSections()->getAllSections();
472
        foreach ($sections as $section) {
473
            self::createContentMetaBundle($section);
474
        }
475
    }
476
477
    /**
478
     * @inheritdoc
479
     */
480
    public static function getGqlInterfaceTypeName()
481
    {
482
        return EntryInterface::getName();
483
    }
484
}
485