1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @copyright Copyright (c) Flipbox Digital Limited |
5
|
|
|
* @license https://github.com/flipboxfactory/craft-integration/blob/master/LICENSE |
6
|
|
|
* @link https://github.com/flipboxfactory/craft-integration/ |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace flipbox\craft\integration\fields; |
10
|
|
|
|
11
|
|
|
use Craft; |
12
|
|
|
use craft\base\Element; |
13
|
|
|
use craft\base\ElementInterface; |
14
|
|
|
use craft\base\Field; |
15
|
|
|
use craft\elements\db\ElementQuery; |
16
|
|
|
use craft\elements\db\ElementQueryInterface; |
17
|
|
|
use craft\helpers\Component as ComponentHelper; |
18
|
|
|
use craft\helpers\Db; |
19
|
|
|
use craft\helpers\StringHelper; |
20
|
|
|
use flipbox\craft\ember\helpers\ModelHelper; |
21
|
|
|
use flipbox\craft\ember\records\ActiveRecord; |
22
|
|
|
use flipbox\craft\ember\validators\MinMaxValidator; |
23
|
|
|
use flipbox\craft\integration\events\RegisterIntegrationFieldActionsEvent; |
24
|
|
|
use flipbox\craft\integration\fields\actions\IntegrationActionInterface; |
25
|
|
|
use flipbox\craft\integration\fields\actions\IntegrationItemActionInterface; |
26
|
|
|
use flipbox\craft\integration\queries\IntegrationAssociationQuery; |
27
|
|
|
use flipbox\craft\integration\records\IntegrationAssociation; |
28
|
|
|
use flipbox\craft\integration\web\assets\integrations\Integrations as IntegrationsAsset; |
29
|
|
|
use yii\base\Exception; |
30
|
|
|
use yii\helpers\ArrayHelper; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @author Flipbox Factory <[email protected]> |
34
|
|
|
* @since 1.0.0 |
35
|
|
|
*/ |
36
|
|
|
abstract class Integrations extends Field |
37
|
|
|
{ |
38
|
|
|
/** |
39
|
|
|
* The Plugin's translation category |
40
|
|
|
*/ |
41
|
|
|
const TRANSLATION_CATEGORY = ''; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* The action path to preform field actions |
45
|
|
|
*/ |
46
|
|
|
const ACTION_PREFORM_ACTION_PATH = ''; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* The action path to preform field actions |
50
|
|
|
*/ |
51
|
|
|
const ACTION_PREFORM_ITEM_ACTION_PATH = ''; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* The action path to associate an item |
55
|
|
|
*/ |
56
|
|
|
const ACTION_ASSOCIATION_ITEM_PATH = ''; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* The action path to dissociate an item |
60
|
|
|
*/ |
61
|
|
|
const ACTION_DISSOCIATION_ITEM_PATH = ''; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* The action path to create an integration object |
65
|
|
|
*/ |
66
|
|
|
const ACTION_CREATE_ITEM_PATH = ''; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* The action event name |
70
|
|
|
*/ |
71
|
|
|
const EVENT_REGISTER_ACTIONS = 'registerActions'; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* The action event name |
75
|
|
|
*/ |
76
|
|
|
const EVENT_REGISTER_AVAILABLE_ACTIONS = 'registerAvailableActions'; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* The item action event name |
80
|
|
|
*/ |
81
|
|
|
const EVENT_REGISTER_ITEM_ACTIONS = 'registerItemActions'; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* The item action event name |
85
|
|
|
*/ |
86
|
|
|
const EVENT_REGISTER_AVAILABLE_ITEM_ACTIONS = 'registerAvailableItemActions'; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* The input template path |
90
|
|
|
*/ |
91
|
|
|
const INPUT_TEMPLATE_PATH = ''; |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* The input template path |
95
|
|
|
*/ |
96
|
|
|
const INPUT_ITEM_TEMPLATE_PATH = '_inputItem'; |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* The input template path |
100
|
|
|
*/ |
101
|
|
|
const SETTINGS_TEMPLATE_PATH = ''; |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* @var string |
105
|
|
|
*/ |
106
|
|
|
public $object; |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @var int|null |
110
|
|
|
*/ |
111
|
|
|
public $min; |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @var int|null |
115
|
|
|
*/ |
116
|
|
|
public $max; |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* @var string |
120
|
|
|
*/ |
121
|
|
|
public $viewUrl = ''; |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @var string |
125
|
|
|
*/ |
126
|
|
|
public $listUrl = ''; |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* @var IntegrationActionInterface[] |
130
|
|
|
*/ |
131
|
|
|
public $selectedActions = []; |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* @var IntegrationItemActionInterface[] |
135
|
|
|
*/ |
136
|
|
|
public $selectedItemActions = []; |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* @var string|null |
140
|
|
|
*/ |
141
|
|
|
public $selectionLabel; |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @inheritdoc |
145
|
|
|
*/ |
146
|
|
|
protected $defaultAvailableActions = []; |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* @inheritdoc |
150
|
|
|
*/ |
151
|
|
|
protected $defaultAvailableItemActions = []; |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* @return string |
155
|
|
|
*/ |
156
|
|
|
abstract public static function recordClass(): string; |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* @inheritdoc |
160
|
|
|
*/ |
161
|
|
|
public static function hasContentColumn(): bool |
162
|
|
|
{ |
163
|
|
|
return false; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* @return string |
168
|
|
|
*/ |
169
|
|
|
public static function defaultSelectionLabel(): string |
170
|
|
|
{ |
171
|
|
|
return Craft::t(static::TRANSLATION_CATEGORY, 'Add an Object'); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* @return string |
176
|
|
|
*/ |
177
|
|
|
protected static function tableAlias(): string |
178
|
|
|
{ |
179
|
|
|
/** @var ActiveRecord $recordClass */ |
180
|
|
|
$recordClass = static::recordClass(); |
181
|
|
|
return $recordClass::tableAlias(); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/******************************************* |
185
|
|
|
* OBJECT |
186
|
|
|
*******************************************/ |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* @return string |
190
|
|
|
*/ |
191
|
|
|
public function getObjectLabel(): string |
192
|
|
|
{ |
193
|
|
|
return StringHelper::titleize($this->object); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/******************************************* |
197
|
|
|
* VALIDATION |
198
|
|
|
*******************************************/ |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @inheritdoc |
202
|
|
|
*/ |
203
|
|
|
public function getElementValidationRules(): array |
204
|
|
|
{ |
205
|
|
|
return [ |
206
|
|
|
[ |
207
|
|
|
MinMaxValidator::class, |
208
|
|
|
'min' => $this->min ? (int)$this->min : null, |
209
|
|
|
'max' => $this->max ? (int)$this->max : null, |
210
|
|
|
'tooFew' => Craft::t( |
211
|
|
|
static::TRANSLATION_CATEGORY, |
212
|
|
|
'{attribute} should contain at least {min, number} {min, plural, one{domain} other{domains}}.' |
213
|
|
|
), |
214
|
|
|
'tooMany' => Craft::t( |
215
|
|
|
static::TRANSLATION_CATEGORY, |
216
|
|
|
'{attribute} should contain at most {max, number} {max, plural, one{domain} other{domains}}.' |
217
|
|
|
), |
218
|
|
|
'skipOnEmpty' => false |
219
|
|
|
] |
220
|
|
|
]; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/******************************************* |
224
|
|
|
* NORMALIZE VALUE |
225
|
|
|
*******************************************/ |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @param $value |
229
|
|
|
* @param ElementInterface|null $element |
230
|
|
|
* @return IntegrationAssociationQuery |
231
|
|
|
*/ |
232
|
|
|
public function normalizeValue( |
233
|
|
|
$value, |
234
|
|
|
ElementInterface $element = null |
235
|
|
|
) { |
236
|
|
|
|
237
|
|
|
if ($value instanceof IntegrationAssociationQuery) { |
238
|
|
|
return $value; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
$query = $this->getQuery($element); |
242
|
|
|
$this->normalizeQueryValue($query, $value, $element); |
243
|
|
|
return $query; |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* @param ElementInterface|null $element |
248
|
|
|
* @return IntegrationAssociationQuery |
249
|
|
|
*/ |
250
|
|
|
protected function getQuery(ElementInterface $element = null): IntegrationAssociationQuery |
251
|
|
|
{ |
252
|
|
|
/** @var IntegrationAssociation $recordClass */ |
253
|
|
|
$recordClass = static::recordClass(); |
254
|
|
|
|
255
|
|
|
$query = $recordClass::find(); |
256
|
|
|
|
257
|
|
|
if ($this->max !== null) { |
258
|
|
|
$query->limit($this->max); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
$query->field($this) |
262
|
|
|
->siteId($this->targetSiteId($element)) |
263
|
|
|
->elementId(($element === null || $element->getId() === null |
264
|
|
|
) ? false : $element->getId()); |
265
|
|
|
|
266
|
|
|
return $query; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* @param IntegrationAssociationQuery $query |
271
|
|
|
* @param $value |
272
|
|
|
* @param ElementInterface|null $element |
273
|
|
|
*/ |
274
|
|
|
protected function normalizeQueryValue( |
275
|
|
|
IntegrationAssociationQuery $query, |
276
|
|
|
$value, |
277
|
|
|
ElementInterface $element = null |
278
|
|
|
) { |
279
|
|
|
|
280
|
|
|
if (is_array($value)) { |
281
|
|
|
$this->normalizeQueryInputValues($query, $value, $element); |
282
|
|
|
return; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
if ($value === '') { |
286
|
|
|
$this->normalizeQueryEmptyValue($query); |
287
|
|
|
return; |
288
|
|
|
} |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* @param IntegrationAssociationQuery $query |
293
|
|
|
* @param array $value |
294
|
|
|
* @param ElementInterface|null $element |
295
|
|
|
*/ |
296
|
|
|
protected function normalizeQueryInputValues( |
297
|
|
|
IntegrationAssociationQuery $query, |
298
|
|
|
array $value, |
299
|
|
|
ElementInterface $element = null |
300
|
|
|
) { |
301
|
|
|
|
302
|
|
|
$models = []; |
303
|
|
|
$sortOrder = 1; |
304
|
|
|
foreach ($value as $val) { |
305
|
|
|
$models[] = $this->normalizeQueryInputValue($val, $sortOrder, $element); |
306
|
|
|
} |
307
|
|
|
$query->setCachedResult($models); |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* @param $value |
312
|
|
|
* @param int $sortOrder |
313
|
|
|
* @param ElementInterface|null $element |
314
|
|
|
* @return IntegrationAssociation |
315
|
|
|
*/ |
316
|
|
|
protected function normalizeQueryInputValue( |
317
|
|
|
$value, |
318
|
|
|
int &$sortOrder, |
319
|
|
|
ElementInterface $element = null |
320
|
|
|
): IntegrationAssociation { |
321
|
|
|
|
322
|
|
|
if (is_array($value)) { |
323
|
|
|
$value = StringHelper::toString($value); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** @var IntegrationAssociation $recordClass */ |
327
|
|
|
$recordClass = static::recordClass(); |
328
|
|
|
|
329
|
|
|
/** @var IntegrationAssociation $association */ |
330
|
|
|
$association = new $recordClass(); |
331
|
|
|
$association->setField($this) |
332
|
|
|
->setElement($element) |
333
|
|
|
->setSiteId($this->targetSiteId($element)); |
334
|
|
|
|
335
|
|
|
$association->sortOrder = $sortOrder++; |
336
|
|
|
$association->objectId = $value; |
337
|
|
|
|
338
|
|
|
return $association; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* @param IntegrationAssociationQuery $query |
343
|
|
|
*/ |
344
|
|
|
protected function normalizeQueryEmptyValue( |
345
|
|
|
IntegrationAssociationQuery $query |
346
|
|
|
) { |
347
|
|
|
|
348
|
|
|
$query->setCachedResult([]); |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Returns the site ID that target elements should have. |
353
|
|
|
* |
354
|
|
|
* @param ElementInterface|Element|null $element |
355
|
|
|
* |
356
|
|
|
* @return int |
357
|
|
|
*/ |
358
|
|
|
protected function targetSiteId(ElementInterface $element = null): int |
359
|
|
|
{ |
360
|
|
|
/** @var Element $element */ |
361
|
|
|
if (Craft::$app->getIsMultiSite() === true && $element !== null) { |
362
|
|
|
return $element->siteId; |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
return Craft::$app->getSites()->currentSite->id; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
|
369
|
|
|
/******************************************* |
370
|
|
|
* MODIFY ELEMENT QUERY |
371
|
|
|
*******************************************/ |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* @inheritdoc |
375
|
|
|
*/ |
376
|
|
|
public function modifyElementsQuery(ElementQueryInterface $query, $value) |
377
|
|
|
{ |
378
|
|
|
if ($value === null || !$query instanceof ElementQuery) { |
379
|
|
|
return null; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
if ($value === false) { |
383
|
|
|
return false; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
if (is_string($value)) { |
387
|
|
|
$this->modifyElementsQueryForStringValue($query, $value); |
388
|
|
|
return null; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
$this->modifyElementsQueryForTargetValue($query, $value); |
392
|
|
|
return null; |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* @param ElementQuery $query |
397
|
|
|
* @param string $value |
398
|
|
|
*/ |
399
|
|
|
protected function modifyElementsQueryForStringValue( |
400
|
|
|
ElementQuery $query, |
401
|
|
|
string $value |
402
|
|
|
) { |
403
|
|
|
|
404
|
|
|
if ($value === 'not :empty:') { |
405
|
|
|
$value = ':notempty:'; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
if ($value === ':notempty:' || $value === ':empty:') { |
409
|
|
|
$this->modifyElementsQueryForEmptyValue($query, $value); |
410
|
|
|
return; |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
$this->modifyElementsQueryForTargetValue($query, $value); |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* @param ElementQuery $query |
418
|
|
|
* @param $value |
419
|
|
|
*/ |
420
|
|
|
protected function modifyElementsQueryForTargetValue( |
421
|
|
|
ElementQuery $query, |
422
|
|
|
$value |
423
|
|
|
) { |
424
|
|
|
|
425
|
|
|
$alias = $this->tableAlias(); |
426
|
|
|
$name = '{{%' . $this->tableAlias() . '}}'; |
427
|
|
|
|
428
|
|
|
$joinTable = "{$name} {$alias}"; |
429
|
|
|
$query->query->innerJoin($joinTable, "[[{$alias}.elementId]] = [[subquery.elementsId]]"); |
430
|
|
|
$query->subQuery->innerJoin($joinTable, "[[{$alias}.elementId]] = [[elements.id]]"); |
431
|
|
|
|
432
|
|
|
$query->subQuery->andWhere( |
433
|
|
|
Db::parseParam($alias . '.fieldId', $this->id) |
|
|
|
|
434
|
|
|
); |
435
|
|
|
|
436
|
|
|
$query->subQuery->andWhere( |
437
|
|
|
Db::parseParam($alias . '.objectId', $value) |
|
|
|
|
438
|
|
|
); |
439
|
|
|
|
440
|
|
|
$query->query->distinct(true); |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
/** |
444
|
|
|
* @param ElementQuery $query |
445
|
|
|
* @param string $value |
446
|
|
|
*/ |
447
|
|
|
protected function modifyElementsQueryForEmptyValue( |
448
|
|
|
ElementQuery $query, |
449
|
|
|
string $value |
450
|
|
|
) { |
451
|
|
|
|
452
|
|
|
$operator = ($value === ':notempty:' ? '!=' : '='); |
453
|
|
|
$query->subQuery->andWhere( |
454
|
|
|
$this->emptyValueSubSelect( |
|
|
|
|
455
|
|
|
$this->tableAlias(), |
456
|
|
|
'{{%' . $this->tableAlias() . '}}', |
457
|
|
|
$operator |
458
|
|
|
) |
459
|
|
|
); |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
/** |
463
|
|
|
* @param string $alias |
464
|
|
|
* @param string $name |
465
|
|
|
* @param string $operator |
466
|
|
|
* @return string |
467
|
|
|
*/ |
468
|
|
|
protected function emptyValueSubSelect( |
469
|
|
|
string $alias, |
470
|
|
|
string $name, |
471
|
|
|
string $operator |
472
|
|
|
): string { |
473
|
|
|
|
474
|
|
|
return "(select count([[{$alias}.elementId]]) from " . |
475
|
|
|
$name . |
476
|
|
|
" {{{$alias}}} where [[{$alias}.elementId" . |
477
|
|
|
"]] = [[elements.id]] and [[{$alias}.fieldId]] = {$this->id}) {$operator} 0"; |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
|
481
|
|
|
/******************************************* |
482
|
|
|
* RULES |
483
|
|
|
*******************************************/ |
484
|
|
|
|
485
|
|
|
/** |
486
|
|
|
* @inheritdoc |
487
|
|
|
*/ |
488
|
|
|
public function rules() |
489
|
|
|
{ |
490
|
|
|
return array_merge( |
491
|
|
|
parent::rules(), |
492
|
|
|
[ |
493
|
|
|
[ |
494
|
|
|
'object', |
495
|
|
|
'required', |
496
|
|
|
'message' => Craft::t(static::TRANSLATION_CATEGORY, 'Object cannot be empty.') |
497
|
|
|
], |
498
|
|
|
[ |
499
|
|
|
[ |
500
|
|
|
'object', |
501
|
|
|
'min', |
502
|
|
|
'max', |
503
|
|
|
'viewUrl', |
504
|
|
|
'listUrl', |
505
|
|
|
'selectionLabel' |
506
|
|
|
], |
507
|
|
|
'safe', |
508
|
|
|
'on' => [ |
509
|
|
|
ModelHelper::SCENARIO_DEFAULT |
510
|
|
|
] |
511
|
|
|
] |
512
|
|
|
] |
513
|
|
|
); |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
|
517
|
|
|
/******************************************* |
518
|
|
|
* SEARCH |
519
|
|
|
*******************************************/ |
520
|
|
|
|
521
|
|
|
/** |
522
|
|
|
* @param IntegrationAssociationQuery $value |
523
|
|
|
* @inheritdoc |
524
|
|
|
*/ |
525
|
|
|
public function getSearchKeywords($value, ElementInterface $element): string |
526
|
|
|
{ |
527
|
|
|
$objects = []; |
528
|
|
|
|
529
|
|
|
/** @var IntegrationAssociation $association */ |
530
|
|
|
foreach ($value->all() as $association) { |
531
|
|
|
array_push($objects, $association->objectId); |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
return parent::getSearchKeywords($objects, $element); |
535
|
|
|
} |
536
|
|
|
|
537
|
|
|
|
538
|
|
|
/******************************************* |
539
|
|
|
* VIEWS |
540
|
|
|
*******************************************/ |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* @inheritdoc |
544
|
|
|
* @param IntegrationAssociationQuery $value |
545
|
|
|
* @throws \Twig_Error_Loader |
546
|
|
|
* @throws \yii\base\Exception |
547
|
|
|
*/ |
548
|
|
|
public function getInputHtml($value, ElementInterface $element = null): string |
549
|
|
|
{ |
550
|
|
|
$value->limit(null); |
551
|
|
|
|
552
|
|
|
Craft::$app->getView()->registerAssetBundle(IntegrationsAsset::class); |
553
|
|
|
|
554
|
|
|
return Craft::$app->getView()->renderTemplate( |
555
|
|
|
static::INPUT_TEMPLATE_PATH, |
556
|
|
|
$this->inputHtmlVariables($value, $element) |
557
|
|
|
); |
558
|
|
|
} |
559
|
|
|
|
560
|
|
|
/** |
561
|
|
|
* @param IntegrationAssociationQuery $query |
562
|
|
|
* @param ElementInterface|null $element |
563
|
|
|
* @param bool $static |
564
|
|
|
* @return array |
565
|
|
|
* @throws \craft\errors\MissingComponentException |
566
|
|
|
* @throws \yii\base\InvalidConfigException |
567
|
|
|
*/ |
568
|
|
|
protected function inputHtmlVariables( |
569
|
|
|
IntegrationAssociationQuery $query, |
570
|
|
|
ElementInterface $element = null, |
571
|
|
|
bool $static = false |
572
|
|
|
): array { |
573
|
|
|
|
574
|
|
|
return [ |
575
|
|
|
'field' => $this, |
576
|
|
|
'element' => $element, |
577
|
|
|
'value' => $query, |
578
|
|
|
'objectLabel' => $this->getObjectLabel(), |
579
|
|
|
'static' => $static, |
580
|
|
|
'itemTemplate' => static::INPUT_ITEM_TEMPLATE_PATH, |
581
|
|
|
'settings' => [ |
582
|
|
|
'translationCategory' => static::TRANSLATION_CATEGORY, |
583
|
|
|
'limit' => $this->max ? $this->max : null, |
584
|
|
|
'data' => [ |
585
|
|
|
'field' => $this->id, |
586
|
|
|
'element' => $element ? $element->getId() : null |
587
|
|
|
], |
588
|
|
|
'actions' => $this->getActionHtml($element), |
589
|
|
|
'actionAction' => static::ACTION_PREFORM_ACTION_PATH, |
590
|
|
|
'createItemAction' => static::ACTION_CREATE_ITEM_PATH, |
591
|
|
|
'itemData' => [ |
592
|
|
|
'field' => $this->id, |
593
|
|
|
'element' => $element ? $element->getId() : null |
594
|
|
|
], |
595
|
|
|
'itemSettings' => [ |
596
|
|
|
'translationCategory' => static::TRANSLATION_CATEGORY, |
597
|
|
|
'actionAction' => static::ACTION_PREFORM_ITEM_ACTION_PATH, |
598
|
|
|
'associateAction' => static::ACTION_ASSOCIATION_ITEM_PATH, |
599
|
|
|
'dissociateAction' => static::ACTION_DISSOCIATION_ITEM_PATH, |
600
|
|
|
'data' => [ |
601
|
|
|
'field' => $this->id, |
602
|
|
|
'element' => $element ? $element->getId() : null |
603
|
|
|
], |
604
|
|
|
'actions' => $this->getItemActionHtml($element), |
605
|
|
|
] |
606
|
|
|
] |
607
|
|
|
]; |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
|
611
|
|
|
/******************************************* |
612
|
|
|
* ACTIONS |
613
|
|
|
*******************************************/ |
614
|
|
|
|
615
|
|
|
/** |
616
|
|
|
* @return IntegrationActionInterface[] |
617
|
|
|
* @throws \craft\errors\MissingComponentException |
618
|
|
|
* @throws \yii\base\InvalidConfigException |
619
|
|
|
*/ |
620
|
|
|
public function getAvailableActions(): array |
621
|
|
|
{ |
622
|
|
|
$event = new RegisterIntegrationFieldActionsEvent([ |
623
|
|
|
'actions' => $this->defaultAvailableActions |
624
|
|
|
]); |
625
|
|
|
|
626
|
|
|
$this->trigger( |
627
|
|
|
static::EVENT_REGISTER_AVAILABLE_ACTIONS, |
628
|
|
|
$event |
629
|
|
|
); |
630
|
|
|
|
631
|
|
|
return $this->resolveActions( |
632
|
|
|
array_filter((array)$event->actions), |
633
|
|
|
IntegrationActionInterface::class |
634
|
|
|
); |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
/** |
638
|
|
|
* @param ElementInterface|null $element |
639
|
|
|
* @return IntegrationActionInterface[] |
640
|
|
|
* @throws \craft\errors\MissingComponentException |
641
|
|
|
* @throws \yii\base\InvalidConfigException |
642
|
|
|
*/ |
643
|
|
|
public function getActions(ElementInterface $element = null): array |
644
|
|
|
{ |
645
|
|
|
$event = new RegisterIntegrationFieldActionsEvent([ |
646
|
|
|
'actions' => $this->selectedActions, |
647
|
|
|
'element' => $element |
648
|
|
|
]); |
649
|
|
|
|
650
|
|
|
$this->trigger( |
651
|
|
|
static::EVENT_REGISTER_ACTIONS, |
652
|
|
|
$event |
653
|
|
|
); |
654
|
|
|
|
655
|
|
|
return $this->resolveActions( |
656
|
|
|
array_filter((array)$event->actions), |
657
|
|
|
IntegrationActionInterface::class |
658
|
|
|
); |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
/** |
662
|
|
|
* @return IntegrationActionInterface[] |
663
|
|
|
* @throws \craft\errors\MissingComponentException |
664
|
|
|
* @throws \yii\base\InvalidConfigException |
665
|
|
|
*/ |
666
|
|
|
public function getAvailableItemActions(): array |
667
|
|
|
{ |
668
|
|
|
$event = new RegisterIntegrationFieldActionsEvent([ |
669
|
|
|
'actions' => $this->defaultAvailableItemActions |
670
|
|
|
]); |
671
|
|
|
|
672
|
|
|
$this->trigger( |
673
|
|
|
static::EVENT_REGISTER_AVAILABLE_ITEM_ACTIONS, |
674
|
|
|
$event |
675
|
|
|
); |
676
|
|
|
|
677
|
|
|
return $this->resolveActions( |
678
|
|
|
array_filter((array)$event->actions), |
679
|
|
|
IntegrationItemActionInterface::class |
680
|
|
|
); |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
/** |
684
|
|
|
* @param ElementInterface|null $element |
685
|
|
|
* @return IntegrationItemActionInterface[] |
686
|
|
|
* @throws \craft\errors\MissingComponentException |
687
|
|
|
* @throws \yii\base\InvalidConfigException |
688
|
|
|
*/ |
689
|
|
|
public function getItemActions(ElementInterface $element = null): array |
690
|
|
|
{ |
691
|
|
|
$event = new RegisterIntegrationFieldActionsEvent([ |
692
|
|
|
'actions' => $this->selectedItemActions, |
693
|
|
|
'element' => $element |
694
|
|
|
]); |
695
|
|
|
|
696
|
|
|
$this->trigger( |
697
|
|
|
static::EVENT_REGISTER_ITEM_ACTIONS, |
698
|
|
|
$event |
699
|
|
|
); |
700
|
|
|
|
701
|
|
|
return $this->resolveActions( |
702
|
|
|
array_filter((array)$event->actions), |
703
|
|
|
IntegrationItemActionInterface::class |
704
|
|
|
); |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
/** |
708
|
|
|
* @param array $actions |
709
|
|
|
* @param string $instance |
710
|
|
|
* @return array |
711
|
|
|
* @throws \craft\errors\MissingComponentException |
712
|
|
|
* @throws \yii\base\InvalidConfigException |
713
|
|
|
*/ |
714
|
|
|
protected function resolveActions(array $actions, string $instance) |
715
|
|
|
{ |
716
|
|
|
foreach ($actions as $i => $action) { |
717
|
|
|
// $action could be a string or config array |
718
|
|
|
if (!$action instanceof $instance) { |
719
|
|
|
$actions[$i] = $action = ComponentHelper::createComponent($action, $instance); |
720
|
|
|
|
721
|
|
|
if ($actions[$i] === null) { |
722
|
|
|
unset($actions[$i]); |
723
|
|
|
} |
724
|
|
|
} |
725
|
|
|
} |
726
|
|
|
|
727
|
|
|
return array_values($actions); |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
/** |
731
|
|
|
* @param ElementInterface|null $element |
732
|
|
|
* @return array |
733
|
|
|
* @throws \craft\errors\MissingComponentException |
734
|
|
|
* @throws \yii\base\InvalidConfigException |
735
|
|
|
*/ |
736
|
|
|
protected function getActionHtml(ElementInterface $element = null): array |
737
|
|
|
{ |
738
|
|
|
$actionData = []; |
739
|
|
|
|
740
|
|
|
foreach ($this->getActions($element) as $action) { |
741
|
|
|
$actionData[] = [ |
742
|
|
|
'type' => get_class($action), |
743
|
|
|
'destructive' => $action->isDestructive(), |
744
|
|
|
'name' => $action->getTriggerLabel(), |
745
|
|
|
'trigger' => $action->getTriggerHtml(), |
746
|
|
|
'confirm' => $action->getConfirmationMessage(), |
747
|
|
|
]; |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
return $actionData; |
751
|
|
|
} |
752
|
|
|
|
753
|
|
|
/** |
754
|
|
|
* @param ElementInterface|null $element |
755
|
|
|
* @return array |
756
|
|
|
* @throws \craft\errors\MissingComponentException |
757
|
|
|
* @throws \yii\base\InvalidConfigException |
758
|
|
|
*/ |
759
|
|
|
protected function getItemActionHtml(ElementInterface $element = null): array |
760
|
|
|
{ |
761
|
|
|
$actionData = []; |
762
|
|
|
|
763
|
|
|
foreach ($this->getItemActions($element) as $action) { |
764
|
|
|
$actionData[] = [ |
765
|
|
|
'type' => get_class($action), |
766
|
|
|
'destructive' => $action->isDestructive(), |
767
|
|
|
'name' => $action->getTriggerLabel(), |
768
|
|
|
'trigger' => $action->getTriggerHtml(), |
769
|
|
|
'confirm' => $action->getConfirmationMessage(), |
770
|
|
|
]; |
771
|
|
|
} |
772
|
|
|
|
773
|
|
|
return $actionData; |
774
|
|
|
} |
775
|
|
|
|
776
|
|
|
|
777
|
|
|
|
778
|
|
|
/******************************************* |
779
|
|
|
* EVENTS |
780
|
|
|
*******************************************/ |
781
|
|
|
|
782
|
|
|
/** |
783
|
|
|
* @param ElementInterface $element |
784
|
|
|
* @param bool $isNew |
785
|
|
|
* @return bool|void |
786
|
|
|
* @throws \Throwable |
787
|
|
|
* @throws \yii\db\StaleObjectException |
788
|
|
|
*/ |
789
|
|
|
public function afterElementSave(ElementInterface $element, bool $isNew) |
790
|
|
|
{ |
791
|
|
|
/** @var IntegrationAssociationQuery $query */ |
792
|
|
|
$query = $element->getFieldValue($this->handle); |
793
|
|
|
|
794
|
|
|
$currentAssociations = []; |
795
|
|
|
|
796
|
|
|
if (!$isNew) { |
797
|
|
|
/** @var ActiveRecord $recordClass */ |
798
|
|
|
$recordClass = static::recordClass(); |
799
|
|
|
|
800
|
|
|
/** @var IntegrationAssociationQuery $existingQuery */ |
801
|
|
|
$existingQuery = $recordClass::find(); |
802
|
|
|
$existingQuery->element = $query->element; |
803
|
|
|
$existingQuery->field = $query->field; |
804
|
|
|
$existingQuery->site = $query->site; |
805
|
|
|
$existingQuery->indexBy = 'objectId'; |
806
|
|
|
|
807
|
|
|
$currentAssociations = $existingQuery->all(); |
808
|
|
|
} |
809
|
|
|
|
810
|
|
|
$success = true; |
811
|
|
|
|
812
|
|
|
if (null === ($records = $query->getCachedResult())) { |
813
|
|
|
foreach ($currentAssociations as $currentAssociation) { |
814
|
|
|
if (!$currentAssociation->delete()) { |
815
|
|
|
$success = false; |
816
|
|
|
} |
817
|
|
|
} |
818
|
|
|
|
819
|
|
|
if (!$success) { |
820
|
|
|
$this->addError('types', 'Unable to dissociate object.'); |
821
|
|
|
throw new Exception('Unable to dissociate object.'); |
822
|
|
|
} |
823
|
|
|
|
824
|
|
|
parent::afterElementSave($element, $isNew); |
825
|
|
|
} else { |
826
|
|
|
$associations = []; |
827
|
|
|
$order = 1; |
828
|
|
|
foreach ($records as $record) { |
829
|
|
|
if (null === ($association = ArrayHelper::remove($currentAssociations, $record->objectId))) { |
830
|
|
|
$association = $record; |
831
|
|
|
} |
832
|
|
|
$association->sortOrder = $order++; |
833
|
|
|
$associations[] = $association; |
834
|
|
|
} |
835
|
|
|
|
836
|
|
|
// DeleteOrganization those removed |
837
|
|
|
foreach ($currentAssociations as $currentAssociation) { |
838
|
|
|
if (!$currentAssociation->delete()) { |
839
|
|
|
$success = false; |
840
|
|
|
} |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
foreach ($associations as $association) { |
844
|
|
|
if (!$association->save()) { |
845
|
|
|
$success = false; |
846
|
|
|
} |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
if (!$success) { |
850
|
|
|
$this->addError('users', 'Unable to associate objects.'); |
851
|
|
|
throw new Exception('Unable to associate objects.'); |
852
|
|
|
} |
853
|
|
|
|
854
|
|
|
parent::afterElementSave($element, $isNew); |
855
|
|
|
} |
856
|
|
|
} |
857
|
|
|
|
858
|
|
|
|
859
|
|
|
/******************************************* |
860
|
|
|
* SETTINGS |
861
|
|
|
*******************************************/ |
862
|
|
|
|
863
|
|
|
/** |
864
|
|
|
* @inheritdoc |
865
|
|
|
* @throws \Twig_Error_Loader |
866
|
|
|
* @throws \yii\base\Exception |
867
|
|
|
*/ |
868
|
|
|
public function getSettingsHtml() |
869
|
|
|
{ |
870
|
|
|
return Craft::$app->getView()->renderTemplate( |
871
|
|
|
static::SETTINGS_TEMPLATE_PATH, |
872
|
|
|
$this->settingsHtmlVariables() |
873
|
|
|
); |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
/** |
877
|
|
|
* @return array |
878
|
|
|
* @throws \craft\errors\MissingComponentException |
879
|
|
|
* @throws \yii\base\InvalidConfigException |
880
|
|
|
*/ |
881
|
|
|
protected function settingsHtmlVariables(): array |
882
|
|
|
{ |
883
|
|
|
return [ |
884
|
|
|
'field' => $this, |
885
|
|
|
'availableActions' => $this->getAvailableActions(), |
886
|
|
|
'availableItemActions' => $this->getAvailableItemActions(), |
887
|
|
|
'translationCategory' => static::TRANSLATION_CATEGORY, |
888
|
|
|
]; |
889
|
|
|
} |
890
|
|
|
|
891
|
|
|
/** |
892
|
|
|
* @inheritdoc |
893
|
|
|
*/ |
894
|
|
|
public function settingsAttributes(): array |
895
|
|
|
{ |
896
|
|
|
return array_merge( |
897
|
|
|
[ |
898
|
|
|
'object', |
899
|
|
|
'min', |
900
|
|
|
'max', |
901
|
|
|
'viewUrl', |
902
|
|
|
'listUrl', |
903
|
|
|
'selectedActions', |
904
|
|
|
'selectedItemActions', |
905
|
|
|
'selectionLabel' |
906
|
|
|
], |
907
|
|
|
parent::settingsAttributes() |
908
|
|
|
); |
909
|
|
|
} |
910
|
|
|
} |
911
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.