SeoEntry::configFilePath()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

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

191
                Seomatic::$view->/** @scrutinizer ignore-call */ 
192
                                 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...
192
                /** @var Entry $entry */
193
                $entry = $event->sender ?? null;
194
                if ($entry !== null && $entry->uri !== null) {
195
                    Seomatic::$plugin->metaContainers->previewMetaContainers($entry->uri, $entry->siteId, true, true, $entry);
196
                    // Render our preview sidebar template
197
                    if (Seomatic::$settings->displayPreviewSidebar && Seomatic::$matchedElement) {
198
                        $html .= PluginTemplate::renderPluginTemplate('_sidebars/entry-preview.twig');
199
                    }
200
                    // Render our analysis sidebar template
201
// @TODO: This will be added an upcoming 'pro' edition
202
//                if (Seomatic::$settings->displayAnalysisSidebar && Seomatic::$matchedElement) {
203
//                    $html .= PluginTemplate::renderPluginTemplate('_sidebars/entry-analysis.twig');
204
//                }
205
                }
206
                $event->html .= $html;
207
            }
208
        );
209
    }
210
211
    /**
212
     * Return an ElementQuery for the sitemap elements for the given MetaBundle
213
     *
214
     * @param MetaBundle $metaBundle
215
     *
216
     * @return ElementQueryInterface
217
     */
218
    public static function sitemapElementsQuery(MetaBundle $metaBundle): ElementQueryInterface
219
    {
220
        $query = Entry::find()
221
            ->section($metaBundle->sourceHandle)
222
            ->siteId($metaBundle->sourceSiteId)
223
            ->limit($metaBundle->metaSitemapVars->sitemapLimit);
224
        if ($metaBundle->sourceType === 'structure'
225
            && !empty($metaBundle->metaSitemapVars->structureDepth)) {
226
            $query->level('<=' . $metaBundle->metaSitemapVars->structureDepth);
227
        }
228
229
        return $query;
230
    }
231
232
    /**
233
     * Return an ElementInterface for the sitemap alt element for the given MetaBundle
234
     * and Element ID
235
     *
236
     * @param MetaBundle $metaBundle
237
     * @param int $elementId
238
     * @param int $siteId
239
     *
240
     * @return null|ElementInterface
241
     */
242
    public static function sitemapAltElement(
243
        MetaBundle $metaBundle,
244
        int        $elementId,
245
        int        $siteId,
246
    ) {
247
        return Entry::find()
248
            ->section($metaBundle->sourceHandle)
249
            ->id($elementId)
250
            ->siteId($siteId)
251
            ->limit(1)
252
            ->one();
253
    }
254
255
    /**
256
     * Return a preview URI for a given $sourceHandle and $siteId
257
     * This just returns the first element
258
     *
259
     * @param string $sourceHandle
260
     * @param int|null $siteId
261
     * @param int|string|null $typeId
262
     *
263
     * @return ?string
264
     */
265
    public static function previewUri(string $sourceHandle, $siteId, $typeId = null): ?string
266
    {
267
        $uri = null;
268
        $query = Entry::find()
269
            ->section($sourceHandle)
270
            ->siteId($siteId);
271
        if (!empty($typeId)) {
272
            $query
273
                ->andWhere([
274
                    'typeId' => (int)$typeId,
275
                ]);
276
        }
277
        $element = $query->one();
278
        if ($element) {
279
            $uri = $element->uri;
280
        }
281
282
        return $uri;
283
    }
284
285
    /**
286
     * Return an array of FieldLayouts from the $sourceHandle
287
     *
288
     * @param string $sourceHandle
289
     * @param int|string|null $typeId
290
     *
291
     * @return array
292
     */
293
    public static function fieldLayouts(string $sourceHandle, $typeId = null): array
294
    {
295
        $layouts = [];
296
        $section = Craft::$app->getSections()->getSectionByHandle($sourceHandle);
297
        if ($section) {
298
            $entryTypes = $section->getEntryTypes();
299
            foreach ($entryTypes as $entryType) {
300
                if ($entryType->fieldLayoutId && ($entryType->id == $typeId || empty($typeId))) {
301
                    $layouts[] = Craft::$app->getFields()->getLayoutById($entryType->fieldLayoutId);
302
                }
303
            }
304
        }
305
306
        return $layouts;
307
    }
308
309
    /**
310
     * Return the (entry) type menu as a $id => $name associative array
311
     *
312
     * @param string $sourceHandle
313
     *
314
     * @return array
315
     */
316
    public static function typeMenuFromHandle(string $sourceHandle): array
317
    {
318
        $typeMenu = [];
319
320
        $section = self::sourceModelFromHandle($sourceHandle);
321
        if ($section !== null) {
322
            $entryTypes = $section->getEntryTypes();
323
            foreach ($entryTypes as $entryType) {
324
                $typeMenu[$entryType->id] = $entryType->name;
325
            }
326
        }
327
328
        return $typeMenu;
329
    }
330
331
    /**
332
     * Return the source model of the given $sourceId
333
     *
334
     * @param int $sourceId
335
     *
336
     * @return Section|null
337
     */
338
    public static function sourceModelFromId(int $sourceId)
339
    {
340
        return Craft::$app->getSections()->getSectionById($sourceId);
341
    }
342
343
    /**
344
     * Return the source model of the given $sourceId
345
     *
346
     * @param string $sourceHandle
347
     *
348
     * @return Section|null
349
     */
350
    public static function sourceModelFromHandle(string $sourceHandle)
351
    {
352
        return Craft::$app->getSections()->getSectionByHandle($sourceHandle);
353
    }
354
355
    /**
356
     * Return the most recently updated Element from a given source model
357
     *
358
     * @param Model $sourceModel
359
     * @param int $sourceSiteId
360
     *
361
     * @return null|ElementInterface
362
     */
363
    public static function mostRecentElement(Model $sourceModel, int $sourceSiteId)
364
    {
365
        /** @var Section $sourceModel */
366
        return Entry::find()
367
            ->section($sourceModel->handle)
368
            ->siteId($sourceSiteId)
369
            ->limit(1)
370
            ->orderBy(['elements.dateUpdated' => SORT_DESC])
371
            ->one();
372
    }
373
374
    /**
375
     * Return the path to the config file directory
376
     *
377
     * @return string
378
     */
379
    public static function configFilePath(): string
380
    {
381
        return self::CONFIG_FILE_PATH;
382
    }
383
384
    /**
385
     * Return a meta bundle config array for the given $sourceModel
386
     *
387
     * @param Model $sourceModel
388
     *
389
     * @return array
390
     */
391
    public static function metaBundleConfig(Model $sourceModel): array
392
    {
393
        /** @var Section $sourceModel */
394
        return ArrayHelper::merge(
395
            ConfigHelper::getConfigFromFile(self::configFilePath()),
396
            [
397
                'sourceId' => $sourceModel->id,
398
                'sourceName' => (string)$sourceModel->name,
399
                'sourceHandle' => $sourceModel->handle,
400
                'sourceType' => $sourceModel->type,
401
            ]
402
        );
403
    }
404
405
    /**
406
     * Return the source id from the $element
407
     *
408
     * @param ElementInterface $element
409
     *
410
     * @return int|null
411
     */
412
    public static function sourceIdFromElement(ElementInterface $element)
413
    {
414
        /** @var Entry $element */
415
        return $element->sectionId;
416
    }
417
418
    /**
419
     * Return the (entry) type id from the $element
420
     *
421
     * @param ElementInterface $element
422
     *
423
     * @return int|null
424
     */
425
    public static function typeIdFromElement(ElementInterface $element)
426
    {
427
        /** @var Entry $element */
428
        return $element->typeId;
429
    }
430
431
    /**
432
     * Return the source handle from the $element
433
     *
434
     * @param ElementInterface $element
435
     *
436
     * @return string|null
437
     */
438
    public static function sourceHandleFromElement(ElementInterface $element)
439
    {
440
        $sourceHandle = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $sourceHandle is dead and can be removed.
Loading history...
441
        /** @var Entry $element */
442
        try {
443
            $sourceHandle = $element->getSection()->handle;
444
        } catch (InvalidConfigException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
445
        }
446
447
        return $sourceHandle;
448
    }
449
450
    /**
451
     * Create a MetaBundle in the db for each site, from the passed in $sourceModel
452
     *
453
     * @param Model $sourceModel
454
     */
455
    public static function createContentMetaBundle(Model $sourceModel)
456
    {
457
        /** @var Section $sourceModel */
458
        $sites = Craft::$app->getSites()->getAllSites();
459
        /** @var Site $site */
460
        foreach ($sites as $site) {
461
            $seoElement = self::class;
462
            Seomatic::$plugin->metaBundles->createMetaBundleFromSeoElement($seoElement, $sourceModel, $site->id, null, true);
463
        }
464
    }
465
466
    /**
467
     * Create all the MetaBundles in the db for this Seo Element
468
     */
469
    public static function createAllContentMetaBundles()
470
    {
471
        // Get all of the sections with URLs
472
        $sections = Craft::$app->getSections()->getAllSections();
473
        foreach ($sections as $section) {
474
            self::createContentMetaBundle($section);
475
        }
476
    }
477
478
    /**
479
     * @inheritdoc
480
     */
481
    public static function getGqlInterfaceTypeName()
482
    {
483
        return EntryInterface::getName();
484
    }
485
}
486