flipbox /
organization
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
Loading history...
|
|||
| 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
Loading history...
|
|||
| 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!=, orswitchconditions), values of different types might be equal.For
integervalues, zero is a special case, in particular the following results might be unexpected: