cms_content_sync-3.0.x-dev/src/Plugin/rest/resource/SyncCoreSiteConfigResource.php
src/Plugin/rest/resource/SyncCoreSiteConfigResource.php
<?php
namespace Drupal\cms_content_sync\Plugin\rest\resource;
use Drupal\cms_content_sync\Controller\LoggerProxy;
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\PullIntent;
use Drupal\cms_content_sync\PushIntent;
use Drupal\cms_content_sync\SyncCoreInterface\SyncCoreFactory;
use Drupal\Core\Language\Language;
use Drupal\Core\Serialization\Yaml;
use Drupal\rest\Plugin\ResourceBase;
use EdgeBox\SyncCore\Interfaces\ISyncCore;
use EdgeBox\SyncCore\V2\Raw\Model\RemoteEntityTypePropertyFormat;
use EdgeBox\SyncCore\V2\Raw\Model\RemoteRequestQueryParamsSiteConfig;
use EdgeBox\SyncCore\V2\Raw\Model\RemoteSiteConfigRequestMode;
use EdgeBox\SyncCore\V2\Raw\Model\SiteConfigResponse;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Allow the Sync Core to query for this site's confiuration like it's entity
* types, Pools and Flows.
*
* @RestResource(
* id = "cms_content_sync_sync_core_site_config",
* label = @Translation("Content Sync: Sync Core: Site Config"),
* uri_paths = {
* "canonical" = "/rest/cms-content-sync/v2/config"
* }
* )
*/
class SyncCoreSiteConfigResource extends ResourceBase implements ContentSyncRestInterface {
use ContentSyncRestTrait;
/**
* @var \EdgeBox\SyncCore\Interfaces\ISyncCore
*/
protected $client;
/**
* Constructs an object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param array $serializer_formats
* The available serialization formats.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
* @param \Drupal\Core\Extension\ExtensionList $extension_list_module
* An extension list instance.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
array $serializer_formats,
LoggerInterface $logger,
?ISyncCore $client,
) {
parent::__construct(
$configuration,
$plugin_id,
$plugin_definition,
$serializer_formats,
$logger
);
$this->client = $client;
}
/**
* {@inheritdoc}
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition,
) {
// When the config for this route is created, it will instantiate this
// class but the site won't be registered yet so there's no Sync Core URL
// set, resulting in an error.
// But as this interface is used by the Sync Core (at which point a
// Sync Core URL must have been set), we can safely ignore this.
try {
$client = SyncCoreFactory::getSyncCoreV2();
}
catch (\Exception $e) {
$client = NULL;
}
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->getParameter('serializer.formats'),
LoggerProxy::get(),
$client
);
}
/**
*
*/
protected function serializeEntityTypeFields($entity_type_name, $bundle_name, $entity_type, $handler) {
$field_plugin_manager = \Drupal::service('plugin.manager.cms_content_sync_field_handler');
$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_handlers = $field_plugin_manager->getHandlerOptions($entity_type_name, $bundle_name, $key, $field, TRUE);
$ignore = in_array($key, $forbidden) || empty($field_handlers);
if ($ignore) {
continue;
}
$handler_id = $ignore ? 'ignore' : key($field_handlers);
$field_settings = [
'handler' => $handler_id,
'export' => NULL,
'import' => NULL,
'preview' => NULL,
'entity_type' => $entity_type_name,
'entity_bundle' => $bundle_name,
'handler_settings' => [],
];
/**
* @var \Drupal\cms_content_sync\Plugin\FieldHandlerInterface $handler
*/
$field_handler = $field_plugin_manager->createInstance($handler_id, [
'entity_type_name' => $entity_type_name,
'bundle_name' => $bundle_name,
'field_name' => $key,
'field_definition' => $field,
'settings' => $field_settings,
'sync' => NULL,
]);
$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);
}
}
/**
*
*/
protected function serializeEntityTypes($full = TRUE, $page = NULL, $items_per_page = NULL) {
$entity_types = \Drupal::service('entity_type.bundle.info')->getAllBundleInfo();
$result = [];
$skip = $page === NULL || $items_per_page === NULL ? NULL : $page * $items_per_page;
$skipped = 0;
ksort($entity_types);
foreach ($entity_types as $entity_type_name => $bundles) {
ksort($bundles);
foreach ($bundles as $bundle_name => $entity_bundle) {
if (!EntityHandlerPluginManager::isSupported($entity_type_name, $bundle_name)) {
continue;
}
$entityPluginManager = \Drupal::service('plugin.manager.cms_content_sync_entity_handler');
$entity_handlers = $entityPluginManager->getHandlerOptions($entity_type_name, $bundle_name, TRUE);
if (!count($entity_handlers)) {
continue;
}
if ($skip !== NULL) {
if ($skipped < $skip) {
$skipped++;
continue;
}
}
$entity_handler_names = array_keys($entity_handlers);
$handler_id = reset($entity_handler_names);
$handler = $entityPluginManager->createInstance(
$handler_id,
[
'entity_type_name' => $entity_type_name,
'bundle_name' => $bundle_name,
'settings' => [],
'sync' => NULL,
]
);
$version = Flow::getEntityTypeVersion($entity_type_name, $bundle_name);
$entity_type_label = $entity_types[$entity_type_name][$bundle_name]['label'];
$entity_type = $this->client
->getConfigurationService()
->defineEntityType(NULL, $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 ($full && EntityHandlerPluginManager::isEntityTypeFieldable($entity_type_name)) {
$this->serializeEntityTypeFields($entity_type_name, $bundle_name, $entity_type, $handler);
}
// 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
);
$result[$entity_type_name . '.' . $bundle_name] = $entity_type;
if ($items_per_page !== NULL && count($result) >= $items_per_page) {
break 2;
}
}
}
return $result;
}
/**
*
*/
protected function serializeLanguage(string $code, Language $language, $native_language) {
$language_definition = $this->client->getConfigurationService()->defineLanguage($code, $language->getName());
if ($language->getDirection()) {
$language_definition->isRightToLeft($language->getDirection() === Language::DIRECTION_RTL);
}
if ($native_language) {
$language_definition->setNativeName($native_language->getName());
}
return $language_definition;
}
/**
*
*/
protected function serializeFlow(Flow $flow, $entity_types) {
$all_pools = Pool::getAll();
$flow_definition = $this->client
->getConfigurationService()
->defineFlow(
$flow->id,
$flow->label(),
Yaml::encode(\Drupal::service('config.storage')->read('cms_content_sync.flow.' . $flow->id()))
);
$allowed_languages = $flow->getController()->getAllowedLanguages();
if (!empty($allowed_languages)) {
$flow_definition->allowedLanguages($allowed_languages);
}
// Ignore disabled flows at export.
if (!$flow->get('status')) {
$flow_definition->isActive(FALSE);
return $flow_definition;
}
foreach ($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 {$flow->id}. Please re-save that Flow first to apply the latest entity type changes.");
}
if (empty($entity_types[$entity_type_name . '.' . $bundle_name])) {
throw new \Exception("Entity type {$entity_type_name}.{$bundle_name} is missing in the cache.");
continue;
}
$entity_type = $entity_types[$entity_type_name . '.' . $bundle_name];
$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($all_pools[$pool_id])) {
continue;
}
$export = $definition['export'] ?? NULL;
$import = $definition['import'] ?? NULL;
if ((!$export || Pool::POOL_USAGE_FORBID == $export) && (!$import || Pool::POOL_USAGE_FORBID == $import)) {
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);
$entityPluginManager = \Drupal::service('plugin.manager.cms_content_sync_entity_handler');
$entity_handlers = $entityPluginManager->getHandlerOptions($entity_type_name, $bundle_name, TRUE);
if (!count($entity_handlers)) {
continue;
}
$entity_handler_names = array_keys($entity_handlers);
$handler_id = reset($entity_handler_names);
$handler = $entityPluginManager->createInstance(
$handler_id,
[
'entity_type_name' => $entity_type_name,
'bundle_name' => $bundle_name,
'settings' => [],
'sync' => NULL,
]
);
$forbidden = $handler->getForbiddenFields();
foreach ($fields as $key => $field) {
$field_config = $flow->getController()->getPropertyConfig($entity_type_name, $bundle_name, $key);
if (!$field_config) {
continue;
}
// Exception for taxonomy terms so they can be filtered by parent
// to only pull a sub-tree of terms.
if (in_array($key, $forbidden) && $key !== "parent") {
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;
}
}
}
/**
* @var \EdgeBox\SyncCore\Interfaces\Configuration\IDefinePoolForFlow $pool_definition
*/
$pool_definition = $flow_definition->usePool($pool_id);
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']));
foreach ($pull_condition as $property => $allowed_entity_ids) {
$pull_configuration
->ifTaggedWith($property, $allowed_entity_ids);
}
}
}
}
}
return $flow_definition;
}
/**
*
*/
protected function getDtos(array $operations) {
$result = [];
foreach ($operations as $operation) {
$result[] = $operation->getSerializedDto();
}
return $result;
}
/**
*
*/
public function get() {
$response = new SiteConfigResponse();
$query = \Drupal::request()->query->all();
$queryObject = new RemoteRequestQueryParamsSiteConfig($query);
$mode = $queryObject->getMode();
$page = $queryObject->getPage();
if (is_string($page)) {
$page = (int) $page;
}
else {
$page = NULL;
}
$items_per_page = $queryObject->getItemsPerPage();
if (is_string($items_per_page)) {
$items_per_page = (int) $items_per_page;
}
else {
$items_per_page = NULL;
}
if ($mode === RemoteSiteConfigRequestMode::POOLS || $mode === RemoteSiteConfigRequestMode::CS || $mode === RemoteSiteConfigRequestMode::ALL) {
$pools = [];
foreach (Pool::getAll() as $pool) {
$pools[] = $this->client->getConfigurationService()->usePool($pool->id(), $pool->label());
}
$response->setPoolCount(count($pools));
if ($items_per_page !== 0) {
$response->setPools($this->getDtos($pools));
}
}
$export_entity_types = $mode === RemoteSiteConfigRequestMode::ENTITY_TYPES || $mode === RemoteSiteConfigRequestMode::ALL;
$any_entity_types = $mode !== RemoteSiteConfigRequestMode::POOLS && $items_per_page !== 0;
$entity_types = $any_entity_types ? $this->serializeEntityTypes() : [];
if ($export_entity_types) {
$entity_types_detailed = $this->serializeEntityTypes($export_entity_types, $page, $items_per_page);
$response->setEntityTypeCount(count($entity_types));
if ($items_per_page !== 0) {
$response->setEntityTypes($this->getDtos(array_values($entity_types_detailed)));
}
}
if ($mode === RemoteSiteConfigRequestMode::FLOWS || $mode === RemoteSiteConfigRequestMode::CS || $mode === RemoteSiteConfigRequestMode::ALL) {
$flows = [];
foreach (Flow::getAll() as $flow) {
$flow->getController()->updateEntityTypeVersions();
$flows[] = $this->serializeFlow($flow, $entity_types);
}
$response->setFlowCount(count($flows));
if ($items_per_page !== 0) {
$response->setFlows($this->getDtos($flows));
}
}
if ($mode === RemoteSiteConfigRequestMode::LANGUAGES || $mode === RemoteSiteConfigRequestMode::ALL) {
$languages = [];
$site_languages = \Drupal::languageManager()->getLanguages();
$native_site_languages = \Drupal::languageManager()->getNativeLanguages();
foreach ($site_languages as $code => $language) {
$languages[] = $this->serializeLanguage($code, $language, $native_site_languages[$code] ?? NULL);
if ($language->isDefault()) {
$response->setDefaultLanguageCode($code);
}
}
$response->setLanguageCount(count($languages));
if ($items_per_page !== 0) {
$response->setLanguages($this->getDtos($languages));
}
}
$body = $response->jsonSerialize();
// Turn objects into arrays.
return $this->respondWith(json_decode(json_encode($body), TRUE), self::CODE_OK, FALSE);
}
}
