Completed
Push — develop ( 009567...fb0a26 )
by Nate
08:31
created

Integrations::getItemActions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 17
c 0
b 0
f 0
ccs 0
cts 15
cp 0
rs 9.7
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://github.com/flipboxfactory/craft-integration/blob/master/LICENSE
6
 * @link       https://github.com/flipboxfactory/craft-integration/
7
 */
8
9
namespace flipbox\craft\integration\fields;
10
11
use Craft;
12
use craft\base\Element;
13
use craft\base\ElementInterface;
14
use craft\base\Field;
15
use craft\helpers\Component as ComponentHelper;
16
use craft\helpers\StringHelper;
17
use flipbox\craft\ember\helpers\ModelHelper;
18
use flipbox\craft\ember\validators\MinMaxValidator;
19
use flipbox\craft\integration\events\RegisterIntegrationFieldActionsEvent;
20
use flipbox\craft\integration\fields\actions\IntegrationActionInterface;
21
use flipbox\craft\integration\fields\actions\IntegrationItemActionInterface;
22
use flipbox\craft\integration\records\IntegrationAssociation;
23
use flipbox\craft\integration\web\assets\integrations\Integrations as IntegrationsAsset;
24
use yii\db\ActiveQuery;
25
26
/**
27
 * @author Flipbox Factory <[email protected]>
28
 * @since 2.0.0
29
 */
30
abstract class Integrations extends Field
31
{
32
    use NormalizeValueTrait,
33
        ModifyElementQueryTrait;
34
35
    /**
36
     * The Plugin's translation category
37
     */
38
    const TRANSLATION_CATEGORY = '';
39
40
    /**
41
     * The action path to preform field actions
42
     */
43
    const ACTION_PREFORM_ACTION_PATH = '';
44
45
    /**
46
     * The action path to preform field actions
47
     */
48
    const ACTION_PREFORM_ITEM_ACTION_PATH = '';
49
50
    /**
51
     * The action path to associate an item
52
     */
53
    const ACTION_ASSOCIATION_ITEM_PATH = '';
54
55
    /**
56
     * The action path to dissociate an item
57
     */
58
    const ACTION_DISSOCIATION_ITEM_PATH = '';
59
60
    /**
61
     * The action path to create an integration object
62
     */
63
    const ACTION_CREATE_ITEM_PATH = '';
64
65
    /**
66
     * The action event name
67
     */
68
    const EVENT_REGISTER_ACTIONS = 'registerActions';
69
70
    /**
71
     * The action event name
72
     */
73
    const EVENT_REGISTER_AVAILABLE_ACTIONS = 'registerAvailableActions';
74
75
    /**
76
     * The item action event name
77
     */
78
    const EVENT_REGISTER_ITEM_ACTIONS = 'registerItemActions';
79
80
    /**
81
     * The item action event name
82
     */
83
    const EVENT_REGISTER_AVAILABLE_ITEM_ACTIONS = 'registerAvailableItemActions';
84
85
    /**
86
     * The default action event name
87
     */
88
    const DEFAULT_AVAILABLE_ACTIONS = [];
89
90
    /**
91
     * The default item action event name
92
     */
93
    const DEFAULT_AVAILABLE_ITEM_ACTIONS = [];
94
95
    /**
96
     * The input template path
97
     */
98
    const INPUT_TEMPLATE_PATH = '';
99
100
    /**
101
     * The input template path
102
     */
103
    const INPUT_ITEM_TEMPLATE_PATH = '_inputItem';
104
105
    /**
106
     * The input template path
107
     */
108
    const SETTINGS_TEMPLATE_PATH = '';
109
110
    /**
111
     * @var string
112
     */
113
    public $object;
114
115
    /**
116
     * @var int|null
117
     */
118
    public $min;
119
120
    /**
121
     * @var int|null
122
     */
123
    public $max;
124
125
    /**
126
     * @var string
127
     */
128
    public $viewUrl = '';
129
130
    /**
131
     * @var string
132
     */
133
    public $listUrl = '';
134
135
    /**
136
     * @var IntegrationActionInterface[]
137
     */
138
    public $selectedActions = [];
139
140
    /**
141
     * @var IntegrationItemActionInterface[]
142
     */
143
    public $selectedItemActions = [];
144
145
    /**
146
     * @var string|null
147
     */
148
    public $selectionLabel;
149
150
    /**
151
     * @return string
152
     */
153
    abstract public static function recordClass(): string;
154
155
    /*******************************************
156
     * OBJECT
157
     *******************************************/
158
159
    /**
160
     * @return string
161
     */
162
    public function getObjectLabel(): string
163
    {
164
        return StringHelper::titleize($this->object);
165
    }
166
167
    /**
168
     * @inheritdoc
169
     */
170
    public static function hasContentColumn(): bool
171
    {
172
        return false;
173
    }
174
175
    /**
176
     * @return string
177
     */
178
    public static function defaultSelectionLabel(): string
179
    {
180
        return Craft::t(static::TRANSLATION_CATEGORY, 'Add an Object');
181
    }
182
183
184
    /*******************************************
185
     * VALIDATION
186
     *******************************************/
187
188
    /**
189
     * @inheritdoc
190
     */
191
    public function getElementValidationRules(): array
192
    {
193
        return [
194
            [
195
                MinMaxValidator::class,
196
                'min' => $this->min ? (int)$this->min : null,
197
                'max' => $this->max ? (int)$this->max : null,
198
                'tooFew' => Craft::t(
199
                    static::TRANSLATION_CATEGORY,
200
                    '{attribute} should contain at least {min, number} {min, plural, one{domain} other{domains}}.'
201
                ),
202
                'tooMany' => Craft::t(
203
                    static::TRANSLATION_CATEGORY,
204
                    '{attribute} should contain at most {max, number} {max, plural, one{domain} other{domains}}.'
205
                ),
206
                'skipOnEmpty' => false
207
            ]
208
        ];
209
    }
210
211
    /*******************************************
212
     * RULES
213
     *******************************************/
214
215
    /**
216
     * @inheritdoc
217
     */
218
    public function rules()
219
    {
220
        return array_merge(
221
            parent::rules(),
222
            [
223
                [
224
                    'object',
225
                    'required',
226
                    'message' => Craft::t(static::TRANSLATION_CATEGORY, 'Object cannot be empty.')
227
                ],
228
                [
229
                    [
230
                        'object',
231
                        'min',
232
                        'max',
233
                        'viewUrl',
234
                        'listUrl',
235
                        'selectionLabel'
236
                    ],
237
                    'safe',
238
                    'on' => [
239
                        ModelHelper::SCENARIO_DEFAULT
240
                    ]
241
                ]
242
            ]
243
        );
244
    }
245
246
247
    /*******************************************
248
     * SEARCH
249
     *******************************************/
250
251
    /**
252
     * @param ActiveQuery $value
253
     * @inheritdoc
254
     */
255
    public function getSearchKeywords($value, ElementInterface $element): string
256
    {
257
        $objects = [];
258
259
        /** @var IntegrationAssociation $association */
260
        foreach ($value->all() as $association) {
261
            array_push($objects, $association->objectId);
262
        }
263
264
        return parent::getSearchKeywords($objects, $element);
265
    }
266
267
268
    /*******************************************
269
     * VIEWS
270
     *******************************************/
271
272
    /**
273
     * @inheritdoc
274
     * @param ActiveQuery $value
275
     * @throws \Twig_Error_Loader
276
     * @throws \yii\base\Exception
277
     */
278
    public function getInputHtml($value, ElementInterface $element = null): string
279
    {
280
        $value->limit(null);
281
282
        Craft::$app->getView()->registerAssetBundle(IntegrationsAsset::class);
283
284
        return Craft::$app->getView()->renderTemplate(
285
            static::INPUT_TEMPLATE_PATH,
286
            $this->inputHtmlVariables($value, $element)
287
        );
288
    }
289
290
    /**
291
     * @param ActiveQuery $query
292
     * @param ElementInterface|null $element
293
     * @param bool $static
294
     * @return array
295
     * @throws \craft\errors\MissingComponentException
296
     * @throws \yii\base\InvalidConfigException
297
     */
298
    protected function inputHtmlVariables(
299
        ActiveQuery $query,
300
        ElementInterface $element = null,
301
        bool $static = false
302
    ): array {
303
        return [
304
            'field' => $this,
305
            'element' => $element,
306
            'value' => $query,
307
            'objectLabel' => $this->getObjectLabel(),
308
            'static' => $static,
309
            'itemTemplate' => $this::INPUT_ITEM_TEMPLATE_PATH,
310
            'settings' => [
311
                'translationCategory' => $this::TRANSLATION_CATEGORY,
312
                'limit' => $this->max ? $this->max : null,
313
                'data' => [
314
                    'field' => $this->id,
315
                    'element' => $element ? $element->getId() : null
316
                ],
317
                'actions' => $this->getActionHtml($element),
318
                'actionAction' => $this::ACTION_PREFORM_ACTION_PATH,
319
                'createItemAction' => $this::ACTION_CREATE_ITEM_PATH,
320
                'itemData' => [
321
                    'field' => $this->id,
322
                    'element' => $element ? $element->getId() : null
323
                ],
324
                'itemSettings' => [
325
                    'translationCategory' => $this::TRANSLATION_CATEGORY,
326
                    'actionAction' => $this::ACTION_PREFORM_ITEM_ACTION_PATH,
327
                    'associateAction' => $this::ACTION_ASSOCIATION_ITEM_PATH,
328
                    'dissociateAction' => $this::ACTION_DISSOCIATION_ITEM_PATH,
329
                    'data' => [
330
                        'field' => $this->id,
331
                        'element' => $element ? $element->getId() : null
332
                    ],
333
                    'actions' => $this->getItemActionHtml($element),
334
                ]
335
            ]
336
        ];
337
    }
338
339
    /**
340
     * @param ElementInterface|null $element
341
     * @return array
342
     * @throws \craft\errors\MissingComponentException
343
     * @throws \yii\base\InvalidConfigException
344
     */
345
    protected function getActionHtml(ElementInterface $element = null): array
346
    {
347
        $actionData = [];
348
349
        foreach ($this->getActions($element) as $action) {
350
            $actionData[] = [
351
                'type' => get_class($action),
352
                'destructive' => $action->isDestructive(),
353
                'name' => $action->getTriggerLabel(),
354
                'trigger' => $action->getTriggerHtml(),
355
                'confirm' => $action->getConfirmationMessage(),
356
            ];
357
        }
358
359
        return $actionData;
360
    }
361
362
    /**
363
     * @param ElementInterface|null $element
364
     * @return array
365
     * @throws \craft\errors\MissingComponentException
366
     * @throws \yii\base\InvalidConfigException
367
     */
368
    protected function getItemActionHtml(ElementInterface $element = null): array
369
    {
370
        $actionData = [];
371
372
        foreach ($this->getItemActions($element) as $action) {
373
            $actionData[] = [
374
                'type' => get_class($action),
375
                'destructive' => $action->isDestructive(),
376
                'name' => $action->getTriggerLabel(),
377
                'trigger' => $action->getTriggerHtml(),
378
                'confirm' => $action->getConfirmationMessage(),
379
            ];
380
        }
381
382
        return $actionData;
383
    }
384
385
386
    /*******************************************
387
     * SETTINGS
388
     *******************************************/
389
390
    /**
391
     * @inheritdoc
392
     * @throws \Twig_Error_Loader
393
     * @throws \yii\base\Exception
394
     */
395
    public function getSettingsHtml()
396
    {
397
        return Craft::$app->getView()->renderTemplate(
398
            static::SETTINGS_TEMPLATE_PATH,
399
            $this->settingsHtmlVariables()
400
        );
401
    }
402
403
    /**
404
     * @return array
405
     * @throws \craft\errors\MissingComponentException
406
     * @throws \yii\base\InvalidConfigException
407
     */
408
    protected function settingsHtmlVariables(): array
409
    {
410
        return [
411
            'field' => $this,
412
            'availableActions' => $this->getAvailableActions(),
413
            'availableItemActions' => $this->getAvailableItemActions(),
414
            'translationCategory' => static::TRANSLATION_CATEGORY,
415
        ];
416
    }
417
418
419
    /*******************************************
420
     * ACTIONS
421
     *******************************************/
422
423
    /**
424
     * @return IntegrationActionInterface[]
425
     * @throws \craft\errors\MissingComponentException
426
     * @throws \yii\base\InvalidConfigException
427
     */
428
    public function getAvailableActions(): array
429
    {
430
        $event = new RegisterIntegrationFieldActionsEvent([
431
            'actions' => static::DEFAULT_AVAILABLE_ACTIONS
432
        ]);
433
434
        $this->trigger(
435
            $this::EVENT_REGISTER_AVAILABLE_ACTIONS,
436
            $event
437
        );
438
439
        return $this->resolveActions(
440
            array_filter((array)$event->actions),
441
            IntegrationActionInterface::class
442
        );
443
    }
444
445
    /**
446
     * @return IntegrationActionInterface[]
447
     * @throws \craft\errors\MissingComponentException
448
     * @throws \yii\base\InvalidConfigException
449
     */
450
    public function getAvailableItemActions(): array
451
    {
452
        $event = new RegisterIntegrationFieldActionsEvent([
453
            'actions' => static::DEFAULT_AVAILABLE_ITEM_ACTIONS
454
        ]);
455
456
        $this->trigger(
457
            $this::EVENT_REGISTER_AVAILABLE_ITEM_ACTIONS,
458
            $event
459
        );
460
461
        return $this->resolveActions(
462
            array_filter((array)$event->actions),
463
            IntegrationItemActionInterface::class
464
        );
465
    }
466
467
    /**
468
     * @param ElementInterface|null $element
469
     * @return IntegrationActionInterface[]
470
     * @throws \craft\errors\MissingComponentException
471
     * @throws \yii\base\InvalidConfigException
472
     */
473
    public function getActions(ElementInterface $element = null): array
474
    {
475
        $event = new RegisterIntegrationFieldActionsEvent([
476
            'actions' => $this->selectedActions,
477
            'element' => $element
478
        ]);
479
480
        $this->trigger(
481
            static::EVENT_REGISTER_ACTIONS,
482
            $event
483
        );
484
485
        return $this->resolveActions(
486
            array_filter((array)$event->actions),
487
            IntegrationActionInterface::class
488
        );
489
    }
490
491
    /**
492
     * @param ElementInterface|null $element
493
     * @return IntegrationItemActionInterface[]
494
     * @throws \craft\errors\MissingComponentException
495
     * @throws \yii\base\InvalidConfigException
496
     */
497
    public function getItemActions(ElementInterface $element = null): array
498
    {
499
        $event = new RegisterIntegrationFieldActionsEvent([
500
            'actions' => $this->selectedItemActions,
501
            'element' => $element
502
        ]);
503
504
        $this->trigger(
505
            static::EVENT_REGISTER_ITEM_ACTIONS,
506
            $event
507
        );
508
509
        return $this->resolveActions(
510
            array_filter((array)$event->actions),
511
            IntegrationItemActionInterface::class
512
        );
513
    }
514
515
    /**
516
     * @param array $actions
517
     * @param string $instance
518
     * @return array
519
     * @throws \craft\errors\MissingComponentException
520
     * @throws \yii\base\InvalidConfigException
521
     */
522
    protected function resolveActions(array $actions, string $instance)
523
    {
524
        foreach ($actions as $i => $action) {
525
            // $action could be a string or config array
526
            if (!$action instanceof $instance) {
527
                $actions[$i] = $action = ComponentHelper::createComponent($action, $instance);
528
529
                if ($actions[$i] === null) {
530
                    unset($actions[$i]);
531
                }
532
            }
533
        }
534
535
        return array_values($actions);
536
    }
537
538
539
    /*******************************************
540
     * EVENTS
541
     *******************************************/
542
543
    /**
544
     * @inheritdoc
545
     */
546
    public function afterElementSave(ElementInterface $element, bool $isNew)
547
    {
548
        $query = $element->getFieldValue($this->handle);
549
550
        (new MinMaxValidator([
551
            'min' => $this->min ? (int)$this->min : null,
552
            'max' => $this->max ? (int)$this->max : null
553
        ]))->validate($query, $error);
554
555
        if (!empty($error)) {
556
            return false;
557
        }
558
559
        return parent::afterElementSave($element, $isNew);
560
    }
561
562
    /*******************************************
563
     * SETTINGS
564
     *******************************************/
565
566
    /**
567
     * @inheritdoc
568
     */
569
    public function settingsAttributes(): array
570
    {
571
        return array_merge(
572
            [
573
                'object',
574
                'min',
575
                'max',
576
                'viewUrl',
577
                'listUrl',
578
                'selectedActions',
579
                'selectedItemActions',
580
                'selectionLabel'
581
            ],
582
            parent::settingsAttributes()
583
        );
584
    }
585
586
    /**
587
     * Returns the site ID that target elements should have.
588
     *
589
     * @param ElementInterface|Element|null $element
590
     *
591
     * @return int
592
     */
593
    protected function targetSiteId(ElementInterface $element = null): int
594
    {
595
        /** @var Element $element */
596
        if (Craft::$app->getIsMultiSite() === true && $element !== null) {
597
            return $element->siteId;
598
        }
599
600
        return Craft::$app->getSites()->currentSite->id;
601
    }
602
}
603