You are here

composer_manager.module in Composer Manager 7

Provides consolidated management of third-party Composer-compatible packages required by contributed modules.

File

composer_manager.module
View source
<?php

/**
 * @file
 * Provides consolidated management of third-party Composer-compatible packages
 * required by contributed modules.
 */

/**
 * Implements hook_boot().
 */
function composer_manager_boot() {
  try {
    composer_manager_register_autoloader();
  } catch (\RuntimeException $e) {
    if (!drupal_is_cli()) {
      watchdog_exception('composer_manager', $e);
    }
  }
}

/**
 * Implements hook_module_implements_alter().
 */
function composer_manager_module_implements_alter(&$implementations, $hook) {

  // We want to place composer_manager first for boot to ensure that the
  // autoloader is registered before other modules.
  if ($hook == 'boot' && isset($implementations['composer_manager'])) {
    $impl = $implementations['composer_manager'];
    unset($implementations['composer_manager']);
    $implementations = array(
      'composer_manager' => $impl,
    ) + $implementations;
  }
}

/**
 * Implements hook_menu().
 */
function composer_manager_menu() {
  $items = array();
  $items['admin/config/system/composer-manager'] = array(
    'title' => 'Composer Manager',
    'description' => 'View the status of packages managed by Composer and configure the location of the composer.json file and verdor directory.',
    'page callback' => 'composer_manager_packages_page',
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'composer_manager.admin.inc',
  );
  $items['admin/config/system/composer-manager/packages'] = array(
    'title' => 'Packages',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/config/system/composer-manager/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'composer_manager_settings_form',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'composer_manager.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Implements hook_modules_enabled().
 *
 * @see composer_manager_write_if_changed()
 */
function composer_manager_modules_enabled($modules) {
  composer_manager_write_if_changed($modules);
}

/**
 * Implements hook_modules_disabled().
 *
 * @see composer_manager_write_if_changed()
 */
function composer_manager_modules_disabled($modules) {
  composer_manager_write_if_changed($modules);
}

/**
 * Writes the composer.json file if one of the enabled / disabled modules
 * has a composer.json file.
 *
 * This is a primitive check to ensure that the composer.json file is built only
 * when it has changes. A static boolean is also set flagging whether one or
 * more modules being acted on contains a composer.json file, which is used in
 * Drush hooks.
 *
 * @param array $modules
 *   The enabled / disabled modules being scanned for a composer.json file.
 */
function composer_manager_write_if_changed(array $modules) {
  $changed =& drupal_static(__FUNCTION__, FALSE);
  if (variable_get('composer_manager_autobuild_file', 1)) {
    if (composer_manager_packages_have_changed($modules)) {
      $changed = TRUE;
      composer_manager_write_file();
    }
  }
}

/**
 * Returns TRUE if at least one passed modules has a composer.json file or
 * implements hook_composer_json_alter(). These conditions indicate that the
 * consolidated composer.json file has likely changed.
 *
 * @param array $modules
 *   The list of modules being scanned for composer.json files, usually a list
 *   of modules that were installed or uninstalled.
 *
 * @return bool
 */
function composer_manager_packages_have_changed(array $modules) {
  foreach ($modules as $module) {

    // Check if the module has a composer.json file.
    $module_path = drupal_get_path('module', $module);
    if (file_exists($module_path . '/composer.json')) {
      return TRUE;
    }

    // Check if the module implements hook_composer_json_alter().
    if (module_hook($module, 'composer_json_alter')) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Returns TRUE if at least one module has a composer.json file.
 *
 * @param array $modules
 *   An array of modules being checked.
 *
 * @return boolean
 *
 * @deprecated since 7.x-1.6 https://www.drupal.org/node/2297413
 *
 * @see composer_manager_packages_have_changed()
 */
function composer_manager_has_composer_file(array $modules) {
  foreach ($modules as $module) {
    $module_path = drupal_get_path('module', $module);
    if (file_exists($module_path . '/composer.json')) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Registers the autoloader for all third-party packages.
 *
 * @return \Composer\Autoload\ClassLoader()
 *   The ClassLoader instance.
 */
function composer_manager_register_autoloader() {
  static $registered = FALSE;
  $autoloader = composer_manager_vendor_dir() . '/autoload.php';
  if (!$registered) {
    if (!file_exists($autoloader)) {
      $message = t('Autoloader not found: @file', array(
        '@file' => $autoloader,
      ));
      throw new \RuntimeException($message);
    }
    $registered = TRUE;
  }
  return require $autoloader;
}

/**
 * Writes the consolidated composer.json file for all modules that require
 * third-party packages managed by Composer.
 *
 * @return bool
 */
function composer_manager_write_file() {

  // Ensure only one process runs at a time. 10 seconds is more than enough.
  // It is rare that a conflict will happen, and it isn't mission critical that
  // we wait for the lock to release and regenerate the file again.
  if (!lock_acquire(__FUNCTION__, 10)) {
    return FALSE;
  }
  require_once __DIR__ . '/composer_manager.writer.inc';
  try {
    $data = composer_manager_fetch_data();
    $json = composer_manager_build_json($data);
    if ($json) {
      $dir_uri = composer_manager_file_dir();
      composer_manager_put_file($dir_uri, $json);
    }
    $success = TRUE;
  } catch (\RuntimeException $e) {
    $success = FALSE;
    if (user_access('administer site configuration')) {
      drupal_set_message(t('Error writing composer.json file'), 'error');
    }
    watchdog_exception('composer_manager', $e);
  }
  lock_release(__FUNCTION__);
  return $success;
}

/**
 * Returns the path to the vendor directory.
 *
 * @return string
 */
function composer_manager_vendor_dir() {

  // Don't break sites that upgraded from <= 7.x-1.0-beta5.
  composer_manager_beta5_compatibility();
  $vendor_dir = variable_get('composer_manager_vendor_dir', 'sites/all/vendor');
  $is_absolute = 0 === strpos($vendor_dir, '/');
  if (!$is_absolute) {
    $vendor_dir = DRUPAL_ROOT . '/' . $vendor_dir;
  }
  return $vendor_dir;
}

/**
 * Ensures that sites don't break after upgrading from <= 7.x-1.0-beta5.
 *
 * In versions <= 7.x-1.0-beta5, the default vendor directory was defined as
 * "sites/all/libraries/composer'". In versions > 7.x-1.0-beta5, the default
 * vendor directory is "sites/all/vendor". This check ensures that the vendor
 * directory doesn't unexpectedly change for people who upgraded the module from
 * an earlier version and haven't changed any of the default settings.
 *
 * Composer Manager explicitly sets the "composer_manager_vendor_dir" to
 * "sites/all/vendor" during installation, so we know that the module was
 * upgraded if the variable isn't set.
 */
function composer_manager_beta5_compatibility() {
  if (NULL === ($default = variable_get('composer_manager_vendor_dir', NULL))) {

    // Set the variable to the old default so it doesn't change unexpectedly.
    variable_set('composer_manager_vendor_dir', 'sites/all/libraries/composer');
  }
}

/**
 * Return the URI to the composer.lock file.
 *
 * @return string|bool
 *   The URI to the composer.lock file, usually in public://. Returns FALSE if
 *   the lock file cannot be read.
 */
function composer_manager_lock_file() {
  $dir_uri = composer_manager_file_dir();
  $lock_file = $dir_uri . '/composer.lock';

  // Make sure we can read composer.lock and it's valid.
  if (file_exists($lock_file) && is_readable($lock_file)) {
    $json = file_get_contents($lock_file);
    $decoded = drupal_json_decode($json);
    if (!is_array($decoded)) {
      return FALSE;
    }
  }
  else {
    return FALSE;
  }
  return $lock_file;
}

/**
 * Returns the realpath to the Composer file directory.
 *
 * @return string
 *
 * @throws \RuntimeException
 */
function composer_manager_file_dir() {
  module_load_include('inc', 'composer_manager', 'composer_manager.writer');
  $scheme = file_default_scheme();

  // Composer can only be run on a locally mounted file system. If the scheme is
  // set to something different like S3, we fall back to the public scheme.
  if (!in_array($scheme, array(
    'public',
    'private',
  ))) {
    $scheme = 'public';
  }
  $dir_uri = variable_get('composer_manager_file_dir', $scheme . '://composer');
  composer_manager_prepare_directory($dir_uri);
  if (!($realpath = drupal_realpath($dir_uri))) {
    throw new \RuntimeException(t('Error resolving directory: @dir', array(
      '@dir' => $dir_uri,
    )));
  }
  return $realpath;
}

Functions

Namesort descending Description
composer_manager_beta5_compatibility Ensures that sites don't break after upgrading from <= 7.x-1.0-beta5.
composer_manager_boot Implements hook_boot().
composer_manager_file_dir Returns the realpath to the Composer file directory.
composer_manager_has_composer_file Deprecated Returns TRUE if at least one module has a composer.json file.
composer_manager_lock_file Return the URI to the composer.lock file.
composer_manager_menu Implements hook_menu().
composer_manager_modules_disabled Implements hook_modules_disabled().
composer_manager_modules_enabled Implements hook_modules_enabled().
composer_manager_module_implements_alter Implements hook_module_implements_alter().
composer_manager_packages_have_changed Returns TRUE if at least one passed modules has a composer.json file or implements hook_composer_json_alter(). These conditions indicate that the consolidated composer.json file has likely changed.
composer_manager_register_autoloader Registers the autoloader for all third-party packages.
composer_manager_vendor_dir Returns the path to the vendor directory.
composer_manager_write_file Writes the consolidated composer.json file for all modules that require third-party packages managed by Composer.
composer_manager_write_if_changed Writes the composer.json file if one of the enabled / disabled modules has a composer.json file.