Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev/core#5603 Support multi-record CustomGroups for Events using Afform tab display #31606

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
30 changes: 15 additions & 15 deletions CRM/Core/BAO/CustomGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -2137,7 +2137,7 @@ public static function getEntityFromExtends(string $extends): string {
* Returns a mix of hard-coded array and `cg_extend_objects` OptionValues.
* - 'id' return key (maps to `cg_extend_objects.value`).
* - 'grouping' key refers to the entity field used to select a sub-type.
* - 'is_multiple' (@internal, not returned by getFields.loadOptions) (maps to `cg_extend_objects.filter`)
* - 'allow_is_multiple' (@internal, not returned by getFields.loadOptions) (maps to `cg_extend_objects.filter`)
* controls whether the entity supports multi-record custom groups.
* - 'table_name' (@internal, not returned by getFields.loadOptions) (maps to `cg_extend_objects.name`).
* We don't return it as the 'name' in getFields because it is not always unique (since contact types are pseudo-entities).
Expand All @@ -2151,89 +2151,89 @@ public static function getCustomGroupExtendsOptions() {
'label' => ts('Activities'),
'grouping' => 'activity_type_id',
'table_name' => 'civicrm_activity',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
[
'id' => 'Relationship',
'label' => ts('Relationships'),
'grouping' => 'relationship_type_id',
'table_name' => 'civicrm_relationship',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
// TODO: Move to civi_contribute extension (example: OptionValue_cg_extends_objects_grant.mgd.php)
[
'id' => 'Contribution',
'label' => ts('Contributions'),
'grouping' => 'financial_type_id',
'table_name' => 'civicrm_contribution',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
[
'id' => 'ContributionRecur',
'label' => ts('Recurring Contributions'),
'grouping' => NULL,
'table_name' => 'civicrm_contribution_recur',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
[
'id' => 'Group',
'label' => ts('Groups'),
'grouping' => NULL,
'table_name' => 'civicrm_group',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
// TODO: Move to civi_member extension (example: OptionValue_cg_extends_objects_grant.mgd.php)
[
'id' => 'Membership',
'label' => ts('Memberships'),
'grouping' => 'membership_type_id',
'table_name' => 'civicrm_membership',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
// TODO: Move to civi_event extension (example: OptionValue_cg_extends_objects_grant.mgd.php)
[
'id' => 'Event',
'label' => ts('Events'),
'grouping' => 'event_type_id',
'table_name' => 'civicrm_event',
'is_multiple' => FALSE,
'allow_is_multiple' => TRUE,
],
[
'id' => 'Participant',
'label' => ts('Participants'),
'grouping' => NULL,
'table_name' => 'civicrm_participant',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
// TODO: Move to civi_pledge extension (example: OptionValue_cg_extends_objects_grant.mgd.php)
[
'id' => 'Pledge',
'label' => ts('Pledges'),
'grouping' => NULL,
'table_name' => 'civicrm_pledge',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
[
'id' => 'Address',
'label' => ts('Addresses'),
'grouping' => NULL,
'table_name' => 'civicrm_address',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
// TODO: Move to civi_campaign extension (example: OptionValue_cg_extends_objects_grant.mgd.php)
[
'id' => 'Campaign',
'label' => ts('Campaigns'),
'grouping' => 'campaign_type_id',
'table_name' => 'civicrm_campaign',
'is_multiple' => FALSE,
'allow_is_multiple' => FALSE,
],
[
'id' => 'Contact',
'label' => ts('Contacts'),
'grouping' => NULL,
'table_name' => 'civicrm_contact',
'is_multiple' => TRUE,
'allow_is_multiple' => TRUE,
],
];
// `CustomGroup.extends` stores contact type as if it were an entity.
Expand All @@ -2243,7 +2243,7 @@ public static function getCustomGroupExtendsOptions() {
'label' => $contactInfo['label'],
'grouping' => 'contact_sub_type',
'table_name' => 'civicrm_contact',
'is_multiple' => TRUE,
'allow_is_multiple' => TRUE,
'icon' => $contactInfo['icon'],
];
}
Expand All @@ -2255,7 +2255,7 @@ public static function getCustomGroupExtendsOptions() {
'label' => $ogValue['label'],
'grouping' => $ogValue['grouping'] ?? NULL,
'table_name' => $ogValue['name'],
'is_multiple' => !empty($ogValue['filter']),
'allow_is_multiple' => !empty($ogValue['filter']),
];
}
foreach ($options as &$option) {
Expand Down
2 changes: 1 addition & 1 deletion CRM/Custom/Form/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public function buildQuickForm() {
// Assign data for use by js chain-selects
$this->assign('entityColumnIdOptions', $entityColumnIdOptions);
// List of entities that allow `is_multiple`
$this->assign('allowMultiple', array_column($extendsOptions, 'is_multiple', 'id'));
$this->assign('allowMultiple', array_column($extendsOptions, 'allow_is_multiple', 'id'));
// Used by warnDataLoss
$this->assign('defaultSubtypes', $this->_values['extends_entity_column_value'] ?? []);
// Used to initially hide selects with no options
Expand Down
2 changes: 1 addition & 1 deletion ang/afform/afsearchTabNote.aff.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div af-fieldset="">
<crm-search-display-table search-name="Contact_Summary_Notes" display-name="Contact_Summary_Notes_Tab" filters="{entity_id: options.contact_id, entity_table: 'civicrm_contact'}"></crm-search-display-table>
<crm-search-display-table search-name="Contact_Summary_Notes" display-name="Contact_Summary_Notes_Tab" filters="{entity_id: options.entity_id, entity_table: 'civicrm_contact'}"></crm-search-display-table>
</div>
4 changes: 2 additions & 2 deletions ang/afform/afsearchTabRel.aff.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div af-fieldset="">
<crm-search-display-table search-name="Contact_Summary_Relationships" display-name="Contact_Summary_Relationships_Active" filters="{near_contact_id: options.contact_id, is_current: true}"></crm-search-display-table>
<crm-search-display-table search-name="Contact_Summary_Relationships" display-name="Contact_Summary_Relationships_Active" filters="{near_contact_id: options.entity_id, is_current: true}"></crm-search-display-table>
<h3>{{:: ts('Inactive Relationships') }}</h3>
<div class="help">{{:: ts('These relationships are Disabled OR have a past End Date.') }}</div>
<crm-search-display-table search-name="Contact_Summary_Relationships" display-name="Contact_Summary_Relationships_Inactive" filters="{near_contact_id: options.contact_id, is_current: false}"></crm-search-display-table>
<crm-search-display-table search-name="Contact_Summary_Relationships" display-name="Contact_Summary_Relationships_Inactive" filters="{near_contact_id: options.entity_id, is_current: false}"></crm-search-display-table>
<div class="help">
<strong>{{:: ts('Permissioned Relationships:') }}</strong>
<i class="crm-i fa-eye"></i> {{:: ts('This contact can be viewed by the other.') }}
Expand Down
65 changes: 59 additions & 6 deletions ext/afform/core/Civi/Api4/Action/CustomGroup/GetAfforms.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class GetAfforms extends \Civi\Api4\Generic\BasicBatchAction {
'afblockCustom',
'afformUpdateCustom',
'afformCreateCustom',
'afformViewCustom',
'afsearchTabCustom',
];

Expand Down Expand Up @@ -74,6 +75,7 @@ protected function doTask($item) {
break;

case 'form':
$forms[] = $this->generateViewForm($item);
$forms[] = $this->generateUpdateForm($item);
if ($item['is_multiple']) {
$forms[] = $this->generateCreateForm($item);
Expand All @@ -83,11 +85,9 @@ protected function doTask($item) {
case 'search':
// TODO:
// 1. tabs with grid display
// 2. tabs for other entities (e.g. Event)
if (
$item['is_multiple']
&& ($item['style'] === 'Tab with table')
&& CoreUtil::isContact($item['extends'])
) {
$forms[] = $this->generateTabForm($item);
}
Expand Down Expand Up @@ -125,6 +125,56 @@ private function generateFieldBlock($item): array {
return $afform;
}

private function generateViewForm($item): array {
$afform = [
'name' => 'afformViewCustom_' . $item['name'],
'type' => 'form',
'title' => E::ts('View %1', [1 => $item['title']]),
'description' => '',
'is_public' => FALSE,
// NOTE: we will use RBAC for entities to ensure
// this form does not allow folks who shouldn't
// to edit contacts
'permission' => ['access CiviCRM'],
'server_route' => 'civicrm/af/custom/' . $item['name'] . '/view',
'icon' => $item['icon'],
];
if ($this->getLayout) {

// form entity depends on whether this is a multirecord custom group
$formEntity = $item['is_multiple'] ?
[
'type' => 'Custom_' . $item['name'],
'name' => 'Record',
'label' => $item['extends'] . ' ' . $item['title'],
'parent_field' => 'entity_id',
'parent_field_defn' => [
'input_type' => 'Hidden',
'label' => FALSE,
],
] :
[
'type' => $item['extends'],
'name' => $item['extends'] . '1',
'label' => $item['extends'],
'parent_field' => 'id',
'parent_field_defn' => [
'input_type' => 'Hidden',
'label' => FALSE,
],
];

$afform['layout'] = \CRM_Core_Smarty::singleton()->fetchWith(
'afform/customGroups/afformView.tpl',
[
'formEntity' => $formEntity,
'group' => $item,
]
);
}
return $afform;
}

private function generateUpdateForm($item): array {
$afform = [
'name' => 'afformUpdateCustom_' . $item['name'],
Expand Down Expand Up @@ -165,7 +215,7 @@ private function generateUpdateForm($item): array {
];

$afform['layout'] = \CRM_Core_Smarty::singleton()->fetchWith(
'afform/customGroups/afform.tpl',
'afform/customGroups/afformEdit.tpl',
[
'formEntity' => $formEntity,
'formActions' => [
Expand Down Expand Up @@ -206,7 +256,7 @@ private function generateCreateForm($item): array {
],
];
$afform['layout'] = \CRM_Core_Smarty::singleton()->fetchWith(
'afform/customGroups/afform.tpl',
'afform/customGroups/afformEdit.tpl',
[
'formEntity' => $formEntity,
'formActions' => [
Expand All @@ -222,10 +272,11 @@ private function generateCreateForm($item): array {
}

private function generateTabForm($item): array {
$extendsLabel = CoreUtil::getInfoItem($item['extends'], 'title');
$afform = [
// name required to replace the existing tab
'name' => 'afsearchTabCustom_' . $item['name'],
'description' => E::ts('Contact summary tab display for %1', [1 => $item['title']]),
'description' => E::ts('%1 tab display for %2', [1 => $extendsLabel, 2 => $item['title']]),
'type' => 'search',
'is_public' => FALSE,
// Q: should this be more permissive if user has access
Expand All @@ -243,7 +294,9 @@ private function generateTabForm($item): array {
$afform['summary_contact_type'] = [$item['extends']];
}
else {
// TODO implement tabs for other tabsets
// tabs for other entities are placed without any
// additional afform meta
// @see civicrm_admin_ui_civicrm_tabset
}
if ($this->getLayout) {
// TODO: the template should be a table or grid depending
Expand Down
12 changes: 10 additions & 2 deletions ext/afform/core/afform.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,20 @@ function afform_civicrm_tabset($tabsetName, &$tabs, $context) {
'icon' => 'crm-i ' . ($afform['icon'] ?: 'fa-list-alt'),
'is_active' => TRUE,
'contact_type' => _afform_get_contact_types($summaryContactType) ?: NULL,
'template' => 'afform/contactSummary/AfformTab.tpl',
'template' => 'afform/InlineAfform.tpl',
'module' => $afform['module_name'],
'directive' => $afform['directive_name'],
];
// If this is the real contact summary page (and not a callback from ContactLayoutEditor), load module.
// If this is the real contact summary page (and not a callback from ContactLayoutEditor), load module
// and assign contact id to required smarty variable
if (empty($context['caller'])) {
// note we assign the contact id to entity_id as preferred key
// but also contact_id to maintain backwards compatibility with older
// afforms
CRM_Core_Smarty::singleton()->assign('afformOptions', [
'entity_id' => $context['contact_id'],
'contact_id' => $context['contact_id'],
]);
Civi::service('angularjs.loader')->addModules($afform['module_name']);
}
}
Expand Down
5 changes: 5 additions & 0 deletions ext/afform/core/templates/afform/InlineAfform.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<crm-angular-js modules="{$afform.module}">
<form id="bootstrap-theme">
<{$afform.directive} options='{$afformOptions|@json_encode}'></{$afform.directive}>
</form>
</crm-angular-js>
5 changes: 0 additions & 5 deletions ext/afform/core/templates/afform/contactSummary/AfformTab.tpl

This file was deleted.

2 changes: 1 addition & 1 deletion ext/afform/core/templates/afform/customGroups/afblock.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

{foreach from=$group.field_names item=field_name}
{* for multiple record fields there is no need to prepend
the group name because it is provided as the join_entity above *}
the group name because it will be the form entity itself *}
<af-field name="{if !$group.is_multiple}{$group.name}.{/if}{$field_name}" />
{/foreach}

Expand Down
22 changes: 22 additions & 0 deletions ext/afform/core/templates/afform/customGroups/afformView.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<af-form ctrl="afform">
<af-entity
type="{$formEntity.type}"
name="{$formEntity.name}"
label="{$formEntity.label}"
actions='{ldelim}create: false, update: true{rdelim}'
security="RBAC"
url-autofill="1"
/>

<fieldset af-fieldset="{$formEntity.name}" class="af-container">
<af-field
name="{$formEntity.parent_field}"
defn='{$formEntity.parent_field_defn|@json_encode}'
/>
{foreach from=$group.field_names item=field_name}
{* for multiple record fields there is no need to prepend
the group name because it will be the form entity itself *}
<af-field name="{if !$group.is_multiple}{$group.name}.{/if}{$field_name}" defn="{ldelim}input_type: 'DisplayOnly'{rdelim}" />
{/foreach}
</fieldset>
</af-form>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
<crm-search-display-{$display_type}
search-name="{$saved_search}"
display-name="{$search_display}"
filters="{ldelim}entity_id: options.contact_id{rdelim}"
filters="{ldelim}entity_id: options.entity_id{rdelim}"
/>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,11 @@ protected function getButtonColumn($group) {
}

public static function getAllManaged() {
// for now we only fetch for Groups that have a Tab with table on the contact summary
// for now we only fetch for Groups that have a Tab
$all = \Civi\Api4\CustomGroup::getSearchKit(FALSE)
->addWhere('is_active', '=', TRUE)
->addWhere('is_multiple', '=', TRUE)
->addWhere('style', 'IN', ['Tab', 'Tab with table'])
->addWhere('extends', 'IN', ['Contact', 'Individual', 'Household', 'Organization'])
->execute()
->column('managed');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public function addCustomGroupLinks(\Civi\Core\Event\GenericHookEvent $event) {
$groupName = substr($name, 7);
$event->entities[$name]['paths']['add'] = "civicrm/af/custom/{$groupName}/create#?entity_id=[entity_id]";
$event->entities[$name]['paths']['update'] = "civicrm/af/custom/{$groupName}/update#?Record=[id]";
$event->entities[$name]['paths']['view'] = "civicrm/af/custom/{$groupName}/view#?Record=[id]";
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions ext/civicrm_admin_ui/ang/afformTabMember.aff.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<div af-fieldset="">
<crm-search-display-table search-name="Contact_Summary_Memberships" display-name="Contact_Summary_Memberships_Active" filters="{contact_id: options.contact_id, 'status_id.is_current_member': true}"></crm-search-display-table>
<crm-search-display-table search-name="Contact_Summary_Memberships" display-name="Contact_Summary_Memberships_Active" filters="{contact_id: options.entity_id, 'status_id.is_current_member': true}"></crm-search-display-table>
<h3>{{:: ts('Pending and Inactive Memberships') }}</h3>
<crm-search-display-table search-name="Contact_Summary_Memberships" display-name="Contact_Summary_Memberships_Inactive" filters="{contact_id: options.contact_id, 'status_id.is_current_member': false}"></crm-search-display-table>
<crm-search-display-table search-name="Contact_Summary_Memberships" display-name="Contact_Summary_Memberships_Inactive" filters="{contact_id: options.entity_id, 'status_id.is_current_member': false}"></crm-search-display-table>
</div>
<div class="af-markup">
<hr />
</div>
<div af-fieldset="" af-title="Membership Types">
<crm-search-display-table search-name="Contact_Summary_Membership_Type" display-name="Contact_Summary_Membership_Type" filters="{member_of_contact_id: options.contact_id, 'is_active': true}"></crm-search-display-table>
<crm-search-display-table search-name="Contact_Summary_Membership_Type" display-name="Contact_Summary_Membership_Type" filters="{member_of_contact_id: options.entity_id, 'is_active': true}"></crm-search-display-table>
</div>
2 changes: 1 addition & 1 deletion ext/civicrm_admin_ui/ang/afsearchTabActivity.aff.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
<af-field name="status_id" defn="{input_attrs: {multiple: true, placeholder: ts('Status')}, label: false}" />
<af-field name="activity_date_time" defn="{input_type: 'Select', search_range: true, input_attrs: {placeholder: ts('Date')}, label: false}" />
</div>
<crm-search-display-table search-name="Contact_Summary_Activities" display-name="Contact_Summary_Activities_Tab" filters="{'Activity_ActivityContact_Contact_01.id': options.contact_id}"></crm-search-display-table>
<crm-search-display-table search-name="Contact_Summary_Activities" display-name="Contact_Summary_Activities_Tab" filters="{'Activity_ActivityContact_Contact_01.id': options.entity_id}"></crm-search-display-table>
</div>
Loading