You are here

hacked.drush.inc in Hacked! 8.2

Hacked drush command.

Enables drush support for the Hacked! module.

File

hacked.drush.inc
View source
<?php

/**
 * @file
 * Hacked drush command.
 *
 * Enables drush support for the Hacked! module.
 */
use Drupal\hacked\hackedProject;

/**
 * Implements hook_drush_help().
 */
function hacked_drush_help($section) {
  switch ($section) {
    case 'drush:hacked-list-projects':
      return dt('List projects and their hacked/unhacked status.');
    case 'drush:hacked-details':
      return dt('Show details of the files in one project, and the hacked/unhacked status of those files.');
    case 'drush:hacked-diff':
      return dt('Output a unified diff of the specified project.');
  }
}

/**
 * Implements hook_drush_command().
 *
 * @See drush_parse_command() for a list of recognized keys.
 */
function hacked_drush_command() {
  $items['hacked-list-projects'] = [
    'description' => "List all projects that can be analysed by Hacked! ",
    'drupal dependencies' => [
      'hacked',
    ],
    'options' => [
      'force-rebuild' => 'Rebuild the Hacked! report instead of getting a cached version.',
    ],
    'aliases' => [
      'hlp',
    ],
  ];
  $items['hacked-lock-modified'] = [
    'description' => "Lock all projects that Hacked! detects are modified, so that drush pm-updatecode will not touch them. (drush-4.x+ only)",
    'drupal dependencies' => [
      'hacked',
    ],
  ];
  $items['hacked-details'] = [
    'description' => "Show the Hacked! report about a specific project.",
    'drupal dependencies' => [
      'hacked',
    ],
    'arguments' => [
      'project' => 'The machine name of the project to report on.',
    ],
    'options' => [
      'include-unchanged' => 'Show the files that are unchanged too.',
    ],
    'aliases' => [
      'hd',
    ],
  ];
  $items['hacked-diff'] = [
    'description' => "Output a unified diff of the project specified.",
    'drupal dependencies' => [
      'hacked',
    ],
    'arguments' => [
      'project' => 'The machine name of the project to report on.',
    ],
    'options' => [
      'diff-options' => 'Command line options to pass through to the diff command.',
    ],
  ];
  return $items;
}

/**
 * Compute the report data for hacked.
 *
 * WARNING: This function can invoke a batch process and end your current page.
 * So you'll want to be very careful if you call this!
 *
 * @param array $projects
 *   An array of Drupal projects.
 * @param bool|FALSE $force
 *   If TRUE, force rebuild of project data.
 */
function hacked_calculate_project_data_drush($projects, $force = FALSE) {
  include_once DRUPAL_ROOT . '/core/includes/batch.inc';

  // Try to get the report form cache if we can.
  $cache = \Drupal::cache(HACKED_CACHE_TABLE)
    ->get('hacked:drush:full-report');
  if (!empty($cache->data) && !$force) {
    return $cache->data;
  }

  // Enter a batch to build the report.
  $operations = [];
  foreach ($projects as $project) {
    $operations[] = [
      'hacked_build_report_batch',
      [
        $project['name'],
      ],
    ];
  }
  $batch = [
    'operations' => $operations,
    'finished' => 'hacked_build_report_batch_finished_drush',
    'file' => drupal_get_path('module', 'hacked') . '/hacked.report.inc',
    'title' => t('Building report'),
  ];
  drush_print('Rebuilding Hacked! report');
  batch_set($batch);
  $batch =& batch_get();
  $batch['progressive'] = FALSE;
  drush_backend_batch_process();
  drush_print('Done.');

  // Now we can get the data from the cache.
  $cache = \Drupal::cache(HACKED_CACHE_TABLE)
    ->get('hacked:drush:full-report');
  if (!empty($cache->data)) {
    return $cache->data;
  }
}

/**
 * Completion callback for the report batch.
 *
 * @param bool $success
 *   Boolean value of batch success.
 * @param array $results
 *   An array of batch results.
 */
function hacked_build_report_batch_finished_drush($success, $results) {
  if ($success) {

    // Sort the results.
    usort($results['report'], '_hacked_project_report_sort_by_status');

    // Store them.
    \Drupal::cache(HACKED_CACHE_TABLE)
      ->set('hacked:drush:full-report', $results['report'], strtotime('+1 day'));
  }
}

/**
 * Drush command callback that shows the listing of changed/unchanged projects.
 */
function drush_hacked_list_projects() {

  // Go get the data:
  module_load_include('inc', 'update', 'update.report');
  if ($available = update_get_available(TRUE)) {
    module_load_include('inc', 'update', 'update.compare');
    $data = update_calculate_project_data($available);
    $force_rebuild = drush_get_option('force-rebuild', FALSE);
    $projects = hacked_calculate_project_data_drush($data, $force_rebuild);

    // Now print the data using drush:
    $rows[] = [
      dt('Title'),
      dt('Name'),
      dt('Version'),
      dt('Status'),
      dt('Changed'),
      dt('Deleted'),
    ];
    foreach ($projects as $project) {
      $row = [
        $project['title'],
        $project['name'],
        $project['existing_version'],
      ];

      // Now add the status:
      switch ($project['status']) {
        case HACKED_STATUS_UNHACKED:
          $row[] = dt('Unchanged');
          break;
        case HACKED_STATUS_HACKED:
          $row[] = t('Changed');
          break;
        case HACKED_STATUS_UNCHECKED:
        default:
          $row[] = t('Unchecked');
          break;
      }
      $row[] = $project['counts']['different'];
      $row[] = $project['counts']['missing'];
      $rows[] = $row;
    }
    drush_print_table($rows, TRUE);
    return $projects;
  }
}

/**
 * Lock all of the modified files so that pm-updatecode will not touch them.
 */
function drush_hacked_lock_modified() {
  $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
  module_load_include('inc', 'update', 'update.report');
  if (isset($drupal_root) && ($available = update_get_available(TRUE))) {
    module_load_include('inc', 'update', 'update.compare');
    $data = update_calculate_project_data($available);
    $projects = hacked_calculate_project_data_drush($data, TRUE);
    foreach ($projects as $project) {
      $row = [
        empty($project['title']) ? $project['name'] : $project['title'],
        $project['name'],
        $project['existing_version'],
      ];

      // Lock the file if it is not already locked.
      switch ($project['status']) {
        case HACKED_STATUS_HACKED:
          $project_ob = NULL;
          $project_ob = new hackedProject($project['project_name']);
          $lockfile = $project_ob
            ->file_get_location('local', '.drush-lock-update');
          if (!file_exists($lockfile)) {
            drush_op('file_put_contents', $lockfile, dt("Locked: modified."));
            drush_print(dt("Locked: @project", [
              '@project' => $project['name'],
            ]));
          }
          else {
            drush_print(dt("@project is modified and already locked", [
              '@project' => $project['name'],
            ]));
          }
          break;
        case HACKED_STATUS_UNHACKED:
        case HACKED_STATUS_UNCHECKED:
        default:
          break;
      }
    }
  }
}

/**
 * Add a --lock-modified flag to pm-updatecode.
 */
function drush_hacked_pre_pm_updatecode() {
  if (drush_get_option('lock-modified')) {
    drush_print(dt('Hacked! is checking for modified projects...'));
    drush_hacked_lock_modified();
    drush_print(dt('Hacked! modification check complete.'));
  }
}

/**
 * Implements hook_drush_help_alter().
 *
 * Add --lock-modified to the pm-updatecode and pm-update help.
 */
function hacked_drush_help_alter(&$command) {
  if ($command['command'] == 'pm-updatecode' || $command['command'] == 'pm-update') {
    $command['sub-options']['--lock']['--lock-modified'] = "Lock any project that Hacked! determines is modified.";
  }
}

/**
 * Validate hook for the hacked_details drush command.
 *
 * @param string $short_name
 *   The project short name.
 */
function drush_hacked_details_validate($short_name = '') {
  return drush_hacked_drush_command_validate($short_name);
}

/**
 * Validate hook for the hacked drush commands that need a project.
 *
 * @param string $short_name
 *   The project short name.
 */
function drush_hacked_drush_command_validate($short_name = '') {
  if (empty($short_name)) {
    return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('A valid project must be specified', [
      '@project' => $short_name,
    ]));
  }
  $project = new hackedProject($short_name);
  $project
    ->identify_project();
  if (!$project->project_identified) {
    return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('Could not find project: @project', [
      '@project' => $short_name,
    ]));
  }
  $project = NULL;
}

/**
 * Drush callback that shows the list of changes/unchanged files in a project.
 *
 * You may specify the --include-unchanged option to show unchanged files too,
 * otherwise just the changed and deleted files are shown.
 *
 * @param string $short_name
 *   The project short name.
 */
function drush_hacked_details($short_name) {
  $project = new hackedProject($short_name);
  $report = $project
    ->compute_details();
  drush_print(dt('Details for project: @name', [
    '@name' => $project
      ->title(),
  ]));
  drush_print(dt('Total files: @total_files, files changed: @changed_files, deleted files: @deleted_files', [
    '@total_files' => count($report['files']),
    '@changed_files' => $report['counts']['different'],
    '@deleted_files' => $report['counts']['missing'],
  ]));
  drush_print('');
  drush_print(dt('Detailed results:'));

  // Sort the results:
  arsort($report['files']);
  $rows[] = [
    dt('Status'),
    dt('File'),
  ];
  $show_unchanged = drush_get_option('include-unchanged', FALSE);
  foreach ($report['files'] as $file => $status) {
    if (!$show_unchanged && $status == HACKED_STATUS_UNHACKED) {
      continue;
    }
    $row = [];

    // Now add the status:
    switch ($status) {
      case HACKED_STATUS_UNHACKED:
        $row[] = dt('Unchanged');
        break;
      case HACKED_STATUS_HACKED:
        $row[] = t('Changed');
        break;
      case HACKED_STATUS_DELETED:
        $row[] = t('Deleted');
        break;
      case HACKED_STATUS_UNCHECKED:
      default:
        $row[] = t('Unchecked');
        break;
    }
    $row[] = $file;
    $rows[] = $row;
  }
  drush_print_table($rows, TRUE);
}

/**
 * Validate hook for the hacked_diff drush command.
 *
 * @param string $short_name
 *   The project short name.
 */
function drush_hacked_diff_validate($short_name = '') {
  return drush_hacked_drush_command_validate($short_name);
}

/**
 * Drush callback that shows the list of changes/unchanged files in a project.
 *
 * You may specify the --include-unchanged option to show unchanged files too,
 * otherwise just the changed and deleted files are shown.
 *
 * @param string $short_name
 *   The project short name.
 */
function drush_hacked_diff($short_name) {
  $project = new hackedProject($short_name);
  $local_location = $project
    ->file_get_location('local', '');
  $clean_location = $project
    ->file_get_location('remote', '');

  // If the hasher is our ignore line endings one, then ignore line endings.
  $hasher = \Drupal::config('hacked.settings')
    ->get('selected_file_hasher');
  $hasher = is_null($hasher) ? HACKED_DEFAULT_FILE_HASHER : $hasher;
  if ($hasher == 'hacked_ignore_line_endings') {
    $default_options = '-uprb';
  }
  else {
    $default_options = '-upr';
  }
  $diff_options = drush_get_option('diff-options', $default_options);
  drush_shell_exec("diff {$diff_options} {$clean_location} {$local_location}");
  $lines = drush_shell_exec_output();
  $local_location_trim = dirname($local_location . '/dummy.file') . '/';
  $clean_location_trim = dirname($clean_location . '/dummy.file') . '/';
  foreach ($lines as $line) {
    if (strpos($line, '+++') === 0) {
      $line = str_replace($local_location_trim, '', $line);
    }
    if (strpos($line, '---') === 0) {
      $line = str_replace($clean_location_trim, '', $line);
    }
    if (strpos($line, 'diff -upr') === 0) {
      $line = str_replace($clean_location_trim, 'a/', $line);
      $line = str_replace($local_location_trim, 'b/', $line);
    }
    drush_print($line);
  }
}

Functions

Namesort descending Description
drush_hacked_details Drush callback that shows the list of changes/unchanged files in a project.
drush_hacked_details_validate Validate hook for the hacked_details drush command.
drush_hacked_diff Drush callback that shows the list of changes/unchanged files in a project.
drush_hacked_diff_validate Validate hook for the hacked_diff drush command.
drush_hacked_drush_command_validate Validate hook for the hacked drush commands that need a project.
drush_hacked_list_projects Drush command callback that shows the listing of changed/unchanged projects.
drush_hacked_lock_modified Lock all of the modified files so that pm-updatecode will not touch them.
drush_hacked_pre_pm_updatecode Add a --lock-modified flag to pm-updatecode.
hacked_build_report_batch_finished_drush Completion callback for the report batch.
hacked_calculate_project_data_drush Compute the report data for hacked.
hacked_drush_command Implements hook_drush_command().
hacked_drush_help Implements hook_drush_help().
hacked_drush_help_alter Implements hook_drush_help_alter().