abstract class EntityProcessorBase in Feeds 8.3
Defines a base entity processor.
Creates entities from feed items.
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\feeds\Plugin\Type\PluginBase implements FeedsPluginInterface uses DependencyTrait
- class \Drupal\feeds\Feeds\Processor\ProcessorBase implements ProcessorInterface
- class \Drupal\feeds\Feeds\Processor\EntityProcessorBase implements ContainerFactoryPluginInterface, MappingPluginFormInterface, EntityProcessorInterface
- class \Drupal\feeds\Feeds\Processor\ProcessorBase implements ProcessorInterface
- class \Drupal\feeds\Plugin\Type\PluginBase implements FeedsPluginInterface uses DependencyTrait
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of EntityProcessorBase
2 files declare their use of EntityProcessorBase
- EntityProcessorBaseTest.php in tests/
src/ Kernel/ Feeds/ Processor/ EntityProcessorBaseTest.php - EntityTestProcessor.php in tests/
modules/ feeds_test_plugin/ src/ Feeds/ Processor/ EntityTestProcessor.php
File
- src/
Feeds/ Processor/ EntityProcessorBase.php, line 41
Namespace
Drupal\feeds\Feeds\ProcessorView source
abstract class EntityProcessorBase extends ProcessorBase implements EntityProcessorInterface, ContainerFactoryPluginInterface, MappingPluginFormInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity storage controller for the entity type being processed.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storageController;
/**
* The entity info for the selected entity type.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* Flag indicating that this processor is locked.
*
* @var bool
*/
protected $isLocked;
/**
* The entity type bundle info.
*
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
*/
protected $entityTypeBundleInfo;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs an EntityProcessorBase object.
*
* @param array $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin id.
* @param array $plugin_definition
* The plugin definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
* The entity type bundle info.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, LanguageManagerInterface $language_manager) {
$this->entityTypeManager = $entity_type_manager;
$this->entityType = $entity_type_manager
->getDefinition($plugin_definition['entity_type']);
$this->storageController = $entity_type_manager
->getStorage($plugin_definition['entity_type']);
$this->entityTypeBundleInfo = $entity_type_bundle_info;
$this->languageManager = $language_manager;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('entity_type.manager'), $container
->get('entity_type.bundle.info'), $container
->get('language_manager'));
}
/**
* {@inheritdoc}
*/
public function process(FeedInterface $feed, ItemInterface $item, StateInterface $state) {
// Initialize clean list if needed.
$clean_state = $feed
->getState(StateInterface::CLEAN);
if (!$clean_state
->initiated()) {
$this
->initCleanList($feed, $clean_state);
}
$skip_new = $this->configuration['insert_new'] == static::SKIP_NEW;
$existing_entity_id = $this
->existingEntityId($feed, $item);
$skip_existing = $this->configuration['update_existing'] == static::SKIP_EXISTING;
// If the entity is an existing entity it must be removed from the clean
// list.
if ($existing_entity_id) {
$clean_state
->removeItem($existing_entity_id);
}
// Bulk load existing entities to save on db queries.
if ($skip_existing && $existing_entity_id || !$existing_entity_id && $skip_new) {
$state->skipped++;
return;
}
// Delay building a new entity until necessary.
if ($existing_entity_id) {
$entity = $this->storageController
->load($existing_entity_id);
}
$hash = $this
->hash($item);
$changed = $existing_entity_id && $hash !== $entity
->get('feeds_item')->hash;
// Do not proceed if the item exists, has not changed, and we're not
// forcing the update.
if ($existing_entity_id && !$changed && !$this->configuration['skip_hash_check']) {
$state->skipped++;
return;
}
// Build a new entity.
if (!$existing_entity_id && !$skip_new) {
$entity = $this
->newEntity($feed);
}
try {
// Set feeds_item values.
$feeds_item = $entity
->get('feeds_item');
$feeds_item->target_id = $feed
->id();
$feeds_item->hash = $hash;
// Set field values.
$this
->map($feed, $entity, $item);
// Validate the entity.
$feed
->dispatchEntityEvent(FeedsEvents::PROCESS_ENTITY_PREVALIDATE, $entity, $item);
$this
->entityValidate($entity);
// Dispatch presave event.
$feed
->dispatchEntityEvent(FeedsEvents::PROCESS_ENTITY_PRESAVE, $entity, $item);
// This will throw an exception on failure.
$this
->entitySaveAccess($entity);
// Set imported time.
$entity
->get('feeds_item')->imported = \Drupal::service('datetime.time')
->getRequestTime();
// And... Save! We made it.
$this->storageController
->save($entity);
// Dispatch postsave event.
$feed
->dispatchEntityEvent(FeedsEvents::PROCESS_ENTITY_POSTSAVE, $entity, $item);
// Track progress.
$existing_entity_id ? $state->updated++ : $state->created++;
} catch (EmptyFeedException $e) {
// Not an error.
$state->skipped++;
} catch (ValidationException $e) {
$state->failed++;
$state
->setMessage($e
->getFormattedMessage(), 'warning');
} catch (\Exception $e) {
$state->failed++;
$state
->setMessage($e
->getMessage(), 'warning');
}
}
/**
* Initializes the list of entities to clean.
*
* This populates $state->cleanList with all existing entities previously
* imported from the source.
*
* @param \Drupal\feeds\FeedInterface $feed
* The feed to import.
* @param \Drupal\feeds\Feeds\State\CleanStateInterface $state
* The state of the clean stage.
*/
protected function initCleanList(FeedInterface $feed, CleanStateInterface $state) {
$state
->setEntityTypeId($this
->entityType());
// Fill the list only if needed.
if ($this
->getConfiguration('update_non_existent') === static::KEEP_NON_EXISTENT) {
return;
}
// Set list of entities to clean.
$ids = $this->entityTypeManager
->getStorage($this
->entityType())
->getQuery()
->condition('feeds_item.target_id', $feed
->id())
->condition('feeds_item.hash', $this
->getConfiguration('update_non_existent'), '<>')
->execute();
$state
->setList($ids);
// And set progress.
$state->total = $state
->count();
$state
->progress($state->total, 0);
}
/**
* {@inheritdoc}
*/
public function clean(FeedInterface $feed, EntityInterface $entity, CleanStateInterface $state) {
$update_non_existent = $this
->getConfiguration('update_non_existent');
if ($update_non_existent === static::KEEP_NON_EXISTENT) {
// No action to take on this entity.
return;
}
switch ($update_non_existent) {
case static::KEEP_NON_EXISTENT:
// No action to take on this entity.
return;
case static::DELETE_NON_EXISTENT:
$entity
->delete();
break;
default:
try {
// Apply action on entity.
\Drupal::service('plugin.manager.action')
->createInstance($update_non_existent)
->execute($entity);
} catch (PluginNotFoundException $e) {
$state
->setMessage(t('Cleaning %entity failed because of non-existing action plugin %name.', [
'%entity' => $entity
->label(),
'%name' => $update_non_existent,
]), 'error');
throw $e;
}
break;
}
// Check if the entity was deleted.
$entity = $this->storageController
->load($entity
->id());
// If the entity was not deleted, update hash.
if (isset($entity->feeds_item)) {
$entity
->get('feeds_item')->hash = $update_non_existent;
$this->storageController
->save($entity);
}
// State progress.
$state->updated++;
$state
->progress($state->total, $state->updated);
}
/**
* {@inheritdoc}
*/
public function clear(FeedInterface $feed, StateInterface $state) {
// Build base select statement.
$query = $this->entityTypeManager
->getStorage($this
->entityType())
->getQuery()
->condition('feeds_item.target_id', $feed
->id());
// If there is no total, query it.
if (!$state->total) {
$count_query = clone $query;
$state->total = (int) $count_query
->count()
->execute();
}
// Delete a batch of entities.
$entity_ids = $query
->range(0, 10)
->execute();
if ($entity_ids) {
$this
->entityDeleteMultiple($entity_ids);
$state->deleted += count($entity_ids);
$state
->progress($state->total, $state->deleted);
}
}
/**
* {@inheritdoc}
*/
public function entityType() {
return $this->pluginDefinition['entity_type'];
}
/**
* The entity's bundle key.
*
* @return string|null
* The bundle type this processor operates on, or null if it is undefined.
*/
public function bundleKey() {
return $this->entityType
->getKey('bundle');
}
/**
* Bundle type this processor operates on.
*
* Defaults to the entity type for entities that do not define bundles.
*
* @return string|null
* The bundle type this processor operates on, or null if it is undefined.
*
* @todo We should be more careful about missing bundles.
*/
public function bundle() {
if (!($bundle_key = $this->entityType
->getKey('bundle'))) {
return $this
->entityType();
}
if (isset($this->configuration['values'][$bundle_key])) {
return $this->configuration['values'][$bundle_key];
}
}
/**
* Returns the bundle label for the entity being processed.
*
* @return string
* The bundle label.
*/
public function bundleLabel() {
if ($label = $this->entityType
->getBundleLabel()) {
return $label;
}
return $this
->t('Bundle');
}
/**
* Provides a list of bundle options for use in select lists.
*
* @return array
* A keyed array of bundle => label.
*/
public function bundleOptions() {
$options = [];
foreach ($this->entityTypeBundleInfo
->getBundleInfo($this
->entityType()) as $bundle => $info) {
if (!empty($info['label'])) {
$options[$bundle] = $info['label'];
}
else {
$options[$bundle] = $bundle;
}
}
return $options;
}
/**
* Provides a list of languages available on the site.
*
* @return array
* A keyed array of language_key => language_name.
* For example: 'en' => 'English').
*/
public function languageOptions() {
foreach ($this->languageManager
->getLanguages(LanguageInterface::STATE_ALL) as $language) {
$langcodes[$language
->getId()] = $language
->getName();
}
return $langcodes;
}
/**
* Returns the label of the entity type being processed.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The label of the entity type.
*/
public function entityTypeLabel() {
return $this->entityType
->getLabel();
}
/**
* Returns the plural label of the entity type being processed.
*
* @return string
* The plural label of the entity type.
*/
public function entityTypeLabelPlural() {
return $this->entityType
->getPluralLabel();
}
/**
* Returns the label for items being created, updated, or deleted.
*
* @return string
* The item label.
*/
public function getItemLabel() {
if (!$this->entityType
->getKey('bundle') || !$this->entityType
->getBundleEntityType()) {
return $this
->entityTypeLabel();
}
$storage = $this->entityTypeManager
->getStorage($this->entityType
->getBundleEntityType());
return $storage
->load($this->configuration['values'][$this->entityType
->getKey('bundle')])
->label();
}
/**
* Returns the plural label for items being created, updated, or deleted.
*
* @return string
* The plural item label.
*/
public function getItemLabelPlural() {
if (!$this->entityType
->getKey('bundle') || !$this->entityType
->getBundleEntityType()) {
return $this
->entityTypeLabelPlural();
}
// Entity bundles do not support plural labels yet.
// @todo Fix after https://www.drupal.org/project/drupal/issues/2765065.
$storage = $this->entityTypeManager
->getStorage($this->entityType
->getBundleEntityType());
$label = $storage
->load($this->configuration['values'][$this->entityType
->getKey('bundle')])
->label();
return $this
->t('@label items', [
'@label' => $label,
]);
}
/**
* {@inheritdoc}
*/
protected function newEntity(FeedInterface $feed) {
$values = $this->configuration['values'];
$entity = $this->storageController
->create($values);
$entity
->enforceIsNew();
if ($entity instanceof EntityOwnerInterface) {
if ($this->configuration['owner_feed_author']) {
$entity
->setOwnerId($feed
->getOwnerId());
}
else {
$entity
->setOwnerId($this->configuration['owner_id']);
}
}
// Set language if the entity type has a field for it.
if ($this->entityType
->hasKey('langcode')) {
$entity->{$this->entityType
->getKey('langcode')} = $this
->entityLanguage();
}
return $entity;
}
/**
* {@inheritdoc}
*/
public function getEntityTranslation(FeedInterface $feed, TranslatableInterface $entity, $langcode) {
if (!$entity
->hasTranslation($langcode)) {
$translation = $entity
->addTranslation($langcode);
if ($translation instanceof EntityOwnerInterface) {
if ($this->configuration['owner_feed_author']) {
$translation
->setOwnerId($feed
->getOwnerId());
}
else {
$translation
->setOwnerId($this->configuration['owner_id']);
}
}
return $translation;
}
return $entity
->getTranslation($langcode);
}
/**
* Checks if the entity exists already.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check.
*
* @return bool
* True if the entity already exists, false otherwise.
*/
protected function entityExists(EntityInterface $entity) {
if ($entity
->id()) {
$result = $this->storageController
->getQuery()
->condition($this->entityType
->getKey('id'), $entity
->id())
->execute();
return !empty($result);
}
return FALSE;
}
/**
* {@inheritdoc}
*/
protected function entityValidate(EntityInterface $entity) {
// Check if an entity with the same ID already exists if the given entity is
// new.
if ($entity
->isNew() && $this
->entityExists($entity)) {
throw new ValidationException($this
->t('An entity with ID %id already exists.', [
'%id' => $entity
->id(),
]));
}
$violations = $entity
->validate();
if (!count($violations)) {
return;
}
$errors = [];
foreach ($violations as $violation) {
$error = $violation
->getMessage();
// Try to add more context to the message.
// @todo if an exception occurred because of a different bundle, add more
// context to the message.
$invalid_value = $violation
->getInvalidValue();
if ($invalid_value instanceof FieldItemListInterface) {
// The invalid value is a field. Get more information about this field.
$error = new FormattableMarkup('@name (@property_name): @error', [
'@name' => $invalid_value
->getFieldDefinition()
->getLabel(),
'@property_name' => $violation
->getPropertyPath(),
'@error' => $error,
]);
}
else {
$error = new FormattableMarkup('@property_name: @error', [
'@property_name' => $violation
->getPropertyPath(),
'@error' => $error,
]);
}
$errors[] = $error;
}
$element = [
'#theme' => 'item_list',
'#items' => $errors,
];
// Compose error message. If available, use the entity label to indicate
// which item failed. Fallback to the GUID value (if available) or else
// no indication.
$label = $entity
->label();
$guid = $entity
->get('feeds_item')->guid;
$messages = [];
$args = [
'@entity' => mb_strtolower($this
->entityTypeLabel()),
'%label' => $label,
'%guid' => $guid,
'@errors' => \Drupal::service('renderer')
->renderRoot($element),
':url' => $this
->url('entity.feeds_feed_type.mapping', [
'feeds_feed_type' => $this->feedType
->id(),
]),
];
if ($label || $label === '0' || $label === 0) {
$messages[] = $this
->t('The @entity %label failed to validate with the following errors: @errors', $args);
}
elseif ($guid || $guid === '0' || $guid === 0) {
$messages[] = $this
->t('The @entity with GUID %guid failed to validate with the following errors: @errors', $args);
}
else {
$messages[] = $this
->t('An entity of type "@entity" failed to validate with the following errors: @errors', $args);
}
$messages[] = $this
->t('Please check your <a href=":url">mappings</a>.', $args);
// Concatenate strings as markup to mark them as safe.
$message_element = [
'#markup' => implode("\n", $messages),
];
$message = \Drupal::service('renderer')
->renderRoot($message_element);
throw new ValidationException($message);
}
/**
* {@inheritdoc}
*/
protected function entitySaveAccess(EntityInterface $entity) {
// No need to authorize.
if (!$this->configuration['authorize'] || !$entity instanceof EntityOwnerInterface) {
return;
}
// If the uid was mapped directly, rather than by email or username, it
// could be invalid.
$account = $entity
->getOwner();
if (!$account) {
$owner_id = $entity
->getOwnerId();
if ($owner_id == 0) {
// We don't check access for anonymous users.
return;
}
throw new EntityAccessException($this
->t('Invalid user with ID %uid mapped to %label.', [
'%uid' => $owner_id,
'%label' => $entity
->label(),
]));
}
// We don't check access for anonymous users.
if ($account
->isAnonymous()) {
return;
}
$op = $entity
->isNew() ? 'create' : 'update';
// Access granted.
if ($entity
->access($op, $account)) {
return;
}
$args = [
'%name' => $account
->getDisplayName(),
'@op' => $op,
'@bundle' => $this
->getItemLabelPlural(),
];
throw new EntityAccessException($this
->t('User %name is not authorized to @op @bundle.', $args));
}
/**
* {@inheritdoc}
*/
public function entityLanguage() {
$langcodes = $this
->languageOptions();
if (isset($this->configuration['langcode']) && isset($langcodes[$this->configuration['langcode']])) {
return $this->configuration['langcode'];
}
// Return default language.
return $this->languageManager
->getDefaultLanguage()
->getId();
}
/**
* {@inheritdoc}
*/
protected function entityDeleteMultiple(array $entity_ids) {
$entities = $this->storageController
->loadMultiple($entity_ids);
$this->storageController
->delete($entities);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
$defaults = [
'insert_new' => static::INSERT_NEW,
'update_existing' => static::SKIP_EXISTING,
'update_non_existent' => static::KEEP_NON_EXISTENT,
'skip_hash_check' => FALSE,
'values' => [],
'authorize' => $this->entityType
->entityClassImplements('Drupal\\user\\EntityOwnerInterface'),
'expire' => static::EXPIRE_NEVER,
'owner_id' => 0,
'owner_feed_author' => 0,
];
// Bundle.
if ($bundle_key = $this->entityType
->getKey('bundle')) {
$defaults['values'] = [
$bundle_key => NULL,
];
}
// Language.
if ($langcode_key = $this->entityType
->getKey('langcode')) {
$defaults['langcode'] = $this->languageManager
->getDefaultLanguage()
->getId();
}
return $defaults;
}
/**
* {@inheritdoc}
*/
public function onFeedTypeSave($update = TRUE) {
$this
->prepareFeedsItemField();
}
/**
* {@inheritdoc}
*/
public function onFeedTypeDelete() {
$this
->removeFeedItemField();
}
/**
* Prepares the feeds_item field.
*
* @todo How does ::load() behave for deleted fields?
*/
protected function prepareFeedsItemField() {
// Do not create field when syncing configuration.
if (\Drupal::isConfigSyncing()) {
return FALSE;
}
// Create field if it doesn't exist.
if (!FieldStorageConfig::loadByName($this
->entityType(), 'feeds_item')) {
FieldStorageConfig::create([
'field_name' => 'feeds_item',
'entity_type' => $this
->entityType(),
'type' => 'feeds_item',
'translatable' => FALSE,
])
->save();
}
// Create field instance if it doesn't exist.
if (!FieldConfig::loadByName($this
->entityType(), $this
->bundle(), 'feeds_item')) {
FieldConfig::create([
'label' => 'Feeds item',
'description' => '',
'field_name' => 'feeds_item',
'entity_type' => $this
->entityType(),
'bundle' => $this
->bundle(),
])
->save();
}
}
/**
* Deletes the feeds_item field.
*/
protected function removeFeedItemField() {
$storage_in_use = FALSE;
$instance_in_use = FALSE;
foreach (FeedType::loadMultiple() as $feed_type) {
if ($feed_type
->id() === $this->feedType
->id()) {
continue;
}
$processor = $feed_type
->getProcessor();
if (!$processor instanceof EntityProcessorInterface) {
continue;
}
if ($processor
->entityType() === $this
->entityType()) {
$storage_in_use = TRUE;
if ($processor
->bundle() === $this
->bundle()) {
$instance_in_use = TRUE;
break;
}
}
}
if ($instance_in_use) {
return;
}
// Delete the field instance.
if ($config = FieldConfig::loadByName($this
->entityType(), $this
->bundle(), 'feeds_item')) {
$config
->delete();
}
if ($storage_in_use) {
return;
}
// Delte the field storage.
if ($storage = FieldStorageConfig::loadByName($this
->entityType(), 'feeds_item')) {
$storage
->delete();
}
}
/**
* {@inheritdoc}
*/
public function expiryTime() {
return $this->configuration['expire'];
}
/**
* {@inheritdoc}
*/
public function getExpiredIds(FeedInterface $feed, $time = NULL) {
if ($time === NULL) {
$time = $this
->expiryTime();
}
if ($time == static::EXPIRE_NEVER) {
return;
}
$expire_time = \Drupal::service('datetime.time')
->getRequestTime() - $time;
return $this->entityTypeManager
->getStorage($this
->entityType())
->getQuery()
->condition('feeds_item.target_id', $feed
->id())
->condition('feeds_item.imported', $expire_time, '<')
->execute();
}
/**
* {@inheritdoc}
*/
public function expireItem(FeedInterface $feed, $item_id, StateInterface $state) {
$this
->entityDeleteMultiple([
$item_id,
]);
$state->total++;
}
/**
* {@inheritdoc}
*/
public function getItemCount(FeedInterface $feed) {
return $this->entityTypeManager
->getStorage($this
->entityType())
->getQuery()
->condition('feeds_item.target_id', $feed
->id())
->count()
->execute();
}
/**
* {@inheritdoc}
*/
public function getImportedItemIds(FeedInterface $feed) {
return $this->entityTypeManager
->getStorage($this
->entityType())
->getQuery()
->condition('feeds_item.target_id', $feed
->id())
->execute();
}
/**
* Returns an existing entity id.
*
* @param \Drupal\feeds\FeedInterface $feed
* The feed being processed.
* @param \Drupal\feeds\Feeds\Item\ItemInterface $item
* The item to find existing ids for.
*
* @return int|string|null
* The ID of the entity, or null if not found.
*/
protected function existingEntityId(FeedInterface $feed, ItemInterface $item) {
foreach ($this->feedType
->getMappings() as $delta => $mapping) {
if (empty($mapping['unique'])) {
continue;
}
foreach ($mapping['unique'] as $key => $true) {
$plugin = $this->feedType
->getTargetPlugin($delta);
$entity_id = $plugin
->getUniqueValue($feed, $mapping['target'], $key, $item
->get($mapping['map'][$key]));
if ($entity_id) {
return $entity_id;
}
}
}
}
/**
* {@inheritdoc}
*/
public function buildAdvancedForm(array $form, FormStateInterface $form_state) {
if ($bundle_key = $this->entityType
->getKey('bundle')) {
$form['values'][$bundle_key] = [
'#type' => 'select',
'#options' => $this
->bundleOptions(),
'#title' => $this
->bundleLabel(),
'#required' => TRUE,
'#default_value' => $this
->bundle() ?: key($this
->bundleOptions()),
'#disabled' => $this
->isLocked(),
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function mappingFormAlter(array &$form, FormStateInterface $form_state) {
$added_target = $form_state
->getValue('add_target');
if (!$added_target) {
// No target was added this time around. Abort.
return;
}
// When adding a mapping target to entity ID, tick 'unique' by default.
$id_key = $this->entityType
->getKey('id');
$mappings = $this->feedType
->getMappings();
$last_delta = array_keys($mappings)[count($mappings) - 1];
$mapping = end($mappings);
if ($mapping['target'] != $added_target) {
return;
}
$target_definition = $this->feedType
->getTargetPlugin($last_delta)
->getTargetDefinition();
if (!$target_definition instanceof FieldTargetDefinition) {
return;
}
/** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
$field_definition = $target_definition
->getFieldDefinition();
if ($field_definition
->getName() != $id_key) {
return;
}
// We made it! Set property as unique.
$form['mappings'][$last_delta]['unique'][$field_definition
->getMainPropertyName()]['#default_value'] = TRUE;
}
/**
* {@inheritdoc}
*/
public function mappingFormValidate(array &$form, FormStateInterface $form_state) {
// Display a warning when mapping to entity ID and having that one not set
// as unique.
$id_key = $this->entityType
->getKey('id');
foreach ($this->feedType
->getMappings() as $delta => $mapping) {
try {
$target_definition = $this->feedType
->getTargetPlugin($delta)
->getTargetDefinition();
if (!$target_definition instanceof FieldTargetDefinition) {
continue;
}
/** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
$field_definition = $target_definition
->getFieldDefinition();
if ($field_definition
->getName() != $id_key) {
continue;
}
$is_unique = $form_state
->getValue([
'mappings',
$delta,
'unique',
$field_definition
->getMainPropertyName(),
]);
if (!$is_unique) {
// Entity ID not set as unique. Display warning.
$this
->messenger()
->addWarning($this
->t('When mapping to the entity ID (@name), it is recommended to set it as unique.', [
'@name' => $target_definition
->getLabel(),
]));
}
} catch (MissingTargetException $e) {
// Ignore missing targets.
}
}
}
/**
* {@inheritdoc}
*/
public function mappingFormSubmit(array &$form, FormStateInterface $form_state) {
// The entity processor doesn't have to do anything when mappings are saved.
}
/**
* {@inheritdoc}
*/
public function isLocked() {
if ($this->isLocked === NULL) {
// Look for feeds.
$this->isLocked = (bool) $this->entityTypeManager
->getStorage('feeds_feed')
->getQuery()
->condition('type', $this->feedType
->id())
->range(0, 1)
->execute();
}
return $this->isLocked;
}
/**
* Creates an MD5 hash of an item.
*
* Includes mappings so that items will be updated if the mapping
* configuration has changed.
*
* @param \Drupal\feeds\Feeds\Item\ItemInterface $item
* The item to hash.
*
* @return string
* An MD5 hash.
*/
protected function hash(ItemInterface $item) {
$sources = $this->feedType
->getMappedSources();
$mapped_item = array_intersect_key($item
->toArray(), $sources);
return hash('md5', serialize($mapped_item) . serialize($this->feedType
->getMappings()));
}
/**
* Execute mapping on an item.
*
* This method encapsulates the central mapping functionality. When an item is
* processed, it is passed through map() where the properties of $source_item
* are mapped onto $target_item following the processor's mapping
* configuration.
*/
protected function map(FeedInterface $feed, EntityInterface $entity, ItemInterface $item) {
$mappings = $this->feedType
->getMappings();
// Mappers add to existing fields rather than replacing them. Hence we need
// to clear target elements of each item before mapping in case we are
// mapping on a prepopulated item such as an existing node.
foreach ($mappings as $delta => $mapping) {
if ($mapping['target'] == 'feeds_item') {
// Skip feeds item as this field gets default values before mapping.
continue;
}
// Clear the target.
$this
->clearTarget($entity, $this->feedType
->getTargetPlugin($delta), $mapping['target']);
}
// Gather all of the values for this item.
$source_values = [];
foreach ($mappings as $delta => $mapping) {
$target = $mapping['target'];
foreach ($mapping['map'] as $column => $source) {
if ($source === '') {
// Skip empty sources.
continue;
}
if (!isset($source_values[$delta][$column])) {
$source_values[$delta][$column] = [];
}
$value = $item
->get($source);
if (!is_array($value)) {
$source_values[$delta][$column][] = $value;
}
else {
$source_values[$delta][$column] = array_merge($source_values[$delta][$column], $value);
}
}
}
// Rearrange values into Drupal's field structure.
$field_values = [];
foreach ($source_values as $field => $field_value) {
$field_values[$field] = [];
foreach ($field_value as $column => $values) {
// Use array_values() here to keep our $delta clean.
foreach (array_values($values) as $delta => $value) {
$field_values[$field][$delta][$column] = $value;
}
}
}
// Set target values.
foreach ($mappings as $delta => $mapping) {
$plugin = $this->feedType
->getTargetPlugin($delta);
// Skip immutable targets for which the entity already has a value.
if (!$plugin
->isMutable() && !$plugin
->isEmpty($feed, $entity, $mapping['target'])) {
continue;
}
if (isset($field_values[$delta])) {
$plugin
->setTarget($feed, $entity, $mapping['target'], $field_values[$delta]);
}
}
return $entity;
}
/**
* Clears the target on the entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to clear the target on.
* @param \Drupal\feeds\Plugin\Type\Target\TargetInterface $target
* The target plugin.
* @param string $target_name
* The property to clear on the entity.
*/
protected function clearTarget(EntityInterface $entity, TargetInterface $target, $target_name) {
if (!$target
->isMutable()) {
// Don't clear immutable targets.
return;
}
$entity_target = $entity;
// If the target implements TranslatableTargetInterface and has a language
// configured, empty the value for the targeted language only.
// In all other cases, empty the target for the entity in the default
// language or just the whole target if the entity isn't translatable.
if ($entity instanceof TranslatableInterface && $target instanceof TranslatableTargetInterface && $entity
->isTranslatable()) {
// We expect the target to return a langcode. If it doesn't return one, we
// expect that the target for the entity in the default language must be
// emptied.
$langcode = $target
->getLangcode();
if ($langcode) {
// Langcode exists, check if the entity is available in that language.
if ($entity
->hasTranslation($langcode)) {
$entity_target = $entity
->getTranslation($langcode);
}
else {
// Entity hasn't got a translation in the given langcode yet, so we
// don't need to empty anything.
return;
}
}
}
unset($entity_target->{$target_name});
}
/**
* {@inheritdoc}
*
* @todo Sort this out so that we aren't calling \Drupal::database()->delete()
* here.
*/
public function onFeedDeleteMultiple(array $feeds) {
$fids = [];
foreach ($feeds as $feed) {
$fids[] = $feed
->id();
}
$table = $this
->entityType() . '__feeds_item';
\Drupal::database()
->delete($table)
->condition('feeds_item_target_id', $fids, 'IN')
->execute();
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
// Add dependency on entity type.
$entity_type = $this->entityTypeManager
->getDefinition($this
->entityType());
$this
->addDependency('module', $entity_type
->getProvider());
// Add dependency on entity bundle.
if ($this
->bundle()) {
$bundle_dependency = $entity_type
->getBundleConfigDependency($this
->bundle());
$this
->addDependency($bundle_dependency['type'], $bundle_dependency['name']);
}
// For the 'update_non_existent' setting, add dependency on selected action.
switch ($this
->getConfiguration('update_non_existent')) {
case static::KEEP_NON_EXISTENT:
case static::DELETE_NON_EXISTENT:
// No dependency to add.
break;
default:
try {
$definition = \Drupal::service('plugin.manager.action')
->getDefinition($this
->getConfiguration('update_non_existent'));
if (isset($definition['provider'])) {
$this
->addDependency('module', $definition['provider']);
}
} catch (PluginNotFoundException $e) {
// It's possible that the selected action plugin no longer exists. Log
// an error about it.
\Drupal::logger('feeds')
->warning('The selected option for the setting "Previously imported items" in the feed type %feed_type_id no longer exists. Please edit the feed type and select a different option for that setting.', [
'%feed_type_id' => $this->feedType
->id(),
]);
}
break;
}
return $this->dependencies;
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
DependencyTrait:: |
protected | property | The object's dependencies. | |
DependencyTrait:: |
protected | function | Adds multiple dependencies. | |
DependencyTrait:: |
protected | function | Adds a dependency. | |
EntityProcessorBase:: |
protected | property | The entity info for the selected entity type. | |
EntityProcessorBase:: |
protected | property | The entity type bundle info. | |
EntityProcessorBase:: |
protected | property | The entity type manager. | |
EntityProcessorBase:: |
protected | property | Flag indicating that this processor is locked. | |
EntityProcessorBase:: |
protected | property | The language manager. | |
EntityProcessorBase:: |
protected | property | The entity storage controller for the entity type being processed. | |
EntityProcessorBase:: |
public | function | ||
EntityProcessorBase:: |
public | function | Bundle type this processor operates on. | |
EntityProcessorBase:: |
public | function | The entity's bundle key. | |
EntityProcessorBase:: |
public | function | Returns the bundle label for the entity being processed. | |
EntityProcessorBase:: |
public | function | Provides a list of bundle options for use in select lists. | |
EntityProcessorBase:: |
public | function |
Calculates dependencies for the configured plugin. Overrides PluginBase:: |
|
EntityProcessorBase:: |
public | function |
Applies an action to an entity to 'clean' it. Overrides CleanableInterface:: |
|
EntityProcessorBase:: |
public | function |
Removes all stored results for a feed. Overrides ClearableInterface:: |
|
EntityProcessorBase:: |
protected | function | Clears the target on the entity. | |
EntityProcessorBase:: |
public static | function |
Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface:: |
|
EntityProcessorBase:: |
public | function |
Gets default configuration for this plugin. Overrides PluginBase:: |
|
EntityProcessorBase:: |
protected | function | ||
EntityProcessorBase:: |
protected | function | Checks if the entity exists already. | |
EntityProcessorBase:: |
public | function |
Returns the current language for entities. Overrides EntityProcessorInterface:: |
|
EntityProcessorBase:: |
protected | function | ||
EntityProcessorBase:: |
public | function | ||
EntityProcessorBase:: |
public | function | Returns the label of the entity type being processed. | |
EntityProcessorBase:: |
public | function | Returns the plural label of the entity type being processed. | |
EntityProcessorBase:: |
protected | function | ||
EntityProcessorBase:: |
protected | function | Returns an existing entity id. | |
EntityProcessorBase:: |
public | function |
Deletes feed items older than REQUEST_TIME - $time. Overrides ProcessorInterface:: |
|
EntityProcessorBase:: |
public | function |
Returns the age of items that should be removed. Overrides ProcessorInterface:: |
|
EntityProcessorBase:: |
public | function |
Returns a translation of the given entity. Overrides EntityProcessorInterface:: |
|
EntityProcessorBase:: |
public | function |
Returns feed item ID's to expire. Overrides ProcessorInterface:: |
|
EntityProcessorBase:: |
public | function |
Returns a list of ID's of entities that were imported. Overrides ProcessorInterface:: |
|
EntityProcessorBase:: |
public | function |
Counts the number of items imported by this processor. Overrides ProcessorInterface:: |
|
EntityProcessorBase:: |
public | function |
Returns the label for items being created, updated, or deleted. Overrides ProcessorInterface:: |
|
EntityProcessorBase:: |
public | function |
Returns the plural label for items being created, updated, or deleted. Overrides ProcessorInterface:: |
|
EntityProcessorBase:: |
protected | function | Creates an MD5 hash of an item. | |
EntityProcessorBase:: |
protected | function | Initializes the list of entities to clean. | |
EntityProcessorBase:: |
public | function |
Returns whether or not this plugin is locked. Overrides LockableInterface:: |
|
EntityProcessorBase:: |
public | function | Provides a list of languages available on the site. | |
EntityProcessorBase:: |
protected | function | Execute mapping on an item. | |
EntityProcessorBase:: |
public | function |
Alter mapping form. Overrides MappingPluginFormInterface:: |
|
EntityProcessorBase:: |
public | function |
Submit handler for the mapping form. Overrides MappingPluginFormInterface:: |
|
EntityProcessorBase:: |
public | function |
Validate handler for the mapping form. Overrides MappingPluginFormInterface:: |
|
EntityProcessorBase:: |
protected | function | ||
EntityProcessorBase:: |
public | function |
@todo Sort this out so that we aren't calling \Drupal::database()->delete()
here. Overrides PluginBase:: |
|
EntityProcessorBase:: |
public | function |
The feed type is being deleted. Overrides PluginBase:: |
|
EntityProcessorBase:: |
public | function |
The feed type is being saved. Overrides PluginBase:: |
|
EntityProcessorBase:: |
protected | function | Prepares the feeds_item field. | |
EntityProcessorBase:: |
public | function |
Processes the results from a parser. Overrides ProcessorInterface:: |
|
EntityProcessorBase:: |
protected | function | Deletes the feeds_item field. | |
EntityProcessorBase:: |
public | function |
Constructs an EntityProcessorBase object. Overrides PluginBase:: |
|
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The importer this plugin is working for. | |
PluginBase:: |
protected | property | The link generator. | |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
protected | property | The url generator. | |
PluginBase:: |
private | function | Returns the service container. | |
PluginBase:: |
public | function |
Returns default feed configuration. Overrides FeedsPluginInterface:: |
3 |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets this plugin's configuration. Overrides ConfigurableInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
PluginBase:: |
protected | function | Renders a link to a route given a route name and its parameters. | |
PluginBase:: |
protected | function | Returns the link generator service. | |
PluginBase:: |
public | function | A feed is being saved. | |
PluginBase:: |
public | function |
Returns the type of plugin. Overrides FeedsPluginInterface:: |
|
PluginBase:: |
public | function |
Sets the configuration for this plugin instance. Overrides ConfigurableInterface:: |
1 |
PluginBase:: |
protected | function | Generates a URL or path for a specific route based on the given parameters. | |
PluginBase:: |
protected | function | Returns the URL generator service. | |
ProcessorBase:: |
public | function | ||
ProcessorBase:: |
public | function |
Called after an import is completed. Overrides ProcessorInterface:: |
|
ProcessorInterface:: |
constant | Delete items that no longer exist in the feed. | ||
ProcessorInterface:: |
constant | Feed items should never be expired. | ||
ProcessorInterface:: |
constant | Create new items from Feed. | ||
ProcessorInterface:: |
constant | Keep items that no longer exist in the feed. | ||
ProcessorInterface:: |
constant | Replace items that exist already. | ||
ProcessorInterface:: |
constant | Skip items that exist already. | ||
ProcessorInterface:: |
constant | Skip new items from feed. | ||
ProcessorInterface:: |
constant | Update items that exist already. | ||
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |