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
|
|||
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 Adding the
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 Adding the
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 Adding the
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 Adding the
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 Adding the
Loading history...
|
|||
411 | } |
||
412 | |||
413 | $this->resetUsers(); |
||
414 | |||
415 | return $success; |
||
416 | } |
||
417 | } |
||
418 |
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
The trait
Idable
provides a methodequalsId
that in turn relies on the methodgetId()
. 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.