Create a custom entity in your extension.¶
This step by step guide will show you how you can create a new entity in your extension. It includes the API functionality and forms for editing the entity.
Alternative: ECK
The Entity Construction Kit Extension provides a user interface based on this step by step guide. So that you create your own entity with clicking rather than programming. You still have to do hard work yourself which is the thinking.
1. Introduction¶
This guide assumes you have successfully set up a CiviCRM development environment and that you have civix working.
2. Create the extension¶
On the command line go the extension directory of your development environment.
Note
In a Drupal installation the extension directory is usually under sites/default/files/civicrm/ext
.
In a WordPress installation this is usually wp-content/uploads/civicrm/ext
Run the following commands to create an extension with the name myentity
cd sites/default/files/civicrm/ext
civix generate:module myentity
cd myentity
When asked whether to enable the extension answer "No" by pressing N
. We will enable the extension later.
Now it is time to change info.xml
and set the name, description and author of the extension.
3. Add a new entity¶
Run the following command to add the new entity, in the directory of your extension:
civix generate:entity MyEntity
This will add the following files:
Civi\Api4\MyEntity.php
the api v4 filexml/schema/CRM/Myentity/MyEntity.entityType.php
a description of the entityxml/schema/CRM/Myentity/MyEntity.xml
the schema.xml file describing the structure of the entity (such as the fields, primary keys, etc.)
Open xml/schema/CRM/Myentity/MyEntity.xml
and change it to your needs. This file contains the schema description, in our example we want to have the following fields:
* ID
* Contact ID
* Title
See Schema Definition for an explanation of the XML file.
Our xml/schema/CRM/Myentity/MyEntity.xml
will initially look like this, however you can delete unwanted fields (e.g. contact_id
) and add others:
<?xml version="1.0" encoding="iso-8859-1" ?>
<table>
<base>CRM/Myentity</base>
<class>MyEntity</class>
<name>civicrm_my_entity</name>
<comment>A test entity for the documenation</comment>
<log>true</log>
<field>
<name>id</name>
<type>int unsigned</type>
<required>true</required>
<comment>Unique MyEntity ID</comment>
</field>
<primaryKey>
<name>id</name>
<autoincrement>true</autoincrement>
</primaryKey>
<field>
<name>contact_id</name>
<type>int unsigned</type>
<comment>FK to Contact</comment>
</field>
<foreignKey>
<name>contact_id</name>
<table>civicrm_contact</table>
<key>id</key>
<onDelete>CASCADE</onDelete>
</foreignKey>
<field>
<name>title</name>
<type>varchar</type>
<length>255</length>
<required>false</required>
</field>
</table>
4. Generate SQL, DAO and BAO files¶
Once you have your desired fields defined in the .xml
file, generate the SQL, DAO and BAO files using the following Civix command:
civix generate:entity-boilerplate
Our sql/auto_install.sql
should look like this:
-- ...
-- /*******************************************************
-- *
-- * civicrm_my_entity
-- *
-- * A test entity for the documenation
-- *
-- *******************************************************/
CREATE TABLE `civicrm_my_entity` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique MyEntity ID',
`contact_id` int unsigned COMMENT 'FK to Contact',
`title` varchar(255) NULL
,
PRIMARY KEY (`id`)
, CONSTRAINT FK_civicrm_my_entity_contact_id FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE
) ENGINE=InnoDb ;
-- ...
Note
You have to repeat this step everytime you make changes to the schema in MyEntity.xml
.
5. Add upgrader class¶
We need an upgrader class so that upon installation and uninstallation of our extension the files auto_install.sql
and auto_unsinstall.sql
are executed.
civix generate:upgrader
6. Install the extension¶
Now it is time to install the extension. In CiviCRM navigate to Administer > System settings > Extensions and click on Install next to our extension, or from the command line type:
cv en myentity
7. Add a form¶
Alternative: Afform
The ECK Entity is migrating to using Afform instead of Quickform. Consider that alternative first.
Add a form to add new entities, we will use the same form for editing existing entities and for deleting an existing entity.
civix generate:form MyEntity civicrm/myentity/form
The above command will generate a skeleton page at CRM/Myentity/Form/MyEntity.php
with the url civicrm/myentity/form.
Change the code of CRM/Myentity/Form/MyEntity.php
to:
<?php
use CRM_Myentity_ExtensionUtil as E;
/**
* Form controller class
*
* @see https://docs.civicrm.org/dev/en/latest/framework/quickform/
*/
class CRM_Myentity_Form_MyEntity extends CRM_Core_Form {
protected $_id;
protected $_myentity;
public function getDefaultEntity() {
return 'MyEntity';
}
public function getDefaultEntityTable() {
return 'civicrm_my_entity';
}
public function getEntityId() {
return $this->_id;
}
/**
* Preprocess form.
*
* This is called before buildForm. Any pre-processing that
* needs to be done for buildForm should be done here.
*
* This is a virtual function and should be redefined if needed.
*/
public function preProcess() {
parent::preProcess();
$this->_action = CRM_Utils_Request::retrieve('action', 'String', $this);
$this->assign('action', $this->_action);
$this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE);
CRM_Utils_System::setTitle('Add Entity');
if ($this->_id) {
CRM_Utils_System::setTitle('Edit Entity');
$entities = civicrm_api4('MyEntity', 'get', ['where' => [['id', '=', $this->_id]], 'limit' => 1]);
$this->_myentity = reset($entities);
$this->assign('myentity', $this->_myentity);
$session = CRM_Core_Session::singleton();
$session->replaceUserContext(CRM_Utils_System::url('civicrm/myentity/form', ['id' => $this->getEntityId(), 'action' => 'update']));
}
}
public function buildQuickForm() {
$this->assign('id', $this->getEntityId());
$this->add('hidden', 'id');
if ($this->_action != CRM_Core_Action::DELETE) {
$this->addEntityRef('contact_id', E::ts('Contact'), [], TRUE);
$this->add('text', 'title', E::ts('Title'), ['class' => 'huge'], FALSE);
$this->addButtons([
[
'type' => 'upload',
'name' => E::ts('Submit'),
'isDefault' => TRUE,
],
]);
} else {
$this->addButtons([
['type' => 'submit', 'name' => E::ts('Delete'), 'isDefault' => TRUE],
['type' => 'cancel', 'name' => E::ts('Cancel')]
]);
}
parent::buildQuickForm();
}
/**
* This virtual function is used to set the default values of various form
* elements.
*
* @return array|NULL
* reference to the array of default values
*/
public function setDefaultValues() {
if ($this->_myentity) {
$defaults = $this->_myentity;
}
return $defaults;
}
public function postProcess() {
if ($this->_action == CRM_Core_Action::DELETE) {
civicrm_api4('MyEntity', 'delete', ['where' => [['id', '=', $this->_id]]]);
CRM_Core_Session::setStatus(E::ts('Removed My Entity'), E::ts('My Entity'), 'success');
} else {
$values = $this->controller->exportValues();
$action = 'create';
if ($this->getEntityId()) {
$params['id'] = $this->getEntityId();
$action = 'update';
}
$params['title'] = $values['title'];
$params['contact_id'] = $values['contact_id'];
civicrm_api4('MyEntity', $action, ['values' => $params]);
}
parent::postProcess();
}
}
In the function preProcess
we check whether an id is passed in the url and if so we retrieve the entity with that id.
In the function buildQuickForm
we add a hidden field for the id, a text field for the title, and a contact reference field for the contact_id field.
In the function setDefaultValues
we return the current entity.
In the function postProcess
we check whether we need to delete the entity of whether we need to create/update the entity.
Now it is time to update the template in templates/CRM/Myentity/Form/MyEntity.tpl
:
{crmScope extensionKey='myentity'}
{if $action eq 8}
{* Are you sure to delete form *}
<h3>{ts}Delete Entity{/ts}</h3>
<div class="crm-block crm-form-block">
<div class="crm-section">{ts 1=$myentity.title}Are you sure you wish to delete the entity with title: %1?{/ts}</div>
</div>
<div class="crm-submit-buttons">
{include file="CRM/common/formButtons.tpl" location="bottom"}
</div>
{else}
<div class="crm-block crm-form-block">
<div class="crm-section">
<div class="label">{$form.title.label}</div>
<div class="content">{$form.title.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section">
<div class="label">{$form.contact_id.label}</div>
<div class="content">{$form.contact_id.html}</div>
<div class="clear"></div>
</div>
<div class="crm-submit-buttons">
{include file="CRM/common/formButtons.tpl" location="bottom"}
</div>
</div>
{/if}
{/crmScope}
8. Integration with Search Kit¶
When we generated the entity it also generated an API version 4. This automatically made the entity available in Search Kit. What it did not do is include links to add, edit or delete the entity. (See Search Kit - Links for an explanation).
To do this we need to add the links to Schema.xml
. Open schema/CRM/Myentity/MyEntity.xml
and add the following lines:
<paths>
<add>civicrm/myentity/form?reset=1&action=add</add>
<view>civicrm/myentity/form?reset=1&action=view&id=[id]</view>
<update>civicrm/myentity/form?reset=1&action=update&id=[id]</update>
<delete>civicrm/myentity/form?reset=1&action=delete&id=[id]</delete>
</paths>
Run the following command to update the SQL, DAO files so that those paths are available in Search Kit:
civix generate:entity-boilerplate
9. Add a search (legacy style custom search)¶
Use Search Kit!
The preferred way to add search capability is via Search Kit. You can find information on how to Add Saved Search to Your Extension.
Add a page which will show all entities in the database. Later on we will add a form to add new entities or edit them.
civix generate:form Search civicrm/myentity/search
The above command will generate a skeleton form which we will use as the base search page at CRM/Myentity/Form/Search.php
with the url civicrm/myentity/search.
Open CRM/Myentity/Page/Search.php
and change it as follows:
<?php
use CRM_Myentity_ExtensionUtil as E;
/**
* Form controller class
*
* @see https://docs.civicrm.org/dev/en/latest/framework/quickform/
*/
class CRM_Myentity_Form_Search extends CRM_Core_Form {
protected $formValues;
protected $pageId = false;
protected $offset = 0;
protected $limit = false;
public $count = 0;
public $rows = [];
public function preProcess() {
parent::preProcess();
$this->formValues = $this->getSubmitValues();
$this->setTitle(E::ts('Search My Entities'));
$this->limit = CRM_Utils_Request::retrieveValue('crmRowCount', 'Positive', 50);
$this->pageId = CRM_Utils_Request::retrieveValue('crmPID', 'Positive', 1);
if ($this->limit !== false) {
$this->offset = ($this->pageId - 1) * $this->limit;
}
$this->query();
$this->assign('entities', $this->rows);
$pagerParams = [];
$pagerParams['total'] = 0;
$pagerParams['status'] =E::ts('%%StatusMessage%%');
$pagerParams['csvString'] = NULL;
$pagerParams['rowCount'] = 50;
$pagerParams['buttonTop'] = 'PagerTopButton';
$pagerParams['buttonBottom'] = 'PagerBottomButton';
$pagerParams['total'] = $this->count;
$pagerParams['pageID'] = $this->pageId;
$this->pager = new CRM_Utils_Pager($pagerParams);
$this->assign('pager', $this->pager);
}
public function buildQuickForm() {
parent::buildQuickForm();
$this->add('text', 'title', E::ts('Title'), array('class' => 'huge'));
$this->addEntityRef('contact_id', E::ts('Contact'), ['create' => false, 'multiple' => true], false, array('class' => 'huge'));
$this->addButtons(array(
array(
'type' => 'refresh',
'name' => E::ts('Search'),
'isDefault' => TRUE,
),
));
}
public function postProcess() {
parent::postProcess();
}
/**
* Runs the query
*
* @throws \CRM_Core_Exception
*/
protected function query() {
$sql = "
SELECT SQL_CALC_FOUND_ROWS
`civicrm_my_entity`.`id`,
`civicrm_my_entity`.`title`,
`civicrm_my_entity`.`contact_id`
FROM `civicrm_my_entity`
WHERE 1";
if (isset($this->formValues['title']) && !empty($this->formValues['title'])) {
$sql .= " AND `civicrm_my_entity`.`title` LIKE '%".$this->formValues['title']."%'";
}
if (isset($this->formValues['contact_id']) && is_array($this->formValues['contact_id']) && count($this->formValues['contact_id'])) {
$sql .= " AND `civicrm_my_entity`.`contact_id` IN (".implode(", ", $this->formValues['contact_id']).")";
}
if ($this->limit !== false) {
$sql .= " LIMIT {$this->offset}, {$this->limit}";
}
$dao = CRM_Core_DAO::executeQuery($sql);
$this->count = CRM_Core_DAO::singleValueQuery("SELECT FOUND_ROWS()");
$this->rows = array();
while($dao->fetch()) {
$row = [
'id' => $dao->id,
'contact_id' => $dao->contact_id,
'title' => $dao->title,
];
if (!empty($row['contact_id'])) {
$row['contact'] = '<a href="'.CRM_Utils_System::url('civicrm/contact/view', ['reset' => 1, 'cid' => $dao->contact_id]).'">'.CRM_Contact_BAO_Contact::displayName($dao->contact_id).'</a>';
}
$this->rows[] = $row;
}
}
}
In the code above the function buildQuickForm
adds the fields and operators to the search form. We allow the user to search on the Contact ID and on the Title.
In the code the function Query
is used to query the database and build the result set which is stored in $this->rows
.
We call the Query
function in the preProcess
function. We also add a pager in the preProcess
function.
The template of this form can be found in templates/CRM/Myentity/Form/Search.tpl
and looks like:
{crmScope extensionKey='myentity'}
<div class="crm-content-block">
<div class="crm-block crm-form-block crm-basic-criteria-form-block">
<div class="crm-accordion-wrapper crm-expenses_search-accordion collapsed">
<div class="crm-accordion-header crm-master-accordion-header">{ts}Search My Entities{/ts}</div><!-- /.crm-accordion-header -->
<div class="crm-accordion-body">
<table class="form-layout">
<tbody>
<tr>
<td class="label">{$form.contact_id.label}</td>
<td>{$form.contact_id.html}</td>
</tr>
<tr>
<td class="label">{$form.title.label}</td>
<td>{$form.title.html}</td>
</tr>
</tbody>
</table>
<div class="crm-submit-buttons">
{include file="CRM/common/formButtons.tpl"}
</div>
</div><!- /.crm-accordion-body -->
</div><!-- /.crm-accordion-wrapper -->
</div><!-- /.crm-form-block -->
<div class="action-link">
<a class="button" href="{crmURL p="civicrm/myentity/form" q="reset=1&action=add" }">
<i class="crm-i fa-plus-circle"> </i>
{ts}Add my entity{/ts}
</a>
</div>
<div class="clear"></div>
<div class="crm-results-block">
{include file="CRM/common/pager.tpl" location="top"}
<div class="crm-search-results">
<table class="selector row-highlight">
<thead class="sticky">
<tr>
<th scope="col">
{ts}ID{/ts}
</th>
<th scope="col">
{ts}Contact{/ts}
</th>
<th scope="col">
{ts}Title{/ts}
</th>
<th> </th>
</tr>
</thead>
{foreach from=$entities item=row}
<tr>
<td>{$row.id}</td>
<td>{$row.contact}</td>
<td>{$row.title}</td>
<td class="right nowrap">
<span>
<a class="action-item crm-hover-button" href="{crmURL p='civicrm/myentity/form' q="id=$row.id&action=update"}"><i class="crm-i fa-pencil"></i> {ts}Edit{/ts}</a>
<a class="action-item crm-hover-button" href="{crmURL p='civicrm/myentity/form' q="id=$row.id&action=delete"}"><i class="crm-i fa-trash"></i> {ts}Delete{/ts}</a>
</span>
</td>
</tr>
{/foreach}
</table>
</div>
{include file="CRM/common/pager.tpl" location="bottom"}
</div>
</div>
{/crmScope}
The template also contains a link to add a new entity to the system.
9.1. Add navigation¶
Open myentity.php
and add the following code:
/**
* Implements hook_civicrm_navigationMenu().
*
* @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_navigationMenu
*/
function myentity_civicrm_navigationMenu(&$menu) {
_myentity_civix_insert_navigation_menu($menu, 'Search', array(
'label' => E::ts('Search My Entities'),
'name' => 'search_my_entity',
'url' => 'civicrm/myentity/search',
'permission' => 'access CiviCRM',
'operator' => 'OR',
'separator' => 0,
));
_myentity_civix_navigationMenu($menu);
}
The code above adds a navigation item with a label, name, url and permission to the civicrm navigation.
10. Add a tab on contact summary¶
The tab on the contact summary screen will list all entities linked to the contact.
You can create a tab by first creating a page with civix.
civix generate:page ContactTab civicrm/myentity/contacttab
Edit the file CRM/Page/ContactTab.php
and make sure it looks as follows:
<?php
use CRM_Myentity_ExtensionUtil as E;
class CRM_Myentity_Page_ContactTab extends CRM_Core_Page {
public function run() {
// Example: Set the page-title dynamically; alternatively, declare a static title in xml/Menu/*.xml
CRM_Utils_System::setTitle(E::ts('My Entity'));
$contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE);
$myEntities = \Civi\Api4\MyEntity::get()
->select('*')
->addWhere('contact_id', '=', $contactId)
->execute();
$rows = array();
foreach($myEntities as $myEntity) {
$row = $myEntity;
if (!empty($row['contact_id'])) {
$row['contact'] = '<a href="'.CRM_Utils_System::url('civicrm/contact/view', ['reset' => 1, 'cid' => $row['contact_id']]).'">'.CRM_Contact_BAO_Contact::displayName($row['contact_id']).'</a>';
}
$rows[] = $row;
}
$this->assign('contactId', $contactId);
$this->assign('rows', $rows);
// Set the user context
$session = CRM_Core_Session::singleton();
$userContext = CRM_Utils_System::url('civicrm/contact/view', 'cid='.$contactId.'&selectedChild=contact_my_entity&reset=1');
$session->pushUserContext($userContext);
parent::run();
}
}
Edit the template templates/CRM/Myentity/Page/ContactTab.tpl
and make sure it looks like:
{crmScope extensionKey='myentity'}
<div class="crm-content-block">
<div class="action-link">
<a class="button" href="{crmURL p="civicrm/myentity/form" q="reset=1&action=add" }">
<i class="crm-i fa-plus-circle"> </i>
{ts}Add my entity{/ts}
</a>
</div>
<div class="clear"></div>
<div class="crm-results-block">
{include file="CRM/common/pager.tpl" location="top"}
<div class="crm-search-results">
<table class="selector row-highlight">
<thead class="sticky">
<tr>
<th scope="col">
{ts}ID{/ts}
</th>
<th scope="col">
{ts}Contact{/ts}
</th>
<th scope="col">
{ts}Title{/ts}
</th>
<th> </th>
</tr>
</thead>
{foreach from=$rows item=row}
<tr>
<td>{$row.id}</td>
<td>{$row.contact}</td>
<td>{$row.title}</td>
<td class="right nowrap">
<span>
<a class="action-item crm-hover-button" href="{crmURL p='civicrm/myentity/form' q="id=`$row.id`&action=update"}"><i class="crm-i fa-pencil"></i> {ts}Edit{/ts}</a>
<a class="action-item crm-hover-button" href="{crmURL p='civicrm/myentity/form' q="id=`$row.id`&action=delete"}"><i class="crm-i fa-trash"></i> {ts}Delete{/ts}</a>
</span>
</td>
</tr>
{/foreach}
</table>
</div>
{include file="CRM/common/pager.tpl" location="bottom"}
</div>
</div>
{/crmScope}
Add the hook hook_civicrm_tabset to myentity.php
:
/**
* Implementation of hook_civicrm_tabset
* @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_tabset
*/
function myentity_civicrm_tabset($path, &$tabs, $context) {
if ($path === 'civicrm/contact/view') {
// add a tab to the contact summary screen
$contactId = $context['contact_id'];
$url = CRM_Utils_System::url('civicrm/myentity/contacttab', ['cid' => $contactId]);
$myEntities = \Civi\Api4\MyEntity::get()
->selectRowCount()
->addWhere('contact_id', '=', $contactId)
->execute();
$tabs[] = array(
'id' => 'contact_my_entity',
'url' => $url,
'count' => $myEntities->count(),
'title' => E::ts('My Entity'),
'weight' => 1,
'icon' => 'crm-i fa-envelope-open',
);
}
}
11. Add Subtype field (optional)¶
If you wish for your entity to be segmented by subtype, similar to many core entities (e.g. Activies are segmented by activity_type_id
, Cases by case_type_id
, etc.),
you must add a column to store that value, plus an option group to hold the possible subtypes. In the following section you'll also see how to allow custom data to be segmented according to this subtype.
Open schema/CRM/Myentity/MyEntity.xml
and add the field named something like type_id
. This field is linked to an option group with the name my_entity_type
.
<!--
...
-->
<primaryKey>
<name>id</name>
<autoincrement>true</autoincrement>
</primaryKey>
<!-- Below the new field for type_id -->
<field>
<name>type_id</name>
<title>Type</title>
<type>int</type>
<length>3</length>
<default>NULL</default>
<pseudoconstant>
<optionGroupName>my_entity_type</optionGroupName>
</pseudoconstant>
<html>
<type>Select</type>
</html>
</field>
<field>
<name>contact_id</name>
<type>int unsigned</type>
<comment>FK to Contact</comment>
</field>
<!-- ... -->
Run the following command to update the SQL, DAO files:
civix generate:entity-boilerplate
Open sql/auto_install.sql
and make sure the create table statement has the ENGINE=InnoDB
and the statement looks like this:
CREATE TABLE `civicrm_my_entity` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique MyEntity ID',
`type_id` int DEFAULT NULL ,
`contact_id` int unsigned COMMENT 'FK to Contact',
`title` varchar(255) NULL,
PRIMARY KEY (`id`),
CONSTRAINT FK_civicrm_my_entity_contact_id FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB;
To create the option group, add a .mgd.php
file (example from CiviGrant):
<?php
use CRM_Grant_ExtensionUtil as E;
return [
[
'name' => 'OptionGroup_grant_type',
'entity' => 'OptionGroup',
'cleanup' => 'always',
'update' => 'unmodified',
'params' => [
'version' => 4,
'values' => [
'name' => 'grant_type',
'title' => 'Grant Type',
'description' => NULL,
'data_type' => NULL,
'is_reserved' => TRUE,
'is_active' => TRUE,
'is_locked' => FALSE,
],
'match' => ['name'],
],
],
];
Change grant_type
to whatever you used for my_entity_type
above.
Enable Managed Entities
If your extension doesn't already support managed entities you need to enable that mixin.
Type civix mixin --enable=mgd-php@1
.
Open CRM/Myentity/Form/MyEntity.php
and change the following:
public function buildQuickForm() {
// ...
if ($this->_action != CRM_Core_Action::DELETE) {
// Add the two lines below:
$types = CRM_Core_OptionGroup::values('my_entity_type');
$this->add('select', 'type_id', E::ts('Type'), $types, TRUE, ['class' => 'huge crm-select2', 'data-option-edit-path' => 'civicrm/admin/options/my_entity_type']);
$this->addEntityRef('contact_id', E::ts('Contact'), [], TRUE);
// ...
}
// ...
}
// ...
public function setDefaultValues() {
// ...
if (empty($defaults['type_id'])) {
$defaults['type_id'] = CRM_Core_OptionGroup::getDefaultValue('expense_type');
}
return $defaults;
}
// ...
public function postProcess() {
// ...
$params['title'] = $values['title'];
$params['contact_id'] = $values['contact_id'];
// Add the line below:
$params['type_id'] = $values['type_id'];
// ...
$result = civicrm_api4('MyEntity', $action, ['values' => $params]);
}
Open templates/CRM/Myentity/Form/MyEntity.tpl
and add type id field to the template with the following code:
<div class="crm-section">
<div class="label">{$form.type_id.label}</div>
<div class="content">{$form.type_id.html}</div>
<div class="clear"></div>
</div>
Add a new file under CRM/Myentity/PseudoConstant.php
with the following contents:
<?php
class CRM_Myentity_PseudoConstant extends CRM_Core_PseudoConstant {
public static function myEntityType() {
$types = CRM_Core_OptionGroup::values('my_entity_type');
return $types;
}
}
Now when you uninstall the extension and reinstall it again you have should be able to configure type's for our entity.
The next step is to make it possible to connect custom fields to our entity or to a certain types of our entity.
12. Enabling Custom Data¶
In this step we make it possible to create custom fields for our entity.
12.1 Making our entity available for custom data¶
We will make our entity available for custom data by inserting an option value via the Managed Entity system:
The value goes in the custom group cg_extend_objects
with the following properties:
label
: user-readable name of our entityvalue
: the API name of our entityname
: the database table name of our entitygrouping
: (optional) The "subtype" field if this entity supports sub-types (see previous section).
Here is an example from the CiviGrant Extension.
This belongs in a file named ./managed/OptionValue_cg_extends_objects.mgd.php
(the exact name of the file isn't important as long as it ends in .mgd.php
).
You should substitute label
, value
& name
as appropriate for your entity:
Supporting multiple entities
If your extension creates multiple entities, you must add one option value per entity that needs Custom Data.
You can place all of them in the same .mgd.php
file, or break each one to its own file.
<?php
use CRM_Grant_ExtensionUtil as E;
// This enables custom fields for Grant entities
return [
[
'name' => 'cg_extend_objects:Grant',
'entity' => 'OptionValue',
'cleanup' => 'always',
'update' => 'always',
'params' => [
'version' => 4,
'values' => [
'option_group_id.name' => 'cg_extend_objects',
'label' => E::ts('Grants'),
'value' => 'Grant',
'name' => 'civicrm_grant',
'is_reserved' => TRUE,
'is_active' => TRUE,
'grouping' => 'grant_type_id',
],
],
],
Enable Managed Entities
If your extension doesn't already support managed entities you need to enable that mixin.
Type civix mixin --enable=mgd-php@1
.
12.2 Add custom data to the form¶
Open CRM/Myentity/Form/MyEntity.php
and change the following functions:
public function preProcess() {
// ...
if (!empty($_POST['hidden_custom'])) {
$type_id = $this->getSubmitValue('type_id');
CRM_Custom_Form_CustomData::preProcess($this, NULL, $type_id, 1, 'MyEntity', $this->getEntityId());
CRM_Custom_Form_CustomData::buildQuickForm($this);
CRM_Custom_Form_CustomData::setDefaultValues($this);
}
}
// ...
public function postProcess() {
// ...
$params['type_id'] = $values['type_id'];
// Add the line below:
$params['custom'] = \CRM_Core_BAO_CustomField::postProcess($values, $this->getEntityId(), $this->getDefaultEntity());
$result = civicrm_api4('MyEntity', $action, ['values' => $params]);
// ...
12.3 Ensure the BAO uses standard functions¶
Open the BAO file for your entity and ensure that it doesn't contain a create
or add
function, as those would interfere
with automatic handling of custom data.
12.4 Add the custom fields to the template¶
Open templates/CRM/Myentity/Form/MyEntity,tpl
and add the following lines:
{* ... *}
</div>
{* Add the line below: *}
{include file="CRM/common/customDataBlock.tpl" customDataType='MyEntity' entityID=$id}
<div class="crm-submit-buttons">
{* ... *}
{* At the bottom of the file add the following lines: *}
{literal}
<script type="text/javascript">
CRM.$(function($) {
function updateCustomData() {
var subType = '{/literal}{$type_id}{literal}';
if ($('#type_id').length) {
subType = $('#type_id').val();
}
CRM.buildCustomData('MyEntity', subType, false, false, false, false, false, {/literal}{$cid}{literal});
}
if ($('#type_id').length) {
$('#type_id').on('change', updateCustomData);
}
updateCustomData();
});
</script>
{/literal}
The javascript code above loads the custom data when the type of our entity is changed.
13. Add a custom permission for our entity¶
When we want to have a custom permission for our entity we have to declare the permission first, we do that with the hook_civicrm_permission.
Open myentity.php
and the following code:
/**
* Implementation of hook_civicrm_permission
* @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_permission/
*/
function myentity_civicrm_permission(&$permissions) {
$permissions['manage my entity'] = E::ts('CiviCRM My Entity: Manage my entity');
}
Change the function myentity_civicrm_tabset
in myentity.php
from:
function myentity_civicrm_tabset($path, &$tabs, $context) {
if ($path === 'civicrm/contact/view') {
to:
function myentity_civicrm_tabset($path, &$tabs, $context) {
if ($path === 'civicrm/contact/view' && CRM_Core_Permission::check('manage my entity')) {
Change the function myentity_civicrm_navigationMenu
in myentity.php
from:
function myentity_civicrm_navigationMenu(&$menu) {
_myentity_civix_insert_navigation_menu($menu, 'Search', array(
'label' => E::ts('Search My Entities'),
'name' => 'search_my_entity',
'url' => 'civicrm/myentity/search',
'permission' => 'access CiviCRM', // This line is changed
'operator' => 'OR',
'separator' => 0,
));
_myentity_civix_navigationMenu($menu);
}
to:
function myentity_civicrm_navigationMenu(&$menu) {
_myentity_civix_insert_navigation_menu($menu, 'Search', array(
'label' => E::ts('Search My Entities'),
'name' => 'search_my_entity',
'url' => 'civicrm/myentity/search',
'permission' => 'manage my entity', // This line is changed
'operator' => 'OR',
'separator' => 0,
));
_myentity_civix_navigationMenu($menu);
}
Open xml/Menu/myentity.xml
and change all <access_arguments>access CiviCRM</access_arguments>
into <access_arguments>manage my entity</access_arguments>
.
Open the APIv4 file and add a permissions()
function, following this example from CiviGrant:
public static function permissions() {
return [
'get' => [
'access CiviGrant',
],
'delete' => [
'delete in CiviGrant',
],
'create' => [
'edit grants',
],
'update' => [
'edit grants',
],
];
}
Note
Unless otherwise set, the APIv4 permissions will default to 'administer CiviCRM'
.
See also¶
- Civix
- Schema Definition for an explanation of the XML file
- hook_civicrm_tabset for adding a new tab on the contact summary screen.
- hook_civicrm_permission
- The example extension
- Entity Construction Kit extension which provides a user interface for creating an entity.