*
* Generated from xml/schema/CRM/Contact/ContactType.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:6ac1343e8af16b17a161fda746457d93)
+ * (GenCodeChecksum:b66036d325985536154ec623ab362e39)
*/
/**
*/
public static $_tableName = 'civicrm_contact_type';
+ /**
+ * Field to show when displaying a record.
+ *
+ * @var string
+ */
+ public static $_labelField = 'label';
+
/**
* Should CiviCRM log any modifications to this table in the civicrm_log table.
*
$changeLog = $this->_viewOptions['log'];
$this->assign_by_ref('changeLog', $changeLog);
- $this->assign('allTabs', $this->getTabs());
+ $this->assign('allTabs', $this->getTabs($defaults));
// hook for contact summary
// ignored but needed to prevent warnings
* @return array
* @throws \CRM_Core_Exception
*/
- public function getTabs() {
+ public function getTabs(array $contact) {
$allTabs = [];
$getCountParams = [];
$weight = 10;
}
// Allow other modules to add or remove tabs
- $context = ['contact_id' => $this->_contactId];
+ $context = [
+ 'contact_id' => $contact['id'],
+ 'contact_type' => $contact['contact_type'],
+ 'contact_sub_type' => CRM_Utils_Array::explodePadded($contact['contact_sub_type'] ?? NULL),
+ ];
CRM_Utils_Hook::tabset('civicrm/contact/view', $allTabs, $context);
$expectedKeys = ['count', 'class', 'template', 'hideCount', 'icon'];
--- /dev/null
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Service\Autocomplete;
+
+use Civi\Core\Event\GenericHookEvent;
+use Civi\Core\HookInterface;
+
+/**
+ * @service
+ * @internal
+ */
+class ContactTypeAutocompleteProvider extends \Civi\Core\Service\AutoService implements HookInterface {
+
+ /**
+ * Provide default SearchDisplay for ContactType autocompletes
+ *
+ * @param \Civi\Core\Event\GenericHookEvent $e
+ */
+ public static function on_civi_search_defaultDisplay(GenericHookEvent $e) {
+ if ($e->display['settings'] || $e->display['type'] !== 'autocomplete' || $e->savedSearch['api_entity'] !== 'ContactType') {
+ return;
+ }
+ $e->display['settings'] = [
+ 'sort' => [
+ ['label', 'ASC'],
+ ],
+ 'columns' => [
+ [
+ 'type' => 'field',
+ 'key' => 'label',
+ 'icons' => [
+ ['field' => 'icon'],
+ ],
+ ],
+ [
+ 'type' => 'field',
+ 'key' => 'description',
+ 'rewrite' => "{if '[description]'}[description]{elseif '[parent_id]'}" . ts('Subtype of %1', [1 => '[parent_id:label]']) . "{/if}",
+ ],
+ ],
+ ];
+ }
+
+}
}
// Special handling for 'current_user' and user lookups
- if ($fk === 'Contact' && !is_numeric($value)) {
+ if ($fk === 'Contact' && isset($value) && !is_numeric($value)) {
$value = \_civicrm_api3_resolve_contactID($value);
if ('unknown-user' === $value) {
throw new \CRM_Core_Exception("\"{$fieldSpec['name']}\" \"{$value}\" cannot be resolved to a contact ID", 2002, ['error_field' => $fieldSpec['name'], "type" => "integer"]);
<option value="tab">{{:: ts('As Tab') }}</option>
</select>
</div>
+ <div class="form-inline" ng-if="editor.afform.contact_summary">
+ <label>{{:: ts('For') }}</label>
+ <input class="form-control" crm-autocomplete="'ContactType'" ng-model="editor.afform.summary_contact_type" auto-open="true" multi="true" crm-autocomplete-params="{key: 'name'}" placeholder="{{:: ts('Any contact type') }}">
+ </div>
<p class="help-block" ng-show="editor.afform.contact_summary">
{{:: ts('Placement can be configured using the Contact Layout Editor.') }}
</p>
'tab' => ts('Contact Summary Tab'),
],
],
+ [
+ 'name' => 'summary_contact_type',
+ 'data_type' => 'Array',
+ 'options' => \CRM_Contact_BAO_ContactType::contactTypePairs(),
+ ],
[
'name' => 'icon',
'description' => 'Icon shown in the contact summary tab',
if ($tabsetName !== 'civicrm/contact/view') {
return;
}
+ $contactTypes = array_merge([$context['contact_type']], $context['contact_sub_type'] ?? []);
$afforms = Civi\Api4\Afform::get(FALSE)
+ ->addSelect('name', 'title', 'icon', 'module_name', 'directive_name', 'summary_contact_type')
->addWhere('contact_summary', '=', 'tab')
- ->addSelect('name', 'title', 'icon', 'module_name', 'directive_name')
+ ->addOrderBy('title')
->execute();
$weight = 111;
foreach ($afforms as $afform) {
- $tabs[] = [
- 'id' => $afform['name'],
- 'title' => $afform['title'],
- 'weight' => $weight++,
- 'icon' => 'crm-i ' . ($afform['icon'] ?: 'fa-list-alt'),
- 'is_active' => TRUE,
- 'template' => 'afform/contactSummary/AfformTab.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 (empty($context['caller'])) {
- Civi::service('angularjs.loader')->addModules($afform['module_name']);
+ if (empty($afform['summary_contact_type']) || array_intersect($afform['summary_contact_type'], $contactTypes)) {
+ $tabs[] = [
+ 'id' => $afform['name'],
+ 'title' => $afform['title'],
+ 'weight' => $weight++,
+ 'icon' => 'crm-i ' . ($afform['icon'] ?: 'fa-list-alt'),
+ 'is_active' => TRUE,
+ 'template' => 'afform/contactSummary/AfformTab.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 (empty($context['caller'])) {
+ Civi::service('angularjs.loader')->addModules($afform['module_name']);
+ }
}
}
}
if (!in_array(get_class($page), ['CRM_Contact_Page_View_Summary', 'CRM_Contact_Page_View_Print'])) {
return;
}
- $scanner = \Civi::service('afform_scanner');
+ $afforms = Civi\Api4\Afform::get(FALSE)
+ ->addSelect('name', 'title', 'icon', 'module_name', 'directive_name', 'summary_contact_type')
+ ->addWhere('contact_summary', '=', 'block')
+ ->addOrderBy('title')
+ ->execute();
$cid = $page->get('cid');
+ $contact = NULL;
$side = 'left';
- foreach ($scanner->getMetas() as $afform) {
- if (!empty($afform['contact_summary']) && $afform['contact_summary'] === 'block') {
- $module = _afform_angular_module_name($afform['name']);
- $block = [
- 'module' => $module,
- 'directive' => _afform_angular_module_name($afform['name'], 'dash'),
- ];
- $content = CRM_Core_Smarty::singleton()->fetchWith('afform/contactSummary/AfformBlock.tpl', ['contactId' => $cid, 'block' => $block]);
- CRM_Core_Region::instance("contact-basic-info-$side")->add([
- 'markup' => '<div class="crm-summary-block">' . $content . '</div>',
- 'weight' => 1,
- ]);
- Civi::service('angularjs.loader')->addModules($module);
- $side = $side === 'left' ? 'right' : 'left';
+ $weight = ['left' => 1, 'right' => 1];
+ foreach ($afforms as $afform) {
+ // If Afform specifies a contact type, lookup the contact and compare
+ if (!empty($afform['summary_contact_type'])) {
+ // Contact.get only needs to happen once
+ $contact = $contact ?? civicrm_api4('Contact', 'get', [
+ 'select' => ['contact_type', 'contact_sub_type'],
+ 'where' => [['id', '=', $cid]],
+ ])->first();
+ $contactTypes = array_merge([$contact['contact_type']], $contact['contact_sub_type'] ?? []);
+ if (!array_intersect($afform['summary_contact_type'], $contactTypes)) {
+ continue;
+ }
}
+ $block = [
+ 'module' => $afform['module_name'],
+ 'directive' => _afform_angular_module_name($afform['name'], 'dash'),
+ ];
+ $content = CRM_Core_Smarty::singleton()->fetchWith('afform/contactSummary/AfformBlock.tpl', ['contactId' => $cid, 'block' => $block]);
+ CRM_Core_Region::instance("contact-basic-info-$side")->add([
+ 'markup' => '<div class="crm-summary-block">' . $content . '</div>',
+ 'name' => 'afform:' . $afform['name'],
+ 'weight' => $weight[$side]++,
+ ]);
+ Civi::service('angularjs.loader')->addModules($afform['module_name']);
+ $side = $side === 'left' ? 'right' : 'left';
}
}
*/
function afform_civicrm_contactSummaryBlocks(&$blocks) {
$afforms = \Civi\Api4\Afform::get(FALSE)
- ->setSelect(['name', 'title', 'directive_name', 'module_name', 'type', 'type:icon', 'type:label'])
+ ->setSelect(['name', 'title', 'directive_name', 'module_name', 'type', 'type:icon', 'type:label', 'summary_contact_type'])
->addWhere('contact_summary', '=', 'block')
+ ->addOrderBy('title')
->execute();
+ $allContactTypes = \CRM_Contact_BAO_ContactType::getAllContactTypes();
foreach ($afforms as $index => $afform) {
// Create a group per afform type
$blocks += [
'blocks' => [],
],
];
+ $contactType = [];
+ // If the form specifies contact types, resolve them to just the parent types (Individual, Organization, Household)
+ // because ContactLayout doesn't care about sub-types
+ foreach ($afform['summary_contact_type'] ?? [] as $name) {
+ $parent = $allContactTypes[$name]['parent'] ?? $name;
+ $contactType[$parent] = $parent;
+ }
$blocks["afform_{$afform['type']}"]['blocks'][$afform['name']] = [
'title' => $afform['title'],
+ // ContactLayout only supports a single contact type
+ 'contact_type' => count($contactType) === 1 ? CRM_Utils_Array::first($contactType) : NULL,
'tpl_file' => 'afform/contactSummary/AfformBlock.tpl',
'module' => $afform['module_name'],
'directive' => $afform['directive_name'],
--- /dev/null
+<?php
+namespace Civi\Afform;
+
+// Hopefully temporry workaround for loading core test classes
+require_once __DIR__ . '/../../../../../../../tests/phpunit/api/v4/Api4TestBase.php';
+
+use Civi\Api4\Afform;
+
+/**
+ * @group headless
+ */
+class AfformContactSummaryTest extends \api\v4\Api4TestBase {
+
+ private $formNames = [
+ 'contact_summary_test1',
+ 'contact_summary_test2',
+ 'contact_summary_test3',
+ ];
+
+ public function setUpHeadless() {
+ return \Civi\Test::headless()->installMe(__DIR__)->install('org.civicrm.search_kit')->apply();
+ }
+
+ public function tearDown(): void {
+ Afform::revert(FALSE)->addWhere('name', 'IN', $this->formNames)->execute();
+ parent::tearDown();
+ }
+
+ public function testAfformContactSummaryTab(): void {
+ $this->saveTestRecords('ContactType', [
+ 'records' => [
+ ['name' => 'FooBar', 'label' => 'FooBar', 'parent_id:name' => 'Individual'],
+ ],
+ 'match' => ['name'],
+ ]);
+
+ Afform::create()
+ ->addValue('name', $this->formNames[0])
+ ->addValue('title', 'Test B')
+ ->addValue('contact_summary', 'tab')
+ ->addValue('summary_contact_type', ['Organization'])
+ ->execute();
+ Afform::create()
+ ->addValue('name', $this->formNames[1])
+ ->addValue('title', 'Test C')
+ ->addValue('contact_summary', 'tab')
+ ->addValue('summary_contact_type', ['FooBar'])
+ ->addValue('icon', 'smiley-face')
+ ->execute();
+ Afform::create()
+ ->addValue('name', $this->formNames[2])
+ ->addValue('title', 'Test A')
+ ->addValue('contact_summary', 'tab')
+ ->execute();
+
+ $tabs = [];
+ $context = [
+ 'contact_id' => 0,
+ 'contact_type' => 'Individual',
+ 'contact_sub_type' => ['FooBar'],
+ 'caller' => 'UnitTests',
+ ];
+ \CRM_Utils_Hook::tabset('civicrm/contact/view', $tabs, $context);
+
+ $tabs = array_column($tabs, NULL, 'id');
+
+ $this->assertArrayHasKey($this->formNames[1], $tabs);
+ $this->assertArrayHasKey($this->formNames[2], $tabs);
+ $this->assertArrayNotHasKey($this->formNames[0], $tabs);
+ $this->assertEquals('Test C', $tabs[$this->formNames[1]]['title']);
+ $this->assertEquals('Test A', $tabs[$this->formNames[2]]['title']);
+ $this->assertEquals('crm-i smiley-face', $tabs[$this->formNames[1]]['icon']);
+ // Fallback icon
+ $this->assertEquals('crm-i fa-list-alt', $tabs[$this->formNames[2]]['icon']);
+ // Forms should be sorted by title alphabetically
+ $this->assertGreaterThan($tabs[$this->formNames[2]]['weight'], $tabs[$this->formNames[1]]['weight']);
+ }
+
+ public function testAfformContactSummaryBlock(): void {
+ $this->saveTestRecords('ContactType', [
+ 'records' => [
+ ['name' => 'Farm', 'label' => 'Farm', 'parent_id:name' => 'Organization'],
+ ],
+ 'match' => ['name'],
+ ]);
+
+ $cid = $this->createTestRecord('Contact', [
+ 'contact_type' => 'Organization',
+ 'contact_sub_type' => ['Farm'],
+ ])['id'];
+
+ Afform::create()
+ ->addValue('name', $this->formNames[0])
+ ->addValue('title', 'Test B')
+ ->addValue('type', 'search')
+ ->addValue('contact_summary', 'block')
+ ->addValue('summary_contact_type', ['Individual', 'Household'])
+ ->execute();
+ Afform::create()
+ ->addValue('name', $this->formNames[1])
+ ->addValue('title', 'Test C')
+ ->addValue('type', 'form')
+ ->addValue('contact_summary', 'block')
+ ->addValue('summary_contact_type', ['Farm'])
+ ->addValue('icon', 'smiley-face')
+ ->execute();
+ Afform::create()
+ ->addValue('name', $this->formNames[2])
+ ->addValue('type', 'form')
+ ->addValue('title', 'Test A')
+ ->addValue('contact_summary', 'block')
+ ->execute();
+
+ // Call pageRun hook and then assert afforms have been added to the appropriate region
+ $dummy = new \CRM_Contact_Page_View_Summary();
+ $dummy->set('cid', $cid);
+ \CRM_Utils_Hook::pageRun($dummy);
+
+ // TODO: Be more flexible
+ // The presence of any other afform blocks in the system might alter the left-right assumptions here
+ $blockA = \CRM_Core_Region::instance('contact-basic-info-left')->get('afform:' . $this->formNames[2]);
+ $this->assertStringContainsString("<contact-summary-test3 options=\"{contact_id: $cid}\"></contact-summary-test3>", $blockA['markup']);
+
+ $blockB = \CRM_Core_Region::instance('contact-basic-info-right')->get('afform:' . $this->formNames[1]);
+ $this->assertStringContainsString("<contact-summary-test2 options=\"{contact_id: $cid}\"></contact-summary-test2>", $blockB['markup']);
+
+ // Block for wrong contact type should not appear
+ $this->assertNull(\CRM_Core_Region::instance('contact-basic-info-left')->get('afform:' . $this->formNames[0]));
+ $this->assertNull(\CRM_Core_Region::instance('contact-basic-info-right')->get('afform:' . $this->formNames[0]));
+
+ // Ensure blocks show up in ContactLayoutEditor
+ $blocks = [];
+ afform_civicrm_contactSummaryBlocks($blocks);
+
+ // ContactLayout doesn't support > 1 contact type, so this ought to be null
+ $this->assertNull($blocks['afform_search']['blocks'][$this->formNames[0]]['contact_type']);
+ // Sub-type should have been converted to parent type
+ $this->assertEquals('Organization', $blocks['afform_form']['blocks'][$this->formNames[1]]['contact_type']);
+ $this->assertNull($blocks['afform_form']['blocks'][$this->formNames[2]]['contact_type']);
+ // Forms should be sorted by title
+ $order = array_flip(array_keys($blocks['afform_form']['blocks']));
+ $this->assertGreaterThan($order[$this->formNames[2]], $order[$this->formNames[1]]);
+ }
+
+}
<name>civicrm_contact_type</name>
<comment>Provide type information for contacts</comment>
<add>3.1</add>
+ <labelField>label</labelField>
<paths>
<add>civicrm/admin/options/subtype/edit?action=add&reset=1</add>
<update>civicrm/admin/options/subtype/edit?action=update&id=[id]&reset=1</update>
<comment>Internal name of Contact Type (or Subtype).</comment>
<html>
<label>Name</label>
+ <type>Text</type>
</html>
<add>3.1</add>
<required>true</required>
<type>varchar</type>
<length>64</length>
<comment>localized Name of Contact Type.</comment>
+ <html>
+ <label>Label</label>
+ <type>Text</type>
+ </html>
<localizable>true</localizable>
<add>3.1</add>
</field>
</pseudoconstant>
<html>
<label>Parent</label>
+ <type>Select</type>
</html>
<add>3.1</add>
</field>