This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
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\organization\services; |
||
10 | |||
11 | use Craft; |
||
12 | use craft\db\Query; |
||
13 | use craft\elements\User as UserElement; |
||
14 | use craft\helpers\ArrayHelper; |
||
15 | use flipbox\organization\elements\db\User as UserQuery; |
||
16 | use flipbox\organization\elements\Organization as OrganizationElement; |
||
17 | use flipbox\organization\events\ManageOrganizationUser; |
||
18 | use flipbox\organization\helpers\Query as QueryHelper; |
||
19 | use flipbox\organization\Organization as OrganizationPlugin; |
||
20 | use flipbox\organization\records\User as OrganizationUserRecord; |
||
21 | use flipbox\spark\helpers\RecordHelper; |
||
22 | use yii\base\Component; |
||
23 | use yii\base\ErrorException as Exception; |
||
24 | |||
25 | /** |
||
26 | * @author Flipbox Factory <[email protected]> |
||
27 | * @since 1.0.0 |
||
28 | */ |
||
29 | class User extends Component |
||
30 | { |
||
31 | |||
32 | /** |
||
33 | * @event ManageOrganizationUserEvent The event that is triggered before a |
||
34 | * user is associated to an organization. |
||
35 | * |
||
36 | * You may set [[ManageOrganizationUserEvent::isValid]] to `false` to prevent the |
||
37 | * user from being associated to the organization. |
||
38 | */ |
||
39 | const EVENT_BEFORE_ASSOCIATE = 'beforeAssociate'; |
||
40 | |||
41 | /** |
||
42 | * @event ManageOrganizationUserEvent The event that is triggered after a |
||
43 | * user is associated to an organization. |
||
44 | * |
||
45 | * * You may set [[ManageOrganizationUserEvent::isValid]] to `false` to prevent the |
||
46 | * user from being associated to the organization. |
||
47 | */ |
||
48 | const EVENT_AFTER_ASSOCIATE = 'afterAssociate'; |
||
49 | |||
50 | /** |
||
51 | * @event ManageOrganizationUserEvent The event that is triggered before a |
||
52 | * user is remove from an organization. |
||
53 | * |
||
54 | * You may set [[ManageOrganizationUserEvent::isValid]] to `false` to prevent the |
||
55 | * user from being removed from the organization. |
||
56 | */ |
||
57 | const EVENT_BEFORE_DISSOCIATE = 'beforeDissociate'; |
||
58 | |||
59 | /** |
||
60 | * @event ManageOrganizationUserEvent The event that is triggered after a |
||
61 | * user is remove from an organization. |
||
62 | * |
||
63 | * * You may set [[ManageOrganizationUserEvent::isValid]] to `false` to prevent the |
||
64 | * user from being removed from the organization. |
||
65 | */ |
||
66 | const EVENT_AFTER_DISSOCIATE = 'afterDissociate'; |
||
67 | |||
68 | |||
69 | /** |
||
70 | * @inheritdoc |
||
71 | */ |
||
72 | public static function elementClass(): string |
||
73 | { |
||
74 | return UserElement::class; |
||
75 | } |
||
76 | |||
77 | /******************************************* |
||
78 | * QUERY |
||
79 | *******************************************/ |
||
80 | |||
81 | /** |
||
82 | * Get query |
||
83 | * |
||
84 | * @param $criteria |
||
85 | * @return UserQuery |
||
86 | */ |
||
87 | public function getQuery($criteria = []) |
||
88 | { |
||
89 | |||
90 | /** @var UserQuery $query */ |
||
91 | $query = new UserQuery(UserElement::class); |
||
92 | |||
93 | // Force array |
||
94 | if (!is_array($criteria)) { |
||
95 | $criteria = ArrayHelper::toArray($criteria, [], false); |
||
96 | } |
||
97 | |||
98 | // Configure it |
||
99 | QueryHelper::configure( |
||
100 | $query, |
||
101 | $criteria |
||
102 | ); |
||
103 | |||
104 | return $query; |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * @param array $ownerCriteria |
||
109 | * @param array $criteria |
||
110 | * @return UserQuery |
||
111 | */ |
||
112 | public function getOwnerQuery($ownerCriteria = [], $criteria = []) |
||
113 | { |
||
114 | |||
115 | $query = $this->getQuery($criteria) |
||
116 | ->organization(['owner' => $ownerCriteria]); |
||
117 | |||
118 | return $query; |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * @param array $userCriteria |
||
123 | * @param array $criteria |
||
124 | * @return UserQuery |
||
125 | */ |
||
126 | public function getUserQuery($userCriteria = [], $criteria = []) |
||
127 | { |
||
128 | |||
129 | $query = $this->getQuery($criteria) |
||
130 | ->organization(['user' => $userCriteria]); |
||
131 | |||
132 | return $query; |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * @param array $memberCriteria |
||
137 | * @param array $criteria |
||
138 | * @return UserQuery |
||
139 | */ |
||
140 | public function getMemberQuery($memberCriteria = [], $criteria = []) |
||
141 | { |
||
142 | |||
143 | $query = $this->getQuery($criteria) |
||
144 | ->organization(['member' => $memberCriteria]); |
||
145 | |||
146 | return $query; |
||
147 | } |
||
148 | |||
149 | /******************************************* |
||
150 | * UTILITY |
||
151 | *******************************************/ |
||
152 | |||
153 | /** |
||
154 | * @param UserElement $user |
||
155 | * @param array $criteria |
||
156 | * @return bool |
||
157 | */ |
||
158 | public function isUser(UserElement $user, $criteria = []) |
||
159 | { |
||
160 | |||
161 | // Gotta have an Id to be a user |
||
162 | if (!$user->id) { |
||
0 ignored issues
–
show
|
|||
163 | return false; |
||
164 | } |
||
165 | |||
166 | return $this->getUserQuery($criteria, ['id' => $user->id]) |
||
167 | ->count() > 0; |
||
168 | } |
||
169 | |||
170 | /** |
||
171 | * @param UserElement $user |
||
172 | * @param array $criteria |
||
173 | * @return bool |
||
174 | */ |
||
175 | public function isOwner(UserElement $user, $criteria = []) |
||
176 | { |
||
177 | |||
178 | // Gotta have an Id to be an owner |
||
179 | if (!$user->id) { |
||
0 ignored issues
–
show
The expression
$user->id of type integer|null is loosely compared to false ; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||
180 | return false; |
||
181 | } |
||
182 | |||
183 | return $this->getOwnerQuery($criteria, ['id' => $user->id]) |
||
184 | ->count() > 0; |
||
185 | } |
||
186 | |||
187 | /** |
||
188 | * @param UserElement $user |
||
189 | * @param array $criteria |
||
190 | * @return bool |
||
191 | */ |
||
192 | public function isMember(UserElement $user, $criteria = []) |
||
193 | { |
||
194 | |||
195 | // Gotta have an Id to be a member |
||
196 | if (!$user->id) { |
||
0 ignored issues
–
show
The expression
$user->id of type integer|null is loosely compared to false ; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For 0 == false // true
0 == null // true
123 == false // false
123 == null // false
// It is often better to use strict comparison
0 === false // false
0 === null // false
![]() |
|||
197 | return false; |
||
198 | } |
||
199 | |||
200 | return $this->getMemberQuery($criteria, ['id' => $user->id]) |
||
201 | ->count() > 0; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * @param UserElement $userElement |
||
206 | * @param OrganizationElement $organizationElement |
||
207 | * @param int|null $siteId |
||
208 | * @param int|null $sortOrder The order which the user should be positioned. |
||
209 | * If the value is zero (0) we position them last. |
||
210 | */ |
||
211 | protected function applySortOrder( |
||
212 | UserElement $userElement, |
||
213 | OrganizationElement $organizationElement, |
||
214 | int $siteId = null, |
||
215 | int $sortOrder = null |
||
216 | ) { |
||
217 | |||
218 | // No order |
||
219 | if (null === $sortOrder) { |
||
220 | return; |
||
221 | } |
||
222 | |||
223 | /** @var array $currentOrder */ |
||
224 | $currentOrder = $this->getCurrentSortOrder($organizationElement, $siteId); |
||
225 | |||
226 | // The target record to position |
||
227 | if (!$target = ArrayHelper::remove($currentOrder, $userElement->id)) { |
||
228 | return; |
||
229 | } |
||
230 | |||
231 | $currentSortOrder = ArrayHelper::getValue($target, 'sortOrder'); |
||
232 | |||
233 | // Sort order already correct? |
||
234 | if ($sortOrder === $currentSortOrder) { |
||
235 | return; |
||
236 | } |
||
237 | |||
238 | /** @var int $items */ |
||
239 | $items = count($currentOrder); |
||
240 | |||
241 | // Last |
||
242 | if (0 === $sortOrder || $sortOrder > $items) { |
||
243 | // Set to last |
||
244 | Craft::$app->getDb()->createCommand()->update( |
||
245 | OrganizationUserRecord::tableName(), |
||
246 | [ |
||
247 | 'sortOrder' => ($items + 1) |
||
248 | ], |
||
249 | $target |
||
250 | )->execute(); |
||
251 | |||
252 | return; |
||
253 | } |
||
254 | |||
255 | // First |
||
256 | if (1 === $sortOrder) { |
||
257 | $newOrder = [$userElement->id => $target] + $currentOrder; |
||
258 | } else { |
||
259 | $offset = $sortOrder - 1; |
||
260 | |||
261 | // Split at sortOrder / offset |
||
262 | $preOrder = array_slice($currentOrder, 0, $offset, true); |
||
263 | $postOrder = array_slice($currentOrder, $offset, null, true); |
||
264 | |||
265 | // Merge them all back together |
||
266 | $newOrder = $preOrder + [$userElement->id => $target] + $postOrder; |
||
267 | } |
||
268 | |||
269 | $ct = 1; |
||
270 | foreach ($newOrder as $userId => $condition) { |
||
271 | // Update |
||
272 | Craft::$app->getDb()->createCommand()->update( |
||
273 | OrganizationUserRecord::tableName(), |
||
274 | [ |
||
275 | 'sortOrder' => $ct++ |
||
276 | ], |
||
277 | $condition |
||
278 | )->execute(); |
||
279 | } |
||
280 | } |
||
281 | |||
282 | /************************************************************ |
||
283 | * ASSOCIATE |
||
284 | ************************************************************/ |
||
285 | |||
286 | /** |
||
287 | * Associate a user to an organization |
||
288 | * |
||
289 | * @param UserElement $userElement |
||
290 | * @param OrganizationElement $organizationElement |
||
291 | * @param int|null $siteId |
||
292 | * @param int|null $sortOrder |
||
293 | * @return bool |
||
294 | * @throws Exception |
||
295 | * @throws \yii\db\Exception |
||
296 | */ |
||
297 | public function associate( |
||
298 | UserElement $userElement, |
||
299 | OrganizationElement $organizationElement, |
||
300 | int $siteId = null, |
||
301 | int $sortOrder = null |
||
302 | ) { |
||
303 | |||
304 | // Already associated |
||
305 | if ($this->associationExists($userElement, $organizationElement, $siteId)) { |
||
306 | $this->applySortOrder($userElement, $organizationElement, $siteId, $sortOrder); |
||
307 | return true; |
||
308 | } |
||
309 | |||
310 | // The event |
||
311 | $event = new ManageOrganizationUser([ |
||
312 | 'user' => $userElement, |
||
313 | 'organization' => $organizationElement |
||
314 | ]); |
||
315 | |||
316 | // Trigger event |
||
317 | $this->trigger( |
||
318 | static::EVENT_BEFORE_ASSOCIATE, |
||
319 | $event |
||
320 | ); |
||
321 | |||
322 | // Green light? |
||
323 | if (!$event->isValid) { |
||
324 | return false; |
||
325 | } |
||
326 | |||
327 | // Restrictions |
||
328 | if (OrganizationPlugin::getInstance()->getSettings()->hasAssociationRestriction()) { |
||
329 | if (OrganizationPlugin::getInstance()->getSettings()->memberAssociationRestriction()) { |
||
330 | $criteria = ['member' => $userElement->id]; |
||
331 | } else { |
||
332 | $criteria = ['user' => $userElement->id]; |
||
333 | } |
||
334 | |||
335 | // Ignore the current organization |
||
336 | $query = OrganizationPlugin::getInstance()->getOrganization()->getQuery( |
||
337 | array_merge( |
||
338 | [ |
||
339 | 'id' => 'not ' . $organizationElement->id, |
||
340 | 'status' => null |
||
341 | ], |
||
342 | $criteria |
||
343 | ) |
||
344 | ); |
||
345 | |||
346 | if ($query->count()) { |
||
347 | return false; |
||
348 | } |
||
349 | } |
||
350 | |||
351 | // Db transaction |
||
352 | $transaction = RecordHelper::beginTransaction(); |
||
353 | |||
354 | try { |
||
355 | // New record |
||
356 | $organizationUserRecord = new OrganizationUserRecord(); |
||
357 | |||
358 | // Transfer element attribute(s) to record |
||
359 | $organizationUserRecord->userId = $userElement->id; |
||
360 | $organizationUserRecord->organizationId = $organizationElement->id; |
||
361 | $organizationUserRecord->siteId = $siteId; |
||
362 | $organizationUserRecord->sortOrder = $sortOrder; |
||
363 | |||
364 | // Save record |
||
365 | if (!$organizationUserRecord->save()) { |
||
366 | // Roll back on failures |
||
367 | $transaction->rollBack(); |
||
368 | |||
369 | return false; |
||
370 | } |
||
371 | |||
372 | // Trigger event |
||
373 | $this->trigger( |
||
374 | static::EVENT_AFTER_ASSOCIATE, |
||
375 | $event |
||
376 | ); |
||
377 | |||
378 | // Green light? |
||
379 | if (!$event->isValid) { |
||
380 | // Roll back on failures |
||
381 | $transaction->rollBack(); |
||
382 | |||
383 | return false; |
||
384 | } |
||
385 | |||
386 | // Apply the sort order |
||
387 | $this->applySortOrder($userElement, $organizationElement, $siteId, $sortOrder); |
||
388 | } catch (Exception $e) { |
||
389 | // Roll back on failures |
||
390 | $transaction->rollBack(); |
||
391 | |||
392 | throw $e; |
||
393 | } |
||
394 | |||
395 | // Commit db transaction |
||
396 | $transaction->commit(); |
||
397 | |||
398 | return true; |
||
399 | } |
||
400 | |||
401 | |||
402 | /************************************************************ |
||
403 | * DISSOCIATE |
||
404 | ************************************************************/ |
||
405 | |||
406 | /** |
||
407 | * Dissociate a user to the organization |
||
408 | * |
||
409 | * @param $userElement |
||
410 | * @param $organizationElement |
||
411 | * @return bool |
||
412 | * @throws Exception |
||
413 | * @throws \yii\db\Exception |
||
414 | */ |
||
415 | public function dissociate(UserElement $userElement, OrganizationElement $organizationElement) |
||
416 | { |
||
417 | |||
418 | // Already not associated |
||
419 | if (!$this->associationExists($userElement, $organizationElement)) { |
||
420 | return true; |
||
421 | } |
||
422 | |||
423 | // The event |
||
424 | $event = new ManageOrganizationUser([ |
||
425 | 'user' => $userElement, |
||
426 | 'organization' => $organizationElement |
||
427 | ]); |
||
428 | |||
429 | // Trigger event |
||
430 | $this->trigger( |
||
431 | static::EVENT_BEFORE_DISSOCIATE, |
||
432 | $event |
||
433 | ); |
||
434 | |||
435 | // Green light? |
||
436 | if (!$event->isValid) { |
||
437 | return false; |
||
438 | } |
||
439 | |||
440 | // Db transaction |
||
441 | $transaction = Craft::$app->getDb()->beginTransaction(); |
||
442 | |||
443 | try { |
||
444 | // Delete |
||
445 | Craft::$app->getDb()->createCommand()->delete( |
||
446 | OrganizationUserRecord::tableName(), |
||
447 | [ |
||
448 | 'userId' => $userElement->id, |
||
449 | 'organizationId' => $organizationElement->id |
||
450 | ] |
||
451 | )->execute(); |
||
452 | |||
453 | // Trigger event |
||
454 | $this->trigger( |
||
455 | static::EVENT_AFTER_DISSOCIATE, |
||
456 | $event |
||
457 | ); |
||
458 | |||
459 | // Green light? |
||
460 | if (!$event->isValid) { |
||
461 | // Roll back on failures |
||
462 | $transaction->rollBack(); |
||
463 | |||
464 | return false; |
||
465 | } |
||
466 | } catch (Exception $e) { |
||
467 | // Roll back on failures |
||
468 | $transaction->rollBack(); |
||
469 | |||
470 | throw $e; |
||
471 | } |
||
472 | |||
473 | // Commit db transaction |
||
474 | $transaction->commit(); |
||
475 | |||
476 | return true; |
||
477 | } |
||
478 | |||
479 | /** |
||
480 | * @param OrganizationElement $organizationElement |
||
481 | * @param int|null $siteId |
||
482 | * @return array |
||
483 | */ |
||
484 | private function getCurrentSortOrder(OrganizationElement $organizationElement, int $siteId = null): array |
||
485 | { |
||
486 | |||
487 | return (new Query()) |
||
488 | ->select(['id', 'userId', 'sortOrder']) |
||
489 | ->from([OrganizationUserRecord::tableName()]) |
||
490 | ->andWhere( |
||
491 | [ |
||
492 | 'organizationId' => $organizationElement->id, |
||
493 | 'siteId' => $siteId |
||
494 | ] |
||
495 | ) |
||
496 | ->indexBy('userId') |
||
497 | ->orderBy([ |
||
498 | 'sortOrder' => SORT_ASC |
||
499 | ]) |
||
500 | ->limit(null) |
||
501 | ->all(); |
||
502 | } |
||
503 | |||
504 | /******************************************* |
||
505 | * RECORD CHECKING |
||
506 | *******************************************/ |
||
507 | |||
508 | /** |
||
509 | * @param UserElement $userElement |
||
510 | * @param OrganizationElement $organizationElement |
||
511 | * @param int|null $siteId |
||
512 | * @return bool |
||
513 | */ |
||
514 | private function associationExists( |
||
515 | UserElement $userElement, |
||
516 | OrganizationElement $organizationElement, |
||
517 | int $siteId = null |
||
518 | ) { |
||
519 | return null !== OrganizationUserRecord::findOne([ |
||
520 | 'organizationId' => $organizationElement->id, |
||
521 | 'userId' => $userElement->id, |
||
522 | 'siteId' => $siteId |
||
523 | ]); |
||
524 | } |
||
525 | } |
||
526 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
integer
values, zero is a special case, in particular the following results might be unexpected: