Completed
Push — master ( 19b2ae...0e0de4 )
by Nate
04:43
created

Provider::getPluginId()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 0
cts 13
cp 0
rs 9.7998
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/patron/license
6
 * @link       https://www.flipboxfactory.com/software/patron/
7
 */
8
9
namespace flipbox\patron\records;
10
11
use Craft;
12
use craft\base\PluginInterface;
13
use craft\db\Query;
14
use craft\helpers\StringHelper;
15
use flipbox\craft\ember\helpers\ModelHelper;
16
use flipbox\craft\ember\helpers\ObjectHelper;
17
use flipbox\craft\ember\helpers\QueryHelper;
18
use flipbox\craft\ember\models\HandleRulesTrait;
19
use flipbox\craft\ember\records\ActiveRecordWithId;
20
use flipbox\craft\ember\records\StateAttributeTrait;
21
use flipbox\patron\helpers\ProviderHelper;
22
use flipbox\patron\Patron;
23
use flipbox\patron\queries\ProviderActiveQuery;
24
use flipbox\patron\validators\ProviderValidator;
25
use yii\helpers\ArrayHelper;
26
27
/**
28
 * @author Flipbox Factory <[email protected]>
29
 * @since 1.0.0
30
 *
31
 * @property string $class
32
 * @property ProviderLock[] $locks
33
 * @property Token[] $tokens
34
 * @property ProviderInstance[] $instances
35
 * @property ProviderEnvironment[] $environments
36
 */
37
class Provider extends ActiveRecordWithId
38
{
39
    use HandleRulesTrait,
40
        StateAttributeTrait;
41
42
    /**
43
     * The table alias
44
     */
45
    const TABLE_ALIAS = 'patron_providers';
46
47
    /**
48
     * @deprecated
49
     */
50
    const CLIENT_ID_LENGTH = ProviderInstance::CLIENT_ID_LENGTH;
51
52
    /**
53
     * @deprecated
54
     */
55
    const CLIENT_SECRET_LENGTH = ProviderInstance::CLIENT_SECRET_LENGTH;
56
57
    /**
58
     * @var bool
59
     */
60
    public $autoSaveInstances = false;
61
62
    /**
63
     * Environments that are temporarily set during the save process
64
     *
65
     * @var null|array
66
     */
67
    private $insertInstances;
68
69
    /**
70
     * @inheritdoc
71
     * @return ProviderActiveQuery
72
     * @throws \yii\base\InvalidConfigException
73
     */
74
    public static function find()
75
    {
76
        /** @noinspection PhpIncompatibleReturnTypeInspection */
77
        return Craft::createObject(ProviderActiveQuery::class, [get_called_class()]);
78
    }
79
80
    /**
81
     * @inheritdoc
82
     */
83
    protected static function findByCondition($condition)
84
    {
85
        if (!is_numeric($condition) && is_string($condition)) {
86
            $condition = ['handle' => $condition];
87
        }
88
89
        /** @noinspection PhpInternalEntityUsedInspection */
90
        return parent::findByCondition($condition);
91
    }
92
93
94
    /**
95
     * @return string|null
96
     */
97
    public function getIcon()
98
    {
99
        if ($this->class === null) {
100
            return null;
101
        }
102
103
        $icons = Patron::getInstance()->getCp()->getProviderIcons();
104
105
        return $icons[$this->class] ?? null;
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111
    public function rules()
112
    {
113
        return array_merge(
114
            parent::rules(),
115
            $this->handleRules(),
116
            $this->stateRules(),
117
            [
118
                [
119
                    [
120
                        'class'
121
                    ],
122
                    ProviderValidator::class
123
                ],
124
                [
125
                    [
126
                        'class'
127
                    ],
128
                    'required'
129
                ],
130
                [
131
                    [
132
                        'class',
133
                        'settings'
134
                    ],
135
                    'safe',
136
                    'on' => [
137
                        ModelHelper::SCENARIO_DEFAULT
138
                    ]
139
                ]
140
            ]
141
        );
142
    }
143
144
    /**
145
     * Get all of the associated tokens.
146
     *
147
     * @param array $config
148
     * @return \yii\db\ActiveQuery
149
     */
150
    public function getTokens(array $config = [])
151
    {
152
        $query = $this->hasMany(
153
            Token::class,
154
            ['providerId' => 'id']
155
        );
156
157
        if (!empty($config)) {
158
            QueryHelper::configure(
159
                $query,
160
                $config
161
            );
162
        }
163
164
        return $query;
165
    }
166
167
    /**
168
     * Get all of the associated tokens.
169
     *
170
     * @param array $config
171
     * @return \yii\db\ActiveQuery
172
     */
173
    public function getLocks(array $config = [])
174
    {
175
        $query = $this->hasMany(
176
            ProviderLock::class,
177
            ['providerId' => 'id']
178
        );
179
180
        if (!empty($config)) {
181
            QueryHelper::configure(
182
                $query,
183
                $config
184
            );
185
        }
186
187
        return $query;
188
    }
189
190
    /**
191
     * Get all of the associated instances.
192
     *
193
     * @param array $config
194
     * @return \yii\db\ActiveQuery
195
     */
196
    public function getInstances(array $config = [])
197
    {
198
        $query = $this->hasMany(
199
            ProviderInstance::class,
200
            ['providerId' => 'id']
201
        )
202
            ->indexBy('id');
203
204
        if (!empty($config)) {
205
            QueryHelper::configure(
206
                $query,
207
                $config
208
            );
209
        }
210
211
        return $query;
212
    }
213
214
    /**
215
     * @param array $instances
216
     * @return $this
217
     */
218
    public function setInstances(array $instances = [])
219
    {
220
        $records = [];
221
        foreach (array_filter($instances) as $environment) {
222
            $records[] = $this->resolveInstance($environment);
223
        }
224
225
        $this->populateRelation('instances', $records);
226
        return $this;
227
    }
228
229
    /**
230
     * @param $instance
231
     * @return ProviderInstance
232
     */
233
    protected function resolveInstance($instance): ProviderInstance
234
    {
235
        if ($instance instanceof ProviderInstance) {
236
            return $instance;
237
        }
238
239
        $record = new ProviderInstance();
240
241
        /** @noinspection PhpIncompatibleReturnTypeInspection */
242
        return ObjectHelper::populate(
243
            $record,
244
            $instance
245
        );
246
    }
247
248
    /**
249
     * Get all of the associated environments.
250
     *
251
     * @param array $config
252
     * @return \yii\db\ActiveQuery
253
     */
254
    public function getEnvironments(array $config = [])
255
    {
256
        $query = $this->hasMany(
257
            ProviderEnvironment::class,
258
            ['instanceId' => 'id']
259
        )
260
            ->via('instances')
261
            ->indexBy('environment');
262
263
        if (!empty($config)) {
264
            QueryHelper::configure(
265
                $query,
266
                $config
267
            );
268
        }
269
270
        return $query;
271
    }
272
273
    /*******************************************
274
     * SAVE
275
     *******************************************/
276
277
    /**
278
     * @param PluginInterface $plugin
279
     * @param bool $runValidation
280
     * @param null $attributeNames
281
     * @return bool
282
     */
283
    public function saveAndLock(PluginInterface $plugin, $runValidation = true, $attributeNames = null): bool
284
    {
285
        if (!$this->save($runValidation, $attributeNames)) {
286
            return false;
287
        }
288
289
        return $this->addLock($plugin);
290
    }
291
292
293
    /*******************************************
294
     * LOCK
295
     *******************************************/
296
297
    /**
298
     * @param PluginInterface $plugin
299
     * @return bool
300
     */
301
    public function addLock(PluginInterface $plugin): bool
302
    {
303
        if (null === ($pluginId = $this->getPluginId($plugin))) {
304
            return false;
305
        }
306
307
        $record = ProviderLock::findOne([
308
            'providerId' => $this->getId(),
309
            'pluginId' => $pluginId
310
        ]);
311
312
        if (!empty($record)) {
313
            return true;
314
        }
315
316
        $record = new ProviderLock();
317
318
        $record->setAttributes([
319
            'providerId' => $this->getId(),
320
            'pluginId' => $pluginId
321
        ]);
322
323
        return (bool)$record->save();
324
    }
325
326
    /**
327
     * @param PluginInterface $plugin
328
     * @return bool
329
     * @throws \Throwable
330
     */
331
    public function removeLock(PluginInterface $plugin): bool
332
    {
333
        if (null === ($pluginId = $this->getPluginId($plugin))) {
334
            return false;
335
        }
336
337
        if (null === ($record = ProviderLock::findOne([
338
                'providerId' => $this->getId() ?: 0,
339
                'pluginId' => $pluginId
340
            ]))) {
341
            return true;
342
        }
343
344
        return (bool)$record->delete();
345
    }
346
347
    /**
348
     * @return bool
349
     */
350
    public function isLocked(): bool
351
    {
352
        return !empty($this->locks);
353
    }
354
355
    /**
356
     * @param PluginInterface $plugin
357
     * @return int|null
358
     */
359
    protected function getPluginId(PluginInterface $plugin)
360
    {
361
        $id = (new Query())
362
            ->select([
363
                'id',
364
            ])
365
            ->from(['{{%plugins}}'])
366
            ->where([
367
                'handle' => $plugin->getHandle()
368
            ])
369
            ->scalar();
370
371
        return $id ? (int)$id : null;
372
    }
373
374
    /**
375
     * @param PluginInterface $plugin
376
     * @return int|null
377
     */
378
    protected function getPluginName(PluginInterface $plugin)
379
    {
380
        $id = (new Query())
381
            ->select([
382
                'id',
383
            ])
384
            ->from(['{{%plugins}}'])
385
            ->where([
386
                'handle' => $plugin->getHandle()
387
            ])
388
            ->scalar();
389
390
        return $id ? (int)$id : null;
391
    }
392
393
394
    /*******************************************
395
     * DELETE
396
     *******************************************/
397
398
    /**
399
     * @param PluginInterface|null $plugin
400
     * @return bool|false|int
401
     * @throws \Throwable
402
     * @throws \yii\db\StaleObjectException
403
     */
404
    public function delete(PluginInterface $plugin = null)
405
    {
406
        return $this->canDelete($plugin) ? parent::delete() : false;
407
    }
408
409
    /**
410
     * @param PluginInterface|null $plugin
411
     * @return bool
412
     * @throws \craft\errors\InvalidPluginException
413
     */
414
    protected function canDelete(PluginInterface $plugin = null)
415
    {
416
        // If a plugin is locking this, prevent deletion
417
        $lockQuery = $this->getLocks();
418
        if (null !== $plugin) {
419
            $lockQuery->andWhere(
420
                ['<>', 'pluginId', $this->getPluginId($plugin)]
421
            );
422
        }
423
424
        $locks = $lockQuery->all();
425
426
        if (count($locks) > 0) {
427
            $handles = (new Query())
428
                ->select([
429
                    'handle',
430
                ])
431
                ->from(['{{%plugins}}'])
432
                ->where([
433
                    'id' => ArrayHelper::getColumn($locks, 'pluginId'),
434
                ])
435
                ->column();
436
437
            $names = [];
438
            foreach ($handles as $handle) {
439
                $plugin = Craft::$app->getPlugins()->getPluginInfo($handle);
440
                $names[] = $plugin['name'] ?? 'Unknown Plugin';
441
            }
442
443
            $this->addError(
444
                'locks',
445
                Craft::t(
446
                    'patron',
447
                    'The provider is locked by the following plugins: {plugins}',
448
                    [
449
                        'plugins' => StringHelper::toString($names, ', ')
450
                    ]
451
                )
452
            );
453
            return false;
454
        }
455
456
        return true;
457
    }
458
459
    /*******************************************
460
     * EVENTS
461
     *******************************************/
462
463
    /**
464
     * @inheritdoc
465
     */
466
    public function beforeSave($insert): bool
467
    {
468
        if (!parent::beforeSave($insert)) {
469
            return false;
470
        }
471
472
        if ($insert !== true ||
473
            $this->isRelationPopulated('instances') !== true ||
474
            $this->autoSaveInstances !== true
475
        ) {
476
            return true;
477
        }
478
479
        $this->insertInstances = $this->instances;
480
481
        return true;
482
    }
483
484
    /*******************************************
485
     * UPDATE / INSERT
486
     *******************************************/
487
488
    /**
489
     * We're extracting the environments that may have been explicitly set on the record.  When the 'id'
490
     * attribute is updated, it removes any associated relationships.
491
     *
492
     * @inheritdoc
493
     * @throws \Throwable
494
     */
495
    protected function insertInternal($attributes = null)
496
    {
497
        if (!parent::insertInternal($attributes)) {
498
            return false;
499
        }
500
501
        if (null === $this->insertInstances) {
502
            return true;
503
        }
504
505
        $this->setInstances($this->insertInstances);
506
        $this->insertInstances = null;
507
508
        return $this->upsertInternal($attributes);
509
    }
510
511
    /**
512
     * @inheritdoc
513
     * @throws \Throwable
514
     */
515
    protected function updateInternal($attributes = null)
516
    {
517
        if (false === ($response = parent::updateInternal($attributes))) {
518
            return false;
519
        }
520
521
        return $this->upsertInternal($attributes) ? $response : false;
522
    }
523
524
    /**
525
     * @param null $attributes
526
     * @return bool
527
     * @throws \Throwable
528
     * @throws \yii\db\StaleObjectException
529
     */
530
    protected function upsertInternal($attributes = null): bool
531
    {
532
        if (empty($attributes)) {
533
            return $this->saveInstances();
534
        }
535
536
        if (array_key_exists('instances', $attributes)) {
537
            return $this->saveInstances(true);
538
        }
539
540
        return true;
541
    }
542
543
    /**
544
     * @param bool $force
545
     * @return bool
546
     * @throws \Throwable
547
     * @throws \yii\db\StaleObjectException
548
     */
549
    protected function saveInstances(bool $force = false): bool
550
    {
551
        if ($force === false && $this->autoSaveInstances !== true) {
552
            return true;
553
        }
554
555
        $successful = true;
556
557
        /** @var ProviderInstance[] $allRecords */
558
        $allRecords = $this->getInstances()
559
            ->all();
560
561
        ArrayHelper::index($allRecords, 'providerId');
562
563
        foreach ($this->instances as $model) {
564
            ArrayHelper::remove($allRecords, $this->getId());
565
            $model->providerId = $this->getId();
566
567
            if (!$model->save()) {
568
                $successful = false;
569
                // Log the errors
570
                $error = Craft::t(
571
                    'patron',
572
                    "Couldn't save instance due to validation errors:"
573
                );
574
                foreach ($model->getFirstErrors() as $attributeError) {
575
                    $error .= "\n- " . Craft::t('patron', $attributeError);
576
                }
577
578
                $this->addError('instances', $error);
579
            }
580
        }
581
582
        // Delete old records
583
        foreach ($allRecords as $record) {
584
            $record->delete();
585
        }
586
587
        return $successful;
588
    }
589
590
    /**
591
     * @return string
592
     * @throws \ReflectionException
593
     */
594
    public function getDisplayName(): string
595
    {
596
        return ProviderHelper::displayName(
597
            $this->class
598
        );
599
    }
600
}
601