cms_content_sync-3.0.x-dev/src/SyncCoreFlowExport.php
src/SyncCoreFlowExport.php
<?php
namespace Drupal\cms_content_sync;
use Drupal\cms_content_sync\Controller\ContentSyncSettings;
use Drupal\cms_content_sync\Entity\Flow;
use Drupal\cms_content_sync\Entity\Pool;
use Drupal\cms_content_sync\Event\BeforeEntityTypeExport;
use Drupal\cms_content_sync\Plugin\Type\EntityHandlerPluginManager;
use Drupal\cms_content_sync\SyncCoreInterface\DrupalApplication;
use Drupal\cms_content_sync\SyncCoreInterface\SyncCoreFactory;
use Drupal\Core\Serialization\Yaml;
use EdgeBox\SyncCore\V2\Raw\Model\RemoteEntityTypePropertyFormat;
/**
* Class SyncCoreFlowExport used to export the Synchronization config to the Sync
* Core backend.
*/
class SyncCoreFlowExport extends SyncCoreExport {
protected $subsequent = FALSE;
/**
* @var \Drupal\cms_content_sync\Entity\Flow
*/
protected $flow;
/**
* Sync Core Config constructor.
*
* @param \Drupal\cms_content_sync\Entity\Flow $flow
* The flow this exporter is used for.
* @param mixed $subsequent
*/
public function __construct(Flow $flow, $subsequent = FALSE) {
$pools = $flow->getController()->getUsedPools();
$first = reset($pools);
parent::__construct($first->getClient());
$this->flow = $flow;
$this->subsequent = $subsequent;
}
/**
*
*/
public static function all($machine_names = NULL) {
foreach (Flow::getAll() as $flow) {
if (!$machine_names || in_array($flow->id(), $machine_names)) {
$flow->getController()->updateEntityTypeVersions();
$exporter = new SyncCoreFlowExport($flow, TRUE);
$batch = $exporter->prepareBatch();
$batch->executeAll();
}
}
}
/**
*
*/
public static function deleteUnusedFlows() {
// If the site wasn't even registered, we can skip trying to delete Flows from the Sync Core.
if (!ContentSyncSettings::getInstance()->getSiteUuid()) {
return;
}
// If the module is being uninstalled, we can't communicate with the Sync Core.
if (!DrupalApplication::get()->getSyncCoreUrl()) {
return;
}
$keep_flows = [];
// Get all active Flows and add them to the "keep list".
foreach (Flow::getAll() as $flow) {
$keep_flows[] = $flow->id;
}
$core = SyncCoreFactory::getSyncCoreV2();
// We are deleting all that are not in the "keep list". This is the
// most reliable way to delete all unwanted configuration as we
// may not always be informed about changes of Flows (e.g. when
// customers use a database dump for deployment or setup), and we
// want the Flow status to be in sync to avoid unnecessary requests
// and failures.
$core->getConfigurationService()->deleteFlows($keep_flows);
}
/**
* Create all entity types, connections and synchronizations as required.
*
* @param bool $force
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \EdgeBox\SyncCore\Exception\SyncCoreException
*
* @return \EdgeBox\SyncCore\Interfaces\IBatch
*/
public function prepareBatch($force = FALSE) {
$pool_configurations = [];
$batch = $this
->client
->batch();
$flow_definition = $this
->client
->getConfigurationService()
->defineFlow(
$this->flow->id,
$this->flow->label(),
Yaml::encode(\Drupal::service('config.storage')->read('cms_content_sync.flow.' . $this->flow->id()))
)
->addToBatch($batch);
$allowed_languages = $this->flow->getController()->getAllowedLanguages();
if (!empty($allowed_languages)) {
$flow_definition->allowedLanguages($allowed_languages);
}
$this
->addConfiguration($flow_definition, $pool_configurations, $batch, FALSE, $force);
// Adding these must be done last so that the entity types are always
// defined before being used. Otherwise the Sync Core will throw an exception.
foreach ($pool_configurations as $pool_id => $operations) {
/**
* @var \EdgeBox\SyncCore\Interfaces\IBatchOperation $pool
*/
$pool = $operations['pool_definition'];
$pool->addToBatch($batch);
}
return $batch;
}
/**
* Create all entity types, connections and synchronizations as required.
*
* @param \EdgeBox\SyncCore\Interfaces\Configuration\IDefineFlow $flow_definition
* @param $pool_configurations
* @param \EdgeBox\SyncCore\Interfaces\IBatch $batch
* @param bool $extend_only
* @param bool $force
*
* @throws \EdgeBox\SyncCore\Exception\SyncCoreException
*/
public function addConfiguration($flow_definition, &$pool_configurations, $batch, $extend_only = FALSE, $force = FALSE) {
// Ignore disabled flows at export.
if (!$this->flow->get('status')) {
return;
}
$enable_preview = ContentSyncSettings::getInstance()->isPreviewEnabled();
$pools = Pool::getAll();
$export_pools = [];
foreach ($this->flow->getController()->getEntityTypeConfig(NULL, NULL, FALSE, TRUE) as $entity_type_name => $bundles) {
foreach ($bundles as $bundle_name => $type) {
$version = $type['version'];
if (Flow::HANDLER_IGNORE == $type['handler']) {
continue;
}
$current = Flow::getEntityTypeVersion($entity_type_name, $bundle_name);
if ($current !== $version) {
throw new \Exception("Entity type {$entity_type_name}.{$bundle_name} was changed without updating Flow {$this->flow->id}. Please re-save that Flow first to apply the latest entity type changes.");
}
$handler = $this->flow->getController()->getEntityTypeHandler($entity_type_name, $bundle_name, $type);
$entity_type_pools = [];
if (isset($type['import_pools'])) {
foreach ($type['import_pools'] as $pool_id => $state) {
if (!isset($entity_type_pools[$pool_id])) {
$entity_type_pools[$pool_id] = [];
}
if (PullIntent::PULL_DISABLED == $type['import']) {
$entity_type_pools[$pool_id]['import'] = Pool::POOL_USAGE_FORBID;
continue;
}
$entity_type_pools[$pool_id]['import'] = $state;
}
}
if (isset($type['export_pools'])) {
foreach ($type['export_pools'] as $pool_id => $state) {
if (!isset($entity_type_pools[$pool_id])) {
$entity_type_pools[$pool_id] = [];
}
if (PushIntent::PUSH_DISABLED == $type['export']) {
$entity_type_pools[$pool_id]['export'] = Pool::POOL_USAGE_FORBID;
continue;
}
$entity_type_pools[$pool_id]['export'] = $state;
}
}
foreach ($entity_type_pools as $pool_id => $definition) {
if (empty($pools[$pool_id])) {
continue;
}
$pool = $pools[$pool_id];
$export = NULL;
$import = NULL;
if (isset($definition['export'])) {
$export = $definition['export'];
}
if (isset($definition['import'])) {
$import = $definition['import'];
}
if ((!$export || Pool::POOL_USAGE_FORBID == $export) && (!$import || Pool::POOL_USAGE_FORBID == $import)) {
continue;
}
if (!in_array($pool, $export_pools)) {
$export_pools[] = $pool;
}
$entity_type_id_without_version = $entity_type_name . '-' . $bundle_name;
if ($extend_only) {
if (!isset($pool_configurations[$pool_id])) {
continue;
}
}
$pull_condition = [];
if (EntityHandlerPluginManager::isEntityTypeFieldable($entity_type_name)) {
$entityFieldManager = \Drupal::service('entity_field.manager');
/** @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields */
$fields = $entityFieldManager->getFieldDefinitions($entity_type_name, $bundle_name);
$forbidden = $handler->getForbiddenFields();
foreach ($fields as $key => $field) {
$field_config = $this->flow->getController()->getPropertyConfig($entity_type_name, $bundle_name, $key);
if (!$field_config) {
continue;
}
if (in_array($key, $forbidden)) {
continue;
}
if (!empty($field_config['handler_settings']['subscribe_only_to'])) {
$allowed = [];
foreach ($field_config['handler_settings']['subscribe_only_to'] as $ref) {
$allowed[] = $ref['uuid'];
}
$pull_condition[$key] = $allowed;
}
}
}
if ($extend_only) {
if (isset($pool_configurations[$pool_id]['entity_types'][$entity_type_id_without_version])) {
if (Pool::POOL_USAGE_FORBID != $import && PullIntent::PULL_DISABLED != $type['import']) {
/**
* @var \EdgeBox\SyncCore\Interfaces\Configuration\IFlowPullConfiguration $pull_configuration
*/
$pull_configuration = $pool_configurations[$pool_id]['entity_types'][$entity_type_id_without_version];
$override = $pull_configuration
->configureOverride($this->flow->id)
->manually(PullIntent::PULL_MANUALLY == $type['import'])
->asDependency(PullIntent::PULL_AS_DEPENDENCY == $type['import'])
->pullDeletions(boolval($type['import_deletion_settings']['import_deletion']));
foreach ($pull_condition as $property => $allowed_entity_ids) {
$override
->ifTaggedWith($property, $allowed_entity_ids);
}
}
continue;
}
}
$entity_type_label = \Drupal::service('entity_type.bundle.info')->getAllBundleInfo()[$entity_type_name][$bundle_name]['label'];
$entity_type = $this
->client
->getConfigurationService()
->defineEntityType($pool_id, $entity_type_name, $bundle_name, $version, $entity_type_label)
->isTranslatable(TRUE);
$entity_type
->addReferenceProperty('menu_items', 'Menu items', TRUE, FALSE, 'menu')
->addAllowedType('menu_link_content');
if (EntityHandlerPluginManager::isEntityTypeFieldable($entity_type_name)) {
$entityFieldManager = \Drupal::service('entity_field.manager');
/** @var \Drupal\Core\Field\FieldDefinitionInterface[] $fields */
$fields = $entityFieldManager->getFieldDefinitions($entity_type_name, $bundle_name);
$forbidden = $handler->getForbiddenFields();
$added = [];
foreach ($fields as $key => $field) {
$field_config = $this->flow->getController()->getPropertyConfig($entity_type_name, $bundle_name, $key);
if (!$field_config) {
continue;
}
if (in_array($key, $forbidden)) {
continue;
}
$field_handler = $this->flow->getController()->getFieldHandler($entity_type_name, $bundle_name, $key);
if (!$field_handler) {
continue;
}
$field_handler->definePropertyAtType($entity_type);
$added[] = $key;
}
if (!in_array('created', $added)) {
$entity_type
->addObjectProperty('created', 'Created', FALSE, FALSE, 'created')
->addIntegerProperty('value', 'Value', FALSE, TRUE, 'integer')
->setFormat(RemoteEntityTypePropertyFormat::UNIX_TIMESTAMP);
}
if (!in_array('changed', $added)) {
$entity_type
->addObjectProperty('changed', 'Changed', FALSE, FALSE, 'changed')
->addIntegerProperty('value', 'Value', FALSE, TRUE, 'integer')
->setFormat(RemoteEntityTypePropertyFormat::UNIX_TIMESTAMP);
}
}
// Remote sites must use the same entity type handler otherwise sync
// will fail- at least for these properties or it will not work at all.
$handler->updateEntityTypeDefinition($entity_type);
// Dispatch EntityTypeExport event to give other modules the possibility
// to adjust the entity type definition and add custom fields.
\Drupal::service('event_dispatcher')->dispatch(
new BeforeEntityTypeExport($entity_type_name, $bundle_name, $entity_type),
BeforeEntityTypeExport::EVENT_NAME
);
// Create the entity type.
$entity_type->addToBatch($batch);
/**
* @var \EdgeBox\SyncCore\Interfaces\Configuration\IDefinePoolForFlow $pool_definition
*/
if (isset($pool_configurations[$pool_id])) {
// If the given entity type has added before, we don't add it a second time.
$pool_definition = $pool_configurations[$pool_id]['pool_definition'];
}
else {
$pool_definition = $flow_definition
->usePool($pool_id);
$pool_configurations[$pool_id] = [
'pool_definition' => $pool_definition,
'entity_types' => [],
];
}
$pool_definition
->useEntityType($entity_type);
if ($extend_only) {
continue;
}
// Create a synchronization from the pool to the preview connection.
if ($enable_preview) {
$pool_definition
->enablePreview($entity_type);
}
if (Pool::POOL_USAGE_FORBID != $export && PushIntent::PUSH_DISABLED != $type['export']) {
$push_config = $pool_definition
->enablePush($entity_type);
if ($push_config) {
$push_config
->manually(PushIntent::PUSH_MANUALLY == $type['export'])
->asDependency(PushIntent::PUSH_AS_DEPENDENCY == $type['export'])
->pushDeletions(boolval($type['export_deletion_settings']['export_deletion']));
}
}
if (Pool::POOL_USAGE_FORBID != $import && PullIntent::PULL_DISABLED != $type['import']) {
$pull_configuration = $pool_definition
->enablePull($entity_type)
->manually(PullIntent::PULL_MANUALLY == $type['import'])
->asDependency(PullIntent::PULL_AS_DEPENDENCY == $type['import'])
->pullDeletions(boolval($type['import_deletion_settings']['import_deletion']))
->addToBatch($batch);
$pool_configurations[$pool_id]['entity_types'][$entity_type_id_without_version] = $pull_configuration;
foreach ($pull_condition as $property => $allowed_entity_ids) {
$pull_configuration
->ifTaggedWith($property, $allowed_entity_ids);
}
}
}
}
}
if ($extend_only) {
return;
}
// Always export required pools as well to prevent any potential issues.
$subsequent = $this->subsequent;
foreach ($export_pools as $pool) {
$exporter = new SyncCorePoolExport($pool);
$batch->prepend(
$exporter->prepareBatch($subsequent, $force)
);
$subsequent = TRUE;
}
}
}
