Organization::afterSave()   A
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 0
cts 13
cp 0
rs 9.0444
c 0
b 0
f 0
cc 6
nc 4
nop 1
crap 42
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/organization/license
6
 * @link       https://www.flipboxfactory.com/software/organization/
7
 */
8
9
namespace flipbox\organizations\elements;
10
11
use Craft;
12
use craft\base\Element;
13
use craft\elements\actions\Edit as EditAction;
14
use craft\elements\actions\SetStatus;
15
use craft\elements\db\ElementQueryInterface;
16
use craft\elements\User;
17
use craft\helpers\DateTimeHelper;
18
use craft\helpers\Json;
19
use craft\helpers\StringHelper;
20
use craft\helpers\UrlHelper as UrlHelper;
21
use flipbox\craft\ember\elements\ExplicitElementTrait;
22
use flipbox\craft\ember\helpers\ModelHelper;
23
use flipbox\organizations\models\DateJoinedAttributeTrait;
24
use flipbox\organizations\Organizations as OrganizationPlugin;
25
use flipbox\organizations\queries\OrganizationQuery;
26
use flipbox\organizations\records\Organization as OrganizationRecord;
27
use flipbox\organizations\records\OrganizationType;
28
use flipbox\organizations\records\OrganizationType as TypeModel;
29
use yii\base\ErrorException as Exception;
30
31
/**
32
 * @author Flipbox Factory <[email protected]>
33
 * @since 1.0.0
34
 *
35
 * @method static Organization[] findAll($criteria = null) : array
36
 */
37
class Organization extends Element
38
{
39
    use ExplicitElementTrait,
40
        DateJoinedAttributeTrait,
41
        TypesAttributeTrait,
42
        UsersAttributeTrait;
43
44
    /**
45
     * Whether associated types should be saved
46
     *
47
     * @var bool
48
     */
49
    private $saveTypes = true;
50
51
    /**
52
     * Whether associated users should be saved
53
     *
54
     * @var bool
55
     */
56
    private $saveUsers = true;
57
58
    /**
59
     * @inheritdoc
60
     */
61
    public static function displayName(): string
62
    {
63
        return Craft::t('organizations', 'Organization');
64
    }
65
66
    /**
67
     * @inheritdoc
68
     */
69
    public function getIsEditable(): bool
70
    {
71
        return true;
72
    }
73
74
    /**
75
     * @inheritdoc
76
     */
77
    public static function hasTitles(): bool
78
    {
79
        return true;
80
    }
81
82
    /**
83
     * @inheritdoc
84
     */
85
    public static function isLocalized(): bool
86
    {
87
        return true;
88
    }
89
90
    /**
91
     * @inheritdoc
92
     */
93
    public static function hasContent(): bool
94
    {
95
        return true;
96
    }
97
98
    /**
99
     * @inheritdoc
100
     */
101
    public static function hasUris(): bool
102
    {
103
        return true;
104
    }
105
106
    /**
107
     * Returns whether this element type can have statuses.
108
     *
109
     * @return boolean
110
     */
111
    public static function hasStatuses(): bool
112
    {
113
        return true;
114
    }
115
116
117
    /************************************************************
118
     * QUERY
119
     ************************************************************/
120
121
    /**
122
     * @inheritdoc
123
     *
124
     * @return OrganizationQuery
125
     */
126
    public static function find(): ElementQueryInterface
127
    {
128
        return new OrganizationQuery(static::class);
129
    }
130
131
    /**
132
     * @inheritdoc
133
     * @return static|null
134
     */
135
    public static function findOne($criteria = null)
136
    {
137
        if ($criteria instanceof self) {
138
            return $criteria;
139
        }
140
141
        // If we're asking by PK, ignore status
142
        if (is_numeric($criteria)) {
143
            $criteria = [
144
                'id' => $criteria,
145
                'status' => null
146
            ];
147
        }
148
149
        return parent::findOne($criteria);
0 ignored issues
show
Bug Compatibility introduced by
The expression parent::findOne($criteria); of type craft\base\ElementInterface|array|null adds the type array to the return on line 149 which is incompatible with the return type declared by the interface craft\base\ElementInterface::findOne of type craft\base\ElementInterface|null.
Loading history...
150
    }
151
152
    /**
153
     * @param mixed $criteria
154
     * @param bool $one
155
     * @return Element|Element[]|null
156
     */
157
    protected static function findByCondition($criteria, bool $one)
158
    {
159
        if (is_numeric($criteria)) {
160
            $criteria = ['id' => $criteria];
161
        }
162
163
        if (is_string($criteria)) {
164
            $criteria = ['slug' => $criteria];
165
        }
166
167
        return parent::findByCondition($criteria, $one);
168
    }
169
170
171
    /************************************************************
172
     * PROPERTIES / ATTRIBUTES
173
     ************************************************************/
174
175
    /**
176
     * @return array
177
     */
178
    public function attributes()
179
    {
180
        return array_merge(
181
            parent::attributes(),
182
            $this->dateJoinedAttributes()
183
        );
184
    }
185
186
    /**
187
     * @inheritdoc
188
     * @throws \yii\base\InvalidConfigException
189
     */
190
    public function rules()
191
    {
192
        return array_merge(
193
            parent::rules(),
194
            $this->dateJoinedRules(),
195
            [
196
                [
197
                    [
198
                        'types',
199
                        'activeType',
200
                        'primaryType',
201
                        'users',
202
                    ],
203
                    'safe',
204
                    'on' => [
205
                        ModelHelper::SCENARIO_DEFAULT
0 ignored issues
show
Deprecated Code introduced by
The constant flipbox\craft\ember\help...elper::SCENARIO_DEFAULT has been deprecated with message: Use `yii\base\Model::SCENARIO_DEFAULT`

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
206
                    ]
207
208
                ],
209
            ]
210
        );
211
    }
212
213
    /**
214
     * @return array
215
     */
216
    public function attributeLabels()
217
    {
218
        return array_merge(
219
            parent::attributeLabels(),
220
            $this->dateJoinedAttributeLabels()
221
        );
222
    }
223
224
225
    /************************************************************
226
     * FIELD LAYOUT
227
     ************************************************************/
228
229
    /**
230
     * @inheritdoc
231
     */
232
    public function getFieldLayout()
233
    {
234
        if (null === ($type = $this->getActiveType())) {
235
            return OrganizationPlugin::getInstance()->getSettings()->getFieldLayout();
236
        }
237
238
        return $type->getFieldLayout();
239
    }
240
241
242
    /************************************************************
243
     * ELEMENT ADMIN
244
     ************************************************************/
245
246
    /**
247
     * @inheritdoc
248
     */
249
    public function getRef()
250
    {
251
        if (!$primary = $this->getPrimaryType()) {
252
            return $this->slug;
253
        }
254
255
        return $primary->handle . '/' . $this->slug;
256
    }
257
258
    /**
259
     * @inheritdoc
260
     */
261
    public function getCpEditUrl()
262
    {
263
        return UrlHelper::cpUrl('organizations/' . $this->id);
264
    }
265
266
    /**
267
     * @inheritdoc
268
     */
269
    protected static function defineSources(string $context = null): array
270
    {
271
        switch ($context) {
272
            case 'user':
273
                return self::defineUserSources();
274
275
            default:
276
                return self::defineTypeSources();
277
        }
278
    }
279
280
    /**
281
     * @return array
282
     */
283
    private static function defineDefaultSources(): array
284
    {
285
        return [
286
            [
287
                'key' => '*',
288
                'label' => Craft::t('organizations', 'All organizations'),
289
                'criteria' => ['status' => null],
290
                'hasThumbs' => true
291
            ]
292
        ];
293
    }
294
295
    /**
296
     * @return array
297
     */
298
    private static function defineTypeSources(): array
299
    {
300
        $sources = self::defineDefaultSources();
301
302
        // Array of all organization types
303
        $organizationTypes = OrganizationType::findAll([]);
304
305
        $sources[] = ['heading' => Craft::t('organizations', 'Types')];
306
307
        /** @var TypeModel $organizationType */
308
        foreach ($organizationTypes as $organizationType) {
309
            $sources[] = [
310
                'key' => 'type:' . $organizationType->id,
311
                'label' => $organizationType->name,
312
                'criteria' => ['status' => null, 'typeId' => $organizationType->id],
313
                'hasThumbs' => true
314
            ];
315
        }
316
317
        return $sources;
318
    }
319
320
    /**
321
     * @return array
322
     */
323
    private static function defineUserSources(): array
324
    {
325
        $sources = self::defineDefaultSources();
326
327
        // Array of all organization types
328
        $organizationUsers = User::find();
329
330
        $sources[] = ['heading' => Craft::t('organizations', 'Users')];
331
332
        /** @var User $organizationUser */
333
        foreach ($organizationUsers as $organizationUser) {
334
            $sources[] = [
335
                'key' => 'user:' . $organizationUser->id,
336
                'label' => $organizationUser->getFullName(),
337
                'criteria' => [
338
                    'status' => null,
339
                    'users' => [$organizationUser->id]
340
                ],
341
                'hasThumbs' => true
342
            ];
343
        }
344
345
        return $sources;
346
    }
347
348
    /**
349
     * @inheritdoc
350
     */
351
    protected static function defineActions(string $source = null): array
352
    {
353
        $actions = [];
354
355
        // Status
356
        $actions[] = SetStatus::class;
357
358
        // Edit
359
        $actions[] = Craft::$app->getElements()->createAction([
360
            'type' => EditAction::class,
361
            'label' => Craft::t('organizations', 'Edit'),
362
        ]);
363
364
//        if (Craft::$app->getUser()->checkPermission('deleteOrganizations')) {
365
//            // Delete Organization
366
//            $actions[] = DeleteAction::class;
367
//        }
368
369
        return $actions;
370
    }
371
372
    /**
373
     * @inheritdoc
374
     */
375
    protected static function defineSearchableAttributes(): array
376
    {
377
        return [
378
            'id'
379
        ];
380
    }
381
382
    /**
383
     * @inheritdoc
384
     */
385
    protected static function defineSortOptions(): array
386
    {
387
        return [
388
            'title' => Craft::t('organizations', 'Name'),
389
            'dateJoined' => Craft::t('organizations', 'Join Date'),
390
        ];
391
    }
392
393
    /**
394
     * @inheritdoc
395
     */
396
    public static function eagerLoadingMap(array $sourceElements, string $handle)
397
    {
398
        switch ($handle) {
399
            case 'users':
400
                return self::eagerLoadingUsersMap($sourceElements);
401
        }
402
403
        return parent::eagerLoadingMap($sourceElements, $handle);
404
    }
405
406
    /**
407
     * @inheritdoc
408
     */
409
    public function setEagerLoadedElements(string $handle, array $elements)
410
    {
411
        switch ($handle) {
412
            case 'users':
413
                $this->getUsers()->clear()->add($elements);
414
                break;
415
416
            default:
417
                parent::setEagerLoadedElements($handle, $elements);
418
        }
419
    }
420
421
    /**
422
     * @inheritdoc
423
     */
424
    public static function defineTableAttributes(): array
425
    {
426
        return [
427
            'id' => ['label' => Craft::t('app', 'Name')],
428
            'uri' => ['label' => Craft::t('app', 'URI')],
429
            'types' => ['label' => Craft::t('organizations', 'Type(s)')],
430
            'dateJoined' => ['label' => Craft::t('organizations', 'Join Date')],
431
            'dateCreated' => ['label' => Craft::t('app', 'Date Created')],
432
            'dateUpdated' => ['label' => Craft::t('app', 'Date Updated')],
433
        ];
434
    }
435
436
437
438
    // Indexes, etc.
439
    // -------------------------------------------------------------------------
440
441
    /**
442
     * @inheritdoc
443
     */
444
    public function tableAttributeHtml(string $attribute): string
445
    {
446
447
        switch ($attribute) {
448
            case 'types':
449
                $typeHtmlParts = [];
450
                foreach ($this->getTypes()->getCollection() as $type) {
451
                    $typeHtmlParts[] = '<a href="' .
452
                        UrlHelper::cpUrl('organizations/' . $this->id . '/' . $type->handle) .
453
                        '">' .
454
                        $type->name .
455
                        '</a>';
456
                }
457
458
                return !empty($typeHtmlParts) ? StringHelper::toString($typeHtmlParts, ', ') : '';
459
        }
460
461
        return parent::tableAttributeHtml($attribute);
462
    }
463
464
    /**
465
     * @inheritdoc
466
     */
467
    public function getUriFormat()
468
    {
469
        if (null === ($siteSettings = $this->getSiteSettings())) {
470
            return null;
471
        }
472
473
        if (!$siteSettings->hasUrls()) {
474
            return null;
475
        }
476
477
        return $siteSettings->getUriFormat();
478
    }
479
480
    /**
481
     * @inheritdoc
482
     */
483
    public function route()
484
    {
485
        if (in_array(
486
            $this->getStatus(),
487
            [static::STATUS_DISABLED, static::STATUS_ARCHIVED],
488
            true
489
        )) {
490
            return null;
491
        }
492
493
        if (null === ($siteSettings = $this->getSiteSettings())) {
494
            return null;
495
        }
496
497
        if (!$siteSettings->hasUrls()) {
498
            return null;
499
        }
500
501
        return [
502
            'templates/render',
503
            [
504
                'template' => $siteSettings->getTemplate(),
505
                'variables' => [
506
                    'organization' => $this,
507
                ]
508
            ]
509
        ];
510
    }
511
512
    /**
513
     * @return \flipbox\organizations\records\OrganizationTypeSiteSettings|null
514
     */
515
    protected function getSiteSettings()
516
    {
517
        try {
518
            $settings = OrganizationPlugin::getInstance()->getSettings();
519
            $siteSettings = $settings->getSiteSettings()[$this->siteId] ?? null;
520
521
            if (null !== ($type = $this->getPrimaryType())) {
522
                $siteSettings = $type->getSiteSettings()[$this->siteId] ?? $siteSettings;
523
            }
524
525
            return $siteSettings;
526
        } catch (\Exception $e) {
527
            OrganizationPlugin::error(
528
                sprintf(
529
                    "An exception was caught while to resolve site settings: %s",
530
                    $e->getMessage()
531
                )
532
            );
533
        }
534
535
        return null;
536
    }
537
538
539
    /************************************************************
540
     * EVENTS
541
     ************************************************************/
542
543
    /**
544
     * @inheritdoc
545
     */
546
    public function save(bool $runValidation = true, bool $propagate = true): bool
547
    {
548
        return Craft::$app->getElements()->saveElement($this, $runValidation, $propagate);
549
    }
550
551
    /**
552
     * @inheritdoc
553
     */
554
    public function beforeSave(bool $isNew): bool
555
    {
556
        if (empty($this->getDateJoined())) {
557
            $this->setDateJoined(DateTimeHelper::currentUTCDateTime());
558
        }
559
560
        return parent::beforeSave($isNew);
561
    }
562
563
    /**
564
     * @inheritdoc
565
     * @throws /Exception
566
     */
567
    public function afterSave(bool $isNew)
568
    {
569
        if (false === $this->saveRecord($isNew)) {
570
            throw new Exception('Unable to save organization record');
571
        }
572
573
        // Types
574
        if (true === $this->saveTypes && false === $this->getTypes()->save()) {
575
            throw new Exception("Unable to save types.");
576
        }
577
578
        // Users
579
        if (true === $this->saveUsers && false === $this->getUsers()->save()) {
580
            throw new Exception("Unable to save users.");
581
        }
582
583
        parent::afterSave($isNew);
584
    }
585
586
587
    /*******************************************
588
     * RELATIONSHIP
589
     *******************************************/
590
591
    /**
592
     * @return static
593
     */
594
    public function withTypes(): self
595
    {
596
        $this->saveTypes = true;
597
        return $this;
598
    }
599
600
    /**
601
     * @return static
602
     */
603
    public function withoutTypes(): self
604
    {
605
        $this->saveTypes = false;
606
        return $this;
607
    }
608
609
    /**
610
     * @return static
611
     */
612
    public function withUsers(): self
613
    {
614
        $this->saveUsers = true;
615
        return $this;
616
    }
617
618
    /**
619
     * @return static
620
     */
621
    public function withoutUsers(): self
622
    {
623
        $this->saveUsers = false;
624
        return $this;
625
    }
626
627
    /*******************************************
628
     * RECORD
629
     *******************************************/
630
631
    /**
632
     * @param bool $isNew
633
     * @return bool
634
     * @throws \Exception
635
     */
636
    protected function saveRecord(bool $isNew): bool
637
    {
638
        $record = $this->elementToRecord();
639
640
        if (!$record->save()) {
641
            $this->addErrors($record->getErrors());
642
643
            OrganizationPlugin::error(
644
                Json::encode($this->getErrors()),
645
                __METHOD__
646
            );
647
648
            return false;
649
        }
650
651
        if (false !== ($dateUpdated = DateTimeHelper::toDateTime($record->dateUpdated))) {
652
            $this->dateUpdated = $dateUpdated;
653
        }
654
655
656
        if ($isNew) {
657
            $this->id = $record->id;
658
659
            if (false !== ($dateCreated = DateTimeHelper::toDateTime($record->dateCreated))) {
660
                $this->dateCreated = $dateCreated;
661
            }
662
        }
663
664
        return true;
665
    }
666
667
    /**
668
     * @inheritdoc
669
     * @return OrganizationRecord
670
     */
671
    protected function elementToRecord(): OrganizationRecord
672
    {
673
        if (!$record = OrganizationRecord::findOne([
674
            'id' => $this->getId()
675
        ])) {
676
            $record = new OrganizationRecord();
677
        }
678
679
        // PopulateOrganizationTypeTrait the record attributes
680
        $record->id = $this->getId();
681
        $record->dateJoined = $this->getDateJoined();
682
683
        return $record;
684
    }
685
}
686