diff --git a/composer.json b/composer.json
index 89b6b81..31b2aad 100644
--- a/composer.json
+++ b/composer.json
@@ -13,6 +13,7 @@
"extra": {
"drush": {
"services": {
+ "drush.12.5+.services.yml": ">=12.5",
"drush.9-11.services.yml": "^9 || ^10 || ^11"
}
}
diff --git a/content_sync.admin.inc b/content_sync.admin.inc
deleted file mode 100644
index 83ad70a..0000000
--- a/content_sync.admin.inc
+++ /dev/null
@@ -1,43 +0,0 @@
- t('Type'),
- 'where' => "w.type = ?",
- 'options' => $types,
- ];
- }
-
- $filters['severity'] = [
- 'title' => t('Severity'),
- 'where' => 'w.severity = ?',
- 'options' => RfcLogLevel::getLevels(),
- ];
-
- return $filters;
-}
diff --git a/content_sync.install b/content_sync.install
index 38d36c9..b5c6958 100644
--- a/content_sync.install
+++ b/content_sync.install
@@ -1,4 +1,5 @@
snapshot();
}
@@ -20,7 +21,7 @@ function content_sync_schema() {
// Content Sync Table to use for diff.
$schema['cs_db_snapshot'] = [
'description' => 'The base table for configuration data.',
- 'fields' => [
+ 'fields' => [
'collection' => [
'description' => 'Primary Key: Config object collection.',
'type' => 'varchar_ascii',
@@ -44,7 +45,7 @@ function content_sync_schema() {
],
'primary key' => ['collection', 'name'],
];
- // Content Sync Logs Table
+ // Content Sync Logs Table.
$schema['cs_logs'] = [
'description' => 'Table that contains content_sync logs.',
'fields' => [
diff --git a/content_sync.links.task.yml b/content_sync.links.task.yml
index efebcac..df9d68a 100644
--- a/content_sync.links.task.yml
+++ b/content_sync.links.task.yml
@@ -41,4 +41,4 @@ content.view_logs:
content.settings:
title: 'Settings'
route_name: content.settings
- base_route: content.sync
\ No newline at end of file
+ base_route: content.sync
diff --git a/content_sync.module b/content_sync.module
index 878d193..83c95a0 100755
--- a/content_sync.module
+++ b/content_sync.module
@@ -1,12 +1,16 @@
setAbsolute(FALSE)->toString());
- if (!in_array($route_name, ['system.modules_list']) && strpos($route_name, 'help.page.content_sync') === FALSE && strpos($path, '/content') === FALSE) {
+ if ($route_name !== 'system.modules_list' && !str_contains($route_name, 'help.page.content_sync') && !str_contains($path, '/content')) {
return NULL;
}
/** @var \Drupal\content_sync\ContentSyncHelpManagerInterface $help_manager */
$help_manager = \Drupal::service('content_sync.help_manager');
- if ($route_name == 'help.page.content_sync') {
+ if ($route_name === 'help.page.content_sync') {
$build = $help_manager->buildIndex();
}
else {
@@ -38,73 +42,10 @@ function content_sync_help($route_name, RouteMatchInterface $route_match) {
$renderer->addCacheableDependency($build, $config);
return $build;
}
- else {
- return NULL;
- }
-
- switch ($route_name) {
- case 'help.page.content_sync':
- $output = '';
- $output .= '
' . t('About') . '
';
- $output .= '' . t('The Content Synchronization module provides a user interface for importing and exporting content changes between installations of your website in different environments. Content is stored in YAML format. For more information, see the online documentation for the Content Synchronization module.', [':url' => 'https://www.drupal.org/project/content_sync']) . '
';
- $output .= '' . t('Uses') . '
';
- $output .= '';
- $output .= '- ' . t('Exporting the full content') . '
';
- $output .= '- ' . t('You can create and download an archive consisting of all your site\'s content exported as *.yml files on the Export page.', [':url' => \Drupal::url('content.export_full')]) . '
';
- $output .= '- ' . t('Importing a full content') . '
';
- $output .= '- ' . t('You can upload a full site content from an archive file on the Import page. When importing data from a different environment, the site and import files must have matching configuration values for UUID in the system.site configuration item. That means that your other environments should initially be set up as clones of the target site.', [':url' => \Drupal::url('content.import_full')]) . '
';
- $output .= '- ' . t('Exporting a single content item') . '
';
- $output .= '- ' . t('You can export a single content item by selecting a Content type and Content name on the Single export page. The content and its corresponding *.yml file name are then displayed on the page for you to copy.', [':single-export' => \Drupal::url('content.export_single')]) . '
';
- $output .= '- ' . t('Importing a single content item') . '
';
- $output .= '- ' . t('You can import a single content item by pasting it in YAML format into the form on the Single import page.', [':single-import' => \Drupal::url('content.import_single')]) . '
';
- $output .= '- ' . t('Synchronizing content') . '
';
- $output .= '- ' . t('You can review differences between the active content and an imported content archive on the Synchronize page to ensure that the changes are as expected, before finalizing the import. The SynchronizeSynchronize page also shows content items that would be added or removed.', [':synchronize' => \Drupal::url('content.sync')]) . '
';
- $output .= '- ' . t('Content logs') . '
';
- $output .= '- ' . t('You can view a chronological list of recorded events containing errors, warnings and operational information of the content import, export and synchronization on the Logs page.', [':content-logs' => \Drupal::url('content.overview')]) . '
';
- $output .= '- ' . t('Content synchronization settings') . '
';
- $output .= '- ' . t('You can set specific settings for the content synchronization behaviour as ignore the UUID Site validation and more on the Settings page.', [':settings' => \Drupal::url('content.settings')]) . '
';
- $output .= '
';
-
- //return $output;
-
- case 'content.export_full':
- $output = '';
- $output .= '' . t('Export and download the full content of this site as a gzipped tar file.') . '
';
- return $output;
-
- case 'content.import_full':
- $output = '';
- $output .= '' . t('Upload a full site content archive to the content sync directory to be imported.') . '
';
- return $output;
-
- case 'content.export_single':
- $output = '';
- $output .= '' . t('Choose a content item to display its YAML structure.') . '
';
- return $output;
-
- case 'content.import_single':
- $output = '';
- $output .= '' . t('Import a single content item by pasting its YAML structure into the text field.') . '
';
- return $output;
-
- case 'content.sync':
- $output = '';
- $output .= '' . t('Compare the content uploaded to your content sync directory with the active content before completing the import.') . '
';
- return $output;
-
- case 'content.settings':
- $output = '';
- $output .= '' . t('Set specific settings for the content synchronization behaviour.') . '
';
- return $output;
- case 'content.overview':
- $output = '';
- $output .= '' . t('chronological list of recorded events containing errors, warnings and operational information of the content import, export and synchronization.') . '
';
- return $output;
- }
+ return NULL;
}
-
/**
* Implements hook_theme().
*/
@@ -118,28 +59,29 @@ function content_sync_theme() {
],
];
- // Since any rendering of a content_sync is going to require 'content_sync.theme.inc'
- // we are going to just add it to every template.
+ // Since any rendering of a content_sync is going to require
+ // 'content_sync.theme.inc' we are going to just add it to every template.
foreach ($info as &$template) {
$template['file'] = 'includes/content_sync.theme.inc';
}
return $info;
}
-
/**
* Implements hook_file_download().
*/
-function content_file_download($uri) {
- $scheme = \Drupal::service('stream_wrapper_manager')->getScheme($uri);
- $target = \Drupal::service('stream_wrapper_manager')->getTarget($uri);
- if ($scheme == 'temporary' && $target == 'content.tar.gz') {
+function content_sync_file_download(string $uri) : array|int {
+ /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
+ $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
+ $scheme = $stream_wrapper_manager::getScheme($uri);
+ $target = $stream_wrapper_manager::getTarget($uri);
+ if ($scheme === 'temporary' && $target === 'content.tar.gz') {
if (\Drupal::currentUser()->hasPermission('export content')) {
$request = \Drupal::request();
$date = DateTime::createFromFormat('U', $request->server->get('REQUEST_TIME'));
$date_string = $date->format('Y-m-d-H-i');
$hostname = str_replace('.', '-', $request->getHttpHost());
- $filename = 'content' . '-' . $hostname . '-' . $date_string . '.tar.gz';
+ $filename = 'content-' . $hostname . '-' . $date_string . '.tar.gz';
$disposition = 'attachment; filename="' . $filename . '"';
return [
'Content-disposition' => $disposition,
@@ -149,24 +91,11 @@ function content_file_download($uri) {
}
}
-
-/* From /docroot/core/includes/bootstrap.inc
-find out how to declare a global and include it ???
-Drupal core provides the
-CONFIG_SYNC_DIRECTORY constant to access the sync directory.
-
-$content_directories;
-Where to declare constant variables
- //const CONFIG_ACTIVE_DIRECTORY = 'active';
- //const CONFIG_SYNC_DIRECTORY = 'sync';
- //const CONFIG_STAGING_DIRECTORY = 'staging';
-*/
-
/**
* Returns the path of a content directory.
*
* Content directories are configured using $content_directories in
- * settings.php
+ * settings.php.
*
* @param string $type
* The type of content directory to return.
@@ -176,15 +105,10 @@ Where to declare constant variables
*
* @throws \Exception
*/
-function content_sync_get_content_directory($type) {
+function content_sync_get_content_directory(string $type) : ?string {
+ // phpcs:ignore Drupal.NamingConventions.ValidGlobal.GlobalUnderScore
global $content_directories;
- // @todo Remove fallback in Drupal 9. https://www.drupal.org/node/2574943
- /*if ($type == CONTENT_SYNC_DIRECTORY &&
- !isset($content_directories[CONTENT_SYNC_DIRECTORY])
- && isset($content_directories[CONTENT_STAGING_DIRECTORY])) {
- $type = CONTENT_STAGING_DIRECTORY;
- }*/
if ($type == 'sync' &&
!isset($content_directories['sync'])
&& isset($content_directories['staging'])) {
@@ -194,15 +118,16 @@ function content_sync_get_content_directory($type) {
if (!empty($content_directories[$type])) {
return $content_directories[$type];
}
- // throw new \Exception("The content directory type '$type' does not exist");
+
\Drupal::messenger()->addError("The content directory type '$type' does not exist");
}
/**
- * hook_entity_update
- * Keep the content snapshot table synced
+ * Implements hook_entity_update().
+ *
+ * Keep the content snapshot table synced.
*/
-function content_sync_entity_update(Drupal\Core\Entity\EntityInterface $entity){
+function content_sync_entity_update(EntityInterface $entity) : void {
$renderer = \Drupal::service('renderer');
$context = new RenderContext();
@@ -210,68 +135,82 @@ function content_sync_entity_update(Drupal\Core\Entity\EntityInterface $entity){
// response whatsoever. The normalizers inside of this call generated
// cacheable metadata but there is no needed context as this is running on
// an entity update and has nothing to do with front-end rendering.
- $renderer->executeInRenderContext($context, function() use ($renderer, $entity) {
- // Get submitted values
+ $renderer->executeInRenderContext($context, function () use ($entity) {
+ // Get submitted values.
$entity_type = $entity->getEntityTypeId();
$entity_bundle = $entity->bundle();
$entity_id = $entity->id();
- //Validate that it is a Content Entity
+ // Validate that it is a Content Entity.
$entityTypeManager = \Drupal::entityTypeManager();
$instances = $entityTypeManager->getDefinitions();
if (isset($instances[$entity_type]) && $instances[$entity_type] instanceof ContentEntityType) {
- $entity = \Drupal::entityTypeManager()->getStorage($entity_type)
+ $reloaded_entity = $entityTypeManager->getStorage($entity_type)
->load($entity_id);
+ assert($reloaded_entity instanceof ContentEntityInterface);
+ if (!$reloaded_entity) {
+ \Drupal::logger('content_sync')->warning('Failed to (re)load entity during update hook: {type}, {id}', [
+ 'type' => $entity_type,
+ 'id' => $entity_id,
+ ]);
+ return;
+ }
// Generate the YAML file.
$serializer_context = [];
- $exported_entity = \Drupal::service('content_sync.exporter')
- ->exportEntity($entity, $serializer_context);
- // Create the name
- $name = $entity_type . "." . $entity_bundle . "." . $entity->uuid();
- //Insert/Update Data
- $activeStorage = new Drupal\content_sync\Content\ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot');
- $activeStorage->cs_write($name, \Drupal\Core\Serialization\Yaml::decode($exported_entity), $entity_type . "." . $entity_bundle);
+ /** @var \Drupal\content_sync\Exporter\ContentExporter $exporter */
+ $exporter = \Drupal::service('content_sync.exporter');
+ $exported_entity = $exporter->exportEntity($reloaded_entity, $serializer_context);
+ // Create the name.
+ $name = $entity_type . '.' . $entity_bundle . '.' . $reloaded_entity->uuid();
+ // Insert/Update Data.
+ /** @var \Drupal\content_sync\Content\DatabaseStorage $activeStorage */
+ $activeStorage = \Drupal::service('content.storage.active');
+ $activeStorage->contentSyncWrite($name, Yaml::decode($exported_entity), $entity_type . "." . $entity_bundle);
// Invalidate the CS Cache of the entity.
- $cache = \Drupal::cache('content')
- ->invalidate($entity_type . "." . $entity_bundle . ":" . $name);
+ \Drupal::cache('content')->invalidate($entity_type . "." . $entity_bundle . ":" . $name);
}
});
}
+
/**
- * hook_entity_insert
- * Keep the content snapshot table synced
+ * Implements hook_entity_insert().
+ *
+ * Keep the content snapshot table synced.
*/
-function content_sync_entity_insert(Drupal\Core\Entity\EntityInterface $entity){
+function content_sync_entity_insert(EntityInterface $entity) : void {
content_sync_entity_update($entity);
}
+
/**
- * hook_entity_delete
- * Keep the content snapshot table synced
+ * Implements hook_entity_delete().
+ *
+ * Keep the content snapshot table synced.
*/
-function content_sync_entity_delete(Drupal\Core\Entity\EntityInterface $entity){
- // Get submitted values
+function content_sync_entity_delete(EntityInterface $entity) : void {
+ // Get submitted values.
$entity_type = $entity->getEntityTypeId();
$entity_bundle = $entity->Bundle();
$entity_uuid = $entity->uuid();
- //Validate that it is a Content Entity
+ // Validate that it is a Content Entity.
$entityTypeManager = \Drupal::entityTypeManager();
$instances = $entityTypeManager->getDefinitions();
- if ( isset($instances[$entity_type]) && $instances[$entity_type] instanceof ContentEntityType ) {
- // Update the data for diff
- $name = $entity_type . "." . $entity_bundle . "." . $entity_uuid;
- //Delete Data
- $activeStorage = new Drupal\content_sync\Content\ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot');
- $activeStorage->cs_delete($name);
+ if (isset($instances[$entity_type]) && $instances[$entity_type] instanceof ContentEntityType) {
+ // Update the data for diff.
+ $name = $entity_type . '.' . $entity_bundle . '.' . $entity_uuid;
+ // Delete Data.
+ /** @var \Drupal\content_sync\Content\DatabaseStorage $activeStorage */
+ $activeStorage = \Drupal::service('content.storage.active');
+ $activeStorage->contentSyncDelete($name);
// Invalidate the CS Cache of the entity.
- $cache = \Drupal::cache('content')->invalidate($entity_type.".".$entity_bundle.":".$name);
+ \Drupal::cache('content')->invalidate($entity_type . '.' . $entity_bundle . ':' . $name);
}
}
/**
* Implements hook_cron().
*/
-function content_sync_cron() {
+function content_sync_cron() : void {
// Adapted from the drupal_batch queue garbage collection to clean up our
// multiple queues.
diff --git a/content_sync.routing.yml b/content_sync.routing.yml
index c7527c6..612f5f5 100755
--- a/content_sync.routing.yml
+++ b/content_sync.routing.yml
@@ -97,7 +97,7 @@ content.help.about:
_controller: '\Drupal\content_sync\Controller\ContentHelpController::about'
_title: 'How can we help you?'
requirements:
- _permission: 'access administration pages'
+ _permission: "access administration pages"
content_sync.element.message.close:
path: '/content_sync/message/close/{storage}/{id}'
diff --git a/content_sync.services.yml b/content_sync.services.yml
index 54daee9..f61350d 100755
--- a/content_sync.services.yml
+++ b/content_sync.services.yml
@@ -1,4 +1,7 @@
services:
+ logger.channel.content_sync:
+ parent: logger.channel_base
+ arguments: ['content_sync']
logger.cslog:
class: Drupal\content_sync\Logger\ContentSyncLog
arguments: ['@database', '@logger.log_message_parser']
@@ -17,9 +20,8 @@ services:
class: Drupal\Core\Config\CachedStorage
arguments: ['@content.storage.active', '@cache.content']
content.storage.active:
- class: Drupal\Core\Config\DatabaseStorage
+ class: Drupal\content_sync\Content\DatabaseStorage
arguments: ['@database', 'cs_db_snapshot']
- public: false
tags:
- { name: backend_overridable }
cache.content:
@@ -43,7 +45,16 @@ services:
arguments: ['@serializer', '@entity_type.manager']
content_sync.manager:
class: Drupal\content_sync\ContentSyncManager
+ autowire: true
arguments: ['@serializer', '@entity_type.manager','@content_sync.exporter', '@content_sync.importer']
+ content_sync.resolver.export:
+ class: Drupal\content_sync\DependencyResolver\ExportQueueResolver
+ autowire: true
+ content_sync.resolver.import:
+ class: Drupal\content_sync\DependencyResolver\ImportQueueResolver
+ autowire: true
+
+
content_sync.normalizer.content_entity:
class: Drupal\content_sync\Normalizer\ContentEntityNormalizer
arguments: ['@entity_type.manager', '@entity_type.repository', '@entity_field.manager', '@entity_type.bundle.info', '@entity.repository', '@plugin.manager.sync_normalizer_decorator']
diff --git a/drush.12.5+.services.yml b/drush.12.5+.services.yml
new file mode 100644
index 0000000..e79427e
--- /dev/null
+++ b/drush.12.5+.services.yml
@@ -0,0 +1,4 @@
+---
+# XXX: Drush services in 12+ are expected to be discovered
+# and constructed by other means.
+services: {}
diff --git a/drush.9-11.services.yml b/drush.9-11.services.yml
index 4587676..28c3f81 100644
--- a/drush.9-11.services.yml
+++ b/drush.9-11.services.yml
@@ -14,7 +14,6 @@ services:
- '@config.typed'
- '@module_installer'
- '@theme_handler'
- - '@string_translation'
- '@content_sync.snaphoshot'
tags:
- { name: drush.command }
diff --git a/includes/content_sync.theme.inc b/includes/content_sync.theme.inc
index cfd44b5..99ed30f 100644
--- a/includes/content_sync.theme.inc
+++ b/includes/content_sync.theme.inc
@@ -4,21 +4,9 @@
* @file
* Preprocessors and helper functions to make theming easier.
*/
-use Drupal\Core\Link;
-use Drupal\Core\Render\Element;
-use Drupal\Core\Url;
-use Drupal\Core\Serialization\Yaml;
-use Drupal\Core\Template\Attribute;
-use Drupal\Component\Utility\Xss;
-use Drupal\webform\Element\WebformCodeMirror;
-use Drupal\webform\WebformMessageManagerInterface;
-use Drupal\webform\Utility\WebformYaml;
-use Drupal\webform\Utility\WebformDateHelper;
-use Drupal\webform\Utility\WebformDialogHelper;
-use Drupal\webform\Utility\WebformElementHelper;
/**
- * Prepares variables for contnt sync help.
+ * Prepares variables for content_sync_help templates.
*
* Default template: content_sync_help.html.twig.
*
@@ -27,7 +15,7 @@ use Drupal\webform\Utility\WebformElementHelper;
* - title: Help title.
* - content: Help content.
*/
-function template_preprocess_content_sync_help(array &$variables) {
+function template_preprocess_content_sync_help(array &$variables) : void {
/** @var \Drupal\content_sync\ContentSyncHelpManagerInterface $help_manager */
$help_manager = \Drupal::service('content_sync.help_manager');
@@ -51,12 +39,8 @@ function template_preprocess_content_sync_help(array &$variables) {
$variables['help'] = $help;
}
-/******************************************************************************/
-// Element templates
-/******************************************************************************/
-
/**
- * Prepares variables for Content Sync message templates.
+ * Prepares variables for content_sync_message templates.
*
* Default template: content-sync-message.html.twig.
*
@@ -67,7 +51,7 @@ function template_preprocess_content_sync_help(array &$variables) {
*
* @see template_preprocess_container()
*/
-function template_preprocess_content_sync_message(array &$variables) {
+function template_preprocess_content_sync_message(array &$variables) : void {
$variables['has_parent'] = FALSE;
$element = $variables['element'];
// Ensure #attributes is set.
@@ -87,4 +71,5 @@ function template_preprocess_content_sync_message(array &$variables) {
if (isset($element['#closed'])) {
$variables['closed'] = $element['#closed'];
}
-}
\ No newline at end of file
+
+}
diff --git a/js/content_sync.element.message.js b/js/content_sync.element.message.js
index 73832ad..c31fe45 100644
--- a/js/content_sync.element.message.js
+++ b/js/content_sync.element.message.js
@@ -43,24 +43,24 @@
function isClosed($element, storage, id) {
if (!id || !storage) {
- return false;
+ return FALSE;
}
switch (storage) {
case 'local':
if (window.localStorage) {
- return localStorage.getItem('Drupal.content_sync.message.' + id) || false;
+ return localStorage.getItem('Drupal.content_sync.message.' + id) || FALSE;
}
- return false;
+ return FALSE;
case 'session':
if (window.sessionStorage) {
- return sessionStorage.getItem('Drupal.content_sync.message.' + id) || false;
+ return sessionStorage.getItem('Drupal.content_sync.message.' + id) || FALSE;
}
- return false;
+ return FALSE;
default:
- return false;
+ return FALSE;
}
}
@@ -72,20 +72,20 @@
switch (storage) {
case 'local':
if (window.localStorage) {
- localStorage.setItem('Drupal.content_sync.message.' + id, true);
+ localStorage.setItem('Drupal.content_sync.message.' + id, TRUE);
}
break;
case 'session':
if (window.sessionStorage) {
- sessionStorage.setItem('Drupal.content_sync.message.' + id, true);
+ sessionStorage.setItem('Drupal.content_sync.message.' + id, TRUE);
}
break;
case 'user':
case 'state':
$.get($element.find('.js-content_sync-message__link').attr('href'));
- return true;
+ return TRUE;
}
}
diff --git a/js/content_sync.help.js b/js/content_sync.help.js
index 42248fa..59cfd93 100644
--- a/js/content_sync.help.js
+++ b/js/content_sync.help.js
@@ -21,7 +21,7 @@
$widget.accordion({
header: 'h2',
- collapsible: true,
+ collapsible: TRUE,
heightStyle: 'content'
});
diff --git a/src/Content/ContentFileStorageFactory.php b/src/Content/ContentFileStorageFactory.php
index 443a8a6..8540852 100644
--- a/src/Content/ContentFileStorageFactory.php
+++ b/src/Content/ContentFileStorageFactory.php
@@ -10,24 +10,14 @@
*/
class ContentFileStorageFactory {
- /**
- * Returns a FileStorage object working with the active content directory.
- *
- * @return \Drupal\Core\Config\FileStorage FileStorage
- *
- * @deprecated in Drupal 8.0.x and will be removed before 9.0.0. Drupal core
- * no longer creates an active directory.
- */
- public static function getActive() {
- return new FileStorage(content_sync_get_content_directory('active') . "/entities");
- }
-
/**
* Returns a FileStorage object working with the sync content directory.
*
- * @return \Drupal\Core\Config\FileStorage FileStorage
+ * @return \Drupal\Core\Config\FileStorage
+ * File storage for sync content.
*/
public static function getSync() {
return new FileStorage(content_sync_get_content_directory(ContentSyncManagerInterface::DEFAULT_DIRECTORY) . "/entities");
}
+
}
diff --git a/src/Content/ContentStorageComparer.php b/src/Content/ContentStorageComparer.php
index 08a8464..d311c67 100644
--- a/src/Content/ContentStorageComparer.php
+++ b/src/Content/ContentStorageComparer.php
@@ -2,7 +2,6 @@
namespace Drupal\content_sync\Content;
-use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\StorageComparer;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\Entity\ConfigDependencyManager;
@@ -20,8 +19,12 @@ class ContentStorageComparer extends StorageComparer {
* Largely copypasta of the parent, with the exception of rolling
* "NullBackend" caches instead of "MemoryBackend".
*/
- public function __construct(StorageInterface $source_storage, StorageInterface $target_storage) {
-
+ public function __construct(
+ StorageInterface $source_storage,
+ StorageInterface $target_storage,
+ ) {
+ // XXX: Intentionally avoiding calling parent constructor to avoid its
+ // cache initialization.
$this->sourceCacheStorage = new NullBackend(__CLASS__ . '::source');
$this->sourceStorage = new CachedStorage($source_storage, $this->sourceCacheStorage);
$this->targetCacheStorage = new NullBackend(__CLASS__ . '::target');
@@ -31,32 +34,46 @@ public function __construct(StorageInterface $source_storage, StorageInterface $
}
/**
- * {@inheritdoc}
+ * Create change list given a target collection.
+ *
+ * @param string $collection
+ * The collection for which to create a change list.
+ *
+ * @return self
+ * Fluent interface.
*/
- public function createChangelistbyCollection($collection) {
+ public function createChangelistbyCollection($collection) : self {
$this->changelist[$collection] = $this->getEmptyChangelist();
$this->getAndSortConfigData($collection);
$this->addChangelistCreate($collection);
$this->addChangelistUpdate($collection);
$this->addChangelistDelete($collection);
// Only collections that support configuration entities can have renames.
- if ($collection == StorageInterface::DEFAULT_COLLECTION) {
+ if ($collection === StorageInterface::DEFAULT_COLLECTION) {
$this->addChangelistRename($collection);
}
return $this;
}
/**
- * {@inheritdoc}
+ * Create change list given collection and target names.
+ *
+ * @param string $collection
+ * The collection for which to create a change list.
+ * @param string $names
+ * Comma-separated set of names to consider.
+ *
+ * @return self
+ * Fluent interface.
*/
- public function createChangelistbyCollectionAndNames($collection, $names) {
+ public function createChangelistbyCollectionAndNames(string $collection, string $names) : self {
$this->changelist[$collection] = $this->getEmptyChangelist();
- if ($this->getAndSortContentDataByCollectionAndNames($collection, $names)){
+ if ($this->getAndSortContentDataByCollectionAndNames($collection, $names)) {
$this->addChangelistCreate($collection);
$this->addChangelistUpdate($collection);
$this->addChangelistDelete($collection);
// Only collections that support configuration entities can have renames.
- if ($collection == StorageInterface::DEFAULT_COLLECTION) {
+ if ($collection === StorageInterface::DEFAULT_COLLECTION) {
$this->addChangelistRename($collection);
}
}
@@ -65,33 +82,39 @@ public function createChangelistbyCollectionAndNames($collection, $names) {
/**
* Gets and sorts configuration data from the source and target storages.
+ *
+ * @param string $collection
+ * The collection in which to get and sort content data.
+ * @param string $csv_names
+ * Comma-separated set of names to consider.
+ *
+ * @return bool
+ * TRUE if we found anything; otherwise, FALSE.
*/
- protected function getAndSortContentDataByCollectionAndNames($collection, $names) {
- $names = explode(',', $names);
- $target_names = [];
- $source_names = [];
- foreach($names as $key => $name){
- $name = $collection.'.'.$name;
+ protected function getAndSortContentDataByCollectionAndNames(string $collection, string $csv_names) : bool {
+ $names = explode(',', $csv_names);
+
+ $targets = [];
+ $sources = [];
+
+ foreach ($names as $name) {
+ $name = $collection . '.' . $name;
$source_storage = $this->getSourceStorage($collection);
$target_storage = $this->getTargetStorage($collection);
- if($source_storage->exists($name) ||
- $target_storage->exists($name) ){
- $target_names = array_merge($target_names, $target_storage->listAll($name));
- $source_names = array_merge($source_names, $source_storage->listAll($name));
+ if ($source_storage->exists($name) ||
+ $target_storage->exists($name)) {
+ $targets[] = $target_storage->listAll($name);
+ $sources[] = $source_storage->listAll($name);
}
}
- $target_names = array_filter($target_names);
- $source_names = array_filter($source_names);
- if(!empty($target_names) || !empty($source_names)){
- // Prime the static caches by reading all the configuration in the source
- // and target storages.
- $target_data = $target_storage->readMultiple($target_names);
- $source_data = $source_storage->readMultiple($source_names);
+ $target_names = array_unique(array_filter(array_merge(...$targets)));
+ $source_names = array_unique(array_filter(array_merge(...$sources)));
+ if (!empty($target_names) || !empty($source_names)) {
$this->targetNames[$collection] = $target_names;
$this->sourceNames[$collection] = $source_names;
- return true;
+ return TRUE;
}
- return false;
+ return FALSE;
}
/**
@@ -101,7 +124,7 @@ protected function getAndSortContentDataByCollectionAndNames($collection, $names
* the two StorageInterface::readMultiple() calls moved, so they can be
* avoided.
*/
- protected function getAndSortConfigData($collection) {
+ protected function getAndSortConfigData($collection) : void {
$source_storage = $this
->getSourceStorage($collection);
$target_storage = $this
@@ -113,7 +136,7 @@ protected function getAndSortConfigData($collection) {
// If the collection only supports simple configuration do not use
// configuration dependencies.
- if ($collection == StorageInterface::DEFAULT_COLLECTION) {
+ if ($collection === StorageInterface::DEFAULT_COLLECTION) {
// XXX: Upstream, this exists outside of this if branch; however, that
// unnecessarily leads to massive memory usage.
// Prime the static caches by reading all the configuration in the source
@@ -143,7 +166,7 @@ protected function getAndSortConfigData($collection) {
* @param string $collection
* The name of the collection to clear.
*/
- public function resetCollectionChangelist($collection) {
+ public function resetCollectionChangelist(string $collection) : void {
$this->changelist[$collection] = $this->getEmptyChangelist();
}
diff --git a/src/Content/ContentDatabaseStorage.php b/src/Content/DatabaseStorage.php
similarity index 56%
rename from src/Content/ContentDatabaseStorage.php
rename to src/Content/DatabaseStorage.php
index afe174f..df8a3b6 100644
--- a/src/Content/ContentDatabaseStorage.php
+++ b/src/Content/DatabaseStorage.php
@@ -2,29 +2,33 @@
namespace Drupal\content_sync\Content;
-use Drupal\Core\Config\DatabaseStorage as DatabaseStorage;
-use Drupal\Core\Database\Database;
+use Drupal\Core\Config\DatabaseStorage as UpstreamDatabaseStorage;
+use Drupal\Core\Config\StorageException;
/**
* Defines the Database storage.
+ *
+ * Collection-independent storage manipulations.
+ *
+ * phpcs:disable Drupal.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
*/
-class ContentDatabaseStorage extends DatabaseStorage {
+class DatabaseStorage extends UpstreamDatabaseStorage implements DatabaseStorageInterface {
/**
- * {@inheritdoc}
+ * {@inheritDoc}
*/
- public function cs_write($name, array $data, $collection) {
- $data = $this->encode($data);
+ public function contentSyncWrite(string $name, array $data, string $collection) : bool {
+ $encoded_data = $this->encode($data);
try {
- return $this->cs_doWrite($name, $data, $collection);
+ return $this->contentSyncDoWrite($name, $encoded_data, $collection);
}
catch (\Exception $e) {
// If there was an exception, try to create the table.
if ($this->ensureTableExists()) {
- return $this->cs_doWrite($name, $data, $collection);
+ return $this->contentSyncDoWrite($name, $encoded_data, $collection);
}
// Some other failure that we can not recover from.
- throw $e;
+ throw new StorageException($e->getMessage(), 0, $e);
}
}
@@ -39,11 +43,10 @@ public function cs_write($name, array $data, $collection) {
* The content collection name, entity type + bundle.
*
* @return bool
+ * TRUE on success; otherwise, FALSE.
*/
- protected function cs_doWrite($name, $data, $collection) {
- $this->connection->delete($this->table, $this->options)
- ->condition('name', $name)
- ->execute();
+ protected function contentSyncDoWrite(string $name, string $data, string $collection) : bool {
+ $this->contentSyncDelete($name);
return (bool) $this->connection->merge($this->table, $this->options)
->keys(['collection', 'name'], [$collection, $name])
@@ -52,31 +55,26 @@ protected function cs_doWrite($name, $data, $collection) {
}
/**
- * {@inheritdoc}
+ * {@inheritDoc}
*/
- public function cs_read($name) {
- $data = FALSE;
+ public function contentSyncRead($name) : array|false {
try {
$raw = $this->connection->query('SELECT data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', [':name' => $name], $this->options)->fetchField();
if ($raw !== FALSE) {
- $data = $this->decode($raw);
+ return $this->decode($raw);
}
}
catch (\Exception $e) {
// If we attempt a read without actually having the database or the table
// available, just return FALSE so the caller can handle it.
}
- return $data;
+ return FALSE;
}
/**
- * Implements Drupal\Core\Config\StorageInterface::delete().
- *
- * @throws PDOException
- *
- * @todo Ignore replica targets for data manipulation operations.
+ * {@inheritDoc}
*/
- public function cs_delete($name) {
+ public function contentSyncDelete($name) : bool {
return (bool) $this->connection->delete($this->table, $this->options)
->condition('name', $name)
->execute();
diff --git a/src/Content/DatabaseStorageInterface.php b/src/Content/DatabaseStorageInterface.php
new file mode 100644
index 0000000..c5993c4
--- /dev/null
+++ b/src/Content/DatabaseStorageInterface.php
@@ -0,0 +1,60 @@
+help[$id])) ? $this->help[$id] : NULL;
}
- else {
- return $this->help;
- }
+ return $this->help;
}
/**
@@ -143,7 +140,7 @@ public function buildHelp($route_name, RouteMatchInterface $route_match) {
'#type' => 'content_sync_message',
'#message_type' => $help['message_type'],
'#message_close' => $help['message_close'],
- '#message_id' => ($help['message_id']) ? $help['message_id'] : 'content_sync.help.' . $help['id'],
+ '#message_id' => $help['message_id'] ?: 'content_sync.help.' . $help['id'],
'#message_storage' => $help['message_storage'],
'#message_message' => [
'#theme' => 'content_sync_help',
@@ -184,7 +181,6 @@ public function buildIndex() {
return $build;
}
-
/**
* {@inheritdoc}
*/
@@ -195,7 +191,7 @@ public function buildHelpMenu() {
];
$issue_query = $default_query + [
- 'body' => "
+ 'body' => "
Problem/Motivation
(Why the issue was filed, steps to reproduce the problem, etc.)
@@ -207,17 +203,17 @@ public function buildHelpMenu() {
Proposed resolution
(Description of the proposed solution, the rationale behind it, and workarounds for people who cannot use the patch.)",
- ];
+ ];
$feature_query = $default_query + [
- 'body' => "
+ 'body' => "
Problem/Motivation
(Explain why this new feature or functionality is important or useful.)
Proposed resolution
(Description of the proposed solution, the rationale behind it, and workarounds for people who cannot use the patch.)",
- ];
-
+ ];
+
$links = [];
$links['index'] = [
'title' => $this->t('How can we help you?'),
@@ -234,30 +230,29 @@ public function buildHelpMenu() {
];
$links['issue'] = [
'title' => $this->t('Report a Bug/Issue'),
- 'url' => Url::fromUri('https://www.drupal.org/node/add/project-issue/content_sync', ['query' => $issue_query, 'attributes' => ['target' => '_blank']]),
+ 'url' => Url::fromUri('https://www.drupal.org/node/add/project-issue/content_sync', [
+ 'query' => $issue_query,
+ 'attributes' => ['target' => '_blank'],
+ ]),
];
$links['request'] = [
'title' => $this->t('Request Feature'),
- 'url' => Url::fromUri('https://www.drupal.org/node/add/project-issue/content_sync', ['query' => $feature_query, 'attributes' => ['target' => '_blank']]),
+ 'url' => Url::fromUri('https://www.drupal.org/node/add/project-issue/content_sync', [
+ 'query' => $feature_query,
+ 'attributes' => ['target' => '_blank'],
+ ]),
];
$links['support'] = [
'title' => $this->t('Additional Support'),
'url' => Url::fromUri('https://www.drupal.org/project/content_sync', ['attributes' => ['target' => '_blank']]),
];
- /*$links['community'] = [
- 'title' => $this->t('Join the Drupal Community'),
- 'url' => Url::fromUri('https://register.drupal.org/user/register', ['query' => ['destination' => '/project/content_sync'], 'attributes' => ['target' => '_blank']]),
- ];
- $links['association'] = [
- 'title' => $this->t('Support the Drupal Association'),
- 'url' => Url::fromUri('https://www.drupal.org/association/campaign/value-2017', ['attributes' => ['target' => '_blank']]),
- ];*/
+
return [
'#type' => 'container',
'#attributes' => ['class' => ['content_sync-help-menu']],
'operations' => [
'#type' => 'operations',
- '#links' => $links
+ '#links' => $links,
],
];
}
@@ -319,11 +314,6 @@ public function buildUses($docs = FALSE) {
return $build;
}
-
- /****************************************************************************/
- // Index sections.
- /****************************************************************************/
-
/**
* {@inheritdoc}
*/
@@ -336,7 +326,7 @@ public function buildAbout() {
'#attributes' => ['class' => ['button', 'button--primary']],
'#suffix' => '
',
];
-
+
$build = [
'title' => [
'#markup' => $this->t('How can we help you?'),
@@ -349,32 +339,14 @@ public function buildAbout() {
],
];
- //$build['content']['quote'] = [];
- //$build['content']['quote']['image'] = [
- // '#theme' => 'image',
- // '#uri' => 'https://pbs.twimg.com/media/C-RXmp7XsAEgMN2.jpg',
- // '#alt' => $this->t('DrupalCon Baltimore'),
- // '#prefix' => '',
- // '#suffix' => '
',
- //];
- //$build['content']['quote']['content']['#markup'] = '' . $this->t('It’s really the Drupal community and not so much the software that makes the Drupal project what it is. So fostering the Drupal community is actually more important than just managing the code base.') . '' . $this->t('- Dries Buytaert') . '
';
-
// Content Sync.
$build['content']['content_sync'] = [];
$build['content']['content_sync']['title']['#markup'] = '' . $this->t('Need help with the Content Sync module?') . '
';
$build['content']['content_sync']['content']['#markup'] = '' . $this->t('The best place to start is by reading the documentation, watching the help videos, and looking at the examples and templates included in the content sync module.') . '
';
$build['content']['content_sync']['link'] = $link_base + [
- '#url' => Url::fromUri('https://www.drupal.org/project/content_sync'),
- '#title' => $this->t('Get help with the Content Sync module'),
- ];
-
- // Help.
- /* if ($help_video = $this->buildAboutVideo('uQo-1s2h06E')) {
- $build['content']['help'] = [];
- $build['content']['help']['title']['#markup'] = '' . $this->t('Help us help you') . '
';
- $build['content']['help']['video'] = $this->buildAboutVideo('uQo-1s2h06E');
- $build['content']['help']['#suffix'] = '
';
- }*/
+ '#url' => Url::fromUri('https://www.drupal.org/project/content_sync'),
+ '#title' => $this->t('Get help with the Content Sync module'),
+ ];
// Issue.
$build['content']['issue'] = [];
@@ -382,53 +354,19 @@ public function buildAbout() {
$build['content']['issue']['content']['#markup'] = '' . $this->t('The first step is to review the Content Sync module’s issue queue for similar issues. You may be able to find a patch or other solution there. You may also be able to contribute to an existing issue with your additional details.') . '
' .
'' . $this->t('If you need to create a new issue, please make and export example of the faulty functionality/YAML files. This will help guarantee that your issue is reproducible. To get the best response, it’s helpful to craft a good issue report. You can find advice and tips on the How to create a good issue page. Please use the issue summary template when creating new issues.') . '
';
$build['content']['issue']['link'] = $link_base + [
- '#url' => $links['issue']['url'],
- '#title' => $this->t('Report a bug/issue with the Content Sync module'),
- ];
+ '#url' => $links['issue']['url'],
+ '#title' => $this->t('Report a bug/issue with the Content Sync module'),
+ ];
// Request.
$build['content']['request'] = [];
$build['content']['request']['title']['#markup'] = '' . $this->t('How can you request a feature?') . '
';
- $build['content']['request']['content']['#markup'] = '' . $this->t('Feature requests can be added to the Content Sync module\'s issue queue. Use the same tips provided for creating issue reports to help you author a feature request. The better you can define your needs and ideas, the easier it will be for people to help you.') . '
';
+ $build['content']['request']['content']['#markup'] = '' . $this->t("Feature requests can be added to the Content Sync module's issue queue. Use the same tips provided for creating issue reports to help you author a feature request. The better you can define your needs and ideas, the easier it will be for people to help you.") . '
';
$build['content']['request']['link'] = $link_base + [
- '#url' => $links['request']['url'],
- '#title' => $this->t('Help improve the ContentSync module'),
- ];
-
- /*
- // Community.
- $build['content']['community'] = [];
- $build['content']['community']['title']['#markup'] = '' . $this->t('Are you new to Drupal?') . '
';
- $build['content']['community']['content']['#markup'] = '' . $this->t('As an open source project, we don’t have employees to provide Drupal improvements and support. We depend on our diverse community of passionate volunteers to move the project forward. Volunteers work not just on web development and user support but also on many other contributions and interests such as marketing, organising user groups and camps, speaking at events, maintaining documentation, and helping to review issues.') . '
';
- $build['content']['community']['link'] = $link_base + [
- '#url' => Url::fromUri('https://www.drupal.org/getting-involved'),
- '#title' => $this->t('Get involved in the Drupal community'),
- ];
-
- // Register.
- $build['content']['register'] = [];
- $build['content']['register']['title']['#markup'] = '' . $this->t('Start by creating your Drupal.org user account') . '
';
- $build['content']['register']['content']['#markup'] = '' . $this->t('When you create a Drupal.org account, you gain access to a whole ecosystem of Drupal.org sites and services. Your account works on Drupal.org and any of its subsites including Drupal Groups, Drupal Jobs, Drupal Association and more.') . '
';
- $build['content']['register']['link'] = $link_base + [
- '#url' => $links['community']['url'],
- '#title' => $this->t('Become a member of the Drupal community'),
+ '#url' => $links['request']['url'],
+ '#title' => $this->t('Help improve the ContentSync module'),
];
- // Association.
- $build['content']['association'] = [];
- $build['content']['association']['title']['#markup'] = '' . $this->t('Join the Drupal Association') . '
';
- $build['content']['association']['content'] = [
- 'content' => ['#markup' => $this->t('The Drupal Association is dedicated to fostering and supporting the Drupal software project, the community, and its growth. We help the Drupal community with funding, infrastructure, education, promotion, distribution, and online collaboration at Drupal.org.')],
- '#prefix' => '',
- '#suffix' => '
',
- ];
- $build['content']['association']['video'] = $this->buildAboutVideo('LZWqFSMul84');
- $build['content']['association']['link'] = $link_base + [
- '#url' => $links['association']['url'],
- '#title' => $this->t('Learn more about the Drupal Association'),
- ];
- */
-
return $build;
}
@@ -450,22 +388,20 @@ protected function buildAboutVideo($youtube_id) {
'#youtube_id' => $youtube_id,
'#autoplay' => FALSE,
];
- break;
case 'link':
return [
'#type' => 'link',
- '#title' => t('Watch video'),
+ '#title' => $this->t('Watch video'),
'#url' => Url::fromUri('https://youtu.be/' . $youtube_id),
'#attributes' => ['class' => ['button', 'button-action', 'button--small', 'button-content_sync-play']],
'#prefix' => ' ',
];
- break;
case 'hidden':
default:
return [];
- break;
+
}
}
@@ -535,10 +471,6 @@ protected function initHelp() {
'video_id' => 'introduction',
];
- /****************************************************************************/
- // General.
- /****************************************************************************/
-
// Content Sync.
$help['content_sync'] = [
'routes' => [
@@ -557,7 +489,7 @@ protected function initHelp() {
],
'title' => $this->t('Exporting the full content'),
'url' => Url::fromRoute('content.export_full'),
- 'content' => $this->t('Create and download an archive consisting of all your site\'s content exported as *.yml files as a gzipped tar file.'),
+ 'content' => $this->t("Create and download an archive consisting of all your site's content exported as *.yml files as a gzipped tar file."),
'menu' => TRUE,
];
diff --git a/src/ContentSyncManager.php b/src/ContentSyncManager.php
index 8c8c931..a56c4c7 100755
--- a/src/ContentSyncManager.php
+++ b/src/ContentSyncManager.php
@@ -2,75 +2,71 @@
namespace Drupal\content_sync;
+use Drupal\content_sync\DependencyResolver\ContentSyncResolverInterface;
use Drupal\content_sync\DependencyResolver\ImportQueueResolver;
use Drupal\content_sync\DependencyResolver\ExportQueueResolver;
use Drupal\content_sync\Exporter\ContentExporterInterface;
use Drupal\content_sync\Importer\ContentImporterInterface;
+use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Serializer\Serializer;
+/**
+ * Manager service for content_sync.
+ */
class ContentSyncManager implements ContentSyncManagerInterface {
- const DELIMITER = '.';
+ use AutowireTrait;
- /**
- * @var \Symfony\Component\Serializer\Serializer
- */
- protected $serializer;
-
- /**
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
- */
- protected $entityTypeManager;
-
- /**
- * @var \Drupal\content_sync\Exporter\ContentExporterInterface
- */
- protected $contentExporter;
-
- /**
- * @var \Drupal\content_sync\Importer\ContentImporterInterface
- */
- protected $contentImporter;
+ const DELIMITER = '.';
/**
- * ContentSyncManager constructor.
+ * Constructor.
*/
- public function __construct(Serializer $serializer, EntityTypeManagerInterface $entity_type_manager, ContentExporterInterface $content_exporter, ContentImporterInterface $content_importer) {
- $this->serializer = $serializer;
- $this->entityTypeManager = $entity_type_manager;
- $this->contentExporter = $content_exporter;
- $this->contentImporter = $content_importer;
- }
+ public function __construct(
+ protected Serializer $serializer,
+ protected EntityTypeManagerInterface $entityTypeManager,
+ protected ContentExporterInterface $contentExporter,
+ protected ContentImporterInterface $contentImporter,
+ #[Autowire(service: 'content_sync.resolver.export')]
+ protected ContentSyncResolverInterface|ExportQueueResolver $exportResolver,
+ #[Autowire(service: 'content_sync.resolver.import')]
+ protected ContentSyncResolverInterface|ImportQueueResolver $importResolver,
+ ) {}
/**
- * @return \Drupal\content_sync\Exporter\ContentExporterInterface
+ * {@inheritDoc}
*/
- public function getContentExporter() {
+ public function getContentExporter() : ContentExporterInterface {
return $this->contentExporter;
}
/**
- * @return \Drupal\content_sync\Importer\ContentImporterInterface
+ * {@inheritDoc}
*/
- public function getContentImporter() {
+ public function getContentImporter() : ContentImporterInterface {
return $this->contentImporter;
}
-
/**
- * @param $file_names
- * @param $directory
+ * Generate import queue.
+ *
+ * @param iterable $file_names
+ * Iterable of filenames to enqueue.
+ * @param string $directory
+ * Directory in which the files exist.
*
* @return array
+ * An array of items to import.
*/
- public function generateImportQueue($file_names, $directory) {
+ public function generateImportQueue(iterable $file_names, string $directory) : array {
$queue = [];
foreach ($file_names as $file) {
$ids = explode('.', $file);
- list($entity_type_id, $bundle, $uuid) = $ids + ['', '', ''];
+ [$entity_type_id, $bundle] = $ids + ['', ''];
$file_path = $directory . "/" . $entity_type_id . "/" . $bundle . "/" . $file . ".yml";
- if (!file_exists($file_path) || !$this->isValidFilename($file)) {
+ if (!file_exists($file_path) || !static::isValidFilename($file)) {
continue;
}
$content = file_get_contents($file_path);
@@ -79,49 +75,54 @@ public function generateImportQueue($file_names, $directory) {
$decoded_entities[$file] = $decoded_entity;
}
if (!empty($decoded_entities)) {
- $resolver = new ImportQueueResolver();
- $queue = $resolver->resolve($decoded_entities);
+ $queue = $this->importResolver->resolve($decoded_entities);
}
return $queue;
}
/**
- * @param $file_names
- * @param $directory
+ * Generate export queue.
+ *
+ * @param array $decoded_entities
+ * File names to enqueue.
+ * @param array $visited
+ * Already touched entities, when resolving dependencies.
*
* @return array
+ * Array of entity names to export.
*/
- public function generateExportQueue($decoded_entities, $visited) {
+ public function generateExportQueue(array $decoded_entities, array $visited) : array {
$queue = [];
if (!empty($decoded_entities)) {
- $resolver = new ExportQueueResolver();
- $queue = $resolver->resolve($decoded_entities, $visited);
+ $queue = $this->exportResolver->resolve($decoded_entities, $visited);
}
return $queue;
}
/**
- * @return \Symfony\Component\Serializer\Serializer
+ * {@inheritDoc}
*/
- public function getSerializer() {
+ public function getSerializer() : Serializer {
return $this->serializer;
}
/**
- * @return \Drupal\Core\Entity\EntityTypeManagerInterface
+ * {@inheritDoc}
*/
- public function getEntityTypeManager() {
+ public function getEntityTypeManager() : EntityTypeManagerInterface {
return $this->entityTypeManager;
}
/**
- * Checks filename structure
+ * Checks filename structure.
*
- * @param $filename
+ * @param string $filename
+ * The filename to check.
*
* @return bool
+ * TRUE if valid; otherwise, FALSE.
*/
- protected function isValidFilename($filename) {
+ protected static function isValidFilename(string $filename) : bool {
$parts = explode(static::DELIMITER, $filename);
return count($parts) === 3;
}
diff --git a/src/ContentSyncManagerInterface.php b/src/ContentSyncManagerInterface.php
index 081fb3a..d2b92ee 100755
--- a/src/ContentSyncManagerInterface.php
+++ b/src/ContentSyncManagerInterface.php
@@ -2,34 +2,36 @@
namespace Drupal\content_sync;
+use Drupal\content_sync\Exporter\ContentExporterInterface;
+use Drupal\content_sync\Importer\ContentImporterInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Symfony\Component\Serializer\Serializer;
/**
- * Interface ContentSyncManagerInterface.
- *
- * @package Drupal\content_sync
+ * Manager interface for content_sync.
*/
interface ContentSyncManagerInterface {
const DEFAULT_DIRECTORY = 'sync';
/**
- * @return \Drupal\content_sync\Importer\ContentImporterInterface
+ * Get the content importer.
*/
- public function getContentImporter();
+ public function getContentImporter() : ContentImporterInterface;
/**
- * @return \Drupal\content_sync\Exporter\ContentExporterInterface
+ * Get the content exporter.
*/
- public function getContentExporter();
+ public function getContentExporter() : ContentExporterInterface;
/**
- * @return \Symfony\Component\Serializer\Serializer
+ * Get the serializer.
*/
- public function getSerializer();
+ public function getSerializer() : Serializer;
/**
- * @return \Drupal\Core\Entity\EntityTypeManagerInterface
+ * Get the entity type manager.
*/
- public function getEntityTypeManager();
+ public function getEntityTypeManager() : EntityTypeManagerInterface;
}
diff --git a/src/Controller/ContentController.php b/src/Controller/ContentController.php
index aa33898..b124551 100644
--- a/src/Controller/ContentController.php
+++ b/src/Controller/ContentController.php
@@ -12,96 +12,48 @@
use Drupal\Core\File\FileSystemInterface;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\MimeTypeGuesserInterface;
/**
* Returns responses for content module routes.
*/
class ContentController implements ContainerInjectionInterface {
- /**
- * The target storage.
- *
- * @var \Drupal\Core\Config\StorageInterface
- */
- protected $targetStorage;
-
- /**
- * The source storage.
- *
- * @var \Drupal\Core\Config\StorageInterface
- */
- protected $sourceStorage;
-
- /**
- * The content manager.
- *
- * @var \Drupal\Core\Config\ConfigManagerInterface
- */
- protected $contentManager;
-
- /**
- * The file download controller.
- *
- * @var \Drupal\system\FileDownloadController
- */
- protected $fileDownloadController;
-
- /**
- * The diff formatter.
- *
- * @var \Drupal\Core\Diff\DiffFormatter
- */
- protected $diffFormatter;
/**
- * The filesystem service.
- *
- * @var \Drupal\Core\File\FileSystemInterface
- */
- protected FileSystemInterface $fileSystem;
-
- /**
- * {@inheritdoc}
+ * {@inheritDoc}
*/
- public static function create(ContainerInterface $container) {
+ public static function create(ContainerInterface $container) : self {
return new static(
$container->get('content.storage'),
$container->get('content.storage.sync'),
$container->get('config.manager'),
$container->get('diff.formatter'),
- $container->get('file_system')
+ $container->get('file_system'),
+ $container->get('file.mime_type.guesser'),
);
}
/**
- * Constructs a ContentController object.
- *
- * @param \Drupal\Core\Config\StorageInterface $target_storage
- * The target storage.
- * @param \Drupal\Core\Config\StorageInterface $source_storage
- * The source storage
- * @param \Drupal\system\FileDownloadController $file_download_controller
- * The file download controller.
+ * Constructor.
*/
- public function __construct(StorageInterface $target_storage, StorageInterface $source_storage, ConfigManagerInterface $content_manager, DiffFormatter $diff_formatter, FileSystemInterface $file_system) {
- $this->targetStorage = $target_storage;
- $this->sourceStorage = $source_storage;
- $this->contentManager = $content_manager;
- $this->diffFormatter = $diff_formatter;
- $this->fileSystem = $file_system;
- }
+ public function __construct(
+ protected StorageInterface $targetStorage,
+ protected StorageInterface $sourceStorage,
+ protected ConfigManagerInterface $contentManager,
+ protected DiffFormatter $diffFormatter,
+ protected FileSystemInterface $fileSystem,
+ protected MimeTypeGuesserInterface $mimeTypeGuesser,
+ ) {}
/**
* Downloads a tarball of the site content.
*/
- public function downloadExport() {
- // NOTE: Getting - You are not authorized to access this page.
- //$request = new Request(['file' => 'content.tar.gz']);
- //return $this->fileDownloadController->download($request, 'temporary');
+ public function downloadExport() : BinaryFileResponse|int {
$filename = 'content.tar.gz';
$file_path = $this->fileSystem->getTempDirectory() . '/' . $filename;
- if (file_exists($file_path) ) {
+ if (file_exists($file_path)) {
unset($_SESSION['content_tar_download_file']);
- $mime = \Drupal::service('file.mime_type.guesser')->guess($file_path);
+ $mime = $this->mimeTypeGuesser->guessMimeType($file_path);
$headers = (new Headers())
->addParameterizedHeader('Content-Type', $mime, ['name' => basename($file_path)])
->addTextHeader('Content-Length', filesize($file_path))
@@ -129,10 +81,11 @@ public function downloadExport() {
* (optional) The content collection name. Defaults to the default
* collection.
*
- * @return string
- * Table showing a two-way diff between the active and staged content.
+ * @return array
+ * Renderable array with table showing a two-way diff between the active and
+ * staged content.
*/
- public function diff($source_name, $target_name = NULL, $collection = NULL) {
+ public function diff($source_name, $target_name = NULL, $collection = NULL) : array {
if (!isset($collection)) {
$collection = StorageInterface::DEFAULT_COLLECTION;
}
@@ -141,7 +94,7 @@ public function diff($source_name, $target_name = NULL, $collection = NULL) {
$build = [];
- $build['#title'] = t('View changes of @content_file', ['@content_file' => $source_name]);
+ $build['#title'] = $this->t('View changes of @content_file', ['@content_file' => $source_name]);
// Add the CSS for the inline diff.
$build['#attached']['library'][] = 'system/diff';
@@ -151,8 +104,8 @@ public function diff($source_name, $target_name = NULL, $collection = NULL) {
'class' => ['diff'],
],
'#header' => [
- ['data' => t('Active'), 'colspan' => '2'],
- ['data' => t('Staged'), 'colspan' => '2'],
+ ['data' => $this->t('Active'), 'colspan' => '2'],
+ ['data' => $this->t('Staged'), 'colspan' => '2'],
],
'#rows' => $this->diffFormatter->format($diff),
];
diff --git a/src/Controller/ContentElementController.php b/src/Controller/ContentElementController.php
index dceec9c..3187082 100644
--- a/src/Controller/ContentElementController.php
+++ b/src/Controller/ContentElementController.php
@@ -34,4 +34,5 @@ public function close($storage, $id) {
ContentSyncMessage::setClosed($storage, $id);
return new AjaxResponse();
}
+
}
diff --git a/src/Controller/ContentHelpController.php b/src/Controller/ContentHelpController.php
index ff4b26d..5254b31 100644
--- a/src/Controller/ContentHelpController.php
+++ b/src/Controller/ContentHelpController.php
@@ -36,7 +36,7 @@ public function __construct(ContentSyncHelpManagerInterface $help_manager) {
*/
public static function create(ContainerInterface $container) {
return new static(
- $container->get('content_sync.help_manager')
+ $container->get('content_sync.help_manager'),
);
}
@@ -47,12 +47,13 @@ public static function create(ContainerInterface $container) {
* The current request.
*
* @return array
- * A renderable array containing a help about (aka How can we help you?) page.
+ * A renderable array containing a help about (aka How can we help you?)
+ * page.
*/
public function about(Request $request) {
$build = $this->helpManager->buildAbout();
unset($build['title']);
- $build +=[
+ $build += [
'#prefix' => '',
'#suffix' => '
',
];
diff --git a/src/Controller/ContentLogController.php b/src/Controller/ContentLogController.php
index a617c42..5445065 100644
--- a/src/Controller/ContentLogController.php
+++ b/src/Controller/ContentLogController.php
@@ -3,10 +3,12 @@
namespace Drupal\content_sync\Controller;
use Drupal\Component\Utility\Html;
-use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\Xss;
+use Drupal\content_sync\Logger\LogFilterTrait;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Query\PagerSelectExtender;
+use Drupal\Core\Database\Query\TableSortExtender;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormBuilderInterface;
@@ -22,6 +24,8 @@
*/
class ContentLogController extends ControllerBase {
+ use LogFilterTrait;
+
/**
* The database service.
*
@@ -81,6 +85,11 @@ public static function create(ContainerInterface $container) {
* The date formatter service.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder service.
+ * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+ * The entity type manager service.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public function __construct(Connection $database, ModuleHandlerInterface $module_handler, DateFormatterInterface $date_formatter, FormBuilderInterface $form_builder, EntityTypeManagerInterface $entity_type_manager) {
$this->database = $database;
@@ -96,7 +105,7 @@ public function __construct(Connection $database, ModuleHandlerInterface $module
* @return array
* An array of log level classes.
*/
- public static function getLogLevelClassMap() {
+ public static function getLogLevelClassMap() : array {
return [
RfcLogLevel::DEBUG => 'cslog-debug',
RfcLogLevel::INFO => 'cslog-info',
@@ -121,40 +130,37 @@ public static function getLogLevelClassMap() {
* @see Drupal\content_sync\Form\logClearLogConfirmForm
* @see Drupal\content_sync\Controller\LogController::eventDetails()
*/
- public function overview() {
+ public function overview() : array {
$filter = $this->buildFilterQuery();
$rows = [];
$classes = static::getLogLevelClassMap();
- $this->moduleHandler->loadInclude('content_sync', 'admin.inc');
-
$header = [
// Icon column.
'',
- /* [
- 'data' => $this->t('Type'),
- 'field' => 'w.type',
- 'class' => [RESPONSIVE_PRIORITY_MEDIUM]], */
[
'data' => $this->t('Date'),
'field' => 'w.csid',
'sort' => 'desc',
- 'class' => [RESPONSIVE_PRIORITY_LOW]],
+ 'class' => [RESPONSIVE_PRIORITY_LOW],
+ ],
$this->t('Message'),
[
'data' => $this->t('User'),
'field' => 'ufd.name',
- 'class' => [RESPONSIVE_PRIORITY_MEDIUM]],
+ 'class' => [RESPONSIVE_PRIORITY_MEDIUM],
+ ],
[
'data' => $this->t('Operations'),
- 'class' => [RESPONSIVE_PRIORITY_LOW]],
+ 'class' => [RESPONSIVE_PRIORITY_LOW],
+ ],
];
$query = $this->database->select('cs_logs', 'w')
- ->extend('\Drupal\Core\Database\Query\PagerSelectExtender')
- ->extend('\Drupal\Core\Database\Query\TableSortExtender');
+ ->extend(PagerSelectExtender::class)
+ ->extend(TableSortExtender::class);
$query->fields('w', [
'csid',
'uid',
@@ -177,20 +183,7 @@ public function overview() {
foreach ($result as $log) {
$message = $this->formatMessage($log);
- if ($message && isset($log->csid)) {
- $title = Unicode::truncate(Html::decodeEntities(strip_tags($message)), 256, TRUE, TRUE);
- $log_text = Unicode::truncate($title, 56, TRUE, TRUE);
- // The link generator will escape any unsafe HTML entities in the final
- // text.
- /*$message = $this->l($log_text, new Url('log.event', ['event_id' => $log->csid], [
- 'attributes' => [
- // Provide a title for the link for useful hover hints. The
- // Attribute object will escape any unsafe HTML entities in the
- // final text.
- 'title' => $title,
- ],
- ]));*/
- }
+
$username = [
'#theme' => 'username',
'#account' => $this->userStorage->load($log->uid),
@@ -216,9 +209,6 @@ public function overview() {
'#rows' => $rows,
'#attributes' => ['id' => 'admin-cslog', 'class' => ['admin-cslog']],
'#empty' => $this->t('No log messages available.'),
- //'#attached' => [
- // 'library' => ['cslog/drupal.cslog'],
- //],
];
$build['log_pager'] = ['#type' => 'pager'];
@@ -248,7 +238,7 @@ public function eventDetails($event_id) {
$rows = [
[
['data' => $this->t('Type'), 'header' => TRUE],
- $this->t($cslog->type),
+ $cslog->type,
],
[
['data' => $this->t('Date'), 'header' => TRUE],
@@ -299,17 +289,15 @@ public function eventDetails($event_id) {
/**
* Builds a query for database log administration filters based on session.
*
- * @return array
+ * @return null|array
* An associative array with keys 'where' and 'args'.
*/
- protected function buildFilterQuery() {
+ protected function buildFilterQuery() : ?array {
if (empty($_SESSION['cslog_overview_filter'])) {
- return;
+ return NULL;
}
- $this->moduleHandler->loadInclude('content_sync', 'admin.inc');
-
- $filters = cs_log_filters();
+ $filters = $this->getFilters();
// Build query.
$where = $args = [];
@@ -345,7 +333,7 @@ protected function buildFilterQuery() {
public function formatMessage($row) {
// Check for required properties.
if (isset($row->message, $row->variables)) {
- $variables = @unserialize($row->variables);
+ $variables = @unserialize($row->variables, ['allowed_classes' => FALSE]);
// Messages without variables or user specified text.
if ($variables === NULL) {
$message = Xss::filterAdmin($row->message);
@@ -355,7 +343,7 @@ public function formatMessage($row) {
}
// Message to translate with injected variables.
else {
- $message = $this->t(Xss::filterAdmin($row->message), $variables);
+ $message = strtr(Xss::filterAdmin($row->message), $variables);
}
}
else {
@@ -407,7 +395,7 @@ public function topLogMessages($type) {
}
}
- $build['cs_log_top_table'] = [
+ $build['cs_log_top_table'] = [
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
diff --git a/src/DependencyResolver/AbstractResolver.php b/src/DependencyResolver/AbstractResolver.php
new file mode 100644
index 0000000..cafb7e8
--- /dev/null
+++ b/src/DependencyResolver/AbstractResolver.php
@@ -0,0 +1,80 @@
+getEntity($identifier, $normalized_entities);
+
+ if (!$entity) {
+ $visited['Missing'][$identifier] = TRUE;
+ continue;
+ }
+
+ // Process dependencies first.
+ if (!empty($entity['_content_sync']['entity_dependencies'])) {
+ foreach ($entity['_content_sync']['entity_dependencies'] as $references) {
+ $this->depthFirstSearch($visited, $references, $normalized_entities);
+ }
+ }
+
+ // Process translations' dependencies if any.
+ if (!empty($entity["_translations"])) {
+ foreach ($entity["_translations"] as $translation) {
+ if (!empty($translation['_content_sync']['entity_dependencies'])) {
+ foreach ($translation['_content_sync']['entity_dependencies'] as $references) {
+ $this->depthFirstSearch($visited, $references, $normalized_entities);
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Gets an entity.
+ *
+ * @param string $identifier
+ * An entity identifier to process.
+ * @param array $normalized_entities
+ * An array of entity identifiers to process.
+ *
+ * @return bool|array
+ * Array of entity data to manage or FALSE if no entity found (db error).
+ */
+ abstract protected function getEntity(string $identifier, array $normalized_entities) : array|false;
+
+ /**
+ * {@inheritDoc}
+ */
+ public function resolve(array $normalized_entities, array &$visited = []) : array {
+ $this->depthFirstSearch($visited, array_keys($normalized_entities), $normalized_entities);
+
+ return $visited;
+ }
+
+}
diff --git a/src/DependencyResolver/ContentSyncResolverInterface.php b/src/DependencyResolver/ContentSyncResolverInterface.php
index dbcf4b7..ded2af6 100755
--- a/src/DependencyResolver/ContentSyncResolverInterface.php
+++ b/src/DependencyResolver/ContentSyncResolverInterface.php
@@ -2,7 +2,22 @@
namespace Drupal\content_sync\DependencyResolver;
+/**
+ * Dependency resolver interface for content_sync.
+ */
interface ContentSyncResolverInterface {
- public function resolve(array $normalized_entities, $visited = []);
+ /**
+ * Resolve dependencies.
+ *
+ * @param array $entities
+ * The entities to resolve.
+ * @param array $visited
+ * Reference to array of visited entities.
+ *
+ * @return array
+ * The fully built-out array of entities to manage.
+ */
+ public function resolve(array $entities, array &$visited = []) : array;
+
}
diff --git a/src/DependencyResolver/ExportQueueResolver.php b/src/DependencyResolver/ExportQueueResolver.php
index d5d575f..895fbaa 100644
--- a/src/DependencyResolver/ExportQueueResolver.php
+++ b/src/DependencyResolver/ExportQueueResolver.php
@@ -2,102 +2,38 @@
namespace Drupal\content_sync\DependencyResolver;
-use Drupal\content_sync\Content\ContentDatabaseStorage;
+use Drupal\content_sync\Content\DatabaseStorageInterface;
+use Drupal\Core\DependencyInjection\AutowireTrait;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
/**
* Class ImportQueueResolver.
*
* @package Drupal\content_sync\DependencyResolver
*/
-class ExportQueueResolver implements ContentSyncResolverInterface {
+class ExportQueueResolver extends AbstractResolver {
+
+ use AutowireTrait;
/**
- * Builds a graph placing the deepest vertexes at the first place.
- *
- * @param array $visited
- * Array of vertexes to return.
- * @param array $identifiers
- * Array of entity identifiers to process.
- * @param array $normalized_entities
- * Parsed entities to import.
+ * Constructor.
*/
- protected function depthFirstSearch(array &$visited, array $identifiers, array $normalized_entities) {
- foreach ($identifiers as $identifier) {
- if (isset($visited[$identifier])) {
- // Already accounted for; skip.
- continue;
- }
- else {
- $visited[$identifier] = $identifier;
- }
-
- // Get a decoded entity.
- $entity = $this->getEntity($identifier, $normalized_entities);
-
- // Process dependencies first.
- if (!empty($entity['_content_sync']['entity_dependencies'])) {
- foreach ($entity['_content_sync']['entity_dependencies'] as $ref_entity_type_id => $references) {
- $this->depthFirstSearch($visited, $references, $normalized_entities);
- }
- }
-
- // Process translations' dependencies if any.
- if (!empty($entity["_translations"])) {
- foreach ($entity["_translations"] as $translation) {
- if (!empty($translation['_content_sync']['entity_dependencies'])) {
- foreach ($translation['_content_sync']['entity_dependencies'] as $ref_entity_type_id => $references) {
- $this->depthFirstSearch($visited, $references, $normalized_entities);
- }
- }
- }
- }
-
-
- }
- }
+ public function __construct(
+ #[Autowire(service: 'content.storage.active')]
+ protected DatabaseStorageInterface $storage,
+ ) {}
/**
- * Gets an entity.
- *
- * @param $identifier
- * An entity identifier to process.
- * @param $normalized_entities
- * An array of entity identifiers to process.
- *
- * @return bool|array
- * Array of entity data to export or FALSE if no entity found (db error).
+ * {@inheritDoc}
*/
- protected function getEntity($identifier, $normalized_entities) {
+ protected function getEntity(string $identifier, array $normalized_entities) : array|false {
if (!empty($normalized_entities[$identifier])) {
$entity = $normalized_entities[$identifier];
}
else {
- $activeStorage = new ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot');
- $entity = $activeStorage->cs_read($identifier);
+ $entity = $this->storage->contentSyncRead($identifier);
}
return $entity;
}
- /**
- * Creates a queue.
- *
- * @param array $normalized_entities
- * Parsed entities to import.
- * @param array $visited
- * Associative array mapping visited identifiers to a value... either the
- * identifier proper, or an array containing:
- * - entity_type: The type of entity; and,
- * - entity_uuid: The UUID of the entity.
- *
- * @return array
- * Queue to be processed within a batch process.
- */
- public function resolve(array $normalized_entities, $visited = []) {
- foreach ($normalized_entities as $identifier => $entity) {
- $this->depthFirstSearch($visited, [$identifier], $normalized_entities);
- }
-
- return $visited;
- }
-
}
diff --git a/src/DependencyResolver/ImportException.php b/src/DependencyResolver/ImportException.php
new file mode 100644
index 0000000..de494a9
--- /dev/null
+++ b/src/DependencyResolver/ImportException.php
@@ -0,0 +1,9 @@
+getEntity($identifier, $normalized_entities);
- } catch (\Exception $e) {
- $entity = FALSE;
- $visited['Missing'][$identifier][] = $e->getMessage();
- }
-
- // Process dependencies first.
- if (!empty($entity['_content_sync']['entity_dependencies'])) {
- foreach ($entity['_content_sync']['entity_dependencies'] as $ref_entity_type_id => $references) {
- $this->depthFirstSearch($visited, $references, $normalized_entities);
- }
- }
-
- // Process translations' dependencies if any.
- if (!empty($entity["_translations"])) {
- foreach ($entity["_translations"] as $translation) {
- if (!empty($translation['_content_sync']['entity_dependencies'])) {
- foreach ($translation['_content_sync']['entity_dependencies'] as $ref_entity_type_id => $references) {
- $this->depthFirstSearch($visited, $references, $normalized_entities);
- }
- }
- }
- }
+ use AutowireTrait;
- if (!isset($visited[$identifier]) && $entity) {
- list($entity_type_id, $bundle, $uuid) = explode('.', $identifier);
- $visited[$identifier] = [
- 'entity_type_id' => $entity_type_id,
- 'decoded_entity' => $entity,
- ];
- }
-
- }
- }
+ public function __construct(
+ #[Autowire(service: 'database')]
+ protected Connection $database,
+ ) {}
/**
* Gets an entity.
*
- * @param $identifier
+ * @param string $identifier
* An entity identifier to process.
- * @param $normalized_entities
+ * @param array $normalized_entities
* An array of entity identifiers to process.
*
* @return bool|mixed
- * Decoded entity or FALSE if an entity already exists and doesn't require to be imported.
+ * Decoded entity or FALSE if an entity already exists and doesn't require
+ * to be imported.
*
* @throws \Exception
*/
- protected function getEntity($identifier, $normalized_entities) {
+ protected function getEntity(string $identifier, array $normalized_entities) : array|false {
if (!empty($normalized_entities[$identifier])) {
$entity = $normalized_entities[$identifier];
}
else {
- list($entity_type_id, $bundle, $uuid) = explode('.', $identifier);
- $file_path = content_sync_get_content_directory(ContentSyncManagerInterface::DEFAULT_DIRECTORY)."/entities/".$entity_type_id."/".$bundle."/".$identifier.".yml";
+ [$entity_type_id, $bundle] = explode('.', $identifier);
+ $file_path = content_sync_get_content_directory(ContentSyncManagerInterface::DEFAULT_DIRECTORY) . "/entities/" . $entity_type_id . "/" . $bundle . "/" . $identifier . ".yml";
$raw_entity = file_get_contents($file_path);
// Problems to open the .yml file.
- if (!$raw_entity) throw new \Exception("Dependency {$identifier} is missing.");
+ if (!$raw_entity) {
+ return FALSE;
+ }
$entity = Yaml::decode($raw_entity);
}
@@ -93,34 +54,11 @@ protected function getEntity($identifier, $normalized_entities) {
}
/**
- * Checks if a dependency exists in the site.
- *
- * @param $identifier
- * An entity identifier to process.
- *
- * @return bool
+ * {@inheritDoc}
*/
- protected function entityExists($identifier) {
- return (bool) \Drupal::database()->queryRange('SELECT 1 FROM {cs_db_snapshot} WHERE name = :name', 0, 1, [
- ':name' => $identifier])->fetchField();
- }
-
- /**
- * Creates a queue.
- *
- * @param array $normalized_entities
- * Parsed entities to import.
- *
- * @return array
- * Queue to be processed within a batch process.
- */
- public function resolve(array $normalized_entities, $visited = []) {
- $visited = [];
- foreach ($normalized_entities as $identifier => $entity) {
- $this->depthFirstSearch($visited, [$identifier], $normalized_entities);
- }
+ public function resolve(array $normalized_entities, array &$visited = []) : array {
// Reverse the array to adjust it to an array_pop-driven iterator.
- return array_reverse($visited);
+ return array_reverse(parent::resolve($normalized_entities, $visited));
}
}
diff --git a/src/Drush/Commands/ContentSyncCommands.php b/src/Drush/Commands/ContentSyncCommands.php
index 0051b92..8dac25f 100644
--- a/src/Drush/Commands/ContentSyncCommands.php
+++ b/src/Drush/Commands/ContentSyncCommands.php
@@ -3,8 +3,9 @@
namespace Drupal\content_sync\Drush\Commands;
use Consolidation\AnnotatedCommand\Attributes\HookSelector;
-use Drupal\Component\DependencyInjection\ContainerInterface;
use Drupal\content_sync\Form\ContentExportForm;
+use Drush\Commands\AutowireTrait;
+use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Drupal\content_sync\ContentSyncManagerInterface;
@@ -22,12 +23,12 @@
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\Core\StringTranslation\TranslationInterface;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Drush\Exceptions\UserAbortException;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Filesystem\Path;
@@ -44,152 +45,70 @@
*/
class ContentSyncCommands extends DrushCommands {
+ use AutowireTrait;
use ContentExportTrait;
use ContentImportTrait;
use DependencySerializationTrait;
use StringTranslationTrait;
- protected $contentStorage;
-
- protected $contentStorageSync;
-
- protected $contentSyncManager;
-
- protected $entityTypeManager;
-
- protected $contentExporter;
-
- protected $lock;
-
- protected $configTyped;
-
- protected $moduleInstaller;
-
- protected $themeHandler;
-
- protected $stringTranslation;
-
- protected $moduleHandler;
-
- /**
- * The snapshot service.
- *
- * @var \Drupal\content_sync\Form\ContentExportForm
- */
- protected ContentExportForm $snapshot;
-
- /**
- * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
- */
- protected EventDispatcherInterface $eventDispatcher;
-
- /**
- * Gets the contentStorage.
- *
- * @return \Drupal\Core\Config\StorageInterface
- * The contentStorage.
- */
- public function getContentStorage() {
- return $this->contentStorage;
- }
-
/**
* Gets the contentStorageSync.
*
* @return \Drupal\Core\Config\StorageInterface
* The contentStorageSync.
*/
- public function getContentStorageSync() {
+ public function getContentStorageSync(): StorageInterface {
return $this->contentStorageSync;
}
/**
* {@inheritdoc}
*/
- protected function getEntityTypeManager() {
+ protected function getEntityTypeManager(): EntityTypeManagerInterface {
return $this->entityTypeManager;
}
/**
* {@inheritdoc}
*/
- protected function getContentExporter() {
- return $this->contentExporter;
+ protected function getContentExporter(): ContentExporterInterface {
+ return $this->contentSyncManager->getContentExporter();
}
/**
* {@inheritdoc}
*/
- protected function getExportLogger() {
- return $this->logger('content_sync');
+ protected function getExportLogger(): LoggerInterface {
+ // phpcs:ignore DrupalPractice.Objects.GlobalDrupal.GlobalDrupal
+ return $this->logger() ?? \Drupal::logger('content_sync');
}
/**
- * ContentSyncCommands constructor.
- *
- * @param \Drupal\Core\Config\StorageInterface $contentStorage
- * The contentStorage.
- * @param \Drupal\Core\Config\StorageInterface $contentStorageSync
- * The contentStorageSync.
- * @param \Drupal\content_sync\ContentSyncManagerInterface $contentSyncManager
- * The contentSyncManager.
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
- * The entityTypeManager.
- * @param \Drupal\content_sync\Exporter\ContentExporterInterface $content_exporter
- * The contentExporter.
- * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
- * The moduleHandler.
- * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
- * The eventDispatcher.
- * @param \Drupal\Core\Lock\LockBackendInterface $lock
- * The lock.
- * @param \Drupal\Core\Config\TypedConfigManagerInterface $configTyped
- * The configTyped.
- * @param \Drupal\Core\Extension\ModuleInstallerInterface $moduleInstaller
- * The moduleInstaller.
- * @param \Drupal\Core\Extension\ThemeHandlerInterface $themeHandler
- * The themeHandler.
- * @param \Drupal\Core\StringTranslation\TranslationInterface $stringTranslation
- * The stringTranslation.
- * @param \Drupal\content_sync\Form\ContentExportForm $snapshot
- * The snapshot service.
+ * Constructor.
*/
- public function __construct(StorageInterface $contentStorage, StorageInterface $contentStorageSync, ContentSyncManagerInterface $contentSyncManager, EntityTypeManagerInterface $entity_type_manager, ContentExporterInterface $content_exporter, ModuleHandlerInterface $moduleHandler, EventDispatcherInterface $eventDispatcher, LockBackendInterface $lock, TypedConfigManagerInterface $configTyped, ModuleInstallerInterface $moduleInstaller, ThemeHandlerInterface $themeHandler, TranslationInterface $stringTranslation, ContentExportForm $snapshot) {
+ public function __construct(
+ #[Autowire(service: 'content.storage')]
+ protected StorageInterface $contentStorage,
+ #[Autowire(service: 'content.storage.sync')]
+ protected StorageInterface $contentStorageSync,
+ #[Autowire(service: 'content_sync.manager')]
+ protected ContentSyncManagerInterface $contentSyncManager,
+ protected EntityTypeManagerInterface $entityTypeManager,
+ protected ModuleHandlerInterface $moduleHandler,
+ #[Autowire(service: 'event_dispatcher')]
+ protected EventDispatcherInterface $eventDispatcher,
+ #[Autowire(service: 'lock')]
+ protected LockBackendInterface $lock,
+ protected TypedConfigManagerInterface $configTyped,
+ protected ModuleInstallerInterface $moduleInstaller,
+ protected ThemeHandlerInterface $themeHandler,
+ #[Autowire(service: 'content_sync.snaphoshot')]
+ protected ContentExportForm $snapshot,
+ #[Autowire(service: 'logger.channel.content_sync')]
+ LoggerInterface $logger,
+ ) {
parent::__construct();
- $this->contentStorage = $contentStorage;
- $this->contentStorageSync = $contentStorageSync;
- $this->contentSyncManager = $contentSyncManager;
- $this->entityTypeManager = $entity_type_manager;
- $this->contentExporter = $content_exporter;
- $this->moduleHandler = $moduleHandler;
- $this->eventDispatcher = $eventDispatcher;
- $this->lock = $lock;
- $this->configTyped = $configTyped;
- $this->moduleInstaller = $moduleInstaller;
- $this->themeHandler = $themeHandler;
- $this->stringTranslation = $stringTranslation;
- $this->snapshot = $snapshot;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container) {
- return new static(
- $container->get('content.storage'),
- $container->get('content.storage.sync'),
- $container->get('content_sync.manager'),
- $container->get('entity_type.manager'),
- $container->get('content_sync.exporter'),
- $container->get('module_handler'),
- $container->get('event_dispatcher'),
- $container->get('lock'),
- $container->get('config.typed'),
- $container->get('module_installer'),
- $container->get('theme_handler'),
- $container->get('string_translation'),
- $container->get('content_sync.snaphoshot')
- );
+ $this->setLogger($logger);
}
/**
@@ -201,17 +120,20 @@ public static function create(ContainerInterface $container) {
*
* @hook interact @interact-content-label
*/
- public function interactContentLabel(InputInterface $input, ConsoleOutputInterface $output) {
+ public function interactContentLabel(InputInterface $input, ConsoleOutputInterface $output) : void {
+ // phpcs:ignore Drupal.NamingConventions.ValidGlobal.GlobalUnderScore
global $content_directories;
+
if (empty($input->getArgument('label'))) {
$keys = array_keys($content_directories);
$choices = array_combine($keys, $keys);
if (count($choices) >= 2) {
$label = $this->io()->choice('Choose a content_sync directory:', $choices);
- $input->setArgument('label', $label);
- } else {
+ }
+ else {
$label = ContentSyncManagerInterface::DEFAULT_DIRECTORY;
}
+ $input->setArgument('label', $label);
}
}
@@ -233,47 +155,54 @@ public function interactContentLabel(InputInterface $input, ConsoleOutputInterfa
* @usage drush content-sync-import.
* @aliases csi,content-sync-import
*/
- public function import($label = NULL, array $options = [
- 'entity-types' => '',
- 'uuids' => '',
- 'actions' => '',
- 'skiplist' => FALSE ]) {
+ public function import(
+ ?string $label = NULL,
+ array $options = [
+ 'entity-types' => '',
+ 'uuids' => '',
+ 'actions' => '',
+ 'skiplist' => FALSE,
+ ],
+ ) : void {
// Determine source directory.
$source_storage_dir = content_sync_get_content_directory($label);
// Prepare content storage for the import.
if ($label == ContentSyncManagerInterface::DEFAULT_DIRECTORY) {
$source_storage = $this->getContentStorageSync();
- } else {
+ }
+ else {
$source_storage = new FileStorage($source_storage_dir);
}
- //Generate comparer with filters.
+ // Generate comparer with filters.
$storage_comparer = new ContentStorageComparer($source_storage, $this->contentStorage);
$change_list = [];
$collections = $storage_comparer->getAllCollectionNames();
- if (!empty($options['entity-types'])){
+ if (!empty($options['entity-types'])) {
$entity_types = explode(',', $options['entity-types']);
$match_collections = [];
- foreach ($entity_types as $entity_type){
- $match_collections = $match_collections + preg_grep('/^'.$entity_type.'/', $collections);
+ foreach ($entity_types as $entity_type) {
+ $match_collections = $match_collections + preg_grep('/^' . $entity_type . '/', $collections);
}
$collections = $match_collections;
}
- foreach ($collections as $collection){
- if (!empty($options['uuids'])){
+ foreach ($collections as $collection) {
+ if (!empty($options['uuids'])) {
$storage_comparer->createChangelistbyCollectionAndNames($collection, $options['uuids']);
- }else{
+ }
+ else {
$storage_comparer->createChangelistbyCollection($collection);
}
- if (!empty($options['actions'])){
+ if (!empty($options['actions'])) {
$actions = explode(',', $options['actions']);
- foreach ($actions as $op){
- if (in_array($op, ['create','update','delete'])){
+ foreach ($actions as $op) {
+ if (in_array($op, ['create', 'update', 'delete'])) {
$change_list[$collection][$op] = $storage_comparer->getChangelist($op, $collection);
}
}
- }else{
+ }
+ else {
$change_list[$collection] = $storage_comparer->getChangelist(NULL, $collection);
}
$change_list = array_map('array_filter', $change_list);
@@ -282,32 +211,36 @@ public function import($label = NULL, array $options = [
unset($change_list['']);
// Display the change list.
- if (empty($options['skiplist'])){
- //Show differences
+ if (empty($options['skiplist'])) {
+ // Show differences.
$this->output()->writeln("Differences of the export directory to the active content:\n");
// Print a table with changes in color.
$table = self::contentChangesTable($change_list, $this->output());
$table->render();
- // Ask to continue
+ // Ask to continue.
if (!$this->io()->confirm(dt('Do you want to import?'))) {
throw new UserAbortException();
}
}
- //Process the Import Data
- $content_to_sync = [];
- $content_to_delete = [];
+ // Process the Import Data.
+ $contents_to_sync = [];
+ $contents_to_delete = [];
foreach ($change_list as $collection => $actions) {
if (!empty($actions['create'])) {
- $content_to_sync = array_merge($content_to_sync, $actions['create']);
+ $contents_to_sync[] = $actions['create'];
}
if (!empty($actions['update'])) {
- $content_to_sync = array_merge($content_to_sync, $actions['update']);
+ $contents_to_sync[] = $actions['update'];
}
if (!empty($actions['delete'])) {
- $content_to_delete = $actions['delete'];
+ $contents_to_delete[] = $actions['delete'];
}
}
- // Set the Import Batch
+
+ $content_to_sync = array_merge(...$contents_to_sync);
+ $content_to_delete = array_merge(...$contents_to_delete);
+
+ // Set the Import Batch.
if (!empty($content_to_sync) || !empty($content_to_delete)) {
$batch = $this->generateImportBatch(
$content_to_sync,
@@ -321,14 +254,12 @@ public function import($label = NULL, array $options = [
}
}
-
/**
* Export Drupal content to a directory.
*
* @param string|null $label
* A content directory label (i.e. a key in $content_directories array in
* settings.php).
- *
* @param array $options
* The command options.
*
@@ -340,52 +271,59 @@ public function import($label = NULL, array $options = [
* @option files A value none/base64/folder - default folder.
* @option include-dependencies export content dependencies.
* @option skiplist skip the change list before proceed with the export.
- * @usage drush content-sync-export.
- * @aliases cse,content-sync-export.
+ * @usage drush content-sync-export
+ * @aliases cse,content-sync-export
*/
- public function export($label = NULL, array $options = [
- 'entity-types' => '',
- 'uuids' => '',
- 'actions' => '',
- 'files' => 'folder',
- 'include-dependencies' => FALSE,
- 'skiplist' => FALSE ]) {
+ public function export(
+ ?string $label = NULL,
+ array $options = [
+ 'entity-types' => '',
+ 'uuids' => '',
+ 'actions' => '',
+ 'files' => 'folder',
+ 'include-dependencies' => FALSE,
+ 'skiplist' => FALSE,
+ ],
+ ) : void {
// Determine destination directory.
$destination_dir = Path::canonicalize(\content_sync_get_content_directory($label));
// Prepare content storage for the export.
if ($label == ContentSyncManagerInterface::DEFAULT_DIRECTORY) {
$target_storage = $this->getContentStorageSync();
- } else {
+ }
+ else {
$target_storage = new FileStorage($destination_dir);
}
- //Generate comparer with filters.
+ // Generate comparer with filters.
$storage_comparer = new ContentStorageComparer($this->contentStorage, $target_storage);
$change_list = [];
$collections = $storage_comparer->getAllCollectionNames();
- if (!empty($options['entity-types'])){
+ if (!empty($options['entity-types'])) {
$entity_types = explode(',', $options['entity-types']);
$match_collections = [];
- foreach ($entity_types as $entity_type){
- $match_collections = $match_collections + preg_grep('/^'.$entity_type.'/', $collections);
+ foreach ($entity_types as $entity_type) {
+ $match_collections = $match_collections + preg_grep('/^' . $entity_type . '/', $collections);
}
$collections = $match_collections;
}
- foreach ($collections as $collection){
- if (!empty($options['uuids'])){
+ foreach ($collections as $collection) {
+ if (!empty($options['uuids'])) {
$storage_comparer->createChangelistbyCollectionAndNames($collection, $options['uuids']);
- }else{
+ }
+ else {
$storage_comparer->createChangelistbyCollection($collection);
}
- if (!empty($options['actions'])){
+ if (!empty($options['actions'])) {
$actions = explode(',', $options['actions']);
- foreach ($actions as $op){
- if (in_array($op, ['create','update','delete'])){
+ foreach ($actions as $op) {
+ if (in_array($op, ['create', 'update', 'delete'])) {
$change_list[$collection][$op] = $storage_comparer->getChangelist($op, $collection);
}
}
- }else{
+ }
+ else {
$change_list[$collection] = $storage_comparer->getChangelist(NULL, $collection);
}
$storage_comparer->resetCollectionChangelist($collection);
@@ -395,21 +333,21 @@ public function export($label = NULL, array $options = [
unset($change_list['']);
// Display the change list.
- if (empty($options['skiplist'])){
- //Show differences
+ if (empty($options['skiplist'])) {
+ // Show differences.
$this->output()->writeln("Differences of the active content to the export directory:\n");
// Print a table with changes in color.
$table = self::contentChangesTable($change_list, $this->output());
$table->render();
- // Ask to continue
+ // Ask to continue.
if (!$this->io()->confirm(dt('Do you want to export?'))) {
throw new UserAbortException();
}
}
- //Process the Export.
+ // Process the Export.
foreach ($change_list as $collection => $changes) {
- //$storage_comparer->getTargetStorage($collection)->deleteAll();
+ // $storage_comparer->getTargetStorage($collection)->deleteAll();
foreach ($changes as $change => $contents) {
switch ($change) {
case 'delete':
@@ -417,6 +355,7 @@ public function export($label = NULL, array $options = [
$storage_comparer->getTargetStorage($collection)->delete($content);
}
break;
+
case 'update':
case 'create':
foreach ($contents as $content) {
@@ -429,9 +368,9 @@ public function export($label = NULL, array $options = [
unset($change_list);
unset($storage_comparer);
- // Files options
+ // Files options.
$include_files = self::processFilesOption($options);
- // Set the Export Batch
+ // Set the Export Batch.
if ($this->getExportQueue()->numberOfItems() > 0) {
$batch = $this->generateExportBatch([], [
'export_type' => 'folder',
@@ -457,45 +396,45 @@ public function export($label = NULL, array $options = [
* @return \Symfony\Component\Console\Helper\Table
* A Symfony table object.
*/
- public static function contentChangesTable(array $content_changes, OutputInterface $output, $use_color = TRUE) {
+ public static function contentChangesTable(array $content_changes, OutputInterface $output, bool $use_color = TRUE): Table {
$rows = [];
foreach ($content_changes as $collection => $changes) {
- if(is_array($changes)){
- foreach ($changes as $change => $contents) {
- switch ($change) {
- case 'delete':
- $colour = '';
- break;
-
- case 'update':
- $colour = '';
- break;
-
- case 'create':
- $colour = '';
- break;
-
- default:
- $colour = "";
- break;
- }
- if ($use_color) {
- $prefix = $colour;
- $suffix = '>';
- }
- else {
- $prefix = $suffix = '';
- }
- foreach ($contents as $content) {
- $rows[] = [
- $collection,
- $content,
- $prefix . ucfirst($change) . $suffix,
- ];
+ if (is_array($changes)) {
+ foreach ($changes as $change => $contents) {
+ switch ($change) {
+ case 'delete':
+ $colour = '';
+ break;
+
+ case 'update':
+ $colour = '';
+ break;
+
+ case 'create':
+ $colour = '';
+ break;
+
+ default:
+ $colour = "";
+ break;
+ }
+ if ($use_color) {
+ $prefix = $colour;
+ $suffix = '>';
+ }
+ else {
+ $prefix = $suffix = '';
+ }
+ foreach ($contents as $content) {
+ $rows[] = [
+ $collection,
+ $content,
+ $prefix . ucfirst($change) . $suffix,
+ ];
+ }
}
}
}
- }
$table = new Table($output);
$table->setHeaders(['Collection', 'Content Name', 'Operation']);
$table->addRows($rows);
@@ -507,12 +446,15 @@ public static function contentChangesTable(array $content_changes, OutputInterfa
*
* @param array $options
* The command options.
+ *
* @return string
* Processed 'files' option value.
*/
- public static function processFilesOption($options) {
+ public static function processFilesOption(array $options) : string {
$include_files = !empty($options['files']) ? $options['files'] : 'folder';
- if (!in_array($include_files, ['folder', 'base64'])) $include_files = 'none';
+ if (!in_array($include_files, ['folder', 'base64'])) {
+ $include_files = 'none';
+ }
return $include_files;
}
@@ -522,9 +464,16 @@ public static function processFilesOption($options) {
#[CLI\Command(name: 'content-sync:snapshot', aliases: ['cs:s'])]
#[HookSelector(name: 'islandora-drush-utils-user-wrap')]
public function buildSnapshot() : void {
- $this->logger()->notice('Building snapshot...');
+ $this->logger->notice('Building snapshot...');
$this->snapshot->snapshot();
- drush_backend_batch_process();
+ $items = $this->snapshot->getExportQueue()->numberOfItems();
+ $this->io()->info($this->formatPlural($items, 'Found 1 item to snapshot.', 'Found @count items to snapshot.'));
+ if ($items > 0) {
+ $this->io()->info('Starting batch...');
+ drush_backend_batch_process();
+ return;
+ }
+ $this->io()->info('Skipping batch.');
}
}
diff --git a/src/Element/ContentSyncMessage.php b/src/Element/ContentSyncMessage.php
index 6509192..3a6f67c 100644
--- a/src/Element/ContentSyncMessage.php
+++ b/src/Element/ContentSyncMessage.php
@@ -41,8 +41,7 @@ class ContentSyncMessage extends RenderElement {
/**
* {@inheritdoc}
*/
- public function getInfo() {
- $class = get_class($this);
+ public function getInfo() : array {
return [
'#message_type' => 'status',
'#message_message' => '',
@@ -52,7 +51,7 @@ public function getInfo() {
'#message_storage' => '',
'#status_headings' => [],
'#pre_render' => [
- [$class, 'preRenderContentSyncMessage'],
+ [static::class, 'preRenderContentSyncMessage'],
],
'#theme_wrappers' => ['content_sync_message'],
];
@@ -68,7 +67,7 @@ public function getInfo() {
* @return array
* The modified element with status message.
*/
- public static function preRenderContentSyncMessage(array $element) {
+ public static function preRenderContentSyncMessage(array $element) : array {
$message_type = $element['#message_type'];
$message_close = $element['#message_close'];
$message_close_effect = $element['#message_close_effect'];
@@ -148,10 +147,6 @@ public static function preRenderContentSyncMessage(array $element) {
return $element;
}
- /****************************************************************************/
- // Manage closed functions.
- /****************************************************************************/
-
/**
* Is message closed via User Data or State API.
*
@@ -163,7 +158,7 @@ public static function preRenderContentSyncMessage(array $element) {
* @return bool
* TRUE if the message is closed.
*/
- public static function isClosed($storage, $id) {
+ public static function isClosed(string $storage, string $id) : bool {
$account = \Drupal::currentUser();
$namespace = 'content_sync.element.message';
switch ($storage) {
@@ -171,13 +166,13 @@ public static function isClosed($storage, $id) {
/** @var \Drupal\Core\State\StateInterface $state */
$state = \Drupal::service('state');
$values = $state->get($namespace, []);
- return (isset($values[$id])) ? TRUE : FALSE;
+ return isset($values[$id]);
case self::STORAGE_USER:
/** @var \Drupal\user\UserDataInterface $user_data */
$user_data = \Drupal::service('user.data');
$values = $user_data->get('content_sync', $account->id(), $namespace) ?: [];
- return (isset($values[$id])) ? TRUE : FALSE;
+ return isset($values[$id]);
}
return FALSE;
@@ -193,7 +188,7 @@ public static function isClosed($storage, $id) {
*
* @see \Drupal\content_sync\Controller\ContentSyncElementController::close
*/
- public static function setClosed($storage, $id) {
+ public static function setClosed(string $storage, string $id) : void {
$account = \Drupal::currentUser();
$namespace = 'content_sync.element.message';
switch ($storage) {
@@ -224,7 +219,7 @@ public static function setClosed($storage, $id) {
*
* @see \Drupal\content_sync\Controller\ContentSyncElementController::close
*/
- public static function resetClosed($storage, $id) {
+ public static function resetClosed(string $storage, string $id) : void {
$account = \Drupal::currentUser();
$namespace = 'content_sync.element.message';
switch ($storage) {
diff --git a/src/Encoder/YamlEncoder.php b/src/Encoder/YamlEncoder.php
index 65351cc..69d7497 100644
--- a/src/Encoder/YamlEncoder.php
+++ b/src/Encoder/YamlEncoder.php
@@ -5,45 +5,52 @@
use Drupal\Component\Serialization\Yaml;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
-use Symfony\Component\Serializer\Encoder\scalar;
-
/**
- * Class YamlEncoder.
- *
- * @package Drupal\yaml_serialization
+ * YAML encoder.
*/
-class YamlEncoder implements EncoderInterface, DecoderInterface{
+class YamlEncoder implements EncoderInterface, DecoderInterface {
/**
* The formats that this Encoder supports.
*
* @var string
*/
- protected $format = 'yaml';
-
- protected $yaml;
+ protected string $format = 'yaml';
/**
* Constructor.
*/
- public function __construct(Yaml $yaml) {
- $this->yaml = $yaml;
- }
+ public function __construct(
+ protected Yaml $yaml,
+ ) {}
- public function decode($data, $format, array $context = array()) {
- return $this->yaml->decode($data);
+ /**
+ * {@inheritDoc}
+ */
+ public function decode($data, $format, array $context = []) {
+ return $this->yaml::decode($data);
}
- public function supportsDecoding($format) {
- return $format == $this->format;
+ /**
+ * {@inheritDoc}
+ */
+ public function supportsDecoding($format) : bool {
+ return $format === $this->format;
}
- public function encode($data, $format, array $context = array()) : string {
- return $this->yaml->encode($data);
+ /**
+ * {@inheritDoc}
+ */
+ public function encode($data, $format, array $context = []) : string {
+ return $this->yaml::encode($data);
}
+ /**
+ * {@inheritDoc}
+ */
public function supportsEncoding($format) : bool {
- return $format == $this->format;
+ return $format === $this->format;
}
+
}
diff --git a/src/EventSubscriber/ContentSyncEvents.php b/src/EventSubscriber/ContentSyncEvents.php
index 5e3ec42..93c4e88 100644
--- a/src/EventSubscriber/ContentSyncEvents.php
+++ b/src/EventSubscriber/ContentSyncEvents.php
@@ -2,27 +2,36 @@
namespace Drupal\content_sync\EventSubscriber;
+use Drupal\Core\DependencyInjection\AutowireTrait;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Entity\EntityTypeEvents;
-use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeEvent;
-
/**
* Create a content subscriber.
*/
class ContentSyncEvents implements EventSubscriberInterface {
+ use AutowireTrait;
+
+ /**
+ * Constructor.
+ */
+ public function __construct(
+ #[Autowire(service: 'logger.channel.content_sync')]
+ protected LoggerInterface $logger,
+ ) {}
+
/**
- * This method is called whenever the EntityTypeEvents::CREATE event is
- * dispatched.
+ * Event callback for EntityTypeEvents::CREATE events.
*
* @param \Drupal\Core\Entity\EntityTypeEvent $event
- * The Event to process.
+ * The event to process.
*/
- public function onContentSyncCreate(EntityTypeEvent $event) {
- kint($event);
- \Drupal::logger('content_sync')->notice("Create Event");
+ public function onContentSyncCreate(EntityTypeEvent $event) : void {
+ $this->logger->notice("Create Event", ['event' => $event]);
}
/**
@@ -31,7 +40,7 @@ public function onContentSyncCreate(EntityTypeEvent $event) {
* @return array
* An array of event listener definitions.
*/
- public static function getSubscribedEvents() {
+ public static function getSubscribedEvents() : array {
$events[EntityTypeEvents::CREATE][] = ['onContentSyncCreate', 40];
return $events;
}
diff --git a/src/Exporter/ContentExporter.php b/src/Exporter/ContentExporter.php
index 0fd92f3..0573bed 100755
--- a/src/Exporter/ContentExporter.php
+++ b/src/Exporter/ContentExporter.php
@@ -6,26 +6,36 @@
use Symfony\Component\Serializer\Serializer;
use Drupal\Component\Serialization\Yaml;
+/**
+ * Content exporter service.
+ */
class ContentExporter implements ContentExporterInterface {
- protected $format = 'yaml';
-
- protected $serializer;
+ /**
+ * Serializer format.
+ *
+ * @var string
+ */
+ protected string $format = 'yaml';
- protected $context = [];
+ /**
+ * Serializer context.
+ *
+ * @var array
+ */
+ protected array $context = [];
/**
* ContentExporter constructor.
*/
- public function __construct(Serializer $serializer) {
- $this->serializer = $serializer;
- }
-
+ public function __construct(
+ protected Serializer $serializer,
+ ) {}
/**
- * @inheritdoc
+ * {@inheritdoc}
*/
- public function exportEntity(ContentEntityInterface $entity, array $context = []) {
+ public function exportEntity(ContentEntityInterface $entity, array $context = []): string {
$context = $this->context + $context;
// Allows normalizers to know that this is a content sync generated entity.
$context += [
@@ -33,22 +43,16 @@ public function exportEntity(ContentEntityInterface $entity, array $context = []
];
$normalized_entity = $this->serializer->serialize($entity, $this->format, $context);
-// $return = [
-// 'entity_type_id' => $entity->getEntityTypeId(),
-// 'entity' => $this->serializer->encode($normalized_entity, $this->format, $context),
-// 'original_entity' => $entity,
-// ];
- // Include translations to the normalized entity
+ // Include translations to the normalized entity.
$yaml_parsed = Yaml::decode($normalized_entity);
$lang_default = $entity->language()->getId();
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
// Verify that it is not the default langcode.
- if ( $langcode != $lang_default ) {
- if ( $entity->hasTranslation($langcode) ) {
+ if ($langcode != $lang_default) {
+ if ($entity->hasTranslation($langcode)) {
$entity_translated = $entity->getTranslation($langcode);
$normalized_entity_translations = $this->serializer->serialize($entity_translated, $this->format, $context);
- //$normalized_data['_translations'][$c] = $contentExporter->exportEntity($object_translated, $serializer_context);
$yaml_parsed['_translations'][$langcode] = Yaml::decode($normalized_entity_translations);
}
}
@@ -57,16 +61,16 @@ public function exportEntity(ContentEntityInterface $entity, array $context = []
}
/**
- * @return string
+ * Format accessor.
*/
- public function getFormat() {
+ public function getFormat(): string {
return $this->format;
}
/**
- * @return \Symfony\Component\Serializer\Serializer
+ * Serializer accessor.
*/
- public function getSerializer() {
+ public function getSerializer(): Serializer {
return $this->serializer;
}
diff --git a/src/Exporter/ContentExporterInterface.php b/src/Exporter/ContentExporterInterface.php
index 0f9ca76..16a8195 100755
--- a/src/Exporter/ContentExporterInterface.php
+++ b/src/Exporter/ContentExporterInterface.php
@@ -2,18 +2,24 @@
namespace Drupal\content_sync\Exporter;
-
use Drupal\Core\Entity\ContentEntityInterface;
+/**
+ * Content sync exporter interface.
+ */
interface ContentExporterInterface {
/**
* Exports the given entity.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity to be exported.
* @param array $context
+ * Content regarding serialization.
*
- * @return array
+ * @return string
+ * YAML-encoded entity.
*/
- public function exportEntity(ContentEntityInterface $entity, array $context = []);
-}
\ No newline at end of file
+ public function exportEntity(ContentEntityInterface $entity, array $context = []) : string;
+
+}
diff --git a/src/Form/ContentExportForm.php b/src/Form/ContentExportForm.php
index de3adf7..03631e7 100755
--- a/src/Form/ContentExportForm.php
+++ b/src/Form/ContentExportForm.php
@@ -9,6 +9,7 @@
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\File\FileSystemInterface;
+use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -19,57 +20,38 @@ class ContentExportForm extends FormBase {
use ContentExportTrait;
/**
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ * Constructor.
*/
- protected $entityTypeManager;
+ public function __construct(
+ protected EntityTypeManagerInterface $entityTypeManager,
+ protected ContentExporterInterface $contentExporter,
+ protected ContentSyncManagerInterface $contentSyncManager,
+ protected FileSystemInterface $fileSystem,
+ ) {}
/**
- * @var \Drupal\content_sync\Exporter\ContentExporterInterface
+ * {@inheritDoc}
*/
- protected $contentExporter;
-
- /**
- * @var \Drupal\content_sync\ContentSyncManagerInterface
- */
- protected $contentSyncManager;
-
- /**
- * The filesystem service.
- *
- * @var \Drupal\Core\File\FileSystemInterface
- */
- protected FileSystemInterface $fileSystem;
-
- /**
- * ContentExportForm constructor.
- */
- public function __construct(EntityTypeManagerInterface $entity_type_manager, ContentExporterInterface $content_exporter, ContentSyncManagerInterface $content_sync_manager, FileSystemInterface $file_system) {
- $this->entityTypeManager = $entity_type_manager;
- $this->contentExporter = $content_exporter;
- $this->contentSyncManager = $content_sync_manager;
- $this->fileSystem = $file_system;
- }
-
- public static function create(ContainerInterface $container) {
+ public static function create(ContainerInterface $container) : static {
return new static(
$container->get('entity_type.manager'),
$container->get('content_sync.exporter'),
$container->get('content_sync.manager'),
- $container->get('file_system')
+ $container->get('file_system'),
);
}
/**
* {@inheritdoc}
*/
- public function getFormId() {
+ public function getFormId() : string {
return 'content_export_form';
}
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, FormStateInterface $form_state) {
+ public function buildForm(array $form, FormStateInterface $form_state) : array {
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Export'),
@@ -80,80 +62,82 @@ public function buildForm(array $form, FormStateInterface $form_state) {
/**
* {@inheritdoc}
*/
- public function submitForm(array &$form, FormStateInterface $form_state) {
+ public function submitForm(array &$form, FormStateInterface $form_state) : void {
// Delete the content tar file in case an older version exist.
$this->fileSystem->delete($this->getTempFile());
- //Set batch operations by entity type/bundle
- $entities_list = [];
- $entity_type_definitions = $this->entityTypeManager->getDefinitions();
- foreach ($entity_type_definitions as $entity_type => $definition) {
- $reflection = new \ReflectionClass($definition->getClass());
- if ($reflection->implementsInterface(ContentEntityInterface::class)) {
- $entities = $this->entityTypeManager->getStorage($entity_type)
- ->getQuery()
- ->accessCheck()
- ->execute();
- foreach ($entities as $entity_id) {
- $entities_list[] = [
- 'entity_type' => $entity_type,
- 'entity_id' => $entity_id,
- ];
- }
- }
+ // Set batch operations by entity type/bundle.
+ $serializer_context['export_type'] = 'tar';
+ $serializer_context['include_files'] = 'folder';
+ $batch = $this->generateExportBatch($this->generateEntities(), $serializer_context);
+
+ // Avoid kicking off batch if there were no items enqueued.
+ if ($this->getExportQueue()->numberOfItems() > 0) {
+ batch_set($batch);
}
- if (!empty($entities_list)) {
- $serializer_context['export_type'] = 'tar';
- $serializer_context['include_files'] = 'folder';
- $batch = $this->generateExportBatch($entities_list, $serializer_context);
+ }
+
+ /**
+ * Trigger snapshot batch.
+ */
+ public function snapshot() : void {
+ // Set batch operations by entity type/bundle.
+ $serializer_context['export_type'] = 'snapshot';
+ $batch = $this->generateExportBatch($this->generateEntities(FALSE), $serializer_context);
+
+ // Avoid kicking off batch if there were no items enqueued.
+ if ($this->getExportQueue()->numberOfItems() > 0) {
batch_set($batch);
}
}
- public function snapshot() {
- //Set batch operations by entity type/bundle
- $entities_list = [];
+ /**
+ * Helper; generate ALL content entities.
+ *
+ * @param bool $access_check
+ * TRUE (default) to perform access checking; FALSE to disable access
+ * checking.
+ *
+ * @return \Traversable
+ * ALL content entities.
+ */
+ protected function generateEntities(bool $access_check = TRUE) : \Traversable {
$entity_type_definitions = $this->entityTypeManager->getDefinitions();
foreach ($entity_type_definitions as $entity_type => $definition) {
- $reflection = new \ReflectionClass($definition->getClass());
- if ($reflection->implementsInterface(ContentEntityInterface::class)) {
- $entities = $this->entityTypeManager->getStorage($entity_type)
- ->getQuery()
- ->accessCheck()
- ->execute();
- foreach ($entities as $entity_id) {
- $entities_list[] = [
- 'entity_type' => $entity_type,
- 'entity_id' => $entity_id,
- ];
- }
+ if (!is_a($definition->getClass(), ContentEntityInterface::class, TRUE)) {
+ continue;
+ }
+ $entities = $this->entityTypeManager->getStorage($entity_type)
+ ->getQuery()
+ ->accessCheck($access_check)
+ ->execute();
+ foreach ($entities as $entity_id) {
+ yield [
+ 'entity_type' => $entity_type,
+ 'entity_id' => $entity_id,
+ ];
}
- }
- if (!empty($entities_list)) {
- $serializer_context['export_type'] = 'snapshot';
- $batch = $this->generateExportBatch($entities_list, $serializer_context);
- batch_set($batch);
}
}
/**
- * @{@inheritdoc}
+ * {@inheritdoc}
*/
- protected function getEntityTypeManager() {
+ protected function getEntityTypeManager(): EntityTypeManagerInterface {
return $this->entityTypeManager;
}
/**
- * @{@inheritdoc}
+ * {@inheritdoc}
*/
- protected function getContentExporter() {
+ protected function getContentExporter(): ContentExporterInterface {
return $this->contentExporter;
}
/**
- * @{@inheritdoc}
+ * {@inheritdoc}
*/
- protected function getExportLogger() {
+ protected function getExportLogger(): LoggerInterface {
return $this->logger('content_sync');
}
diff --git a/src/Form/ContentExportMultiple.php b/src/Form/ContentExportMultiple.php
index e25534b..0433424 100755
--- a/src/Form/ContentExportMultiple.php
+++ b/src/Form/ContentExportMultiple.php
@@ -3,80 +3,47 @@
namespace Drupal\content_sync\Form;
use Drupal\content_sync\ContentSyncManagerInterface;
+use Drupal\content_sync\Exporter\ContentExporterInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\File\FileSystemInterface;
+use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
- * Class ContentExportMultiple
- *
- * @package Drupal\content_sync_ui\Form
+ * Multi-export confirm form.
*/
class ContentExportMultiple extends ConfirmFormBase {
use ContentExportTrait;
/**
- * Entity type manager service.
+ * List on entities pulled from temp store in building and used in submit.
*
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
- */
- protected $entityTypeManager;
-
- /**
- * Private Temp Store Factory service.
- *
- * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
- */
- protected $tempStoreFactory;
-
- /**
- * @var \Drupal\content_sync\ContentSyncManagerInterface
- */
- protected $contentSyncManager;
-
- /**
- * @var \Drupal\content_sync_ui\Toolbox\ContentSyncUIToolboxInterface
- */
- protected $contentSyncUIToolbox;
-
- /**
* @var array
*/
- protected $entityList = [];
-
- protected $formats;
-
- /**
- * @var \Drupal\Core\File\FileSystemInterface
- */
- protected FileSystemInterface $fileSystem;
+ protected array $entityList = [];
/**
- * Constructs a ContentSyncMultiple form object.
- *
- * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
- * The tempstore factory.
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $manager
- * The entity type manager.
+ * Constructor.
*/
- public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $manager, ContentSyncManagerInterface $content_sync_manager, array $formats, FileSystemInterface $file_system) {
- $this->tempStoreFactory = $temp_store_factory;
- $this->entityTypeManager = $manager;
- $this->contentSyncManager = $content_sync_manager;
- $this->formats = $formats;
- $this->fileSystem = $file_system;
- }
+ public function __construct(
+ protected PrivateTempStoreFactory $tempStoreFactory,
+ protected EntityTypeManagerInterface $entityTypeManager,
+ protected ContentSyncManagerInterface $contentSyncManager,
+ protected array $formats,
+ protected FileSystemInterface $fileSystem,
+ ) {}
/**
* {@inheritdoc}
*/
- public static function create(ContainerInterface $container) {
+ public static function create(ContainerInterface $container) : static {
return new static(
$container->get('tempstore.private'),
$container->get('entity_type.manager'),
@@ -89,38 +56,37 @@ public static function create(ContainerInterface $container) {
/**
* {@inheritdoc}
*/
- public function getFormId() {
+ public function getFormId(): string {
return 'content_sync_export_multiple_confirm';
}
/**
* {@inheritdoc}
*/
- public function getQuestion() {
+ public function getQuestion() : TranslatableMarkup {
return $this->formatPlural(count($this->entityList), 'Are you sure you want to export this item?', 'Are you sure you want to export these items?');
}
/**
* {@inheritdoc}
*/
- public function getCancelUrl() {
- return new Url('system.admin_content');
+ public function getCancelUrl(): Url {
+ return Url::fromRoute('system.admin_content');
}
/**
* {@inheritdoc}
*/
- public function getConfirmText() {
- return t('Export');
+ public function getConfirmText(): TranslatableMarkup {
+ return $this->t('Export');
}
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, FormStateInterface $form_state) {
+ public function buildForm(array $form, FormStateInterface $form_state) : array|RedirectResponse {
$this->entityList = $this->tempStoreFactory->get('content_sync_ui_multiple_confirm')
- ->get($this->currentUser()
- ->id());
+ ->get($this->currentUser()->id());
if (empty($this->entityList)) {
return new RedirectResponse($this->getCancelUrl()
@@ -147,11 +113,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
return $form;
}
-
/**
* {@inheritdoc}
*/
- public function submitForm(array &$form, FormStateInterface $form_state) {
+ public function submitForm(array &$form, FormStateInterface $form_state) : void {
if ($form_state->getValue('confirm') && !empty($this->entityList)) {
// Delete the content tar file in case an older version exist.
@@ -165,7 +130,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
];
}
if (!empty($entities_list)) {
- $batch = $this->generateBatch($entities_list);
+ $batch = $this->generateExportBatch($entities_list);
batch_set($batch);
}
}
@@ -177,21 +142,21 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
/**
* {@inheritdoc}
*/
- protected function getEntityTypeManager() {
+ protected function getEntityTypeManager() : EntityTypeManagerInterface {
return $this->entityTypeManager;
}
/**
* {@inheritdoc}
*/
- protected function getContentExporter() {
+ protected function getContentExporter() : ContentExporterInterface {
return $this->contentSyncManager->getContentExporter();
}
/**
* {@inheritdoc}
*/
- protected function getExportLogger() {
+ protected function getExportLogger() : LoggerInterface {
return $this->logger('content_sync');
}
diff --git a/src/Form/ContentExportTrait.php b/src/Form/ContentExportTrait.php
index e89467a..e1a1cd1 100755
--- a/src/Form/ContentExportTrait.php
+++ b/src/Form/ContentExportTrait.php
@@ -2,19 +2,26 @@
namespace Drupal\content_sync\Form;
+use Drupal\content_sync\Content\DatabaseStorageInterface;
use Drupal\content_sync\ContentSyncManagerInterface;
+use Drupal\content_sync\Exporter\ContentExporterInterface;
use Drupal\Core\Archiver\ArchiveTar;
-use Drupal\content_sync\Content\ContentDatabaseStorage;
use Drupal\Core\Entity\ContentEntityType;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Messenger\MessengerTrait;
+use Drupal\Core\Queue\QueueInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Url;
+use Psr\Log\LoggerInterface;
/**
* Defines the content export form.
*/
trait ContentExportTrait {
+ use MessengerTrait;
+
/**
* Define the export queue prefix.
*
@@ -23,21 +30,23 @@ trait ContentExportTrait {
* @return string
* The queue prefix to use.
*/
- public static function getExportQueuePrefix() {
+ public static function getExportQueuePrefix() : string {
return 'content_sync_export';
}
/**
- * @var ArchiveTar
+ * Archiver service.
+ *
+ * @var \Drupal\Core\Archiver\ArchiveTar
*/
- protected $archiver;
+ protected ArchiveTar $archiver;
/**
* The queue in which to keep the items to export prior to processing.
*
* @var \Drupal\Core\Queue\QueueInterface
*/
- protected $exportQueue;
+ protected QueueInterface $exportQueue;
/**
* Lazy accessor for the export queue.
@@ -45,7 +54,7 @@ public static function getExportQueuePrefix() {
* @return \Drupal\Core\Queue\QueueInterface
* The export queue.
*/
- public function getExportQueue() {
+ public function getExportQueue() : QueueInterface {
if (!isset($this->exportQueue)) {
$uuid = \Drupal::service('uuid')->generate();
$this->exportQueue = \Drupal::queue(static::getExportQueuePrefix() . ":{$uuid}", TRUE);
@@ -54,46 +63,47 @@ public function getExportQueue() {
}
/**
- * @param $entities
- *
- * @param $serializer_context
- * export_type:
- * Tar -> YML to Tar file
- * Snapshot -> YML to content_sync table.
- * Directory -> YML to content_sync_directory_entities
- *
- * content_sync_directory:
- * path for the content sync directory.
- *
- * content_sync_directory_entities:
- * path for the content sync entities directory.
+ * Generate export batch.
*
- * content_sync_directory_files:
- * path to store media/files.
- *
- * content_sync_file_base_64:
- * Include file as a data in the YAML.
+ * @param iterable $entities
+ * The entities to be exported.
+ * @param array $serializer_context
+ * An associative array containing:
+ * - export_type: One of:
+ * - tar: YML to Tar file
+ * - snapshot: YML to content_sync table.
+ * - directory: YML to content_sync_directory_entities
+ * - content_sync_directory: path for the content sync directory.
+ * - content_sync_directory_entities: path for the content sync entity
+ * directory.
+ * - content_sync_directory_files: path to store media/files.
+ * - content_sync_file_base_64: Include file as a data in the YAML.
+ * Additionally, may contain:
+ * - include_files: One of:
+ * - folder: Will set content_sync_directory_files.
+ * - base64: Will set content_sync_file_base_64.
*
* @return array
+ * A batch definition ready to pass to batch_set().
*/
- public function generateExportBatch(array $entities = [], array $serializer_context = []) {
+ public function generateExportBatch(iterable $entities, array $serializer_context = []) : array {
if (!isset($serializer_context['content_sync_directory'])) {
$serializer_context['content_sync_directory'] = content_sync_get_content_directory(ContentSyncManagerInterface::DEFAULT_DIRECTORY);
}
$serializer_context['content_sync_directory_entities'] = $serializer_context['content_sync_directory'] . "/entities";
- if (isset($serializer_context['include_files'])){
- if ($serializer_context['include_files'] == 'folder'){
+ if (isset($serializer_context['include_files'])) {
+ if ($serializer_context['include_files'] === 'folder') {
$serializer_context['content_sync_directory_files'] = $serializer_context['content_sync_directory'] . "/files";
}
- if ($serializer_context['include_files'] == 'base64') {
+ if ($serializer_context['include_files'] === 'base64') {
$serializer_context['content_sync_file_base_64'] = TRUE;
}
unset($serializer_context['include_files']);
}
- //Set batch operations by entity type/bundle
+ // Set batch operations by entity type/bundle.
$operations = [];
- $operations[] = [[$this, 'generateSiteUUIDFile'], [$serializer_context]];
+ $operations[] = [[$this, 'generateSiteUuidFile'], [$serializer_context]];
foreach ($entities as $entity) {
$this->getExportQueue()->createItem($entity);
}
@@ -102,7 +112,7 @@ public function generateExportBatch(array $entities = [], array $serializer_cont
[$serializer_context],
];
- //Set Batch
+ // Set Batch.
$batch = [
'operations' => $operations,
'title' => $this->t('Exporting content'),
@@ -111,8 +121,8 @@ public function generateExportBatch(array $entities = [], array $serializer_cont
'error_message' => $this->t('Content export has encountered an error.'),
];
if (isset($serializer_context['export_type'])
- && $serializer_context['export_type'] == 'tar') {
- $batch['finished'] = [$this,'finishContentExportBatch'];
+ && $serializer_context['export_type'] === 'tar') {
+ $batch['finished'] = [$this, 'finishContentExportBatch'];
}
return $batch;
}
@@ -128,26 +138,27 @@ public function generateExportBatch(array $entities = [], array $serializer_cont
* - entity_type: The type of entity.
* - entity_uuid: The UUID of the identified entity.
*/
- protected static function exportSplitName($name) {
+ protected static function exportSplitName(string $name) : array {
[$entity_type, , $entity_uuid] = explode('.', $name);
return compact('entity_type', 'entity_uuid');
}
/**
- * Processes the content archive export batch
+ * Processes the content archive export batch.
*
* @param array $serializer_context
* The serializer context.
- * @param \DrushBatchContext|array $context
+ * @param array $context
* The batch context.
*/
- public function processContentExportFiles(array $serializer_context = [], &$context = []) {
- //Initialize Batch
- if (!isset($context['sandbox']['progress'])) {
- $context['sandbox']['progress'] = 0;
- $context['sandbox']['max'] = $this->exportQueue->numberOfItems();
- $context['sandbox']['dependencies'] = [];
- $context['sandbox']['exported'] = [];
+ public function processContentExportFiles(array $serializer_context = [], array &$context = []) : void {
+ $sandbox =& $context['sandbox'];
+ // Initialize Batch.
+ if (!isset($sandbox['progress'])) {
+ $sandbox['progress'] = 0;
+ $sandbox['max'] = $this->exportQueue->numberOfItems();
+ $sandbox['dependencies'] = [];
+ $sandbox['exported'] = [];
}
$queue_item = $this->exportQueue->claimItem();
@@ -162,107 +173,112 @@ public function processContentExportFiles(array $serializer_context = [], &$cont
$item = static::exportSplitName($item);
}
- // Get submitted values
+ // Get submitted values.
$entity_type = $item['entity_type'];
- //Validate that it is a Content Entity
+ // Validate that it is a Content Entity.
$instances = $this->getEntityTypeManager()->getDefinitions();
if (!(isset($instances[$entity_type]) && $instances[$entity_type] instanceof ContentEntityType)) {
$context['results']['errors'][] = $this->t('Entity type does not exist or it is not a content instance.') . $entity_type;
}
else {
- if (isset($item['entity_uuid'])){
+ $storage = $this->getEntityTypeManager()->getStorage($entity_type);
+ if (isset($item['entity_uuid'])) {
$entity_id = $item['entity_uuid'];
- $entity = $this->getEntityTypeManager()->getStorage($entity_type)
- ->loadByProperties(['uuid' => $entity_id]);
+ $entity = $storage->loadByProperties(['uuid' => $entity_id]);
$entity = array_shift($entity);
- }else{
+ }
+ else {
$entity_id = $item['entity_id'];
- $entity = $this->getEntityTypeManager()->getStorage($entity_type)
- ->load($entity_id);
+ $entity = $storage->load($entity_id);
}
- //Make sure the entity exist for import
- if(empty($entity)){
- $context['results']['errors'][] = $this->t('Entity does not exist:') . $entity_type . "(".$entity_id.")";
- }else{
+ // Make sure the entity exist for import.
+ if (empty($entity)) {
+ $context['results']['errors'][] = $this->t('Entity does not exist: @type (@id)', [
+ '@type' => $entity_type,
+ '@id' => $entity_id,
+ ]);
+ }
+ else {
- // Create the name
+ // Create the name.
$bundle = $entity->bundle();
$uuid = $entity->uuid();
- $name = $entity_type . "." . $bundle . "." . $uuid;
+ $name = $entity_type . "." . $bundle . "." . $uuid;
- if (!isset($context['sandbox']['exported'][$name])) {
+ if (!isset($sandbox['exported'][$name])) {
// Generate the YAML file.
$exported_entity = $this->getContentExporter()
- ->exportEntity($entity, $serializer_context);
-
- if (isset($serializer_context['export_type'])){
- if ($serializer_context['export_type'] == 'snapshot') {
- //Save to cs_db_snapshot table.
- $activeStorage = new ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot');
- $activeStorage->cs_write($name, Yaml::decode($exported_entity), $entity_type.'.'.$bundle);
- }else{
+ ->exportEntity($entity, $serializer_context);
+
+ if (isset($serializer_context['export_type'])) {
+ if ($serializer_context['export_type'] === 'snapshot') {
+ // Save to cs_db_snapshot table.
+ $activeStorage = $this->getSnapshotStorage();
+ $activeStorage->contentSyncWrite($name, Yaml::decode($exported_entity), $entity_type . '.' . $bundle);
+ }
+ else {
// Compate the YAML from the snapshot.
// If for some reason is not on our snapshoot then add it.
// Or if the new YAML is different the update it.
- $activeStorage = new ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot');
- $exported_entity_snapshoot = $activeStorage->cs_read($name);
+ $activeStorage = $this->getSnapshotStorage();
+ $exported_entity_snapshoot = $activeStorage->contentSyncRead($name);
- if (!$exported_entity_snapshoot || Yaml::encode($exported_entity_snapshoot) !== $exported_entity ){
- //Save to cs_db_snapshot table.
- $activeStorage->cs_write($name, Yaml::decode($exported_entity), $entity_type.'.'.$bundle);
+ if (!$exported_entity_snapshoot || Yaml::encode($exported_entity_snapshoot) !== $exported_entity) {
+ // Save to cs_db_snapshot table.
+ $activeStorage->contentSyncWrite($name, Yaml::decode($exported_entity), $entity_type . '.' . $bundle);
}
- if ($serializer_context['export_type'] == 'tar') {
+ if ($serializer_context['export_type'] === 'tar') {
// YAML in Archive .
$this->getArchiver()->addString("entities/$entity_type/$bundle/$name.yml", $exported_entity);
// Include Files to the archiver.
if (method_exists($entity, 'getFileUri')
- && !empty($serializer_context['content_sync_directory_files']) ) {
+ && !empty($serializer_context['content_sync_directory_files'])) {
$uri = $entity->getFileUri();
$scheme = \Drupal::service('stream_wrapper_manager')->getScheme($uri);
$destination = "{$serializer_context['content_sync_directory_files']}/{$scheme}/";
$destination = str_replace($scheme . '://', $destination, $uri);
- $strip_path = str_replace('/files' , '', $serializer_context['content_sync_directory_files'] );
+ $strip_path = str_replace('/files', '', $serializer_context['content_sync_directory_files']);
$this->getArchiver()->addModify([$destination], '', $strip_path);
}
}
- if( $serializer_context['export_type'] == 'folder') {
+ if ($serializer_context['export_type'] === 'folder') {
// YAML in a directory.
- $path = $serializer_context['content_sync_directory_entities']."/$entity_type/$bundle";
+ $path = $serializer_context['content_sync_directory_entities'] . "/$entity_type/$bundle";
$destination = $path . "/$name.yml";
- \Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
- $file = \Drupal::service('file_system')->saveData($exported_entity, $destination, FileSystemInterface::EXISTS_REPLACE);
+ $this->getFileSystem()->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
+ $this->getFileSystem()->saveData($exported_entity, $destination, FileSystemInterface::EXISTS_REPLACE);
}
// Invalidate the CS Cache of the entity.
- $cache = \Drupal::cache('content')->invalidate($entity_type.".".$bundle.":".$name);
+ \Drupal::cache('content')->invalidate($entity_type . "." . $bundle . ":" . $name);
if ($serializer_context['include_dependencies']) {
- //Include Dependencies
- if (!isset($context['sandbox']['dependencies'][$name])) {
+ // Include Dependencies.
+ if (!isset($sandbox['dependencies'][$name])) {
$exported_entity = Yaml::decode($exported_entity);
- $queue = $this->contentSyncManager->generateExportQueue([$name => $exported_entity], $context['sandbox']['exported']);
- $new_deps = array_diff_key($queue, $context['sandbox']['dependencies']);
- $context['sandbox']['dependencies'] += $new_deps;
+ $queue = $this->contentSyncManager->generateExportQueue([$name => $exported_entity], $sandbox['exported']);
+ $new_deps = array_diff_key($queue, $sandbox['dependencies']);
+ $sandbox['dependencies'] += $new_deps;
unset($new_deps[$name]);
if (!empty($new_deps)) {
// Update the batch queue.
array_map([$this->exportQueue, 'createItem'], $new_deps);
- $context['sandbox']['max'] = $context['sandbox']['max'] + count($new_deps);
+ $sandbox['max'] = $sandbox['max'] + count($new_deps);
}
}
}
- $context['sandbox']['exported'][$name] = $name;
+ $sandbox['exported'][$name] = $name;
$context['results'][] = $name;
// Increment the progress for the message.
- $message_progress = $context['sandbox']['progress'] + 1;
- $context['message'] = "$name {$message_progress}/{$context['sandbox']['max']}";
+ $message_progress = $sandbox['progress'] + 1;
+ $context['message'] = "$name {$message_progress}/{$sandbox['max']}";
}
}
@@ -272,42 +288,43 @@ public function processContentExportFiles(array $serializer_context = [], &$cont
$this->exportQueue->deleteItem($queue_item);
- $context['sandbox']['progress']++;
- $context['finished'] = $context['sandbox']['max'] > 0
- && $context['sandbox']['progress'] < $context['sandbox']['max'] ?
- $context['sandbox']['progress'] / $context['sandbox']['max'] : 1;
+ $sandbox['progress']++;
+ $context['finished'] = $sandbox['max'] > 0
+ && $sandbox['progress'] < $sandbox['max'] ?
+ $sandbox['progress'] / $sandbox['max'] : 1;
}
/**
- * Generate UUID YAML file
- * To use for site UUID validation.
+ * Generate UUID YAML file to use for site UUID validation.
*
* @param array $serializer_context
* The batch content to persist.
- * @param \DrushBatchContext|array $context
+ * @param array $context
* The batch context.
*/
- public function generateSiteUUIDFile(array $serializer_context, &$context) {
- //Include Site UUID to YML file
+ public function generateSiteUuidFile(array $serializer_context, array &$context) : void {
+ // Include Site UUID to YML file.
$site_config = \Drupal::config('system.site');
$site_uuid_source = $site_config->get('uuid');
$entity['site_uuid'] = $site_uuid_source;
- // Set the name
+ // Set the name.
$name = "site.uuid";
- if (isset($serializer_context['export_type'])){
- if ($serializer_context['export_type'] == 'snapshot') {
- //Save to cs_db_snapshot table.
- $activeStorage = new ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot');
+ if (isset($serializer_context['export_type'])) {
+ if ($serializer_context['export_type'] === 'snapshot') {
+ // Save to cs_db_snapshot table.
+ $activeStorage = $this->getSnapshotStorage();
$activeStorage->write($name, $entity);
- }elseif( $serializer_context['export_type'] == 'tar') {
- // Add YAML to the archiver
+ }
+ elseif ($serializer_context['export_type'] === 'tar') {
+ // Add YAML to the archiver.
$this->getArchiver()->addString("entities/$name.yml", Yaml::encode($entity));
- }elseif( $serializer_context['export_type'] == 'folder') {
+ }
+ elseif ($serializer_context['export_type'] === 'folder') {
$path = $serializer_context['content_sync_directory_entities'];
$destination = $path . "/$name.yml";
- \Drupal::service('file_system')->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
- $file = \Drupal::service('file_system')->saveData(Yaml::encode($entity), $destination, FileSystemInterface::EXISTS_REPLACE);
+ $this->getFileSystem()->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
+ $this->getFileSystem()->saveData(Yaml::encode($entity), $destination, FileSystemInterface::EXISTS_REPLACE);
}
}
$context['message'] = $name;
@@ -320,44 +337,43 @@ public function generateSiteUUIDFile(array $serializer_context, &$context) {
*
* Provide information about the Content Batch results.
*/
- public function finishContentExportBatch($success, $results, $operations) {
+ public function finishContentExportBatch($success, $results, $operations) {
if ($success) {
- if (isset($results['errors'])){
+ if (isset($results['errors'])) {
$errors = $results['errors'];
unset($results['errors']);
}
$results = array_unique($results);
- // Log all the items processed
+ // Log all the items processed.
foreach ($results as $key => $result) {
- if ($key != 'errors') {
- //drupal_set_message(t('Processed UUID @title.', array('@title' => $result)));
+ if ($key !== 'errors') {
$this->getExportLogger()
- ->info('Processed UUID @title.', [
- '@title' => $result,
- 'link' => 'Export',
- ]);
+ ->info('Processed UUID @title.', [
+ '@title' => $result,
+ 'link' => 'Export',
+ ]);
}
}
if (isset($errors) && !empty($errors)) {
- // Log the errors
+ // Log the errors.
$errors = array_unique($errors);
foreach ($errors as $error) {
- //drupal_set_message($error, 'error');
+ // drupal_set_message($error, 'error');.
$this->getExportLogger()->error($error);
}
// Log the note that the content was exported with errors.
- \Drupal::messenger()->addWarning($this->t('The content was exported with errors. Logs', [':content-overview' => Url::fromRoute('content.overview')->toString()]));
+ $this->messenger()->addWarning($this->t('The content was exported with errors. Logs', [':content-overview' => Url::fromRoute('content.overview')->toString()]));
$this->getExportLogger()
- ->warning('The content was exported with errors.', ['link' => 'Export']);
+ ->warning('The content was exported with errors.', ['link' => 'Export']);
}
else {
// Log the new created export link if applicable.
- \Drupal::messenger()->addStatus($this->t('The content was exported successfully. Download tar file', [':export-download' => Url::fromRoute('content.export_download')->toString()]));
+ $this->messenger()->addStatus($this->t('The content was exported successfully. Download tar file', [':export-download' => Url::fromRoute('content.export_download')->toString()]));
$this->getExportLogger()
- ->info('The content was exported successfully. Download tar file', [
- ':export-download' => Url::fromRoute('content.export_download')->toString(),
- 'link' => 'Export',
- ]);
+ ->info('The content was exported successfully. Download tar file', [
+ ':export-download' => Url::fromRoute('content.export_download')->toString(),
+ 'link' => 'Export',
+ ]);
}
}
else {
@@ -365,34 +381,65 @@ public function finishContentExportBatch($success, $results, $operations) {
$message = $this->t('Finished with an error.Logs', [':content-overview' => Url::fromRoute('content.overview')->toString()]);
\Drupal::messenger()->addStatus($message);
$this->getExportLogger()
- ->error('Finished with an error.', ['link' => 'Export']);
+ ->error('Finished with an error.', ['link' => 'Export']);
}
}
- protected function getArchiver() {
+ /**
+ * Lazy archiver initializer.
+ */
+ protected function getArchiver() : ArchiveTar {
if (!isset($this->archiver)) {
$this->archiver = new ArchiveTar($this->getTempFile(), 'gz');
}
return $this->archiver;
}
- protected function getTempFile() {
- return \Drupal::service('file_system')->getTempDirectory() . '/content.tar.gz';
+ /**
+ * Temp file initializer.
+ */
+ protected function getTempFile() : string {
+ return $this->getFileSystem()->getTempDirectory() . '/content.tar.gz';
}
/**
- * @return \Drupal\Core\Entity\EntityTypeManagerInterface
+ * Get the entity type manager.
+ */
+ abstract protected function getEntityTypeManager() : EntityTypeManagerInterface;
+
+ /**
+ * Get the content exporter.
+ */
+ abstract protected function getContentExporter() : ContentExporterInterface;
+
+ /**
+ * Get a logger.
+ */
+ abstract protected function getExportLogger() : LoggerInterface;
+
+ /**
+ * Get Drupal's file system service.
*/
- abstract protected function getEntityTypeManager();
+ protected function getFileSystem() : FileSystemInterface {
+ return \Drupal::service('file_system');
+ }
/**
- * @return \Drupal\content_sync\Exporter\ContentExporterInterface
+ * Memoized/lazy-loaded snapshot storage.
+ *
+ * @var \Drupal\content_sync\Content\DatabaseStorageInterface
*/
- abstract protected function getContentExporter();
+ private DatabaseStorageInterface $storage;
/**
- * @return \Psr\Log\LoggerInterface
+ * Snapshot storage accessor.
+ *
+ * @return \Drupal\content_sync\Content\DatabaseStorageInterface
+ * The snapshot storage service.
*/
- abstract protected function getExportLogger();
+ protected function getSnapshotStorage() : DatabaseStorageInterface {
+ $this->storage ??= \Drupal::service('content.storage.active');
+ return $this->storage;
+ }
}
diff --git a/src/Form/ContentImportForm.php b/src/Form/ContentImportForm.php
index 5c32efc..54aac39 100644
--- a/src/Form/ContentImportForm.php
+++ b/src/Form/ContentImportForm.php
@@ -11,21 +11,25 @@
* Defines the content import form.
*/
class ContentImportForm extends FormBase {
+
/**
* {@inheritdoc}
*/
- public function getFormId() {
+ public function getFormId() : string {
return 'content_import_form';
}
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, FormStateInterface $form_state) {
+ public function buildForm(array $form, FormStateInterface $form_state) : array {
$directory = content_sync_get_content_directory(ContentSyncManagerInterface::DEFAULT_DIRECTORY);
$directory_is_writable = is_writable($directory);
if (!$directory_is_writable) {
- $this->logger('content_sync')->error('The directory %directory is not writable.', ['%directory' => $directory, 'link' => 'Import Archive']);
+ $this->logger('content_sync')->error('The directory %directory is not writable.', [
+ '%directory' => $directory,
+ 'link' => 'Import Archive',
+ ]);
$this->messenger()->addError($this->t('The directory %directory is not writable.', ['%directory' => $directory]));
}
@@ -64,7 +68,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($path = $form_state->getValue('import_tarball')) {
$directory = content_sync_get_content_directory(ContentSyncManagerInterface::DEFAULT_DIRECTORY);
- emptyDirectory($directory);
+ static::emptyDirectory($directory);
try {
$archiver = new ArchiveTar($path, 'gz');
$files = [];
@@ -78,33 +82,53 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
}
catch (\Exception $e) {
$this->messenger()->addError($this->t('Could not extract the contents of the tar file. The error message is @message', ['@message' => $e->getMessage()]));
- $this->logger('content_sync')->error('Could not extract the contents of the tar file. The error message is @message', ['@message' => $e->getMessage(), 'link' => 'Import Archive']);
+ $this->logger('content_sync')->error('Could not extract the contents of the tar file. The error message is @message', [
+ '@message' => $e->getMessage(),
+ 'link' => 'Import Archive',
+ ]);
}
drupal_flush_all_caches();
unlink($path);
}
}
-}
-/*
- * Help to empty a directory
- */
-function emptyDirectory($dirname,$self_delete=false) {
- if (is_dir($dirname))
+ /**
+ * Help to empty a directory.
+ */
+ private static function emptyDirectory(string $dirname, bool $self_delete = FALSE) : bool {
+ if (is_dir($dirname)) {
$dir_handle = opendir($dirname);
- if (!$dir_handle)
- return false;
- while($file = readdir($dir_handle)) {
- if ($file != "." && $file != "..") {
- if (!is_dir($dirname."/".$file))
- @unlink($dirname."/".$file);
- else
- emptyDirectory($dirname.'/'.$file,true);
+ }
+ else {
+ // The passed name is not a directory?
+ return FALSE;
+ }
+
+ if (!$dir_handle) {
+ // We failed to open the given directory; missing permissions?
+ return FALSE;
+ }
+
+ try {
+ while ($file = readdir($dir_handle)) {
+ if ($file !== "." && $file !== "..") {
+ if (!is_dir($dirname . "/" . $file)) {
+ @unlink($dirname . "/" . $file);
+ }
+ elseif (!static::emptyDirectory($dirname . '/' . $file, TRUE)) {
+ // Failed to delete something inside...
+ return FALSE;
+ }
+ }
}
- }
- closedir($dir_handle);
- if ($self_delete){
+ if ($self_delete) {
@rmdir($dirname);
- }
- return true;
+ }
+ return TRUE;
+ }
+ finally {
+ closedir($dir_handle);
+ }
+ }
+
}
diff --git a/src/Form/ContentImportTrait.php b/src/Form/ContentImportTrait.php
index 99eb56d..1aa59ae 100644
--- a/src/Form/ContentImportTrait.php
+++ b/src/Form/ContentImportTrait.php
@@ -3,15 +3,10 @@
namespace Drupal\content_sync\Form;
use Drupal\content_sync\ContentSyncManagerInterface;
-use Drupal\Core\Entity\EntityStorageException;
-use Drupal\Core\Config\ConfigManagerInterface;
+use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\StorageInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Config\StorageComparer;
+use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Queue\QueueInterface;
-use Drupal\Core\Url;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Drupal\Core\Entity\ContentEntityType;
/**
* Defines the content import form.
@@ -52,27 +47,31 @@ public static function getDeleteQueuePrefix() {
* @return string
* The queue prefix to use.
*/
- public static function getSyncQueuePrefix() {
+ public static function getSyncQueuePrefix() : string {
return 'content_sync_sync';
}
/**
- * @param $content_to_sync
+ * Generate import batch.
*
- * @param $content_to_delete
- *
- * @param $serializer_context
- * content_sync_directory
- * The content sync directory from which to import.
+ * @param array $content_to_sync
+ * Array of content to sync.
+ * @param array $content_to_delete
+ * Array of content to delete.
+ * @param array $serializer_context
+ * An associative array containing:
+ * - content_sync_directory: The content sync directory from which to
+ * import.
*
* @return array
+ * Batch definition.
*/
- public function generateImportBatch($content_to_sync, $content_to_delete, $serializer_context = []) {
+ public function generateImportBatch(array $content_to_sync, array $content_to_delete, array $serializer_context = []) : array {
if (!isset($serializer_context['content_sync_directory'])) {
$serializer_context['content_sync_directory'] = content_sync_get_content_directory(ContentSyncManagerInterface::DEFAULT_DIRECTORY);
}
- $serializer_context['content_sync_directory_entities'] = $serializer_context['content_sync_directory'] . "/entities";
- $serializer_context['content_sync_directory_files'] = $serializer_context['content_sync_directory'] . "/files";
+ $serializer_context['content_sync_directory_entities'] = $serializer_context['content_sync_directory'] . "/entities";
+ $serializer_context['content_sync_directory_files'] = $serializer_context['content_sync_directory'] . "/files";
$uuid = \Drupal::service('uuid')->generate();
@@ -81,10 +80,10 @@ public function generateImportBatch($content_to_sync, $content_to_delete, $seria
$this->queueSync = \Drupal::queue(static::getSyncQueuePrefix() . ":{$uuid}", TRUE);
array_map(
[$this->queueSync, 'createItem'],
- array_reverse($this->contentSyncManager->generateImportQueue(
+ array_keys(array_reverse($this->contentSyncManager->generateImportQueue(
$content_to_sync,
$serializer_context['content_sync_directory_entities']
- ))
+ )))
);
$operations[] = [[$this, 'deleteContent'], [$serializer_context]];
@@ -94,19 +93,42 @@ public function generateImportBatch($content_to_sync, $content_to_delete, $seria
'title' => $this->t('Synchronizing Content...'),
'message' => $this->t('Synchronizing Content...'),
'operations' => $operations,
- //'finished' => [$this, 'finishImportBatch'],
];
return $batch;
}
/**
- * Processes the content import to be updated or created batch and persists the importer.
+ * Memoized storage(s).
+ *
+ * @var \Drupal\Core\Config\StorageInterface[]
+ */
+ protected array $storages = [];
+
+ /**
+ * Get storage for the given directory.
+ *
+ * @param string $dir
+ * The directory for which to obtain a file storage.
+ *
+ * @return \Drupal\Core\Config\StorageInterface
+ * A file storage instance for the given directory.
+ */
+ protected function getStorage(string $dir) : StorageInterface {
+ return $this->storages[$dir] ??= new FileStorage($dir);
+ }
+
+ /**
+ * Batch operation callback; process items from sync queue accordingly.
+ *
+ * Processes the content import to be updated or created batch and persists
+ * the importer.
*
* @param array $serializer_context
- * @param \DrushBatchContext|array $context
- * The batch context.
+ * Serializer context.
+ * @param array $context
+ * Reference to batch context.
*/
- public function syncContent(array $serializer_context = [], &$context = []) {
+ public function syncContent(array $serializer_context = [], array &$context = []) : void {
if (empty($context['sandbox'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['directory'] = $serializer_context['content_sync_directory_entities'];
@@ -117,12 +139,17 @@ public function syncContent(array $serializer_context = [], &$context = []) {
if ($queue_item) {
$error = FALSE;
- $item = $queue_item->data;
- $decoded_entity = $item['decoded_entity'];
- $entity_type_id = $item['entity_type_id'];
+ $item_id = $queue_item->data;
+
+ [$entity_type_id, $bundle] = explode('.', $item_id);
+ $decoded_entity = $this->getStorage($context['sandbox']['directory'])->createCollection("{$entity_type_id}.{$bundle}")->read($item_id);
+ if (!$decoded_entity) {
+ return;
+ }
+
$entity = $this->contentSyncManager->getContentImporter()
- ->importEntity($decoded_entity, $serializer_context);
- if($entity) {
+ ->importEntity($decoded_entity, $serializer_context);
+ if ($entity) {
$context['results'][] = TRUE;
$context['message'] = $this->t('Imported content @label (@entity_type: @id).', [
'@label' => $entity->label(),
@@ -132,8 +159,8 @@ public function syncContent(array $serializer_context = [], &$context = []) {
// Invalidate the CS Cache of the entity.
$bundle = $entity->bundle();
$entity_id = $entity->getEntityTypeId();
- $name = $entity_id . "." . $bundle . "." . $entity->uuid();
- $cache = \Drupal::cache('content')->invalidate($entity_id.".".$bundle.":".$name);
+ $name = $entity_id . '.' . $bundle . '.' . $entity->uuid();
+ \Drupal::cache('content')->invalidate($entity_id . '.' . $bundle . ':' . $name);
unset($entity);
}
else {
@@ -161,13 +188,17 @@ public function syncContent(array $serializer_context = [], &$context = []) {
}
/**
- * Processes the content import to be deleted or created batch and persists the importer.
+ * Batch operation callback; process items from delete queue accordingly.
+ *
+ * Processes the content import to be deleted or created batch and persists
+ * the importer.
*
* @param array $serializer_context
- * @param array|\DrushBatchContext $context
- * The batch context.
+ * Serializer context.
+ * @param array $context
+ * Reference to batch context.
*/
- public function deleteContent(array $serializer_context = [], &$context = []) {
+ public function deleteContent(array $serializer_context = [], array &$context = []) : void {
if (empty($context['sandbox'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['directory'] = $serializer_context['content_sync_directory_entities'];
@@ -182,22 +213,22 @@ public function deleteContent(array $serializer_context = [], &$context = []) {
[$entity_type_id, $bundle, $uuid] = $ids;
$entity = $this->contentSyncManager->getEntityTypeManager()->getStorage($entity_type_id)
- ->loadByProperties(['uuid' => $uuid]);
+ ->loadByProperties(['uuid' => $uuid]);
$entity = array_shift($entity);
if (!empty($entity)) {
// Prevent Anonymous User and Super Admin from being deleted.
- if ($entity_type_id == 'user' && (
+ if ($entity_type_id === 'user' && (
(int) $entity->id() === 0 ||
(int) $entity->id() === 1)) {
$message = $this->t('@uuid - Anonymous user or super admin can not be removed.', [
- '@entity_type' => $entity_type_id,
- '@uuid' => $uuid,
+ '@entity_type' => $entity_type_id,
+ '@uuid' => $uuid,
]);
- }else{
-
+ }
+ else {
try {
$message = $this->t('Deleted content @label (@entity_type: @id).', [
'@label' => $entity->label(),
@@ -208,9 +239,10 @@ public function deleteContent(array $serializer_context = [], &$context = []) {
$error = FALSE;
// Invalidate the CS Cache of the entity.
$bundle = $entity->bundle();
- $name = $entity_type_id . "." . $bundle . "." . $entity->uuid();
- $cache = \Drupal::cache('content')->invalidate($entity_type_id.".".$bundle.":".$name);
- } catch (EntityStorageException $e) {
+ $name = $entity_type_id . '.' . $bundle . '.' . $entity->uuid();
+ \Drupal::cache('content')->invalidate($entity_type_id . '.' . $bundle . ':' . $name);
+ }
+ catch (EntityStorageException $e) {
$message = $e->getMessage();
\Drupal::messenger()->addError($message);
}
@@ -266,10 +298,10 @@ public static function finishImportBatch($success, $results, $operations) {
// $operations contains the operations that remained unprocessed.
$error_operation = reset($operations);
$message = \Drupal::translation()
- ->translate('An error occurred while processing %error_operation with arguments: @arguments', [
- '%error_operation' => $error_operation[0],
- '@arguments' => print_r($error_operation[1], TRUE),
- ]);
+ ->translate('An error occurred while processing %error_operation with arguments: @arguments', [
+ '%error_operation' => $error_operation[0],
+ '@arguments' => print_r($error_operation[1], TRUE),
+ ]);
\Drupal::messenger()->addError($message, 'error');
}
}
diff --git a/src/Form/ContentLogFilterForm.php b/src/Form/ContentLogFilterForm.php
index 8eb15ab..f0fa363 100644
--- a/src/Form/ContentLogFilterForm.php
+++ b/src/Form/ContentLogFilterForm.php
@@ -2,6 +2,7 @@
namespace Drupal\content_sync\Form;
+use Drupal\content_sync\src\Logger\LogFilterTrait;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
@@ -10,10 +11,12 @@
*/
class ContentLogFilterForm extends FormBase {
+ use LogFilterTrait;
+
/**
* {@inheritdoc}
*/
- public function getFormId() {
+ public function getFormId() : string {
return 'cs_log_filter_form';
}
@@ -21,14 +24,12 @@ public function getFormId() {
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
- //$filters = cs_log_filters();
-
$form['filters'] = [
'#type' => 'details',
'#title' => $this->t('Filter log messages'),
'#open' => TRUE,
];
- foreach ($filters as $key => $filter) {
+ foreach ($this->getFilters() as $key => $filter) {
$form['filters']['status'][$key] = [
'#title' => $filter['title'],
'#type' => 'select',
@@ -73,8 +74,7 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
- $filters = cs_log_filters();
- foreach ($filters as $name => $filter) {
+ foreach ($this->getFilters() as $name => $filter) {
if ($form_state->hasValue($name)) {
$_SESSION['cs_log_overview_filter'][$name] = $form_state->getValue($name);
}
diff --git a/src/Form/ContentSettingsForm.php b/src/Form/ContentSettingsForm.php
index 5abe6c3..ffc1608 100644
--- a/src/Form/ContentSettingsForm.php
+++ b/src/Form/ContentSettingsForm.php
@@ -5,6 +5,9 @@
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
+/**
+ * Admin settings form.
+ */
class ContentSettingsForm extends ConfigFormBase {
/**
@@ -21,12 +24,12 @@ public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$config = $this->config('content_sync.settings');
- $form['site_uuid_override'] = array(
+ $form['site_uuid_override'] = [
'#type' => 'checkbox',
'#title' => $this->t('Bypass site UUID validation'),
'#description' => $this->t('If checked, site UUID validation would be ignored allowing to import the staged content even if it originates from a different site than this site.'),
'#default_value' => $config->get('content_sync.site_uuid_override'),
- );
+ ];
$form['help_menu_disabled'] = [
'#type' => 'checkbox',
'#title' => $this->t('Disable help menu'),
@@ -58,4 +61,4 @@ protected function getEditableConfigNames() {
];
}
-}
\ No newline at end of file
+}
diff --git a/src/Form/ContentSingleExportForm.php b/src/Form/ContentSingleExportForm.php
index accad21..ac523dd 100755
--- a/src/Form/ContentSingleExportForm.php
+++ b/src/Form/ContentSingleExportForm.php
@@ -3,54 +3,29 @@
namespace Drupal\content_sync\Form;
use Drupal\content_sync\Exporter\ContentExporterInterface;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfo;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Entity\ContentEntityType;
use Symfony\Component\DependencyInjection\ContainerInterface;
-
-
/**
* Provides a form for exporting a single content file.
*/
class ContentSingleExportForm extends FormBase {
/**
- * The entity type manager.
- *
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
- */
- protected $entityTypeManager;
-
- /**
- * The entity bundle manager.
- *
- * @var \Drupal\Core\Entity\EntityTypeBundleInfo
- */
- protected $entityBundleManager;
-
- /**
- * @var \Drupal\content_sync\Exporter\ContentExporterInterface
- */
- protected $contentExporter;
-
- /**
- * Constructs a new ContentSingleExportForm.
- *
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
- *
- * @param \Drupal\Core\Entity\EntityTypeBundleInfo $entity_bundle_manager
- *
- * @param \Drupal\content_sync\Exporter\ContentExporterInterface $content_exporter
+ * Constructor.
*/
- public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfo $entity_bundle_manager, ContentExporterInterface $content_exporter) {
- $this->entityTypeManager = $entity_type_manager;
- $this->entityBundleManager = $entity_bundle_manager;
- $this->contentExporter = $content_exporter;
- }
+ public function __construct(
+ protected EntityTypeManagerInterface $entityTypeManager,
+ protected EntityTypeBundleInfoInterface $entityBundleManager,
+ protected ContentExporterInterface $contentExporter,
+ protected LanguageManagerInterface $languageManager,
+ ) {}
/**
* {@inheritdoc}
@@ -59,21 +34,22 @@ public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('entity_type.bundle.info'),
- $container->get('content_sync.exporter')
+ $container->get('content_sync.exporter'),
+ $container->get('language_manager'),
);
}
/**
* {@inheritdoc}
*/
- public function getFormId() {
+ public function getFormId() : string {
return 'config_single_export_form';
}
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, FormStateInterface $form_state, $content_type = 'node', $content_name = NULL, $content_entity = NULL) {
+ public function buildForm(array $form, FormStateInterface $form_state, $content_type = 'node', $content_name = NULL, $content_entity = NULL) : array {
$entity_types = [];
$entity_type_definitions = $this->entityTypeManager->getDefinitions();
foreach ($entity_type_definitions as $entity_type => $definition) {
@@ -89,7 +65,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $content_
'#type' => 'select',
'#options' => $content_types,
'#default_value' => $content_type,
- '#attributes' => array('onchange' => 'this.form.content_name.value = null; if(this.form.content_entity){ this.form.content_entity.value = null; } this.form.export.value = null; this.form.submit();'),
+ '#attributes' => ['onchange' => 'this.form.content_name.value = null; if(this.form.content_entity){ this.form.content_entity.value = null; } this.form.export.value = null; this.form.submit();'],
];
$default_type = $form_state->getValue('content_type', $content_type);
@@ -99,11 +75,11 @@ public function buildForm(array $form, FormStateInterface $form_state, $content_
'#type' => 'select',
'#options' => $this->findContent($default_type),
'#default_value' => $content_name,
- '#attributes' => array('onchange' => 'if(this.form.content_entity){ this.form.content_entity.value = null; } this.form.export.value = null; this.form.submit();'),
+ '#attributes' => ['onchange' => 'if(this.form.content_entity){ this.form.content_entity.value = null; } this.form.export.value = null; this.form.submit();'],
];
- // Auto-complete field for the content entity
- if($default_type && $default_name){
+ // Auto-complete field for the content entity.
+ if ($default_type && $default_name) {
$form['content_entity'] = [
'#title' => $this->t('Content Entity'),
'#type' => 'entity_autocomplete',
@@ -115,13 +91,14 @@ public function buildForm(array $form, FormStateInterface $form_state, $content_
'#ajax' => [
'callback' => '::updateExport',
'wrapper' => 'edit-export-wrapper',
- 'event' => 'autocompleteclose',
+ 'event' => 'autocompleteclose',
],
];
- // Autocomplete doesn't support target bundles parameter on bundle-less entities.
+ // Autocomplete doesn't support target bundles parameter on bundle-less
+ // entities.
$target_type = $this->entityTypeManager->getDefinition($default_type);
$target_type_bundles = $target_type->getBundleEntityType();
- if(is_null($target_type_bundles)){
+ if (is_null($target_type_bundles)) {
unset($form['content_entity']['#selection_settings']);
}
}
@@ -140,7 +117,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $content_
/**
* Handles switching the content type selector.
*/
- protected function findContent($content_type) {
+ protected function findContent($content_type) : array {
$names = [
'' => $this->t('- Select -'),
];
@@ -164,23 +141,23 @@ protected function findContent($content_type) {
* Handles switching the export textarea.
*/
public function updateExport($form, FormStateInterface $form_state) {
- // Get submitted values
+ // Get submitted values.
$entity_type = $form_state->getValue('content_type');
$entity_id = $form_state->getValue('content_entity');
- // DB entity to YAML
+ // DB entity to YAML.
$entity = $this->entityTypeManager->getStorage($entity_type)->load($entity_id);
// Generate the YAML file.
$serializer_context = [];
- $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
+ $language = $this->languageManager->getCurrentLanguage()->getId();
$entity = $entity->getTranslation($language);
$exported_entity = $this->contentExporter->exportEntity($entity, $serializer_context);
- // Create the name
+ // Create the name.
$name = $entity_type . "." . $entity->bundle() . "." . $entity->uuid();
- // Return form values
+ // Return form values.
$form['export']['#value'] = $exported_entity;
$form['export']['#description'] = $this->t('Filename: %name', ['%name' => $name . '.yml']);
return $form['export'];
@@ -189,7 +166,7 @@ public function updateExport($form, FormStateInterface $form_state) {
/**
* {@inheritdoc}
*/
- public function submitForm(array &$form, FormStateInterface $form_state) {
+ public function submitForm(array &$form, FormStateInterface $form_state) : void {
// Nothing to submit.
}
diff --git a/src/Form/ContentSingleImportForm.php b/src/Form/ContentSingleImportForm.php
index 72e51bd..ac8d61e 100755
--- a/src/Form/ContentSingleImportForm.php
+++ b/src/Form/ContentSingleImportForm.php
@@ -15,24 +15,17 @@
class ContentSingleImportForm extends FormBase {
/**
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ * Constructor.
*/
- protected $entityTypeManager;
+ public function __construct(
+ protected EntityTypeManagerInterface $entityTypeManager,
+ protected ContentImporterInterface $contentImporter,
+ ) {}
/**
- * @var \Drupal\content_sync\Importer\ContentImporterInterface
+ * {@inheritDoc}
*/
- protected $contentImporter;
-
- /**
- * ContentImportForm constructor.
- */
- public function __construct(EntityTypeManagerInterface $entity_type_manager, ContentImporterInterface $content_importer) {
- $this->entityTypeManager = $entity_type_manager;
- $this->contentImporter = $content_importer;
- }
-
- public static function create(ContainerInterface $container) {
+ public static function create(ContainerInterface $container) : self {
return new static(
$container->get('entity_type.manager'),
$container->get('content_sync.importer')
@@ -42,14 +35,14 @@ public static function create(ContainerInterface $container) {
/**
* {@inheritdoc}
*/
- public function getFormId() {
+ public function getFormId() : string {
return 'content_single_import_form';
}
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, FormStateInterface $form_state) {
+ public function buildForm(array $form, FormStateInterface $form_state) : array {
$form['import'] = [
'#title' => $this->t('Paste your content here'),
'#type' => 'textarea',
@@ -69,29 +62,30 @@ public function buildForm(array $form, FormStateInterface $form_state) {
/**
* {@inheritdoc}
*/
- public function validateForm(array &$form, FormStateInterface $form_state) {
+ public function validateForm(array &$form, FormStateInterface $form_state) : void {
try {
// Decode the submitted import.
$data = Yaml::decode($form_state->getValue('import'));
// Store the decoded version of the submitted import.
$form_state->setValueForElement($form['import'], $data);
if (empty($data['_content_sync']['entity_type'])) {
- throw new \Exception($this->t('Entity type could not be determined.'));
+ throw new \Exception('Entity type could not be determined.');
}
- } catch (\Exception $e) {
+ }
+ catch (\Exception $e) {
$form_state->setErrorByName('import', $this->t('The import failed with the following message: %message', ['%message' => $e->getMessage()]));
$this->logger('content_sync')
- ->error('The import failed with the following message: %message', [
- '%message' => $e->getMessage(),
- 'link' => 'Import Single',
- ]);
+ ->error('The import failed with the following message: %message', [
+ '%message' => $e->getMessage(),
+ 'link' => 'Import Single',
+ ]);
}
}
/**
* {@inheritdoc}
*/
- public function submitForm(array &$form, FormStateInterface $form_state) {
+ public function submitForm(array &$form, FormStateInterface $form_state) : void {
$data = $form_state->getValue('import');
$entity = $this->contentImporter->importEntity($data);
if ($entity) {
@@ -105,4 +99,5 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
$this->messenger()->addError($this->t('Entity could not be imported.'));
}
}
+
}
diff --git a/src/Form/ContentSync.php b/src/Form/ContentSync.php
index 04238f4..935ab65 100755
--- a/src/Form/ContentSync.php
+++ b/src/Form/ContentSync.php
@@ -2,13 +2,12 @@
namespace Drupal\content_sync\Form;
+use Drupal\content_sync\Content\ContentStorageComparer;
use Drupal\content_sync\ContentSyncManagerInterface;
-use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Config\StorageComparer;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -20,49 +19,14 @@ class ContentSync extends FormBase {
use ContentImportTrait;
/**
- * The sync content object.
- *
- * @var \Drupal\Core\Config\StorageInterface
+ * Constructor.
*/
- protected $syncStorage;
-
- /**
- * The active content object.
- *
- * @var \Drupal\Core\Config\StorageInterface
- */
- protected $activeStorage;
-
- /**
- * The configuration manager.
- *
- * @var \Drupal\Core\Config\ConfigManagerInterface;
- */
- protected $configManager;
-
- /**
- * @var \Drupal\content_sync\ContentSyncManagerInterface
- */
- protected $contentSyncManager;
-
- /**
- * Constructs the object.
- *
- * @param \Drupal\Core\Config\StorageInterface $sync_storage
- * The source storage.
- * @param \Drupal\Core\Config\StorageInterface $active_storage
- * The target storage.
- * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
- * Configuration manager.
- * @param \Drupal\content_sync\ContentSyncManagerInterface $content_sync_manager
- * The content sync manager.
- */
- public function __construct(StorageInterface $sync_storage, StorageInterface $active_storage, ConfigManagerInterface $config_manager, ContentSyncManagerInterface $content_sync_manager) {
- $this->syncStorage = $sync_storage;
- $this->activeStorage = $active_storage;
- $this->configManager = $config_manager;
- $this->contentSyncManager = $content_sync_manager;
- }
+ public function __construct(
+ protected StorageInterface $syncStorage,
+ protected StorageInterface $activeStorage,
+ protected ConfigManagerInterface $configManager,
+ protected ContentSyncManagerInterface $contentSyncManager,
+ ) {}
/**
* {@inheritdoc}
@@ -72,28 +36,28 @@ public static function create(ContainerInterface $container) {
$container->get('content.storage.sync'),
$container->get('content.storage'),
$container->get('config.manager'),
- $container->get('content_sync.manager')
+ $container->get('content_sync.manager'),
);
}
/**
* {@inheritdoc}
*/
- public function getFormId() {
+ public function getFormId() : string {
return 'content_admin_import_form';
}
/**
* {@inheritdoc}
*/
- public function buildForm(array $form, FormStateInterface $form_state) {
- // Validate site uuid unless bypass the validation is selected
- $config = \Drupal::config('content_sync.settings');
- if ($config->get('content_sync.site_uuid_override') == FALSE) {
+ public function buildForm(array $form, FormStateInterface $form_state) : array {
+ // Validate site uuid unless bypass the validation is selected.
+ $config = $this->config('content_sync.settings');
+ if (!$config->get('content_sync.site_uuid_override')) {
// Get site uuid from site settings configuration.
$site_config = $this->config('system.site');
$target = $site_config->get('uuid');
- // Get site uuid from content sync folder
+ // Get site uuid from content sync folder.
$source = $this->syncStorage->read('site.uuid');
if ($source && $source['site_uuid'] !== $target) {
$this->messenger()->addError($this->t('The staged content cannot be imported, because it originates from a different site than this site. You can only synchronize content between cloned instances of this site.'));
@@ -108,9 +72,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
'#value' => $this->t('Import all'),
];
- //check that there is something on the content sync folder.
- $source_list = $this->syncStorage->listAll();
- $storage_comparer = new StorageComparer($this->syncStorage, $this->activeStorage, $this->configManager);
+ // Check that there is something on the content sync folder.
+ $storage_comparer = new ContentStorageComparer($this->syncStorage, $this->activeStorage);
$storage_comparer->createChangelist();
// Store the comparer for use in the submit.
@@ -121,7 +84,6 @@ public function buildForm(array $form, FormStateInterface $form_state) {
foreach ($storage_comparer->getAllCollectionNames() as $collection) {
-
foreach ($storage_comparer->getChangelist(NULL, $collection) as $config_change_type => $config_names) {
if (empty($config_names)) {
continue;
@@ -134,19 +96,19 @@ public function buildForm(array $form, FormStateInterface $form_state) {
];
switch ($config_change_type) {
case 'create':
- $form[$collection][$config_change_type]['heading']['#value'] = $collection .' '. $this->formatPlural(count($config_names), '@count new', '@count new');
+ $form[$collection][$config_change_type]['heading']['#value'] = $collection . ' ' . $this->formatPlural(count($config_names), '@count new', '@count new');
break;
case 'update':
- $form[$collection][$config_change_type]['heading']['#value'] = $collection .' '. $this->formatPlural(count($config_names), '@count changed', '@count changed');
+ $form[$collection][$config_change_type]['heading']['#value'] = $collection . ' ' . $this->formatPlural(count($config_names), '@count changed', '@count changed');
break;
case 'delete':
- $form[$collection][$config_change_type]['heading']['#value'] = $collection .' '. $this->formatPlural(count($config_names), '@count removed', '@count removed');
+ $form[$collection][$config_change_type]['heading']['#value'] = $collection . ' ' . $this->formatPlural(count($config_names), '@count removed', '@count removed');
break;
case 'rename':
- $form[$collection][$config_change_type]['heading']['#value'] = $collection .' '. $this->formatPlural(count($config_names), '@count renamed', '@count renamed');
+ $form[$collection][$config_change_type]['heading']['#value'] = $collection . ' ' . $this->formatPlural(count($config_names), '@count renamed', '@count renamed');
break;
}
$form[$collection][$config_change_type]['list'] = [
@@ -168,7 +130,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
else {
$route_options = ['source_name' => $config_name];
}
- if ($collection != StorageInterface::DEFAULT_COLLECTION) {
+ if ($collection !== StorageInterface::DEFAULT_COLLECTION) {
$route_name = 'content.diff_collection';
$route_options['collection'] = $collection;
}
@@ -204,25 +166,29 @@ public function buildForm(array $form, FormStateInterface $form_state) {
/**
* {@inheritdoc}
*/
- public function submitForm(array &$form, FormStateInterface $form_state) {
+ public function submitForm(array &$form, FormStateInterface $form_state) : void {
$comparer = $form_state->get('storage_comparer');
$collections = $comparer->getAllCollectionNames();
- //Set Batch to process the files from the content directory.
- //Get the files to be processed
- $content_to_sync = [];
- $content_to_delete = [];
- foreach ($collections as $collection => $collection_name) {
+ // Set Batch to process the files from the content directory.
+ // Get the files to be processed.
+ $contents_to_sync = [];
+ $contents_to_delete = [];
+ foreach ($collections as $collection_name) {
$actions = $comparer->getChangeList("", $collection_name);
if (!empty($actions['create'])) {
- $content_to_sync = array_merge($content_to_sync, $actions['create']);
+ $contents_to_sync[] = $actions['create'];
}
if (!empty($actions['update'])) {
- $content_to_sync = array_merge($content_to_sync, $actions['update']);
+ $contents_to_sync[] = $actions['update'];
}
if (!empty($actions['delete'])) {
- $content_to_delete = $actions['delete'];
+ $contents_to_delete[] = $actions['delete'];
}
}
+
+ $content_to_sync = array_unique(array_merge(...$contents_to_sync));
+ $content_to_delete = array_unique(array_merge(...$contents_to_delete));
+
$serializer_context = [];
$batch = $this->generateImportBatch($content_to_sync, $content_to_delete, $serializer_context);
batch_set($batch);
diff --git a/src/Importer/ContentImporter.php b/src/Importer/ContentImporter.php
index bf87dd0..7cf6692 100755
--- a/src/Importer/ContentImporter.php
+++ b/src/Importer/ContentImporter.php
@@ -3,39 +3,52 @@
namespace Drupal\content_sync\Importer;
use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\serialization\Normalizer\SerializedColumnNormalizerTrait;
+use Drupal\user\UserInterface;
use Symfony\Component\Serializer\Serializer;
+/**
+ * Content importer service.
+ */
class ContentImporter implements ContentImporterInterface {
use SerializedColumnNormalizerTrait;
- protected $format = 'yaml';
-
- protected $updateEntities = TRUE;
-
- protected $context = [];
+ /**
+ * Valid format.
+ *
+ * @var string
+ */
+ protected string $format = 'yaml';
/**
- * @var \Symfony\Component\Serializer\Serializer
+ * Flag to apply updates.
+ *
+ * @var bool
*/
- protected $serializer;
+ protected bool $updateEntities = TRUE;
/**
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+ * Importer context.
+ *
+ * @var array
*/
- protected $entityTypeManager;
+ protected array $context = [];
/**
- * ContentImporter constructor.
+ * Constructor.
*/
- public function __construct(Serializer $serializer, EntityTypeManagerInterface $entity_type_manager) {
- $this->serializer = $serializer;
- $this->entityTypeManager = $entity_type_manager;
- }
+ public function __construct(
+ protected Serializer $serializer,
+ protected EntityTypeManagerInterface $entityTypeManager,
+ ) {}
- public function importEntity($decoded_entity, $context = []) {
+ /**
+ * {@inheritDoc}
+ */
+ public function importEntity(array $decoded_entity, array $context = []) : ?ContentEntityInterface {
$context = $this->context + $context;
if (!empty($context['entity_type'])) {
@@ -49,21 +62,24 @@ public function importEntity($decoded_entity, $context = []) {
}
// Replace a menu link to a node with an actual one.
- if ($entity_type_id == 'menu_link_content' && !empty($decoded_entity["_content_sync"]["menu_entity_link"])) {
+ if ($entity_type_id === 'menu_link_content' && !empty($decoded_entity["_content_sync"]["menu_entity_link"])) {
$decoded_entity = $this->alterMenuLink($decoded_entity);
}
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
- //Exception for parent null -- allowing the term to be displayed on the taxonomy list.
- if ($entity_type_id == 'taxonomy_term') {
- if(empty($decoded_entity['parent'])){
- $decoded_entity['parent']['target_id'] = 0;
- }
+ if (!$entity_type instanceof ContentEntityTypeInterface) {
+ return NULL;
}
- //Get Translations before denormalize
- if(!empty($decoded_entity['_translations'])){
+ // Exception for parent null, allowing the term to be displayed on the
+ // taxonomy list.
+ if (($entity_type_id === 'taxonomy_term') && empty($decoded_entity['parent'])) {
+ $decoded_entity['parent']['target_id'] = 0;
+ }
+
+ // Get Translations before denormalize.
+ if (!empty($decoded_entity['_translations'])) {
$entity_translations = $decoded_entity['_translations'];
}
@@ -71,15 +87,15 @@ public function importEntity($decoded_entity, $context = []) {
if (!empty($entity)) {
// Prevent Anonymous User from being saved.
- if ($entity_type_id == 'user' && !$entity->isNew() && (int) $entity->id() === 0) {
+ if ($entity_type_id === 'user' && !$entity->isNew() && (int) $entity->id() === 0) {
return $entity;
}
$entity = $this->syncEntity($entity);
}
- // Include Translations
- if ($entity){
- if ( isset($entity_translations) && is_array($entity_translations) ) {
+ // Include Translations.
+ if ($entity) {
+ if (isset($entity_translations) && is_array($entity_translations)) {
$this->updateTranslation($entity, $entity_type, $entity_translations, $context);
}
}
@@ -89,16 +105,21 @@ public function importEntity($decoded_entity, $context = []) {
/**
* Updates translations.
*
- * @param $entity
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
* An entity object.
- * @param \Drupal\Core\Entity\ContentEntityType $entity_type
+ * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
* A ContentEntityType object.
* @param array $entity_translations
* An array of translations.
- * @param $context
- * The batch context.
+ * @param array $context
+ * The importer context.
*/
- protected function updateTranslation(&$entity, $entity_type, $entity_translations, $context) {
+ protected function updateTranslation(
+ ContentEntityInterface $entity,
+ ContentEntityTypeInterface $entity_type,
+ array $entity_translations,
+ array $context,
+ ) : void {
foreach ($entity_translations as $langcode => $translation) {
// Denormalize.
$translation = $this->serializer->denormalize($translation, $entity_type->getClass(), $this->format, $context);
@@ -110,10 +131,8 @@ protected function updateTranslation(&$entity, $entity_type, $entity_translation
$fields = $translation->getFieldDefinitions();
foreach ($translation as $itemID => $item) {
- if ($entity_translation->hasField($itemID)){
- if ($fields[$itemID]->isTranslatable() == TRUE){
- $entity_translation->$itemID->setValue($item->getValue());
- }
+ if ($entity_translation->hasField($itemID) && $fields[$itemID]->isTranslatable() == TRUE) {
+ $entity_translation->{$itemID}->setValue($item->getValue());
}
}
@@ -138,7 +157,7 @@ protected function updateTranslation(&$entity, $entity_type, $entity_translation
* @return array
* Array of entity values with the link values changed.
*/
- protected function alterMenuLink(array $decoded_entity) {
+ protected function alterMenuLink(array $decoded_entity) : array {
$referenced_entity_uuid = reset($decoded_entity["_content_sync"]["menu_entity_link"]);
$referenced_entity_type = key($decoded_entity["_content_sync"]["menu_entity_link"]);
if ($referenced_entity = \Drupal::service('entity.repository')->loadEntityByUuid($referenced_entity_type, $referenced_entity_uuid)) {
@@ -149,28 +168,31 @@ protected function alterMenuLink(array $decoded_entity) {
}
/**
+ * Get the format.
+ *
* @return string
+ * The format.
*/
- public function getFormat() {
+ public function getFormat() : string {
return $this->format;
}
/**
* Synchronize a given entity.
*
- * @param ContentEntityInterface $entity
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity to update.
*
- * @return ContentEntityInterface
+ * @return \Drupal\Core\Entity\ContentEntityInterface|null
* The updated entity
*/
- protected function syncEntity(ContentEntityInterface $entity) {
+ protected function syncEntity(ContentEntityInterface $entity) : ?ContentEntityInterface {
$preparedEntity = $this->prepareEntity($entity);
if ($this->validateEntity($preparedEntity)) {
$preparedEntity->save();
return $preparedEntity;
}
- elseif (!$preparedEntity->isNew()) {
+ if (!$preparedEntity->isNew()) {
return $preparedEntity;
}
return NULL;
@@ -179,13 +201,13 @@ protected function syncEntity(ContentEntityInterface $entity) {
/**
* Serializes fields which have to be stored serialized.
*
- * @param $entity
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity to update.
*
- * @return mixed
+ * @return \Drupal\Core\Entity\ContentEntityInterface
* The entity with the fields being serialized.
*/
- protected function processSerializedFields($entity) {
+ protected function processSerializedFields(ContentEntityInterface $entity) : ContentEntityInterface {
foreach ($entity->getTypedData() as $name => $field_items) {
foreach ($field_items as $field_item) {
// The field to be stored in a serialized way.
@@ -201,10 +223,10 @@ protected function processSerializedFields($entity) {
/**
* {@inheritdoc}
*/
- public function prepareEntity(ContentEntityInterface $entity) {
+ public function prepareEntity(ContentEntityInterface $entity) : ContentEntityInterface {
$uuid = $entity->uuid();
$original_entity = $this->entityTypeManager->getStorage($entity->getEntityTypeId())
- ->loadByProperties(['uuid' => $uuid]);
+ ->loadByProperties(['uuid' => $uuid]);
if (!empty($original_entity)) {
$original_entity = reset($original_entity);
@@ -217,7 +239,7 @@ public function prepareEntity(ContentEntityInterface $entity) {
foreach ($entity->_restSubmittedFields as $field_name) {
if ($this->isValidEntityField($original_entity, $entity, $field_name)) {
$original_entity->set($field_name, $entity->get($field_name)
- ->getValue());
+ ->getValue());
}
}
}
@@ -233,9 +255,9 @@ public function prepareEntity(ContentEntityInterface $entity) {
/**
* Checks if the entity field needs to be synchronized.
*
- * @param ContentEntityInterface $original_entity
+ * @param \Drupal\Core\Entity\ContentEntityInterface $original_entity
* The original entity.
- * @param ContentEntityInterface $entity
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity.
* @param string $field_name
* The field name.
@@ -243,7 +265,7 @@ public function prepareEntity(ContentEntityInterface $entity) {
* @return bool
* True if the field needs to be synced.
*/
- protected function isValidEntityField(ContentEntityInterface $original_entity, ContentEntityInterface $entity, $field_name) {
+ protected function isValidEntityField(ContentEntityInterface $original_entity, ContentEntityInterface $entity, string $field_name) : bool {
$valid = TRUE;
$entity_keys = $entity->getEntityType()->getKeys();
// Check if the target entity has the field.
@@ -257,7 +279,7 @@ protected function isValidEntityField(ContentEntityInterface $original_entity, C
elseif (in_array($field_name, $entity_keys, TRUE)) {
// Unchanged values for entity keys don't need access checking.
if ($original_entity->get($field_name)
- ->getValue() === $entity->get($field_name)->getValue()
+ ->getValue() === $entity->get($field_name)->getValue()
// It is not possible to set the language to NULL as it is
// automatically re-initialized.
// As it must not be empty, skip it if it is.
@@ -275,24 +297,28 @@ protected function isValidEntityField(ContentEntityInterface $original_entity, C
}
/**
- * {@inheritdoc}
+ * Perform entity validation.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity to validate.
+ *
+ * @return bool
+ * TRUE if valid; otherwise, FALSE. NOTE: We presently only validate user
+ * entities.
*/
- public function validateEntity(ContentEntityInterface $entity) {
- $reflection = new \ReflectionClass($entity);
+ public function validateEntity(ContentEntityInterface $entity) : bool {
$valid = TRUE;
- if ($reflection->implementsInterface('\Drupal\user\UserInterface')) {
+ if ($entity instanceof UserInterface) {
$validations = $entity->validate();
if (count($validations)) {
- /**
- * @var ConstraintViolation $validation
- */
+ /** @var \Symfony\Component\Validator\ConstraintViolation $validation */
foreach ($validations as $validation) {
if (!empty($this->getContext()['skipped_constraints']) && in_array(get_class($validation->getConstraint()), $this->getContext()['skipped_constraints'])) {
continue;
}
$valid = FALSE;
\Drupal::logger('content_sync')
- ->error($validation->getMessage());
+ ->error($validation->getMessage());
}
}
}
@@ -300,17 +326,23 @@ public function validateEntity(ContentEntityInterface $entity) {
}
/**
+ * Get context.
+ *
* @return array
+ * The context.
*/
- public function getContext() {
+ public function getContext() : array {
return $this->context;
}
/**
+ * Set context.
+ *
* @param array $context
+ * The context.
*/
- public function setContext($context) {
+ public function setContext(array $context) : void {
$this->context = $context;
}
-}
\ No newline at end of file
+}
diff --git a/src/Importer/ContentImporterInterface.php b/src/Importer/ContentImporterInterface.php
index 90920bb..e844d91 100755
--- a/src/Importer/ContentImporterInterface.php
+++ b/src/Importer/ContentImporterInterface.php
@@ -2,16 +2,24 @@
namespace Drupal\content_sync\Importer;
+use Drupal\Core\Entity\ContentEntityInterface;
+/**
+ * Content importer service interface.
+ */
interface ContentImporterInterface {
/**
- * @param $decoded_entity
- * @param $entity_type_id
+ * Import entity.
+ *
+ * @param array $decoded_entity
+ * The data describing the entity to create/import.
* @param array $context
+ * Context around the import operation.
*
- * @return \Drupal\Core\Entity\ContentEntityInterface
+ * @return \Drupal\Core\Entity\ContentEntityInterface|null
+ * The loaded, imported entity on success; otherwise, NULL.
*/
- public function importEntity($decoded_entity, $context = []);
+ public function importEntity(array $decoded_entity, array $context = []) : ?ContentEntityInterface;
-}
\ No newline at end of file
+}
diff --git a/src/Logger/ContentSyncLog.php b/src/Logger/ContentSyncLog.php
index 70fbb0f..e6569a0 100644
--- a/src/Logger/ContentSyncLog.php
+++ b/src/Logger/ContentSyncLog.php
@@ -2,7 +2,6 @@
namespace Drupal\content_sync\Logger;
-use Drupal\Component\Utility\Unicode;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\DatabaseException;
@@ -11,7 +10,6 @@
use Drupal\Core\Logger\RfcLoggerTrait;
use Psr\Log\LoggerInterface;
-
/**
* Logs events in the cs_log database table.
*/
@@ -80,16 +78,16 @@ public function log($level, \Stringable|string $message, array $context = []) :
->execute();
}
catch (\Exception $e) {
- // When running Drupal on MySQL or MariaDB you can run into several errors
- // that corrupt the database connection. Some examples for these kind of
- // errors on the database layer are "1100 - Table 'xyz' was not locked
- // with LOCK TABLES" and "1153 - Got a packet bigger than
- // 'max_allowed_packet' bytes". If such an error happens, the MySQL server
- // invalidates the connection and answers all further requests in this
- // connection with "2006 - MySQL server had gone away". In that case the
- // insert statement above results in a database exception. To ensure that
- // the causal error is written to the log we try once to open a dedicated
- // connection and write again.
+ // When running Drupal on MySQL or MariaDB you can run into several
+ // errors that corrupt the database connection. Some examples for these
+ // kind of errors on the database layer are "1100 - Table 'xyz' was not
+ // locked with LOCK TABLES" and "1153 - Got a packet bigger than
+ // 'max_allowed_packet' bytes". If such an error happens, the MySQL
+ // server invalidates the connection and answers all further requests in
+ // this connection with "2006 - MySQL server had gone away". In that
+ // case the insert statement above results in a database exception. To
+ // ensure that the causal error is written to the log we try once to
+ // open a dedicated connection and write again.
if (
// Only handle database related exceptions.
($e instanceof DatabaseException || $e instanceof \PDOException) &&
diff --git a/src/Logger/LogFilterTrait.php b/src/Logger/LogFilterTrait.php
new file mode 100644
index 0000000..e543c74
--- /dev/null
+++ b/src/Logger/LogFilterTrait.php
@@ -0,0 +1,53 @@
+ t('Type'),
+ 'where' => "w.type = ?",
+ 'options' => $types,
+ ];
+ }
+
+ $filters['severity'] = [
+ 'title' => t('Severity'),
+ 'where' => 'w.severity = ?',
+ 'options' => RfcLogLevel::getLevels(),
+ ];
+
+ return $filters;
+ }
+
+}
diff --git a/src/Normalizer/ContentEntityNormalizer.php b/src/Normalizer/ContentEntityNormalizer.php
index 8d7fce1..fa371b8 100755
--- a/src/Normalizer/ContentEntityNormalizer.php
+++ b/src/Normalizer/ContentEntityNormalizer.php
@@ -12,6 +12,7 @@
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\RevisionableInterface;
+use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Url;
use Drupal\serialization\Normalizer\ContentEntityNormalizer as BaseContentEntityNormalizer;
@@ -23,44 +24,17 @@ class ContentEntityNormalizer extends BaseContentEntityNormalizer {
use SyncNormalizerDecoratorTrait;
/**
- * @var SyncNormalizerDecoratorManager
+ * Constructor.
*/
- protected $decoratorManager;
-
- /**
- * The entity bundle info.
- *
- * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
- */
- protected $entityTypeBundleInfo;
-
- /**
- * The entity repository.
- *
- * @var \Drupal\Core\Entity\EntityRepositoryInterface
- */
- protected $entityRepository;
-
- /**
- * Constructs an EntityNormalizer object.
- *
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
- * The entity type manager.
- * @param \Drupal\Core\Entity\EntityTypeRepositoryInterface $entity_type_repository
- * The entity type repository.
- * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
- * The entity field manager.
- * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
- * The entity bundle info.
- * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
- * The entity repository.
- * @param SyncNormalizerDecoratorManager $decorator_manager
- */
- public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityTypeRepositoryInterface $entity_type_repository, EntityFieldManagerInterface $entity_field_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, EntityRepositoryInterface $entity_repository, SyncNormalizerDecoratorManager $decorator_manager) {
+ public function __construct(
+ EntityTypeManagerInterface $entity_type_manager,
+ EntityTypeRepositoryInterface $entity_type_repository,
+ EntityFieldManagerInterface $entity_field_manager,
+ protected EntityTypeBundleInfoInterface $entityTypeBundleInfo,
+ protected EntityRepositoryInterface $entityRepository,
+ protected SyncNormalizerDecoratorManager $decoratorManager,
+ ) {
parent::__construct($entity_type_manager, $entity_type_repository, $entity_field_manager);
- $this->decoratorManager = $decorator_manager;
- $this->entityRepository = $entity_repository;
- $this->entityTypeBundleInfo = $entity_type_bundle_info;
}
/**
@@ -86,23 +60,24 @@ public function denormalize($data, $class, $format = NULL, array $context = [])
// Get the ID key from the base field definition for the bundle key or
// default to 'value'.
- $key_id = isset($base_field_definitions[$bundle_key]) ? $base_field_definitions[$bundle_key]->getFieldStorageDefinition()
- ->getMainPropertyName() : 'value';
+ $key_id = isset($base_field_definitions[$bundle_key]) ?
+ $base_field_definitions[$bundle_key]->getFieldStorageDefinition()->getMainPropertyName() :
+ 'value';
// Normalize the bundle if it is not explicitly set.
- $bundle = isset($data[$bundle_key][0][$key_id]) ? $data[$bundle_key][0][$key_id] : (isset($data[$bundle_key]) ? $data[$bundle_key] : NULL);
+ $bundle = $data[$bundle_key][0][$key_id] ?? ($data[$bundle_key] ?? NULL);
}
// Decorate data before denormalizing it.
$this->decorateDenormalization($data, $entity_type_id, $format, $context);
- // Resolve references
+ // Resolve references.
$this->fixReferences($data, $entity_type_id, $bundle);
- // Remove invalid fields
+ // Remove invalid fields.
$this->cleanupData($data, $entity_type_id, $bundle);
- // Data to Entity
+ // Data to Entity.
$entity = parent::denormalize($data, $class, $format, $context);
// Decorate denormalized entity before retuning it.
@@ -115,7 +90,7 @@ public function denormalize($data, $class, $format = NULL, array $context = [])
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) : float|array|\ArrayObject|bool|int|string|null {
- /* @var ContentEntityInterface $object */
+ /** @var \Drupal\Core\Entity\ContentEntityInterface $object */
$normalized_data = parent::normalize($object, $format, $context);
$normalized_data['_content_sync'] = $this->getContentSyncMetadata($object, $context);
@@ -126,7 +101,7 @@ public function normalize($object, $format = NULL, array $context = []) : float|
$referenced_entities = $object->referencedEntities();
// Add node uuid for menu link if any.
- if ($object->getEntityTypeId() == 'menu_link_content') {
+ if ($object->getEntityTypeId() === 'menu_link_content') {
if ($entity = $this->getMenuLinkNodeAttached($object)) {
$normalized_data['_content_sync']['menu_entity_link'][$entity->getEntityTypeId()] = $entity->uuid();
$referenced_entities[] = $entity;
@@ -163,31 +138,30 @@ public function normalize($object, $format = NULL, array $context = []) : float|
*
* @param string $dependency
* An entity identifier.
- * @param $dependencies
+ * @param array $dependencies
* A nested array of dependencies.
*
* @return bool
+ * TRUE if the value is present; otherwise, FALSE.
*/
- protected function inDependencies($dependency, $dependencies) {
- [$entity_type_id, $bundle, $uuid] = explode('.', $dependency);
- if (isset($dependencies[$entity_type_id])) {
- if (in_array($dependency, $dependencies[$entity_type_id])) return TRUE;
- }
- return FALSE;
+ protected function inDependencies(string $dependency, array $dependencies) : bool {
+ [$entity_type_id] = explode('.', $dependency);
+ return isset($dependencies[$entity_type_id]) && in_array($dependency, $dependencies[$entity_type_id], FALSE);
}
/**
* Gets a node attached to a menu link. The node has already been imported.
*
* @param \Drupal\Core\Entity\EntityInterface $object
- * Menu Link Entity
+ * Menu Link Entity.
*
- * @return bool|\Drupal\Core\Entity\EntityInterface|null
- * Node Entity.
+ * @return false|\Drupal\Core\Entity\EntityInterface|null
+ * The linked entity if discovered. NULL if we discovered an entity but
+ * failed to load it. FALSE if if we failed to find an entity.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
- protected function getMenuLinkNodeAttached($object) {
+ protected function getMenuLinkNodeAttached(ContentEntityInterface $object) : ContentEntityInterface|false|null {
$entity = FALSE;
$uri = $object->get('link')->getString();
@@ -199,57 +173,60 @@ protected function getMenuLinkNodeAttached($object) {
catch (\Exception $e) {
// If menu link is linked to a non-node page - just do nothing.
}
- if (is_array($route_parameters) && count($route_parameters) == 1) {
+ if (is_array($route_parameters) && count($route_parameters) === 1) {
$entity_id = reset($route_parameters);
$entity_type = key($route_parameters);
- $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id);
+ $entity = $this->entityTypeManager->getStorage($entity_type)->load($entity_id);
}
return $entity;
}
/**
- * @param $object
+ * Get content sync metadata for the given entity and context.
+ *
+ * @param \Drupal\Core\Entity\ContentEntityInterface $object
+ * The content entity to import.
* @param array $context
+ * Import context.
*
* @return array
+ * The relevant metadata.
*/
- protected function getContentSyncMetadata($object, $context = []) {
- $metadata = [
+ protected function getContentSyncMetadata(ContentEntityInterface $object, array $context = []) : array {
+ return [
'entity_type' => $object->getEntityTypeId(),
];
- return $metadata;
}
/**
- * @inheritdoc
+ * {@inheritdoc}
*/
- protected function getDecoratorManager() {
+ protected function getDecoratorManager() : SyncNormalizerDecoratorManager {
return $this->decoratorManager;
}
/**
+ * Fix references in passed data.
+ *
* @param array $data
- * @param $entity_type_id
+ * A reference to the data in which to fix references.
+ * @param string $entity_type_id
+ * The type of entity that $data represents.
+ * @param string|false $bundle
+ * The bundle represented by the data.
*
* @return array
+ * The fixed data. Given $data comes in as a reference, it might not be
+ * necessary to use this return.
*/
- protected function fixReferences(&$data, $entity_type_id, $bundle = FALSE) {
- if ($bundle) {
- $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
- }
- else {
- $bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id));
- $field_definitions = [];
- foreach ($bundles as $bundle) {
- $field_definitions_bundle = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
- if (is_array($field_definitions_bundle)) {
- $field_definitions += $field_definitions_bundle;
- }
- }
- }
- foreach ($field_definitions as $field_name => $field_definition) {
+ protected function fixReferences(array &$data, $entity_type_id, string|false $bundle = FALSE) : array {
+ /**
+ * @var string $field_name
+ * @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ */
+ foreach ($this->getFieldDefinitions($entity_type_id, $bundle) as $field_name => $field_definition) {
// We are only interested in importing content entities.
- if (!is_a($field_definition->getClass(), '\Drupal\Core\Field\EntityReferenceFieldItemList', TRUE)) {
+ if (!$field_definition instanceof EntityReferenceFieldItemListInterface) {
continue;
}
if (!empty($data[$field_name]) && is_array($data[$field_name])) {
@@ -260,13 +237,14 @@ protected function fixReferences(&$data, $entity_type_id, $bundle = FALSE) {
$reference = $this->entityRepository->loadEntityByUuid($item['target_type'], $item['target_uuid']);
if ($reference) {
$item[$key] = $reference->id();
- if (is_a($reference, RevisionableInterface::class, TRUE)) {
+ if ($reference instanceof RevisionableInterface) {
$item['target_revision_id'] = $reference->getRevisionId();
}
}
else {
- $reflection = new \ReflectionClass($this->entityTypeManager->getStorage($item['target_type'])->getEntityType()->getClass());
- if ($reflection->implementsInterface(ContentEntityInterface::class)) {
+ $entity_type = $this->entityTypeManager->getStorage($item['target_type'])->getEntityType();
+
+ if ($entity_type instanceof ContentEntityInterface) {
unset($data[$field_name][$i]);
}
}
@@ -278,44 +256,59 @@ protected function fixReferences(&$data, $entity_type_id, $bundle = FALSE) {
}
/**
- * @param $data
- * @param $entity_type_id
+ * Cleanup given data.
+ *
+ * @param array $data
+ * Reference to data to cleanup.
+ * @param string $entity_type_id
+ * The type of entity that the data represents.
+ * @param string|false $bundle
+ * The bundle of the entity represented.
*/
- protected function cleanupData(&$data, $entity_type_id, $bundle = FALSE) {
- if ($bundle) {
- $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
- }
- else {
- $bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id));
- $field_definitions = [];
- foreach ($bundles as $bundle) {
- $field_definitions_bundle = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
- if (is_array($field_definitions_bundle)) {
- $field_definitions += $field_definitions_bundle;
- }
- }
- }
- $field_names = array_keys($field_definitions);
+ protected function cleanupData(array &$data, string $entity_type_id, string|false $bundle = FALSE) : void {
+ $field_names = array_keys($this->getFieldDefinitions($entity_type_id, $bundle));
foreach ($data as $field_name => $field_data) {
- if (!in_array($field_name, $field_names)) {
+ if (!in_array($field_name, $field_names, TRUE)) {
unset($data[$field_name]);
}
}
}
-
/**
- * @inheritdoc
+ * Get field definitions for the given type and bundle.
+ *
+ * @param string $entity_type_id
+ * The entity type for which to get field definitions.
+ * @param string|false $bundle
+ * A bundle to which to constrain fields, if desired.
+ *
+ * @return \Drupal\Core\Field\FieldDefinitionInterface[]
+ * An associative array mapping the field names to the related field
+ * definitions.
*/
- public function supportsNormalization($data, $format = NULL, array $context = []): bool {
- return parent::supportsNormalization($data, $format, $context) && ($context['content_sync'] ?? FALSE);
+ protected function getFieldDefinitions(string $entity_type_id, string|false $bundle) : array {
+ if ($bundle) {
+ // Given a particular bundle, targetedly get the fields.
+ return $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle);
+ }
+
+ // No bundle passed, get _ALL_ the fields!
+ $bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id));
+ $field_definitions = [];
+ foreach ($bundles as $_bundle) {
+ $field_definitions_bundle = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $_bundle);
+ if (is_array($field_definitions_bundle)) {
+ $field_definitions += $field_definitions_bundle;
+ }
+ }
+ return $field_definitions;
}
/**
- * @inheritdoc
+ * {@inheritdoc}
*/
- public function supportsDenormalization($data, $type, $format = NULL, array $context = []): bool {
- return parent::supportsDenormalization($data, $type, $format, $context);
+ public function supportsNormalization($data, $format = NULL, array $context = []): bool {
+ return parent::supportsNormalization($data, $format, $context) && ($context['content_sync'] ?? FALSE);
}
}
diff --git a/src/Normalizer/EntityReferenceFieldItemNormalizer.php b/src/Normalizer/EntityReferenceFieldItemNormalizer.php
index c7fcb73..5d2ee12 100755
--- a/src/Normalizer/EntityReferenceFieldItemNormalizer.php
+++ b/src/Normalizer/EntityReferenceFieldItemNormalizer.php
@@ -20,24 +20,14 @@ class EntityReferenceFieldItemNormalizer extends FieldItemNormalizer {
*
* @var string
*/
- protected $supportedInterfaceOrClass = EntityReferenceItem::class;
+ protected string $supportedInterfaceOrClass = EntityReferenceItem::class;
/**
- * The entity repository.
- *
- * @var \Drupal\Core\Entity\EntityRepositoryInterface
- */
- protected $entityRepository;
-
- /**
- * Constructs a EntityReferenceFieldItemNormalizer object.
- *
- * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
- * The entity repository.
+ * Constructor.
*/
- public function __construct(EntityRepositoryInterface $entity_repository) {
- $this->entityRepository = $entity_repository;
- }
+ public function __construct(
+ protected EntityRepositoryInterface $entityRepository,
+ ) {}
/**
* {@inheritdoc}
@@ -80,15 +70,14 @@ protected function constructValue($data, $context) {
if ($entity = $this->entityRepository->loadEntityByUuid($target_type, $data['target_uuid'])) {
- if (is_a($entity, RevisionableInterface::class, TRUE)) {
+ if ($entity instanceof RevisionableInterface) {
return ['target_id' => $entity->id(), 'target_revision_id' => $entity->getRevisionId()];
}
return ['target_id' => $entity->id()];
}
- else {
- // Unable to load entity by uuid.
- throw new InvalidArgumentException(sprintf('No "%s" entity found with UUID "%s" for field "%s".', $data['target_type'], $data['target_uuid'], $field_item->getName()));
- }
+
+ // Unable to load entity by uuid.
+ throw new InvalidArgumentException(sprintf('No "%s" entity found with UUID "%s" for field "%s".', $data['target_type'], $data['target_uuid'], $field_item->getName()));
}
return parent::constructValue($data, $context);
}
diff --git a/src/Normalizer/FileEntityNormalizer.php b/src/Normalizer/FileEntityNormalizer.php
index 157651e..a487678 100755
--- a/src/Normalizer/FileEntityNormalizer.php
+++ b/src/Normalizer/FileEntityNormalizer.php
@@ -11,6 +11,7 @@
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\File\FileUrlGeneratorInterface;
+use Drupal\file\FileInterface;
/**
* Adds the file URI to embedded file entities.
@@ -22,21 +23,7 @@ class FileEntityNormalizer extends ContentEntityNormalizer {
*
* @var string
*/
- protected $supportedInterfaceOrClass = 'Drupal\file\FileInterface';
-
- /**
- * File system service.
- *
- * @var \Drupal\Core\File\FileSystemInterface
- */
- protected $fileSystem;
-
- /**
- * File URL generator service.
- *
- * @var \Drupal\Core\File\FileUrlGeneratorInterface
- */
- protected FileUrlGeneratorInterface $fileUrlGenerator;
+ protected string $supportedInterfaceOrClass = FileInterface::class;
/**
* Constructor.
@@ -48,18 +35,16 @@ public function __construct(
EntityTypeBundleInfoInterface $entity_type_bundle_info,
EntityRepositoryInterface $entity_repository,
SyncNormalizerDecoratorManager $decorator_manager,
- FileSystemInterface $file_system,
- FileUrlGeneratorInterface $file_url_generator
+ protected FileSystemInterface $fileSystem,
+ protected FileUrlGeneratorInterface $fileUrlGenerator,
) {
parent::__construct($entity_type_manager, $entity_type_repository, $entity_field_manager, $entity_type_bundle_info, $entity_repository, $decorator_manager);
- $this->fileSystem = $file_system;
- $this->fileUrlGenerator = $file_url_generator;
}
/**
* {@inheritdoc}
*/
- public function denormalize($data, $class, $format = NULL, array $serializer_context = array()) : mixed {
+ public function denormalize($data, $class, $format = NULL, array $serializer_context = []) : mixed {
$file_data = '';
@@ -74,7 +59,7 @@ public function denormalize($data, $class, $format = NULL, array $serializer_con
if (!empty($serializer_context['content_sync_directory_files'])) {
$scheme = \Drupal::service('stream_wrapper_manager')->getScheme($data['uri'][0]['value']);
if (!empty($scheme)) {
- $source_path = realpath($serializer_context['content_sync_directory_files']) . '/' .$scheme . '/';
+ $source_path = realpath($serializer_context['content_sync_directory_files']) . '/' . $scheme . '/';
$source = str_replace($scheme . '://', $source_path, $data['uri'][0]['value']);
if (file_exists($source)) {
$file = $this->fileSystem->realpath($data['uri'][0]['value']);
@@ -85,8 +70,8 @@ public function denormalize($data, $class, $format = NULL, array $serializer_con
$data['uri'] = [
[
'value' => $uri,
- 'url' => str_replace($GLOBALS['base_url'], '', $this->fileUrlGenerator->generateString($uri))
- ]
+ 'url' => str_replace($GLOBALS['base_url'], '', $this->fileUrlGenerator->generateString($uri)),
+ ],
];
// We just need one method to create the image.
@@ -112,27 +97,13 @@ public function denormalize($data, $class, $format = NULL, array $serializer_con
}
}
- // If the image was sent as URL we must to create the physical file.
- /*if ($file_data) {
- // Decode and save to file.
- $file_contents = base64_decode($file_data);
- $dirname = $this->fileSystem->dirname($entity->getFileUri());
- file_prepare_directory($dirname, FILE_CREATE_DIRECTORY);
- if ($uri = file_unmanaged_save_data($file_contents, $entity->getFileUri())) {
- $entity->setFileUri($uri);
- }
- else {
- throw new \RuntimeException(SafeMarkup::format('Failed to write @filename.', array('@filename' => $entity->getFilename())));
- }
- }*/
-
return $entity;
}
/**
* {@inheritdoc}
*/
- public function normalize($object, $format = NULL, array $serializer_context = array()) : float|int|bool|\ArrayObject|array|string|null {
+ public function normalize($object, $format = NULL, array $serializer_context = []) : float|int|bool|\ArrayObject|array|string|null {
$data = parent::normalize($object, $format, $serializer_context);
// The image will be saved in the export directory.
diff --git a/src/Normalizer/ImageItemNormalizer.php b/src/Normalizer/ImageItemNormalizer.php
index 2d42234..e0a762b 100644
--- a/src/Normalizer/ImageItemNormalizer.php
+++ b/src/Normalizer/ImageItemNormalizer.php
@@ -2,10 +2,12 @@
namespace Drupal\content_sync\Normalizer;
-
use Drupal\image\Plugin\Field\FieldType\ImageItem;
use Drupal\serialization\Normalizer\EntityReferenceFieldItemNormalizer;
+/**
+ * Image item normalizer.
+ */
class ImageItemNormalizer extends EntityReferenceFieldItemNormalizer {
/**
@@ -19,13 +21,13 @@ class ImageItemNormalizer extends EntityReferenceFieldItemNormalizer {
* {@inheritdoc}
*/
protected function constructValue($data, $context) {
- $denormalized_data = parent::constructValue($data, $context);
+ $denormalized_data = parent::constructValue($data, $context);
foreach (['alt', 'title'] as $field) {
- if(!empty($data[$field])) {
+ if (!empty($data[$field])) {
$denormalized_data[$field] = $data[$field];
}
}
return $denormalized_data;
}
-}
\ No newline at end of file
+}
diff --git a/src/Normalizer/UserEntityNormalizer.php b/src/Normalizer/UserEntityNormalizer.php
index cefb201..2c749a7 100755
--- a/src/Normalizer/UserEntityNormalizer.php
+++ b/src/Normalizer/UserEntityNormalizer.php
@@ -2,6 +2,9 @@
namespace Drupal\content_sync\Normalizer;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\user\UserInterface;
+
/**
* User entity normalizer class.
*/
@@ -13,7 +16,7 @@ class UserEntityNormalizer extends ContentEntityNormalizer {
*
* @var string
*/
- protected $supportedInterfaceOrClass = 'Drupal\user\UserInterface';
+ protected string $supportedInterfaceOrClass = UserInterface::class;
/**
* {@inheritdoc}
@@ -59,7 +62,7 @@ public function normalize($object, $format = NULL, array $context = []) : float|
/**
* {@inheritdoc}
*/
- protected function getContentSyncMetadata($object, $context = []) {
+ protected function getContentSyncMetadata(ContentEntityInterface $object, array $context = []): array {
/** @var \Drupal\user\Entity\User $object */
$metadata = parent::getContentSyncMetadata($object, $context);
if ($object->isAnonymous()) {
@@ -67,4 +70,5 @@ protected function getContentSyncMetadata($object, $context = []) {
}
return $metadata;
}
+
}
diff --git a/src/Plugin/Action/ExportNodes.php b/src/Plugin/Action/ExportNodes.php
index 1f395eb..6d5255f 100755
--- a/src/Plugin/Action/ExportNodes.php
+++ b/src/Plugin/Action/ExportNodes.php
@@ -45,7 +45,7 @@ class ExportNodes extends ActionBase implements ContainerFactoryPluginInterface
* The plugin implementation definition.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
- * @param AccountInterface $current_user
+ * @param \Drupal\Core\Session\AccountInterface $current_user
* Current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) {
@@ -88,13 +88,13 @@ public function executeMultiple(array $entities) {
* {@inheritdoc}
*/
public function execute($object = NULL) {
- $this->executeMultiple(array($object));
+ $this->executeMultiple([$object]);
}
/**
* {@inheritdoc}
*/
- public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+ public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
return $account->hasPermission('administer site configuration');
}
diff --git a/src/Plugin/SyncNormalizerDecorator/Alias.php b/src/Plugin/SyncNormalizerDecorator/Alias.php
index b5895ab..d7eaa47 100755
--- a/src/Plugin/SyncNormalizerDecorator/Alias.php
+++ b/src/Plugin/SyncNormalizerDecorator/Alias.php
@@ -2,7 +2,6 @@
namespace Drupal\content_sync\Plugin\SyncNormalizerDecorator;
-
use Drupal\content_sync\Plugin\SyncNormalizerDecoratorBase;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\path_alias\AliasManager;
@@ -20,49 +19,43 @@
class Alias extends SyncNormalizerDecoratorBase implements ContainerFactoryPluginInterface {
/**
- * @var \Drupal\path_alias\AliasManager
+ * Constructor.
*/
- protected $aliasManager;
-
- public function __construct(array $configuration, $plugin_id, $plugin_definition, AliasManager $alias_manager) {
+ public function __construct(
+ array $configuration,
+ $plugin_id,
+ $plugin_definition,
+ protected AliasManager $aliasManager,
+ ) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
- $this->aliasManager = $alias_manager;
}
/**
- * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
- * @param array $configuration
- * @param $plugin_id
- * @param $plugin_definition
- *
- * @return static
+ * {@inheritDoc}
*/
- public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) : self {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
- $container->get('path_alias.manager')
+ $container->get('path_alias.manager'),
);
}
/**
- * @param array $normalized_entity
- * @param \Drupal\Core\Entity\ContentEntityInterface $entity
- * @param $format
- * @param array $context
+ * {@inheritDoc}
*/
- public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) {
+ public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) : void {
if ($entity->hasLinkTemplate('canonical')) {
$url = $entity->toUrl();
if (!empty($url)) {
$system_path = '/' . $url->getInternalPath();
$langcode = $entity->language()->getId();
$path_alias = $this->aliasManager->getAliasByPath($system_path, $langcode);
- if (!empty($path_alias) && $path_alias != $system_path) {
+ if (!empty($path_alias) && $path_alias !== $system_path) {
$normalized_entity['path'] = [
[
- 'alias' => $path_alias
+ 'alias' => $path_alias,
],
];
}
diff --git a/src/Plugin/SyncNormalizerDecorator/IdsCleaner.php b/src/Plugin/SyncNormalizerDecorator/IdsCleaner.php
index 4125d8f..8671bd6 100755
--- a/src/Plugin/SyncNormalizerDecorator/IdsCleaner.php
+++ b/src/Plugin/SyncNormalizerDecorator/IdsCleaner.php
@@ -2,9 +2,13 @@
namespace Drupal\content_sync\Plugin\SyncNormalizerDecorator;
-
use Drupal\content_sync\Plugin\SyncNormalizerDecoratorBase;
use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\ContentEntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a decorator for setting the alias to entity.
@@ -14,47 +18,68 @@
* name = @Translation("IDs Cleaner"),
* )
*/
-class IdsCleaner extends SyncNormalizerDecoratorBase {
+class IdsCleaner extends SyncNormalizerDecoratorBase implements ContainerFactoryPluginInterface {
- public function __construct(array $configuration, $plugin_id, $plugin_definition) {
+ /**
+ * Constructor.
+ */
+ public function __construct(
+ array $configuration,
+ $plugin_id,
+ $plugin_definition,
+ protected EntityTypeManagerInterface $entityTypeManager,
+ ) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
- * @param array $normalized_entity
- * @param \Drupal\Core\Entity\ContentEntityInterface $entity
- * @param $format
- * @param array $context
+ * {@inheritDoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) : self {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_type.manager'),
+ );
+ }
+
+ /**
+ * {@inheritDoc}
*/
- public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) {
+ public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) : void {
$this->cleanReferenceIds($normalized_entity, $entity);
$this->cleanIds($normalized_entity, $entity);
}
/**
- * @param $normalized_entity
- * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * Clear out referenced IDs.
*
- * @return mixed
+ * @param array $normalized_entity
+ * The entity data from which to clear reference IDs.
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity.
*/
- protected function cleanReferenceIds(&$normalized_entity, ContentEntityInterface $entity) {
+ protected function cleanReferenceIds(array &$normalized_entity, ContentEntityInterface $entity) : void {
$field_definitions = $entity->getFieldDefinitions();
+ /**
+ * @var string $field_name
+ * @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+ */
foreach ($field_definitions as $field_name => $field_definition) {
// We are only interested in importing content entities.
- if (!is_a($field_definition->getClass(), '\Drupal\Core\Field\EntityReferenceFieldItemList', TRUE)) {
+ if (!is_a($field_definition->getClass(), EntityReferenceFieldItemListInterface::class, TRUE)) {
continue;
}
- if (isset($normalized_entity[$field_name]) && !empty($normalized_entity[$field_name]) && is_array($normalized_entity[$field_name])) {
- $entity_type = $field_definition->getFieldStorageDefinition()
- ->getSetting('target_type');
- $reflection = new \ReflectionClass(\Drupal::entityTypeManager()
- ->getDefinition($entity_type)
- ->getClass());
- if (!$reflection->implementsInterface('\Drupal\Core\Entity\ContentEntityInterface')) {
+ if (!empty($normalized_entity[$field_name]) && is_array($normalized_entity[$field_name])) {
+ $entity_type_id = $field_definition->getFieldStorageDefinition()->getSetting('target_type');
+ $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+
+ if (!$entity_type instanceof ContentEntityTypeInterface) {
continue;
}
$key = $field_definition->getFieldStorageDefinition()
- ->getMainPropertyName();
+ ->getMainPropertyName();
foreach ($normalized_entity[$field_name] as &$item) {
if (!empty($item[$key])) {
unset($item[$key]);
@@ -65,24 +90,24 @@ protected function cleanReferenceIds(&$normalized_entity, ContentEntityInterface
}
}
}
- return $normalized_entity;
}
-
/**
- * @param $normalized_entity
- * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * Clear out IDs.
*
- * @return mixed
+ * @param array $normalized_entity
+ * Normalized entity in which to clean IDs.
+ * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+ * The entity of which to clear out IDs.
*/
- protected function cleanIds(&$normalized_entity, ContentEntityInterface $entity) {
+ protected function cleanIds(array &$normalized_entity, ContentEntityInterface $entity) : void {
$keys = $entity->getEntityType()->getKeys();
if (isset($normalized_entity[$keys['id']])) {
unset($normalized_entity[$keys['id']]);
}
- if (isset($keys['revision']) && isset($normalized_entity[$keys['revision']])) {
+ if (isset($keys['revision'], $normalized_entity[$keys['revision']])) {
unset($normalized_entity[$keys['revision']]);
}
- return $normalized_entity;
}
+
}
diff --git a/src/Plugin/SyncNormalizerDecorator/Parents.php b/src/Plugin/SyncNormalizerDecorator/Parents.php
index a4931d6..1990236 100755
--- a/src/Plugin/SyncNormalizerDecorator/Parents.php
+++ b/src/Plugin/SyncNormalizerDecorator/Parents.php
@@ -2,7 +2,6 @@
namespace Drupal\content_sync\Plugin\SyncNormalizerDecorator;
-
use Drupal\content_sync\Plugin\SyncNormalizerDecoratorBase;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -10,6 +9,7 @@
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
+ * Parent sync normalizer decorator.
*
* @SyncNormalizerDecorator(
* id = "parents",
@@ -18,23 +18,19 @@
*/
class Parents extends SyncNormalizerDecoratorBase implements ContainerFactoryPluginInterface {
-
- protected $entityTypeManager;
-
- public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
+ public function __construct(
+ array $configuration,
+ $plugin_id,
+ $plugin_definition,
+ protected EntityTypeManagerInterface $entityTypeManager,
+ ) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
- $this->entityTypeManager = $entity_type_manager;
}
/**
- * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
- * @param array $configuration
- * @param $plugin_id
- * @param $plugin_definition
- *
- * @return static
+ * {@inheritDoc}
*/
- public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) : self {
return new static(
$configuration,
$plugin_id,
@@ -44,18 +40,15 @@ public static function create(ContainerInterface $container, array $configuratio
}
/**
- * @param array $normalized_entity
- * @param \Drupal\Core\Entity\ContentEntityInterface $entity
- * @param $format
- * @param array $context
+ * {@inheritDoc}
*/
- public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) {
+ public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) : void {
if ($entity->hasField('parent')) {
$entity_type = $entity->getEntityTypeId();
$storage = $this->entityTypeManager->getStorage($entity_type);
if (method_exists($storage, 'loadParents')) {
$parents = $storage->loadParents($entity->id());
- foreach ($parents as $parent_key => $parent) {
+ foreach ($parents as $parent) {
if (!$this->parentExistence($parent->uuid(), $normalized_entity)) {
$normalized_entity['parent'][] = [
'target_type' => $entity_type,
@@ -67,10 +60,10 @@ public function decorateNormalization(array &$normalized_entity, ContentEntityIn
}
elseif (method_exists($entity, 'getParentId')) {
$parent_id = $entity->getParentId();
- if (($tmp = strstr($parent_id, ':')) !== false) {
+ if (($tmp = strstr($parent_id, ':')) !== FALSE) {
$parent_uuid = substr($tmp, 1);
$parents = $storage->loadByProperties(['uuid' => $parent_uuid]);
- $parent = !empty($parents) ? reset($parents) : null;
+ $parent = !empty($parents) ? reset($parents) : NULL;
if (!empty($parent) && !$this->parentExistence($parent_uuid, $normalized_entity)) {
$normalized_entity['parent'][] = [
'target_type' => $entity_type,
@@ -94,8 +87,8 @@ public function decorateNormalization(array &$normalized_entity, ContentEntityIn
* @return bool
* TRUE if it already exists, FALSE if not.
*/
- protected function parentExistence($parent_uuid, array $normalized_entity) {
- return array_search($parent_uuid, array_column($normalized_entity['parent'], 'target_uuid')) !== FALSE;
+ protected function parentExistence(string $parent_uuid, array $normalized_entity) : bool {
+ return in_array($parent_uuid, array_column($normalized_entity['parent'], 'target_uuid'), TRUE);
}
}
diff --git a/src/Plugin/SyncNormalizerDecoratorBase.php b/src/Plugin/SyncNormalizerDecoratorBase.php
index fcc1e41..b50aa1b 100755
--- a/src/Plugin/SyncNormalizerDecoratorBase.php
+++ b/src/Plugin/SyncNormalizerDecoratorBase.php
@@ -13,21 +13,22 @@ abstract class SyncNormalizerDecoratorBase extends PluginBase implements SyncNor
/**
* {@inheritdoc}
*/
- public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) {
+ public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) : void {
}
/**
* {@inheritdoc}
*/
- public function decorateDenormalization(array &$normalized_entity, $type, $format, array $context = []) {
+ public function decorateDenormalization(array &$normalized_entity, $type, $format, array $context = []) : void {
}
/**
* {@inheritdoc}
*/
- public function decorateDenormalizedEntity(ContentEntityInterface $entity, array $normalized_entity, $format, array $context = []) {
+ public function decorateDenormalizedEntity(ContentEntityInterface $entity, array $normalized_entity, $format, array $context = []) : void {
}
+
}
diff --git a/src/Plugin/SyncNormalizerDecoratorInterface.php b/src/Plugin/SyncNormalizerDecoratorInterface.php
index f6b9aab..fb1e7aa 100755
--- a/src/Plugin/SyncNormalizerDecoratorInterface.php
+++ b/src/Plugin/SyncNormalizerDecoratorInterface.php
@@ -13,17 +13,16 @@ interface SyncNormalizerDecoratorInterface extends PluginInspectionInterface {
/**
* Apply decoration for the normalization process.
*/
- public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []);
+ public function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) : void;
/**
* Apply decoration for the denormalization process.
*/
- public function decorateDenormalization(array &$normalized_entity, $type, $format, array $context = []);
-
+ public function decorateDenormalization(array &$normalized_entity, $type, $format, array $context = []) : void;
/**
* Apply decoration to the entity after the denormalization process is done.
*/
- public function decorateDenormalizedEntity(ContentEntityInterface $entity, array $normalized_entity, $format, array $context = []);
+ public function decorateDenormalizedEntity(ContentEntityInterface $entity, array $normalized_entity, $format, array $context = []) : void;
}
diff --git a/src/Plugin/SyncNormalizerDecoratorManager.php b/src/Plugin/SyncNormalizerDecoratorManager.php
index db0675e..df31ec5 100755
--- a/src/Plugin/SyncNormalizerDecoratorManager.php
+++ b/src/Plugin/SyncNormalizerDecoratorManager.php
@@ -2,6 +2,7 @@
namespace Drupal\content_sync\Plugin;
+use Drupal\content_sync\Annotation\SyncNormalizerDecorator;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
@@ -23,7 +24,13 @@ class SyncNormalizerDecoratorManager extends DefaultPluginManager {
* The module handler to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
- parent::__construct('Plugin/SyncNormalizerDecorator', $namespaces, $module_handler, 'Drupal\content_sync\Plugin\SyncNormalizerDecoratorInterface', 'Drupal\content_sync\Annotation\SyncNormalizerDecorator');
+ parent::__construct(
+ 'Plugin/SyncNormalizerDecorator',
+ $namespaces,
+ $module_handler,
+ SyncNormalizerDecoratorInterface::class,
+ SyncNormalizerDecorator::class,
+ );
$this->alterInfo('content_sync_sync_normalizer_decorator_info');
$this->setCacheBackend($cache_backend, 'content_sync_sync_normalizer_decorator_plugins');
diff --git a/src/Plugin/SyncNormalizerDecoratorTrait.php b/src/Plugin/SyncNormalizerDecoratorTrait.php
index 518119f..e537def 100755
--- a/src/Plugin/SyncNormalizerDecoratorTrait.php
+++ b/src/Plugin/SyncNormalizerDecoratorTrait.php
@@ -4,38 +4,50 @@
use Drupal\Core\Entity\ContentEntityInterface;
+/**
+ * Helper trait; facilitate invocations of decorated (de)normalization plugins.
+ */
trait SyncNormalizerDecoratorTrait {
- protected function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) {
+ /**
+ * Invoke all managed normalization plugins.
+ */
+ protected function decorateNormalization(array &$normalized_entity, ContentEntityInterface $entity, $format, array $context = []) : void {
$plugins = $this->getDecoratorManager()->getDefinitions();
foreach ($plugins as $decorator) {
- /* @var $instance SyncNormalizerDecoratorInterface */
+ /** @var SyncNormalizerDecoratorInterface $instance */
$instance = $this->getDecoratorManager()->createInstance($decorator['id']);
$instance->decorateNormalization($normalized_entity, $entity, $format, $context);
}
}
- protected function decorateDenormalization(array &$normalized_entity, $type, $format, array $context = []) {
+ /**
+ * Invoke all managed denormalization plugins.
+ */
+ protected function decorateDenormalization(array &$normalized_entity, $type, $format, array $context = []) : void {
$plugins = $this->getDecoratorManager()->getDefinitions();
foreach ($plugins as $decorator) {
- /* @var $instance SyncNormalizerDecoratorInterface */
+ /** @var SyncNormalizerDecoratorInterface $instance */
$instance = $this->getDecoratorManager()->createInstance($decorator['id']);
$instance->decorateDenormalization($normalized_entity, $type, $format, $context);
}
}
+ /**
+ * Invoke all managed entity denormalization plugins.
+ */
protected function decorateDenormalizedEntity(ContentEntityInterface $entity, array $normalized_entity, $format, array $context = []) {
$plugins = $this->getDecoratorManager()->getDefinitions();
foreach ($plugins as $decorator) {
- /* @var $instance SyncNormalizerDecoratorInterface */
+ /** @var SyncNormalizerDecoratorInterface $instance */
$instance = $this->getDecoratorManager()->createInstance($decorator['id']);
$instance->decorateDenormalizedEntity($entity, $normalized_entity, $format, $context);
}
}
/**
- * @return SyncNormalizerDecoratorManager
+ * Get the sync normalizer decorator manager service.
*/
- protected abstract function getDecoratorManager();
+ abstract protected function getDecoratorManager() : SyncNormalizerDecoratorManager;
-}
\ No newline at end of file
+}
diff --git a/src/Utility/ContentSyncDialogHelper.php b/src/Utility/ContentSyncDialogHelper.php
index f10379c..7adbb7e 100644
--- a/src/Utility/ContentSyncDialogHelper.php
+++ b/src/Utility/ContentSyncDialogHelper.php
@@ -3,7 +3,6 @@
namespace Drupal\content_sync\Utility;
use Drupal\Component\Serialization\Json;
-use Drupal\imce\Imce;
/**
* Helper class for dialog methods.
@@ -15,18 +14,20 @@ class ContentSyncDialogHelper {
*
* @var string
*/
- protected static $offCanvasTriggerName;
+ protected static string $offCanvasTriggerName;
/**
* Get Off canvas trigger name.
*
+ * Dealing with issue #2862625: Rename offcanvas to two words in code and
+ * comments.
+ *
* @return string
* The off canvas trigger name.
*
- * @see Issue #2862625: Rename offcanvas to two words in code and comments.
* @see https://www.drupal.org/node/2862625
*/
- public static function getOffCanvasTriggerName() {
+ public static function getOffCanvasTriggerName() : string {
if (isset(self::$offCanvasTriggerName)) {
return self::$offCanvasTriggerName;
}
@@ -49,8 +50,8 @@ public static function getOffCanvasTriggerName() {
* @return bool
* TRUE if outside_in.module is enabled and system trays are not disabled.
*/
- public static function useOffCanvas() {
- return ((floatval(\Drupal::VERSION) >= 8.3) && \Drupal::moduleHandler()->moduleExists('outside_in') && !\Drupal::config('content_sync.settings')->get('ui.offcanvas_disabled')) ? TRUE : FALSE;
+ public static function useOffCanvas() : bool {
+ return (((float) \Drupal::VERSION >= 8.3) && \Drupal::moduleHandler()->moduleExists('outside_in') && !\Drupal::config('content_sync.settings')->get('ui.offcanvas_disabled'));
}
/**
@@ -59,7 +60,7 @@ public static function useOffCanvas() {
* @param array $build
* A render array.
*/
- public static function attachLibraries(array &$build) {
+ public static function attachLibraries(array &$build) : void {
$build['#attached']['library'][] = 'content_sync/content_sync.admin.dialog';
}
@@ -74,7 +75,7 @@ public static function attachLibraries(array &$build) {
* @return array
* Modal dialog attributes.
*/
- public static function getModalDialogAttributes($width = 800, array $class = []) {
+ public static function getModalDialogAttributes(int $width = 800, array $class = []) : array {
if (\Drupal::config('content_sync.settings')->get('ui.dialog_disabled')) {
return $class ? ['class' => $class] : [];
}