Completed
Push — master ( b27204...a2c64d )
by Nate
05:15 queued 02:48
created

TypesAttributeTrait::setTypesFromRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 7
cp 0
rs 10
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/organization/license
6
 * @link       https://www.flipboxfactory.com/software/organization/
7
 */
8
9
namespace flipbox\organizations\elements;
10
11
use Craft;
12
use craft\helpers\ArrayHelper;
13
use flipbox\craft\ember\helpers\QueryHelper;
14
use flipbox\organizations\queries\OrganizationTypeQuery;
15
use flipbox\organizations\records\OrganizationType;
16
use flipbox\organizations\records\OrganizationType as TypeModel;
17
use flipbox\organizations\records\OrganizationTypeAssociation;
18
19
/**
20
 * @author Flipbox Factory <[email protected]>
21
 * @since 1.0.0
22
 */
23
trait TypesAttributeTrait
24
{
25
    /**
26
     * @var OrganizationTypeQuery
27
     */
28
    private $types;
29
30
    /**
31
     * @var TypeModel|false
32
     */
33
    private $activeType;
34
35
    /************************************************************
36
     * REQUEST
37
     ************************************************************/
38
39
    /**
40
     * AssociateUserToOrganization an array of types from request input
41
     *
42
     * @param string $identifier
43
     * @return $this
44
     */
45
    public function setTypesFromRequest(string $identifier = 'types')
46
    {
47
        if (null !== ($types = Craft::$app->getRequest()->getBodyParam($identifier))) {
48
            $this->setTypes((array) $types);
49
        }
50
51
        return $this;
52
    }
53
54
    /************************************************************
55
     * ACTIVE TYPE
56
     ************************************************************/
57
58
    /**
59
     * @param TypeModel|null $type
60
     * @return $this
61
     */
62
    public function setActiveType(TypeModel $type = null)
63
    {
64
        if ($type) {
65
            $this->addType($type);
66
        }
67
68
        $this->activeType = (null === $type) ? false : $type;
69
        return $this;
70
    }
71
72
    /**
73
     * @return TypeModel|null
74
     */
75
    public function getActiveType()
76
    {
77
        if (null === $this->activeType) {
78
            if (!$activeType = $this->getPrimaryType()) {
79
                $activeType = false;
80
            }
81
82
            $this->activeType = $activeType;
83
        }
84
85
        return (false === $this->activeType) ? null : $this->activeType;
86
    }
87
88
    /************************************************************
89
     * TYPES
90
     ************************************************************/
91
92
    /**
93
     * Get an array of types associated to an organization
94
     *
95
     * @param array $criteria
96
     * @return OrganizationTypeQuery
97
     */
98
    public function getTypes($criteria = [])
99
    {
100
        if (null === $this->types) {
101
            $this->types = OrganizationType::find()
102
                ->organization($this);
103
        }
104
105
        if (!empty($criteria)) {
106
            QueryHelper::configure(
107
                $this->types,
108
                $criteria
109
            );
110
        }
111
112
        return $this->types;
113
    }
114
115
    /**
116
     * AssociateUserToOrganization types to an organization
117
     *
118
     * @param $types
119
     * @return $this
120
     */
121
    public function setTypes($types)
122
    {
123
        if ($types instanceof OrganizationTypeQuery) {
124
            $this->types = $types;
125
            return $this;
126
        }
127
128
        // Reset the query
129
        $this->types = OrganizationType::find()
130
            ->organization($this);
131
132
        // Remove all types
133
        $this->types->setCachedResult([]);
134
135
        if (!empty($types)) {
136
            if (!is_array($types)) {
137
                $types = [$types];
138
            }
139
140
            $this->addTypes($types);
141
        }
142
143
        return $this;
144
    }
145
146
    /**
147
     * AssociateUserToOrganization an array of types to an organization
148
     *
149
     * @param $types
150
     * @return $this
151
     */
152
    public function addTypes(array $types)
153
    {
154
        // In case a config is directly passed
155
        if (ArrayHelper::isAssociative($types)) {
156
            $types = [$types];
157
        }
158
159
        foreach ($types as $key => $type) {
160
            // Ensure we have a model
161
            if (!$type instanceof OrganizationType) {
162
                $type = $this->resolveType($type);
163
            }
164
165
            $this->addType($type);
166
        }
167
168
        return $this;
169
    }
170
171
    /**
172
     * AssociateUserToOrganization a type to an organization
173
     *
174
     * @param OrganizationType $type
175
     * @return $this
176
     */
177
    public function addType(OrganizationType $type)
178
    {
179
        $currentTypes = $this->getTypes()->all();
180
181
        $indexedTypes = ArrayHelper::index(
182
            $currentTypes,
183
            'handle'
184
        );
185
186
        if (!array_key_exists($type->handle, $indexedTypes)) {
187
            $currentTypes[] = $type;
188
            $this->getTypes()->setCachedResult($currentTypes);
189
        }
190
191
        return $this;
192
    }
193
194
    /**
195
     * DissociateUserFromOrganization a type from an organization
196
     *
197
     * @param array $types
198
     * @return $this
199
     */
200
    public function removeTypes(array $types)
201
    {
202
        // In case a config is directly passed
203
        if (ArrayHelper::isAssociative($types)) {
204
            $types = [$types];
205
        }
206
207
        foreach ($types as $key => $type) {
208
            if (!$type instanceof OrganizationType) {
209
                $type = $this->resolveType($type);
210
            }
211
212
            $this->removeType($type);
213
        }
214
215
        return $this;
216
    }
217
218
    /**
219
     * @param mixed $type
220
     * @return OrganizationType
221
     */
222
    protected function resolveType($type): OrganizationType
223
    {
224
        if (null !== ($type = OrganizationType::findOne($type))) {
225
            return $type;
226
        }
227
228
        if (!is_array($type)) {
229
            $type = ArrayHelper::toArray($type, [], false);
230
        }
231
232
        return new OrganizationType($type);
233
    }
234
235
    /**
236
     * DissociateUserFromOrganization a type from an organization
237
     *
238
     * @param OrganizationType $type
239
     * @return $this
240
     */
241
    public function removeType(OrganizationType $type)
242
    {
243
        $indexedTypes = ArrayHelper::index(
244
            $this->getTypes()->all(),
245
            'handle'
246
        );
247
248
        // Does the type already exist?
249
        if (array_key_exists($type->handle, $indexedTypes)) {
250
            unset($indexedTypes[$type->handle]);
251
252
            $this->getTypes()->setCachedResult(
253
                array_values($indexedTypes)
254
            );
255
        }
256
257
        return $this;
258
    }
259
260
    /**
261
     * Reset types
262
     *
263
     * @return $this
264
     */
265
    public function resetTypes()
266
    {
267
        $this->types = null;
268
        return $this;
269
    }
270
271
    /**
272
     * Get an associated type by identifier (id/handle)
273
     *
274
     * @param $identifier
275
     * @return null|TypeModel
276
     */
277
    public function getType($identifier)
278
    {
279
        // Determine index type
280
        $indexBy = (is_numeric($identifier)) ? 'id' : 'handle';
281
282
        // Find all types
283
        $allTypes = ArrayHelper::index(
284
            $this->getTypes()->all(),
285
            $indexBy
286
        );
287
288
        return array_key_exists($identifier, $allTypes) ? $allTypes[$identifier] : null;
289
    }
290
291
    /**
292
     * Identify whether a type is associated to the element
293
     *
294
     * @param TypeModel|null $type
295
     * @return bool
296
     */
297
    public function hasType(TypeModel $type = null): bool
298
    {
299
        if (null === $type) {
300
            return !empty($this->getTypes());
301
        }
302
303
        return null !== $this->getType($type->id);
304
    }
305
306
307
    /************************************************************
308
     * PRIMARY TYPE
309
     ************************************************************/
310
311
    /**
312
     * Identify whether a primary type is set
313
     *
314
     * @return bool
315
     */
316
    public function hasPrimaryType()
317
    {
318
        return $this->getTypes()->one() instanceof TypeModel;
319
    }
320
321
    /**
322
     * Identify whether the type is primary
323
     *
324
     * @param $type
325
     * @return bool
326
     */
327
    public function isPrimaryType(TypeModel $type)
328
    {
329
        if ($primaryType = $this->getPrimaryType()) {
330
            return $primaryType->id === $type->id;
331
        }
332
333
        return false;
334
    }
335
336
    /**
337
     * @param TypeModel $type
338
     * @return $this
339
     */
340
    public function setPrimaryType(TypeModel $type = null)
341
    {
342
        if (null === $type) {
343
            return $this;
344
        }
345
346
        return $this->setTypes(
347
            array_merge(
348
                [
349
                    $type
350
                ],
351
                $this->getTypes()->all()
352
            )
353
        );
354
    }
355
356
    /**
357
     * Get the primary type
358
     *
359
     * @return TypeModel|null
360
     */
361
    public function getPrimaryType()
362
    {
363
        if (!$this->hasPrimaryType()) {
364
            return null;
365
        }
366
367
        return $this->getTypes()->one();
368
    }
369
370
    /*******************************************
371
     * ASSOCIATE and/or DISASSOCIATE
372
     *******************************************/
373
374
    /**
375
     * @return bool
376
     * @throws \Throwable
377
     * @throws \flipbox\craft\ember\exceptions\RecordNotFoundException
378
     * @throws \yii\db\StaleObjectException
379
     */
380
    public function saveTypes(): bool
381
    {
382
        // No changes?
383
        if (null === ($types = $this->getTypes()->getCachedResult())) {
384
            return true;
385
        }
386
387
        $currentAssociations = OrganizationTypeAssociation::find()
388
            ->organizationId($this->getId() ?: false)
0 ignored issues
show
Bug introduced by
It seems like getId() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
389
            ->indexBy('typeId')
390
            ->all();
391
392
        $success = true;
393
        $associations = [];
394
        $order = 1;
395
        foreach ($types as $type) {
396
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
397
                $association = (new OrganizationTypeAssociation())
398
                    ->setType($type)
399
                    ->setOrganization($this);
400
            }
401
402
            $association->sortOrder = $order++;
403
404
            $associations[] = $association;
405
        }
406
407
        // Delete those removed
408
        foreach ($currentAssociations as $currentAssociation) {
409
            if (!$currentAssociation->delete()) {
410
                $success = false;
411
            }
412
        }
413
414
        foreach ($associations as $association) {
415
            if (!$association->save()) {
416
                $success = false;
417
            }
418
        }
419
420
        if (!$success) {
421
            $this->addError('types', 'Unable to associate types.');
0 ignored issues
show
Bug introduced by
It seems like addError() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
422
        }
423
424
        return $success;
425
    }
426
427
    /**
428
     * @param OrganizationTypeQuery $query
429
     * @return bool
430
     * @throws \Throwable
431
     */
432
    public function associateTypes(OrganizationTypeQuery $query): bool
433
    {
434
        $types = $query->all();
435
436
        if (empty($types)) {
437
            return true;
438
        }
439
440
        $currentAssociations = $currentAssociations = OrganizationTypeAssociation::find()
441
            ->organizationId($this->getId() ?: false)
0 ignored issues
show
Bug introduced by
It seems like getId() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
442
            ->indexBy('typeId')
443
            ->all();
444
445
        $success = true;
446
        foreach ($types as $type) {
447
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
448
                $association = (new OrganizationTypeAssociation())
449
                    ->setType($type)
450
                    ->setOrganization($this);
451
            }
452
453
            if (!$association->save()) {
454
                $success = false;
455
            }
456
        }
457
458
        if (!$success) {
459
            $this->addError('organizations', 'Unable to associate types.');
0 ignored issues
show
Bug introduced by
It seems like addError() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
460
        }
461
462
        $this->resetTypes();
463
464
        return $success;
465
    }
466
467
    /**
468
     * @param OrganizationTypeQuery $query
469
     * @return bool
470
     */
471
    public function dissociateTypes(OrganizationTypeQuery $query): bool
472
    {
473
        $types = $query->all();
474
475
        if (empty($types)) {
476
            return true;
477
        }
478
479
        $currentAssociations = $currentAssociations = OrganizationTypeAssociation::find()
480
            ->organizationId($this->getId() ?: false)
0 ignored issues
show
Bug introduced by
It seems like getId() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
481
            ->indexBy('typeId')
482
            ->all();
483
484
        $success = true;
485
        foreach ($types as $type) {
486
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
487
                continue;
488
            }
489
490
            if (!$association->delete()) {
491
                $success = false;
492
            }
493
        }
494
495
        if (!$success) {
496
            $this->addError('organizations', 'Unable to dissociate types.');
0 ignored issues
show
Bug introduced by
It seems like addError() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
497
        }
498
499
        $this->resetTypes();
500
501
        return $success;
502
    }
503
}
504