civicrm_entity.module in CiviCRM Entity 8.3
Same filename and directory in other branches
Module file for the CiviCRM Entity module.
File
civicrm_entity.moduleView source
<?php
/**
* @file
* Module file for the CiviCRM Entity module.
*/
use Drupal\civicrm_entity\CivicrmEntityAccessHandler;
use Drupal\civicrm_entity\CivicrmEntityListBuilder;
use Drupal\civicrm_entity\CiviCrmEntityViewBuilder;
use Drupal\civicrm_entity\CivicrmEntityViewsData;
use Drupal\civicrm_entity\CiviEntityStorage;
use Drupal\civicrm_entity\Entity\CivicrmEntity;
use Drupal\civicrm_entity\Entity\Sql\CivicrmEntityStorageSchema;
use Drupal\civicrm_entity\Form\CivicrmEntityForm;
use Drupal\civicrm_entity\Routing\CiviCrmEntityRouteProvider;
use Drupal\civicrm_entity\SupportedEntities;
use Drupal\Core\Entity\ContentEntityDeleteForm;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\FieldConfigInterface;
use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\query\QueryPluginBase;
/**
* Implements hook_theme().
*/
function civicrm_entity_theme() {
return [
'civicrm_entity_entity_form' => [
'render element' => 'form',
],
'civicrm_entity' => [
'render element' => 'elements',
],
];
}
/**
* Implements hook_entity_type_build().
*
* Populates supported CiviCRM Entity definitions.
*/
function civicrm_entity_entity_type_build(array &$entity_types) {
$logger = \Drupal::logger('civicrm-entity');
$supported_entities = SupportedEntities::getInfo();
$config = \Drupal::config('civicrm_entity.settings');
$enabled_entity_types = $config
->get('enabled_entity_types') ?: [];
foreach ($supported_entities as $entity_type_id => $civicrm_entity_info) {
$clean_entity_type_id = str_replace('_', '-', $entity_type_id);
$civicrm_entity_name = $civicrm_entity_info['civicrm entity name'];
if (empty($civicrm_entity_info['label property'])) {
$logger
->debug(sprintf('Missing label property: %s', $entity_type_id));
continue;
}
$entity_type_info = [
'provider' => 'civicrm_entity',
'class' => CivicrmEntity::class,
'originalClass' => CivicrmEntity::class,
'id' => $entity_type_id,
'component' => $civicrm_entity_info['component'] ?? NULL,
'civicrm_entity' => $civicrm_entity_name,
'civicrm_entity_ui_exposed' => in_array($entity_type_id, $enabled_entity_types),
'label' => new TranslatableMarkup('CiviCRM :name', [
':name' => $civicrm_entity_info['civicrm entity label'],
]),
// @todo add label_singular
// @todo add label_plural
// @todo add label_count
'entity_keys' => [
'id' => 'id',
'label' => $civicrm_entity_info['label property'],
],
'base_table' => $entity_type_id,
'admin_permission' => 'administer civicrm entity',
'permission_granularity' => 'entity_type',
'handlers' => [
'storage' => CiviEntityStorage::class,
'access' => CivicrmEntityAccessHandler::class,
'views_data' => CivicrmEntityViewsData::class,
'storage_schema' => CivicrmEntityStorageSchema::class,
],
];
if (in_array($entity_type_id, $enabled_entity_types)) {
$entity_type_info = array_merge_recursive($entity_type_info, [
'handlers' => [
'list_builder' => CivicrmEntityListBuilder::class,
'view_builder' => CiviCrmEntityViewBuilder::class,
'route_provider' => [
'default' => CiviCrmEntityRouteProvider::class,
],
'form' => [
'default' => CivicrmEntityForm::class,
'add' => CivicrmEntityForm::class,
'edit' => CivicrmEntityForm::class,
'delete' => ContentEntityDeleteForm::class,
],
],
// Generate route paths.
'links' => [
'canonical' => sprintf('/%s/{%s}', $clean_entity_type_id, $entity_type_id),
'delete-form' => sprintf('/%s/{%s}/delete', $clean_entity_type_id, $entity_type_id),
'edit-form' => sprintf('/%s/{%s}/edit', $clean_entity_type_id, $entity_type_id),
'add-form' => sprintf('/%s/add', $clean_entity_type_id, $entity_type_id),
'collection' => sprintf('/admin/structure/civicrm-entity/%s', $clean_entity_type_id),
],
'field_ui_base_route' => "entity.{$entity_type_id}.collection",
]);
}
// If this entity has bundle support, we define the bundle field as "bundle"
// and will use the "bundle property" as the field to fetch field options
// from CiviCRM with.
//
// @see civicrm_entity_entity_bundle_info()
// @see \Drupal\civicrm_entity\Entity\CivicrmEntity::baseFieldDefinitions()
if (!empty($civicrm_entity_info['bundle property'])) {
$entity_type_info['entity_keys']['bundle'] = 'bundle';
$entity_type_info['civicrm_bundle_property'] = $civicrm_entity_info['bundle property'];
if (isset($entity_type_info['links'])) {
// For entities with bundles that are exposed, add the `bundle` key to
// the add-form route. In CiviCrmEntityRouteProvider::getAddFormRoute
// we default the value, so that it isn't actually required in the URL.
$entity_type_info['links']['add-form'] = sprintf('%s/{%s}', $entity_type_info['links']['add-form'], $entity_type_info['entity_keys']['bundle']);
}
}
$entity_types[$entity_type_id] = new ContentEntityType($entity_type_info);
}
}
/**
* Implements hook_entity_bundle_info().
*/
function civicrm_entity_entity_bundle_info() {
$transliteration = \Drupal::transliteration();
/** @var \Drupal\civicrm_entity\CiviCrmApiInterface $civicrm_api */
$civicrm_api = \Drupal::service('civicrm_entity.api');
$bundles = [];
$entity_types_with_bundles = array_filter(SupportedEntities::getInfo(), static function (array $civicrm_entity_info) {
return !empty($civicrm_entity_info['bundle property']);
});
foreach ($entity_types_with_bundles as $entity_type_id => $civicrm_entity_info) {
// We keep a bundle that is the same as the entity type ID. This allows us
// to create fields as if this entity has no bundles.
$bundles[$entity_type_id] = [
$entity_type_id => [
'label' => $civicrm_entity_info['civicrm entity label'],
],
];
$options = $civicrm_api
->getOptions($civicrm_entity_info['civicrm entity name'], $civicrm_entity_info['bundle property']);
foreach ($options as $option) {
$machine_name = SupportedEntities::optionToMachineName($option, $transliteration);
$bundles[$entity_type_id][$machine_name]['label'] = $option;
}
}
return $bundles;
}
/**
* Implements hook_entity_bundle_field_info().
*
* This ensures CiviCRM Entity entity types have their field config instances
* across all bundles. It's a copy of the Field module's logic, but clones
* field config definitions.
*
* @see field_entity_bundle_field_info().
*/
function civicrm_entity_entity_bundle_field_info(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions) {
$result = [];
if ($entity_type
->get('civicrm_entity_ui_exposed') && $entity_type
->hasKey('bundle')) {
// Query by filtering on the ID as this is more efficient than filtering
// on the entity_type property directly.
$ids = \Drupal::entityQuery('field_config')
->condition('id', $entity_type
->id() . '.', 'STARTS_WITH')
->execute();
// Fetch all fields and key them by field name.
$field_configs = FieldConfig::loadMultiple($ids);
// Clone the field configs, so that we can modify them and change the
// target bundle type without manipulating the statically cached entries
// in the entity storage;
$cloned_field_configs = array_map(static function (FieldConfigInterface $field) use ($bundle) {
$cloned = clone $field;
$cloned
->set('bundle', $bundle);
return $cloned;
}, $field_configs);
foreach ($cloned_field_configs as $field_instance) {
$result[$field_instance
->getName()] = $field_instance;
}
}
return $result;
}
/**
* Implements hook_entity_view_display_alter().
*
* There is no way to handle this in the entity type's view builder.
*/
function civicrm_entity_entity_view_display_alter(EntityViewDisplayInterface $display, array $context) {
$entity_type = \Drupal::entityTypeManager()
->getDefinition($context['entity_type']);
assert($entity_type !== NULL);
if ($entity_type
->get('civicrm_entity') && $entity_type
->hasKey('bundle')) {
$entity_display_repository = \Drupal::service('entity_display.repository');
assert($entity_display_repository instanceof EntityDisplayRepositoryInterface);
$root_display = $entity_display_repository
->getViewDisplay($entity_type
->id(), $entity_type
->id());
$display
->set('content', $root_display
->get('content'));
$display
->set('hidden', $root_display
->get('hidden'));
if ($root_display instanceof LayoutBuilderEntityViewDisplay) {
$layout_builder_settings = $root_display
->getThirdPartySettings('layout_builder');
foreach ($layout_builder_settings as $setting_key => $setting) {
$display
->setThirdPartySetting('layout_builder', $setting_key, $setting);
}
}
}
}
/**
* Implements callback_allowed_values_function().
*
* Provides the pseudoconstant values for CiviCRM entity fields.
*
* @param \Drupal\Core\Field\FieldStorageDefinitionInterface $definition
* The field storage definition.
* @param \Drupal\Core\Entity\FieldableEntityInterface|NULL $entity
* The entity.
* @param bool $cacheable
* If the options are cacheable.
*
* @return array
* The array of field options.
*/
function civicrm_entity_pseudoconstant_options(FieldStorageDefinitionInterface $definition, FieldableEntityInterface $entity = NULL, &$cacheable = NULL) {
/** @var \Drupal\civicrm_entity\CiviCrmApiInterface $civicrm_api */
$civicrm_api = \Drupal::service('civicrm_entity.api');
$entity_type = \Drupal::entityTypeManager()
->getDefinition($definition
->getTargetEntityTypeId());
$options = $civicrm_api
->getOptions($entity_type
->get('civicrm_entity'), $definition
->getName());
return $options;
}
/**
* Implements hook_preprocess().
*/
function template_preprocess_civicrm_entity(&$variables) {
// Add fields as content to template.
$variables += [
'content' => [],
];
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
// Add the view_mode to the template.
$variables['view_mode'] = $variables['elements']['#view_mode'];
// Add the bundle to the template.
$variables['entity_type'] = _civicrm_entity_get_entity_type_from_elements($variables['elements']);
}
/**
* Implements hook_theme_suggestions_HOOK_alter().
*/
function civicrm_entity_theme_suggestions_civicrm_entity_alter(array &$suggestions, array $variables) {
$view_mode = $variables['elements']['#view_mode'];
$hook = $variables['theme_hook_original'];
// Add a suggestion based on the entity type.
if ($entity_type = _civicrm_entity_get_entity_type_from_elements($variables['elements'])) {
$suggestions[] = $hook . '__' . $entity_type;
// Add a suggestion based on the view mode.
$suggestions[] = $hook . '__' . $entity_type . '__' . $view_mode;
}
}
/**
* Helper to find the entity type from $variables['elements'].
*/
function _civicrm_entity_get_entity_type_from_elements($elements) {
if (isset($elements['#entity_type'])) {
return $elements['#entity_type'];
}
// Find the CivicrmEntity from elements if #entity_type is not set.
foreach ($elements as $element) {
if ($element instanceof CivicrmEntity) {
/** @var CivicrmEntity $element */
return $element
->getEntityTypeId();
}
}
return NULL;
}
/**
* For stashing deleted entities until we need them later.
*
* @todo Convert hooks to a service and keep this in a member variable
*
* @param string $objectName
* The CiviCRM entity type.
* @param int $id
* The id.
* @param \Drupal\Core\Entity\EntityInterface|null|string $entity
* The entity.
*
* @return void|\Drupal\Core\Entity\EntityInterface
*/
function _civicrm_entity_stash($objectName, $id, $entity = NULL) {
$cache =& drupal_static(__FUNCTION__, []);
if (empty($entity)) {
return isset($cache[$objectName][$id]) ? $cache[$objectName][$id] : NULL;
}
elseif ($entity === 'clear') {
unset($cache[$objectName][$id]);
}
else {
$cache[$objectName][$id] = $entity;
}
}
/**
* Implements hook_civicrm_pre().
*/
function civicrm_entity_civicrm_pre($op, $objectName, $id, &$params) {
if (\Drupal::config('civicrm_entity.settings')
->get('disable_hooks')) {
return;
}
$operations = [
'create',
'edit',
'delete',
'restore',
];
if (!in_array($op, $operations)) {
return;
}
$entityType = SupportedEntities::getEntityType($objectName);
// Check valid entity type.
if (!$entityType) {
return;
}
/** @var \Drupal\civicrm_entity\CiviEntityStorage $storage */
$storage = \Drupal::entityTypeManager()
->getStorage($entityType);
if ($op == 'create') {
$entity = $storage
->create($params);
}
elseif (empty($id)) {
// Sometimes 'delete' is called with an $id of NULL, but we can't really do
// anything with that in this context, so return.
return;
}
else {
// Special handling for EntityTag objects.
if ($objectName == 'EntityTag') {
$id = $storage
->getEntityTagEntityId($params[0][0], $params[1]);
}
$entity = $storage
->load($id);
}
if (!$entity) {
return;
}
if ($entity
->id()) {
$entity->original = $storage
->loadUnchanged($entity
->id());
}
switch ($op) {
case 'create':
$storage
->civiPreSave($entity);
break;
case 'delete':
$storage
->civiPreDelete($entity);
_civicrm_entity_stash($objectName, $id, $entity);
break;
case 'restore':
$storage
->civiPreSave($entity);
break;
case 'edit':
$storage
->civiPreSave($entity);
break;
}
}
/**
* Implements hook_civicrm_post().
*/
function civicrm_entity_civicrm_post($op, $objectName, $objectId, &$objectRef) {
if (\Drupal::config('civicrm_entity.settings')
->get('disable_hooks')) {
return;
}
$operations = [
'create',
'edit',
'delete',
'restore',
];
if (!in_array($op, $operations)) {
return;
}
$entityType = SupportedEntities::getEntityType($objectName);
// Check valid entity type.
if (!$entityType) {
return;
}
/** @var \Drupal\civicrm_entity\CiviEntityStorage $storage */
$storage = \Drupal::entityTypeManager()
->getStorage($entityType);
// Fix because $objectId is not set for participant payments, possibly other
// entity types.
if (!$objectId) {
// If we cannot determine the id, bail.
if (empty($objectRef->id)) {
return;
}
$objectId = $objectRef->id;
}
if ($op == 'delete') {
$entity = _civicrm_entity_stash($objectName, $objectId);
}
else {
// Special handling for EntityTag objects.
if ($entityType == 'civicrm_entity_tag') {
foreach ($objectRef[0] as $entityTag) {
$object = new CRM_Core_BAO_EntityTag();
$object->entity_id = $entityTag;
$object->entity_table = 'civicrm_contact';
$object->tag_id = $objectId;
if ($object
->find(TRUE)) {
$entity = $storage
->load($object->id);
$entity->original = $storage
->loadUnchanged($entity
->id());
_civicrm_entity_post_invoke($op, $storage, $entity);
}
}
return;
}
if ($entity = $storage
->load($objectId)) {
$entity->original = $storage
->loadUnchanged($entity
->id());
}
}
if ($entity) {
_civicrm_entity_post_invoke($op, $storage, $entity);
}
_civicrm_entity_stash($objectName, $objectId, 'clear');
}
/**
* Invokes the post save hooks for a CiviCRM entity.
*
* @param string $op
* The operation being performed.
* @param \Drupal\civicrm_entity\CiviEntityStorage $storage
* The entity storage.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*/
function _civicrm_entity_post_invoke($op, $storage, $entity) {
switch ($op) {
case 'create':
$storage
->civiPostSave($entity, FALSE);
break;
case 'delete':
$storage
->civiPostDelete($entity);
break;
case 'restore':
$storage
->civiPostSave($entity, TRUE);
break;
case 'edit':
$storage
->civiPostSave($entity, TRUE);
break;
}
}
function civicrm_entity_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
\Drupal::service('civicrm')
->initialize();
$multilingual = \CRM_Core_I18n::isMultilingual();
if ($multilingual) {
global $dbLocale;
$columns = CRM_Core_I18n_SchemaStructure::columns();
$affectedColumns = [];
foreach ($columns as $table => $hash) {
foreach (array_keys($hash) as $column) {
$affectedColumns[] = "{$table}.{$column}";
}
}
foreach ($query->where as &$condition_group) {
foreach ($condition_group['conditions'] as &$condition) {
if (!is_object($condition['field'])) {
foreach ($affectedColumns as $aff_column) {
if (strpos($aff_column, $condition['field']) !== FALSE) {
$condition['field'] = str_replace($aff_column, $aff_column . $dbLocale, $condition['field']);
}
}
}
}
}
foreach ($query->fields as &$field) {
if (array_key_exists($field['table'], $columns) && array_key_exists($field['field'], $columns[$field['table']])) {
$field['field'] .= $dbLocale;
}
}
}
}
/**
* Implements hook_theme_registry_alter().
*/
function civicrm_entity_theme_registry_alter(&$theme_registry) {
$theme_registry['civicrm_entity']['preprocess functions'][] = 'field_group_build_entity_groups';
}
/**
* Implements hook_rebuild().
*
* This resets the field storage and entity type definitions for civicrm_entity
* according to the active definitions to avoid mismatches since the definitions
* are not necessary to be updated.
*/
function civicrm_entity_rebuild() {
/** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_repository */
$entity_last_installed_repository = \Drupal::service('entity.last_installed_schema.repository');
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
$entity_field_manager = \Drupal::service('entity_field.manager');
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = \Drupal::service('entity_type.manager');
$supported_entities = SupportedEntities::getInfo();
foreach (array_keys($supported_entities) as $entity_type_id) {
// Reset field storage definitions.
$field_storage_definitions = $entity_field_manager
->getFieldStorageDefinitions($entity_type_id);
$entity_last_installed_repository
->setLastInstalledFieldStorageDefinitions($entity_type_id, $field_storage_definitions);
// Reset entity type definition.
$definition = $entity_type_manager
->getDefinition($entity_type_id);
$entity_last_installed_repository
->setLastInstalledDefinition($definition);
}
}
/**
* Implements hook_rules_action_info_alter().
*/
function civicrm_entity_rules_action_info_alter(&$rules_actions) {
$rules_actions['civicrm_entity_user_create']['context']['format']
->setDescription(t('Format of the username. Use <a href="@url">Twig style</a> tokens for using the available data.', [
'@url' => 'https://www.drupal.org/docs/8/modules/typed-data-api-enhancements/typeddata-tokens',
]));
}