cms_content_sync-3.0.x-dev/src/Controller/FlowControllerBase.php
src/Controller/FlowControllerBase.php
<?php
namespace Drupal\cms_content_sync\Controller;
use Drupal\cms_content_sync\Entity\EntityStatus;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Entity\Pool;
use Drupal\cms_content_sync\PullIntent;
use Drupal\cms_content_sync\PushIntent;
use Drupal\cms_content_sync\SyncIntent;
use Drupal\Core\Entity\EntityInterface;
/**
* The Flow controller base.
*/
class FlowControllerBase {
/**
* The Content Sync flow entity.
*
* @var \Drupal\cms_content_sync\Entity\Flow
*/
protected $flow;
/**
* Constructor.
*/
public function __construct(Flow $flow) {
$this->flow = $flow;
}
/**
* Get the flow type.
*
* @return null|string
* Returns the flow type.
*/
public function getType() {
return $this->flow->type;
}
/**
* The entity types to be pulled.
*
* @return array
* Returns the entity types to be pulled.
*/
public function getEntityTypesToPull($pull_type = NULL) {
$pulled_entity_types = [];
$entity_types = $this->getEntityTypeConfig();
foreach ($entity_types as $entity_type_name => $bundles) {
foreach ($bundles as $bundle_name => $config) {
if (is_null($pull_type) ? PullIntent::PULL_DISABLED != $config['import'] : $config['import'] == $pull_type) {
$pulled_entity_types[$entity_type_name][$bundle_name] = $config;
}
}
}
return $pulled_entity_types;
}
/**
* Check if an entity type can be pushed.
*/
public function canPushEntityType($entity_type_name, $bundle_name, $reason, $action = SyncIntent::ACTION_CREATE, $pool = NULL) {
static $any_reason = [
PushIntent::PUSH_AUTOMATICALLY,
PushIntent::PUSH_MANUALLY,
PushIntent::PUSH_AS_DEPENDENCY,
];
static $independent_reason = [
PushIntent::PUSH_AUTOMATICALLY,
PushIntent::PUSH_MANUALLY,
];
if ($this->flow->type === Flow::TYPE_PULL) {
return FALSE;
}
if (is_string($reason)) {
if (PushIntent::PUSH_ANY === $reason) {
$reason = $any_reason;
}
elseif (PushIntent::PUSH_FORCED === $reason) {
$reason = $independent_reason;
}
else {
$reason = [$reason];
}
}
if (!$bundle_name) {
foreach ($this->getEntityTypeConfig($entity_type_name) as $bundle_name => $config) {
if ($this->canPushEntityType($entity_type_name, $bundle_name, $reason, $action, $pool)) {
return TRUE;
}
}
return FALSE;
}
$config = $this->getEntityTypeConfig($entity_type_name, $bundle_name);
if (empty($config) || Flow::HANDLER_IGNORE == $config['handler']) {
return FALSE;
}
if (PushIntent::PUSH_DISABLED == $config['export']) {
return FALSE;
}
if (SyncIntent::ACTION_DELETE == $action && !boolval($config['export_deletion_settings']['export_deletion'])) {
return FALSE;
}
if ($pool) {
if (empty($config['export_pools'][$pool->id]) || Pool::POOL_USAGE_FORBID == $config['export_pools'][$pool->id]) {
return FALSE;
}
}
// If this has not been exported yet, we can't push the entity.
if (empty($config['version'])) {
return FALSE;
}
return in_array($config['export'], $reason);
}
/**
* Check if an Entity can be pushed.
*/
public function canPushEntity(EntityInterface $entity, $reason, $action = SyncIntent::ACTION_CREATE, $pool = NULL) {
if ($this->flow->type === Flow::TYPE_PULL) {
return FALSE;
}
$infos = $entity->uuid() ? EntityStatus::getInfosForEntity(
$entity->getEntityTypeId(),
$entity->uuid(),
[
'flow' => $this->flow->id(),
]
) : [];
// Fresh entity- no pool restriction.
if (!count($infos) || NULL !== $pool) {
return $this->canPushEntityType($entity->getEntityTypeId(), $entity->bundle(), $reason, $action, $pool);
}
// If the entity has been pulled or pushed before, only the Flows that support the pools that were assigned
// are relevant. So we filter out any Flows here that don't support any of the assigned pools.
foreach ($infos as $info) {
if ($this->canPushEntityType($entity->getEntityTypeId(), $entity->bundle(), $reason, $action, $info->getPool())) {
return TRUE;
}
}
// Flow config may have changed so status entities exist but now they no longer push the entity. In this case we
// fall back into the behavior as if the entity was new (see above)
return $this->canPushEntityType($entity->getEntityTypeId(), $entity->bundle(), $reason, $action, $pool);
}
/**
* Check if an entity can be added as an dependency.
*/
public function canAddEntityAsDependency(EntityInterface $entity) {
$settings = $this->getEntityTypeConfig($entity->getEntityTypeId(), $entity->bundle());
if (empty($settings)) {
return FALSE;
}
if (PushIntent::PUSH_AS_DEPENDENCY !== $settings['export']) {
return FALSE;
}
return TRUE;
}
/**
* Get pools to push to.
*
* Get a list of all pools that are used for pushing this entity, either
* automatically or manually selected.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that should be pushed.
* @param string|string[] $reason
* {@see Flow::PUSH_*}.
* @param string $action
* {@see ::ACTION_*}.
* @param bool $include_forced
* Include forced pools. Otherwise only use-selected / referenced ones.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*
* @return \Drupal\cms_content_sync\Entity\Pool[]
* The pools the entity should be pushed to.
*/
public function getPoolsToPushTo(EntityInterface $entity, $reason, $action, $include_forced = TRUE) {
$config = $this->getEntityTypeConfig($entity->getEntityTypeId(), $entity->bundle());
if (!$this->canPushEntity($entity, $reason, $action)) {
return [];
}
$result = [];
$pools = Pool::getAll();
foreach ($config['export_pools'] as $id => $setting) {
if (!isset($pools[$id])) {
continue;
}
$pool = $pools[$id];
if (Pool::POOL_USAGE_FORBID == $setting) {
continue;
}
if (Pool::POOL_USAGE_FORCE == $setting) {
if ($include_forced) {
$result[$id] = $pool;
}
continue;
}
$entity_status = EntityStatus::getInfoForEntity($entity->getEntityTypeId(), $entity->uuid(), $this->flow, $pool);
if ($entity_status && $entity_status->isPushEnabled()) {
$result[$id] = $pool;
}
}
return $result;
}
/**
* Get used pool for pulling.
*
* Get a list of all pools that are used for pushing this entity, either
* automatically or manually selected.
*
* @param string $entity_type
* The checked entity type.
* @param string $bundle
* The checked entity type bundle.
*
* @return \Drupal\cms_content_sync\Entity\Pool[]
* Returns the used pools.
*/
public function getUsedPoolsForPulling($entity_type, $bundle) {
$config = $this->getEntityTypeConfig($entity_type, $bundle);
if (empty($config['import_pools'])) {
return [];
}
$result = [];
$pools = Pool::getAll();
foreach ($config['import_pools'] as $id => $setting) {
$pool = $pools[$id];
if (Pool::POOL_USAGE_FORBID == $setting) {
continue;
}
$result[] = $pool;
}
return $result;
}
/**
* Get a list of all pools this Flow is using.
*
* @return \Drupal\cms_content_sync\Entity\Pool[]
* The used pools.
*/
public function getUsedPools() {
$result = [];
$pools = Pool::getAll();
foreach ($pools as $id => $pool) {
if ($this->usesPool($pool)) {
$result[$id] = $pool;
}
}
return $result;
}
/**
* Check if the given pool is used by this Flow.
*
* If any handler set the flow as FORCE or ALLOW, this will return TRUE.
*
* @param \Drupal\cms_content_sync\Entity\Pool $pool
* The pool to check.
*
* @return bool
* TRUE if the pool is used by this flow, FALSE otherwise.
*/
public function usesPool(Pool $pool) {
foreach ($this->getEntityTypeConfig(NULL, NULL, TRUE) as $bundles) {
foreach ($bundles as $config) {
if (Flow::HANDLER_IGNORE == $config['handler']) {
continue;
}
if (PushIntent::PUSH_DISABLED != $config['export']) {
if (!empty($config['export_pools'][$pool->id]) && Pool::POOL_USAGE_FORBID != $config['export_pools'][$pool->id]) {
return TRUE;
}
}
if (PullIntent::PULL_DISABLED != $config['import']) {
if (!empty($config['import_pools'][$pool->id]) && Pool::POOL_USAGE_FORBID != $config['import_pools'][$pool->id]) {
return TRUE;
}
}
}
}
return FALSE;
}
/**
* Ask this Flow whether or not it can pull the provided entity.
*
* @param string $entity_type_name
* The name of the entity type to check.
* @param string $bundle_name
* The name of the entity bundle to check.
* @param string $reason
* The reason for the pull.
* @param string $action
* The action to perform on the entity.
* @param bool $strict
* If asking for DEPENDENCY as a $reason, then $strict will NOT include a Flow that pulls AUTOMATICALLY.
*
* @return bool
* Whether or not the entity can be pulled.
*/
public function canPullEntity($entity_type_name, $bundle_name, $reason, $action = SyncIntent::ACTION_CREATE, $strict = FALSE) {
if ($this->flow->type === Flow::TYPE_PUSH) {
return FALSE;
}
$config = $this->getEntityTypeConfig($entity_type_name, $bundle_name);
if (empty($config) || Flow::HANDLER_IGNORE == $config['handler']) {
return FALSE;
}
if (PullIntent::PULL_DISABLED == $config['import']) {
return FALSE;
}
if (SyncIntent::ACTION_DELETE == $action && !boolval($config['import_deletion_settings']['import_deletion'])) {
return FALSE;
}
// If any handler is available, we can pull this entity.
if (PullIntent::PULL_FORCED == $reason) {
return TRUE;
}
// Flows that pull automatically can also handle referenced entities.
if (PullIntent::PULL_AUTOMATICALLY == $config['import']) {
if (PullIntent::PULL_AS_DEPENDENCY == $reason && !$strict) {
return TRUE;
}
}
// Once pulled manually, updates will arrive automatically.
if (PullIntent::PULL_AUTOMATICALLY == $reason && PullIntent::PULL_MANUALLY == $config['import']) {
if (SyncIntent::ACTION_UPDATE == $action || SyncIntent::ACTION_DELETE == $action) {
return TRUE;
}
}
return $config['import'] == $reason;
}
/**
* Ask this synchronization whether it supports the provided entity.
*
* Returns false if either the entity type is not known or the config handler
* is set to {@see Flow::HANDLER_IGNORE}.
*
* @return bool
* Returns true of the entity is supported.
*/
public function supportsEntity(EntityInterface $entity) {
$config = $this->getEntityTypeConfig($entity->getEntityTypeId(), $entity->bundle());
if (empty($config) || empty($config['handler'])) {
return FALSE;
}
return Flow::HANDLER_IGNORE != $config['handler'];
}
/**
* The the entity type handler for the given config.
*
* @param string $entity_type_name
* The entity type name to check for.
* @param string $bundle_name
* The entity type bundle name to check for.
* @param null|array $config
* {@see Flow::getEntityTypeConfig()}.
*
* @return \Drupal\cms_content_sync\Plugin\EntityHandlerInterface
* Returns the entity type handler for the given config.
*/
public function getEntityTypeHandler(string $entity_type_name, string $bundle_name, $config) {
static $cache = [];
$cache_key = join(":", [$this->flow->id(), $entity_type_name, $bundle_name]);
if (isset($cache[$cache_key])) {
return $cache[$cache_key];
}
$entityPluginManager = \Drupal::service('plugin.manager.cms_content_sync_entity_handler');
return $cache[$cache_key] = $entityPluginManager->createInstance(
$config['handler'],
[
'entity_type_name' => $entity_type_name,
'bundle_name' => $bundle_name,
'settings' => $config,
'sync' => $this->flow,
]
);
}
/**
* Get the field handler.
*
* Get the correct field handler instance for this entity type and field
* config.
*
* @param string $entity_type_name
* The entity type name to get the field handler for.
* @param string $bundle_name
* The entity type bundle name to get the field handler for.
* @param string $field_name
* The field name to get the field handler for.
*
* @return \Drupal\cms_content_sync\Plugin\FieldHandlerInterface
* The field handler instance for the given configuration.
*/
public function getFieldHandler($entity_type_name, $bundle_name, $field_name) {
static $cache = [];
$cache_key = join(":", [$this->flow->id(), $entity_type_name, $bundle_name, $field_name]);
if (isset($cache[$cache_key])) {
return $cache[$cache_key];
}
$fieldPluginManager = \Drupal::service('plugin.manager.cms_content_sync_field_handler');
$config = $this->getPropertyConfig($entity_type_name, $bundle_name, $field_name);
if (empty($config)) {
return $cache[$cache_key] = NULL;
}
if (Flow::HANDLER_IGNORE == $config['handler']) {
return $cache[$cache_key] = NULL;
}
$entityFieldManager = \Drupal::service('entity_field.manager');
$field_definition = $entityFieldManager->getFieldDefinitions($entity_type_name, $bundle_name)[$field_name];
return $cache[$cache_key] = $fieldPluginManager->createInstance(
$config['handler'],
[
'entity_type_name' => $entity_type_name,
'bundle_name' => $bundle_name,
'field_name' => $field_name,
'field_definition' => $field_definition,
'settings' => $config,
'sync' => $this->flow,
]
);
}
}
