Issues (257)

src/seoelements/SeoEvent.php (9 issues)

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

90
            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...
91
                Craft::debug(
92
                    'CalendarsService::EVENT_AFTER_DELETE',
93
                    __METHOD__
94
                );
95
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
96
            }
97
        );
98
        BaseEvent::on(
99
            CalendarsService::class,
100
            CalendarsService::EVENT_AFTER_DELETE,
101
            function(DeleteModelEvent $event) {
0 ignored issues
show
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

101
            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...
102
                Craft::debug(
103
                    'CalendarsService::EVENT_AFTER_DELETE',
104
                    __METHOD__
105
                );
106
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
107
            }
108
        );
109
110
        // Install for all non-console requests
111
        if (!$request->getIsConsoleRequest()) {
112
            // Handler: CalendarsService::EVENT_AFTER_SAVE
113
            BaseEvent::on(
114
                CalendarsService::class,
115
                CalendarsService::EVENT_AFTER_SAVE,
116
                function(SaveModelEvent $event) {
117
                    Craft::debug(
118
                        'CalendarsService::EVENT_AFTER_SAVE',
119
                        __METHOD__
120
                    );
121
                    /** @var CalendarModel $calendarModel */
122
                    $calendarModel = $event->getModel();
123
                    if ($calendarModel !== null && $calendarModel->id !== null) {
124
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
125
                            SeoEvent::getMetaBundleType(),
126
                            $calendarModel->id,
127
                            $event->isNew()
128
                        );
129
                        // Create the meta bundles for this Event if it's new
130
                        if ($event->isNew()) {
131
                            SeoEvent::createContentMetaBundle($calendarModel);
132
                            Seomatic::$plugin->sitemaps->submitSitemapIndex();
133
                        }
134
                    }
135
                }
136
            );
137
            // Handler: CalendarsService::EVENT_AFTER_DELETE
138
            BaseEvent::on(
139
                CalendarsService::class,
140
                CalendarsService::EVENT_AFTER_DELETE,
141
                function(DeleteModelEvent $event) {
142
                    Craft::debug(
143
                        'CalendarsService::EVENT_AFTER_DELETE',
144
                        __METHOD__
145
                    );
146
                    /** @var CalendarModel $calendarModel */
147
                    $calendarModel = $event->getModel();
148
                    if ($calendarModel !== null && $calendarModel->id !== null) {
149
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
150
                            SeoEvent::getMetaBundleType(),
151
                            $calendarModel->id,
152
                            false
153
                        );
154
                        // Delete the meta bundles for this Event
155
                        Seomatic::$plugin->metaBundles->deleteMetaBundleBySourceId(
156
                            SeoEvent::getMetaBundleType(),
157
                            $calendarModel->id
158
                        );
159
                    }
160
                }
161
            );
162
        }
163
164
        // Install only for non-console site requests
165
        if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
166
        }
167
168
        // Handler: Entry::EVENT_DEFINE_SIDEBAR_HTML
169
        BaseEvent::on(
170
            Event::class,
171
            Event::EVENT_DEFINE_SIDEBAR_HTML,
172
            static function(DefineHtmlEvent $event) {
173
                Craft::debug(
174
                    'Entry::EVENT_DEFINE_SIDEBAR_HTML',
175
                    __METHOD__
176
                );
177
                $html = '';
178
                Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
0 ignored issues
show
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

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