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

src/behaviors/UserOrganizationsBehavior.php (1 issue)

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
namespace flipbox\organizations\behaviors;
4
5
use Craft;
6
use craft\elements\User;
7
use craft\events\ModelEvent;
8
use craft\helpers\ArrayHelper;
9
use flipbox\craft\ember\helpers\QueryHelper;
10
use flipbox\organizations\elements\Organization;
11
use flipbox\organizations\Organizations as OrganizationPlugin;
12
use flipbox\organizations\queries\OrganizationQuery;
13
use flipbox\organizations\queries\UserAssociationQuery;
14
use flipbox\organizations\records\UserAssociation;
15
use flipbox\organizations\validators\OrganizationsValidator;
16
use yii\base\Behavior;
17
use yii\base\Event;
18
use yii\base\Exception;
19
use yii\helpers\Json;
20
21
/**
22
 * Class UserOrganizationsBehavior
23
 * @package flipbox\organizations\behaviors
24
 *
25
 * @property User $owner;
26
 */
27
class UserOrganizationsBehavior extends Behavior
28
{
29
    /**
30
     * @var OrganizationQuery|null
31
     */
32
    private $organizations;
33
34
    /**
35
     * @inheritdoc
36
     */
37
    public function init()
38
    {
39
        parent::init();
40
41
        // Validate organizations
42
        Event::on(
43
            User::class,
44
            User::EVENT_AFTER_VALIDATE,
45
            function (Event $e) {
46
                /** @var User $user */
47
                $user = $e->sender;
48
                $this->onAfterValidate($user);
49
            }
50
        );
51
52
        // Associate
53
        Event::on(
54
            User::class,
55
            User::EVENT_AFTER_SAVE,
56
            function (ModelEvent $e) {
57
                /** @var User $user */
58
                $user = $e->sender;
59
                $this->onAfterSave($user);
60
            }
61
        );
62
63
        // Dissociate
64
        Event::on(
65
            User::class,
66
            User::EVENT_AFTER_DELETE,
67
            function (Event $e) {
68
                /** @var User $user */
69
                $user = $e->sender;
70
                $this->onAfterDelete($user);
71
            }
72
        );
73
    }
74
75
    /**
76
     * @param User $user
77
     * @return void
78
     * @throws \Throwable
79
     */
80
    private function onAfterDelete(User $user)
81
    {
82
        /** @var UserOrganizationsBehavior $user */
83
        // Remove organizations
84
        $user->setOrganizations([]);
85
86
        // Save associations (which is really deleting them all)
87
        $user->saveOrganizations();
88
    }
89
90
    /**
91
     * @param User|self $user
92
     * @throws Exception
93
     * @throws \Exception
94
     * @throws \Throwable
95
     * @throws \craft\errors\ElementNotFoundException
96
     */
97
    private function onAfterSave(User $user)
98
    {
99
        // Check cache for explicitly set (and possibly not saved) organizations
100
        if (null !== ($organizations = $user->getOrganizations()->getCachedResult())) {
101
102
            /** @var Organization $organization */
103
            foreach ($organizations as $organization) {
104
                if (!$organization->id) {
105
                    if (!Craft::$app->getElements()->saveElement($organization)) {
106
                        $user->addError(
107
                            'organizations',
108
                            Craft::t('organizations', 'Unable to save organization.')
109
                        );
110
111
                        throw new Exception('Unable to save organization.');
112
                    }
113
                }
114
            }
115
        }
116
117
        $this->saveOrganizations();
118
    }
119
120
    /**
121
     * @param User|self $user
122
     * @return void
123
     */
124
    private function onAfterValidate(User $user)
125
    {
126
        $error = null;
127
128
        if (!(new OrganizationsValidator())->validate($user->getOrganizations(), $error)) {
129
            $user->addError('organizations', $error);
130
        }
131
    }
132
133
    /**
134
     * @param array $criteria
135
     * @return OrganizationQuery
136
     */
137
    public function organizationQuery($criteria = []): OrganizationQuery
138
    {
139
        $query = Organization::find()
140
            ->user($this->owner)
0 ignored issues
show
It seems like $this->owner can also be of type object<yii\base\Component>; however, flipbox\craft\ember\quer...rAttributeTrait::user() does only seem to accept string|array<integer,str...ft\elements\User>>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
141
            ->orderBy([
142
                'organizationOrder' => SORT_ASC,
143
                'title' => SORT_ASC
144
            ]);
145
146
        if (!empty($criteria)) {
147
            QueryHelper::configure(
148
                $query,
149
                $criteria
150
            );
151
        }
152
153
        return $query;
154
    }
155
156
    /**
157
     * Get a query with associated organizations
158
     *
159
     * @param array $criteria
160
     * @return OrganizationQuery
161
     */
162
    public function getOrganizations($criteria = []): OrganizationQuery
163
    {
164
        if (null === $this->organizations) {
165
            $this->organizations = $this->organizationQuery();
166
        }
167
168
        if (!empty($criteria)) {
169
            QueryHelper::configure(
170
                $this->organizations,
171
                $criteria
172
            );
173
        }
174
175
        return $this->organizations;
176
    }
177
178
    /**
179
     * Set an array or query of organizations to a user
180
     *
181
     * @param $organizations
182
     * @return $this
183
     */
184
    public function setOrganizations($organizations)
185
    {
186
        if ($organizations instanceof OrganizationQuery) {
187
            $this->organizations = $organizations;
188
            return $this;
189
        }
190
191
        // Reset the query
192
        $this->organizations = $this->organizationQuery();
193
        $this->organizations->setCachedResult([]);
194
        $this->addOrganizations($organizations);
195
        return $this;
196
    }
197
198
    /**
199
     * Add an array of organizations to a user.  Note: This does not save the organization associations.
200
     *
201
     * @param $organizations
202
     * @return $this
203
     */
204
    protected function addOrganizations(array $organizations)
205
    {
206
        // In case a config is directly passed
207
        if (ArrayHelper::isAssociative($organizations)) {
208
            $organizations = [$organizations];
209
        }
210
211
        foreach ($organizations as $key => $organization) {
212
            if (!$organization = $this->resolveOrganization($organization)) {
213
                OrganizationPlugin::info(sprintf(
214
                    "Unable to resolve organization: %s",
215
                    (string)Json::encode($organization)
216
                ));
217
                continue;
218
            }
219
220
            $this->addOrganization($organization);
221
        }
222
223
        return $this;
224
    }
225
226
    /**
227
     * Add a organization to a user.  Note: This does not save the organization association.
228
     *
229
     * @param Organization $organization
230
     * @param bool $addToOrganization
231
     * @return $this
232
     */
233
    public function addOrganization(Organization $organization, bool $addToOrganization = true)
234
    {
235
        // Current associated organizations
236
        $allOrganizations = $this->getOrganizations()->all();
237
        $allOrganizations[] = $organization;
238
239
        $this->getOrganizations()->setCachedResult($allOrganizations);
240
241
        // Add user to organization as well?
242
        if ($addToOrganization && $organization->id !== null) {
243
            $user = $this->owner;
244
            if ($user instanceof User) {
245
                $organization->addUser($user);
246
            };
247
        }
248
249
        return $this;
250
    }
251
252
    /**
253
     * @param $organization
254
     * @return Organization
255
     */
256
    protected function resolveOrganization($organization)
257
    {
258
        if ($organization instanceof Organization) {
259
            return $organization;
260
        }
261
262
        if (is_array($organization) &&
263
            null !== ($id = ArrayHelper::getValue($organization, 'id'))
264
        ) {
265
            return Organization::findOne($id);
266
        }
267
268
        if (null !== ($object = Organization::findOne($organization))) {
269
            return $object;
270
        }
271
272
        return new Organization($organization);
273
    }
274
275
    /*******************************************
276
     * ASSOCIATE and/or DISASSOCIATE
277
     *******************************************/
278
279
    /**
280
     * @return bool
281
     * @throws \Throwable
282
     * @throws \yii\db\StaleObjectException
283
     */
284
    public function saveOrganizations(): bool
285
    {
286
        $currentAssociations = $this->currentAssociationQuery()->all();
287
288
        $success = true;
289
290
        if (null === ($records = $this->getOrganizations()->getCachedResult())) {
291
            // Delete anything that's currently set
292
            foreach ($currentAssociations as $currentAssociation) {
293
                if (!$currentAssociation->delete()) {
294
                    $success = false;
295
                }
296
            }
297
298
            if (!$success) {
299
                $this->owner->addError('types', 'Unable to dissociate organizations.');
300
            }
301
302
            return $success;
303
        }
304
305
        $associations = [];
306
        $order = 1;
307
        foreach ($records as $type) {
308
            if (null === ($association = ArrayHelper::remove($currentAssociations, $type->getId()))) {
309
                $association = (new UserAssociation())
310
                    ->setUser($this->owner)
311
                    ->setOrganization($type);
312
            }
313
314
            $association->organizationOrder = $order++;
315
316
            $associations[] = $association;
317
        }
318
319
        // Delete anything that has been removed
320
        foreach ($currentAssociations as $currentAssociation) {
321
            if (!$currentAssociation->delete()) {
322
                $success = false;
323
            }
324
        }
325
326
        // Save'em
327
        foreach ($associations as $association) {
328
            if (!$association->save()) {
329
                $success = false;
330
            }
331
        }
332
333
        if (!$success) {
334
            $this->owner->addError('organizations', 'Unable to save user organizations.');
335
        }
336
337
        return $success;
338
    }
339
340
    /**
341
     * @param OrganizationQuery $query
342
     * @return bool
343
     * @throws \Throwable
344
     */
345
    public function associateOrganizations(OrganizationQuery $query): bool
346
    {
347
        $organizations = $query->all();
348
349
        if (empty($organizations)) {
350
            return true;
351
        }
352
353
        $currentAssociations = $this->currentAssociationQuery()->all();
354
355
        $success = true;
356
        foreach ($organizations as $organization) {
357
            if (null === ($association = ArrayHelper::remove($currentAssociations, $organization->getId()))) {
358
                $association = (new UserAssociation())
359
                    ->setUser($this->owner)
360
                    ->setOrganization($organization);
361
            }
362
363
            if (!$association->save()) {
364
                $success = false;
365
            }
366
        }
367
368
        if (!$success) {
369
            $this->owner->addError('organizations', 'Unable to associate organizations.');
370
        }
371
372
        $this->resetOrganizations();
373
374
        return $success;
375
    }
376
377
    /**
378
     * @param OrganizationQuery $query
379
     * @return bool
380
     * @throws \Throwable
381
     */
382
    public function dissociateOrganizations(OrganizationQuery $query): bool
383
    {
384
        $organizations = $query->all();
385
386
        if (empty($organizations)) {
387
            return true;
388
        }
389
390
        $currentAssociations = $this->currentAssociationQuery()->all();
391
392
        $success = true;
393
        foreach ($organizations as $organization) {
394
            if (null === ($association = ArrayHelper::remove($currentAssociations, $organization->getId()))) {
395
                continue;
396
            }
397
398
            if (!$association->delete()) {
399
                $success = false;
400
            }
401
        }
402
403
        if (!$success) {
404
            $this->owner->addError('organizations', 'Unable to associate organizations.');
405
        }
406
407
        $this->resetOrganizations();
408
409
        return $success;
410
    }
411
412
    /**
413
     * @return UserAssociationQuery
414
     */
415
    protected function currentAssociationQuery(): UserAssociationQuery
416
    {
417
        return UserAssociation::find()
418
            ->userId($this->owner->getId() ?: false)
419
            ->indexBy('organizationId')
420
            ->orderBy(['organizationOrder' => SORT_ASC]);
421
    }
422
423
    /**
424
     * @return User
425
     */
426
    public function resetOrganizations()
427
    {
428
        $this->organizations = null;
429
        return $this->owner;
430
    }
431
}
432