Issues (260)

src/seoelements/SeoDigitalProduct.php (1 issue)

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) 2020 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\digitalproducts\elements\Product;
18
use craft\digitalproducts\events\ProductTypeEvent;
19
use craft\digitalproducts\gql\interfaces\elements\Product as DigitalProductInterface;
20
use craft\digitalproducts\models\ProductType;
21
use craft\digitalproducts\Plugin as DigitalProductsPlugin;
22
use craft\digitalproducts\services\ProductTypes;
23
use craft\elements\db\ElementQueryInterface;
24
use craft\events\DefineHtmlEvent;
25
use craft\models\Site;
26
use Exception;
27
use nystudio107\seomatic\assetbundles\seomatic\SeomaticAsset;
28
use nystudio107\seomatic\base\GqlSeoElementInterface;
29
use nystudio107\seomatic\base\SeoElementInterface;
30
use nystudio107\seomatic\helpers\ArrayHelper;
31
use nystudio107\seomatic\helpers\Config as ConfigHelper;
32
use nystudio107\seomatic\helpers\PluginTemplate;
33
use nystudio107\seomatic\models\MetaBundle;
34
use nystudio107\seomatic\Seomatic;
35
use yii\base\Event;
36
37
/**
38
 * @author    nystudio107
39
 * @package   Seomatic
40
 * @since     3.3.4
41
 */
42
class SeoDigitalProduct implements SeoElementInterface, GqlSeoElementInterface
43
{
44
    // Constants
45
    // =========================================================================
46
47
    public const META_BUNDLE_TYPE = 'digitalproduct';
48
    public const ELEMENT_CLASSES = [
49
        Product::class,
50
    ];
51
    public const REQUIRED_PLUGIN_HANDLE = 'digital-products';
52
    public const CONFIG_FILE_PATH = 'digitalproductmeta/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 Product::refHandle() ?? 'product';
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
            ProductTypes::class,
107
            ProductTypes::EVENT_AFTER_SAVE_PRODUCTTYPE,
108
            function(ProductTypeEvent $event) {
109
                Craft::debug(
110
                    'ProductTypes::EVENT_AFTER_SAVE_PRODUCTTYPE',
111
                    __METHOD__
112
                );
113
                Seomatic::$plugin->metaBundles->resaveMetaBundles(self::META_BUNDLE_TYPE);
114
            }
115
        );
116
117
        // Install for all non-console requests
118
        if (!$request->getIsConsoleRequest()) {
119
            // Handler: ProductTypes::EVENT_AFTER_SAVE_PRODUCTTYPE
120
            Event::on(
121
                ProductTypes::class,
122
                ProductTypes::EVENT_AFTER_SAVE_PRODUCTTYPE,
123
                /** @var ProductTypeEvent $event */
124
                static function($event) {
125
                    Craft::debug(
126
                        'ProductTypes::EVENT_AFTER_SAVE_PRODUCTTYPE',
127
                        __METHOD__
128
                    );
129
                    if ($event->productType !== null && $event->productType->id !== null) {
130
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
131
                            SeoProduct::getMetaBundleType(),
132
                            $event->productType->id,
133
                            $event->isNew
134
                        );
135
                        // Create the meta bundles for this Product Type if it's new
136
                        if ($event->isNew) {
137
                            SeoProduct::createContentMetaBundle($event->productType);
138
                            Seomatic::$plugin->sitemaps->submitSitemapIndex();
139
                        }
140
                    }
141
                }
142
            );
143
144
            /*
145
             * @TODO Sadly this event doesn't exist yet
146
            // Handler: ProductTypes::EVENT_AFTER_DELETE_PRODUCTTYPE
147
            Event::on(
148
                ProductTypes::class,
149
                ProductTypes::EVENT_AFTER_DELETE_PRODUCTTYPE,
150
                function (ProductTypeEvent $event) {
151
                    Craft::debug(
152
                        'ProductTypes::EVENT_AFTER_DELETE_PRODUCTTYPE',
153
                        __METHOD__
154
                    );
155
                    if ($event->productType !== null && $event->productType->id !== null) {
156
                        Seomatic::$plugin->metaBundles->invalidateMetaBundleById(
157
                            SeoProduct::getMetaBundleType(),
158
                            $event->productType->id,
159
                            false
160
                        );
161
                        // Delete the meta bundles for this Product Type
162
                        Seomatic::$plugin->metaBundles->deleteMetaBundleBySourceId(
163
                            SeoProduct::getMetaBundleType(),
164
                            $event->productType->id
165
                        );
166
                    }
167
                }
168
            );
169
            */
170
        }
171
172
        // Install only for non-console site requests
173
        if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
174
        }
175
176
        // Handler: Product::EVENT_DEFINE_SIDEBAR_HTML
177
        Event::on(
178
            Product::class,
179
            Product::EVENT_DEFINE_SIDEBAR_HTML,
180
            static function(DefineHtmlEvent $event) {
181
                Craft::debug(
182
                    'Product::EVENT_DEFINE_SIDEBAR_HTML',
183
                    __METHOD__
184
                );
185
                $html = '';
186
                Seomatic::$view->registerAssetBundle(SeomaticAsset::class);
187
                /** @var Product $product */
188
                $product = $event->sender ?? null;
189
                if ($product !== null && $product->uri !== null) {
190
                    Seomatic::$plugin->metaContainers->previewMetaContainers($product->uri, $product->siteId, true);
191
                    // Render our preview sidebar template
192
                    if (Seomatic::$settings->displayPreviewSidebar) {
193
                        $html .= PluginTemplate::renderPluginTemplate('_sidebars/product-preview.twig');
194
                    }
195
                    // Render our analysis sidebar template
196
// @TODO: This will be added an upcoming 'pro' edition
197
//                if (Seomatic::$settings->displayAnalysisSidebar) {
198
//                    $html .= PluginTemplate::renderPluginTemplate('_sidebars/product-analysis.twig');
199
//                }
200
                }
201
                $event->html .= $html;
202
            }
203
        );
204
    }
205
206
    /**
207
     * Return an ElementQuery for the sitemap elements for the given MetaBundle
208
     *
209
     * @param MetaBundle $metaBundle
210
     *
211
     * @return ElementQueryInterface
212
     */
213
    public static function sitemapElementsQuery(MetaBundle $metaBundle): ElementQueryInterface
214
    {
215
        $query = Product::find()
216
            ->type($metaBundle->sourceHandle)
217
            ->siteId($metaBundle->sourceSiteId)
218
            ->limit($metaBundle->metaSitemapVars->sitemapLimit);
219
220
        return $query;
221
    }
222
223
    /**
224
     * Return an ElementInterface for the sitemap alt element for the given MetaBundle
225
     * and Element ID
226
     *
227
     * @param MetaBundle $metaBundle
228
     * @param int $elementId
229
     * @param int $siteId
230
     *
231
     * @return null|ElementInterface
232
     */
233
    public static function sitemapAltElement(
234
        MetaBundle $metaBundle,
235
        int        $elementId,
236
        int        $siteId,
237
    )
238
    {
239
        return Product::find()
240
            ->id($elementId)
241
            ->siteId($siteId)
242
            ->limit(1)
243
            ->one();
244
    }
245
246
    /**
247
     * Return a preview URI for a given $sourceHandle and $siteId
248
     * This just returns the first element
249
     *
250
     * @param string $sourceHandle
251
     * @param int|null $siteId
252
     *
253
     * @return string|null
254
     */
255
    public static function previewUri(string $sourceHandle, $siteId)
256
    {
257
        $uri = null;
258
        $element = Product::find()
259
            ->type($sourceHandle)
260
            ->siteId($siteId)
261
            ->one();
262
        if ($element) {
263
            $uri = $element->uri;
264
        }
265
266
        return $uri;
267
    }
268
269
    /**
270
     * Return an array of FieldLayouts from the $sourceHandle
271
     *
272
     * @param string $sourceHandle
273
     *
274
     * @return array
275
     */
276
    public static function fieldLayouts(string $sourceHandle): array
277
    {
278
        $layouts = [];
279
        $digitalProducts = DigitalProductsPlugin::getInstance();
280
        if ($digitalProducts !== null) {
281
            $layoutId = null;
282
            try {
283
                $productType = $digitalProducts->getProductTypes()->getProductTypeByHandle($sourceHandle);
284
                if ($productType) {
285
                    $layoutId = $productType->getFieldLayoutId();
286
                }
287
            } catch (Exception $e) {
288
                $layoutId = null;
289
            }
290
            if ($layoutId) {
291
                $layouts[] = Craft::$app->getFields()->getLayoutById($layoutId);
292
            }
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 ProductType|null
316
     */
317
    public static function sourceModelFromId(int $sourceId)
318
    {
319
        $productType = null;
320
        $digitalProducts = DigitalProductsPlugin::getInstance();
321
        if ($digitalProducts !== null) {
322
            $productType = $digitalProducts->getProductTypes()->getProductTypeById($sourceId);
323
        }
324
325
        return $productType;
326
    }
327
328
    /**
329
     * Return the source model of the given $sourceHandle
330
     *
331
     * @param string $sourceHandle
332
     *
333
     * @return ProductType|null
334
     */
335
    public static function sourceModelFromHandle(string $sourceHandle)
336
    {
337
        $productType = null;
338
        $digitalProducts = DigitalProductsPlugin::getInstance();
339
        if ($digitalProducts !== null) {
340
            $productType = $digitalProducts->getProductTypes()->getProductTypeByHandle($sourceHandle);
341
        }
342
343
        return $productType;
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 ProductType $sourceModel */
357
        return Product::find()
358
            ->type($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 ProductType $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 Product $element */
405
        return $element->typeId;
406
    }
407
408
    /**
409
     * Return the (product) 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
        /** @var Product $element */
418
        return null;
419
    }
420
421
    /**
422
     * Return the source handle from the $element
423
     *
424
     * @param ElementInterface $element
425
     *
426
     * @return string|null
427
     */
428
    public static function sourceHandleFromElement(ElementInterface $element)
429
    {
430
        $sourceHandle = '';
431
        /** @var Product $element */
432
        try {
433
            $sourceHandle = $element->getType()->handle;
434
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
435
        }
436
437
        return $sourceHandle;
438
    }
439
440
    /**
441
     * Create a MetaBundle in the db for each site, from the passed in $sourceModel
442
     *
443
     * @param Model $sourceModel
444
     */
445
    public static function createContentMetaBundle(Model $sourceModel)
446
    {
447
        /** @var ProductType $sourceModel */
448
        $sites = Craft::$app->getSites()->getAllSites();
449
        /** @var Site $site */
450
        foreach ($sites as $site) {
451
            $seoElement = self::class;
452
            /** @var SeoElementInterface $seoElement */
453
            Seomatic::$plugin->metaBundles->createMetaBundleFromSeoElement($seoElement, $sourceModel, $site->id);
454
        }
455
    }
456
457
    /**
458
     * Create all the MetaBundles in the db for this Seo Element
459
     */
460
    public static function createAllContentMetaBundles()
461
    {
462
        $digitslProducts = DigitalProductsPlugin::getInstance();
463
        if ($digitslProducts !== null) {
464
            // Get all of the calendars with URLs
465
            $productTypes = $digitslProducts->getProductTypes()->getAllProductTypes();
466
            foreach ($productTypes as $productType) {
467
                self::createContentMetaBundle($productType);
468
            }
469
        }
470
    }
471
472
    /**
473
     * @inheritdoc
474
     */
475
    public static function getGqlInterfaceTypeName()
476
    {
477
        return DigitalProductInterface::getName();
478
    }
479
}
480