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.
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 --api-version=3,4 MyEntity
This will add the following files:
api/v3/MyEntity.php
the api v3 fileCivi\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
should look like this:
<?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¶
In this step we will generate the SQL, DAO and BAO files. Those will be generated for you by Civix. we only need to change the SQL file afterwards.
Run the following command to generate the SQL and DAO files.
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.
7. Add a form¶
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. Add a search (legacy style custom search)¶
Note that the preferred way to add search capability is via search kit
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.
8.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.
9. 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',
);
}
}
10. Add Type ID field¶
In this step we are going to add functionality to have certain types of our entity. We will also make it possible to attach custom fields to our entity.
Open schema/CRM/Myentity/MyEntity.xml
and add the field for 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;
Open CRM/Myentity/Upgrader.php
and add the following to the install and uninstall functions:
<?php
use CRM_Myentity_ExtensionUtil as E;
/**
* Collection of upgrade steps.
*/
class CRM_Myentity_Upgrader extends CRM_Myentity_Upgrader_Base {
public function install() {
// Create the my_entity_type option group
$typeOptionGroupId = civicrm_api3('OptionGroup', 'create', ['name' => 'my_entity_type', 'title' => E::ts('My Entity Type')]);
$typeOptionGroupId = $typeOptionGroupId['id'];
civicrm_api3('OptionValue', 'create', ['value' => 1, 'is_default' => '1', 'name' => 'General', 'label' => E::ts('General'), 'option_group_id' => $typeOptionGroupId]);
}
public function uninstall() {
try {
$optionGroupId = civicrm_api3('OptionGroup', 'getvalue', ['return' => 'id', 'name' => 'my_entity_type']);
$optionValues = civicrm_api3('OptionValue', 'get', ['option_group_id' => $optionGroupId, 'options' => ['limit' => 0]]);
foreach ($optionValues['values'] as $optionValue) {
civicrm_api3('OptionValue', 'delete', ['id' => $optionValue['id']]);
}
civicrm_api3('OptionGroup', 'delete', ['id' => $optionGroupId]);
} catch (\CiviCRM_API3_Exception $ex) {
// Ignore exception.
}
}
}
This will create the option group for My Entity Type and it will add one option: General.
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.
11. Attaching Custom Data functionality¶
In this step we are going to attach the custom data functionality to our entity making it possible to create custom fields for our entity.
11.1 Making our entity available for custom data¶
We will make our entity available for custom data in the installer function of the upgrader class.
What is needed is that we add our entity to the custom group cg_extend_objects
:
* Name: the database table name of our entity
* Value: the name of our entity
* Description: a callback to return the subtypes.
We will also remove our entity upon uninstallation.
Open CRM/Myentity/Upgrader.php
and add the following to the install and uninstall function:
public function install() {
civicrm_api3('OptionValue', 'create', [
'option_group_id' => "cg_extend_objects",
'label' => E::ts('My Entity'),
'value' => 'MyEntity',
'name' => 'civicrm_my_entity',
'description' => 'CRM_Myentity_PseudoConstant::myEntityType;',
]);
// ....
}
public function uninstall() {
// ...
// Remove all custom data attached to our entity.
try {
$customGroups = civicrm_api3('CustomGroup', 'get', [
'extends' => 'MyEntity',
'options' => ['limit' => 0],
]);
foreach($customGroups['values'] as $customGroup) {
$customFields = civicrm_api3('CustomField', 'get', [
'custom_group_id' => $customGroup['id'],
'options' => ['limit' => 0],
]);
foreach($customFields['values'] as $customField) {
civicrm_api3('CustomField', 'delete', ['id' => $customField['id']]);
}
civicrm_api3('CustomGroup', 'delete', ['id' => $customGroup['id']]);
}
// Remove our entity from the cg_extend_objects option group.
$cgExtendOptionId = civicrm_api3('OptionValue', 'getvalue', [
'option_group_id' => "cg_extend_objects",
'value' => 'MyEntity',
'return' => 'id',
]);
civicrm_api3('OptionValue', 'delete', ['id' => $cgExtendOptionId]);
} catch (\CiviCRM_API3_Exception $ex) {
// Ignore exception
}
}
11.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]);
// ...
11.3 Add a create function to the BAO¶
Now we have to create our own create
function in the BAO in this function we add the handling of the custom fields.
Open CRM/Myentity/BAO/MyEntity.php
and make sure it looks as follows:
<?php
use CRM_Myentity_ExtensionUtil as E;
class CRM_Myentity_BAO_MyEntity extends CRM_Myentity_DAO_MyEntity {
/**
* Create a new MyEntity based on array-data
*
* @param array $params key-value pairs
* @return CRM_Myentity_DAO_MyEntity|NULL
*/
public static function create($params) {
$className = 'CRM_Myentity_DAO_MyEntity';
$entityName = 'MyEntity';
$hook = empty($params['id']) ? 'create' : 'edit';
CRM_Utils_Hook::pre($hook, $entityName, CRM_Utils_Array::value('id', $params), $params);
$instance = new $className();
$instance->copyValues($params);
$instance->save();
if (!empty($params['custom']) &&
is_array($params['custom'])
) {
CRM_Core_BAO_CustomValueTable::store($params['custom'], self::$_tableName, $instance->id);
}
CRM_Utils_Hook::post($hook, $entityName, $instance->id, $instance);
return $instance;
}
}
11.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.
12. 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>
.
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