Issues (587)

src/InstantAnalytics.php (1 issue)

1
<?php
2
/**
3
 * Instant Analytics plugin for Craft CMS
4
 *
5
 * Instant Analytics brings full Google Analytics support to your Twig templates
6
 *
7
 * @link      https://nystudio107.com
8
 * @copyright Copyright (c) 2017 nystudio107
9
 */
10
11
namespace nystudio107\instantanalytics;
12
13
use Craft;
14
use craft\base\Plugin;
15
use craft\commerce\elements\Order;
16
use craft\commerce\events\LineItemEvent;
17
use craft\commerce\Plugin as Commerce;
18
use craft\events\PluginEvent;
19
use craft\events\RegisterUrlRulesEvent;
20
use craft\events\TemplateEvent;
21
use craft\helpers\UrlHelper;
22
use craft\services\Plugins;
23
use craft\web\twig\variables\CraftVariable;
24
use craft\web\UrlManager;
25
use craft\web\View;
26
use nystudio107\instantanalytics\helpers\Field as FieldHelper;
27
use nystudio107\instantanalytics\helpers\IAnalytics;
28
use nystudio107\instantanalytics\models\Settings;
29
use nystudio107\instantanalytics\services\ServicesTrait;
30
use nystudio107\instantanalytics\twigextensions\InstantAnalyticsTwigExtension;
31
use nystudio107\instantanalytics\variables\InstantAnalyticsVariable;
32
use nystudio107\seomatic\Seomatic;
33
use Twig\Error\LoaderError;
34
use yii\base\Event;
35
36
/** @noinspection MissingPropertyAnnotationsInspection */
37
38
/**
39
 * @author    nystudio107
40
 * @package   InstantAnalytics
41
 * @since     1.0.0
42
 */
43
class InstantAnalytics extends Plugin
44
{
45
    // Traits
46
    // =========================================================================
47
48
    use ServicesTrait;
49
50
    // Constants
51
    // =========================================================================
52
53
    const COMMERCE_PLUGIN_HANDLE = 'commerce';
54
    const SEOMATIC_PLUGIN_HANDLE = 'seomatic';
55
56
    // Static Properties
57
    // =========================================================================
58
59
    /**
60
     * @var InstantAnalytics
61
     */
62
    public static $plugin;
63
64
    /**
65
     * @var Settings
66
     */
67
    public static $settings;
68
69
    /**
70
     * @var Commerce|null
71
     */
72
    public static $commercePlugin;
73
74
    /**
75
     * @var Seomatic|null
76
     */
77
    public static $seomaticPlugin;
78
79
    /**
80
     * @var string
81
     */
82
    public static $currentTemplate = '';
83
84
    /**
85
     * @var bool
86
     */
87
    public static $pageViewSent = false;
88
89
    /**
90
     * @var bool
91
     */
92
    public static $craft31 = false;
93
94
    // Public Properties
95
    // =========================================================================
96
97
    /**
98
     * @var string
99
     */
100
    public $schemaVersion = '1.0.0';
101
102
    /**
103
     * @var bool
104
     */
105
    public $hasCpSection = false;
106
107
    /**
108
     * @var bool
109
     */
110
    public $hasCpSettings = true;
111
112
    // Public Methods
113
    // =========================================================================
114
115
    /**
116
     * @inheritdoc
117
     */
118
    public function init()
119
    {
120
        parent::init();
121
        self::$plugin = $this;
122
        self::$settings = $this->getSettings();
123
        self::$craft31 = version_compare(Craft::$app->getVersion(), '3.1', '>=');
124
125
        // Determine if Craft Commerce is installed & enabled
126
        self::$commercePlugin = Craft::$app->getPlugins()->getPlugin(self::COMMERCE_PLUGIN_HANDLE);
127
        // Determine if SEOmatic is installed & enabled
128
        self::$seomaticPlugin = Craft::$app->getPlugins()->getPlugin(self::SEOMATIC_PLUGIN_HANDLE);
129
        // Add in our Craft components
130
        $this->addComponents();
131
        // Install our global event handlers
132
        $this->installEventListeners();
133
134
        Craft::info(
135
            Craft::t(
136
                'instant-analytics',
137
                '{name} plugin loaded',
138
                ['name' => $this->name]
139
            ),
140
            __METHOD__
141
        );
142
    }
143
144
    /**
145
     * @inheritdoc
146
     */
147
    public function settingsHtml()
148
    {
149
        $commerceFields = [];
150
151
        if (self::$commercePlugin) {
152
            $productTypes = self::$commercePlugin->getProductTypes()->getAllProductTypes();
153
154
            foreach ($productTypes as $productType) {
155
                $productFields = $this->getPullFieldsFromLayoutId($productType->fieldLayoutId);
156
                /** @noinspection SlowArrayOperationsInLoopInspection */
157
                $commerceFields = \array_merge($commerceFields, $productFields);
158
                if ($productType->hasVariants) {
159
                    $variantFields = $this->getPullFieldsFromLayoutId($productType->variantFieldLayoutId);
160
                    /** @noinspection SlowArrayOperationsInLoopInspection */
161
                    $commerceFields = \array_merge($commerceFields, $variantFields);
162
                }
163
            }
164
        }
165
166
        // Rend the settings template
167
        try {
168
            return Craft::$app->getView()->renderTemplate(
169
                'instant-analytics/settings',
170
                [
171
                    'settings' => $this->getSettings(),
172
                    'commerceFields' => $commerceFields,
173
                ]
174
            );
175
        } catch (LoaderError $e) {
176
            Craft::error($e->getMessage(), __METHOD__);
177
        } catch (\Exception $e) {
178
            Craft::error($e->getMessage(), __METHOD__);
179
        }
180
181
        return '';
182
    }
183
184
    /**
185
     * Handle the `{% hook iaSendPageView %}`
186
     *
187
     * @param array &$context
188
     *
189
     * @return string|null
190
     */
191
    public function iaSendPageView(/** @noinspection PhpUnusedParameterInspection */ array &$context)
192
    {
193
        $this->sendPageView();
194
195
        return '';
196
    }
197
198
    // Protected Methods
199
    // =========================================================================
200
201
    /**
202
     * Add in our Craft components
203
     */
204
    protected function addComponents()
205
    {
206
        $view = Craft::$app->getView();
207
        // Add in our Twig extensions
208
        $view->registerTwigExtension(new InstantAnalyticsTwigExtension());
209
        // Install our template hook
210
        $view->hook('iaSendPageView', [$this, 'iaSendPageView']);
211
        // Register our variables
212
        Event::on(
213
            CraftVariable::class,
214
            CraftVariable::EVENT_INIT,
215
            function (Event $event) {
216
                /** @var CraftVariable $variable */
217
                $variable = $event->sender;
218
                $variable->set('instantAnalytics', [
219
                    'class' => InstantAnalyticsVariable::class,
220
                    'viteService' => $this->vite,
221
                ]);
222
            }
223
        );
224
    }
225
226
    /**
227
     * Install our event listeners
228
     */
229
    protected function installEventListeners()
230
    {
231
        // Handler: Plugins::EVENT_AFTER_INSTALL_PLUGIN
232
        Event::on(
233
            Plugins::class,
234
            Plugins::EVENT_AFTER_INSTALL_PLUGIN,
235
            function (PluginEvent $event) {
236
                if ($event->plugin === $this) {
237
                    $request = Craft::$app->getRequest();
238
                    if ($request->isCpRequest) {
239
                        Craft::$app->getResponse()->redirect(UrlHelper::cpUrl('instant-analytics/welcome'))->send();
240
                    }
241
                }
242
            }
243
        );
244
        $request = Craft::$app->getRequest();
245
        // Install only for non-console site requests
246
        if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
247
            $this->installSiteEventListeners();
248
        }
249
        // Install only for non-console Control Panel requests
250
        if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
251
            $this->installCpEventListeners();
252
        }
253
    }
254
255
    /**
256
     * Install site event listeners for site requests only
257
     */
258
    protected function installSiteEventListeners()
259
    {
260
        // Handler: UrlManager::EVENT_REGISTER_SITE_URL_RULES
261
        Event::on(
262
            UrlManager::class,
263
            UrlManager::EVENT_REGISTER_SITE_URL_RULES,
264
            function (RegisterUrlRulesEvent $event) {
265
                Craft::debug(
266
                    'UrlManager::EVENT_REGISTER_SITE_URL_RULES',
267
                    __METHOD__
268
                );
269
                // Register our Control Panel routes
270
                $event->rules = array_merge(
271
                    $event->rules,
272
                    $this->customFrontendRoutes()
273
                );
274
            }
275
        );
276
        // Remember the name of the currently rendering template
277
        Event::on(
278
            View::class,
279
            View::EVENT_BEFORE_RENDER_PAGE_TEMPLATE,
280
            function (TemplateEvent $event) {
281
                self::$currentTemplate = $event->template;
282
            }
283
        );
284
        // Remember the name of the currently rendering template
285
        Event::on(
286
            View::class,
287
            View::EVENT_AFTER_RENDER_PAGE_TEMPLATE,
288
            function (TemplateEvent $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

288
            function (/** @scrutinizer ignore-unused */ TemplateEvent $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...
289
                if (self::$settings->autoSendPageView) {
290
                    $this->sendPageView();
291
                }
292
            }
293
        );
294
        // Commerce-specific hooks
295
        if (self::$commercePlugin) {
296
            Event::on(Order::class, Order::EVENT_AFTER_COMPLETE_ORDER, function (Event $e) {
297
                $order = $e->sender;
298
                if (self::$settings->autoSendPurchaseComplete) {
299
                    $this->commerce->orderComplete($order);
300
                }
301
            });
302
303
            Event::on(Order::class, Order::EVENT_AFTER_ADD_LINE_ITEM, function (LineItemEvent $e) {
304
                $lineItem = $e->lineItem;
305
                if (self::$settings->autoSendAddToCart) {
306
                    $this->commerce->addToCart($lineItem->order, $lineItem);
307
                }
308
            });
309
310
            // Check to make sure Order::EVENT_AFTER_REMOVE_LINE_ITEM is defined
311
            if (defined(Order::class . '::EVENT_AFTER_REMOVE_LINE_ITEM')) {
312
                Event::on(Order::class, Order::EVENT_AFTER_REMOVE_LINE_ITEM, function (LineItemEvent $e) {
313
                    $lineItem = $e->lineItem;
314
                    if (self::$settings->autoSendRemoveFromCart) {
315
                        $this->commerce->removeFromCart($lineItem->order, $lineItem);
316
                    }
317
                });
318
            }
319
        }
320
    }
321
322
    /**
323
     * Install site event listeners for Control Panel requests only
324
     */
325
    protected function installCpEventListeners()
326
    {
327
    }
328
329
    /**
330
     * Return the custom frontend routes
331
     *
332
     * @return array
333
     */
334
    protected function customFrontendRoutes(): array
335
    {
336
        return [
337
            'instantanalytics/pageViewTrack/<filename:[-\w\.*]+>?' =>
338
                'instant-analytics/track/track-page-view-url',
339
            'instantanalytics/eventTrack/<filename:[-\w\.*]+>?' =>
340
                'instant-analytics/track/track-event-url',
341
        ];
342
    }
343
344
    /**
345
     * @inheritdoc
346
     */
347
    protected function createSettingsModel()
348
    {
349
        return new Settings();
350
    }
351
352
    // Private Methods
353
    // =========================================================================
354
355
    /**
356
     * Send a page view with the pre-loaded IAnalytics object
357
     */
358
    private function sendPageView()
359
    {
360
        $request = Craft::$app->getRequest();
361
        if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest() && !self::$pageViewSent) {
362
            self::$pageViewSent = true;
363
            /** @var IAnalytics $analytics */
364
            $analytics = self::$plugin->ia->getGlobals(self::$currentTemplate);
365
            // Bail if we have no analytics object
366
            if ($analytics === null) {
367
                return;
368
            }
369
            // If SEOmatic is installed, set the page title from it
370
            $this->setTitleFromSeomatic($analytics);
371
            // Send the page view
372
            $response = $analytics->sendPageview();
373
            Craft::info(
374
                Craft::t(
375
                    'instant-analytics',
376
                    'pageView sent, response:: {response}',
377
                    [
378
                        'response' => print_r($response, true),
379
                    ]
380
                ),
381
                __METHOD__
382
            );
383
        } else {
384
            Craft::error(
385
                Craft::t(
386
                    'instant-analytics',
387
                    'Analytics not sent because googleAnalyticsTracking is not set'
388
                ),
389
                __METHOD__
390
            );
391
        }
392
    }
393
394
    /**
395
     * If SEOmatic is installed, set the page title from it
396
     *
397
     * @param $analytics
398
     */
399
    private function setTitleFromSeomatic(IAnalytics $analytics)
400
    {
401
        if (self::$seomaticPlugin && Seomatic::$settings->renderEnabled) {
402
            $titleTag = Seomatic::$plugin->title->get('title');
403
            if ($titleTag) {
404
                $titleArray = $titleTag->renderAttributes();
405
                if (!empty($titleArray['title'])) {
406
                    $analytics->setDocumentTitle($titleArray['title']);
407
                }
408
            }
409
        }
410
    }
411
412
    /**
413
     * @param $layoutId
414
     *
415
     * @return array
416
     */
417
    private function getPullFieldsFromLayoutId($layoutId): array
418
    {
419
        $result = ['' => 'none'];
420
        if ($layoutId === null) {
421
            return $result;
422
        }
423
        $fieldLayout = Craft::$app->getFields()->getLayoutById($layoutId);
424
        if ($fieldLayout) {
425
            $result = FieldHelper::fieldsOfTypeFromLayout(FieldHelper::TEXT_FIELD_CLASS_KEY, $fieldLayout, false);
426
        }
427
428
        return $result;
429
    }
430
}
431