Completed
Push — develop ( 9e4a5a...e9ed9c )
by Nate
03:41
created

Organization::save()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
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\Element|null|craft\base\Element[] adds the type craft\base\Element[] 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
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
                $users = $elements ?? [];
414
                $this->getUserManager()->setMany($users);
415
                break;
416
417
            default:
418
                parent::setEagerLoadedElements($handle, $elements);
419
        }
420
    }
421
422
    /**
423
     * @inheritdoc
424
     */
425
    public static function defineTableAttributes(): array
426
    {
427
        return [
428
            'id' => ['label' => Craft::t('app', 'Name')],
429
            'uri' => ['label' => Craft::t('app', 'URI')],
430
            'types' => ['label' => Craft::t('organizations', 'Type(s)')],
431
            'dateJoined' => ['label' => Craft::t('organizations', 'Join Date')],
432
            'dateCreated' => ['label' => Craft::t('app', 'Date Created')],
433
            'dateUpdated' => ['label' => Craft::t('app', 'Date Updated')],
434
        ];
435
    }
436
437
438
439
    // Indexes, etc.
440
    // -------------------------------------------------------------------------
441
442
    /**
443
     * @inheritdoc
444
     */
445
    public function tableAttributeHtml(string $attribute): string
446
    {
447
448
        switch ($attribute) {
449
            case 'types':
450
                $typeHtmlParts = [];
451
                foreach ($this->getTypes() as $type) {
452
                    $typeHtmlParts[] = '<a href="' .
453
                        UrlHelper::cpUrl('organizations/' . $this->id . '/' . $type->handle) .
454
                        '">' .
455
                        $type->name .
456
                        '</a>';
457
                }
458
459
                return !empty($typeHtmlParts) ? StringHelper::toString($typeHtmlParts, ', ') : '';
460
        }
461
462
        return parent::tableAttributeHtml($attribute);
463
    }
464
465
    /**
466
     * @inheritdoc
467
     */
468
    public function getUriFormat()
469
    {
470
        if (null === ($siteSettings = $this->getSiteSettings())) {
471
            return null;
472
        }
473
474
        if (!$siteSettings->hasUrls()) {
475
            return null;
476
        }
477
478
        return $siteSettings->getUriFormat();
479
    }
480
481
    /**
482
     * @inheritdoc
483
     */
484
    public function route()
485
    {
486
        if (in_array(
487
            $this->getStatus(),
488
            [static::STATUS_DISABLED, static::STATUS_ARCHIVED],
489
            true
490
        )) {
491
            return null;
492
        }
493
494
        if (null === ($siteSettings = $this->getSiteSettings())) {
495
            return null;
496
        }
497
498
        if (!$siteSettings->hasUrls()) {
499
            return null;
500
        }
501
502
        return [
503
            'templates/render',
504
            [
505
                'template' => $siteSettings->getTemplate(),
506
                'variables' => [
507
                    'organization' => $this,
508
                ]
509
            ]
510
        ];
511
    }
512
513
    /**
514
     * @return \flipbox\organizations\records\OrganizationTypeSiteSettings|null
515
     */
516
    protected function getSiteSettings()
517
    {
518
        try {
519
            $settings = OrganizationPlugin::getInstance()->getSettings();
520
            $siteSettings = $settings->getSiteSettings()[$this->siteId] ?? null;
521
522
            if (null !== ($type = $this->getPrimaryType())) {
523
                $siteSettings = $type->getSiteSettings()[$this->siteId] ?? $siteSettings;
524
            }
525
526
            return $siteSettings;
527
        } catch (\Exception $e) {
528
            OrganizationPlugin::error(
529
                sprintf(
530
                    "An exception was caught while to resolve site settings: %s",
531
                    $e->getMessage()
532
                )
533
            );
534
        }
535
536
        return null;
537
    }
538
539
540
    /************************************************************
541
     * EVENTS
542
     ************************************************************/
543
544
    /**
545
     * @inheritdoc
546
     */
547
    public function save(bool $runValidation = true, bool $propagate = true): bool
548
    {
549
        return Craft::$app->getElements()->saveElement($this, $runValidation, $propagate);
550
    }
551
552
    /**
553
     * @inheritdoc
554
     */
555
    public function beforeSave(bool $isNew): bool
556
    {
557
        if (empty($this->getDateJoined())) {
558
            $this->setDateJoined(DateTimeHelper::currentUTCDateTime());
559
        }
560
561
        return parent::beforeSave($isNew);
562
    }
563
564
    /**
565
     * @inheritdoc
566
     * @throws /Exception
567
     */
568
    public function afterSave(bool $isNew)
569
    {
570
        if (false === $this->saveRecord($isNew)) {
571
            throw new Exception('Unable to save organization record');
572
        }
573
574
        // Types
575
        if (true === $this->saveTypes && false === $this->getTypeManager()->save()) {
576
            throw new Exception("Unable to save types.");
577
        }
578
579
        // Users
580
        if (true === $this->saveUsers && false === $this->getUserManager()->save()) {
581
            throw new Exception("Unable to save users.");
582
        }
583
584
        parent::afterSave($isNew);
585
    }
586
587
588
    /*******************************************
589
     * RELATIONSHIP
590
     *******************************************/
591
592
    /**
593
     * @return static
594
     */
595
    public function withTypes(): self
596
    {
597
        $this->saveTypes = false;
598
        return $this;
599
    }
600
601
    /**
602
     * @return static
603
     */
604
    public function withoutTypes(): self
605
    {
606
        $this->saveTypes = true;
607
        return $this;
608
    }
609
610
    /**
611
     * @return static
612
     */
613
    public function withUsers(): self
614
    {
615
        $this->saveUsers = false;
616
        return $this;
617
    }
618
619
    /**
620
     * @return static
621
     */
622
    public function withoutUsers(): self
623
    {
624
        $this->saveUsers = true;
625
        return $this;
626
    }
627
628
    /*******************************************
629
     * RECORD
630
     *******************************************/
631
632
    /**
633
     * @param bool $isNew
634
     * @return bool
635
     * @throws \Exception
636
     */
637
    protected function saveRecord(bool $isNew): bool
638
    {
639
        $record = $this->elementToRecord();
640
641
        if (!$record->save()) {
642
            $this->addErrors($record->getErrors());
643
644
            OrganizationPlugin::error(
645
                Json::encode($this->getErrors()),
646
                __METHOD__
647
            );
648
649
            return false;
650
        }
651
652
        if (false !== ($dateUpdated = DateTimeHelper::toDateTime($record->dateUpdated))) {
653
            $this->dateUpdated = $dateUpdated;
654
        }
655
656
657
        if ($isNew) {
658
            $this->id = $record->id;
659
660
            if (false !== ($dateCreated = DateTimeHelper::toDateTime($record->dateCreated))) {
661
                $this->dateCreated = $dateCreated;
662
            }
663
        }
664
665
        return true;
666
    }
667
668
    /**
669
     * @inheritdoc
670
     * @return OrganizationRecord
671
     */
672
    protected function elementToRecord(): OrganizationRecord
673
    {
674
        if (!$record = OrganizationRecord::findOne([
675
            'id' => $this->getId()
676
        ])) {
677
            $record = new OrganizationRecord();
678
        }
679
680
        // PopulateOrganizationTypeTrait the record attributes
681
        $record->id = $this->getId();
682
        $record->dateJoined = $this->getDateJoined();
683
684
        return $record;
685
    }
686
}
687