Issues (257)

src/seoelements/SeoCampaign.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\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\integrations\campaign\behaviors\CampaignBehavior;
27
use nystudio107\seomatic\models\MetaBundle;
28
use nystudio107\seomatic\Seomatic;
29
use putyourlightson\campaign\Campaign;
30
use putyourlightson\campaign\elements\CampaignElement;
31
use putyourlightson\campaign\events\CampaignTypeEvent;
32
use putyourlightson\campaign\models\CampaignTypeModel;
33
use putyourlightson\campaign\services\CampaignTypesService;
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 SeoCampaign implements SeoElementInterface
43
{
44
    // Constants
45
    // =========================================================================
46
47
    public const META_BUNDLE_TYPE = 'campaign';
48
    public const ELEMENT_CLASSES = [
49
        CampaignElement::class,
50
    ];
51
    public const REQUIRED_PLUGIN_HANDLE = 'campaign';
52
    public const CONFIG_FILE_PATH = 'campaignmeta/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 CampaignElement::refHandle() ?? 'campaign';
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
            CampaignTypesService::class,
107
            CampaignTypesService::EVENT_AFTER_SAVE_CAMPAIGN_TYPE,
108
            function(CampaignTypeEvent $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

108
            function(/** @scrutinizer ignore-unused */ CampaignTypeEvent $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
                    'CampaignTypesService::EVENT_AFTER_SAVE_CAMPAIGN_TYPE',
111
                    __METHOD__
112
                );
113
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
114
            }
115
        );
116
        Event::on(
117
            CampaignTypesService::class,
118
            CampaignTypesService::EVENT_AFTER_DELETE_CAMPAIGN_TYPE,
119
            function(CampaignTypeEvent $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

119
            function(/** @scrutinizer ignore-unused */ CampaignTypeEvent $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
                    'CampaignTypesService::EVENT_AFTER_DELETE_CAMPAIGN_TYPE',
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: CampaignTypesService::EVENT_AFTER_SAVE_CAMPAIGN_TYPE
131
            Event::on(
132
                CampaignTypesService::class,
133
                CampaignTypesService::EVENT_AFTER_SAVE_CAMPAIGN_TYPE,
134
                function(CampaignTypeEvent $event) {
135
                    Craft::debug(
136
                        'CampaignTypesService::EVENT_AFTER_SAVE_CAMPAIGN_TYPE',
137
                        __METHOD__
138
                    );
139
                    if ($event->campaignType !== null && $event->campaignType->id !== null) {
140
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
141
                            SeoCampaign::getMetaBundleType(),
142
                            $event->campaignType->id,
143
                            $event->isNew
144
                        );
145
                        // Create the meta bundles for this campaign type if it's new
146
                        if ($event->isNew) {
147
                            SeoCampaign::createContentMetaBundle($event->campaignType);
148
                            Seomatic::$plugin->sitemaps->submitSitemapIndex();
149
                        }
150
                    }
151
                }
152
            );
153
            // Handler: CampaignTypesService::EVENT_AFTER_DELETE_CAMPAIGN_TYPE
154
            Event::on(
155
                CampaignTypesService::class,
156
                CampaignTypesService::EVENT_AFTER_DELETE_CAMPAIGN_TYPE,
157
                function(CampaignTypeEvent $event) {
158
                    Craft::debug(
159
                        'CampaignTypesService::EVENT_AFTER_DELETE_CAMPAIGN_TYPE',
160
                        __METHOD__
161
                    );
162
                    if ($event->campaignType !== null && $event->campaignType->id !== null) {
163
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
164
                            SeoCampaign::getMetaBundleType(),
165
                            $event->campaignType->id,
166
                            false
167
                        );
168
                        // Delete the meta bundles for this campaign type
169
                        Seomatic::$plugin->metaBundles->deleteMetaBundleBySourceId(
170
                            SeoCampaign::getMetaBundleType(),
171
                            $event->campaignType->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
            CampaignElement::class,
185
            CampaignElement::EVENT_DEFINE_SIDEBAR_HTML,
186
            static function(DefineHtmlEvent $event) {
187
                Craft::debug(
188
                    'CampaignElement::EVENT_DEFINE_SIDEBAR_HTML',
189
                    __METHOD__
190
                );
191
                $html = '';
192
                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

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 CampaignElement $campaign */
194
                $campaign = $event->sender ?? null;
195
                if ($campaign !== null && $campaign->uri !== null) {
196
                    Seomatic::$plugin->metaContainers->previewMetaContainers($campaign->uri, $campaign->siteId, true);
197
                    // Render our preview sidebar template
198
                    if (Seomatic::$settings->displayPreviewSidebar && Seomatic::$matchedElement) {
199
                        $html .= PluginTemplate::renderPluginTemplate('_sidebars/campaign-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/campaign-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 = CampaignElement::find()
222
            ->campaignType($metaBundle->sourceHandle)
223
            ->siteId($metaBundle->sourceSiteId)
224
            ->limit($metaBundle->metaSitemapVars->sitemapLimit);
225
226
        return $query;
227
    }
228
229
    /**
230
     * Return an ElementInterface for the sitemap alt element for the given MetaBundle
231
     * and Element ID
232
     *
233
     * @param MetaBundle $metaBundle
234
     * @param int $elementId
235
     * @param int $siteId
236
     *
237
     * @return null|ElementInterface
238
     */
239
    public static function sitemapAltElement(
240
        MetaBundle $metaBundle,
241
        int        $elementId,
242
        int        $siteId,
243
    ) {
244
        return CampaignElement::find()
0 ignored issues
show
Bug Best Practice introduced by
The expression return putyourlightson\c...iteId)->limit(1)->one() also could return the type array which is incompatible with the documented return type craft\base\ElementInterface|null.
Loading history...
245
            ->campaignType($metaBundle->sourceHandle)
246
            ->id($elementId)
247
            ->siteId($siteId)
248
            ->limit(1)
249
            ->one();
250
    }
251
252
    /**
253
     * Return a preview URI for a given $sourceHandle and $siteId
254
     * This just returns the first element
255
     *
256
     * @param string $sourceHandle
257
     * @param int|null $siteId
258
     * @param int|string|null $typeId
259
     *
260
     * @return ?string
261
     */
262
    public static function previewUri(string $sourceHandle, $siteId, $typeId = null): ?string
263
    {
264
        $uri = null;
265
        $element = CampaignElement::find()
266
            ->campaignType($sourceHandle)
267
            ->siteId($siteId)
268
            ->one();
269
        if ($element) {
270
            $uri = $element->uri;
271
        }
272
273
        return $uri;
274
    }
275
276
    /**
277
     * Return an array of FieldLayouts from the $sourceHandle
278
     *
279
     * @param string $sourceHandle
280
     * @param int|string|null $typeId
281
     *
282
     * @return array
283
     */
284
    public static function fieldLayouts(string $sourceHandle, $typeId = null): array
285
    {
286
        $layouts = [];
287
        try {
288
            $campaignType = Campaign::$plugin->campaignTypes->getCampaignTypeByHandle($sourceHandle);
289
            if ($campaignType) {
290
                $layouts[] = $campaignType->getFieldLayout();
291
            }
292
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
293
        }
294
295
        return $layouts;
296
    }
297
298
    /**
299
     * Return the (entry) type menu as a $id => $name associative array
300
     *
301
     * @param string $sourceHandle
302
     *
303
     * @return array
304
     */
305
    public static function typeMenuFromHandle(string $sourceHandle): array
306
    {
307
        return [];
308
    }
309
310
    /**
311
     * Return the source model of the given $sourceId
312
     *
313
     * @param int $sourceId
314
     *
315
     * @return CampaignTypeModel|null
316
     */
317
    public static function sourceModelFromId(int $sourceId)
318
    {
319
        // Attach a behavior to implement ::getSiteSettings() which the CampaignTypeModel lacks
320
        $sourceModel = Campaign::$plugin->campaignTypes->getCampaignTypeById($sourceId);
321
        if ($sourceModel) {
322
            $sourceModel->attachBehavior('SEOmaticCampaignBehavior', CampaignBehavior::class);
323
        }
324
325
        return $sourceModel;
326
    }
327
328
    /**
329
     * Return the source model of the given $sourceId
330
     *
331
     * @param string $sourceHandle
332
     *
333
     * @return CampaignTypeModel|null
334
     */
335
    public static function sourceModelFromHandle(string $sourceHandle)
336
    {
337
        // Attach a behavior to implement ::getSiteSettings() which the CampaignTypeModel lacks
338
        $sourceModel = Campaign::$plugin->campaignTypes->getCampaignTypeByHandle($sourceHandle);
339
        if ($sourceModel) {
340
            $sourceModel->attachBehavior('SEOmaticCampaignBehavior', CampaignBehavior::class);
341
        }
342
343
        return $sourceModel;
344
    }
345
346
    /**
347
     * Return the most recently updated Element from a given source model
348
     *
349
     * @param Model $sourceModel
350
     * @param int $sourceSiteId
351
     *
352
     * @return null|ElementInterface
353
     */
354
    public static function mostRecentElement(Model $sourceModel, int $sourceSiteId)
355
    {
356
        /** @var CampaignTypeModel $sourceModel */
357
        return CampaignElement::find()
0 ignored issues
show
Bug Best Practice introduced by
The expression return putyourlightson\c...ents\SORT_DESC))->one() also could return the type array which is incompatible with the documented return type craft\base\ElementInterface|null.
Loading history...
358
            ->campaignType($sourceModel->handle)
359
            ->siteId($sourceSiteId)
360
            ->limit(1)
361
            ->orderBy(['elements.dateUpdated' => SORT_DESC])
362
            ->one();
363
    }
364
365
    /**
366
     * Return the path to the config file directory
367
     *
368
     * @return string
369
     */
370
    public static function configFilePath(): string
371
    {
372
        return self::CONFIG_FILE_PATH;
373
    }
374
375
    /**
376
     * Return a meta bundle config array for the given $sourceModel
377
     *
378
     * @param Model $sourceModel
379
     *
380
     * @return array
381
     */
382
    public static function metaBundleConfig(Model $sourceModel): array
383
    {
384
        /** @var CampaignTypeModel $sourceModel */
385
        return ArrayHelper::merge(
386
            ConfigHelper::getConfigFromFile(self::configFilePath()),
387
            [
388
                'sourceId' => $sourceModel->id,
389
                'sourceName' => (string)$sourceModel->name,
390
                'sourceHandle' => $sourceModel->handle,
391
            ]
392
        );
393
    }
394
395
    /**
396
     * Return the source id from the $element
397
     *
398
     * @param ElementInterface $element
399
     *
400
     * @return int|null
401
     */
402
    public static function sourceIdFromElement(ElementInterface $element)
403
    {
404
        /** @var CampaignElement $element */
405
        return $element->campaignTypeId;
406
    }
407
408
    /**
409
     * Return the (entry) type id from the $element
410
     *
411
     * @param ElementInterface $element
412
     *
413
     * @return int|null
414
     */
415
    public static function typeIdFromElement(ElementInterface $element)
416
    {
417
        return null;
418
    }
419
420
    /**
421
     * Return the source handle from the $element
422
     *
423
     * @param ElementInterface $element
424
     *
425
     * @return string|null
426
     */
427
    public static function sourceHandleFromElement(ElementInterface $element)
428
    {
429
        $sourceHandle = '';
0 ignored issues
show
The assignment to $sourceHandle is dead and can be removed.
Loading history...
430
        /** @var CampaignElement $element */
431
        try {
432
            $sourceHandle = $element->getCampaignType()->handle;
433
        } catch (InvalidConfigException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
434
        }
435
436
        return $sourceHandle;
437
    }
438
439
    /**
440
     * Create a MetaBundle in the db for each site, from the passed in $sourceModel
441
     *
442
     * @param Model $sourceModel
443
     */
444
    public static function createContentMetaBundle(Model $sourceModel)
445
    {
446
        /** @var CampaignTypeModel $sourceModel */
447
        $sourceModel->attachBehavior('SEOmaticCampaignBehavior', CampaignBehavior::class);
448
        $sites = Craft::$app->getSites()->getAllSites();
449
        /** @var Site $site */
450
        foreach ($sites as $site) {
451
            $seoElement = self::class;
452
            Seomatic::$plugin->metaBundles->createMetaBundleFromSeoElement($seoElement, $sourceModel, $site->id, null, true);
453
        }
454
    }
455
456
    /**
457
     * Create all the MetaBundles in the db for this Seo Element
458
     */
459
    public static function createAllContentMetaBundles()
460
    {
461
        // Get all of the campaign types with URLs
462
        $campaignTypes = Campaign::$plugin->campaignTypes->getAllCampaignTypes();
463
        foreach ($campaignTypes as $campaignType) {
464
            self::createContentMetaBundle($campaignType);
465
        }
466
    }
467
}
468