SeoEvent::getMetaBundleType()   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
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
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\models\Site;
19
use Exception;
20
use nystudio107\seomatic\assetbundles\seomatic\SeomaticAsset;
21
use nystudio107\seomatic\base\GqlSeoElementInterface;
22
use nystudio107\seomatic\base\SeoElementInterface;
23
use nystudio107\seomatic\helpers\ArrayHelper;
24
use nystudio107\seomatic\helpers\Config as ConfigHelper;
25
use nystudio107\seomatic\helpers\PluginTemplate;
26
use nystudio107\seomatic\models\MetaBundle;
27
use nystudio107\seomatic\Seomatic;
28
use Solspace\Calendar\Bundles\GraphQL\Interfaces\EventInterface;
29
use Solspace\Calendar\Calendar as CalendarPlugin;
30
use Solspace\Calendar\Elements\Db\EventQuery;
31
use Solspace\Calendar\Elements\Event;
32
use Solspace\Calendar\Events\DeleteModelEvent;
33
use Solspace\Calendar\Events\SaveModelEvent;
34
use Solspace\Calendar\Models\CalendarModel;
35
use Solspace\Calendar\Services\CalendarsService;
36
use yii\base\Event as BaseEvent;
37
38
/**
39
 * @author    nystudio107
40
 * @package   Seomatic
41
 * @since     3.2.0
42
 */
43
class SeoEvent implements SeoElementInterface, GqlSeoElementInterface
44
{
45
    // Constants
46
    // =========================================================================
47
48
    const META_BUNDLE_TYPE = 'event';
49
    const ELEMENT_CLASSES = [
50
        Event::class,
51
    ];
52
    const REQUIRED_PLUGIN_HANDLE = 'calendar';
53
    const CONFIG_FILE_PATH = 'eventmeta/Bundle';
54
55
    // Public Static Methods
56
    // =========================================================================
57
58
    /**
59
     * Returns an array of the element classes that are handled by this SeoElement
60
     *
61
     * @return array
62
     */
63
    public static function getElementClasses(): array
64
    {
65
        return self::ELEMENT_CLASSES;
66
    }
67
68
    /**
69
     * Return the handle to a required plugin for this SeoElement type
70
     *
71
     * @return null|string
72
     */
73
    public static function getRequiredPluginHandle()
74
    {
75
        return self::REQUIRED_PLUGIN_HANDLE;
76
    }
77
78
    /**
79
     * Install any event handlers for this SeoElement type
80
     */
81
    public static function installEventHandlers()
82
    {
83
        $request = Craft::$app->getRequest();
84
85
        // Install for all requests
86
        BaseEvent::on(
87
            CalendarsService::class,
88
            CalendarsService::EVENT_AFTER_SAVE,
89
            function(SaveModelEvent $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

89
            function(/** @scrutinizer ignore-unused */ SaveModelEvent $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...
90
                Craft::debug(
91
                    'CalendarsService::EVENT_AFTER_DELETE',
92
                    __METHOD__
93
                );
94
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
95
            }
96
        );
97
        BaseEvent::on(
98
            CalendarsService::class,
99
            CalendarsService::EVENT_AFTER_DELETE,
100
            function(DeleteModelEvent $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

100
            function(/** @scrutinizer ignore-unused */ DeleteModelEvent $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...
101
                Craft::debug(
102
                    'CalendarsService::EVENT_AFTER_DELETE',
103
                    __METHOD__
104
                );
105
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
106
            }
107
        );
108
109
        // Install for all non-console requests
110
        if (!$request->getIsConsoleRequest()) {
111
            // Handler: CalendarsService::EVENT_AFTER_SAVE
112
            BaseEvent::on(
113
                CalendarsService::class,
114
                CalendarsService::EVENT_AFTER_SAVE,
115
                function(SaveModelEvent $event) {
116
                    Craft::debug(
117
                        'CalendarsService::EVENT_AFTER_SAVE',
118
                        __METHOD__
119
                    );
120
                    /** @var CalendarModel $calendarModel */
121
                    $calendarModel = $event->getModel();
122
                    if ($calendarModel !== null && $calendarModel->id !== null) {
123
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
124
                            SeoEvent::getMetaBundleType(),
125
                            $calendarModel->id,
126
                            $event->isNew()
127
                        );
128
                        // Create the meta bundles for this Event if it's new
129
                        if ($event->isNew()) {
130
                            SeoEvent::createContentMetaBundle($calendarModel);
131
                            Seomatic::$plugin->sitemaps->submitSitemapIndex();
132
                        }
133
                    }
134
                }
135
            );
136
            // Handler: CalendarsService::EVENT_AFTER_DELETE
137
            BaseEvent::on(
138
                CalendarsService::class,
139
                CalendarsService::EVENT_AFTER_DELETE,
140
                function(DeleteModelEvent $event) {
141
                    Craft::debug(
142
                        'CalendarsService::EVENT_AFTER_DELETE',
143
                        __METHOD__
144
                    );
145
                    /** @var CalendarModel $calendarModel */
146
                    $calendarModel = $event->getModel();
147
                    if ($calendarModel !== null && $calendarModel->id !== null) {
148
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
149
                            SeoEvent::getMetaBundleType(),
150
                            $calendarModel->id,
151
                            false
152
                        );
153
                        // Delete the meta bundles for this Event
154
                        Seomatic::$plugin->metaBundles->deleteMetaBundleBySourceId(
155
                            SeoEvent::getMetaBundleType(),
156
                            $calendarModel->id
157
                        );
158
                    }
159
                }
160
            );
161
        }
162
163
        // Install only for non-console site requests
164
        if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
165
        }
166
167
        // Install only for non-console Control Panel requests
168
        if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
169
            // Events sidebar
170
            Seomatic::$view->hook('cp.solspace.calendar.events.edit.details', function(&$context) {
171
                $html = '';
172
                Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
173
                /** @var Event $event */
174
                $event = $context[self::getElementRefHandle()] ?? null;
175
                if ($event !== null && $event->uri !== null) {
176
                    Seomatic::$plugin->metaContainers->previewMetaContainers($event->uri, $event->siteId, true);
177
                    // Render our preview sidebar template
178
                    if (Seomatic::$settings->displayPreviewSidebar) {
179
                        $html .= PluginTemplate::renderPluginTemplate('_sidebars/event-preview.twig');
180
                    }
181
                    // Render our analysis sidebar template
182
// @TODO: This will be added an upcoming 'pro' edition
183
//                if (Seomatic::$settings->displayAnalysisSidebar) {
184
//                    $html .= PluginTemplate::renderPluginTemplate('_sidebars/event-analysis.twig');
185
//                }
186
                }
187
188
                return $html;
189
            });
190
        }
191
    }
192
193
    /**
194
     * Return the sourceBundleType for that this SeoElement handles
195
     *
196
     * @return string
197
     */
198
    public static function getMetaBundleType(): string
199
    {
200
        return self::META_BUNDLE_TYPE;
201
    }
202
203
    /**
204
     * Create a MetaBundle in the db for each site, from the passed in $sourceModel
205
     *
206
     * @param Model $sourceModel
207
     */
208
    public static function createContentMetaBundle(Model $sourceModel)
209
    {
210
        /** @var CalendarModel $sourceModel */
211
        $sites = Craft::$app->getSites()->getAllSites();
212
        /** @var Site $site */
213
        foreach ($sites as $site) {
214
            $seoElement = self::class;
215
            Seomatic::$plugin->metaBundles->createMetaBundleFromSeoElement($seoElement, $sourceModel, $site->id, null, true);
216
        }
217
    }
218
219
    /**
220
     * Return the refHandle (e.g.: `entry` or `category`) for the SeoElement
221
     *
222
     * @return string
223
     */
224
    public static function getElementRefHandle(): string
225
    {
226
        return Event::refHandle() ?? 'event';
0 ignored issues
show
Bug introduced by
Are you sure the usage of Solspace\Calendar\Elements\Event::refHandle() targeting craft\base\Element::refHandle() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
227
    }
228
229
    /**
230
     * Return an ElementQuery for the sitemap elements for the given MetaBundle
231
     *
232
     * @param MetaBundle $metaBundle
233
     *
234
     * @return ElementQueryInterface
235
     */
236
    public static function sitemapElementsQuery(MetaBundle $metaBundle): ElementQueryInterface
237
    {
238
        /** @var EventQuery $query */
239
        $query = Event::find();
240
        $query
241
            ->setCalendar($metaBundle->sourceHandle)
242
            ->setLoadOccurrences(false)
243
            ->siteId((int)$metaBundle->sourceSiteId)
244
            ->limit((int)$metaBundle->metaSitemapVars->sitemapLimit);
245
246
        return $query;
247
    }
248
249
    /**
250
     * Return an ElementInterface for the sitemap alt element for the given MetaBundle
251
     * and Element ID
252
     *
253
     * @param MetaBundle $metaBundle
254
     * @param int $elementId
255
     * @param int $siteId
256
     *
257
     * @return null|ElementInterface
258
     */
259
    public static function sitemapAltElement(
260
        MetaBundle $metaBundle,
261
        int        $elementId,
262
        int        $siteId
263
    ) {
264
        return Event::find()
0 ignored issues
show
Bug Best Practice introduced by
The expression return Solspace\Calendar...iteId)->limit(1)->one() also could return the type array which is incompatible with the documented return type craft\base\ElementInterface|null.
Loading history...
265
            ->id($elementId)
266
            ->siteId($siteId)
267
            ->limit(1)
268
            ->one();
269
    }
270
271
    /**
272
     * Return a preview URI for a given $sourceHandle and $siteId
273
     * This just returns the first element
274
     *
275
     * @param string $sourceHandle
276
     * @param int|null $siteId
277
     * @param int|string|null $typeId
278
     *
279
     * @return ?string
280
     */
281
    public static function previewUri(string $sourceHandle, $siteId, $typeId = null)
282
    {
283
        $uri = null;
284
        /** @var EventQuery $query */
285
        $query = Event::find();
286
        $element = $query
287
            ->setCalendar($sourceHandle)
288
            ->siteId($siteId)
289
            ->one();
290
        if ($element) {
291
            $uri = $element->uri;
0 ignored issues
show
Bug introduced by
Accessing uri on the interface craft\base\ElementInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
292
        }
293
294
        return $uri;
295
    }
296
297
    /**
298
     * Return an array of FieldLayouts from the $sourceHandle
299
     *
300
     * @param string $sourceHandle
301
     * @param int|string|null $typeId
302
     *
303
     * @return array
304
     */
305
    public static function fieldLayouts(string $sourceHandle, $typeId = null): array
306
    {
307
        $layouts = [];
308
        $calendar = CalendarPlugin::getInstance();
309
        if ($calendar !== null) {
310
            $layoutId = null;
311
            try {
312
                $calendarModel = $calendar->calendars->getCalendarByHandle($sourceHandle);
313
                if ($calendarModel) {
314
                    $layoutId = $calendarModel->fieldLayoutId;
315
                }
316
            } catch (Exception $e) {
317
                $layoutId = null;
318
            }
319
            if ($layoutId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $layoutId 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...
320
                $layouts[] = Craft::$app->getFields()->getLayoutById($layoutId);
321
            }
322
        }
323
324
        return $layouts;
325
    }
326
327
    /**
328
     * Return the (entry) type menu as a $id => $name associative array
329
     *
330
     * @param string $sourceHandle
331
     *
332
     * @return array
333
     */
334
    public static function typeMenuFromHandle(string $sourceHandle): array
335
    {
336
        return [];
337
    }
338
339
    /**
340
     * Return the source model of the given $sourceId
341
     *
342
     * @param int $sourceId
343
     *
344
     * @return CalendarModel|null
345
     */
346
    public static function sourceModelFromId(int $sourceId)
347
    {
348
        $calendarModel = null;
349
        $calendar = CalendarPlugin::getInstance();
350
        if ($calendar !== null) {
351
            $calendarModel = $calendar->calendars->getCalendarById($sourceId);
352
        }
353
354
        return $calendarModel;
355
    }
356
357
    /**
358
     * Return the source model of the given $sourceId
359
     *
360
     * @param string $sourceHandle
361
     *
362
     * @return CalendarModel|null
363
     */
364
    public static function sourceModelFromHandle(string $sourceHandle)
365
    {
366
        $calendarModel = null;
367
        $calendar = CalendarPlugin::getInstance();
368
        if ($calendar !== null) {
369
            $calendarModel = $calendar->calendars->getCalendarByHandle($sourceHandle);
370
        }
371
372
        return $calendarModel;
373
    }
374
375
    /**
376
     * Return the most recently updated Element from a given source model
377
     *
378
     * @param CalendarModel $sourceModel
379
     * @param int $sourceSiteId
380
     *
381
     * @return null|ElementInterface
382
     */
383
    public static function mostRecentElement(Model $sourceModel, int $sourceSiteId)
384
    {
385
        /** @var EventQuery $query */
386
        $query = Event::find();
387
        return $query
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->setCalend...ents\SORT_DESC))->one() also could return the type array which is incompatible with the documented return type craft\base\ElementInterface|null.
Loading history...
388
            ->setCalendar($sourceModel->handle)
389
            ->siteId($sourceSiteId)
390
            ->limit(1)
391
            ->orderBy(['elements.dateUpdated' => SORT_DESC])
392
            ->one();
393
    }
394
395
    /**
396
     * Return a meta bundle config array for the given $sourceModel
397
     *
398
     * @param Model $sourceModel
399
     *
400
     * @return array
401
     */
402
    public static function metaBundleConfig(Model $sourceModel): array
403
    {
404
        /** @var CalendarModel $sourceModel */
405
        return ArrayHelper::merge(
406
            ConfigHelper::getConfigFromFile(self::configFilePath()),
407
            [
408
                'sourceId' => $sourceModel->id,
409
                'sourceName' => (string)$sourceModel->name,
410
                'sourceHandle' => $sourceModel->handle,
411
            ]
412
        );
413
    }
414
415
    /**
416
     * Return the path to the config file directory
417
     *
418
     * @return string
419
     */
420
    public static function configFilePath(): string
421
    {
422
        return self::CONFIG_FILE_PATH;
423
    }
424
425
    /**
426
     * Return the source id from the $element
427
     *
428
     * @param ElementInterface $element
429
     *
430
     * @return int|null
431
     */
432
    public static function sourceIdFromElement(ElementInterface $element)
433
    {
434
        /** @var Event $element */
435
        return $element->calendarId;
436
    }
437
438
    /**
439
     * Return the (entry) type id from the $element
440
     *
441
     * @param ElementInterface $element
442
     *
443
     * @return int|null
444
     */
445
    public static function typeIdFromElement(ElementInterface $element)
446
    {
447
        /** @var Event $element */
448
        return null;
449
    }
450
451
    /**
452
     * Return the source handle from the $element
453
     *
454
     * @param ElementInterface $element
455
     *
456
     * @return string|null
457
     */
458
    public static function sourceHandleFromElement(ElementInterface $element)
459
    {
460
        $sourceHandle = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $sourceHandle is dead and can be removed.
Loading history...
461
        /** @var Event $element */
462
        try {
463
            $sourceHandle = $element->getCalendar()->handle;
464
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
465
        }
466
467
        return $sourceHandle;
468
    }
469
470
    /**
471
     * Create all the MetaBundles in the db for this Seo Element
472
     */
473
    public static function createAllContentMetaBundles()
474
    {
475
        $calendar = CalendarPlugin::getInstance();
476
        if ($calendar !== null) {
477
            // Get all of the calendars with URLs
478
            $calendarModels = $calendar->calendars->getAllCalendars();
479
            foreach ($calendarModels as $calendarModel) {
480
                self::createContentMetaBundle($calendarModel);
481
            }
482
        }
483
    }
484
485
    /**
486
     * @inheritdoc
487
     */
488
    public static function getGqlInterfaceTypeName()
489
    {
490
        return EventInterface::getName();
491
    }
492
}
493