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

UsersAttributeTrait::dissociateUsers()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 0
cts 25
cp 0
rs 8.4586
c 0
b 0
f 0
cc 7
nc 9
nop 1
crap 56
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
        $currentAssociations = UserAssociation::find()
294
            ->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...
295
            ->indexBy('userId')
296
            ->orderBy(['userOrder' => SORT_ASC])
297
            ->all();
298
299
        $success = true;
300
301
        if (null === ($users = $this->getUsers()->getCachedResult())) {
302
            foreach ($currentAssociations as $currentAssociation) {
303
                if (!$currentAssociation->delete()) {
304
                    $success = false;
305
                }
306
            }
307
308
            if (!$success) {
309
                $this->addError('types', 'Unable to dissociate 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...
310
            }
311
312
            return $success;
313
        }
314
315
        $associations = [];
316
        $order = 1;
317
        foreach ($users as $user) {
318
            if (null === ($association = ArrayHelper::remove($currentAssociations, $user->getId()))) {
319
                $association = (new UserAssociation())
320
                    ->setUser($user)
321
                    ->setOrganization($this);
322
            }
323
324
            $association->userOrder = $order++;
325
326
            $associations[] = $association;
327
        }
328
329
        // DeleteOrganization those removed
330
        foreach ($currentAssociations as $currentAssociation) {
331
            if (!$currentAssociation->delete()) {
332
                $success = false;
333
            }
334
        }
335
336
        foreach ($associations as $association) {
337
            if (!$association->save()) {
338
                $success = false;
339
            }
340
        }
341
342
        if (!$success) {
343
            $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...
344
        }
345
346
        return $success;
347
    }
348
349
    /**
350
     * @param UserQuery $query
351
     * @return bool
352
     * @throws \Throwable
353
     */
354
    public function associateUsers(UserQuery $query)
355
    {
356
        $users = $query->all();
357
358
        if (empty($users)) {
359
            return true;
360
        }
361
362
        $success = true;
363
        $currentAssociations = UserAssociation::find()
364
            ->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...
365
            ->indexBy('userId')
366
            ->all();
367
368
        foreach ($users as $user) {
369
            if (null === ($association = ArrayHelper::remove($currentAssociations, $user->getId()))) {
370
                $association = (new UserAssociation())
371
                    ->setUser($user)
372
                    ->setOrganization($this);
373
            }
374
375
            if (!$association->save()) {
376
                $success = false;
377
            }
378
        }
379
380
        if (!$success) {
381
            $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...
382
        }
383
384
        $this->resetUsers();
385
386
        return $success;
387
    }
388
389
    /**
390
     * @param UserQuery $query
391
     * @return bool
392
     * @throws \Throwable
393
     */
394
    public function dissociateUsers(UserQuery $query)
395
    {
396
        $users = $query->all();
397
398
        if (empty($users)) {
399
            return true;
400
        }
401
402
        $currentAssociations = UserAssociation::find()
403
            ->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...
404
            ->indexBy('userId')
405
            ->all();
406
407
        $success = true;
408
409
        foreach ($users as $user) {
410
            if (null === ($association = ArrayHelper::remove($currentAssociations, $user->getId()))) {
411
                continue;
412
            }
413
414
            if (!$association->delete()) {
415
                $success = false;
416
            }
417
        }
418
419
        if (!$success) {
420
            $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...
421
        }
422
423
        $this->resetUsers();
424
425
        return $success;
426
    }
427
}
428