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\db\ElementQueryInterface; |
14
|
|
|
use craft\elements\db\UserQuery; |
15
|
|
|
use craft\elements\User as UserElement; |
16
|
|
|
use craft\helpers\DateTimeHelper; |
17
|
|
|
use craft\models\FieldLayout as FieldLayoutModel; |
18
|
|
|
use craft\records\Element as ElementRecord; |
19
|
|
|
use flipbox\organization\elements\db\Organization as OrganizationQuery; |
20
|
|
|
use flipbox\organization\elements\Organization as OrganizationElement; |
21
|
|
|
use flipbox\organization\events\ChangeOwner as ChangeOwnerEvent; |
22
|
|
|
use flipbox\organization\events\ChangeStatus as ChangeStatusEvent; |
23
|
|
|
use flipbox\organization\Organization as OrganizationPlugin; |
24
|
|
|
use flipbox\organization\records\Organization as OrganizationRecord; |
25
|
|
|
use flipbox\spark\helpers\ArrayHelper; |
26
|
|
|
use flipbox\spark\helpers\RecordHelper; |
27
|
|
|
use flipbox\spark\helpers\SiteHelper; |
28
|
|
|
use flipbox\spark\services\Element as ElementService; |
29
|
|
|
use flipbox\spark\services\traits\Element; |
30
|
|
|
use yii\base\Exception; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @author Flipbox Factory <[email protected]> |
34
|
|
|
* @since 1.0.0 |
35
|
|
|
* |
36
|
|
|
* @method OrganizationElement|null find($identifier, int $siteId = null) |
37
|
|
|
* @method OrganizationElement get($identifier, int $siteId = null) |
38
|
|
|
* @method OrganizationElement|null findById(int $id, int $siteId = null) |
39
|
|
|
* @method OrganizationElement getById(int $id, int $siteId = null) |
40
|
|
|
*/ |
41
|
|
|
class Organization extends ElementService |
42
|
|
|
{ |
43
|
|
|
|
44
|
|
|
use Element; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @inheritdoc |
48
|
|
|
*/ |
49
|
|
|
public static function elementClass(): string |
50
|
|
|
{ |
51
|
|
|
return OrganizationElement::class; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @inheritdoc |
56
|
|
|
*/ |
57
|
|
|
public static function recordClass(): string |
58
|
|
|
{ |
59
|
|
|
return OrganizationRecord::class; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @event ChangeStatusEvent The event that is triggered before a organization has a custom status change. |
64
|
|
|
* |
65
|
|
|
* You may set [[ChangeStatusEvent::isValid]] to `false` to prevent the organization changing the status. |
66
|
|
|
*/ |
67
|
|
|
const EVENT_BEFORE_STATUS_CHANGE = 'beforeStatusChange'; |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @event ChangeStatusEvent The event that is triggered after a organization has a custom status change. |
71
|
|
|
* |
72
|
|
|
* You may set [[ChangeStatusEvent::isValid]] to `false` to prevent the organization changing the status. |
73
|
|
|
*/ |
74
|
|
|
const EVENT_AFTER_STATUS_CHANGE = 'afterStatusChange'; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @event ChangeOwnerEvent The event that is triggered before an organization ownership is transferred. |
78
|
|
|
* |
79
|
|
|
* You may set [[ChangeOwnerEvent::isValid]] to `false` to prevent the organization ownership |
80
|
|
|
* from getting transferred. |
81
|
|
|
*/ |
82
|
|
|
const EVENT_BEFORE_TRANSFER_OWNERSHIP = 'beforeOwnerChange'; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @event ChangeOwnerEvent The event that is triggered after an organization ownership is transferred. |
86
|
|
|
* |
87
|
|
|
* You may set [[ChangeOwnerEvent::isValid]] to `false` to prevent the organization ownership |
88
|
|
|
* from getting transferred. |
89
|
|
|
*/ |
90
|
|
|
const EVENT_AFTER_TRANSFER_OWNERSHIP = 'afterOwnerChange'; |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* @var array |
94
|
|
|
*/ |
95
|
|
|
private $statusesByOrganization = []; |
96
|
|
|
|
97
|
|
|
|
98
|
|
|
/******************************************* |
99
|
|
|
* STATUS |
100
|
|
|
*******************************************/ |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @return array |
104
|
|
|
*/ |
105
|
|
|
public function getStatuses(): array |
106
|
|
|
{ |
107
|
|
|
return array_merge( |
108
|
|
|
[ |
109
|
|
|
OrganizationElement::STATUS_ENABLED => Craft::t('organization', 'Active') |
110
|
|
|
], |
111
|
|
|
OrganizationPlugin::getInstance()->getSettings()->getStatuses(), |
112
|
|
|
[ |
113
|
|
|
OrganizationElement::STATUS_DISABLED => Craft::t('organization', 'Disabled') |
114
|
|
|
] |
115
|
|
|
); |
116
|
|
|
} |
117
|
|
|
/** |
118
|
|
|
* @param $status |
119
|
|
|
* @return bool |
120
|
|
|
*/ |
121
|
|
|
public function isCustomStatus($status) |
122
|
|
|
{ |
123
|
|
|
|
124
|
|
|
if (!is_string($status) || empty($status)) { |
125
|
|
|
return false; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
return array_key_exists($status, OrganizationPlugin::getInstance()->getSettings()->getStatuses()); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
|
132
|
|
|
/******************************************* |
133
|
|
|
* SAVE |
134
|
|
|
*******************************************/ |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* @param OrganizationElement $organization |
138
|
|
|
* @param bool $isNew |
139
|
|
|
* @throws Exception |
140
|
|
|
* @throws \Exception |
141
|
|
|
*/ |
142
|
|
|
public function beforeSave(OrganizationElement $organization, bool $isNew) |
143
|
|
|
{ |
144
|
|
|
|
145
|
|
|
// Join Date |
146
|
|
|
if (empty($organization->dateJoined)) { |
147
|
|
|
$organization->dateJoined = DateTimeHelper::currentUTCDateTime(); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
if (!$isNew) { |
151
|
|
|
|
152
|
|
|
/** @var OrganizationRecord $recordClass */ |
153
|
|
|
$recordClass = static::recordClass(); |
154
|
|
|
|
155
|
|
|
$query = (new Query()) |
156
|
|
|
->select(['enabled', 'archived', 'status']) |
157
|
|
|
->from([$recordClass::tableName() . ' ' . $recordClass::tableAlias()]) |
158
|
|
|
->innerJoin( |
159
|
|
|
ElementRecord::tableName() . ' elements', |
160
|
|
|
'elements.id = ' . $recordClass::tableAlias() . '.id' |
161
|
|
|
) |
162
|
|
|
->andWhere( |
163
|
|
|
[ |
164
|
|
|
'elements.id' => $organization->getId() |
165
|
|
|
] |
166
|
|
|
)->one(); |
167
|
|
|
|
168
|
|
|
$currentStatus = $organization->getStatus(); |
169
|
|
|
$existingStatus = $query['status']; |
170
|
|
|
|
171
|
|
|
// Quick logic to determine the status |
172
|
|
|
if (empty($existingStatus)) { |
173
|
|
|
$existingStatus = (!empty($query['archived']) ? |
174
|
|
|
OrganizationElement::STATUS_ARCHIVED : |
175
|
|
|
($query['enabled'] ? |
176
|
|
|
OrganizationElement::STATUS_ENABLED : |
177
|
|
|
OrganizationElement::STATUS_DISABLED |
178
|
|
|
) |
179
|
|
|
); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
// If they don't match, store it and set the original. |
183
|
|
|
// We'll handle changing the status on the after event. |
184
|
|
|
if ($currentStatus !== $existingStatus) { |
185
|
|
|
$this->statusesByOrganization[$organization->getId()] = $currentStatus; |
186
|
|
|
$organization->setStatus($existingStatus); |
187
|
|
|
} |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* @param OrganizationElement $organization |
193
|
|
|
* @param bool $isNew |
194
|
|
|
* @throws Exception |
195
|
|
|
* @throws \Exception |
196
|
|
|
*/ |
197
|
|
|
public function afterSave(OrganizationElement $organization, bool $isNew) |
198
|
|
|
{ |
199
|
|
|
|
200
|
|
|
// Get the category record |
201
|
|
|
if (!$isNew) { |
202
|
|
|
$record = OrganizationRecord::findOne($organization->id); |
203
|
|
|
|
204
|
|
|
if (!$record) { |
205
|
|
|
throw new Exception('Invalid organization Id: ' . $organization->id); |
206
|
|
|
} |
207
|
|
|
} else { |
208
|
|
|
$record = new OrganizationRecord(); |
209
|
|
|
$record->id = $organization->id; |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
$record->dateJoined = $organization->dateJoined; |
213
|
|
|
|
214
|
|
|
if ($isNew) { |
215
|
|
|
// Transfer element attribute(s) to record |
216
|
|
|
$record->status = $organization->getStatus(); |
217
|
|
|
|
218
|
|
|
if (!$this->isCustomStatus( |
219
|
|
|
$record->status |
220
|
|
|
) |
221
|
|
|
) { |
222
|
|
|
$record->status = null; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
$record->ownerId = $organization->ownerId; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
// Save the record |
229
|
|
|
if (!$record->save()) { |
230
|
|
|
$organization->addErrors($record->getErrors()); |
231
|
|
|
|
232
|
|
|
Craft::error( |
233
|
|
|
$organization->getErrors(), |
234
|
|
|
__CLASS__ |
235
|
|
|
); |
236
|
|
|
|
237
|
|
|
throw new Exception('Unable to save record'); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
// Transfer id to the new records |
241
|
|
|
if ($isNew) { |
242
|
|
|
$organization->id = $record->id; |
243
|
|
|
$organization->dateCreated = DateTimeHelper::toDateTime($record->dateCreated); |
|
|
|
|
244
|
|
|
} |
245
|
|
|
$organization->dateUpdated = DateTimeHelper::toDateTime($record->dateUpdated); |
|
|
|
|
246
|
|
|
|
247
|
|
|
if (!$isNew) { |
248
|
|
|
// Change status |
249
|
|
|
$status = $organization->getStatus(); |
250
|
|
|
|
251
|
|
|
$toStatus = ArrayHelper::remove( |
252
|
|
|
$this->statusesByOrganization, |
253
|
|
|
$organization->getId(), |
254
|
|
|
$status |
255
|
|
|
); |
256
|
|
|
|
257
|
|
|
if ($status !== $toStatus) { |
258
|
|
|
// Change status |
259
|
|
|
if (!$this->changeStatus($organization, $toStatus)) { |
260
|
|
|
// Add error |
261
|
|
|
$organization->addError( |
262
|
|
|
'status', |
263
|
|
|
Craft::t('organization', 'Unable to change status.') |
264
|
|
|
); |
265
|
|
|
|
266
|
|
|
throw new Exception("Unable to change status."); |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
// The owner we're changing to |
271
|
|
|
$toOwner = $organization->ownerId; |
272
|
|
|
if ($record->ownerId !== $toOwner) { |
273
|
|
|
// Revert element to old owner |
274
|
|
|
$organization->ownerId = $record->ownerId; |
275
|
|
|
|
276
|
|
|
// Change owner |
277
|
|
|
if (!$this->transferOwner($organization, $toOwner)) { |
278
|
|
|
// Add error |
279
|
|
|
$organization->addError( |
280
|
|
|
'ownerId', |
281
|
|
|
Craft::t('organization', 'Unable to change owner.') |
282
|
|
|
); |
283
|
|
|
|
284
|
|
|
throw new Exception("Unable to change owner."); |
285
|
|
|
} |
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
// Save organization types |
290
|
|
|
if (!$this->associateTypes($organization)) { |
291
|
|
|
// Add error |
292
|
|
|
$organization->addError( |
293
|
|
|
'types', |
294
|
|
|
Craft::t('organization', 'Unable to save types.') |
295
|
|
|
); |
296
|
|
|
|
297
|
|
|
throw new Exception("Unable to save types."); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
// Save organization users |
301
|
|
|
if (!$this->associateUsers($organization)) { |
302
|
|
|
// Add error |
303
|
|
|
$organization->addError( |
304
|
|
|
'users', |
305
|
|
|
Craft::t('organization', 'Unable to save users.') |
306
|
|
|
); |
307
|
|
|
|
308
|
|
|
throw new Exception("Unable to save users."); |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
|
313
|
|
|
/******************************************* |
314
|
|
|
* USER QUERY |
315
|
|
|
*******************************************/ |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* @param OrganizationElement $organization |
319
|
|
|
* @param array $criteria |
320
|
|
|
* @param bool $match |
321
|
|
|
* @return UserQuery |
322
|
|
|
*/ |
323
|
|
|
public function getMemberQuery(OrganizationElement $organization, $criteria = [], bool $match = true) |
324
|
|
|
{ |
325
|
|
|
return OrganizationPlugin::getInstance()->getUser()->getMemberQuery( |
326
|
|
|
($match ? '' : 'not ') . |
327
|
|
|
$organization->id ?: 'x', |
328
|
|
|
$criteria |
329
|
|
|
); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* @param OrganizationElement $organization |
334
|
|
|
* @param array $criteria |
335
|
|
|
* @param bool $match |
336
|
|
|
* @return UserQuery |
337
|
|
|
*/ |
338
|
|
|
public function getUserQuery(OrganizationElement $organization, $criteria = [], bool $match = true) |
339
|
|
|
{ |
340
|
|
|
return OrganizationPlugin::getInstance()->getUser()->getUserQuery( |
341
|
|
|
($match ? '' : 'not ') . |
342
|
|
|
$organization->id ?: 'x', |
343
|
|
|
$criteria |
344
|
|
|
); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* @param OrganizationElement $organization |
349
|
|
|
* @param array $criteria |
350
|
|
|
* @param bool $match |
351
|
|
|
* @return UserQuery |
352
|
|
|
*/ |
353
|
|
|
public function getOwnerQuery(OrganizationElement $organization, $criteria = [], bool $match = true) |
354
|
|
|
{ |
355
|
|
|
return OrganizationPlugin::getInstance()->getUser()->getOwnerQuery( |
356
|
|
|
($match ? '' : 'not ') . |
357
|
|
|
$organization->id ?: 'x', |
358
|
|
|
$criteria |
359
|
|
|
); |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
|
363
|
|
|
/******************************************* |
364
|
|
|
* UTILITY |
365
|
|
|
*******************************************/ |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* @param UserElement $user |
369
|
|
|
* @param OrganizationElement $organization |
370
|
|
|
* @param array $criteria |
371
|
|
|
* @param bool $match |
372
|
|
|
* @return bool |
373
|
|
|
*/ |
374
|
|
|
public function isUser(UserElement $user, OrganizationElement $organization, $criteria = [], bool $match = true) |
375
|
|
|
{ |
376
|
|
|
|
377
|
|
|
// User query |
378
|
|
|
$emails = $this->getUserQuery($organization, $criteria, $match) |
379
|
|
|
->select(['users.email']) |
380
|
|
|
->column(); |
381
|
|
|
|
382
|
|
|
return in_array($user->email, $emails); |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* @param UserElement $user |
387
|
|
|
* @param OrganizationElement $organization |
388
|
|
|
* @param array $criteria |
389
|
|
|
* @param bool $match |
390
|
|
|
* @return bool |
391
|
|
|
*/ |
392
|
|
|
public function isOwner(UserElement $user, OrganizationElement $organization, $criteria = [], bool $match = true) |
393
|
|
|
{ |
394
|
|
|
|
395
|
|
|
// User query |
396
|
|
|
$emails = $this->getOwnerQuery($organization, $criteria, $match) |
397
|
|
|
->select(['users.email']) |
398
|
|
|
->column(); |
399
|
|
|
|
400
|
|
|
return in_array($user->email, $emails); |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* @param UserElement $user |
405
|
|
|
* @param OrganizationElement $organization |
406
|
|
|
* @param array $criteria |
407
|
|
|
* @param bool $match |
408
|
|
|
* @return bool |
409
|
|
|
*/ |
410
|
|
|
public function isMember(UserElement $user, OrganizationElement $organization, $criteria = [], bool $match = true) |
411
|
|
|
{ |
412
|
|
|
|
413
|
|
|
// User query |
414
|
|
|
$emails = $this->getMemberQuery($organization, $criteria, $match) |
415
|
|
|
->select(['users.email']) |
416
|
|
|
->column(); |
417
|
|
|
|
418
|
|
|
return in_array($user->email, $emails); |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
|
422
|
|
|
/******************************************* |
423
|
|
|
* QUERY |
424
|
|
|
*******************************************/ |
425
|
|
|
|
426
|
|
|
/** |
427
|
|
|
* @inheritdoc |
428
|
|
|
* @return ElementQueryInterface|OrganizationQuery |
429
|
|
|
*/ |
430
|
|
|
public function getQuery($criteria = []) |
431
|
|
|
{ |
432
|
|
|
return parent::getQuery($criteria); |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
|
436
|
|
|
/******************************************* |
437
|
|
|
* POPULATE (from Request) |
438
|
|
|
*******************************************/ |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* @param OrganizationElement $organization |
442
|
|
|
*/ |
443
|
|
|
public function populateFromRequest(OrganizationElement $organization) |
444
|
|
|
{ |
445
|
|
|
|
446
|
|
|
$request = Craft::$app->getRequest(); |
447
|
|
|
|
448
|
|
|
// Set the entry attributes, defaulting to the existing values for whatever is missing from the post data |
449
|
|
|
$organization->slug = $request->getBodyParam('slug', $organization->slug); |
450
|
|
|
|
451
|
|
|
// Enabled |
452
|
|
|
$organization->enabledForSite = (bool)$request->getBodyParam( |
453
|
|
|
'enabledForSite', |
454
|
|
|
$organization->enabledForSite |
455
|
|
|
); |
456
|
|
|
$organization->title = $request->getBodyParam('title', $organization->title); |
457
|
|
|
|
458
|
|
|
// Status |
459
|
|
|
$organization->setStatus( |
460
|
|
|
$request->getBodyParam('status', $organization->getStatus()) |
461
|
|
|
); |
462
|
|
|
|
463
|
|
|
// Join date |
464
|
|
|
$this->populateDateFromRequest($organization, 'dateJoined'); |
465
|
|
|
|
466
|
|
|
// Active type |
467
|
|
|
$this->populateActiveTypeFromRequest($organization); |
468
|
|
|
|
469
|
|
|
// Owner |
470
|
|
|
$this->populateOwnerFromRequest($organization); |
471
|
|
|
|
472
|
|
|
// Set types |
473
|
|
|
$organization->setTypesFromRequest( |
474
|
|
|
$request->getParam('typesLocation', 'types') |
475
|
|
|
); |
476
|
|
|
|
477
|
|
|
// Set users |
478
|
|
|
$organization->setUsersFromRequest( |
479
|
|
|
$request->getParam('usersLocation', 'users') |
480
|
|
|
); |
481
|
|
|
|
482
|
|
|
// Set content |
483
|
|
|
$organization->setFieldValuesFromRequest( |
484
|
|
|
$request->getParam('fieldsLocation', 'fields') |
485
|
|
|
); |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
/** |
489
|
|
|
* @param OrganizationElement $organization |
490
|
|
|
* @param string $dateProperty |
491
|
|
|
*/ |
492
|
|
|
private function populateDateFromRequest(OrganizationElement $organization, string $dateProperty) |
493
|
|
|
{ |
494
|
|
|
$dateTime = DateTimeHelper::toDateTime( |
495
|
|
|
Craft::$app->getRequest()->getBodyParam($dateProperty, $organization->{$dateProperty}) |
496
|
|
|
); |
497
|
|
|
$organization->{$dateProperty} = $dateTime === false ? null : $dateTime; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* @param OrganizationElement $organization |
502
|
|
|
*/ |
503
|
|
|
private function populateActiveTypeFromRequest(OrganizationElement $organization) |
504
|
|
|
{ |
505
|
|
|
|
506
|
|
|
$type = null; |
507
|
|
|
if ($typeId = Craft::$app->getRequest()->getBodyParam('type', null)) { |
508
|
|
|
$type = OrganizationPlugin::getInstance()->getType()->get($typeId); |
509
|
|
|
} |
510
|
|
|
$organization->setActiveType($type); |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* @param OrganizationElement $organization |
515
|
|
|
*/ |
516
|
|
|
private function populateOwnerFromRequest(OrganizationElement $organization) |
517
|
|
|
{ |
518
|
|
|
|
519
|
|
|
$ownerId = Craft::$app->getRequest()->getBodyParam( |
520
|
|
|
'owner', |
521
|
|
|
$organization->ownerId |
522
|
|
|
); |
523
|
|
|
if (is_array($ownerId)) { |
524
|
|
|
$ownerId = ArrayHelper::firstValue($ownerId); |
525
|
|
|
} |
526
|
|
|
$organization->ownerId = $ownerId; |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
|
530
|
|
|
/******************************************* |
531
|
|
|
* TYPES - ASSOCIATE and/or DISASSOCIATE |
532
|
|
|
*******************************************/ |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* @param OrganizationElement $organizationElement |
536
|
|
|
* @return bool |
537
|
|
|
* @throws \Exception |
538
|
|
|
*/ |
539
|
|
|
protected function associateTypes(OrganizationElement $organizationElement) |
540
|
|
|
{ |
541
|
|
|
|
542
|
|
|
// Db transaction |
543
|
|
|
$transaction = RecordHelper::beginTransaction(); |
544
|
|
|
|
545
|
|
|
try { |
546
|
|
|
// Primary type (previously saved?) |
547
|
|
|
$primaryType = $organizationElement->getPrimaryType(); |
548
|
|
|
|
549
|
|
|
// All types |
550
|
|
|
$currentTypes = $organizationElement->getTypes(); |
551
|
|
|
|
552
|
|
|
// Existing types |
553
|
|
|
$existingTypes = ArrayHelper::index( |
554
|
|
|
OrganizationPlugin::getInstance()->getType()->findAllByOrganization($organizationElement), |
555
|
|
|
'id' |
556
|
|
|
); |
557
|
|
|
|
558
|
|
|
// Verify primary type is still valid |
559
|
|
|
if ($primaryType) { |
560
|
|
|
if (!array_key_exists($primaryType->id, $currentTypes)) { |
561
|
|
|
$primaryType = null; |
562
|
|
|
} |
563
|
|
|
} |
564
|
|
|
|
565
|
|
|
$count = 0; |
566
|
|
|
foreach ($currentTypes as $currentType) { |
567
|
|
|
if (null !== ArrayHelper::remove($existingTypes, $currentType->id)) { |
568
|
|
|
continue; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
// If primary isn't already set, and it's the first one |
572
|
|
|
$isPrimary = (0 === $count++ && empty($primaryType)); |
573
|
|
|
|
574
|
|
|
// Associate |
575
|
|
|
if (!OrganizationPlugin::getInstance()->getType()->associate( |
576
|
|
|
$currentType, |
577
|
|
|
$organizationElement, |
578
|
|
|
$isPrimary |
579
|
|
|
) |
580
|
|
|
) { |
581
|
|
|
// Roll back on failures |
582
|
|
|
$transaction->rollBack(); |
583
|
|
|
|
584
|
|
|
return false; |
585
|
|
|
} |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
foreach ($existingTypes as $currentType) { |
589
|
|
|
// Dissociate |
590
|
|
|
if (!OrganizationPlugin::getInstance()->getType()->dissociate( |
591
|
|
|
$currentType, |
592
|
|
|
$organizationElement |
593
|
|
|
) |
594
|
|
|
) { |
595
|
|
|
// Roll back on failures |
596
|
|
|
$transaction->rollBack(); |
597
|
|
|
|
598
|
|
|
return false; |
599
|
|
|
} |
600
|
|
|
} |
601
|
|
|
} catch (\Exception $e) { |
602
|
|
|
// Roll back on failures |
603
|
|
|
$transaction->rollBack(); |
604
|
|
|
|
605
|
|
|
throw $e; |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
// commit db actions (success) |
609
|
|
|
$transaction->commit(); |
610
|
|
|
|
611
|
|
|
return true; |
612
|
|
|
} |
613
|
|
|
|
614
|
|
|
/******************************************* |
615
|
|
|
* USERS - ASSOCIATE and/or DISASSOCIATE |
616
|
|
|
*******************************************/ |
617
|
|
|
|
618
|
|
|
/** |
619
|
|
|
* @param OrganizationElement $organizationElement |
620
|
|
|
* @param int|null $siteId |
621
|
|
|
* @return bool |
622
|
|
|
* @throws \Exception |
623
|
|
|
*/ |
624
|
|
|
protected function associateUsers(OrganizationElement $organizationElement, int $siteId = null) |
625
|
|
|
{ |
626
|
|
|
|
627
|
|
|
/** @var UserQuery $query */ |
628
|
|
|
$query = $organizationElement->getUsers(); |
629
|
|
|
|
630
|
|
|
// Only perform save if we have cached result (meaning it was may have changed) |
631
|
|
|
if ($query->getCachedResult() === null) { |
632
|
|
|
return true; |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
// Db transaction |
636
|
|
|
$transaction = Craft::$app->getDb()->beginTransaction(); |
637
|
|
|
|
638
|
|
|
try { |
639
|
|
|
// Find all currently associated and index by userId |
640
|
|
|
$existingUsers = $this->getUserQuery( |
641
|
|
|
$organizationElement, |
642
|
|
|
[ |
643
|
|
|
'status' => null, |
644
|
|
|
'indexBy' => 'id' |
645
|
|
|
] |
646
|
|
|
)->all(); |
647
|
|
|
|
648
|
|
|
// Get array of associated users (index by email -> so we don't have dupes) |
649
|
|
|
$currentUsers = ArrayHelper::index( |
650
|
|
|
$query->getCachedResult(), |
651
|
|
|
'email' |
652
|
|
|
); |
653
|
|
|
|
654
|
|
|
// Exclude owner |
655
|
|
|
if ($organizationElement->hasOwner()) { |
656
|
|
|
ArrayHelper::remove($currentUsers, $organizationElement->getOwner()->email); |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
/** |
660
|
|
|
* @var string $key |
661
|
|
|
* @var UserElement $currentUser |
662
|
|
|
*/ |
663
|
|
|
foreach ($currentUsers as $key => $currentUser) { |
664
|
|
|
if (!$currentUser->getId() && |
665
|
|
|
!Craft::$app->getElements()->saveElement($currentUser) |
666
|
|
|
) { |
667
|
|
|
// Roll back on failures |
668
|
|
|
$transaction->rollBack(); |
669
|
|
|
|
670
|
|
|
return false; |
671
|
|
|
} |
672
|
|
|
|
673
|
|
|
// Only associate if they are new |
674
|
|
|
if (null !== ArrayHelper::remove($existingUsers, $currentUser->getId())) { |
675
|
|
|
continue; |
676
|
|
|
} |
677
|
|
|
|
678
|
|
|
// Otherwise, associate |
679
|
|
|
if (!OrganizationPlugin::getInstance()->getUser()->associate( |
680
|
|
|
$currentUser, |
681
|
|
|
$organizationElement, |
682
|
|
|
$siteId |
683
|
|
|
) |
684
|
|
|
) { |
685
|
|
|
// Roll back on failures |
686
|
|
|
$transaction->rollBack(); |
687
|
|
|
|
688
|
|
|
return false; |
689
|
|
|
} |
690
|
|
|
} |
691
|
|
|
|
692
|
|
|
foreach ($existingUsers as $key => $existingUser) { |
693
|
|
|
// Dissociate |
694
|
|
|
if (!OrganizationPlugin::getInstance()->getUser()->dissociate( |
695
|
|
|
$existingUser, |
696
|
|
|
$organizationElement |
697
|
|
|
) |
698
|
|
|
) { |
699
|
|
|
// Roll back on failures |
700
|
|
|
$transaction->rollBack(); |
701
|
|
|
|
702
|
|
|
return false; |
703
|
|
|
} |
704
|
|
|
} |
705
|
|
|
} catch (\Exception $e) { |
706
|
|
|
// Roll back on failures |
707
|
|
|
$transaction->rollBack(); |
708
|
|
|
|
709
|
|
|
throw $e; |
710
|
|
|
} |
711
|
|
|
|
712
|
|
|
// commit db actions (success) |
713
|
|
|
$transaction->commit(); |
714
|
|
|
|
715
|
|
|
return true; |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
|
719
|
|
|
/******************************************* |
720
|
|
|
* STATUS |
721
|
|
|
*******************************************/ |
722
|
|
|
|
723
|
|
|
/** |
724
|
|
|
* @param OrganizationElement $element |
725
|
|
|
* @param $status |
726
|
|
|
* @return bool |
727
|
|
|
* @throws \Exception |
728
|
|
|
* @throws \yii\db\Exception |
729
|
|
|
*/ |
730
|
|
|
public function changeStatus( |
731
|
|
|
OrganizationElement $element, |
732
|
|
|
$status |
733
|
|
|
) { |
734
|
|
|
|
735
|
|
|
|
736
|
|
|
// The before event |
737
|
|
|
$event = new ChangeStatusEvent([ |
738
|
|
|
'organization' => $element, |
739
|
|
|
'fromStatus' => $element->getStatus(), |
740
|
|
|
'toStatus' => $status |
741
|
|
|
]); |
742
|
|
|
|
743
|
|
|
// Trigger event |
744
|
|
|
$this->trigger( |
745
|
|
|
static::EVENT_BEFORE_STATUS_CHANGE, |
746
|
|
|
$event |
747
|
|
|
); |
748
|
|
|
|
749
|
|
|
// Green light? |
750
|
|
|
if (!$event->isValid) { |
751
|
|
|
return false; |
752
|
|
|
} |
753
|
|
|
|
754
|
|
|
// Db transaction |
755
|
|
|
$transaction = RecordHelper::beginTransaction(); |
756
|
|
|
|
757
|
|
|
try { |
758
|
|
|
|
759
|
|
|
/** @var OrganizationRecord $record */ |
760
|
|
|
$record = $this->getRecordByCondition([ |
761
|
|
|
'id' => $element->id |
762
|
|
|
]); |
763
|
|
|
|
764
|
|
|
// Set status |
765
|
|
|
$record->status = $this->isCustomStatus($status) ? $status : null; |
766
|
|
|
|
767
|
|
|
// Validate record (status only) |
768
|
|
|
if (!$record->validate('status')) { |
|
|
|
|
769
|
|
|
// Transfer errors |
770
|
|
|
$element->addErrors($record->getErrors()); |
771
|
|
|
|
772
|
|
|
// Roll back on failures |
773
|
|
|
$transaction->rollBack(); |
774
|
|
|
|
775
|
|
|
return false; |
776
|
|
|
} |
777
|
|
|
|
778
|
|
|
// Organization status |
779
|
|
|
Craft::$app->getDb()->createCommand()->update( |
780
|
|
|
$record::tableName(), |
781
|
|
|
['status' => $record->status], |
782
|
|
|
['id' => $element->id] |
783
|
|
|
)->execute(); |
784
|
|
|
|
785
|
|
|
// Element status |
786
|
|
|
switch ($status) { |
787
|
|
|
case OrganizationElement::STATUS_ARCHIVED: |
788
|
|
|
$condition = [ |
789
|
|
|
'enabled' => 0, |
790
|
|
|
'archived' => 1, |
791
|
|
|
]; |
792
|
|
|
break; |
793
|
|
|
|
794
|
|
|
case OrganizationElement::STATUS_DISABLED: |
795
|
|
|
$condition = [ |
796
|
|
|
'enabled' => 0, |
797
|
|
|
'archived' => 0, |
798
|
|
|
]; |
799
|
|
|
break; |
800
|
|
|
|
801
|
|
|
default: |
802
|
|
|
$condition = [ |
803
|
|
|
'enabled' => 1, |
804
|
|
|
'archived' => 0, |
805
|
|
|
]; |
806
|
|
|
break; |
807
|
|
|
} |
808
|
|
|
|
809
|
|
|
Craft::$app->getDb()->createCommand()->update( |
810
|
|
|
ElementRecord::tableName(), |
811
|
|
|
$condition, |
812
|
|
|
['id' => $element->id] |
813
|
|
|
)->execute(); |
814
|
|
|
|
815
|
|
|
// Transfer record attribute(s) to element |
816
|
|
|
$element->setStatus($status); |
817
|
|
|
|
818
|
|
|
// Trigger event |
819
|
|
|
$this->trigger( |
820
|
|
|
static::EVENT_AFTER_STATUS_CHANGE, |
821
|
|
|
$event |
822
|
|
|
); |
823
|
|
|
|
824
|
|
|
// Green light? |
825
|
|
|
if (!$event->isValid) { |
826
|
|
|
// Roll back on failures |
827
|
|
|
$transaction->rollBack(); |
828
|
|
|
|
829
|
|
|
return false; |
830
|
|
|
} |
831
|
|
|
} catch (\Exception $e) { |
832
|
|
|
// Roll back on failures |
833
|
|
|
$transaction->rollBack(); |
834
|
|
|
|
835
|
|
|
throw $e; |
836
|
|
|
} |
837
|
|
|
|
838
|
|
|
// Commit db transaction |
839
|
|
|
$transaction->commit(); |
840
|
|
|
|
841
|
|
|
return true; |
842
|
|
|
} |
843
|
|
|
|
844
|
|
|
/******************************************* |
845
|
|
|
* OWNER |
846
|
|
|
*******************************************/ |
847
|
|
|
|
848
|
|
|
/** |
849
|
|
|
* @param OrganizationElement $element |
850
|
|
|
* @param $newOwnerId |
851
|
|
|
* @return bool |
852
|
|
|
* @throws \Exception |
853
|
|
|
* @throws \yii\db\Exception |
854
|
|
|
*/ |
855
|
|
|
public function transferOwner( |
856
|
|
|
OrganizationElement $element, |
857
|
|
|
$newOwnerId |
858
|
|
|
) { |
859
|
|
|
|
860
|
|
|
|
861
|
|
|
// The event |
862
|
|
|
$event = new ChangeOwnerEvent([ |
863
|
|
|
'organization' => $element, |
864
|
|
|
'fromOwner' => $element->getOwner(), |
865
|
|
|
'toOwner' => $newOwnerId |
866
|
|
|
]); |
867
|
|
|
|
868
|
|
|
// Trigger event |
869
|
|
|
$this->trigger( |
870
|
|
|
static::EVENT_BEFORE_TRANSFER_OWNERSHIP, |
871
|
|
|
$event |
872
|
|
|
); |
873
|
|
|
|
874
|
|
|
// Green light? |
875
|
|
|
if (!$event->isValid) { |
876
|
|
|
return false; |
877
|
|
|
} |
878
|
|
|
|
879
|
|
|
// Db transaction |
880
|
|
|
$transaction = Craft::$app->getDb()->beginTransaction(); |
881
|
|
|
|
882
|
|
|
try { |
883
|
|
|
// Get record (or throw an Exception) |
884
|
|
|
/** @var OrganizationRecord $record */ |
885
|
|
|
$record = $this->getRecordByCondition([ |
886
|
|
|
'id' => $element->id |
887
|
|
|
]); |
888
|
|
|
|
889
|
|
|
// Set owner |
890
|
|
|
$record->ownerId = $newOwnerId; |
891
|
|
|
|
892
|
|
|
// Validate record (status only) |
893
|
|
|
if (!$record->save(true, ['ownerId'])) { |
894
|
|
|
// Transfer errors |
895
|
|
|
$element->addErrors($record->getErrors()); |
896
|
|
|
|
897
|
|
|
// Roll back on failures |
898
|
|
|
$transaction->rollBack(); |
899
|
|
|
|
900
|
|
|
return false; |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
// Transfer record attribute(s) to element |
904
|
|
|
$element->ownerId = $record->ownerId; |
905
|
|
|
|
906
|
|
|
// Trigger event |
907
|
|
|
$this->trigger( |
908
|
|
|
static::EVENT_AFTER_TRANSFER_OWNERSHIP, |
909
|
|
|
$event |
910
|
|
|
); |
911
|
|
|
|
912
|
|
|
// Green light? |
913
|
|
|
if (!$event->isValid) { |
914
|
|
|
// Roll back on failures |
915
|
|
|
$transaction->rollBack(); |
916
|
|
|
|
917
|
|
|
return false; |
918
|
|
|
} |
919
|
|
|
} catch (\Exception $e) { |
920
|
|
|
// Roll back on failures |
921
|
|
|
$transaction->rollBack(); |
922
|
|
|
|
923
|
|
|
throw $e; |
924
|
|
|
} |
925
|
|
|
|
926
|
|
|
// Commit db transaction |
927
|
|
|
$transaction->commit(); |
928
|
|
|
|
929
|
|
|
return true; |
930
|
|
|
} |
931
|
|
|
|
932
|
|
|
/******************************************* |
933
|
|
|
* UTILITIES |
934
|
|
|
*******************************************/ |
935
|
|
|
|
936
|
|
|
/** |
937
|
|
|
* @param int|null $siteId |
938
|
|
|
* @return FieldLayoutModel |
939
|
|
|
*/ |
940
|
|
|
public function getDefaultFieldLayout(int $siteId = null) |
941
|
|
|
{ |
942
|
|
|
return OrganizationPlugin::getInstance()->getSettings()->getSite( |
943
|
|
|
SiteHelper::resolveSiteId($siteId) |
944
|
|
|
)->getFieldLayout(); |
945
|
|
|
} |
946
|
|
|
} |
947
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.