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

src/elements/UsersAttributeTrait.php (6 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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