civicrm_entity-8.x-3.0-beta1/src/CiviEntityStorage.php

src/CiviEntityStorage.php
<?php

namespace Drupal\civicrm_entity;

use Drupal\civicrm_entity\Entity\CivicrmEntity;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Sql\DefaultTableMapping;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Utility\Error;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\field\FieldStorageConfigInterface;

/**
 * Defines entity class for external CiviCRM entities.
 */
class CiviEntityStorage extends SqlContentEntityStorage {

  /**
   * The CiviCRM API.
   *
   * @var \Drupal\civicrm_entity\CiviCrmApi
   */
  protected $civicrmApi;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The entity field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManager
   */
  protected $entityFieldManager;

  /**
   * The Logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * Gets the CiviCRM API.
   *
   * @return \Drupal\civicrm_entity\CiviCrmApiInterface
   *   The CiviCRM APi.
   */
  private function getCiviCrmApi() {
    if (!$this->civicrmApi) {
      $this->civicrmApi = \Drupal::service('civicrm_entity.api');
    }
    return $this->civicrmApi;
  }

  /**
   * Gets the config factory.
   *
   * @return \Drupal\Core\Config\ConfigFactoryInterface
   *   The configuration factory service.
   */
  private function getConfigFactory() {
    if (!$this->configFactory) {
      $this->configFactory = \Drupal::configFactory();
    }
    return $this->configFactory;
  }

  /**
   * Gets the config factory.
   *
   * @return \Psr\Log\LoggerInterface
   *   The logger channel.
   */
  private function getLogger() {
    if (!$this->logger) {
      $this->logger = \Drupal::logger('civicrm_entity');
    }
    return $this->logger;
  }

  /**
   * Initializes table name variables.
   */
  protected function initTableLayout() {
    $this->tableMapping = NULL;
    $this->revisionKey = NULL;
    $this->revisionTable = NULL;
    $this->dataTable = NULL;
    $this->revisionDataTable = NULL;
  }

  /**
   * {@inheritdoc}
   */
  protected function doDelete($entities) {
    /** @var \Drupal\Core\Entity\EntityInterface $entity */
    foreach ($entities as $entity) {
      try {
        $params['id'] = $entity->id();
        $this->getCiviCrmApi()->delete($this->entityType->get('civicrm_entity'), $params);
      }
      catch (\Exception $e) {
        throw $e;
      }
    }
    $this->doDeleteFieldItems($entities);
  }

  /**
   * {@inheritdoc}
   */
  protected function doDeleteFieldItems($entities) {
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = $this->getTableMapping();

    foreach ($entities as $entity) {
      foreach ($this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_definition) {
        $storage_definition = $field_definition->getFieldStorageDefinition();
        if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
          continue;
        }
        $table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
        $this->database->delete($table_name)
          ->condition('entity_id', $entity->id())
          ->execute();
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function doSave($id, EntityInterface $entity) {
    /** @var \Drupal\civicrm_entity\Entity\CivicrmEntity $entity */
    $return = $entity->isNew() ? SAVED_NEW : SAVED_UPDATED;

    $params = $entity->civicrmApiNormalize();
    $non_base_fields = array_filter($entity->getFieldDefinitions(), function (FieldDefinitionInterface $definition) {
      return !$definition->getFieldStorageDefinition()->isBaseField();
    });
    $non_base_fields = array_map(function (FieldDefinitionInterface $definition) {
      return $definition->getName();
    }, $non_base_fields);

    $result = $this->getCiviCrmApi()->save($this->entityType->get('civicrm_entity'), $params);
    if ($entity->isNew()) {
      $entity->{$this->idKey} = (string) $result['id'];
    }
    $this->doSaveFieldItems($entity, $non_base_fields);

    return $return;
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Core\Entity\Sql\SqlContentEntityStorageException
   */
  protected function doLoadMultiple(?array $ids = NULL) {
    $entities = [];
    if ($ids === NULL) {
      $civicrm_entity = $this->getCiviCrmApi()->get($this->entityType->get('civicrm_entity'));
      $civicrm_entity = reset($civicrm_entity);
      $entity = $this->prepareLoadedEntity($civicrm_entity);
      $entities[$entity->id()] = $entity;
    }

    // Get all the fields.
    $fields = $this->getCiviCrmApi()->getFields($this->entityType->get('civicrm_entity'));

    $ids = $this->cleanIds($ids);

    if (!empty($ids)) {
      $options = [
        'id' => ['IN' => $ids],
        'return' => array_keys($fields),
        'options' => ['limit' => 0],
      ];

      if ($this->entityType->get('civicrm_entity') === 'participant') {
        unset($options['return']);
      }

      try {
        $civicrm_entities = $this->getCiviCrmApi()->get($this->entityType->get('civicrm_entity'), $options);

        foreach ($civicrm_entities as $civicrm_entity) {
          if ($this->entityType->get('civicrm_entity') === 'participant') {
            // Massage the values.
            $temporary = [];
            foreach ($civicrm_entity as $key => $value) {
              if (strpos($key, 'participant_') === 0) {
                $temporary[str_replace('participant_', '', $key)] = $value;
              }
              else {
                $temporary[$key] = $value;
              }
            }

            $civicrm_entity = $temporary;
          }

          $massaged_civicrm_entity = [];
          foreach ($fields as $name => $field) {
            if (isset($civicrm_entity[$name])) {
              $massaged_civicrm_entity[$field['name']] = $civicrm_entity[$name];
            }
          }

          $entity = $this->prepareLoadedEntity($civicrm_entity + $massaged_civicrm_entity);
          $entities[$entity->id()] = $entity;
        }
      }
      catch (\Exception $e) {
        Error::logException($this->getLogger(), $e);
      }
    }

    return $entities;
  }

  /**
   * Prepares a loaded entity.
   *
   * @param array $civicrm_entity
   *   The entity data.
   *
   * @return \Drupal\civicrm_entity\Entity\CivicrmEntity
   *   The prepared entity.
   *
   * @throws \Drupal\Core\Entity\Sql\SqlContentEntityStorageException
   */
  protected function prepareLoadedEntity(array $civicrm_entity) {
    $this->loadFromDedicatedTables($civicrm_entity);
    $bundle = FALSE;
    if ($this->bundleKey) {
      $bundle_property = $this->entityType->get('civicrm_bundle_property');
      if (!isset($civicrm_entity[$bundle_property])) {
        throw new EntityStorageException('Missing bundle for entity type ' . $this->entityTypeId);
      }
      $bundle_value = $civicrm_entity[$bundle_property];
      $options = $this->civicrmApi->getOptions($this->entityType->get('civicrm_entity'), $bundle_property);
      $bundle = $options[$bundle_value];

      $transliteration = \Drupal::transliteration();
      $bundle = SupportedEntities::optionToMachineName($bundle, $transliteration);
    }
    $entity_class = $this->getEntityClass();
    $entity = new $entity_class([], $this->entityTypeId, $bundle);
    // Use initFieldValues to fix CiviCRM data array to Drupal.
    $this->initFieldValues($entity, $civicrm_entity);
    return $entity;
  }

  /**
   * {@inheritdoc}
   */
  protected function getQueryServiceName() {
    return 'entity.query.civicrm_entity';
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Core\Entity\Sql\SqlContentEntityStorageException
   */
  public function countFieldData($storage_definition, $as_bool = FALSE) {
    // The table mapping contains stale data during a request when a field
    // storage definition is added, so bypass the internal storage definitions
    // and fetch the table mapping using the passed in storage definition.
    // @todo Fix this in https://www.drupal.org/node/2705205.
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = $this->getTableMapping();

    if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
      $is_deleted = $storage_definition instanceof FieldStorageConfigInterface && $storage_definition->isDeleted();
      $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
      $query = $this->database->select($table_name, 't');
      $or = $query->orConditionGroup();
      foreach ($storage_definition->getColumns() as $column_name => $data) {
        $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
      }
      $query->condition($or);
      if (!$as_bool) {
        $query
          ->fields('t', ['entity_id'])
          ->distinct(TRUE);
      }
    }

    // @todo Find a way to count field data also for fields having custom
    //   storage. See https://www.drupal.org/node/2337753.
    $count = 0;
    if (isset($query)) {
      // If we are performing the query just to check if the field has data
      // limit the number of rows.
      if ($as_bool) {
        $query
          ->range(0, 1)
          ->addExpression('1');
      }
      else {
        // Otherwise count the number of rows.
        $query = $query->countQuery();
      }
      $count = $query->execute()->fetchField();
    }
    return $as_bool ? (bool) $count : (int) $count;
  }

  /**
   * {@inheritdoc}
   */
  public function hasData() {
    if (($component = $this->entityType->get('component')) !== NULL) {
      $components = $this->getCiviCrmApi()->getValue('Setting', ['name' => 'enable_components']);
      return !in_array($component, $components) ? FALSE :
        $this->getCiviCrmApi()->getCount($this->entityType->get('civicrm_entity')) > 0;
    }
    else {
      $enabled_components_as_entity = $this->getConfigFactory()->get('civicrm_entity.settings')->get('enabled_entity_types');
      $component = $this->entityType->get('civicrm_entity');
      return !in_array($component, $enabled_components_as_entity) ? FALSE :
        $this->getCiviCrmApi()->getCount($this->entityType->get('civicrm_entity')) > 0;
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function doLoadRevisionFieldItems($revision_id) {}

  /**
   * {@inheritdoc}
   */
  protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
    $update = !$entity->isNew();
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = $this->getTableMapping();
    $storage_definitions = $this->entityFieldManager->getFieldStorageDefinitions($this->entityTypeId);
    $dedicated_table_fields = [];

    // Collect the name of fields to be written in dedicated tables and check
    // whether shared table records need to be updated.
    foreach ($names as $name) {
      $storage_definition = $storage_definitions[$name];
      if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
        $dedicated_table_fields[] = $name;
      }
    }

    // Update dedicated table records if necessary.
    if ($dedicated_table_fields) {
      $names = is_array($dedicated_table_fields) ? $dedicated_table_fields : [];
      $this->saveToDedicatedTables($entity, $update, $names);
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision) {}

  /**
   * {@inheritdoc}
   */
  public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
    $this->wrapSchemaException(function () use ($storage_definition) {
      $this->getStorageSchema()->onFieldStorageDefinitionCreate($storage_definition);
    });
  }

  /**
   * {@inheritdoc}
   */
  public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = $this->getTableMapping(
      $this->entityFieldManager->getActiveFieldStorageDefinitions($this->entityType->id())
    );

    if ($storage_definition instanceof FieldStorageConfigInterface && $table_mapping->requiresDedicatedTableStorage($storage_definition)) {
      // Mark all data associated with the field for deletion.
      $table = $table_mapping->getDedicatedDataTableName($storage_definition);
      $this->database->update($table)
        ->fields(['deleted' => 1])
        ->execute();
    }

    // Update the field schema.
    $this->wrapSchemaException(function () use ($storage_definition) {
      $this->getStorageSchema()->onFieldStorageDefinitionDelete($storage_definition);
    });
  }

  /**
   * {@inheritdoc}
   *
   * Provide any additional processing of values from CiviCRM API.
   */
  protected function initFieldValues(ContentEntityInterface $entity, array $values = [], array $field_names = []) {
    parent::initFieldValues($entity, $values, $field_names);
    $civicrm_entity_settings = $this->getConfigFactory()->get('civicrm_entity.settings');
    $field_definitions = $entity->getFieldDefinitions();
    foreach ($field_definitions as $definition) {
      if ($definition->getType() == 'metatag_computed') {
        continue;
      }

      if ($definition->getName() == 'path' && (!$entity->hasLinkTemplate('canonical') || !$entity->hasLinkTemplate('edit-form'))) {
        continue;
      }

      $items = $entity->get($definition->getName());
      if ($items->isEmpty()) {
        continue;
      }
      $main_property_name = $definition->getFieldStorageDefinition()->getMainPropertyName();

      // Set a default format for text fields.
      if ($definition->getType() === 'text_long') {
        $filter_format = $civicrm_entity_settings->get('filter_format') ?: filter_fallback_format();

        $item_values = $items->getValue();
        foreach ($item_values as $delta => $item) {
          $item_values[$delta]['format'] = $filter_format;
        }
        $items->setValue($item_values);
      }
      // Fix DateTime values for Drupal format.
      elseif ($definition->getType() === 'datetime') {
        $item_values = $items->getValue();
        foreach ($item_values as $delta => $item) {
          // On Contribution entities, there are dates sometimes set to the
          // string value of 'null'.
          if ($item[$main_property_name] === 'null') {
            $item_values[$delta][$main_property_name] = NULL;
          }
          // Handle if the value provided is a timestamp.
          // @note: This only occurred during test migrations.
          elseif (is_numeric($item[$main_property_name])) {
            $item_values[$delta][$main_property_name] = (new \DateTime())->setTimestamp($item[$main_property_name])->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
          }
          else {
            $datetime_format = $definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
            $default_timezone = $definition->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::STORAGE_TIMEZONE : \Drupal::config('system.date')->get('timezone.default') ?? date_default_timezone_get();
            $datetime_value = (new \DateTime($item[$main_property_name], new \DateTimeZone($default_timezone)))->setTimezone(new \DateTimeZone('UTC'))->format($datetime_format);
            $item_values[$delta][$main_property_name] = $datetime_value;
          }
        }
        $items->setValue($item_values);
      }
    }

    // Handle special cases for field definitions.
    foreach ($field_definitions as $definition) {
      $data_types = ['Float', 'Money'];
      if (($field_metadata = $definition->getSetting('civicrm_entity_field_metadata')) && isset($field_metadata['custom_group_id']) && in_array($field_metadata['data_type'], $data_types)) {
        $items = $entity->get($definition->getName());
        $item_values = $items->getValue();
        if (!empty($item_values)) {
          $ret = [];
          foreach ($item_values as $value) {
            $v = \CRM_Utils_Rule::cleanMoney($value['value']);
            $ret[] = ['value' => $v];
          }
          if (!empty($ret)) {
            $items->setValue($ret);
          }
        }
      }
      elseif (($field_metadata = $definition->getSetting('civicrm_entity_field_metadata')) && isset($field_metadata['custom_group_id']) && $field_metadata['data_type'] === 'File') {
        $items = $entity->get($definition->getName());
        $item_values = $items->getValue();

        if (!empty($item_values)) {
          $ret = [];
          foreach ($item_values as $value) {
            if (!isset($value['fid'])) {
              continue;
            }

            $ret[] = ['value' => $value['fid']];
          }

          if (!empty($ret)) {
            $items->setValue($ret);
          }
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getTableMapping(?array $storage_definitions = NULL) {
    $table_mapping = $this->tableMapping;

    if ($table_mapping) {
      return $table_mapping;
    }

    $table_mapping_class = DefaultTableMapping::class;
    $definitions = $this->entityFieldManager->getFieldStorageDefinitions($this->entityTypeId);
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping|\Drupal\Core\Entity\Sql\TemporaryTableMapping $table_mapping */
    $table_mapping = new $table_mapping_class($this->entityType, $definitions);

    // Add dedicated tables.
    $dedicated_table_definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
      return $table_mapping->requiresDedicatedTableStorage($definition);
    });
    $extra_columns = [
      'bundle',
      'deleted',
      'entity_id',
      'revision_id',
      'langcode',
      'delta',
    ];
    foreach ($dedicated_table_definitions as $field_name => $definition) {
      $tables = [$table_mapping->getDedicatedDataTableName($definition)];
      foreach ($tables as $table_name) {
        $table_mapping->setFieldNames($table_name, [$field_name]);
        $table_mapping->setExtraColumns($table_name, $extra_columns);
      }
    }
    $this->tableMapping = $table_mapping;
    return $table_mapping;
  }

  /**
   * Loads values of fields stored in dedicated tables for a group of entities.
   *
   * @param array &$values
   *   An array of values keyed by entity ID.
   *   defaults to FALSE.
   * @param bool $load_from_revision
   *   Boolean to load from revision.
   *
   * @throws \Drupal\Core\Entity\Sql\SqlContentEntityStorageException
   */
  protected function loadFromDedicatedTables(array &$values, $load_from_revision = FALSE) {
    if (empty($values)) {
      return;
    }

    // Collect impacted fields.
    $storage_definitions = [];
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = $this->getTableMapping();

    $definitions = $this->entityFieldManager->getFieldDefinitions($this->entityTypeId, $this->entityTypeId);
    foreach ($definitions as $field_name => $field_definition) {
      $storage_definition = $field_definition->getFieldStorageDefinition();
      if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
        $storage_definitions[$field_name] = $storage_definition;
      }
    }

    // Load field data.
    $langcodes = array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL));
    foreach ($storage_definitions as $field_name => $storage_definition) {
      $table = $table_mapping->getDedicatedDataTableName($storage_definition);

      // Ensure that only values having valid languages are retrieved. Since we
      // are loading values for multiple entities, we cannot limit the query to
      // the available translations.
      $results = $this->database->select($table, 't')
        ->fields('t')
        ->condition('entity_id', [$values[$this->getEntityType()->getKey('id')]], 'IN')
        ->condition('deleted', 0)
        ->condition('langcode', $langcodes, 'IN')
        ->orderBy('delta')
        ->execute();

      foreach ($results as $row) {
        if (!isset($values[$field_name])) {
          $values[$field_name] = [];
        }

        if ($storage_definition->getCardinality() == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || count($values[$field_name]) < $storage_definition->getCardinality()) {
          $item = [];
          // For each column declared by the field, populate the item from the
          // prefixed database column.
          foreach ($storage_definition->getColumns() as $column => $attributes) {
            $column_name = $table_mapping->getFieldColumnName($storage_definition, $column);
            // Unserialize the value if specified in the column schema.
            $item[$column] = (!empty($attributes['serialize'])) ? unserialize($row->$column_name) : $row->$column_name;
          }

          // Add the item to the field values for the entity.
          $values[$field_name][] = $item;
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
    // The entity base table is managed by CiviCRM.
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
    // The entity base table is managed by CiviCRM.
    return FALSE;
  }

  /**
   * Saves values of fields that use dedicated tables.
   *
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The entity.
   * @param bool $update
   *   TRUE if the entity is being updated, FALSE if it is being inserted.
   * @param string[] $names
   *   (optional) The names of the fields to be stored. Defaults to all the
   *   available fields.
   *
   * @throws \Drupal\Core\Entity\Sql\SqlContentEntityStorageException
   */
  protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE, $names = []) {
    $vid = $entity->getRevisionId();
    $id = $entity->id();
    $bundle = $entity->bundle();
    $entity_type = $entity->getEntityTypeId();
    $default_langcode = $entity->getUntranslated()->language()->getId();
    $translation_langcodes = array_keys($entity->getTranslationLanguages());
    /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = $this->getTableMapping();

    if (!isset($vid)) {
      $vid = $id;
    }

    $original = !empty($entity->original) ? $entity->original : NULL;

    // Determine which fields should be actually stored.
    $definitions = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
    if ($names) {
      $definitions = array_intersect_key($definitions, array_flip($names));
    }

    foreach ($definitions as $field_name => $field_definition) {
      $storage_definition = $field_definition->getFieldStorageDefinition();
      if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
        continue;
      }

      // When updating an existing revision, keep the existing records if the
      // field values did not change.
      if (!$entity->isNewRevision() && $original && !$this->hasFieldValueChanged($field_definition, $entity, $original)) {
        continue;
      }

      $table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
      $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);

      // Delete and insert, rather than update, in case a value was added.
      if ($update) {
        // Only overwrite the field's base table if saving the default revision
        // of an entity.
        if ($entity->isDefaultRevision()) {
          $this->database->delete($table_name)
            ->condition('entity_id', $id)
            ->execute();
        }
        if ($this->entityType->isRevisionable()) {
          $this->database->delete($revision_name)
            ->condition('entity_id', $id)
            ->condition('revision_id', $vid)
            ->execute();
        }
      }

      // Prepare the multi-insert query.
      $do_insert = FALSE;
      $columns = ['entity_id', 'revision_id', 'bundle', 'delta', 'langcode'];
      foreach ($storage_definition->getColumns() as $column => $attributes) {
        $columns[] = $table_mapping->getFieldColumnName($storage_definition, $column);
      }
      $query = $this->database->insert($table_name)->fields($columns);
      if ($this->entityType->isRevisionable()) {
        $revision_query = $this->database->insert($revision_name)->fields($columns);
      }

      $langcodes = $field_definition->isTranslatable() ? $translation_langcodes : [$default_langcode];
      foreach ($langcodes as $langcode) {
        $delta_count = 0;
        $items = $entity->getTranslation($langcode)->get($field_name);
        $items->filterEmptyItems();
        foreach ($items as $delta => $item) {
          // We now know we have something to insert.
          $do_insert = TRUE;
          $record = [
            'entity_id' => $id,
            'revision_id' => $vid,
            'bundle' => $bundle,
            'delta' => $delta,
            'langcode' => $langcode,
          ];
          foreach ($storage_definition->getColumns() as $column => $attributes) {
            $column_name = $table_mapping->getFieldColumnName($storage_definition, $column);
            // Serialize the value if specified in the column schema.
            $value = $item->$column;
            if (!empty($attributes['serialize'])) {
              $value = serialize($value);
            }
            $record[$column_name] = SqlContentEntityStorageSchema::castValue($attributes, $value);
          }
          $query->values($record);
          if ($this->entityType->isRevisionable() && isset($revision_query)) {
            $revision_query->values($record);
          }

          if ($storage_definition->getCardinality() != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && ++$delta_count == $storage_definition->getCardinality()) {
            break;
          }
        }
      }

      // Execute the query if we have values to insert.
      if ($do_insert) {
        // Only overwrite the field's base table if saving the default revision
        // of an entity.
        if ($entity->isDefaultRevision()) {
          $query->execute();
        }
        if ($this->entityType->isRevisionable() && isset($revision_query)) {
          $revision_query->execute();
        }
      }
    }
  }

  /**
   * Allows CiviCRM hook to invoke presave.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to save.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   *   If the entity identifier is invalid.
   *
   * @see \Drupal\Core\Entity\ContentEntityStorageBase::doPreSave
   */
  public function civiPreSave(EntityInterface $entity) {
    if (!empty($entity->drupal_crud)) {
      return;
    }
    $this->doPreSave($entity);
  }

  /**
   * Allows CiviCRM hook to invoke postsave.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The saved entity.
   * @param bool $update
   *   Specifies whether the entity is being updated or created.
   *
   * @see \Drupal\Core\Entity\ContentEntityStorageBase::doPostSave
   */
  public function civiPostSave(EntityInterface $entity, $update) {
    if (!empty($entity->drupal_crud)) {
      return;
    }
    $this->doPostSave($entity, $update);
  }

  /**
   * Allows CiviCRM hook to invoke predelete.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to be deleted.
   *
   * @see \Drupal\Core\Entity\EntityStorageInterface::delete
   */
  public function civiPreDelete(EntityInterface $entity) {
    if (!empty($entity->drupal_crud)) {
      return;
    }
    CivicrmEntity::preDelete($this, [$entity]);
    $this->invokeHook('predelete', $entity);
  }

  /**
   * Allows CiviCRM hook to invoke delete.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity deleted.
   *
   * @see \Drupal\Core\Entity\EntityStorageInterface::delete
   */
  public function civiPostDelete(EntityInterface $entity) {
    if (!empty($entity->drupal_crud)) {
      return;
    }
    $this->doDeleteFieldItems([$entity]);
    $this->resetCache([$entity->id()]);
    CivicrmEntity::postDelete($this, [$entity]);
    $this->invokeHook('delete', $entity);
  }

  /**
   * Loads the EntityTag ID.
   *
   * When saving EntityTag objects, the 'id' that's passed to CiviCRM hooks is
   * not the ID of the EntityTag, but rather the object to which the EntityTag
   * applies. This provides the lookup to determing the ID of the EntityTag
   * object itself.
   *
   * @param int $entityId
   *   The entity ID.
   * @param string $entityTable
   *   The entity table.
   *
   * @return int|null
   *   The EntityTag object's ID, or NULL if not found.
   */
  public function getEntityTagEntityId($entityId, $entityTable) {
    $api_params = [
      'sequential' => 1,
      'entity_id' => $entityId,
      'entity_table' => $entityTable,
    ];
    $api_results = civicrm_api3('EntityTag', 'get', $api_params);
    if (!empty($api_results['values'])) {
      foreach ($api_results['values'] as $delta => $result) {
        if ($result['entity_id'] == $entityId) {
          return $result['id'];
        }
      }
    }
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
    // Don't do anything.
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc