Skip to content

Commit 794daaa

Browse files
authored
Merge pull request #31480 from colemanw/afformActionMode
Afform - Add action modes for joins
2 parents 1700d5d + bd7fd2d commit 794daaa

File tree

7 files changed

+324
-16
lines changed

7 files changed

+324
-16
lines changed

ext/afform/admin/ang/afGuiEditor.css

+3
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,9 @@ body.af-gui-dragging {
433433
white-space: nowrap;
434434
}
435435

436+
#afGuiEditor .dropdown-menu li .checkbox-inline label {
437+
font-weight: normal;
438+
}
436439
#afGuiEditor .dropdown-menu li > * > label {
437440
font-weight: normal;
438441
cursor: pointer;

ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer-menu.html

+13
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@
3131
</div>
3232
</div>
3333
</li>
34+
<li ng-click="$event.stopPropagation()" ng-if="$ctrl.isJoin()">
35+
<div class="form-inline af-gui-field-select-in-dropdown">
36+
<div class="form-group">
37+
<label>{{:: ts('Allow:') }}</label>
38+
<div class="checkbox-inline">
39+
<label><input type="checkbox" ng-model="$ctrl.node.actions.update" ng-change="$ctrl.onChangeUpdateAction()">{{:: ts('Update') }}</label>
40+
</div>
41+
<div class="checkbox-inline">
42+
<label><input type="checkbox" ng-model="$ctrl.node.actions.delete" ng-disabled="!$ctrl.node.actions.update">{{:: ts('Delete') }}</label>
43+
</div>
44+
</div>
45+
</div>
46+
</li>
3447
<li><af-gui-menu-item-style node="$ctrl.node"></af-gui-menu-item-style></li>
3548
<li><af-gui-menu-item-border node="$ctrl.node"></af-gui-menu-item-border></li>
3649
<li><af-gui-menu-item-background node="$ctrl.node"></af-gui-menu-item-background></li>

ext/afform/admin/ang/afGuiEditor/elements/afGuiContainer.component.js

+14
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@
111111
return 'layout' in block;
112112
};
113113

114+
this.isJoin = function() {
115+
return !!ctrl.join;
116+
};
117+
114118
$scope.getSetChildren = function(val) {
115119
var collection = block.layout || (ctrl.node && ctrl.node['#children']);
116120
return arguments.length ? (collection = val) : collection;
@@ -255,7 +259,17 @@
255259
}
256260
};
257261

262+
this.onChangeUpdateAction = function() {
263+
if (!ctrl.node.actions.update) {
264+
ctrl.node.actions.delete = false;
265+
}
266+
};
267+
258268
function initializeBlockContainer() {
269+
// Set defaults for 'actions'
270+
if (!('actions' in ctrl.node)) {
271+
ctrl.node.actions = {update: true, delete: true};
272+
}
259273

260274
// Cancel the below $watch expressions if already set
261275
_.each(block.listeners, function(deregister) {

ext/afform/core/CRM/Afform/ArrayHtml.php

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class CRM_Afform_ArrayHtml {
2424
'*' => 'text',
2525
'af-fieldset' => 'text',
2626
'data' => 'js',
27+
'actions' => 'js',
2728
],
2829
'af-entity' => [
2930
'#selfClose' => TRUE,

ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php

+28-3
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,10 @@ public function loadEntity(array $entity, array $values, string $mode = 'update'
241241
if (!empty($result[$key])) {
242242
$data = ['fields' => $result[$key]];
243243
foreach ($entity['joins'] ?? [] as $joinEntity => $join) {
244-
$data['joins'][$joinEntity] = $this->loadJoins($joinEntity, $join, $entity, $entityId, $index);
244+
$joinAllowedAction = self::getJoinAllowedAction($entity, $joinEntity);
245+
if ($joinAllowedAction['update']) {
246+
$data['joins'][$joinEntity] = $this->loadJoins($joinEntity, $entity, $entityId, $index);
247+
}
245248
}
246249
$this->_entityValues[$entity['name']][$index] = $data;
247250
}
@@ -251,8 +254,9 @@ public function loadEntity(array $entity, array $values, string $mode = 'update'
251254
/**
252255
* Finds all joins after loading an entity.
253256
*/
254-
public function loadJoins($joinEntity, $join, $afEntity, $entityId, $index): array {
257+
public function loadJoins(string $joinEntity, array $afEntity, $entityId, $index): array {
255258
$joinIdField = CoreUtil::getIdFieldName($joinEntity);
259+
$join = $afEntity['joins'][$joinEntity];
256260
$multipleLocationBlocks = is_array($join['data']['location_type_id'] ?? NULL);
257261
$limit = 1;
258262
// Repeating blocks - set limit according to `max`, if set, otherwise 0 for unlimited
@@ -462,9 +466,13 @@ protected static function getJoinWhereClause(FormDataModel $formDataModel, strin
462466
}
463467

464468
protected static function getFkField($mainEntity, $otherEntity): ?array {
469+
$fkEntities = [$otherEntity];
470+
if ($otherEntity !== 'Contact' && CoreUtil::isContact($otherEntity)) {
471+
$fkEntities[] = 'Contact';
472+
}
465473
foreach (self::getEntityFields($mainEntity) as $field) {
466474
if ($field['type'] === 'Field' && empty($field['custom_field_id']) &&
467-
($field['fk_entity'] === $otherEntity || in_array($otherEntity, $field['dfk_entities'] ?? [], TRUE))
475+
(in_array($field['fk_entity'], $fkEntities, TRUE) || array_intersect($fkEntities, $field['dfk_entities'] ?? []))
468476
) {
469477
return $field;
470478
}
@@ -732,4 +740,21 @@ protected static function getNestedKey(array $values) {
732740
return is_array($firstValue) && $firstValue ? array_keys($firstValue)[0] : NULL;
733741
}
734742

743+
/**
744+
* Function to get allowed action of a join entity
745+
*
746+
* @param array $mainEntity
747+
* @param string $joinEntityName
748+
*
749+
* @return array{update: bool, delete: bool}
750+
*/
751+
public static function getJoinAllowedAction(array $mainEntity, string $joinEntityName) {
752+
$actions = ["update" => TRUE, "delete" => TRUE];
753+
if (array_key_exists('actions', $mainEntity['joins'][$joinEntityName])) {
754+
$actions = array_merge($actions, $mainEntity['joins'][$joinEntityName]['actions']);
755+
}
756+
757+
return $actions;
758+
}
759+
735760
}

ext/afform/core/Civi/Api4/Action/Afform/Submit.php

+37-13
Original file line numberDiff line numberDiff line change
@@ -486,17 +486,18 @@ private static function saveRelationshipByValues(array $relationship, array $ent
486486
*/
487487
protected static function saveJoins(AfformSubmitEvent $event, $index, $entityId, $joins) {
488488
$mainEntity = $event->getFormDataModel()->getEntity($event->getEntityName());
489-
foreach ($joins as $joinEntityName => $join) {
490-
$values = self::filterEmptyJoins($mainEntity, $joinEntityName, $join);
489+
foreach ($joins as $joinEntityName => $joinValues) {
490+
$values = self::filterEmptyJoins($mainEntity, $joinEntityName, $joinValues);
491491
$whereClause = self::getJoinWhereClause($event->getFormDataModel(), $event->getEntityName(), $joinEntityName, $entityId);
492492
$mainIdField = CoreUtil::getIdFieldName($mainEntity['type']);
493493
$joinIdField = CoreUtil::getIdFieldName($joinEntityName);
494+
$joinAllowedAction = self::getJoinAllowedAction($mainEntity, $joinEntityName);
494495

495496
// Forward FK e.g. Event.loc_block_id => LocBlock
496497
$forwardFkField = self::getFkField($mainEntity['type'], $joinEntityName);
497498
if ($forwardFkField && $values) {
498499
// Add id to values for update op, but only if id is not already on the form
499-
if ($whereClause && empty($mainEntity['joins'][$joinEntityName]['fields'][$joinIdField])) {
500+
if ($whereClause && $joinAllowedAction['update'] && empty($mainEntity['joins'][$joinEntityName]['fields'][$joinIdField])) {
500501
$values[0][$joinIdField] = $whereClause[0][2];
501502
}
502503
$result = civicrm_api4($joinEntityName, 'save', [
@@ -514,21 +515,44 @@ protected static function saveJoins(AfformSubmitEvent $event, $index, $entityId,
514515
}
515516

516517
// Reverse FK e.g. Contact <= Email.contact_id
517-
// TODO: REPLACE works for creating or updating contacts, but different logic would be needed if
518-
// the contact was being auto-updated via a dedupe rule; in that case we would not want to
519-
// delete any existing records.
520518
elseif ($values) {
521-
$result = civicrm_api4($joinEntityName, 'replace', [
522-
// Disable permission checks because the main entity has already been vetted
523-
'checkPermissions' => FALSE,
524-
'where' => $whereClause,
525-
'records' => $values,
526-
]);
519+
// In update mode, set ids of existing values
520+
if ($joinAllowedAction['update']) {
521+
$existingJoinValues = $event->getApiRequest()->loadJoins($joinEntityName, $mainEntity, $entityId, $index);
522+
foreach ($existingJoinValues as $joinIndex => $existingJoin) {
523+
if (!empty($existingJoin[$joinIdField]) && !empty($values[$joinIndex])) {
524+
$values[$joinIndex][$joinIdField] = $existingJoin[$joinIdField];
525+
}
526+
}
527+
}
528+
else {
529+
foreach ($values as $key => $value) {
530+
unset($values[$key][$joinIdField]);
531+
}
532+
}
533+
// Use REPLACE action if update+delete are both allowed (only need to check for 'delete' as it implies 'update')
534+
if ($joinAllowedAction['delete']) {
535+
$result = civicrm_api4($joinEntityName, 'replace', [
536+
// Disable permission checks because the main entity has already been vetted
537+
'checkPermissions' => FALSE,
538+
'where' => $whereClause,
539+
'records' => $values,
540+
]);
541+
}
542+
else {
543+
$fkField = self::getFkField($joinEntityName, $mainEntity['type']);
544+
$result = civicrm_api4($joinEntityName, 'save', [
545+
// Disable permission checks because the main entity has already been vetted
546+
'checkPermissions' => FALSE,
547+
'defaults' => [$fkField['name'] => $entityId],
548+
'records' => $values,
549+
]);
550+
}
527551
$indexedResult = array_combine(array_keys($values), (array) $result);
528552
$event->setJoinIds($index, $joinEntityName, $indexedResult);
529553
}
530554
// REPLACE doesn't work if there are no records, have to use DELETE
531-
else {
555+
elseif ($joinAllowedAction['delete']) {
532556
try {
533557
civicrm_api4($joinEntityName, 'delete', [
534558
// Disable permission checks because the main entity has already been vetted

0 commit comments

Comments
 (0)