diff --git a/civicrm_entity.module b/civicrm_entity.module
index d70d553c..d05b2c06 100644
--- a/civicrm_entity.module
+++ b/civicrm_entity.module
@@ -5,36 +5,26 @@
* 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\Hook\EntityAlterHooks;
+use Drupal\civicrm_entity\Hook\EntityHooks;
+use Drupal\civicrm_entity\Hook\FormHooks;
+use Drupal\civicrm_entity\Hook\QueryHooks;
+use Drupal\civicrm_entity\Hook\RulesHooks;
use Drupal\civicrm_entity\SupportedEntities;
use Drupal\Core\Database\Query\AlterableInterface;
-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;
-use Drupal\Core\Database\Database;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Field\Entity\BaseFieldOverride;
+use Drupal\Core\Hook\Attribute\LegacyHook;
/**
* Implements hook_theme().
@@ -55,152 +45,17 @@ function civicrm_entity_theme() {
*
* Populates supported CiviCRM Entity definitions.
*/
+#[LegacyHook]
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') ?: [];
- $enable_links_per_type = $config->get('enable_links_per_type') ?: [];
- 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' => $civicrm_entity_info['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 (!empty($enable_links_per_type) && in_array($entity_type_id, array_keys($enable_links_per_type))) {
- $enable_links = array_filter($enable_links_per_type[$entity_type_id]['values']);
-
- if (!in_array('view', $enable_links)) {
- unset($entity_type_info['links']['canonical']);
- }
-
- if (!in_array('delete', $enable_links)) {
- unset($entity_type_info['links']['delete-form']);
- }
-
- if (!in_array('edit', $enable_links)) {
- unset($entity_type_info['links']['edit-form']);
- }
-
- if (!in_array('add', $enable_links)) {
- unset($entity_type_info['links']['add-form']);
- }
- }
-
- if ($config->get('disable_links')) {
- unset(
- $entity_type_info['links']['canonical'],
- $entity_type_info['links']['delete-form'],
- $entity_type_info['links']['edit-form'],
- $entity_type_info['links']['add-form'],
- );
- }
- }
-
- // 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']['add-form'])) {
- // 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);
- }
+ \Drupal::service(EntityHooks::class)->entityTypeBuild($entity_types);
}
/**
* Implements hook_entity_bundle_info().
*/
+#[LegacyHook]
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;
+ return \Drupal::service(EntityHooks::class)->entityBundleInfo();
}
/**
@@ -212,41 +67,9 @@ function civicrm_entity_entity_bundle_info() {
*
* @see field_entity_bundle_field_info()
*/
+#[LegacyHook]
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')
- ->accessCheck(FALSE)
- ->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;
- }
- }
- // Ensure all fields have a definition.
- if ($entity_type->get('civicrm_entity_ui_exposed') && $entity_type->hasKey('bundle')) {
- foreach ($base_field_definitions as $field_name => $definition) {
- if (isset($result[$field_name]) || isset($definition) || empty($bundle)) {
- continue;
- }
- $field = BaseFieldOverride::createFromBaseFieldDefinition($base_field_definitions[$field_name], $bundle);
- $result[$field_name] = $field;
- }
- }
- return $result;
+ return \Drupal::service(EntityHooks::class)->entityBundleFieldInfo($entity_type, $bundle, $base_field_definitions);
}
/**
@@ -254,87 +77,25 @@ function civicrm_entity_entity_bundle_field_info(EntityTypeInterface $entity_typ
*
* There is no way to handle this in the entity type's view builder.
*/
+#[LegacyHook]
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);
- $entity_view_mode_ids = array_keys($entity_display_repository->getViewModeOptions($entity_type->id()));
- $view_mode = !empty($context['view_mode']) && in_array($context['view_mode'], $entity_view_mode_ids) ? $context['view_mode'] : $entity_display_repository::DEFAULT_DISPLAY_MODE;
- $root_display = $entity_display_repository->getViewDisplay(
- $entity_type->id(),
- $entity_type->id(),
- $view_mode
- );
- $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);
- }
- }
- $ds_settings = $root_display->getThirdPartySettings('ds');
- if (!empty($ds_settings) && is_array($ds_settings)) {
- foreach ($ds_settings as $setting_key => $setting) {
- $display->setThirdPartySetting('ds', $setting_key, $setting);
- }
- }
- }
+ \Drupal::service(EntityAlterHooks::class)->entityViewDisplayAlter($display, $context);
}
/**
* Implements hook_entity_view_alter().
*/
+#[LegacyHook]
function civicrm_entity_entity_view_alter(array &$build, EntityInterface $entity, EntityDisplayInterface $display) {
- $entity_type = $entity->getEntityType();
- if ($entity_type->get('civicrm_entity') && $entity_type->hasKey('bundle') && \Drupal::moduleHandler()->moduleExists('field_group')) {
- $entity_display_repository = \Drupal::service('entity_display.repository');
- $entity_view_mode_ids = array_keys($entity_display_repository->getViewModeOptions($entity_type->id()));
-
- $context = [
- 'entity_type' => $display->getTargetEntityTypeId(),
- 'bundle' => $entity_type->id(),
- 'entity' => $entity,
- 'display_context' => 'view',
- 'mode' => in_array($display->getMode(), $entity_view_mode_ids) ? $display->getMode() : $entity_display_repository::DEFAULT_DISPLAY_MODE,
- ];
-
- field_group_attach_groups($build, $context);
- }
+ \Drupal::service(EntityAlterHooks::class)->entityViewAlter($build, $entity, $display);
}
/**
* Implements hook_form_alter().
*/
+#[LegacyHook]
function civicrm_entity_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
- $form_object = $form_state->getFormObject();
- if ($form_object instanceof CivicrmEntityForm) {
-
- /**
- * @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
- */
- $storage = $form_state->getStorage();
- if (!empty($storage['form_display'])) {
- $form_display = $storage['form_display'];
- $entity = $form_object->getEntity();
-
- if ($entity->getEntityType()->hasKey('bundle') && \Drupal::moduleHandler()->moduleExists('field_group')) {
- $context = [
- 'entity_type' => $entity->getEntityTypeId(),
- 'bundle' => $entity->getEntityTypeId(),
- 'entity' => $entity,
- 'context' => 'form',
- 'display_context' => 'form',
- 'mode' => $form_display->getMode(),
- ];
-
- field_group_attach_groups($form, $context);
- }
- }
- }
+ \Drupal::service(FormHooks::class)->formAlter($form, $form_state, $form_id);
}
/**
@@ -636,67 +397,9 @@ function _civicrm_entity_post_invoke($op, CiviEntityStorage $storage, EntityInte
/**
* Implements hook_views_query_alter().
*/
+#[LegacyHook]
function civicrm_entity_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
- // Provide fully qualified table name in all Views queries.
- // If CiviCRM tables in a separate database.
- $civicrm_connection_name = drupal_valid_test_ua() ? 'civicrm_test' : 'civicrm';
- $civicrm_database_info = Database::getConnectionInfo($civicrm_connection_name);
- if (isset($civicrm_database_info['default']) && method_exists($query, "getTableQueue")) {
- $civicrm_connection = Database::getConnection('default', $civicrm_connection_name);
- $table_queue =& $query->getTableQueue();
- foreach ($table_queue as $alias => &$table_info) {
- if (!empty($table_info['table']) && ((strpos($table_info['table'], 'civicrm_') === 0 && strpos($table_info['table'], '.') === FALSE && strpos($table_info['table'], '__') === FALSE) || strpos($table_info['table'], 'civicrm_value_') === 0)) {
- $table_info['table'] = $civicrm_connection->getFullQualifiedTableName($table_info['table']);
- }
- if (!empty($table_info['join']->table) && ((strpos($table_info['join']->table, 'civicrm_') === 0 && strpos($table_info['join']->table, '.') === FALSE && strpos($table_info['join']->table, '__') === FALSE) || strpos($table_info['join']->table, 'civicrm_value_') === 0)) {
- $table_info['join']->table = $civicrm_connection->getFullQualifiedTableName($table_info['join']->table);
- }
- }
- }
-
- \Drupal::service('civicrm')->initialize();
- $multilingual = \CRM_Core_I18n::isMultilingual();
-
- if ($multilingual) {
- // @codingStandardsIgnoreStart
- global $dbLocale;
- // @codingStandardsIgnoreEnd
- $columns = CRM_Core_I18n_SchemaStructure::columns();
- $affectedColumns = [];
- foreach ($columns as $table => $hash) {
- foreach (array_keys($hash) as $column) {
- $affectedColumns[] = "{$table}.{$column}";
- }
- }
- $class = get_class($query);
- if ($class == 'Drupal\search_api\Plugin\views\query\SearchApiQuery' && method_exists($query, "getWhere")) {
- $where = $query->getWhere();
- }
- elseif (isset($query->where)) {
- $where = $query->where;
- }
- if (!empty($where)) {
- foreach ($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']);
- }
- }
- }
- }
- }
- }
-
- if (!empty($query->fields)) {
- foreach ($query->fields as &$field) {
- if (array_key_exists($field['table'], $columns) && array_key_exists($field['field'], $columns[$field['table']])) {
- $field['field'] .= $dbLocale;
- }
- }
- }
- }
+ \Drupal::service(QueryHooks::class)->viewsQueryAlter($view, $query);
}
/**
@@ -713,53 +416,17 @@ function civicrm_entity_theme_registry_alter(&$theme_registry) {
* according to the active definitions to avoid mismatches since the definitions
* are not necessary to be updated.
*/
+#[LegacyHook]
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);
- }
+ \Drupal::service(EntityHooks::class)->rebuild();
}
/**
* Implements hook_rules_action_info_alter().
*/
+#[LegacyHook]
function civicrm_entity_rules_action_info_alter(&$rules_actions) {
- $definitions = \Drupal::service('plugin.manager.typed_data_filter')->getDefinitions();
- $filters = "";
- foreach ($definitions as $key) {
- if ($key['provider'] == 'civicrm_entity') {
- $filters .= ($filters == '' ? '' : ', ') . $key['id'];
- }
- }
- if (array_key_exists('format', $rules_actions['civicrm_entity_user_create']['context_definitions'])) {
- // Drupal 9 use 'context_definitions' instead of 'context'.
- $rules_actions['civicrm_entity_user_create']['context_definitions']['format']->setDescription(t('Format of the username. Use Twig style tokens for using the available data.
Civicrm Entity filter available : @filters.',
- [
- '@url' => 'https://www.drupal.org/docs/8/modules/typed-data-api-enhancements/typeddata-tokens',
- '@filters' => $filters,
- ]));
- }
- else {
- $rules_actions['civicrm_entity_user_create']['context']['format']->setDescription(t('Format of the username. Use Twig style tokens for using the available data.
Civicrm Entity filter available : @filters.',
- [
- '@url' => 'https://www.drupal.org/docs/8/modules/typed-data-api-enhancements/typeddata-tokens',
- '@filters' => $filters,
- ]));
- }
+ \Drupal::service(RulesHooks::class)->rulesActionInfoAlter($rules_actions);
}
/**
@@ -818,31 +485,15 @@ function civicrm_entity_civicrm_merge($mode, &$sqls, $mainId = NULL, $otherId =
/**
* Implements hook_query_TAG_alter().
*/
+#[LegacyHook]
function civicrm_entity_query_pathauto_bulk_update_alter(AlterableInterface $query) {
- $tables = &$query->getTables();
-
- if (strpos($tables['base_table']['table'], 'civicrm_') !== FALSE) {
- $civicrm_connection_name = drupal_valid_test_ua() ? 'civicrm_test' : 'civicrm';
- $civicrm_database_info = Database::getConnectionInfo($civicrm_connection_name);
- if (isset($civicrm_database_info['default'])) {
- $connection = Database::getConnection('default', $civicrm_connection_name);
- $tables['base_table']['table'] = $connection->getFullQualifiedTableName($tables['base_table']['table']);
- }
- }
+ \Drupal::service(QueryHooks::class)->queryPathautoBulkUpdateAlter($query);
}
/**
* Implements hook_query_TAG_alter().
*/
+#[LegacyHook]
function civicrm_entity_query_pathauto_bulk_delete_alter(AlterableInterface $query) {
- $tables = &$query->getTables();
-
- if (strpos($tables['base_table']['table'], 'civicrm_') !== FALSE) {
- $civicrm_connection_name = drupal_valid_test_ua() ? 'civicrm_test' : 'civicrm';
- $civicrm_database_info = Database::getConnectionInfo($civicrm_connection_name);
- if (isset($civicrm_database_info['default'])) {
- $connection = Database::getConnection('default', $civicrm_connection_name);
- $tables['base_table']['table'] = $connection->getFullQualifiedTableName($tables['base_table']['table']);
- }
- }
+ \Drupal::service(QueryHooks::class)->queryPathautoBulkDeleteAlter($query);
}
diff --git a/civicrm_entity.services.yml b/civicrm_entity.services.yml
index ceaadf7c..09423fc2 100644
--- a/civicrm_entity.services.yml
+++ b/civicrm_entity.services.yml
@@ -3,6 +3,8 @@ services:
class: 'Drupal\civicrm_entity\CiviCrmApi'
arguments: ['@civicrm']
+ Drupal\civicrm_entity\CiviCrmApiInterface: '@civicrm_entity.api'
+
civicrm_entity.field_definition_provider:
class: Drupal\civicrm_entity\Entity\FieldDefinitionProvider
@@ -37,3 +39,23 @@ services:
class: Drupal\civicrm_entity\EventSubscriber\SearchApiSubscriber
tags:
- { name: event_subscriber }
+
+ Drupal\civicrm_entity\Hook\EntityAlterHooks:
+ class: Drupal\civicrm_entity\Hook\EntityAlterHooks
+ autowire: true
+
+ Drupal\civicrm_entity\Hook\EntityHooks:
+ class: Drupal\civicrm_entity\Hook\EntityHooks
+ autowire: true
+
+ Drupal\civicrm_entity\Hook\FormHooks:
+ class: Drupal\civicrm_entity\Hook\FormHooks
+ autowire: true
+
+ Drupal\civicrm_entity\Hook\QueryHooks:
+ class: Drupal\civicrm_entity\Hook\QueryHooks
+ autowire: true
+
+ Drupal\civicrm_entity\Hook\RulesHooks:
+ class: Drupal\civicrm_entity\Hook\RulesHooks
+ autowire: true
diff --git a/src/Hook/EntityAlterHooks.php b/src/Hook/EntityAlterHooks.php
new file mode 100644
index 00000000..0f3034f4
--- /dev/null
+++ b/src/Hook/EntityAlterHooks.php
@@ -0,0 +1,88 @@
+entityTypeManager->getDefinition($context['entity_type']);
+ assert($entity_type !== NULL);
+ if ($entity_type->get('civicrm_entity') && $entity_type->hasKey('bundle')) {
+ $entity_display_repository = $this->entityDisplayRepository;
+ assert($entity_display_repository instanceof EntityDisplayRepositoryInterface);
+ $entity_view_mode_ids = array_keys($entity_display_repository->getViewModeOptions($entity_type->id()));
+ $view_mode = !empty($context['view_mode']) && in_array($context['view_mode'], $entity_view_mode_ids) ? $context['view_mode'] : $entity_display_repository::DEFAULT_DISPLAY_MODE;
+ $root_display = $entity_display_repository->getViewDisplay(
+ $entity_type->id(),
+ $entity_type->id(),
+ $view_mode
+ );
+ $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);
+ }
+ }
+ $ds_settings = $root_display->getThirdPartySettings('ds');
+ if (!empty($ds_settings) && is_array($ds_settings)) {
+ foreach ($ds_settings as $setting_key => $setting) {
+ $display->setThirdPartySetting('ds', $setting_key, $setting);
+ }
+ }
+ }
+ }
+
+ /**
+ * Implements hook_entity_view_alter().
+ */
+ #[Hook('entity_view_alter')]
+ public function entityViewAlter(array &$build, EntityInterface $entity, EntityDisplayInterface $display): void {
+ $entity_type = $entity->getEntityType();
+ if ($entity_type->get('civicrm_entity') && $entity_type->hasKey('bundle') && $this->moduleHandler->moduleExists('field_group')) {
+ $entity_display_repository = $this->entityDisplayRepository;
+ $entity_view_mode_ids = array_keys($entity_display_repository->getViewModeOptions($entity_type->id()));
+
+ $context = [
+ 'entity_type' => $display->getTargetEntityTypeId(),
+ 'bundle' => $entity_type->id(),
+ 'entity' => $entity,
+ 'display_context' => 'view',
+ 'mode' => in_array($display->getMode(), $entity_view_mode_ids) ? $display->getMode() : $entity_display_repository::DEFAULT_DISPLAY_MODE,
+ ];
+
+ field_group_attach_groups($build, $context);
+ }
+ }
+
+}
diff --git a/src/Hook/EntityHooks.php b/src/Hook/EntityHooks.php
new file mode 100644
index 00000000..06e34e9c
--- /dev/null
+++ b/src/Hook/EntityHooks.php
@@ -0,0 +1,279 @@
+logger = $loggerChannelFactory->get('civicrm_entity');
+ }
+
+ /**
+ * Implements hook_entity_type_build().
+ *
+ * Populates supported CiviCRM Entity definitions.
+ */
+ #[Hook('entity_type_build')]
+ public function entityTypeBuild(array &$entity_types): void {
+ $supported_entities = SupportedEntities::getInfo();
+ $config = \Drupal::config('civicrm_entity.settings');
+ $enabled_entity_types = $config->get('enabled_entity_types') ?: [];
+ $enable_links_per_type = $config->get('enable_links_per_type') ?: [];
+ 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'])) {
+ $this->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' => $civicrm_entity_info['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 (!empty($enable_links_per_type) && in_array($entity_type_id, array_keys($enable_links_per_type))) {
+ $enable_links = array_filter($enable_links_per_type[$entity_type_id]['values']);
+
+ if (!in_array('view', $enable_links)) {
+ unset($entity_type_info['links']['canonical']);
+ }
+
+ if (!in_array('delete', $enable_links)) {
+ unset($entity_type_info['links']['delete-form']);
+ }
+
+ if (!in_array('edit', $enable_links)) {
+ unset($entity_type_info['links']['edit-form']);
+ }
+
+ if (!in_array('add', $enable_links)) {
+ unset($entity_type_info['links']['add-form']);
+ }
+ }
+
+ if ($config->get('disable_links')) {
+ unset(
+ $entity_type_info['links']['canonical'],
+ $entity_type_info['links']['delete-form'],
+ $entity_type_info['links']['edit-form'],
+ $entity_type_info['links']['add-form'],
+ );
+ }
+ }
+
+ // 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']['add-form'])) {
+ // 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().
+ */
+ #[Hook('entity_bundle_info')]
+ public function entityBundleInfo(): array {
+ $transliteration = \Drupal::transliteration();
+
+ $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 = $this->civicrmApi->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()
+ */
+ #[Hook('entity_bundle_field_info')]
+ public function entityBundleFieldInfo(EntityTypeInterface $entity_type, $bundle, array $base_field_definitions): array {
+ $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 = $this->entityTypeManager
+ ->getStorage('field_config')
+ ->getQuery()
+ ->condition('id', $entity_type->id() . '.', 'STARTS_WITH')
+ ->accessCheck(FALSE)
+ ->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;
+ }
+ }
+ // Ensure all fields have a definition.
+ if ($entity_type->get('civicrm_entity_ui_exposed') && $entity_type->hasKey('bundle')) {
+ foreach ($base_field_definitions as $field_name => $definition) {
+ if (isset($result[$field_name]) || isset($definition) || empty($bundle)) {
+ continue;
+ }
+ $field = BaseFieldOverride::createFromBaseFieldDefinition($base_field_definitions[$field_name], $bundle);
+ $result[$field_name] = $field;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * 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.
+ */
+ #[Hook('rebuild')]
+ public function rebuild(): void {
+ $supported_entities = SupportedEntities::getInfo();
+
+ foreach (array_keys($supported_entities) as $entity_type_id) {
+ // Reset field storage definitions.
+ $field_storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($entity_type_id);
+ $this->entityLastInstalledSchemaRepository->setLastInstalledFieldStorageDefinitions($entity_type_id, $field_storage_definitions);
+
+ // Reset entity type definition.
+ $definition = $this->entityTypeManager->getDefinition($entity_type_id);
+ $this->entityLastInstalledSchemaRepository->setLastInstalledDefinition($definition);
+ }
+ }
+
+}
diff --git a/src/Hook/FormHooks.php b/src/Hook/FormHooks.php
new file mode 100644
index 00000000..9119e3d7
--- /dev/null
+++ b/src/Hook/FormHooks.php
@@ -0,0 +1,53 @@
+getFormObject();
+ if ($form_object instanceof CivicrmEntityForm) {
+
+ /**
+ * @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
+ */
+ $storage = $form_state->getStorage();
+ if (!empty($storage['form_display'])) {
+ $form_display = $storage['form_display'];
+ $entity = $form_object->getEntity();
+
+ if ($entity->getEntityType()->hasKey('bundle') && $this->moduleHandler->moduleExists('field_group')) {
+ $context = [
+ 'entity_type' => $entity->getEntityTypeId(),
+ 'bundle' => $entity->getEntityTypeId(),
+ 'entity' => $entity,
+ 'context' => 'form',
+ 'display_context' => 'form',
+ 'mode' => $form_display->getMode(),
+ ];
+
+ field_group_attach_groups($form, $context);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/Hook/QueryHooks.php b/src/Hook/QueryHooks.php
new file mode 100644
index 00000000..a5766124
--- /dev/null
+++ b/src/Hook/QueryHooks.php
@@ -0,0 +1,117 @@
+getTableQueue();
+ foreach ($table_queue as $alias => &$table_info) {
+ if (!empty($table_info['table']) && ((strpos($table_info['table'], 'civicrm_') === 0 && strpos($table_info['table'], '.') === FALSE && strpos($table_info['table'], '__') === FALSE) || strpos($table_info['table'], 'civicrm_value_') === 0)) {
+ $table_info['table'] = $civicrm_connection->getFullQualifiedTableName($table_info['table']);
+ }
+ if (!empty($table_info['join']->table) && ((strpos($table_info['join']->table, 'civicrm_') === 0 && strpos($table_info['join']->table, '.') === FALSE && strpos($table_info['join']->table, '__') === FALSE) || strpos($table_info['join']->table, 'civicrm_value_') === 0)) {
+ $table_info['join']->table = $civicrm_connection->getFullQualifiedTableName($table_info['join']->table);
+ }
+ }
+ }
+
+ \Drupal::service('civicrm')->initialize();
+ $multilingual = \CRM_Core_I18n::isMultilingual();
+
+ if ($multilingual) {
+ // @codingStandardsIgnoreStart
+ global $dbLocale;
+ // @codingStandardsIgnoreEnd
+ $columns = CRM_Core_I18n_SchemaStructure::columns();
+ $affectedColumns = [];
+ foreach ($columns as $table => $hash) {
+ foreach (array_keys($hash) as $column) {
+ $affectedColumns[] = "{$table}.{$column}";
+ }
+ }
+ $class = get_class($query);
+ if ($class == 'Drupal\search_api\Plugin\views\query\SearchApiQuery' && method_exists($query, "getWhere")) {
+ $where = $query->getWhere();
+ }
+ elseif (isset($query->where)) {
+ $where = $query->where;
+ }
+ if (!empty($where)) {
+ foreach ($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']);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!empty($query->fields)) {
+ 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_query_TAG_alter().
+ */
+ #[Hook('query_pathauto_bulk_update_alter')]
+ public function queryPathautoBulkUpdateAlter(AlterableInterface $query): void {
+ $tables = &$query->getTables();
+
+ if (strpos($tables['base_table']['table'], 'civicrm_') !== FALSE) {
+ $civicrm_connection_name = drupal_valid_test_ua() ? 'civicrm_test' : 'civicrm';
+ $civicrm_database_info = Database::getConnectionInfo($civicrm_connection_name);
+ if (isset($civicrm_database_info['default'])) {
+ $connection = Database::getConnection('default', $civicrm_connection_name);
+ $tables['base_table']['table'] = $connection->getFullQualifiedTableName($tables['base_table']['table']);
+ }
+ }
+ }
+
+ /**
+ * Implements hook_query_TAG_alter().
+ */
+ #[Hook('query_pathauto_bulk_delete_alter')]
+ public function queryPathautoBulkDeleteAlter(AlterableInterface $query): void {
+ $tables = &$query->getTables();
+
+ if (strpos($tables['base_table']['table'], 'civicrm_') !== FALSE) {
+ $civicrm_connection_name = drupal_valid_test_ua() ? 'civicrm_test' : 'civicrm';
+ $civicrm_database_info = Database::getConnectionInfo($civicrm_connection_name);
+ if (isset($civicrm_database_info['default'])) {
+ $connection = Database::getConnection('default', $civicrm_connection_name);
+ $tables['base_table']['table'] = $connection->getFullQualifiedTableName($tables['base_table']['table']);
+ }
+ }
+ }
+
+}
diff --git a/src/Hook/RulesHooks.php b/src/Hook/RulesHooks.php
new file mode 100644
index 00000000..51cc7b7c
--- /dev/null
+++ b/src/Hook/RulesHooks.php
@@ -0,0 +1,41 @@
+getDefinitions();
+ $filters = "";
+ foreach ($definitions as $key) {
+ if ($key['provider'] == 'civicrm_entity') {
+ $filters .= ($filters == '' ? '' : ', ') . $key['id'];
+ }
+ }
+ if (array_key_exists('format', $rules_actions['civicrm_entity_user_create']['context_definitions'])) {
+ // Drupal 9 use 'context_definitions' instead of 'context'.
+ $rules_actions['civicrm_entity_user_create']['context_definitions']['format']->setDescription(t('Format of the username. Use Twig style tokens for using the available data.
Civicrm Entity filter available : @filters.',
+ [
+ '@url' => 'https://www.drupal.org/docs/8/modules/typed-data-api-enhancements/typeddata-tokens',
+ '@filters' => $filters,
+ ]));
+ }
+ else {
+ $rules_actions['civicrm_entity_user_create']['context']['format']->setDescription(t('Format of the username. Use Twig style tokens for using the available data.
Civicrm Entity filter available : @filters.',
+ [
+ '@url' => 'https://www.drupal.org/docs/8/modules/typed-data-api-enhancements/typeddata-tokens',
+ '@filters' => $filters,
+ ]));
+ }
+ }
+
+}