cms_content_sync-3.0.x-dev/src/PullIntent.php
src/PullIntent.php
<?php
namespace Drupal\cms_content_sync;
use Drupal\cms_content_sync\Controller\ContentSyncSettings;
use Drupal\cms_content_sync\Controller\LoggerProxy;
use Drupal\cms_content_sync\Controller\UpdateLock;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Entity\Pool;
use Drupal\cms_content_sync\Event\AfterEntityPull;
use Drupal\cms_content_sync\Exception\SyncException;
use Drupal\cms_content_sync\Plugin\cms_content_sync\entity_handler\DefaultTaxonomyHandler;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\TranslatableInterface;
/**
*
*/
class PullIntent extends SyncIntent {
/**
* @var string PULL_DISABLED
* Disable pull completely for this entity type, unless forced.
* - used as a configuration option
* - not used as $action
*/
public const PULL_DISABLED = 'disabled';
/**
* @var string PULL_AUTOMATICALLY
* Automatically pull all entities of this entity type.
* - used as a configuration option
* - used as $action
*/
public const PULL_AUTOMATICALLY = 'automatically';
/**
* @var string PULL_MANUALLY
* Pull only some of these entities, chosen manually.
* - used as a configuration option
* - used as $action
*/
public const PULL_MANUALLY = 'manually';
/**
* @var string PULL_AS_DEPENDENCY
* Pull only some of these entities, pulled if other pulled entities
* use it.
* - used as a configuration option
* - used as $action
*/
public const PULL_AS_DEPENDENCY = 'dependency';
/**
* @var string PULL_FORCED
* Force the entity to be pulled (as long as a handler is also selected).
* Can be used programmatically for custom workflows.
* - not used as a configuration option
* - used as $action
*/
public const PULL_FORCED = 'forced';
/**
* @var string PULL_UPDATE_IGNORE
* Ignore all incoming updates
*/
public const PULL_UPDATE_IGNORE = 'ignore';
/**
* @var string PULL_UPDATE_FORCE
* Overwrite any local changes on all updates
*/
public const PULL_UPDATE_FORCE = 'force';
/**
* @var string PULL_UPDATE_FORCE_AND_FORBID_EDITING
* Pull all changes and forbid local editors to change the content
*/
public const PULL_UPDATE_FORCE_AND_FORBID_EDITING = 'force_and_forbid_editing';
/**
* @var string PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN
* Pull all changes and forbid local editors to change the content unless
* they check the "override" checkbox. As long as that is checked, we
* ignore any incoming updates in favor of the local changes.
*/
public const PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN = 'allow_override';
/**
* @var string PULL_UPDATE_UNPUBLISHED
* Pull all changes and as an unpublished revision. Only available for nodes
* that are revisionable.
*/
public const PULL_UPDATE_UNPUBLISHED = 'pull_update_unpublished';
/**
* @var string PULL_FAILED_DIFFERENT_VERSION
* The remote entity type version is different to the local entity type version
*/
public const PULL_FAILED_DIFFERENT_VERSION = 'import_failed_different_version';
/**
* @var string PULL_FAILED_INTERNAL_ERROR
* An internal Content Sync error occurred when trying to pull the entity
*/
public const PULL_FAILED_CONTENT_SYNC_ERROR = 'import_failed_content_sync_error';
/**
* @var string PULL_FAILED_INTERNAL_ERROR
* An unexpected error occurred when trying to pull the entity
*/
public const PULL_FAILED_INTERNAL_ERROR = 'import_failed_internal_error';
/**
* @var string PULL_FAILED_UNKNOWN_POOL
* Soft: The provided Pool doesn't exist
*/
public const PULL_FAILED_UNKNOWN_POOL = 'import_failed_unknown_pool';
/**
* @var string PULL_FAILED_NO_FLOW
* Soft: No Flow is configured to pull this entity
*/
public const PULL_FAILED_NO_FLOW = 'import_failed_no_flow';
/**
* @var string PULL_FAILED_HANDLER_DENIED
* Soft: The pull failed because the handler returned FALSE when executing the pull
*/
public const PULL_FAILED_HANDLER_DENIED = 'import_failed_handler_denied';
/**
* @var array
* Structure:
* [ entity_type_id:string ][ entity_uuid:string ] => message
*/
protected static $noPullMessages = [];
protected $mergeChanges;
protected $isNewTranslation = FALSE;
/**
* @var array
*/
protected $overriddenProperties = [];
/**
* @var array
*/
protected $overriddenTranslatedProperties = [];
/**
* @var \EdgeBox\SyncCore\Interfaces\Syndication\IPullOperation
*/
protected $operation;
/**
* @var \Drupal\cms_content_sync\Plugin\EntityHandlerInterface
*/
protected $handler;
/**
* @var string
*/
protected $individualLanguage;
/**
* Skip unchanged content, based on the last pulled version hash.
*
* @var bool
*/
protected $skipUnchanged = FALSE;
/**
* The parent entity for an embedded entity.
*
* @var \Drupal\Core\Entity\EntityInterface|null
*/
protected $parent = NULL;
/**
* SyncIntent constructor.
*
* @param \Drupal\cms_content_sync\Entity\Flow $flow
* {@see SyncIntent::$sync}.
* @param \Drupal\cms_content_sync\Entity\Pool[] $pools
* {@see SyncIntent::$pool}.
* @param string $reason
* {@see Flow::PUSH_*} or {@see Flow::PULL_*}.
* @param string $action
* {@see ::ACTION_*}.
* @param string $entity_type
* {@see SyncIntent::$entityType}.
* @param string $bundle
* {@see SyncIntent::$bundle}.
* @param \EdgeBox\SyncCore\Interfaces\Syndication\IPullOperation $operation
* The data provided from Sync Core for pulls.
* Format is the same as in ::getData()
* @param null $parent_type
* @param null $parent_uuid
* @param null|mixed $individual_language
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function __construct(Flow $flow, array $pools, $reason, $action, $entity_type, $bundle, $operation, $individual_language = NULL, ?EntityInterface $parent = NULL) {
$this->operation = $operation;
// For embedded entities the language may sometimes be different e.g. if
// the embedded entity has not been translated. Then we want to grab it's
// langcode property instead as the language passed to this constructor
// will be the parent entity's language / request language.
$langcode = $this->operation->getProperty('langcode');
while (is_array($langcode)) {
$langcode = reset($langcode);
}
if ($individual_language && $langcode && is_string($langcode)) {
$individual_language = $langcode;
}
if (EntityHandlerPluginManager::mapById($entity_type)) {
$entity_id = $this->operation->getId();
}
else {
$entity_id = NULL;
}
parent::__construct(
$flow,
$pools,
$reason,
$action,
$entity_type,
$bundle,
$this->operation->getUuid(),
$entity_id,
$this->operation->getSourceUrl(),
(bool) $individual_language
);
$this->individualLanguage = $individual_language;
if ($this->operation->getSourceUrl()) {
$this->entity_status->setSourceUrl($this->operation->getSourceUrl());
}
if ($translations = $this->operation->getUsedTranslationLanguages()) {
$root_url = $this->operation->getSourceUrl();
foreach ($translations as $language) {
$url = $this->operation->getSourceUrl($language);
if (!$url || $url === $root_url) {
continue;
}
$this->entity_status->setTranslationSourceUrl($language, $url);
}
}
if ($parent) {
$this->parent = $parent;
$this->entity_status->wasPulledEmbedded(TRUE);
$this->entity_status->setParentEntity($parent->getEntityTypeId(), $parent->uuid());
}
else {
$this->entity_status->wasPulledEmbedded(FALSE);
}
$this->mergeChanges = PullIntent::PULL_UPDATE_FORCE_UNLESS_OVERRIDDEN == $this->flow->getController()->getEntityTypeConfig($this->entityType, $this->bundle)['import_updates']
&& $this->entity_status->isOverriddenLocally();
}
/**
* Get the parent of the embedded entity.
*
* @return \Drupal\Core\Entity\EntityInterface|null
*/
public function getParent() {
return $this->parent;
}
/**
*
*/
public function setSkipUnchanged(bool $set) {
$this->skipUnchanged = $set;
}
/**
*
*/
public function getSkipUnchanged() {
return $this->skipUnchanged;
}
/**
*
*/
public static function setNoPullMessage(string $entity_type, string $shared_entity_id, $message) {
self::$noPullMessages[$entity_type][$shared_entity_id] = $message;
}
/**
*
*/
public static function getNoPullMessage(string $entity_type, string $shared_entity_id) {
return self::$noPullMessages[$entity_type][$shared_entity_id] ?? NULL;
}
protected static $pullResults = [];
/**
* Increase the update counter.
*/
public static function addPullResult(string $result) {
if (empty(self::$pullResults[$result])) {
self::$pullResults[$result] = 1;
}
else {
return self::$pullResults[$result]++;
}
}
/**
*
*/
public static function getPullResults() {
return self::$pullResults;
}
/**
*
*/
public function setIgnoreMessage($message) {
self::setNoPullMessage($this->entityType, $this->getSharedId(), $message);
}
/**
* @return bool
*/
public function wasPulledEmbedded() {
return $this->entity_status->wasPulledEmbedded();
}
/**
* @return \EdgeBox\SyncCore\Interfaces\Syndication\IPullOperation
*/
public function getOperation() {
return $this->operation;
}
/**
* Mark the given dependency as missing so it's automatically resolved whenever it gets pulled.
*
* @param array $definition
* @param null|string $field
* @param null|array $data
*/
public function saveUnresolvedDependency($definition, $field = NULL, $data = NULL) {
$reference = $this->operation->loadReference($definition);
// User references are ignored.
if (!$reference->getType() || (!$reference->getId() && !$reference->getUuid())) {
return;
}
MissingDependencyManager::saveUnresolvedDependency(
$reference->getType(),
$reference->getId() ? $reference->getId() : $reference->getUuid(),
$this->getEntity(),
$this->getReason(),
$field,
$data
);
}
/**
* @return bool
*/
public function shouldMergeChanges($ignore_new_translation = FALSE) {
return $this->mergeChanges && ($ignore_new_translation || !$this->isNewTranslation);
}
/**
*
*/
public function setIsNewTranslation($set) {
$this->isNewTranslation = $set;
}
/**
*
*/
public function getIsNewTranslation() {
return $this->isNewTranslation;
}
/**
*
*/
public function getAction() {
$action = parent::getAction();
if (SyncIntent::ACTION_UPDATE === $action && $this->isNewTranslation) {
return SyncIntent::ACTION_CREATE;
}
return $action;
}
/**
*
*/
public function getViewUrl() {
if (!$this->handler) {
$config = $this->flow->getController()->getEntityTypeConfig($this->entityType, $this->bundle);
$handler = $this->flow->getController()->getEntityTypeHandler($this->entityType, $this->bundle, $config);
return $handler->getViewUrl($this->getEntity());
}
return $this->handler->getViewUrl($this->getEntity());
}
/**
*
*/
public function getVersionId(bool $including_translations) {
return $this->operation->getVersionId($this->getActiveLanguage(), $including_translations);
}
/**
* Pull the provided entity.
*
* @throws Exception\SyncException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @return bool
*/
public function execute() {
// Skip pulling the entity multiple times. E.g. if an entity is referenced
// by a parent entity multiple times, we don't need to update it again
// in the same run.
if (self::entityHasBeenPulledFromRemoteSite($this->entityType, $this->uuid)) {
return TRUE;
}
$pull = reset($this->pools)->getNewestTimestamp($this->entityType, $this->uuid, TRUE, $this->pools);
if (!$pull) {
if (SyncIntent::ACTION_UPDATE == $this->action) {
$this->action = SyncIntent::ACTION_CREATE;
}
}
elseif (SyncIntent::ACTION_CREATE == $this->action) {
$this->action = SyncIntent::ACTION_UPDATE;
}
$pull = time();
$config = $this->flow->getController()->getEntityTypeConfig($this->entityType, $this->bundle);
$handler = $this->flow->getController()->getEntityTypeHandler($this->entityType, $this->bundle, $config);
$this->handler = $handler;
self::entityHasBeenPulledFromRemoteSite($this->entityType, $this->uuid, TRUE);
// As paragraphs are embedded, they are only updated when the parent is
// updated. So we don't need to add them explicitly becasuse their parent
// will be locked and fail to update already. This only works for 1:1
// relationships. E.g. media can be re-used in different content items,
// so can't be optimized even if it's only ever pulled as a dependency.
// This saves us some performance during updates.
$requires_lock = $this->entityType !== 'paragraph';
if ($requires_lock) {
if (UpdateLock::isLocked($this->entityType, $this->getSharedId())) {
$unlocked = FALSE;
// If this is an embedded it could be e.g. two nodes using the same
// media item being published simultaneously. An entity update typically
// takes less than 1 second to complete, so even if it's nested, it
// should be finished within 2 seconds in most cases.
// By waiting a little bit and retrying, we can eliminate most update
// locks for nested entities and avoid retries / increase throughput
// without sacrificing safety.
if ($this->getParent()) {
$this->startTimer('lock-wait');
// Retry for up to 8 seconds.
for ($i = 0; $i < 7; $i++) {
// Wait for 2 seconds the first time, then another second at every
// consecutive retry.
sleep($i ? 1 : 2);
$unlocked = !UpdateLock::isLocked($this->entityType, $this->getSharedId());
if ($unlocked) {
break;
}
}
$this->stopTimer('lock-wait');
}
if (!$unlocked) {
throw new SyncException(SyncException::CODE_UPDATE_LOCK, NULL, "The " . $this->entityType . " " . $this->getSharedId() . " is locked for updates, so another update is still running. Please retry in " . UpdateLock::getLockExpiration() . "s.");
}
}
UpdateLock::lock($this->entityType, $this->getSharedId());
}
else {
UpdateLock::renew();
}
try {
$result = $handler->pull($this);
}
catch (\Exception $e) {
if ($requires_lock) {
UpdateLock::unlock($this->entityType, $this->getSharedId());
}
throw $e;
}
$new_version_with_translations = $this->getVersionId(TRUE);
$new_version = $this->getVersionId(FALSE);
LoggerProxy::get()->info('@not PULL @duration @action @entity_type:@bundle @uuid @ids @reason: @message<br>Flow: @flow_id | Pool: @pool_id | Timers: @timers | Version: @version_with_translations:@version | Language: @language', [
'@reason' => $this->reason,
'@action' => $this->action,
'@duration' => $this->formatDuration(),
'@timers' => $this->formatTimers(),
'@version_with_translations' => $new_version_with_translations ?? '-',
'@version' => $new_version ?? '-',
'@language' => $this->individualLanguage ?? '-',
'@entity_type' => $this->entityType,
'@bundle' => $this->bundle,
'@uuid' => $this->uuid,
'@not' => $result ? '' : 'NO',
'@message' => $result ? t('The entity has been pulled.') : (self::getNoPullMessage($this->entityType, $this->getSharedId()) ?? t('The entity handler denied to pull this entity.')),
'@flow_id' => $this->getFlow()->id(),
'@pool_id' => implode(',', $this->getPoolIds()),
'@ids' => (!empty($this->entity) ? ($this->entity->getEntityType()->isRevisionable() ? "entity_id:{$this->entity->id()} revision_id:{$this->entity->getRevisionId()}" : "entity_id:{$this->entity->id()}") : ('')),
]);
if ($result) {
self::addPullResult('updateCount');
}
else {
if ($requires_lock) {
UpdateLock::unlock($this->entityType, $this->getSharedId());
}
self::addPullResult('ignoreCount');
// Don't save entity_status entity if entity wasn't pulled anyway.
return FALSE;
}
// Need to save after setting timestamp to prevent exception.
$this->entity_status->setLastPull($pull, $this->individualLanguage);
reset($this->pools)->setTimestamp($this->entityType, $this->uuid, $pull, TRUE, $this->pools, $this->individualLanguage);
$this->entity_status->isDeleted(SyncIntent::ACTION_DELETE == $this->action);
$this->entity_status->save();
if (SyncIntent::ACTION_DELETE == $this->action) {
reset($this->pools)->markDeleted($this->entityType, $this->uuid, $this->pools);
}
$entity = $this->getEntity();
// Dispatch EntityExport event to give other modules the possibility to react on it.
// Ignore deleted entities.
if ($entity) {
\Drupal::service('event_dispatcher')->dispatch(new AfterEntityPull($entity, $this), AfterEntityPull::EVENT_NAME);
}
if ($requires_lock) {
UpdateLock::unlock($this->entityType, $this->getSharedId());
}
// Handle Extended Entity Import logging.
$settings = ContentSyncSettings::getInstance();
if ($settings->getExtendedEntityImportLogging()) {
$url = NULL;
if ($entity && $entity->hasLinkTemplate('canonical')) {
$url = $entity->toUrl('canonical', ['absolute' => TRUE])
->toString(TRUE)
->getGeneratedUrl();
}
$serializer = \Drupal::service('serializer');
$data = $serializer->serialize($this->getOperation()->getResponseBody($url), 'json', ['plugin_id' => 'entity']);
\Drupal::logger('cms_content_sync_entity_import_log')->debug('%entity_type - %uuid <br>Data: <br><pre><code>%data</code></pre>', [
'%entity_type' => $entity->getEntityTypeId(),
'%uuid' => $entity->uuid(),
'%data' => $data,
]);
}
$this->resolveMissingDependencies();
return TRUE;
}
/**
* Check if the provided entity has just been pulled by Sync Core in this
* very request. In this case it doesn't make sense to perform a remote
* request telling Sync Core it has been created/updated/deleted
* (it will know as a result of this current request).
*
* @param string $entity_type
* The entity type.
* @param string $entity_uuid
* The entity UUID.
* @param bool $set
* If TRUE, this entity will be set to have been pulled at this request.
*
* @return bool
*/
public static function entityHasBeenPulledFromRemoteSite($entity_type = NULL, $entity_uuid = NULL, $set = FALSE) {
static $entities = [];
if (!$entity_type) {
return !empty($entities);
}
if ($set) {
return $entities[$entity_type][$entity_uuid] = TRUE;
}
return !empty($entities[$entity_type][$entity_uuid]);
}
/**
* Get the reference meta-data for the given field. If you want to load an
* entity by reference, use `::loadEmbeddedEntity()` instead.
*
* @param array $definition
* The definition you saved in a field and gotten
* back when calling one of the mentioned functions above.
*
* @return null|\EdgeBox\SyncCore\Interfaces\Syndication\IEntityReference
*/
public function loadReference($definition) {
return $this->operation->loadReference($definition);
}
/**
* Restore a file that was added via
* {@see SyncIntent::embedEntityDefinition} or
* {@see SyncIntent::embedEntity} but only given the file URI, e.g. because
* it is embedded within a text field.
*
* @param string $uri
* The File URI.
*
* @return \Drupal\Core\Entity\EntityInterface|null the restored entity
*/
public function loadEmbeddedFile($uri) {
$props = [
'uri' => [
0 => [
'value' => $uri,
],
],
];
$files = $this->operation->loadReferencesByProperties($props);
if (empty($files)) {
return NULL;
}
$reference = reset($files);
return $this->loadEmbeddedEntity([], $reference);
}
/**
* Restore an entity that was added via
* {@see SyncIntent::embedEntityDefinition} or
* {@see SyncIntent::embedEntity}.
*
* @param array $definition
* The definition you saved in a field and gotten
* back when calling one of the mentioned functions above.
* @param null|mixed $reference
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
* @throws \Drupal\cms_content_sync\Exception\SyncException
*
* @return \Drupal\Core\Entity\EntityInterface the restored entity
*/
public function loadEmbeddedEntity($definition, $reference = NULL) {
$start = microtime(TRUE);
if (!$reference) {
$reference = $this->operation->loadReference($definition);
if (empty($reference) || !$reference->getType() || !$reference->getBundle()) {
return NULL;
}
}
if ($reference->isEmbedded()) {
$embedded_entity = $reference->getEmbeddedEntity();
if (empty($embedded_entity)) {
return NULL;
}
$entity_type_name = $reference->getType();
$entity_bundle = $reference->getBundle();
$flow = $this->flow;
$pool_ids = $reference->getPoolIds();
$all_pools = Pool::getAll();
$ideal_pools = [];
foreach ($pool_ids as $pool_id) {
if (isset($all_pools[$pool_id])) {
$ideal_pools[$pool_id] = $all_pools[$pool_id];
}
}
// The pools we actually use for the pull operation below.
$pools = [];
// Prefer the current Flow if possible for at least one of the given Pools.
if (!$flow->getController()->canPullEntity($entity_type_name, $entity_bundle, PullIntent::PULL_AS_DEPENDENCY, SyncIntent::ACTION_CREATE)) {
$flow = NULL;
}
else {
$allowed_pools = $flow->getController()->getUsedPoolsForPulling($entity_type_name, $entity_bundle);
foreach ($pool_ids as $pool_id) {
if (isset($all_pools[$pool_id]) && in_array($all_pools[$pool_id], $allowed_pools)) {
$pools[] = $all_pools[$pool_id];
}
}
// Current Flow doesn't match for any of the Pools, so can't be used.
if (empty($pools)) {
$flow = NULL;
}
}
// Otherwise fall back to any Flow that can handle any of the given Pools.
if (!$flow) {
foreach ($ideal_pools as $pool) {
$flow = Flow::getFlowForPoolAndEntityType($pool, $entity_type_name, $entity_bundle, PullIntent::PULL_AS_DEPENDENCY, SyncIntent::ACTION_CREATE);
if ($flow) {
$allowed_pools = $flow->getController()->getUsedPoolsForPulling($entity_type_name, $entity_bundle);
foreach ($pool_ids as $pool_id) {
if (isset($all_pools[$pool_id]) && in_array($all_pools[$pool_id], $allowed_pools)) {
$pools[] = $all_pools[$pool_id];
}
}
break;
}
}
}
if (!$flow || empty($pools)) {
return NULL;
}
$intent = new PullIntent($flow, $pools, PullIntent::PULL_AS_DEPENDENCY, SyncIntent::ACTION_CREATE, $entity_type_name, $entity_bundle, $embedded_entity, $this->individualLanguage, $this->getEntity());
$intent->setSkipUnchanged($this->skipUnchanged);
$result = $intent->execute();
$this->childTime += microtime(TRUE) - $start;
if ($result) {
return $intent->getEntity();
}
}
if (empty($reference->getId())) {
$entity = \Drupal::service('entity.repository')->loadEntityByUuid(
$reference->getType(),
$reference->getUuid()
);
}
else {
$entity = \Drupal::entityTypeManager()->getStorage($reference->getType())->load($reference->getId());
}
// Taxonomy terms can be mapped by their name.
if (!$entity && !empty($reference->getName())) {
$config = $this->flow->getController()->getEntityTypeConfig($reference->getType(), $reference->getBundle());
if (!empty($config) && !empty($config['handler_settings'][DefaultTaxonomyHandler::MAP_BY_LABEL_SETTING])) {
$entity_type = \Drupal::entityTypeManager()->getDefinition($reference->getType());
$label_property = $entity_type->getKey('label');
$existing = \Drupal::entityTypeManager()->getStorage($reference->getType())->loadByProperties([
$label_property => $reference->getName(),
]);
$entity = reset($existing);
}
}
return $entity;
}
/**
* Get all embedded entity data besides the predefined keys.
* Images for example have "alt" and "title" in addition to the file reference.
*
* @param $definition
*
* @return array
*/
public function getEmbeddedEntityData($definition) {
$reference = $this->operation->loadReference($definition);
return $reference->getDetails();
}
/**
* Get all field values at once for the currently active language.
*
* @return array all field values for the active language
*/
public function getOverriddenProperties() {
if ($this->activeLanguage) {
if (!isset($this->overriddenTranslatedProperties[$this->activeLanguage])) {
return NULL;
}
return $this->overriddenTranslatedProperties[$this->activeLanguage];
}
return $this->overriddenProperties;
}
/**
* Provide the value of a field you stored when pushing by using.
*
* @see SyncIntent::setField()
*
* @param string $name
* The name of the field to restore.
*
* @return mixed the value you stored for this field
*/
public function getProperty($name) {
$overridden = $this->getOverriddenProperties();
return $overridden[$name] ?? $this->operation->getProperty($name, $this->activeLanguage);
}
/**
* Overwrite the value for the given field to preprocess the values or access
* them at a later stage.
*
* @param string $name
* The name of the field in question.
* @param mixed $value
* The value to store.
*/
public function overwriteProperty($name, $value) {
if ($this->activeLanguage) {
$this->overriddenTranslatedProperties[$this->activeLanguage][$name] = $value;
return;
}
$this->overriddenProperties[$name] = $value;
}
/**
* Get all languages for field translations that are currently used.
*/
public function getTranslationLanguages() {
return $this->operation->getUsedTranslationLanguages();
}
/**
*
*/
public function setEntity($entity) {
parent::setEntity($entity);
if (!$this->entity) {
return;
}
if ($this->isIndividualTranslation()) {
if ($this->entity instanceof TranslatableInterface) {
if ($this->entity->language()->getId() !== $this->individualLanguage) {
$exists = $this->entity->hasTranslation($this->individualLanguage);
$this->entity = $exists
? $this->entity->getTranslation($this->individualLanguage)
: $this->entity->addTranslation($this->individualLanguage);
$this->setIsNewTranslation(!$exists);
}
}
}
return $this->entity;
}
/**
* Resolve all references to the entity that has just been pulled if they're missing at other content.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function resolveMissingDependencies() {
// Ignore deleted entities.
if ($this->getEntity()) {
MissingDependencyManager::resolveDependencies($this->getEntity());
}
}
}
