You are here

quickupdate.module in Quick update 7

Same filename and directory in other branches
  1. 8 quickupdate.module

Primarily Drupal hooks and global API functions.

File

quickupdate.module
View source
<?php

/**
 * @file
 * Primarily Drupal hooks and global API functions.
 */

/**
 * Implements hook_help().
 */
function quickupdate_help($path, $arg) {
  switch ($path) {
    case 'admin/help#quickupdate':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Drupal core provides a way to install module or theme one by one, but you can install multiple projects via the Quick update module. There is an admin UI to search the most installed projects easier.') . '</p>';
      $output .= '<p>' . t('The Quick update module enhances Drupal core update features and provides a quick way to batch install new projects and install missing dependency projects.') . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Admin UI') . '</dt>';
      $output .= '<dd>' . t('The Quick update module depends on the Update module and uses the same workflow as the Update module. Thus, you can run the updates via the <a href="@quickupdate">admin update page</a>.', array(
        '@quickupdate' => url('admin/reports/updates/update'),
      )) . '</dd>';
      $output .= '<dt>' . t('Drush command') . '</dt>';
      $output .= '<dd>' . t('There are some custom Drush commands to list or install missing dependency projects that include modules and themes.') . '</dd>';
      $output .= '<dd>' . t('"!command" lists current missing dependency projects.', array(
        '!command' => 'drush qup-list-md',
      )) . '</dd>';
      $output .= '<dd>' . t('"!command" downloads projects and their dependency projects.', array(
        '!command' => 'drush qup-dl',
      )) . '</dd>';
      $output .= '<dd>' . t('"!command" downloads all missing dependency projects.', array(
        '!command' => 'drush qup-dl-md',
      )) . '</dd>';
      $output .= '</dl>';
      return $output;
  }
}

/**
 * Implements hook_quickupdate_search_projects().
 */
function quickupdate_quickupdate_search_projects() {
  return array(
    'modules' => quickupdate_most_installed_modules(),
    'themes' => quickupdate_most_installed_themes(),
  );
}

/**
 * Implements hook_menu().
 */
function quickupdate_menu() {
  $items = array();
  $items['quickupdate/autocomplete/search_projects'] = array(
    'title' => 'Search projects',
    'page callback' => 'quickupdate_autocomplete_search_projects',
    'access arguments' => array(
      'administer site configuration',
    ),
    'weight' => 20,
  );
  return $items;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function quickupdate_form_update_manager_update_form_alter(&$form, &$form_state, $form_id) {

  // Loads all the missing dependency projects.
  $dependencies = quickupdate_load_missing_dependencies();

  // Adds the "missing_dependency_projects" form item.
  if (count($dependencies) > 0) {
    $headers = array(
      'title' => array(
        'data' => t('Name'),
        'class' => array(
          'update-project-name',
        ),
      ),
      'installed_version' => t('Installed version'),
      'recommended_version' => t('Recommended version'),
    );
    $form['missing_dependency_projects'] = array(
      '#type' => 'tableselect',
      '#header' => $headers,
      '#options' => $dependencies,
      '#prefix' => '<h2>' . t('Missing dependency projects') . '</h2>',
      '#weight' => 20,
      '#suffix' => t("Some modules may have multiple level dependencies, so you should run the update process multiple times until there is no more missing dependency projects."),
    );
  }

  // Adds the "other_projects" form item.
  // It allows you to install multiple projects at a time.
  $form['search_project'] = array(
    '#type' => 'textfield',
    '#default_value' => "",
    '#weight' => 21.0,
    '#autocomplete_path' => 'quickupdate/autocomplete/search_projects',
    '#prefix' => '<h2>' . t('Install new projects') . '</h2><div class="container-inline">',
    '#attributes' => array(
      'placeholder' => t('Search the most installed projects.'),
    ),
  );
  $form['add_project'] = array(
    '#type' => 'button',
    '#default_value' => t('Add'),
    '#weight' => 21.1,
  );
  $form['view_project'] = array(
    '#type' => 'button',
    '#default_value' => t('View'),
    '#weight' => 21.2,
  );
  $form['clear_project'] = array(
    '#type' => 'button',
    '#default_value' => t('Clear'),
    '#weight' => 21.3,
    '#suffix' => '</div>',
  );
  $form['other_projects'] = array(
    '#type' => 'textarea',
    '#rows' => 10,
    '#default_value' => "",
    '#weight' => 22,
    '#description' => t('Enter the projects names that you want to install. One project per line.'),
  );
  $form['#attached']['js'] = array(
    drupal_get_path('module', 'quickupdate') . '/js/quickupdate.js',
  );

  // Uses validation function 'quickupdate_manager_update_form_validate'
  // instead of 'update_manager_update_form_validate'.
  $form['#validate'] = array(
    'quickupdate_manager_update_form_validate',
  );

  // Uses submit function 'quickupdate_manager_update_form_submit'
  // instead of 'update_manager_update_form_submit'.
  $form['#submit'] = array(
    'quickupdate_manager_update_form_submit',
  );

  // Always show the 'Download these updates' button.
  $form['actions'] = array(
    '#type' => 'actions',
  );
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Download these updates'),
  );
}

/**
 * Form validation handler for quickupdate_manager_update_form().
 *
 * Ensures that at least one project is selected.
 *
 * @see quickupdate_manager_update_form_submit()
 */
function quickupdate_manager_update_form_validate($form, &$form_state) {
  if (!empty($form_state['values']['projects'])) {
    $enabled = array_filter($form_state['values']['projects']);
  }
  if (!empty($form_state['values']['disabled_projects'])) {
    $disabled = array_filter($form_state['values']['disabled_projects']);
  }
  if (!empty($form_state['values']['missing_dependency_projects'])) {
    $missing_dependency = array_filter($form_state['values']['missing_dependency_projects']);
  }
  if (!empty($form_state['values']['other_projects'])) {
    $other_projects = array_filter(explode("\n", $form_state['values']['other_projects']));
  }
  if (empty($enabled) && empty($disabled) && empty($missing_dependency) && empty($other_projects)) {
    form_set_error('projects', t('You must select at least one project to update.'));
  }
}

/**
 * Form submission handler for quickupdate_manager_update_form().
 *
 * Sets up a batch that downloads, extracts, and verifies the selected releases.
 *
 * @see quickupdate_manager_update_form_validate()
 */
function quickupdate_manager_update_form_submit($form, &$form_state) {
  $projects = array();
  foreach (array(
    'projects',
    'disabled_projects',
    'missing_dependency_projects',
    'other_projects',
  ) as $type) {
    if (isset($form_state['values'][$type]) && !empty($form_state['values'][$type])) {
      if ($type == 'other_projects') {
        $other_projects = array();
        foreach (explode("\n", $form_state['values'][$type]) as $v) {
          $v = trim($v);
          $other_projects[$v] = $v;
        }
        $projects = array_merge($projects, array_keys(array_filter($other_projects)));
      }
      else {
        $projects = array_merge($projects, array_keys(array_filter($form_state['values'][$type])));
      }
    }
  }
  $operations = array();
  foreach ($projects as $project) {
    $operations[] = array(
      'quickupdate_manager_batch_project_get',
      array(
        $project,
        isset($form_state['values']['project_downloads'][$project]) ? $form_state['values']['project_downloads'][$project] : '',
      ),
    );
  }
  $batch = array(
    'title' => t('Downloading updates'),
    'init_message' => t('Preparing to download selected updates'),
    'operations' => $operations,
    'finished' => 'quickupdate_manager_download_batch_finished',
  );
  batch_set($batch);
}

/**
 * Batch callback: Downloads, unpacks, and verifies a project.
 *
 * This function assumes that the provided URL points to a file archive of some
 * sort. The URL can have any scheme that we have a file stream wrapper to
 * support. The file is downloaded to a local cache.
 *
 * @param string $project
 *   The short name of the project to download.
 * @param string $url
 *   The URL to download a specific project release archive file.
 * @param array $context
 *   Reference to an array used for Batch API storage.
 */
function quickupdate_manager_batch_project_get($project, $url, &$context) {
  if (empty($url)) {

    // For installing missing dependency projects or new projects,
    // we need to get their download urls because we don't have their
    // information in Drupal system.
    $available = quickupdate_get_project($project);
    $url = $available['download_link'];

    // If we still didn't get the download url, just ignore this project.
    // Probably the project doesn't exist.
    if (empty($url)) {
      return;
    }
  }
  module_load_include('inc', 'update', 'update.manager');
  return update_manager_batch_project_get($project, $url, $context);
}

/**
 * Batch callback: Performs actions when the download batch is completed.
 *
 * @param bool $success
 *   TRUE if the batch operation was successful, FALSE if there were errors.
 * @param array $results
 *   An associative array of results from the batch operation.
 */
function quickupdate_manager_download_batch_finished($success, $results) {

  // Checks if there is any valid projects to be downloaded.
  if (!isset($results['projects'])) {
    drupal_set_message(t('There is no available projects to download.'), 'error');
  }
  else {
    module_load_include('inc', 'update', 'update.manager');
    update_manager_download_batch_finished($success, $results);
  }
}

/**
 * Gets the most installed themes.
 *
 * @return array
 *   An array that contains theme's short name and title.
 */
function quickupdate_most_installed_themes() {
  $themes = array(
    'zen' => t('Zen'),
    'omega' => t('Omega'),
    'adaptivetheme' => t('AdaptiveTheme'),
    'fusion' => t('Fusion'),
    'marinelli' => t('Marinelli'),
    'danland' => t('Danland'),
    'corporateclean' => t('Corporateclean'),
    'tao' => t('Tao'),
    'bluemasters' => t('Bluemasters'),
    'rubik' => t('Rubik'),
    'business' => t('Business'),
    'pixture_reloaded' => t('Pixture Reloaded'),
    'corolla' => t('Corolla'),
    'mayo' => t('Mayo'),
    'acquia_marina' => t('Acquia Marina'),
    'sky' => t('Sky'),
    'nucleus' => t('Nucleus'),
    'zeropoint' => t('Zeropoint'),
    'bootstrap' => t('Bootstrap'),
    'skeletontheme' => t('Skeleton'),
    'andromeda' => t('Andromeda'),
    'basic' => t('Basic'),
    'rootcandy' => t('RootCandy'),
    'acquia_prosper' => t('Acquia Prosper'),
    'shiny' => t('Shiny'),
    'omega_kickstart' => t('Omega Kkickstart'),
    'professional_theme' => t('Professional Ttheme'),
    'framework' => t('Framework'),
    'ninesixty' => t('NineSixty (960 Grid System)'),
    'touch' => t('Touch'),
    'genesis' => t('Genesis'),
    'at-commerce' => t('AT Commerce'),
    'busy' => t('Busy'),
    'responsive' => t('Responsive'),
    'jackson' => t('Jackson'),
    'mix_and_match' => t('Mix and Match'),
    'blogbuzz' => t('BlogBuzz'),
    'corporate' => t('Corporate'),
    'UrbanSolice' => t('Urban Solice'),
    'cti_flex' => t('CTI Flex'),
    'kanji' => t('Kanji'),
    'best_responsive' => t('Best Responsive'),
    'journalcrunch' => t('Journalcrunch'),
    'bamboo' => t('Bamboo'),
    'responsive_blog' => t('Responsive Blog'),
    'mobile' => t('Mobile'),
    'mothership' => t('Mothership'),
    'newsflash' => t('Newsflash'),
    'analytic' => t('Analytic'),
    'tb_sirate' => t('TB Sirate'),
    'fusion_mobile' => t('Fusion Mobile'),
    'zircon' => t('Zircon'),
    'footheme' => t('Footheme'),
    'metropolis' => t('Metropolis'),
    'tma' => t('The Morning After'),
    'twitter_bootstrap' => t("Twitter's Bootstrap"),
    'blueprint' => t('Blueprint'),
    'dark_elegant' => t('Dark Elegant'),
    'plasma' => t('Plasma'),
    'colourise' => t('Colourise'),
    'boron' => t('Boron (HTML5 base theme)'),
    'corkedscrewer' => t('Corked Screwer'),
    'simpleclean' => t('Simple Clean'),
    'magazeen_lite' => t('Magazeen Lite'),
    'zurb-foundation' => t('ZURB Foundation'),
    'simplecorp' => t('SimpleCorp'),
    'tb_purity' => t('TB Purity'),
    'company' => t('Company Theme'),
    'mobile_jquery' => t('Mobile jQuery Theme'),
    'clean' => t('Clean'),
    'clean_theme' => t('Clean Theme'),
    'deco' => t('Deco'),
    'superclean' => t('SuperClean'),
    'litejazz' => t('litejazz'),
    'arthemia' => t('Arthemia'),
    'samara' => t('Samara'),
    'orange' => t('Orange'),
    'newswire' => t('Newswire'),
    'alphorn' => t('Alphorn'),
    'gordon' => t('Gordon'),
    'fontfolio' => t('FontFolio'),
    'arctica' => t('Arctica'),
    'elegant_theme' => t('Elegant Theme'),
    'impact_theme' => t('Impact Theme'),
    'alpine' => t('Alpine'),
    'ad_novus' => t('AD Novus'),
    'om' => t('OM 2 HTML5'),
    'black_premium' => t('Black Premium'),
    'openchurch_theme' => t('OpenChurch Theme'),
    'professional_pro' => t('Professional Pro'),
    'business_theme' => t('Business Theme'),
    'ICE-BUSINESS' => t('ICE Business'),
    'typebased' => t('Typebased'),
    'nitobe' => t('Nitobe'),
    'tundra' => t('Tundra'),
    'responsive_bartik' => t('Responsive Bartik D7'),
    'business_responsive_theme' => t('Business Responsive Theme'),
  );
  return $themes;
}

/**
 * Gets the most installed modules.
 *
 * @return array
 *   An array that contains module's short name and title.
 */
function quickupdate_most_installed_modules() {
  $modules = array(
    'views' => t('Views'),
    'token' => t('Token'),
    'ctools' => t('Chaos tool suite'),
    'pathauto' => t('Pathauto'),
    'webform' => t('Webform'),
    'date' => t('Date'),
    'wysiwyg' => t('Wysiwyg'),
    'libraries' => t('Libraries API'),
    'google_analytics' => t('Google Analytics'),
    'imce' => t('IMCE'),
    'admin_menu' => t('Administration menu'),
    'entity' => t('Entity API'),
    'cck' => t('Content Construction Kit'),
    'backup_migrate' => t('Backup and Migrate'),
    'link' => t('Link'),
    'captcha' => t('CAPTCHA'),
    'ckeditor' => t('CKEditor - WYSIWYG HTML editor'),
    'rules' => t('Rules'),
    'filefield' => t('FileField'),
    'imagefield' => t('ImageField'),
    'imageapi' => t('ImageAPI'),
    'panels' => t('Panels'),
    'jquery_update' => t('jQuery Update'),
    'devel' => t('Devel'),
    'imagecache' => t('ImageCache'),
    'xmlsitemap' => t('XML sitemap'),
    'colorbox' => t('Colorbox'),
    'features' => t('Features'),
    'views_slideshow' => t('Views Slideshow'),
    'advanced_help' => t('Advanced help'),
    'imce_wysiwyg' => t('IMCE Wysiwyg bridge'),
    'globalredirect' => t('Global Redirect'),
    'lightbox2' => t('Lightbox2'),
    'views_bulk_operations' => t('Views Bulk Operations'),
    'meida' => t('Media'),
    'page_title' => t('Page Title'),
    'menu_block' => t('Menu block'),
    'context' => t('Context'),
    'i18n' => t('Internationalization'),
    'transliteration' => t('Transliteration'),
    'jquery_ui' => t('jQuery UI'),
    'metatag' => t('Metatag'),
    'calendar' => t('Calendar'),
    'email' => t('Email Field'),
    'variable' => t('Variable'),
    'field_group' => t('Field group'),
    'nice_menus' => t('Nice Menus'),
    'poormanscron' => t('Poormanscron'),
    'superfish' => t('Superfish'),
    'nodewords' => t('Nodewords: D6 Meta Tags'),
    'references' => t('References'),
    'l10n_update' => t('Localization update'),
    'site_map' => t('Site map'),
    'auto_nodetitle' => t('Automatic Nodetitles'),
    'media_youtube' => t('Media: YouTube'),
    'ds' => t('Display Suite'),
    'job_scheduler' => t('Job Scheduler'),
    'redirect' => t('Redirect'),
    'logintoboggan' => t('LoginToboggan'),
    'image' => t('Image'),
    'print' => t('Printer, email and PDF versions'),
    'taxonomy_menu' => t('Taxonomy menu'),
    'location' => t('Location'),
    'content_access' => t('Content Access'),
    'imagecache_actions' => t('ImageCache Actions'),
    'quicktabs' => t('Quick Tabs'),
    'mollom' => t('Mollom'),
    'recaptcha' => t('reCAPTCHA'),
    'module_filter' => t('Module Filter'),
    'emfield' => t('Embedded Media Field'),
    'mimemail' => t('Mime Mail'),
    'feeds' => t('Feeds'),
    'addressfield' => t('Address Field'),
    'strongarm' => t('Strongarm'),
    'votingapi' => t('Voting API'),
    'simplenews' => t('Simplenews'),
    'insert' => t('Insert'),
    'gmap' => t('GMap Module'),
    'extlink' => t('External Links'),
    'ubercart' => t('Ubercart'),
    'better_formats' => t('Better Formats'),
    'custom_breadcrumbs' => t('Custom Breadcrumbs'),
    'scheduler' => t('Scheduler'),
    'node_clone' => t('Node clone'),
    'block_class' => t('Block Class'),
    'diff' => t('Diff'),
    'path_redirect' => t('Path Redirect'),
    'entityreference' => t('Entity reference'),
    'smtp' => t('SMTP Authentication Support'),
    'filefield_sources' => t('FileField Sources'),
    'field_permissions' => t('Field Permissions'),
    'fckeditor' => t('FCKeditor - WYSIWYG HTML editor'),
    'views_php' => t('Views PHP'),
    'nodequeue' => t('Nodequeue'),
    'login_destination' => t('Login Destination'),
    'admin' => t('Admin'),
    'menu_attributes' => t('Menu attributes'),
    'menu_breadcrumb' => t('Menu Breadcrumb'),
    'omega_tools' => t('Omega Tools'),
  );
  return $modules;
}

/**
 * Gets a project's information.
 *
 * @param string $project_name
 *   The short name of the project to download.
 *
 * @return array
 *   An array that contains the project's short name, title, download link,
 *   project type, and version.
 */
function quickupdate_get_project($project_name = '') {
  module_load_include('inc', 'update', 'update.fetch');
  $xml = drupal_http_request(UPDATE_DEFAULT_URL . '/' . $project_name . '/' . DRUPAL_CORE_COMPATIBILITY);
  $array = array();
  if (!isset($xml->error) && isset($xml->data) && !empty($xml->data)) {
    $array = update_parse_xml($xml->data);
  }
  if (count($array) > 0 && isset($array['releases'])) {
    $downloaded_major = isset($array['recommended_major']) ? $array['recommended_major'] : $array['default_major'];
    foreach ($array['releases'] as $item) {
      if ($item['status'] == 'published' && $item['version_major'] == $downloaded_major) {
        $return = array();
        $return['title'] = check_plain($array['title']);
        $return['short_name'] = $array['short_name'];
        $return['download_link'] = $item['download_link'];
        $return['version'] = $item['version'];
        return $return;
      }
    }
  }
  return array();
}

/**
 * Callback function for searching projects.
 *
 * @param string $string
 *   The keywords that the user want to search.
 *   User can entry multiple words as a keyword.
 */
function quickupdate_autocomplete_search_projects($string = '') {
  $matches = array();
  $sources = array();
  $searches = array();
  $result = 10;
  $keywords = !empty($string) ? explode(' ', $string) : array();
  $projects = quickupdate_get_search_projects();
  if (isset($projects['modules'])) {
    foreach ($projects['modules'] as $short_name => $title) {
      $sources[$short_name] = t('Module - !title (!short_name)', array(
        '!short_name' => $short_name,
        '!title' => $title,
      ));
      $searches[$short_name] = $title . ' ' . $short_name;
    }
  }
  if (isset($projects['themes'])) {
    foreach ($projects['themes'] as $short_name => $title) {
      $sources[$short_name] = t('Theme - !title (!short_name)', array(
        '!short_name' => $short_name,
        '!title' => $title,
      ));
      $searches[$short_name] = $title . ' ' . $short_name;
    }
  }
  foreach ($searches as $short_name => $title) {
    foreach ($keywords as $keyword) {

      // Wildcard search.
      if (fnmatch("*" . $keyword . "*", $title)) {
        $matches[$short_name] = $sources[$short_name];
        continue;
      }
    }
    if (count($matches) >= $result) {
      break;
    }
  }
  print drupal_json_output($matches);
  exit;
}

/**
 * Loads all missing dependency projects.
 *
 * @param array $projects
 *   The short name of the projects or leave empty.
 *   If empty, then loads all dependency projects.
 *   Otherwise, only loads the dependency projects of the specific projects.
 *
 * @return array
 *   An array that contains the missing dependency project's title,
 *   installed version, and recommended version.
 */
function quickupdate_load_missing_dependencies($projects = array()) {

  // Resets the modules cache.
  drupal_static('system_rebuild_module_data', NULL, TRUE);

  // Resets the themes cache.
  drupal_static('system_rebuild_theme_data', NULL, TRUE);
  $module_files = system_rebuild_module_data();
  $theme_files = system_rebuild_theme_data();
  $themes_list = list_themes(TRUE);
  $entry = array();
  $counts = count($projects);
  foreach ($module_files as $module) {
    if ($counts > 0 && !in_array($module->name, $projects)) {
      continue;
    }
    foreach ($module->requires as $requires => $v) {
      if (!isset($module_files[$requires]) && $requires != '_missing_dependency') {
        $entry[$requires] = array(
          'title' => drupal_ucfirst($requires),
          'installed_version' => t('None'),
          'recommended_version' => t('Unknown'),
          'required_by' => $module->name,
        );
      }
    }
  }
  foreach ($theme_files as $theme) {
    if ($counts > 0 && !in_array($theme->name, $projects)) {
      continue;
    }
    if (isset($theme->base_themes)) {
      foreach ($theme->base_themes as $short_name => $v) {
        if (!array_key_exists($short_name, $themes_list)) {
          $entry[$short_name] = array(
            'title' => drupal_ucfirst($short_name),
            'installed_version' => t('None'),
            'recommended_version' => t('Unknown'),
            'required_by' => $theme->name,
          );
        }
      }
    }
  }
  return $entry;
}

/**
 * Gets a list of available projects to be searched.
 *
 * @return array
 *   All available projects in an array.
 */
function quickupdate_get_search_projects() {
  $projects = module_invoke_all('quickupdate_search_projects');

  // Invokes hook_quickupdate_search_projects_alter().
  // To allow all modules to alter the projects.
  drupal_alter('quickupdate_search_projects', $projects);
  return $projects;
}

Functions

Namesort descending Description
quickupdate_autocomplete_search_projects Callback function for searching projects.
quickupdate_form_update_manager_update_form_alter Implements hook_form_FORM_ID_alter().
quickupdate_get_project Gets a project's information.
quickupdate_get_search_projects Gets a list of available projects to be searched.
quickupdate_help Implements hook_help().
quickupdate_load_missing_dependencies Loads all missing dependency projects.
quickupdate_manager_batch_project_get Batch callback: Downloads, unpacks, and verifies a project.
quickupdate_manager_download_batch_finished Batch callback: Performs actions when the download batch is completed.
quickupdate_manager_update_form_submit Form submission handler for quickupdate_manager_update_form().
quickupdate_manager_update_form_validate Form validation handler for quickupdate_manager_update_form().
quickupdate_menu Implements hook_menu().
quickupdate_most_installed_modules Gets the most installed modules.
quickupdate_most_installed_themes Gets the most installed themes.
quickupdate_quickupdate_search_projects Implements hook_quickupdate_search_projects().