You are here

apps.installer.inc in Apps 7

installer.inc hold all of the function used to download apps and thier deps

File

apps.installer.inc
View source
<?php

/**
 * @file
 * installer.inc hold all of the function used to download apps and thier deps
 */

/**
 * Download all modules and library to a temp location.
 *
 * Find all downloadables needed (which we do not have) and sets a batch for
 * download. Then proccess that batch and returns to the current
 * apps_install_next page
 *
 * @param string $app
 *   The app modules to download
 */
function apps_download_apps($app) {
  $download_batch = apps_download_apps_batch(array(
    $app,
  ));
  batch_set($download_batch);
  batch_process($_SESSION['apps_install_next']);
}

/**
 * Make the batch for downloading modules for app.
 *
 * Split out for use in apps.profile.inc
 */
function apps_download_apps_batch($apps) {
  $download_commands = array();
  foreach ($apps as $app) {
    $downloads = apps_download_apps_list($app);
    foreach ($downloads as $download) {
      $download_commands[] = array(
        'apps_download_batch',
        array(
          $download['name'],
          $download['url'],
          $download['type'],
          $download['version'],
          $app,
        ),
      );
    }
  }
  $batch = array(
    'operations' => $download_commands,
    'file' => drupal_get_path('module', 'apps') . '/apps.installer.inc',
    'title' => t('Downloading modules'),
    'init_message' => t('Preparing to download needed modules'),
    'finished' => 'apps_download_batch_finished',
  );
  return $batch;
}

/**
 * Construct an array of downloadables to download.
 */
function apps_download_apps_list($app) {
  $downloads = array();
  $update = $app['installed'] && !empty($app['upgradeable']);

  // Find all downloads needed for dependencies.
  foreach (array(
    'dependencies' => 'module',
    'libraries' => 'library',
  ) as $download_key => $download_type) {
    if (!empty($app[$download_key])) {
      foreach ($app[$download_key] as $dep) {
        if (!$dep['installed'] || $update) {
          if ($dep['installed'] && $update && !empty($dep['version']['version'])) {
            $info_file_location = $download_type == 'module' ? drupal_get_path('module', $dep['version']['name'], FALSE) : 'sites/all/libraries/' . $dep['version']['name'];
            if ($info = drupal_parse_info_file($info_file_location . '/' . APPS_APP_INFO)) {

              // if don't have current version of version the same, skip this download.
              if (empty($info['version']) || $dep['version']['version'] == $info['version']) {
                continue;
              }
            }
          }
          $downloads[$dep['downloadable']]['for'][] = $dep['version']['name'];
          $downloads[$dep['downloadable']]['type'] = $download_type;
          $downloads[$dep['downloadable']]['version'] = isset($dep['version']['version']) ? $dep['version']['version'] : $app['version'];
        }
      }
    }
  }

  // Add our core modules download.
  if (!$app['installed'] || !empty($app['upgradeable'])) {
    $downloads[$app['downloadable']]['for'][] = $app['machine_name'];
    $downloads[$app['downloadable']]['type'] = 'app';
    $downloads[$app['downloadable']]['version'] = $app['version'];
  }

  // Foreach download find the URL.
  foreach ($downloads as $key => $download) {
    $downloads[$key]['url'] = $app['downloadables'][$key];

    // Do a quick dirty pull of the name from the key.
    $downloads[$key]['name'] = ($e = strpos($key, " ")) ? substr($key, 0, $e) : $key;
  }
  return $downloads;
}

/**
 * Batch callback invoked when the download batch is completed.
 *
 * A pass though to update_manager_download_batch_finished
 * but we set $_GET['destination'] to control the drupal_goto that is
 * in that function.
 */
function apps_download_batch_finished($success, $results) {
  module_load_include("inc", "update", "update.manager");
  $_GET['destination'] = $_SESSION['apps_install_next'];
  update_manager_download_batch_finished($success, $results);
}

/**
 * Setup the download batch process.
 *
 * Pass though to update_manager_batch_project_get we need this because in a
 * batch set the file param is for both the operations as well as the other
 * callbacks.
 */
function apps_download_batch($project, $url, $type, $version, $app, &$context) {
  module_load_include("inc", "update", "update.manager");

  // This is here to show the user that we are in the process of downloading.
  if (!isset($context['sandbox']['started'])) {
    $context['sandbox']['started'] = TRUE;
    $context['message'] = t('Downloading %project', array(
      '%project' => $project,
    ));
    $context['finished'] = 0;
    return;
  }

  // Actually try to download the file.
  if (!($local_cache = update_manager_file_get($url))) {
    $context['results']['errors'][$project] = t('Failed to download %project from %url', array(
      '%project' => $project,
      '%url' => $url,
    ));
    return;
  }

  // Extract it.
  $extract_directory = apps_extract_directory($type);
  try {
    update_manager_archive_extract($local_cache, $extract_directory);
  } catch (Exception $e) {
    $context['results']['errors'][$project] = $e
      ->getMessage();
    return;
  }

  // Update might not have been extracted to directory named after project.
  // Github for example extracts to [project name]-[release version].
  if (!is_dir("{$extract_directory}/{$project}")) {
    foreach (scandir("{$extract_directory}") as $file) {

      // Hopefully this is the directory, so move it to project directory for
      // update module.
      if ($file != '.' && $file != '..' && is_dir("{$extract_directory}/{$file}") && strpos($file, $project) === 0) {
        rename("{$extract_directory}/{$file}", "{$extract_directory}/{$project}");
      }
    }
  }

  // Update/create info file with download information for future updating.
  if (is_dir("{$extract_directory}/{$project}")) {

    // Need to make version is set.
    $add = array(
      '; Information created by apps module.',
      '',
    );
    $add[] = 'version = ' . $version;
    $add[] = 'source_app = ' . $app['machine_name'];
    $add[] = 'download_time = ' . time();
    $add[] = '';
    file_put_contents("{$extract_directory}/{$project}/" . APPS_APP_INFO, implode("\n", $add));
  }

  // Verify it.
  $archive_errors = update_manager_archive_verify($project, $local_cache, $extract_directory);
  $archive_errors = array();
  if (!empty($archive_errors)) {

    // We just need to make sure our array keys don't collide, so use the
    // numeric keys from the $archive_errors array.
    foreach ($archive_errors as $key => $error) {
      $context['results']['errors']["{$project}-{$key}"] = $error;
    }
    return;
  }

  // Yay, success.
  $context['results']['projects'][$type][$project] = $url;
  $context['finished'] = 1;
}

/**
 * Wrapper for _update_manager_extract_directory().
 *
 * Since libraries and modules can live in the same location, we need to
 * namespace the types so they don't collide like the colorbox module.
 */
function apps_extract_directory($type = '') {
  $directory = _update_manager_extract_directory();
  if ($type) {
    $directory .= '/' . $type;
    if (!file_exists($directory)) {
      mkdir($directory);
    }
  }
  return $directory;
}

/**
 * Move modules from there temp location in to the drupal tree.
 *
 * Taken from update_manager_update_ready_form_submit
 * we are using apps_run_install instead of update_authorize_run_update
 *
 * @TODO: Get the install to work when we do not own sites OPIC-377
 */
function apps_install_downloads() {
  module_load_include("inc", "update", "update.manager");
  if (!empty($_SESSION['update_manager_update_projects'])) {

    // Make sure the Updater registry is loaded.
    drupal_get_updaters();
    $updates = array();
    $project_types = $_SESSION['update_manager_update_projects'];
    foreach ($project_types as $type => $projects) {
      $directory = apps_extract_directory($type);
      foreach ($projects as $project => $url) {
        $project_location = $directory . '/' . $project;
        try {
          $updater = Updater::factory($project_location);
        } catch (Exception $e) {
          drupal_set_message(t('Error installing @project: @message', array(
            '@project' => $project,
            '@message' => $e
              ->getMessage(),
          )), 'error');
          return FALSE;
        }
        $project_real_location = drupal_realpath($project_location);
        $updates[] = array(
          'project' => $project,
          'updater_name' => get_class($updater),
          'local_url' => $project_real_location,
        );
      }
    }

    // If the owner of the last directory we extracted is the same as the
    // owner of our configuration directory (e.g. sites/default) where we're
    // trying to install the code, there's no need to prompt for FTP/SSH
    // credentials. Instead, we instantiate a FileTransferLocal and invoke
    // update_authorize_run_update() directly.
    if (apps_installer_has_write_access()) {
      module_load_include('inc', 'update', 'update.authorize');
      $filetransfer = new FileTransferLocal(DRUPAL_ROOT);

      // This is our change.
      apps_run_install($filetransfer, $updates);
      unset($_SESSION['update_manager_update_projects']);
    }
    else {

      // Set the $_SESSION variables so that authorize form knows what to do
      // after authorization.
      system_authorized_init('apps_run_install', drupal_get_path('module', 'apps') . '/apps.installer.inc', array(
        $updates,
      ), t('Update manager'));

      // Get the authorize form.
      require_once DRUPAL_ROOT . '/includes/authorize.inc';
      return drupal_get_form('authorize_filetransfer_form');
    }
  }
}

/**
 * The batch builder and processor for moving files to drupal.
 *
 * taken from update_authorize_run_update
 * builds a batch and process it for installing modules from the templocation
 */
function apps_run_install($filetransfer, $projects) {
  $operations = array();
  foreach ($projects as $project => $project_info) {
    $operations[] = array(
      'apps_update_authorize_batch_copy_project',
      array(
        $project_info['project'],
        $project_info['updater_name'],
        $project_info['local_url'],
        $filetransfer,
      ),
    );
  }
  $batch = array(
    'title' => t('Downloading apps'),
    'init_message' => t('Preparing to download apps.'),
    'operations' => $operations,
    'finished' => 'apps_update_authorize_update_batch_finished',
    'file' => drupal_get_path('module', 'apps') . '/apps.installer.inc',
  );
  batch_set($batch);

  // Invoke the batch via authorize.php.
  batch_process($_SESSION['apps_install_next']);
}

/**
 * Wrapper around update_authorize_update_batch_finished to include file.
 *
 * Since need to override update_authorize_batch_copy_project, need to have the
 * path be this file, so need the 'finished' operation to be in this file also.
 */
function apps_update_authorize_update_batch_finished($success, $results) {
  module_load_include('authorize.inc', 'update');
  update_authorize_update_batch_finished($success, $results);
}

/**
 * Copy of update_authorize_batch_copy_project cept override install_dir. 
 */
function apps_update_authorize_batch_copy_project($project, $updater_name, $local_url, $filetransfer, &$context) {
  module_load_include('authorize.inc', 'update');

  // Initialize some variables in the Batch API $context array.
  if (!isset($context['results']['log'])) {
    $context['results']['log'] = array();
  }
  if (!isset($context['results']['log'][$project])) {
    $context['results']['log'][$project] = array();
  }
  if (!isset($context['results']['tasks'])) {
    $context['results']['tasks'] = array();
  }

  // The batch API uses a session, and since all the arguments are serialized
  // and unserialized between requests, although the FileTransfer object itself
  // will be reconstructed, the connection pointer itself will be lost. However,
  // the FileTransfer object will still have the connection variable, even
  // though the connection itself is now gone. So, although it's ugly, we have
  // to unset the connection variable at this point so that the FileTransfer
  // object will re-initiate the actual connection.
  unset($filetransfer->connection);
  if (!empty($context['results']['log'][$project]['#abort'])) {
    $context['finished'] = 1;
    return;
  }
  $updater = new $updater_name($local_url);
  try {
    $args = array();
    if ($updater
      ->isInstalled()) {

      // This is an update.
      $tasks = $updater
        ->update($filetransfer, $args);
    }
    else {
      if ($updater_name == 'ModuleUpdater') {
        $args = array(
          'install_dir' => DRUPAL_ROOT . '/' . variable_get('apps_install_path', APPS_INSTALL_PATH),
        );
      }
      $tasks = $updater
        ->install($filetransfer, $args);
    }
  } catch (UpdaterException $e) {
    _update_batch_create_message($context['results']['log'][$project], t('Error installing / updating'), FALSE);
    _update_batch_create_message($context['results']['log'][$project], $e
      ->getMessage(), FALSE);
    $context['results']['log'][$project]['#abort'] = TRUE;
    return;
  }
  _update_batch_create_message($context['results']['log'][$project], t('Installed %project_name successfully', array(
    '%project_name' => $project,
  )));
  if (!empty($tasks)) {
    $context['results']['tasks'] += $tasks;
  }

  // This particular operation is now complete, even though the batch might
  // have other operations to perform.
  $context['finished'] = 1;
}

/**
 * Delete the update cache directories so clean update is run.
 */
function apps_clear_update_disk_cache() {
  file_unmanaged_delete_recursive(_update_manager_cache_directory(FALSE));
  file_unmanaged_delete_recursive(_update_manager_extract_directory(FALSE));

  // Recreate the directories.
  _update_manager_cache_directory();
  _update_manager_extract_directory();
}

Functions

Namesort descending Description
apps_clear_update_disk_cache Delete the update cache directories so clean update is run.
apps_download_apps Download all modules and library to a temp location.
apps_download_apps_batch Make the batch for downloading modules for app.
apps_download_apps_list Construct an array of downloadables to download.
apps_download_batch Setup the download batch process.
apps_download_batch_finished Batch callback invoked when the download batch is completed.
apps_extract_directory Wrapper for _update_manager_extract_directory().
apps_install_downloads Move modules from there temp location in to the drupal tree.
apps_run_install The batch builder and processor for moving files to drupal.
apps_update_authorize_batch_copy_project Copy of update_authorize_batch_copy_project cept override install_dir.
apps_update_authorize_update_batch_finished Wrapper around update_authorize_update_batch_finished to include file.