Passed
Push — develop ( 4ddf62...944a5f )
by Andrew
04:25
created

InstantAnalytics::iaSendPageView()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * Instant Analytics plugin for Craft CMS 3.x
4
 *
5
 * Instant Analytics brings full Google Analytics support to your Twig templates
6
 *
7
 * @link      https://nystudio107.com
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
8
 * @copyright Copyright (c) 2017 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
9
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
10
11
namespace nystudio107\instantanalytics;
12
13
use nystudio107\instantanalytics\helpers\IAnalytics;
14
use nystudio107\instantanalytics\helpers\Field as FieldHelper;
15
use nystudio107\instantanalytics\models\Settings;
16
use nystudio107\instantanalytics\services\Commerce as CommerceService;
17
use nystudio107\instantanalytics\services\IA as IAService;
18
use nystudio107\instantanalytics\variables\InstantAnalyticsVariable;
19
use nystudio107\instantanalytics\twigextensions\InstantAnalyticsTwigExtension;
20
21
use Craft;
22
use craft\base\Plugin;
23
use craft\events\PluginEvent;
24
use craft\events\RegisterUrlRulesEvent;
25
use craft\events\TemplateEvent;
26
use craft\fields\RichText;
0 ignored issues
show
Bug introduced by
The type craft\fields\RichText 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...
27
use craft\fields\Redactor;
0 ignored issues
show
Bug introduced by
The type craft\fields\Redactor 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...
28
use craft\helpers\UrlHelper;
29
use craft\services\Plugins;
30
use craft\web\twig\variables\CraftVariable;
31
use craft\web\UrlManager;
32
use craft\web\View;
33
34
use craft\commerce\Plugin as Commerce;
0 ignored issues
show
Bug introduced by
The type craft\commerce\Plugin 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...
35
use craft\commerce\elements\Order;
0 ignored issues
show
Bug introduced by
The type craft\commerce\elements\Order 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...
36
37
use nystudio107\seomatic\Seomatic;
0 ignored issues
show
Bug introduced by
The type nystudio107\seomatic\Seomatic 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...
38
39
use yii\base\Event;
40
use yii\base\Exception;
41
42
/** @noinspection MissingPropertyAnnotationsInspection */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
43
44
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
45
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value indented incorrectly; expected 2 spaces but found 4
Loading history...
46
 * @package   InstantAnalytics
0 ignored issues
show
Coding Style introduced by
Tag value indented incorrectly; expected 1 spaces but found 3
Loading history...
47
 * @since     1.0.0
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
Coding Style introduced by
Tag value indented incorrectly; expected 3 spaces but found 5
Loading history...
48
 *
49
 * @property  IAService $ia
0 ignored issues
show
Coding Style introduced by
Tag value indented incorrectly; expected 1 spaces but found 2
Loading history...
50
 * @property  CommerceService $commerce
0 ignored issues
show
Coding Style introduced by
Tag value indented incorrectly; expected 1 spaces but found 2
Loading history...
51
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
52
class InstantAnalytics extends Plugin
53
{
54
    // Constants
55
    // =========================================================================
56
57
    const COMMERCE_PLUGIN_HANDLE = 'commerce';
58
    const SEOMATIC_PLUGIN_HANDLE = 'seomatic';
59
60
    // Static Properties
61
    // =========================================================================
62
63
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
64
     * @var InstantAnalytics
65
     */
66
    public static $plugin;
67
68
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
69
     * @var Commerce|null
70
     */
71
    public static $commercePlugin;
72
73
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
74
     * @var Seomatic|null
75
     */
76
    public static $seomaticPlugin;
77
78
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
79
     * @var string
80
     */
81
    public static $currentTemplate = '';
82
83
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
84
     * @var bool
85
     */
86
    public static $pageViewSent = false;
87
88
    // Public Methods
89
    // =========================================================================
90
91
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
92
     * @inheritdoc
93
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
94
    public function init()
95
    {
96
        parent::init();
97
        self::$plugin = $this;
98
99
        // Determine if Craft Commerce is installed & enabled
100
        self::$commercePlugin = Craft::$app->getPlugins()->getPlugin(self::COMMERCE_PLUGIN_HANDLE);
0 ignored issues
show
Documentation Bug introduced by
It seems like Craft::app->getPlugins()...COMMERCE_PLUGIN_HANDLE) can also be of type craft\base\PluginInterface. However, the property $commercePlugin is declared as type craft\commerce\Plugin|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
101
        // Determine if SEOmatic is installed & enabled
102
        self::$seomaticPlugin = Craft::$app->getPlugins()->getPlugin(self::SEOMATIC_PLUGIN_HANDLE);
0 ignored issues
show
Documentation Bug introduced by
It seems like Craft::app->getPlugins()...SEOMATIC_PLUGIN_HANDLE) can also be of type craft\base\PluginInterface. However, the property $seomaticPlugin is declared as type null|nystudio107\seomatic\Seomatic. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
103
        // Add in our Craft components
104
        $this->addComponents();
105
        // Install our global event handlers
106
        $this->installEventListeners();
107
108
        Craft::info(
109
            Craft::t(
110
                'instant-analytics',
111
                '{name} plugin loaded',
112
                ['name' => $this->name]
113
            ),
114
            __METHOD__
115
        );
116
    }
117
118
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
119
     * @inheritdoc
120
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
121
    public function settingsHtml()
122
    {
123
        $commerceFields = [];
124
125
        if (self::$commercePlugin) {
126
            $productTypes = Commerce::getInstance()->getProductTypes()->getAllProductTypes();
127
128
            foreach ($productTypes as $productType) {
129
                $productFields = $this->getPullFieldsFromLayoutId($productType->fieldLayoutId);
130
                $commerceFields = \array_merge($commerceFields, $productFields);
131
132
                if ($productType->hasVariants) {
133
                    $variantFields = $this->getPullFieldsFromLayoutId($productType->variantFieldLayoutId);
134
                    $commerceFields = \array_merge($commerceFields, $variantFields);
135
                }
136
            }
137
        }
138
139
        // Rend the settings template
140
        try {
141
            return Craft::$app->getView()->renderTemplate(
142
                'instant-analytics/settings',
143
                [
144
                    'settings' => $this->getSettings(),
145
                    'commerceFields' => $commerceFields,
146
                ]
147
            );
148
        } catch (\Twig_Error_Loader $e) {
149
            Craft::error($e->getMessage(), __METHOD__);
150
        } catch (Exception $e) {
151
            Craft::error($e->getMessage(), __METHOD__);
152
        }
153
154
        return '';
155
    }
156
157
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $context should have a doc-comment as per coding-style.
Loading history...
158
     * Handle the `{% hook isSendPageView %}`
159
     *
160
     * @param array &$context
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Doc comment for parameter &$context does not match actual variable name $context
Loading history...
161
     *
162
     * @return string|null
163
     */
164
    public function iaSendPageView(/** @noinspection PhpUnusedParameterInspection */ array &$context)
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
165
    {
166
        $this->sendPageView();
167
168
        return '';
169
    }
170
171
    // Protected Methods
172
    // =========================================================================
173
174
    /**
175
     * Add in our Craft components
176
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
177
    protected function addComponents()
178
    {
179
        $view = Craft::$app->getView();
180
        // Add in our Twig extensions
181
        $view->registerTwigExtension(new InstantAnalyticsTwigExtension());
182
        // Install our template hook
183
        $view->hook('iaSendPageView', [$this, 'iaSendPageView']);
184
        // Register our variables
185
        Event::on(
186
            CraftVariable::class,
187
            CraftVariable::EVENT_INIT,
188
            function (Event $event) {
189
                /** @var CraftVariable $variable */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
190
                $variable = $event->sender;
191
                $variable->set('instantAnalytics', InstantAnalyticsVariable::class);
192
            }
193
        );
194
    }
195
196
    /**
197
     * Install our event listeners
198
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
199
    protected function installEventListeners()
200
    {
201
        // Handler: Plugins::EVENT_AFTER_INSTALL_PLUGIN
202
        Event::on(
203
            Plugins::class,
204
            Plugins::EVENT_AFTER_INSTALL_PLUGIN,
205
            function (PluginEvent $event) {
206
                if ($event->plugin === $this) {
207
                    $request = Craft::$app->getRequest();
208
                    if ($request->isCpRequest) {
209
                        Craft::$app->getResponse()->redirect(UrlHelper::cpUrl('instant-analytics/welcome'))->send();
210
                    }
211
                }
212
            }
213
        );
214
        $request = Craft::$app->getRequest();
215
        // Install only for non-console site requests
216
        if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest()) {
217
            $this->installSiteEventListeners();
218
        }
219
        // Install only for non-console AdminCP requests
220
        if ($request->getIsCpRequest() && !$request->getIsConsoleRequest()) {
221
            $this->installCpEventListeners();
222
        }
223
    }
224
225
    /**
226
     * Install site event listeners for site requests only
227
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
228
    protected function installSiteEventListeners()
229
    {
230
        // Handler: UrlManager::EVENT_REGISTER_SITE_URL_RULES
231
        Event::on(
232
            UrlManager::class,
233
            UrlManager::EVENT_REGISTER_SITE_URL_RULES,
234
            function (RegisterUrlRulesEvent $event) {
235
                Craft::debug(
236
                    'UrlManager::EVENT_REGISTER_SITE_URL_RULES',
237
                    __METHOD__
238
                );
239
                // Register our AdminCP routes
240
                $event->rules = array_merge(
241
                    $event->rules,
242
                    $this->customFrontendRoutes()
243
                );
244
            }
245
        );
246
        // Remember the name of the currently rendering template
247
        Event::on(
248
            View::class,
249
            View::EVENT_BEFORE_RENDER_PAGE_TEMPLATE,
250
            function (TemplateEvent $event) {
251
                self::$currentTemplate = $event->template;
252
            }
253
        );
254
        // Remember the name of the currently rendering template
255
        Event::on(
256
            View::class,
257
            View::EVENT_AFTER_RENDER_PAGE_TEMPLATE,
258
            function (TemplateEvent $event) {
259
                $settings = InstantAnalytics::$plugin->getSettings();
260
                if ($settings->autoSendPageView) {
261
                    $this->sendPageView();
262
                }
263
            }
264
        );
265
        // Commerce-specific hooks
266
        if (self::$commercePlugin) {
267
            Event::on(Order::class, Order::EVENT_AFTER_COMPLETE_ORDER, function (Event $e) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
268
                $order = $e->sender;
269
                $settings = InstantAnalytics::$plugin->getSettings();
270
271
                if ($settings->autoSendPurchaseComplete) {
272
                    $this->commerce->orderComplete($order);
273
                }
274
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
275
276
            Event::on(Order::class, Order::EVENT_AFTER_ADD_LINE_ITEM, function (Event $e) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
277
                $lineItem = $e->lineItem;
278
279
                $settings = InstantAnalytics::$plugin->getSettings();
280
281
                if ($settings->autoSendAddToCart) {
282
                    $this->commerce->addToCart($lineItem->order, $lineItem);
283
                }
284
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
285
286
            Event::on(Order::class, Order::EVENT_AFTER_REMOVE_LINE_ITEM, function (Event $e) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
287
                $lineItem = $e->lineItem;
288
289
                $settings = InstantAnalytics::$plugin->getSettings();
290
291
                if ($settings->autoSendRemoveFromCart) {
292
                    $this->commerce->removeFromCart($lineItem->order, $lineItem);
293
                }
294
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
295
        }
296
    }
297
298
    /**
299
     * Install site event listeners for AdminCP requests only
300
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
301
    protected function installCpEventListeners()
302
    {
303
    }
304
305
    /**
306
     * Return the custom frontend routes
307
     *
308
     * @return array
309
     */
310
    protected function customFrontendRoutes(): array
311
    {
312
        return [
313
            'instantanalytics/pageViewTrack/<filename:[-\w\.*]+>?' =>
314
                'instant-analytics/track/track-page-view-url',
315
            'instantanalytics/eventTrack/<filename:[-\w\.*]+>?' =>
316
                'instant-analytics/track/track-event-url',
317
            // Make webpack async bundle loading work out of published AssetBundles
318
            '/cpresources/instant-analytics/<resourceType:{handle}>/<fileName>' => 'instant-analytics/manifest/resource',
319
        ];
320
    }
321
322
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
323
     * @inheritdoc
324
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
325
    protected function createSettingsModel()
326
    {
327
        return new Settings();
328
    }
329
330
    // Private Methods
331
    // =========================================================================
332
333
    /**
334
     * Send a page view with the pre-loaded IAnalytics object
335
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
336
    private function sendPageView()
0 ignored issues
show
Coding Style introduced by
Private method name "InstantAnalytics::sendPageView" must be prefixed with an underscore
Loading history...
337
    {
338
        $request = Craft::$app->getRequest();
339
        if ($request->getIsSiteRequest() && !$request->getIsConsoleRequest() && !self::$pageViewSent) {
340
            self::$pageViewSent = true;
341
            /** @var IAnalytics $analytics */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
342
            $analytics = InstantAnalytics::$plugin->ia->getGlobals(self::$currentTemplate);
343
            // Bail if we have no analytics object
344
            if ($analytics === null) {
345
                return;
346
            }
347
            // If SEOmatic is installed, set the page title from it
348
            $this->setTitleFromSeomatic($analytics);
349
            // Send the page view
350
            if ($analytics) {
0 ignored issues
show
introduced by
$analytics is of type nystudio107\instantanalytics\helpers\IAnalytics, thus it always evaluated to true.
Loading history...
351
                $response = $analytics->sendPageview();
352
                Craft::info(
353
                    Craft::t(
354
                        'instant-analytics',
355
                        'pageView sent, response:: {response}',
356
                        [
357
                            'response' => print_r($response, true),
358
                        ]
359
                    ),
360
                    __METHOD__
361
                );
362
            } else {
363
                Craft::error(
364
                    Craft::t(
365
                        'instant-analytics',
366
                        'Analytics not sent because googleAnalyticsTracking is not set'
367
                    ),
368
                    __METHOD__
369
                );
370
            }
371
        }
372
    }
373
374
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $analytics should have a doc-comment as per coding-style.
Loading history...
375
     * If SEOmatic is installed, set the page title from it
376
     *
377
     * @param $analytics
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
378
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
379
    private function setTitleFromSeomatic(IAnalytics $analytics)
0 ignored issues
show
Coding Style introduced by
Private method name "InstantAnalytics::setTitleFromSeomatic" must be prefixed with an underscore
Loading history...
380
    {
381
        if (self::$seomaticPlugin && Seomatic::$settings->renderEnabled) {
382
            $titleTag = Seomatic::$plugin->title->get('title');
383
            if ($titleTag) {
384
                $titleArray = $titleTag->renderAttributes();
385
                if (!empty($titleArray['title'])) {
386
                    $analytics->setDocumentTitle($titleArray['title']);
387
                }
388
            }
389
        }
390
    }
391
392
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $layoutId should have a doc-comment as per coding-style.
Loading history...
393
     * @param $layoutId
0 ignored issues
show
Coding Style Documentation introduced by
Missing parameter name
Loading history...
394
     *
395
     * @return array
396
     */
397
    private function getPullFieldsFromLayoutId($layoutId): array
0 ignored issues
show
Coding Style introduced by
Private method name "InstantAnalytics::getPullFieldsFromLayoutId" must be prefixed with an underscore
Loading history...
398
    {
399
        $result = ['' => 'none'];
400
        $fieldLayout = Craft::$app->getFields()->getLayoutById($layoutId);
401
        if ($fieldLayout) {
402
            $result = FieldHelper::fieldsOfTypeFromLayout(FieldHelper::TEXT_FIELD_CLASS_KEY, $fieldLayout, false);
403
        }
404
405
        return $result;
406
    }
407
}
408