Completed
Push — temp/wip ( 87d9a9...2b4a6c )
by Nate
04:31
created

UsersAttributeTrait::saveUsers()   B

Complexity

Conditions 10
Paths 55

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
dl 0
loc 48
ccs 0
cts 37
cp 0
rs 7.2678
c 0
b 0
f 0
cc 10
nc 55
nop 0
crap 110

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\db\Query;
13
use craft\elements\db\UserQuery;
14
use craft\elements\User;
15
use craft\helpers\ArrayHelper;
16
use flipbox\craft\ember\helpers\QueryHelper;
17
use flipbox\organizations\records\UserAssociation;
18
use flipbox\organizations\records\UserAssociation as OrganizationUsersRecord;
19
20
/**
21
 * @author Flipbox Factory <[email protected]>
22
 * @since 1.0.0
23
 */
24
trait UsersAttributeTrait
25
{
26
    /**
27
     * @var UserQuery
28
     */
29
    private $users;
30
31
    /**
32
     * @param array $sourceElements
33
     * @return array
34
     */
35
    private static function eagerLoadingUsersMap(array $sourceElements)
36
    {
37
        // Get the source element IDs
38
        $sourceElementIds = ArrayHelper::getColumn($sourceElements, 'id');
39
40
        $map = (new Query())
41
            ->select(['organizationId as source', 'userId as target'])
42
            ->from(OrganizationUsersRecord::tableName())
43
            ->where(['organizationId' => $sourceElementIds])
44
            ->all();
45
46
        return [
47
            'elementType' => User::class,
48
            'map' => $map
49
        ];
50
    }
51
52
    /************************************************************
53
     * REQUEST
54
     ************************************************************/
55
56
    /**
57
     * AssociateUserToOrganization an array of users from request input
58
     *
59
     * @param string $identifier
60
     * @return $this
61
     */
62
    public function setUsersFromRequest(string $identifier = 'users')
63
    {
64
        if ($users = Craft::$app->getRequest()->getBodyParam($identifier, [])) {
65
            // Set users array
66
            $this->setUsers($users);
67
        }
68
69
        return $this;
70
    }
71
72
    /************************************************************
73
     * USERS
74
     ************************************************************/
75
76
    /**
77
     * @param array $criteria
78
     * @return UserQuery
79
     */
80
    public function userQuery($criteria = []): UserQuery
81
    {
82
        /** @noinspection PhpUndefinedMethodInspection */
83
        $query = User::find()
84
            ->organization($this)
85
            ->orderBy([
86
                'userOrder' => SORT_ASC,
87
                'username' => SORT_ASC,
88
            ]);
89
90
        if (!empty($criteria)) {
91
            QueryHelper::configure(
92
                $query,
93
                $criteria
94
            );
95
        }
96
97
        return $query;
98
    }
99
100
    /**
101
     * Get an array of users associated to an organization
102
     *
103
     * @param array $criteria
104
     * @return UserQuery
105
     */
106
    public function getUsers($criteria = [])
107
    {
108
        if (null === $this->users) {
109
            $this->users = $this->userQuery();
110
        }
111
112
        if (!empty($criteria)) {
113
            QueryHelper::configure(
114
                $this->users,
115
                $criteria
116
            );
117
        }
118
119
        return $this->users;
120
    }
121
122
    /**
123
     * AssociateUserToOrganization users to an organization
124
     *
125
     * @param $users
126
     * @return $this
127
     */
128
    public function setUsers($users)
129
    {
130
        if ($users instanceof UserQuery) {
131
            $this->users = $users;
132
            return $this;
133
        }
134
135
        // Reset the query
136
        $this->users = $this->userQuery();
137
138
        // Remove all users
139
        $this->users->setCachedResult([]);
140
141
        $this->addUsers($users);
142
143
        return $this;
144
    }
145
146
    /**
147
     * AssociateUserToOrganization an array of users to an organization
148
     *
149
     * @param $users
150
     * @return $this
151
     */
152
    public function addUsers(array $users)
153
    {
154
        // In case a config is directly passed
155
        if (ArrayHelper::isAssociative($users)) {
156
            $users = [$users];
157
        }
158
159
        foreach ($users as $key => $user) {
160
            // Ensure we have a model
161
            if (!$user instanceof User) {
162
                $user = $this->resolveUser($user);
163
            }
164
165
            $this->addUser($user);
166
        }
167
168
        return $this;
169
    }
170
171
    protected function resolveUser($user)
172
    {
173
        if (is_array($user) &&
174
            null !== ($id = ArrayHelper::getValue($user, 'id'))
175
        ) {
176
            $user = ['id' => $id];
177
        }
178
179
        $object = null;
180
        if (is_array($user)) {
181
            $object = User::findOne($user);
182
        } elseif (is_numeric($user)) {
183
            $object = Craft::$app->getUsers()->getUserById($user);
184
        } elseif (is_string($user)) {
185
            $object = Craft::$app->getUsers()->getUserByUsernameOrEmail($user);
186
        }
187
188
        if (null !== $object) {
189
            return $object;
190
        }
191
192
        return new User($user);
193
    }
194
195
    /**
196
     * AssociateUserToOrganization a user to an organization
197
     *
198
     * @param User $user
199
     * @return $this
200
     */
201
    public function addUser(User $user)
202
    {
203
204
        $currentUsers = $this->getUsers()->all();
205
206
        $userElementsByEmail = ArrayHelper::index(
207
            $currentUsers,
208
            'email'
209
        );
210
211
        // Does the user already exist?
212
        if (!array_key_exists($user->email, $userElementsByEmail)) {
213
            $currentUsers[] = $user;
214
            $this->getUsers()->setCachedResult($currentUsers);
215
        }
216
217
        return $this;
218
    }
219
220
    /**
221
     * DissociateUserFromOrganization a user from an organization
222
     *
223
     * @param array $users
224
     * @return $this
225
     */
226
    public function removeUsers(array $users)
227
    {
228
        // In case a config is directly passed
229
        if (ArrayHelper::isAssociative($users)) {
230
            $users = [$users];
231
        }
232
233
        foreach ($users as $key => $user) {
234
            // Ensure we have a model
235
            if (!$user instanceof User) {
236
                $user = $this->resolveUser($user);
237
            }
238
239
            $this->removeUser($user);
240
        }
241
242
        return $this;
243
    }
244
245
    /**
246
     * DissociateUserFromOrganization a user from an organization
247
     *
248
     * @param User $user
249
     * @return $this
250
     */
251
    public function removeUser(User $user)
252
    {
253
        $userElementsByEmail = ArrayHelper::index(
254
            $this->getUsers()->all(),
255
            'email'
256
        );
257
258
        // Does the user already exist?
259
        if (array_key_exists($user->email, $userElementsByEmail)) {
260
            unset($userElementsByEmail[$user->email]);
261
262
            $this->getUsers()->setCachedResult(
263
                array_values($userElementsByEmail)
264
            );
265
        }
266
267
        return $this;
268
    }
269
270
    /**
271
     * Reset users
272
     *
273
     * @return $this
274
     */
275
    public function resetUsers()
276
    {
277
        $this->users = null;
278
        return $this;
279
    }
280
281
282
    /*******************************************
283
     * ASSOCIATE and/or DISASSOCIATE
284
     *******************************************/
285
286
    /**
287
     * @return bool
288
     * @throws \Throwable
289
     * @throws \yii\db\StaleObjectException
290
     */
291
    public function saveUsers()
292
    {
293
        // No changes?
294
        if (null === ($users = $this->getUsers()->getCachedResult())) {
295
            return true;
296
        }
297
298
        $currentAssociations = UserAssociation::find()
299
            ->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...
300
            ->indexBy('userId')
301
            ->orderBy(['userOrder' => SORT_ASC])
302
            ->all();
303
304
        $success = true;
305
306
        $associations = [];
307
        $order = 1;
308
        foreach ($users as $user) {
309
            if (null === ($association = ArrayHelper::remove($currentAssociations, $user->getId()))) {
310
                $association = (new UserAssociation())
311
                    ->setUser($user)
312
                    ->setOrganization($this);
313
            }
314
315
            $association->userOrder = $order++;
316
317
            $associations[] = $association;
318
        }
319
320
        // Delete those removed
321
        foreach ($currentAssociations as $currentAssociation) {
322
            if (!$currentAssociation->delete()) {
323
                $success = false;
324
            }
325
        }
326
327
        foreach ($associations as $association) {
328
            if (!$association->save()) {
329
                $success = false;
330
            }
331
        }
332
333
        if (!$success) {
334
            $this->addError('users', 'Unable to associate users.');
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...
335
        }
336
337
        return $success;
338
    }
339
340
    /**
341
     * @param UserQuery $query
342
     * @return bool
343
     * @throws \Throwable
344
     */
345
    public function associateUsers(UserQuery $query)
346
    {
347
        $users = $query->all();
348
349
        if (empty($users)) {
350
            return true;
351
        }
352
353
        $success = true;
354
        $currentAssociations = UserAssociation::find()
355
            ->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...
356
            ->indexBy('userId')
357
            ->all();
358
359
        foreach ($users as $user) {
360
            if (null === ($association = ArrayHelper::remove($currentAssociations, $user->getId()))) {
361
                $association = (new UserAssociation())
362
                    ->setUser($user)
363
                    ->setOrganization($this);
364
            }
365
366
            if (!$association->save()) {
367
                $success = false;
368
            }
369
        }
370
371
        if (!$success) {
372
            $this->addError('users', 'Unable to associate users.');
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...
373
        }
374
375
        $this->resetUsers();
376
377
        return $success;
378
    }
379
380
    /**
381
     * @param UserQuery $query
382
     * @return bool
383
     * @throws \Throwable
384
     */
385
    public function dissociateUsers(UserQuery $query)
386
    {
387
        $users = $query->all();
388
389
        if (empty($users)) {
390
            return true;
391
        }
392
393
        $currentAssociations = UserAssociation::find()
394
            ->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...
395
            ->indexBy('userId')
396
            ->all();
397
398
        $success = true;
399
400
        foreach ($users as $user) {
401
            if (null === ($association = ArrayHelper::remove($currentAssociations, $user->getId()))) {
402
                continue;
403
            }
404
405
            if (!$association->delete()) {
406
                $success = false;
407
            }
408
        }
409
410
        if (!$success) {
411
            $this->addError('users', 'Unable to associate users.');
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...
412
        }
413
414
        $this->resetUsers();
415
416
        return $success;
417
    }
418
}
419