cms_content_sync-3.0.x-dev/src/SyncCoreInterface/DrupalApplication.php
src/SyncCoreInterface/DrupalApplication.php
<?php
namespace Drupal\cms_content_sync\SyncCoreInterface;
use Drupal\cms_content_sync\Controller\AuthenticationByUser;
use Drupal\cms_content_sync\Controller\ContentSyncSettings;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Url;
use EdgeBox\SyncCore\Interfaces\Embed\IEmbedService;
use EdgeBox\SyncCore\Interfaces\IApplicationInterface;
/**
* Class DrupalApplication.
*
* Implement the ApplicationInterface to provide basic information about this site to the Sync Core. Must be provided
* to all client instances to communicate with the Sync Core.
*/
class DrupalApplication implements IApplicationInterface {
/**
* @var string APPLICATION_ID Unique ID to identify the kind of application
*/
public const APPLICATION_ID = 'drupal';
/**
* @var DrupalApplication
*/
protected static $instance;
/**
* @return DrupalApplication
*/
public static function get() {
if (!empty(self::$instance)) {
return self::$instance;
}
return self::$instance = new DrupalApplication();
}
/**
* {@inheritdoc}
*/
public function getSiteBaseUrl() {
return ContentSyncSettings::getInstance()->getSiteBaseUrl();
}
/**
* {@inheritdoc}
*/
public function getAuthentication() {
$type = ContentSyncSettings::getInstance()->getAuthenticationType();
$authentication_provider = AuthenticationByUser::getInstance();
return [
'type' => $type,
'username' => $authentication_provider->getUsername(),
'password' => $authentication_provider->getPassword(),
];
}
/**
* Apply the required _format query parameter and fix issues with the base URL
* and languages interfering.
*
* @param string $url
*
* @return string
*/
protected function processUrl(string $url) {
// Always request JSON as the format. Drupal doesn't support Accept headers, so we have to pass it as a query parameter.
$url .= (strpos($url, '?') === FALSE ? '?' : '&') . '_format=json';
// When users migrate from v1, their base URL will include e.g. the site name or the language suffix.
// As above's Url::fromRoute() will append that, too, it would be included twice in the resulting URL.
// So we have to cut it off from the path to allow v1 users to continue with their existing base URL.
$base_url = $this->getSiteBaseUrl();
$base_url_path = parse_url($base_url, PHP_URL_PATH);
$base_url_path_parts = array_slice(explode('/', $base_url_path ?? ''), 1);
$url_parts = array_slice(explode('/', $url), 1);
// If the start of the path is equal to the end of the base URL, we have to
// cut it off here so that it doesn't result in two language prefixes in the url.
// e.g. https://example.com/en + /en/rest/... would result in https://example.com/en/en/rest/...
// instead of https://example.com/en/rest/...
if (count($base_url_path_parts) && $url_parts[0] === $base_url_path_parts[count($base_url_path_parts) - 1]) {
$url = '/' . implode('/', array_slice($url_parts, 1));
}
// When using the UI, Drupal may prepend the site's path to the route when using multisite (but not when using Drush).
// So we check if the path of the base URL is the prefix of the provided route path.
// Otherwise this would result in URLs like https://example.com/sub-site/sub-site/... instead of https://example.com/sub-site/...
// or https://example.com/sub-site/en/sub-site/en/... instead of https://example.com/sub-site/en/...
elseif ($base_url_path && mb_substr($url, 0, mb_strlen($base_url_path)) === $base_url_path) {
$url = mb_substr($url, mb_strlen($base_url_path));
}
return $url;
}
/**
* {@inheritdoc}
*/
public function getRelativeReferenceForSiteRestCall(string $action) {
if (IApplicationInterface::REST_ACTION_SITE_STATUS === $action) {
$url = Url::fromRoute('rest.cms_content_sync_sync_core_site_status.GET')->toString();
}
elseif (IApplicationInterface::REST_ACTION_SITE_CONFIG === $action) {
$url = Url::fromRoute('rest.cms_content_sync_sync_core_site_config.GET')->toString();
}
else {
return NULL;
}
$url = $this->processUrl($url);
return $url;
}
/**
* {@inheritdoc}
*/
public function getRelativeReferenceForRestCall(string $flow_machine_name, string $action) {
if (IApplicationInterface::REST_ACTION_LIST_ENTITIES === $action) {
$url = Url::fromRoute('rest.cms_content_sync_sync_core_entity_list.GET', ['flow_id' => $flow_machine_name])->toString();
}
elseif (in_array($action, [
IApplicationInterface::REST_ACTION_CREATE_ENTITY,
IApplicationInterface::REST_ACTION_DELETE_ENTITY,
IApplicationInterface::REST_ACTION_RETRIEVE_ENTITY,
])) {
$url = Url::fromRoute('rest.cms_content_sync_sync_core_entity_item.GET', [
'flow_id' => $flow_machine_name,
'entity_type' => '[type.namespaceMachineName]',
'entity_bundle' => '[type.machineName]',
// When retrieving the entity we require the UUID as we're only saving
// the UUID at status entities.
'shared_entity_id' => IApplicationInterface::REST_ACTION_RETRIEVE_ENTITY === $action ? '[entity.uuid]' : '[entity.sharedId]',
])->toString();
}
else {
return NULL;
}
// Drupal will url encode the passed arguments, so turn the [] brackets into %.. characters. So we reverse that here as these are placeholders and must be actual brackets.
$url = preg_replace('@%5B([a-zA-Z0-9.]+)%5D@', '[$1]', $url);
// Ask for additional localization parameters to be passed as query parameters when requesting a specific entity.
if (IApplicationInterface::REST_ACTION_LIST_ENTITIES !== $action) {
$url .= '?language=[entity.language]';
$url .= '&isTranslationRoot=[entity.isTranslationRoot]';
$url .= '&individualTranslation=[entity.individualTranslation]';
}
$url = $this->processUrl($url);
return $url;
}
/**
* {@inheritdoc}
*/
public function getEmbedBaseUrl(string $feature) {
$export_url = $this->getSiteBaseUrl();
$name = IEmbedService::REGISTER_SITE === $feature || IEmbedService::SITE_REGISTERED === $feature || IEmbedService::MIGRATE === $feature ? 'site' : $feature;
$url = sprintf(
'%s/admin/config/services/cms_content_sync/%s',
$export_url,
$name
);
// If something went wrong and the site needs to be re-registered again forcefully, we add a "force" param.
if (IEmbedService::REGISTER_SITE === $feature) {
$url .= '?force=' . IEmbedService::REGISTER_SITE;
}
elseif (IEmbedService::MIGRATE === $feature) {
$url .= '?force=' . IEmbedService::MIGRATE;
}
return $url;
}
/**
* {@inheritDoc}
*/
public function setSyncCoreUrl(string $set) {
ContentSyncSettings::getInstance()->setSyncCoreUrl($set);
}
/**
* {@inheritDoc}
*/
public function getSyncCoreUrl() {
return ContentSyncSettings::getInstance()->getSyncCoreUrl();
}
/**
* {@inheritdoc}
*/
public function getRestUrl($pool_id, $type_machine_name, $bundle_machine_name, $version_id, $entity_uuid = NULL, $manually = NULL, $as_dependency = NULL) {
$export_url = $this->getSiteBaseUrl();
$url = sprintf(
'%s/rest/cms-content-sync/%s/%s/%s/%s',
$export_url,
$pool_id,
$type_machine_name,
$bundle_machine_name,
$version_id
);
if ($entity_uuid) {
$url .= '/' . $entity_uuid;
}
$url .= '?_format=json';
if ($as_dependency) {
$url .= '&is_dependency=' . $as_dependency;
}
if ($manually) {
$url .= '&is_manual=' . $manually;
}
return $url;
}
/**
* {@inheritdoc}
*/
public function getSiteName() {
$name = getenv('CMS_CONTENT_SYNC_SITE_NAME') ?? getenv('CONTENT_SYNC_SITE_NAME');
if ($name) {
return $name;
}
try {
return ContentSyncSettings::getInstance()->getSiteName();
}
// If the site is not registered yet or the Sync Core is unavailable, we return the
// Drupal site name as default.
catch (\Exception $e) {
return \Drupal::config('system.site')->get('name');
}
}
/**
* {@inheritdoc}
*/
public function setSiteId($set) {
ContentSyncSettings::getInstance()->setSiteId($set);
}
/**
* {@inheritdoc}
*/
public function getSiteId() {
return ContentSyncSettings::getInstance()->getSiteMachineName();
}
/**
* {@inheritdoc}
*/
public function getSiteMachineName() {
return ContentSyncSettings::getInstance()->getSiteMachineName();
}
/**
* {@inheritdoc}
*/
public function setSiteMachineName($set) {
ContentSyncSettings::getInstance()->setSiteMachineName($set);
}
/**
* {@inheritdoc}
*/
public function setSiteUuid(string $set) {
ContentSyncSettings::getInstance()->setSiteUuid($set);
// Clear some caches to set the local action buttons to active.
// @todo Adjust this to just clear the cache tags for the local actions.
$bins = Cache::getBins();
$bins['render']->deleteAll();
$bins['discovery']->deleteAll();
}
/**
* {@inheritdoc}
*/
public function getSiteUuid() {
return ContentSyncSettings::getInstance()->getSiteUuid();
}
/**
* {@inheritdoc}
*/
public function getApplicationId() {
return self::APPLICATION_ID;
}
/**
* {@inheritdoc}
*/
public function getApplicationVersion() {
return \Drupal::VERSION;
}
/**
* {@inheritdoc}
*/
public function getApplicationModuleVersion() {
$version = \Drupal::service('extension.list.module')
->getExtensionInfo('cms_content_sync')['version'];
return $version ? $version : 'dev';
}
/**
* {@inheritdoc}
*/
public function getHttpClient() {
return \Drupal::httpClient();
}
/**
* {@inheritdoc}
*/
public function getHttpOptions() {
$options = [];
// Allow to set a custom timeout for Sync Core requests.
global $config;
$config_name = 'cms_content_sync.sync_core_request_timeout';
if (!empty($config[$config_name]) && is_int($config[$config_name])) {
$options['timeout'] = $config[$config_name];
}
return $options;
}
/**
*
*/
public function getFeatureFlags() {
return [
'count-entities' => 1,
'request-per-translation' => 1,
'request-polling' => \Drupal::service('module_handler')->moduleExists('cms_content_sync_private_environment') ? 1 : 0,
'site-settings-tab' => 1,
'skip-unchanged-entities' => 1,
'skip-unchanged-translations' => 1,
'prefer-2xx-status-code' => 1,
'entity-operation' => 1,
'entity-operation.clear-cache' => 1,
];
}
}
