content_sync-8.x-2.x-dev/content_sync.module
content_sync.module
<?php /** * @file * Allows site administrators to modify content. */ use Drupal\content_sync\Content\ContentDatabaseStorage; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Entity\ContentEntityType; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Serialization\Yaml; use Drupal\Core\Url; /** * Implements hook_help(). */ function content_sync_help($route_name, RouteMatchInterface $route_match) { // Get path from route match. $path = preg_replace('/^' . preg_quote(base_path(), '/') . '/', '/', Url::fromRouteMatch($route_match)->setAbsolute(FALSE)->toString()); if (!in_array($route_name, ['system.modules_list']) && strpos($route_name, 'help.page.content_sync') === FALSE && strpos($path, '/content') === FALSE) { return NULL; } /** @var \Drupal\content_sync\ContentSyncHelpManagerInterface $help_manager */ $help_manager = \Drupal::service('content_sync.help_manager'); if ($route_name == 'help.page.content_sync') { $build = $help_manager->buildIndex(); } else { $build = $help_manager->buildHelp($route_name, $route_match); } if ($build) { $renderer = \Drupal::service('renderer'); $config = \Drupal::config('content_sync.settings'); $renderer->addCacheableDependency($build, $config); return $build; } else { return NULL; } switch ($route_name) { case 'help.page.content_sync': $output = ''; $output .= '<h3>' . t('About') . '</h3>'; $output .= '<p>' . 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 <a href=":url">online documentation for the Content Synchronization module</a>.', [':url' => 'https://www.drupal.org/project/content_sync']) . '</p>'; $output .= '<h3>' . t('Uses') . '</h3>'; $output .= '<dl>'; $output .= '<dt>' . t('Exporting the full content') . '</dt>'; $output .= '<dd>' . t('You can create and download an archive consisting of all your site\'s content exported as <em>*.yml</em> files on the <a href=":url">Export</a> page.', [':url' => \Drupal::url('content.export_full')]) . '</dd>'; $output .= '<dt>' . t('Importing a full content') . '</dt>'; $output .= '<dd>' . t('You can upload a full site content from an archive file on the <a href=":url">Import</a> page. When importing data from a different environment, the site and import files must have matching configuration values for UUID in the <em>system.site</em> 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')]) . '</dd>'; $output .= '<dt>' . t('Exporting a single content item') . '</dt>'; $output .= '<dd>' . t('You can export a single content item by selecting a <em>Content type</em> and <em>Content name</em> on the <a href=":single-export">Single export</a> page. The content and its corresponding <em>*.yml file name</em> are then displayed on the page for you to copy.', [':single-export' => \Drupal::url('content.export_single')]) . '</dd>'; $output .= '<dt>' . t('Importing a single content item') . '</dt>'; $output .= '<dd>' . t('You can import a single content item by pasting it in YAML format into the form on the <a href=":single-import">Single import</a> page.', [':single-import' => \Drupal::url('content.import_single')]) . '</dd>'; $output .= '<dt>' . t('Synchronizing content') . '</dt>'; $output .= '<dd>' . t('You can review differences between the active content and an imported content archive on the <a href=":synchronize">Synchronize</a> page to ensure that the changes are as expected, before finalizing the import. The <a href=":synchronize">Synchronize</a>Synchronize</a> page also shows content items that would be added or removed.', [':synchronize' => \Drupal::url('content.sync')]) . '</dd>'; $output .= '<dt>' . t('Content logs') . '</dt>'; $output .= '<dd>' . 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 <a href=":content-logs">Logs</a> page.', [':content-logs' => \Drupal::url('content.overview')]) . '</dd>'; $output .= '<dt>' . t('Content synchronization settings') . '</dt>'; $output .= '<dd>' . t('You can set specific settings for the content synchronization behavior as ignore the UUID Site validation and more on the <a href=":settings">Settings</a> page.', [':settings' => \Drupal::url('content.settings')]) . '</dd>'; $output .= '</dl>'; //return $output; case 'content.export_full': $output = ''; $output .= '<p>' . t('Export and download the full content of this site as a gzipped tar file.') . '</p>'; return $output; case 'content.import_full': $output = ''; $output .= '<p>' . t('Upload a full site content archive to the content sync directory to be imported.') . '</p>'; return $output; case 'content.export_single': $output = ''; $output .= '<p>' . t('Choose a content item to display its YAML structure.') . '</p>'; return $output; case 'content.import_single': $output = ''; $output .= '<p>' . t('Import a single content item by pasting its YAML structure into the text field.') . '</p>'; return $output; case 'content.sync': $output = ''; $output .= '<p>' . t('Compare the content uploaded to your content sync directory with the active content before completing the import.') . '</p>'; return $output; case 'content.settings': $output = ''; $output .= '<p>' . t('Set specific settings for the content synchronization behavior.') . '</p>'; return $output; case 'content.overview': $output = ''; $output .= '<p>' . t('chronological list of recorded events containing errors, warnings and operational information of the content import, export and synchronization.') . '</p>'; return $output; } } /** * Implements hook_theme(). */ function content_sync_theme() { $info = [ 'content_sync_help' => [ 'variables' => ['info' => []], ], 'content_sync_message' => [ 'render element' => 'element', ], ]; // 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') { 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'; $disposition = 'attachment; filename="' . $filename . '"'; return [ 'Content-disposition' => $disposition, ]; } return -1; } } /* 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. * * @param string $type * The type of content directory to return. * * @return string * The content directory path. * * @throws \Exception */ function content_sync_get_content_directory($type) { 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'])) { $type = 'staging'; } if (!empty($content_directories[$type])) { return $content_directories[$type]; } // @todo https://www.drupal.org/node/2696103 Throw a more specific exception. // throw new \Exception("The content directory type '$type' does not exist"); \Drupal::messenger()->addError("The content directory type '$type' does not exist"); } /** * Implements hook_entity_update(). * * Keep the content snapshot table synced. */ function content_sync_entity_update(EntityInterface $entity) { // Get submitted values. $entity_type = $entity->getEntityTypeId(); $entity_bundle = $entity->bundle(); $entity_id = $entity->id(); // 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) ->load($entity_id); // 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 ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot'); $activeStorage->cs_write($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); } } /** * Implements hook_entity_insert(). * * Keep the content snapshot table synced. */ function content_sync_entity_insert(EntityInterface $entity) { content_sync_entity_update($entity); } /** * Implements hook_entity_delete(). * * Keep the content snapshot table synced. */ function content_sync_entity_delete(EntityInterface $entity) { // Get submitted values. $entity_type = $entity->getEntityTypeId(); $entity_bundle = $entity->Bundle(); $entity_uuid = $entity->uuid(); // 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 ContentDatabaseStorage(\Drupal::database(), 'cs_db_snapshot'); $activeStorage->cs_delete($name); // Invalidate the CS Cache of the entity. $cache = \Drupal::cache('content')->invalidate($entity_type.".".$entity_bundle.":".$name); } }