You are here

ConfigurationManagement.php in Configuration Management 7.2

File

lib/Drupal/configuration/Config/ConfigurationManagement.php
View source
<?php

/**
 * @file
 * Definition of Drupal\configuration\Config\ConfigurationManagement.
 */
namespace Drupal\configuration\Config;

use StdClass;
use Drupal\configuration\Config\Configuration;
use Drupal\configuration\Utils\ConfigIteratorSettings;
use Drupal\configuration\Storage\Storage;
class ConfigurationManagement {

  /**
   * Constant used to indicate the api version of Configuration Management.
   */
  const api = '2.0.0';

  /**
   * The stream to use while importing and exporting configurations.
   *
   * @var string
   */
  protected static $stream = 'config://';

  /**
   * Returns TRUE if the minimun
   *
   * @param $minimum
   *   The version of the api of the current configuration.
   *
   * @return  boolean
   *   Rerturns TRUE if the current version of Configuration Management
   *   installed can handle the configuration.
   */
  public static function validApiVersion($config_version) {
    return version_compare($config_version, ConfigurationManagement::api) >= 0;
  }

  /**
   * Returns the current stream used to import and export configurations.
   * Default value is config://
   *
   * @return string
   */
  public static function getStream() {
    $temp_stream = static::$stream;

    // During the install process strema wrappers are to available so this is
    // a work around.
    if (!file_stream_wrapper_get_instance_by_uri($temp_stream)) {
      $temp_stream = variable_get('configuration_config_path', conf_path() . '/files/config');
    }
    return $temp_stream;
  }

  /**
   * Set the stream to use while importing and exporting configurations.
   *
   * @param string $stream
   */
  public static function setStream($stream) {
    static::$stream = $stream;
  }

  /**
   * Returns a Configuration Object of the type specified in the firt part of
   * the $configuration_id.
   *
   * @param  string $configuration_id
   *   A string that identified the configuration and its identifier. e.g
   *   content_type.article
   */
  public static function createConfigurationInstance($configuration_id) {
    list($component_name, $identifier) = explode('.', $configuration_id, 2);
    $handler = static::getConfigurationHandler($component_name);
    if (!empty($handler)) {
      return new $handler($identifier, $component_name);
    }
    else {
      throw new \Exception('There is no configuration handler for: ' . $configuration_id);
    }
  }

  /**
   * Returns a handler that manages the configurations for the given component.
   */
  public static function getConfigurationHandler($component = NULL, $skip_module_checking = FALSE) {
    static $handlers;
    static $map;
    if (!isset($map)) {
      $map = array();
    }
    if (!isset($handlers)) {
      $handlers = module_invoke_all('configuration_handlers');
    }
    foreach ($handlers as $handler) {
      if ($skip_module_checking || $handler::isActive()) {
        foreach ($handler::supportedComponents() as $component_name) {
          $map[$component_name] = $handler;
        }
      }
    }
    if (empty($component)) {
      return $map;
    }
    else {
      if (!empty($map[$component])) {
        return $map[$component];
      }
    }
  }

  /**
   * Returns a list of modules required to import the configurations indicated
   * in $list.
   *
   * @param  array  $modules
   *   The list of modules that are listed in the tracked.inc file.
   */
  public static function discoverRequiredModules($modules) {
    $settings = new ConfigIteratorSettings(array(
      'info' => array(
        'modules' => array(),
        'modules_missing' => array(),
        'modules_to_install' => array(),
      ),
    ));
    $stack = array();
    foreach ($modules as $module) {
      Configuration::getDependentModules($module, $stack);
    }
    $missing = array();
    $to_install = array();
    foreach ($stack as $module_name => $status) {
      if ($status == Configuration::moduleMissing) {
        $missing[] = $module_name;
      }
      elseif ($status == Configuration::moduleToInstall) {
        $to_install[] = $module_name;
      }
    }
    $settings
      ->setInfo('modules_to_install', array_filter(array_unique($to_install)));
    $settings
      ->setInfo('modules_missing', array_filter(array_unique($missing)));
    return $settings;
  }

  /**
   * Includes a record of each configuration tracked in the
   * configuration_tracked table and export the configurations to the DataStore.
   *
   * @param  array   $list
   *   The list of components that have to will be tracked.
   * @param  boolean $track_dependencies
   *   If TRUE, dependencies of each proccessed configuration will be tracked
   *   too.
   * @param  boolean $track_optionals
   *   If TRUE, optionals configurations of each proccessed configuration will
   *   be tracked too.
   * @return ConfigIteratorSettings
   *   An ConfigIteratorSettings object that contains the tracked
   *   configurations.
   */
  public static function startTracking($list = array(), $track_dependencies = TRUE, $track_optionals = TRUE) {
    return static::exportToDataStore($list, $track_dependencies, $track_optionals, TRUE);
  }

  /**
   * Removes a record of each configuration that is not tracked anymore and
   * deletes the configuration file in the DataStore.
   *
   * @param  array   $list
   *   The list of components that have to will be tracked.
   * @param  boolean $track_dependencies
   *   If TRUE, dependencies of each proccessed configuration will not be
   *   tracked anymore.
   * @param  boolean $track_optionals
   *   If TRUE, optionals configurations of each proccessed configuration will
   *   not be tracked anymore.
   * @return ConfigIteratorSettings
   *   An ConfigIteratorSettings object that contains configurations that are
   *   not tracked anymore.
   */
  public static function stopTracking($list = array(), $stop_track_dependencies = TRUE, $stop_track_optionals = TRUE) {
    $excluded = static::excludedConfigurations();
    $settings = new ConfigIteratorSettings(array(
      'build_callback' => 'build',
      'callback' => 'removeConfiguration',
      'process_dependencies' => $stop_track_dependencies,
      'process_optionals' => $stop_track_optionals,
      'settings' => array(
        'excluded' => $excluded,
      ),
      'info' => array(
        'untracked' => array(),
      ),
    ));
    if (Storage::checkFilePermissions('tracked.inc')) {
      foreach ($list as $component) {
        if (in_array($component, $excluded)) {
          continue;
        }
        $config = static::createConfigurationInstance($component);

        // Make sure the object is built before start to iterate on its
        // dependencies.
        $config
          ->setContext($settings);
        $config
          ->build();
        $config
          ->iterate($settings);
      }
      $tracked = static::trackedConfigurations();
      $args = array();
      foreach ($tracked as $component => $list) {
        foreach ($list as $identifier => $hash) {
          $id = $component . '.' . $identifier;
          $args[] = $id;
        }
      }
      static::exportToDataStore($args, TRUE, TRUE, TRUE);
    }
    return $settings;
  }

  /**
   * Loads the configuration from the DataStore into the ActiveStore.
   *
   * @param  array   $list
   *   The list of components that have to will be imported.
   * @param  boolean $import_dependencies
   *   If TRUE, dependencies of each proccessed configuration will be imported
   *   too.
   * @param  boolean $import_optionals
   *   If TRUE, optionals configurations of each proccessed configuration will
   *   be imported too.
   * @param  boolean $start_tracking
   *   If TRUE, after import the configuration, it will be also tracked.
   * @param $source
   *   Optional. An optional path to load configurations.
   * @return ConfigIteratorSettings
   *   An ConfigIteratorSettings object that contains the imported
   *   configurations.
   */
  public static function importToActiveStore($list = array(), $import_dependencies = TRUE, $import_optionals = TRUE, $start_tracking = FALSE, $source = NULL) {
    $excluded = static::excludedConfigurations();
    $settings = new ConfigIteratorSettings(array(
      'build_callback' => 'loadFromStorage',
      'callback' => 'import',
      'process_dependencies' => $import_dependencies,
      'process_optionals' => $import_optionals,
      'settings' => array(
        'start_tracking' => $start_tracking,
        'source' => $source,
        'excluded' => $excluded,
      ),
      'info' => array(
        'imported' => array(),
        'fail' => array(),
        'no_handler' => array(),
      ),
    ));
    module_invoke_all('configuration_pre_import', $settings);
    $handlers = static::getConfigurationHandler();
    foreach ($list as $component) {
      if (in_array($component, $excluded)) {
        continue;
      }
      $part = explode('.', $component, 2);
      if (empty($handlers[$part[0]])) {
        $settings
          ->addInfo('no_handler', $part[0]);
      }
      else {
        $config = static::createConfigurationInstance($component);

        // Make sure the object is built before start to iterate on its
        // dependencies.
        $config
          ->setContext($settings);
        $config
          ->loadFromStorage();
        $config
          ->iterate($settings);
      }
    }
    drupal_flush_all_caches();
    if ($start_tracking) {
      static::exportToDataStore($list, $import_dependencies, $import_optionals, TRUE);
    }
    module_invoke_all('configuration_post_import', $settings);
    return $settings;
  }

  /**
   * Export the configuration from the ActiveStore to the DataStore.
   *
   * @param  array   $list
   *   The list of components that have to will be exported.
   * @param  boolean $import_dependencies
   *   If TRUE, dependencies of each proccessed configuration will be exported
   *   too.
   * @param  boolean $import_optionals
   *   If TRUE, optionals configurations of each proccessed configuration will
   *   be exported too.
   * @param  boolean $star_tracking
   *   If TRUE, after export the configuration, it will be also tracked.
   * @return ConfigIteratorSettings
   *   An ConfigIteratorSettings object that contains the exported
   *   configurations.
   */
  public static function exportToDataStore($list = array(), $export_dependencies = TRUE, $export_optionals = TRUE, $start_tracking = FALSE) {
    $excluded = static::excludedConfigurations();
    $settings = new ConfigIteratorSettings(array(
      'build_callback' => 'build',
      'callback' => 'export',
      'process_dependencies' => $export_dependencies,
      'process_optionals' => $export_optionals,
      'settings' => array(
        'start_tracking' => $start_tracking,
        'excluded' => $excluded,
      ),
      'info' => array(
        'modules' => array(),
        'exported' => array(),
        'hash' => array(),
      ),
    ));
    module_invoke_all('configuration_pre_export', $settings);
    foreach ($list as $component) {
      if (in_array($component, $excluded)) {
        continue;
      }
      $config = static::createConfigurationInstance($component);

      // Make sure the object is built before start to iterate on its
      // dependencies.
      $config
        ->setContext($settings);
      $config
        ->build();
      $config
        ->iterate($settings);
    }

    // Even if we are exporting only a few configurations, all tracked
    // configurations should be considered while creating the list of required
    // modules.
    if ($start_tracking) {
      $modules = array();
      foreach (array_keys(static::trackedConfigurations(FALSE)) as $config_id) {
        $config = static::createConfigurationInstance($config_id);
        $config
          ->build();
        $modules = array_merge($modules, array_keys($config
          ->getRequiredModules()));
      }
      array_merge($modules, $settings
        ->getInfo('modules'));
      static::updateTrackingFile($modules);
    }
    module_invoke_all('configuration_post_export', $settings);
    return $settings;
  }

  /**
   * Returns a list of configurations that are currently being tracked.
   *
   * @param boolean $tree
   *   A boolean flag to indicate if the tracked configuration have to be
   *   organized in a tree structure.
   *
   * @return array
   *   If $tree is TRUE the returned array is structured in the this way:
   *
   *   @code
   *     array(
   *       'content_type' => array(
   *         'article' => array(
   *           'hash' => 'c08223610b3eb55161d4539c704e40989dcf3e72',
   *           'name' => 'Article',
   *         ),
   *         'page' => array(
   *           'hash' => '5161d4539c704e40989dcf3e72c08223610b3eb5',
   *           'name' => 'Page',
   *         ),
   *       ),
   *       'variable' => array(
   *         'site_name' => array(
   *           'hash' => '539c704e40989dcf35161d4e72c08223610b3eb5',
   *           'name' => 'site_name',
   *         ),
   *       )
   *     );
   *
   *     If $tree is FALSE the returned array is structured in the this way:
   *
   *     array(
   *       'content_type.article' => array(
   *         'hash' => 'c08223610b3eb55161d4539c704e40989dcf3e72',
   *         'name' => 'Article',
   *       ),
   *       'content_type.page' => array(
   *         'hash' => '5161d4539c704e40989dcf3e72c08223610b3eb5',
   *         'name' => 'Page',
   *       ),
   *       'variable.site_name' => array(
   *         'hash' =>'539c704e40989dcf35161d4e72c08223610b3eb5',
   *         'name' => 'site_name'
   *       )
   *     );
   *   @endcode
   */
  public static function trackedConfigurations($tree = TRUE) {
    $excluded = static::excludedConfigurations();
    $tracked = db_select('configuration_tracked', 'ct')
      ->fields('ct', array(
      'component',
      'identifier',
      'hash',
    ))
      ->execute()
      ->fetchAll();
    $return = array();

    // Prepare the array to return
    $handlers = static::getConfigurationHandler();
    if ($tree) {
      foreach ($handlers as $component => $handler) {
        $return[$component] = array();
      }
    }
    foreach ($tracked as $object) {
      $id = $object->component . '.' . $object->identifier;
      if (in_array($id, $excluded)) {
        continue;
      }

      // Only return tracked Configurations for supported components.
      if (isset($handlers[$object->component])) {
        $all_identifiers = $handlers[$object->component]::getAllIdentifiersCached($object->component);
        if (empty($all_identifiers[$object->identifier])) {
          $name = $object->identifier;
        }
        else {
          $name = $all_identifiers[$object->identifier];
        }
        if ($tree) {
          $return[$object->component][$object->identifier] = array(
            'hash' => $object->hash,
            'name' => $name,
          );
        }
        else {
          $return[$object->component . '.' . $object->identifier] = array(
            'hash' => $object->hash,
            'name' => $name,
          );
        }
      }
    }
    return $return;
  }

  /**
   * Returns a list of configurations that are not currently being tracked.
   *
   * @return array
   */
  public static function nonTrackedConfigurations() {
    $excluded = static::excludedConfigurations();
    $handlers = static::getConfigurationHandler();
    $tracked = static::trackedConfigurations();
    $non_tracked = array();
    foreach (array_keys($handlers) as $component) {
      $handler = static::getConfigurationHandler($component);
      $identifiers = $handler::getAllIdentifiersCached($component);
      foreach ($identifiers as $identifier => $identifier_human_name) {
        if (empty($tracked[$component]) || empty($tracked[$component][$identifier])) {
          $id = $component . '.' . $identifier;
          if (in_array($id, $excluded)) {
            continue;
          }
          $non_tracked[$component][$identifier] = array(
            'id' => $id,
            'name' => $identifier_human_name,
          );
        }
      }
    }
    return $non_tracked;
  }

  /**
   * Return a list of configurations that will not be proccessed by
   * configuration management.
   */
  public static function excludedConfigurations() {
    $list = variable_get('configuration_exclude_configurations', '');
    $list = explode("\n", $list);
    $list = array_map('trim', $list);
    $list = array_filter($list, 'strlen');
    return $list;
  }

  /**
   * Returns a list of configurations available in the site without distinction
   * of tracked and not tracked.
   *
   * @return array
   */
  public static function allConfigurations() {
    $excluded = static::excludedConfigurations();
    $handlers = static::getConfigurationHandler();
    $tracked = static::trackedConfigurations();
    $all = array();
    foreach ($handlers as $component => $handler) {
      $identifiers = $handler::getAllIdentifiersCached($component);
      foreach ($identifiers as $identifier => $identifier_human_name) {
        $id = $component . '.' . $identifier;
        if (in_array($id, $excluded)) {
          continue;
        }
        if (!empty($tracked[$component][$identifier])) {

          // Set the hash for the tracked configurations
          $all[$component][$identifier] = array(
            'hash' => $tracked[$component][$identifier],
            'name' => $identifiers[$identifier],
          );
        }
        else {

          // Set FALSE for the non tracked configurations
          $all[$component][$identifier] = array(
            'hash' => FALSE,
            'name' => $identifiers[$identifier],
          );
        }
      }
    }
    return $all;
  }

  /**
   * This function save into config://tracked.inc file the configurations that
   * are currently tracked.
   */
  public static function updateTrackingFile($modules = array()) {
    $tracked = static::trackedConfigurations();
    $file = array();
    foreach ($tracked as $component => $list) {
      foreach ($list as $identifier => $info) {
        $file[$component . '.' . $identifier] = $info['hash'];
      }
    }
    $file_content = "<?php\n\n";
    $file_content .= "// This file contains the current being tracked configurations.\n\n";
    $file_content .= '$tracked = ' . var_export($file, TRUE) . ";\n";
    $file_content .= "\n\n// The following modules are required to run the configurations of this file.\n\n";
    $file_content .= "\$modules = array(\n";
    foreach (array_unique($modules) as $module) {
      $file_content .= "  '{$module}',\n";
    }
    $file_content .= ");\n";
    if (Storage::checkFilePermissions('tracked.inc')) {
      file_put_contents(static::getStream() . '/tracked.inc', $file_content);
    }
  }

  /**
   * Returns a list of files that are listed in the config://tracked.inc file.
   */
  public static function readTrackingFile() {
    if (file_exists(static::getStream() . '/tracked.inc')) {
      $file_content = drupal_substr(file_get_contents(static::getStream() . '/tracked.inc'), 6);
      @eval($file_content);
      return array(
        'tracked' => $tracked,
        'modules' => $modules,
      );
    }
    return array();
  }

  /**
   * Import configurations from a Tar file.
   *
   * @param  StdClass $file
   *   A file object.
   * @param  boolean $start_tracking
   *   If TRUE, all the configurations provided in the Tar file will be imported
   *   and automatically tracked.
   *
   * @return ConfigIteratorSettings
   *   An ConfigIteratorSettings object that contains the imported
   *   configurations.
   */
  public static function importToActiveStoreFromTar($uri, $start_tracking = FALSE) {
    $path = 'temporary://';
    $archive = archiver_get_archiver($uri);
    $files = $archive
      ->listContents();
    foreach ($files as $filename) {
      if (is_file($path . $filename)) {
        file_unmanaged_delete($path . $filename);
      }
    }
    $config_temp_path = 'temporary://' . 'config-tmp-' . time();
    $archive
      ->extract(drupal_realpath($config_temp_path));
    $file_content = drupal_substr(file_get_contents($config_temp_path . '/configuration/configurations.inc'), 6);
    @eval($file_content);
    $source = $config_temp_path . '/configuration/';
    $modules_results = ConfigurationManagement::discoverRequiredModules($modules);
    $missing_modules = $modules_results
      ->getInfo('modules_missing');
    $error = FALSE;
    if (!empty($missing_modules)) {
      drupal_set_message(t('Configurations cannot be synchronized because the following modules are not available to install: %modules', array(
        '%modules' => implode(', ', $missing_modules),
      )), 'error');
      return $modules_results;
    }
    else {
      $modules_to_install = $modules_results
        ->getInfo('modules_to_install');
      if (!empty($modules_to_install)) {
        module_enable($modules_to_install, TRUE);
        drupal_set_message(t('The following modules have been enabled: %modules', array(
          '%modules' => implode(', ', $modules_to_install),
        )));
        drupal_flush_all_caches();
      }
    }
    $settings = static::importToActiveStore($configurations, FALSE, FALSE, $start_tracking, $source);
    static::deteleTempConfigDir($config_temp_path);
    return $settings;
  }

  /**
   * Download the entire configuration packaged up into tar file
   */
  public static function exportAsTar($list = array(), $export_dependencies = TRUE, $export_optionals = TRUE) {
    $excluded = static::excludedConfigurations();
    $settings = new ConfigIteratorSettings(array(
      'build_callback' => 'build',
      'callback' => 'printRaw',
      'process_dependencies' => $export_dependencies,
      'process_optionals' => $export_optionals,
      'info' => array(
        'exported' => array(),
        'exported_files' => array(),
        'hash' => array(),
        'modules' => array(),
        'excluded' => $excluded,
      ),
      'settings' => array(
        'format' => 'tar',
        'tar_folder' => 'configuration',
      ),
    ));
    module_invoke_all('configuration_pre_export', $settings);
    $filename = 'configuration.' . time() . '.tar';

    // Clear out output buffer to remove any garbage from tar output.
    if (ob_get_level()) {
      ob_end_clean();
    }
    drupal_add_http_header('Content-type', 'application/x-tar');
    drupal_add_http_header('Content-Disposition', 'attachment; filename="' . $filename . '"');
    drupal_send_headers();
    foreach ($list as $component) {
      if (in_array($component, $excluded)) {
        continue;
      }
      $config = static::createConfigurationInstance($component);

      // Make sure the object is built before start to iterate on its
      // dependencies.
      $config
        ->setContext($settings);
      $config
        ->build();
      $config
        ->iterate($settings);
    }
    $exported = $settings
      ->getInfo('exported');
    module_invoke_all('configuration_post_export', $settings);
    $file_content = "<?php\n\n";
    $file_content .= "// This file contains the list of configurations contained in this package.\n\n";
    $file_content .= "\$configurations = array(\n";
    foreach ($exported as $config) {
      $file_content .= "  '{$config}',\n";
    }
    $file_content .= ");\n\n";
    $file_content .= "\$modules = array(\n";
    foreach (array_unique($settings
      ->getInfo('modules')) as $module) {
      $file_content .= "  '{$module}',\n";
    }
    $file_content .= ");\n";
    print static::createTarContent($settings
      ->getSetting('tar_folder') . "/configurations.inc", $file_content);
    print pack("a1024", "");
    exit;
  }

  /**
   * Download the entire configuration packaged up into tar file
   */
  public static function rawDepdendencyInfo($list = array(), $include_dependencies = TRUE, $include_optionals = TRUE) {
    $excluded = static::excludedConfigurations();
    $settings = new ConfigIteratorSettings(array(
      'build_callback' => 'build',
      'callback' => 'printRaw',
      'process_dependencies' => $include_dependencies,
      'process_optionals' => $include_optionals,
      'info' => array(
        'exported' => array(),
        'exported_files' => array(),
        'hash' => array(),
      ),
      'settings' => array(
        'print' => array(
          'optionals' => $include_optionals,
          'dependencies' => $include_dependencies,
        ),
        'excluded' => $excluded,
      ),
    ));

    // Clear out output buffer to remove any garbage from tar output.
    if (ob_get_level()) {
      ob_end_clean();
    }
    drupal_add_http_header('Content-type', 'application/json');
    print "{\n";
    foreach ($list as $component) {
      if (in_array($component, $excluded)) {
        continue;
      }
      $config = static::createConfigurationInstance($component);

      // Make sure the object is built before start to iterate on its
      // dependencies.
      $config
        ->setContext($settings);
      $config
        ->build();
      $config
        ->iterate($settings);
    }
    print '"null": "null"';
    print "}\n";
    exit;
  }
  protected static function deteleTempConfigDir($dir, $force = FALSE) {

    // Allow to delete symlinks even if the target doesn't exist.
    if (!is_link($dir) && !file_exists($dir)) {
      return TRUE;
    }
    if (!is_dir($dir)) {
      if ($force) {

        // Force deletion of items with readonly flag.
        @chmod($dir, 0777);
      }
      return unlink($dir);
    }
    foreach (scandir($dir) as $item) {
      if ($item == '.' || $item == '..') {
        continue;
      }
      if ($force) {
        @chmod($dir, 0777);
      }
      if (!static::deteleTempConfigDir($dir . '/' . $item, $force)) {
        return FALSE;
      }
    }
    if ($force) {

      // Force deletion of items with readonly flag.
      @chmod($dir, 0777);
    }
    return rmdir($dir);
  }

  /**
   * Tar creation function. Written by dmitrig01.
   *
   * @param $name
   *   Filename of the file to be tarred.
   * @param $contents
   *   String contents of the file.
   *
   * @return
   *   A string of the tar file contents.
   */
  public static function createTarContent($name, $contents) {
    $tar = '';
    $binary_data_first = pack("a100a8a8a8a12A12", $name, '100644 ', '   765 ', '   765 ', sprintf("%11s ", decoct(drupal_strlen($contents))), sprintf("%11s", decoct(REQUEST_TIME)));
    $binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", '', '', '', '', '', '', '', '', '', '');
    $checksum = 0;
    for ($i = 0; $i < 148; $i++) {
      $checksum += ord(drupal_substr($binary_data_first, $i, 1));
    }
    for ($i = 148; $i < 156; $i++) {
      $checksum += ord(' ');
    }
    for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
      $checksum += ord(drupal_substr($binary_data_last, $j, 1));
    }
    $tar .= $binary_data_first;
    $tar .= pack("a8", sprintf("%6s ", decoct($checksum)));
    $tar .= $binary_data_last;
    $buffer = str_split($contents, 512);
    foreach ($buffer as $item) {
      $tar .= pack("a512", $item);
    }
    return $tar;
  }

}

Classes