You are here

content_sync.module in Content Synchronization 3.0.x

Same filename and directory in other branches
  1. 8.2 content_sync.module
  2. 8 content_sync.module

Allows site administrators to modify content.

File

content_sync.module
View source
<?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);
  }
}