content_deploy-1.0.1/content_deploy.module

content_deploy.module
<?php

/**
 * @file
 * Allows site administrators to modify content.
 */

use Drupal\content_deploy\Content\ContentDatabaseStorage;
use Drupal\content_deploy\Utility\ContentDeployNodeExportLogsHelper;
use Drupal\Core\Archiver\ArchiveTar;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Url;
use Drupal\content_deploy\Utility\ContentDeployNodesWithDependencyHelper;

/**
 * Implements hook_help().
 */
function content_deploy_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_deploy') === FALSE && strpos($path, '/content') === FALSE) {
    return NULL;
  }

  /** @var \Drupal\content_deploy\ContentSyncHelpManagerInterface $help_manager */
  $help_manager = \Drupal::service('content_deploy.help_manager');
  if ($route_name == 'help.page.content_deploy') {
    $build = $help_manager->buildIndex();
  }
  else {
    $build = $help_manager->buildHelp($route_name, $route_match);
  }

  if ($build) {
    $renderer = \Drupal::service('renderer');
    $config = \Drupal::config('content_deploy.settings');
    $renderer->addCacheableDependency($build, $config);
    return $build;
  }
  else {
    return NULL;
  }

  switch ($route_name) {
    case 'help.page.content_deploy':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Content Deploy 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 Deploy module</a>.', [':url' => 'https://www.drupal.org/']) . '</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 Deploy settings') . '</dt>';
      $output .= '<dd>' . t('You can set specific settings for the Content Deploy behaviour 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 Deploy 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 Deploy 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 Deploy behaviour.') . '</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_deploy_theme() {
  $info = [
    'content_deploy_help' => [
      'variables' => ['info' => []],
    ],
    'content_deploy_message' => [
      'render element' => 'element',
    ],
  ];

  // Since any rendering of a content_deploy is going to require
  // 'content_deploy.theme.inc' we are going to just add it to every template.
  foreach ($info as &$template) {
    $template['file'] = 'includes/content_deploy.theme.inc';
  }
  return $info;
}

/**
 * Implements hook_file_download().
 */
function content_file_download($uri) {
  $scheme = file_uri_scheme($uri);
  $target = file_uri_target($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_deploy_get_content_directory($type) {
  global $content_directories;

  // @todo Remove fallback in Drupal 9. https://www.drupal.org/node/2574943
  /*if ($type == content_deploy_DIRECTORY &&
  !isset($content_directories[content_deploy_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_set_message(t("The content directory type '$type' does not exist"), 'error');
  \Drupal::messenger()->addError("The content directory type '$type' does not exist");
}

/**
 * Implements hook_entity_update().
 *
 * Keep the content snapshot table synced.
 */
function content_deploy_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_deploy.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_deploy_entity_insert(EntityInterface $entity) {
  content_deploy_entity_update($entity);
}

/**
 * Implements hook_entity_delete().
 *
 * Keep the content snapshot table synced.
 */
function content_deploy_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);
  }
}

/**
 * Implements hook_cron().
 */
function content_deploy_cron() {

  // Auto deployment.
  _auto_content_deployment();
}

/**
 * Export the nodes selected for auto deployment.
 */
function _auto_content_deployment() {
  $currTimestamp = \Drupal::time()->getCurrentTime();
  $autoDeployQuery = \Drupal::database()->select('cd_auto_nodes_export', 'ane');
  $autoDeployQuery->fields('ane', ['node_uuid', 'user_uuid', 'node_target_status', 'target_environment']);
  $autoDeployQuery->condition('ane.deployment_time', $currTimestamp, '<=');
  $autoDeployQuery->condition('ane.deployment_status', 0, '=');
  $autoDeployQuery->orderBy('ane.deployment_time', 'ASC');
  $autoDeployNodes = $autoDeployQuery->execute()->fetchAll();
  
  if (!empty($autoDeployNodes)) {

      $file_system = \Drupal::service('file_system');
      $contentExporter = \Drupal::service('content_deploy.exporter');

      $deploymentArchives = [];
      $foundEnvs = [];
      $exportedFiles = [];
      
      foreach ($autoDeployNodes as $key => $singleEntity) {

        $entityUUID = $singleEntity->node_uuid;
        $exportOwnerUUID = $singleEntity->user_uuid;
        $nodeTargetStatus = $singleEntity->node_target_status;
        $targetEnv = $singleEntity->target_environment;
        $archiveName = "$targetEnv-auto-deploy-$currTimestamp";

        // To generate the diff archiver for diff env.
        if (!in_array($targetEnv, $foundEnvs)) {

          $archiveFilepath = \Drupal::service('file_system')->getTempDirectory()."/$archiveName.tar.gz";
          if (file_exists($archiveFilepath)) {
            $file_system->delete($archiveFilepath);
          }
          ${'archiver_'.$targetEnv} = new ArchiveTar($archiveFilepath, 'gz');
          $foundEnvs[] = $targetEnv;
          $deploymentArchives[] = $archiveFilepath;

        }
        // Move to next element if archiver not found.
        if (!isset(${'archiver_'.$targetEnv}) || empty(${'archiver_'.$targetEnv})) {
          \Drupal::logger('content_deploy')->error(t('Target Environment not found for the auto deployment of node with UUID : '.$entityUUID));
          continue;
        }
        $archiver = ${'archiver_'.$targetEnv};

        $entity = \Drupal::service('entity.repository')
          ->loadEntityByUuid('node', $entityUUID);
        $entity_type = $entity->getEntityTypeId();
        $entity_bundle = $entity->bundle();
        
        // Set Entity publish/un-publish status
        if ($nodeTargetStatus !== NULL && $nodeTargetStatus === 0  || $nodeTargetStatus == 0) {
          $entity->setPublished(FALSE);
          $entity->setUnpublished();
        }
        if ($nodeTargetStatus === 1 || $nodeTargetStatus == 1) {
          $entity->setPublished();
        }
        
        // Generate the YAML file.
        $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
        if ($entity->hasTranslation($language)) {
          $entity = $entity->getTranslation($language);
        }
        $defaultOwnerUid = 0;
        if ($entity->getOwnerId()) {
          $defaultOwnerUid = $entity->getOwnerId();
        }
        $exported_entity = $contentExporter->exportEntity($entity, []);

        // Create the name.
        $nodeFileName = $entity_type . "." . $entity_bundle . "." . $entity->uuid();

        // get the node dependencies.
        if (!empty($exported_entity)) {
          $decodedEntity = \Drupal\Component\Serialization\Yaml::decode($exported_entity);
          if (isset($decodedEntity['_content_deploy']['entity_dependencies']) && !empty($decodedEntity['_content_deploy']['entity_dependencies'])) {
            $entityDependencies = $decodedEntity['_content_deploy']['entity_dependencies'];
            // Export entity_dependencies recursively.
            $helperResponse = ContentDeployNodesWithDependencyHelper::export_entity_dependencies($entityDependencies, $archiver, $defaultOwnerUid, $exportedFiles, $nodeTargetStatus);
            if (!empty($helperResponse) && isset($helperResponse['exportedFiles']) && !empty($helperResponse['exportedFiles'])) {
              $exportedFiles = $helperResponse['exportedFiles'];
            }
          }
          // get the parent node.
          $exportedFiles[] = $entity->getEntityTypeId() . '.' . $entity->uuid();
          ContentDeployNodeExportLogsHelper::createLog($entity->uuid(), $exportOwnerUUID);
          $archiver->addString("$archiveName/entities/$entity_type/$entity_bundle/$nodeFileName.yml", $exported_entity);
        }
      }

      if (!empty($deploymentArchives)) {

        foreach ($deploymentArchives as $key => $singleArchivePath) {
          $targetEnv = basename($singleArchivePath);
          $targetEnv = explode('-', $targetEnv);
          $targetEnv = $targetEnv[0];

          $sshUsername = \Drupal::config('content_deploy.settings')->get('content_deploy.' . $targetEnv . '_ssh_username');
          $sshHost = \Drupal::config('content_deploy.settings')->get('content_deploy.' . $targetEnv . '_ssh_host');
          $sshAuthenticationType = \Drupal::config('content_deploy.settings')->get('content_deploy.' . $targetEnv . '_authentication_type');
          $sshPort = \Drupal::config('content_deploy.settings')->get('content_deploy.' . $targetEnv . '_ssh_port');
          if (empty($sshPort)) {
            $sshPort = 22;
          }

          // Our destination site's dir where files should be placed.
          $remoteDir = \Drupal::config('content_deploy.settings')->get('content_deploy.' . $targetEnv . '_auto_deploy_dir');
          $remoteDir = rtrim($remoteDir, '/');
          if (empty($remoteDir)) {
            \Drupal::logger('content_deploy')->error(t('Auto deploy directory is empty for environment : '.$targetEnv));
            continue;
          }

          $realFilePath = \Drupal::service('file_system')->realpath($singleArchivePath);
          $fileBasename = basename($singleArchivePath);

          if ($sshAuthenticationType == 'key') {

            $sshPubKey = \Drupal::config('content_deploy.settings')->get('content_deploy.' . $targetEnv . '_ssh_public_key');
            $sshPrvKey = \Drupal::config('content_deploy.settings')->get('content_deploy.' . $targetEnv . '_ssh_private_key');

            if (empty($sshPubKey) || empty($sshPrvKey)) {
              \Drupal::logger('content_deploy')->error(t($targetEnv.' : SSH Private key path or Public key path is empty.'));
              continue;
            }

            $rsyncCmd = 'rsync -rltDvPh -e "ssh -p '.$sshPort.' -i '.$sshPrvKey.'" '.$realFilePath.' '.$sshUsername.'@'.$sshHost.':'.$remoteDir;
            $output = NULL;
            $retval = NULL;
            exec($rsyncCmd, $output, $retval);
          }
          else {

            if (!function_exists("ssh2_connect")) {
              \Drupal::logger('content_deploy')->error(t($targetEnv.' : SSH2 PECL extension is not installed/enabled in PHP to maintain the connection using SSH with password. Please install or enable it.'));
              continue;
            }

            $connection = NULL;

            // Authenticate using password
            $sshPass = base64_decode(\Drupal::config('content_deploy.settings')->get('content_deploy.' . $targetEnv . '_ssh_password'));

            // Make our connection
            $connection = @ssh2_connect($sshHost, $sshPort);

            if (!@ssh2_auth_password($connection, $sshUsername, $sshPass)){
              \Drupal::logger('content_deploy')->error(t($targetEnv.' : Unable to connect with SSH using password.'));
              continue;
            }

            if (!$sftp = @ssh2_sftp($connection)) {
              \Drupal::logger('content_deploy')->error(t($targetEnv.' : Unable to maintain SFTP connection using password.'));
              continue;
            }

            @ssh2_scp_send($connection, "$realFilePath", "$remoteDir/$fileBasename");
            $connection = null; unset($connection);
          }

          $destSite = \Drupal::config('content_deploy.settings')->get('content_deploy.' . $targetEnv . '_env_url');
          $destSite = parse_url($destSite);
          $destSiteHost = $destSite['host'];

          // create a new cURL resource to hit the import process on destination site.
          $ch = curl_init();
          // set URL and other appropriate options
          curl_setopt($ch, CURLOPT_URL, "$destSiteHost/content-deploy/import-content-files?import_files=import_migrated_files");
          curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
          curl_setopt($ch, CURLOPT_HEADER, 0);
          // grab URL and pass it to the browser
          $output = curl_exec($ch);
          $output = json_decode($output, TRUE);

          // close cURL resource, and free up system resources
          curl_close($ch);
          \Drupal::logger('content_deploy')->info("Auto Deployment Import process output from $targetEnv site : <br><pre>".print_r($output, TRUE)."</pre>");
        }
      }

      \Drupal::database()->update('cd_auto_nodes_export')
        ->fields(['deployment_status' => 1])
        ->condition('deployment_time', $currTimestamp, '<=')
        ->condition('deployment_status', 0, '=')
        ->execute();

      \Drupal::logger('content_deploy')->info(t('Auto deployment of nodes completed. Please check logs if there is any error related to deployment.'));

  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc