Completed
Push — develop ( fcce06...87d9a9 )
by Nate
02:07
created

Organization   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 657
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 18

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 65
lcom 3
cbo 18
dl 0
loc 657
ccs 0
cts 352
cp 0
rs 3.1429
c 0
b 0
f 0

35 Methods

Rating   Name   Duplication   Size   Complexity  
A displayName() 0 4 1
A getIsEditable() 0 4 1
A hasTitles() 0 4 1
A isLocalized() 0 4 1
A hasContent() 0 4 1
A hasUris() 0 4 1
A find() 0 4 1
A getOne() 0 13 2
A getAll() 0 15 2
A findByCondition() 0 12 3
A hasStatuses() 0 4 1
A getRef() 0 8 2
A attributes() 0 13 1
A rules() 0 22 1
A attributeLabels() 0 13 1
A getFieldLayout() 0 8 2
A getCpEditUrl() 0 4 1
A defineSources() 0 10 2
A defineDefaultSources() 0 11 1
A defineTypeSources() 0 21 2
A defineUserSources() 0 24 2
A defineActions() 0 17 1
A defineSearchableAttributes() 0 6 1
A defineSortOptions() 0 7 1
A eagerLoadingMap() 0 9 2
A setEagerLoadedElements() 0 12 2
A defineTableAttributes() 0 11 1
A tableAttributeHtml() 0 19 4
A getUriFormat() 0 12 3
A route() 0 28 4
A getSiteSettings() 0 22 3
A beforeSave() 0 8 2
A afterSave() 0 18 4
A saveRecord() 0 30 5
A elementToRecord() 0 14 2

How to fix   Complexity   

Complex Class

Complex classes like Organization often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Organization, and based on these observations, apply Extract Interface, too.

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\db\ElementQueryInterface;
15
use craft\elements\User;
16
use craft\errors\ElementNotFoundException;
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\helpers\ModelHelper;
22
use flipbox\organizations\models\DateJoinedAttributeTrait;
23
use flipbox\organizations\Organizations as OrganizationPlugin;
24
use flipbox\organizations\queries\OrganizationQuery;
25
use flipbox\organizations\records\Organization as OrganizationRecord;
26
use flipbox\organizations\records\OrganizationType;
27
use flipbox\organizations\records\OrganizationType as TypeModel;
28
use yii\base\ErrorException as Exception;
29
30
/**
31
 * @author Flipbox Factory <[email protected]>
32
 * @since 1.0.0
33
 */
34
class Organization extends Element
35
{
36
    use DateJoinedAttributeTrait,
37
        TypesAttributeTrait,
38
        UsersAttributeTrait;
39
40
    /**
41
     * @inheritdoc
42
     */
43
    public static function displayName(): string
44
    {
45
        return Craft::t('organizations', 'Organization');
46
    }
47
48
    /**
49
     * @inheritdoc
50
     */
51
    public function getIsEditable(): bool
52
    {
53
        return true;
54
    }
55
56
    /**
57
     * @inheritdoc
58
     */
59
    public static function hasTitles(): bool
60
    {
61
        return true;
62
    }
63
64
    /**
65
     * @inheritdoc
66
     */
67
    public static function isLocalized(): bool
68
    {
69
        return true;
70
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75
    public static function hasContent(): bool
76
    {
77
        return true;
78
    }
79
80
    /**
81
     * @inheritdoc
82
     */
83
    public static function hasUris(): bool
84
    {
85
        return true;
86
    }
87
88
    /**
89
     * @inheritdoc
90
     *
91
     * @return OrganizationQuery
92
     */
93
    public static function find(): ElementQueryInterface
94
    {
95
        return new OrganizationQuery(static::class);
96
    }
97
98
    /*******************************************
99
     * GET
100
     *******************************************/
101
102
    /**
103
     * Returns a single element instance by a primary key or a set of element criteria parameters.
104
     *
105
     * The method accepts:
106
     *
107
     *  - an int: query by a single ID value and return the corresponding element (or null if not found).
108
     *  - an array of name-value pairs: query by a set of parameter values and return the first element
109
     *    matching all of them (or null if not found).
110
     *
111
     * Note that this method will automatically call the `one()` method and return an
112
     * [[ElementInterface|\craft\base\Element]] instance. For example,
113
     *
114
     * ```php
115
     * // find a single entry whose ID is 10
116
     * $entry = Entry::findOne(10);
117
     * // the above code is equivalent to:
118
     * $entry = Entry::find->id(10)->one();
119
     * // find the first user whose email ends in "example.com"
120
     * $user = User::findOne(['email' => '*example.com']);
121
     * // the above code is equivalent to:
122
     * $user = User::find()->email('*example.com')->one();
123
     * ```
124
     *
125
     * @param mixed $criteria The element ID or a set of element criteria parameters
126
     * @return static Element instance matching the condition, or null if nothing matches.
127
     * @throws ElementNotFoundException
128
     */
129
    public static function getOne($criteria)
130
    {
131
        if (null === ($element = static::findOne($criteria))) {
0 ignored issues
show
Bug Compatibility introduced by
The expression static::findOne($criteria); of type craft\base\Element|null|craft\base\Element[] adds the type craft\base\Element[] to the return on line 140 which is incompatible with the return type documented by flipbox\organizations\el...ts\Organization::getOne of type flipbox\organizations\elements\Organization.
Loading history...
132
            throw new ElementNotFoundException(
133
                sprintf(
134
                    "Organization not found with the following criteria: %s",
135
                    Json::encode($criteria)
136
                )
137
            );
138
        }
139
140
        return $element;
141
    }
142
143
    /**
144
     * Returns a list of elements that match the specified ID(s) or a set of element criteria parameters.
145
     *
146
     * The method accepts:
147
     *
148
     *  - an int: query by a single ID value and return an array containing the corresponding element
149
     *    (or an empty array if not found).
150
     *  - an array of integers: query by a list of ID values and return the corresponding elements (or an
151
     *    empty array if none was found).
152
     *    Note that an empty array will result in an empty result as it will be interpreted as a search for
153
     *    primary keys and not an empty set of element criteria parameters.
154
     *  - an array of name-value pairs: query by a set of parameter values and return an array of elements
155
     *    matching all of them (or an empty array if none was found).
156
     *
157
     * Note that this method will automatically call the `all()` method and return an array of
158
     * [[ElementInterface|\craft\base\Element]] instances. For example,
159
     *
160
     * ```php
161
     * // find the entries whose ID is 10
162
     * $entries = Entry::findAll(10);
163
     * // the above code is equivalent to:
164
     * $entries = Entry::find()->id(10)->all();
165
     * // find the entries whose ID is 10, 11 or 12.
166
     * $entries = Entry::findAll([10, 11, 12]);
167
     * // the above code is equivalent to:
168
     * $entries = Entry::find()->id([10, 11, 12]])->all();
169
     * // find users whose email ends in "example.com"
170
     * $users = User::findAll(['email' => '*example.com']);
171
     * // the above code is equivalent to:
172
     * $users = User::find()->email('*example.com')->all();
173
     * ```
174
     *
175
     * @param mixed $criteria The element ID, an array of IDs, or a set of element criteria parameters
176
     * @return static[] an array of Element instances, or an empty array if nothing matches.
177
     * @throws ElementNotFoundException
178
     */
179
    public static function getAll($criteria)
180
    {
181
        $elements = static::findAll($criteria);
182
183
        if (empty($elements)) {
184
            throw new ElementNotFoundException(
185
                sprintf(
186
                    "Organization not found with the following criteria: %s",
187
                    Json::encode($criteria)
188
                )
189
            );
190
        }
191
192
        return $elements;
193
    }
194
195
    /**
196
     * @param mixed $criteria
197
     * @param bool $one
198
     * @return Element|Element[]|null
199
     */
200
    protected static function findByCondition($criteria, bool $one)
201
    {
202
        if (is_numeric($criteria)) {
203
            $criteria = ['id' => $criteria];
204
        }
205
206
        if (is_string($criteria)) {
207
            $criteria = ['slug' => $criteria];
208
        }
209
210
        return parent::findByCondition($criteria, $one);
211
    }
212
213
    /**
214
     * Returns whether this element type can have statuses.
215
     *
216
     * @return boolean
217
     */
218
    public static function hasStatuses(): bool
219
    {
220
        return true;
221
    }
222
223
    /**
224
     * @inheritdoc
225
     */
226
    public function getRef()
227
    {
228
        if (!$primary = $this->getPrimaryType()) {
229
            return $this->slug;
230
        }
231
232
        return $primary->handle . '/' . $this->slug;
233
    }
234
235
    /**
236
     * @return array
237
     */
238
    public function attributes()
239
    {
240
        return array_merge(
241
            parent::attributes(),
242
            $this->dateJoinedAttributes(),
243
            [
244
                //                'types',
245
                //                'activeType',
246
                //                'primaryType',
247
                //                'users',
248
            ]
249
        );
250
    }
251
252
    /**
253
     * @inheritdoc
254
     * @throws \yii\base\InvalidConfigException
255
     */
256
    public function rules()
257
    {
258
        return array_merge(
259
            parent::rules(),
260
            $this->dateJoinedRules(),
261
            [
262
                [
263
                    [
264
                        'types',
265
                        'activeType',
266
                        'primaryType',
267
                        'users',
268
                    ],
269
                    'safe',
270
                    'on' => [
271
                        ModelHelper::SCENARIO_DEFAULT
272
                    ]
273
274
                ],
275
            ]
276
        );
277
    }
278
279
    /**
280
     * @return array
281
     */
282
    public function attributeLabels()
283
    {
284
        return array_merge(
285
            parent::attributeLabels(),
286
            $this->dateJoinedAttributeLabels(),
287
            [
288
                //                'types' => Craft::t('organizations', 'Types'),
289
                //                'activeType' => Craft::t('organizations', 'Active Type'),
290
                //                'primaryType' => Craft::t('organizations', 'Primary Type'),
291
                //                'users' => Craft::t('organizations', 'Users'),
292
            ]
293
        );
294
    }
295
296
    /************************************************************
297
     * FIELD LAYOUT
298
     ************************************************************/
299
300
    /**
301
     * @inheritdoc
302
     */
303
    public function getFieldLayout()
304
    {
305
        if (!$type = $this->getActiveType()) {
306
            return OrganizationPlugin::getInstance()->getSettings()->getFieldLayout();
307
        }
308
309
        return $type->getFieldLayout();
310
    }
311
312
    /************************************************************
313
     * ELEMENT ADMIN
314
     ************************************************************/
315
316
    /**
317
     * @inheritdoc
318
     */
319
    public function getCpEditUrl()
320
    {
321
        return UrlHelper::cpUrl('organizations/' . $this->id);
322
    }
323
324
    /**
325
     * @inheritdoc
326
     */
327
    protected static function defineSources(string $context = null): array
328
    {
329
        switch ($context) {
330
            case 'user':
331
                return self::defineUserSources();
332
333
            default:
334
                return self::defineTypeSources();
335
        }
336
    }
337
338
    /**
339
     * @return array
340
     */
341
    private static function defineDefaultSources(): array
342
    {
343
        return [
344
            [
345
                'key' => '*',
346
                'label' => Craft::t('organizations', 'All organizations'),
347
                'criteria' => ['status' => null],
348
                'hasThumbs' => true
349
            ]
350
        ];
351
    }
352
353
    /**
354
     * @return array
355
     */
356
    private static function defineTypeSources(): array
357
    {
358
        $sources = self::defineDefaultSources();
359
360
        // Array of all organization types
361
        $organizationTypes = OrganizationType::findAll([]);
362
363
        $sources[] = ['heading' => Craft::t('organizations', 'Types')];
364
365
        /** @var TypeModel $organizationType */
366
        foreach ($organizationTypes as $organizationType) {
367
            $sources[] = [
368
                'key' => 'type:' . $organizationType->id,
369
                'label' => $organizationType->name,
370
                'criteria' => ['status' => null, 'typeId' => $organizationType->id],
371
                'hasThumbs' => true
372
            ];
373
        }
374
375
        return $sources;
376
    }
377
378
    /**
379
     * @return array
380
     */
381
    private static function defineUserSources(): array
382
    {
383
        $sources = self::defineDefaultSources();
384
385
        // Array of all organization types
386
        $organizationUsers = User::find();
387
388
        $sources[] = ['heading' => Craft::t('organizations', 'Users')];
389
390
        /** @var User $organizationUser */
391
        foreach ($organizationUsers as $organizationUser) {
392
            $sources[] = [
393
                'key' => 'user:' . $organizationUser->id,
394
                'label' => $organizationUser->getFullName(),
395
                'criteria' => [
396
                    'status' => null,
397
                    'users' => [$organizationUser->id]
398
                ],
399
                'hasThumbs' => true
400
            ];
401
        }
402
403
        return $sources;
404
    }
405
406
    /**
407
     * @inheritdoc
408
     */
409
    protected static function defineActions(string $source = null): array
410
    {
411
        $actions = [];
412
413
        // Edit
414
        $actions[] = Craft::$app->getElements()->createAction([
415
            'type' => EditAction::class,
416
            'label' => Craft::t('organizations', 'Edit organization'),
417
        ]);
418
419
//        if (Craft::$app->getUser()->checkPermission('deleteOrganizations')) {
420
//            // DeleteOrganization
421
//            $actions[] = DeleteAction::class;
422
//        }
423
424
        return $actions;
425
    }
426
427
    /**
428
     * @inheritdoc
429
     */
430
    protected static function defineSearchableAttributes(): array
431
    {
432
        return [
433
            'id'
434
        ];
435
    }
436
437
    /**
438
     * @inheritdoc
439
     */
440
    protected static function defineSortOptions(): array
441
    {
442
        return [
443
            'title' => Craft::t('organizations', 'Name'),
444
            'dateJoined' => Craft::t('organizations', 'Join Date'),
445
        ];
446
    }
447
448
    /**
449
     * @inheritdoc
450
     */
451
    public static function eagerLoadingMap(array $sourceElements, string $handle)
452
    {
453
        switch ($handle) {
454
            case 'users':
455
                return self::eagerLoadingUsersMap($sourceElements);
456
        }
457
458
        return parent::eagerLoadingMap($sourceElements, $handle);
459
    }
460
461
    /**
462
     * @inheritdoc
463
     */
464
    public function setEagerLoadedElements(string $handle, array $elements)
465
    {
466
        switch ($handle) {
467
            case 'users':
468
                $users = $elements ?? [];
469
                $this->setUsers($users);
470
                break;
471
472
            default:
473
                parent::setEagerLoadedElements($handle, $elements);
474
        }
475
    }
476
477
    /**
478
     * @inheritdoc
479
     */
480
    public static function defineTableAttributes(): array
481
    {
482
        return [
483
            'id' => ['label' => Craft::t('app', 'Name')],
484
            'uri' => ['label' => Craft::t('app', 'URI')],
485
            'types' => ['label' => Craft::t('organizations', 'Type(s)')],
486
            'dateJoined' => ['label' => Craft::t('organizations', 'Join Date')],
487
            'dateCreated' => ['label' => Craft::t('app', 'Date Created')],
488
            'dateUpdated' => ['label' => Craft::t('app', 'Date Updated')],
489
        ];
490
    }
491
492
493
494
    // Indexes, etc.
495
    // -------------------------------------------------------------------------
496
497
    /**
498
     * @inheritdoc
499
     */
500
    public function tableAttributeHtml(string $attribute): string
501
    {
502
503
        switch ($attribute) {
504
            case 'types':
505
                $typeHtmlParts = [];
506
                foreach ($this->getTypes()->all() as $type) {
507
                    $typeHtmlParts[] = '<a href="' .
508
                        UrlHelper::cpUrl('organizations/' . $this->id . '/' . $type->handle) .
509
                        '">' .
510
                        $type->name .
511
                        '</a>';
512
                }
513
514
                return !empty($typeHtmlParts) ? StringHelper::toString($typeHtmlParts, ', ') : '';
515
        }
516
517
        return parent::tableAttributeHtml($attribute);
518
    }
519
520
    /**
521
     * @inheritdoc
522
     */
523
    public function getUriFormat()
524
    {
525
        if (null === ($siteSettings = $this->getSiteSettings())) {
526
            return null;
527
        }
528
529
        if (!$siteSettings->hasUrls()) {
530
            return null;
531
        }
532
533
        return $siteSettings->getUriFormat();
534
    }
535
536
    /**
537
     * @inheritdoc
538
     */
539
    public function route()
540
    {
541
        if (in_array(
542
            $this->getStatus(),
543
            [static::STATUS_DISABLED, static::STATUS_ARCHIVED],
544
            true
545
        )) {
546
            return null;
547
        }
548
549
        if (null === ($siteSettings = $this->getSiteSettings())) {
550
            return null;
551
        }
552
553
        if (!$siteSettings->hasUrls()) {
554
            return null;
555
        }
556
557
        return [
558
            'templates/render',
559
            [
560
                'template' => $siteSettings->getTemplate(),
561
                'variables' => [
562
                    'organization' => $this,
563
                ]
564
            ]
565
        ];
566
    }
567
568
    /**
569
     * @return \flipbox\organizations\records\OrganizationTypeSiteSettings|null
570
     */
571
    protected function getSiteSettings()
572
    {
573
        try {
574
            $settings = OrganizationPlugin::getInstance()->getSettings();
575
            $siteSettings = $settings->getSiteSettings()[$this->siteId] ?? null;
576
577
            if (null !== ($type = $this->getPrimaryType())) {
578
                $siteSettings = $type->getSiteSettings()[$this->siteId] ?? $siteSettings;
579
            }
580
581
            return $siteSettings;
582
        } catch (\Exception $e) {
583
            OrganizationPlugin::error(
584
                sprintf(
585
                    "An exception was caught while to resolve site settings: %s",
586
                    $e->getMessage()
587
                )
588
            );
589
        }
590
591
        return null;
592
    }
593
594
    /************************************************************
595
     * EVENTS
596
     ************************************************************/
597
598
    /**
599
     * @inheritdoc
600
     */
601
    public function beforeSave(bool $isNew): bool
602
    {
603
        if (empty($this->getDateJoined())) {
604
            $this->setDateJoined(DateTimeHelper::currentUTCDateTime());
605
        }
606
607
        return parent::beforeSave($isNew);
608
    }
609
610
    /**
611
     * @inheritdoc
612
     * @throws /Exception
613
     */
614
    public function afterSave(bool $isNew)
615
    {
616
        if (false === $this->saveRecord($isNew)) {
617
            throw new Exception('Unable to save organization record');
618
        }
619
620
        // Types
621
        if (false === $this->saveTypes()) {
622
            throw new Exception("Unable to save types.");
623
        }
624
625
        // Users
626
        if (false === $this->saveUsers()) {
627
            throw new Exception("Unable to save users.");
628
        }
629
630
        parent::afterSave($isNew);
631
    }
632
633
    /*******************************************
634
     * RECORD
635
     *******************************************/
636
637
    /**
638
     * @param bool $isNew
639
     * @return bool
640
     */
641
    protected function saveRecord(bool $isNew): bool
642
    {
643
        $record = $this->elementToRecord();
644
645
        if (!$record->save()) {
646
            $this->addErrors($record->getErrors());
647
648
            OrganizationPlugin::error(
649
                Json::encode($this->getErrors()),
650
                __METHOD__
651
            );
652
653
            return false;
654
        }
655
656
        if (false !== ($dateUpdated = DateTimeHelper::toDateTime($record->dateUpdated))) {
657
            $this->dateUpdated = $dateUpdated;
658
        }
659
660
661
        if ($isNew) {
662
            $this->id = $record->id;
663
664
            if (false !== ($dateCreated = DateTimeHelper::toDateTime($record->dateCreated))) {
665
                $this->dateCreated = $dateCreated;
666
            }
667
        }
668
669
        return true;
670
    }
671
672
    /**
673
     * @inheritdoc
674
     * @return OrganizationRecord
675
     */
676
    protected function elementToRecord(): OrganizationRecord
677
    {
678
        if (!$record = OrganizationRecord::findOne([
679
            'id' => $this->getId()
680
        ])) {
681
            $record = new OrganizationRecord();
682
        }
683
684
        // PopulateOrganizationTypeTrait the record attributes
685
        $record->id = $this->getId();
686
        $record->dateJoined = $this->getDateJoined();
687
688
        return $record;
689
    }
690
}
691