5 * Hacked drush command.
7 * Enables drush support for the Hacked! module.
10 use Drupal\hacked\hackedProject;
13 * Implements hook_drush_help().
15 function hacked_drush_help($section) {
17 case 'drush:hacked-list-projects':
18 return dt('List projects and their hacked/unhacked status.');
20 case 'drush:hacked-details':
21 return dt('Show details of the files in one project, and the hacked/unhacked status of those files.');
23 case 'drush:hacked-diff':
24 return dt('Output a unified diff of the specified project.');
29 * Implements hook_drush_command().
31 * @See drush_parse_command() for a list of recognized keys.
33 function hacked_drush_command() {
34 $items['hacked-list-projects'] = [
35 'description' => "List all projects that can be analysed by Hacked! ",
36 'drupal dependencies' => ['hacked'],
38 'force-rebuild' => 'Rebuild the Hacked! report instead of getting a cached version.',
43 $items['hacked-lock-modified'] = [
44 'description' => "Lock all projects that Hacked! detects are modified, so that drush pm-updatecode will not touch them. (drush-4.x+ only)",
45 'drupal dependencies' => ['hacked'],
48 $items['hacked-details'] = [
49 'description' => "Show the Hacked! report about a specific project.",
50 'drupal dependencies' => ['hacked'],
52 'project' => 'The machine name of the project to report on.',
55 'include-unchanged' => 'Show the files that are unchanged too.',
60 $items['hacked-diff'] = [
61 'description' => "Output a unified diff of the project specified.",
62 'drupal dependencies' => ['hacked'],
64 'project' => 'The machine name of the project to report on.',
67 'diff-options' => 'Command line options to pass through to the diff command.',
75 * Compute the report data for hacked.
77 * WARNING: This function can invoke a batch process and end your current page.
78 * So you'll want to be very careful if you call this!
80 * @param array $projects
81 * An array of Drupal projects.
82 * @param bool|FALSE $force
83 * If TRUE, force rebuild of project data.
85 function hacked_calculate_project_data_drush($projects, $force = FALSE) {
86 include_once DRUPAL_ROOT . '/core/includes/batch.inc';
88 // Try to get the report form cache if we can.
89 $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get('hacked:drush:full-report');
90 if (!empty($cache->data) && !$force) {
94 // Enter a batch to build the report.
96 foreach ($projects as $project) {
98 'hacked_build_report_batch',
104 'operations' => $operations,
105 'finished' => 'hacked_build_report_batch_finished_drush',
106 'file' => drupal_get_path('module', 'hacked') . '/hacked.report.inc',
107 'title' => t('Building report'),
110 drush_print('Rebuilding Hacked! report');
112 $batch =& batch_get();
113 $batch['progressive'] = FALSE;
114 drush_backend_batch_process();
115 drush_print('Done.');
117 // Now we can get the data from the cache.
118 $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get('hacked:drush:full-report');
119 if (!empty($cache->data)) {
125 * Completion callback for the report batch.
127 * @param bool $success
128 * Boolean value of batch success.
129 * @param array $results
130 * An array of batch results.
132 function hacked_build_report_batch_finished_drush($success, $results) {
135 usort($results['report'], '_hacked_project_report_sort_by_status');
137 \Drupal::cache(HACKED_CACHE_TABLE)
138 ->set('hacked:drush:full-report', $results['report'], strtotime('+1 day'));
143 * Drush command callback that shows the listing of changed/unchanged projects.
145 function drush_hacked_list_projects() {
147 module_load_include('inc', 'update', 'update.report');
148 if ($available = update_get_available(TRUE)) {
149 module_load_include('inc', 'update', 'update.compare');
150 $data = update_calculate_project_data($available);
151 $force_rebuild = drush_get_option('force-rebuild', FALSE);
152 $projects = hacked_calculate_project_data_drush($data, $force_rebuild);
153 // Now print the data using drush:
162 foreach ($projects as $project) {
166 $project['existing_version'],
169 // Now add the status:
170 switch ($project['status']) {
171 case HACKED_STATUS_UNHACKED:
172 $row[] = dt('Unchanged');
175 case HACKED_STATUS_HACKED:
176 $row[] = t('Changed');
179 case HACKED_STATUS_UNCHECKED:
181 $row[] = t('Unchecked');
185 $row[] = $project['counts']['different'];
186 $row[] = $project['counts']['missing'];
190 drush_print_table($rows, TRUE);
197 * Lock all of the modified files so that pm-updatecode will not touch them.
199 function drush_hacked_lock_modified() {
200 $drupal_root = drush_get_context('DRUSH_DRUPAL_ROOT');
201 module_load_include('inc', 'update', 'update.report');
202 if (isset($drupal_root) && ($available = update_get_available(TRUE))) {
203 module_load_include('inc', 'update', 'update.compare');
204 $data = update_calculate_project_data($available);
205 $projects = hacked_calculate_project_data_drush($data, TRUE);
207 foreach ($projects as $project) {
209 empty($project['title']) ? $project['name'] : $project['title'],
211 $project['existing_version'],
214 // Lock the file if it is not already locked.
215 switch ($project['status']) {
216 case HACKED_STATUS_HACKED:
218 $project_ob = new hackedProject($project['project_name']);
219 $lockfile = $project_ob->file_get_location('local', '.drush-lock-update');
220 if (!file_exists($lockfile)) {
221 drush_op('file_put_contents', $lockfile, dt("Locked: modified."));
222 drush_print(dt("Locked: @project", ['@project' => $project['name']]));
225 drush_print(dt("@project is modified and already locked", ['@project' => $project['name']]));
229 case HACKED_STATUS_UNHACKED:
230 case HACKED_STATUS_UNCHECKED:
239 * Add a --lock-modified flag to pm-updatecode.
241 function drush_hacked_pre_pm_updatecode() {
242 if (drush_get_option('lock-modified')) {
243 drush_print(dt('Hacked! is checking for modified projects...'));
244 drush_hacked_lock_modified();
245 drush_print(dt('Hacked! modification check complete.'));
250 * Implements hook_drush_help_alter().
252 * Add --lock-modified to the pm-updatecode and pm-update help.
254 function hacked_drush_help_alter(&$command) {
255 if (($command['command'] == 'pm-updatecode') || ($command['command'] == 'pm-update')) {
256 $command['sub-options']['--lock']['--lock-modified'] = "Lock any project that Hacked! determines is modified.";
261 * Validate hook for the hacked_details drush command.
263 * @param string $short_name
264 * The project short name.
266 function drush_hacked_details_validate($short_name = '') {
267 return drush_hacked_drush_command_validate($short_name);
271 * Validate hook for the hacked drush commands that need a project.
273 * @param string $short_name
274 * The project short name.
276 function drush_hacked_drush_command_validate($short_name = '') {
277 if (empty($short_name)) {
278 return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('A valid project must be specified', ['@project' => $short_name]));
281 $project = new hackedProject($short_name);
282 $project->identify_project();
283 if (!$project->project_identified) {
284 return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('Could not find project: @project', ['@project' => $short_name]));
290 * Drush callback that shows the list of changes/unchanged files in a project.
292 * You may specify the --include-unchanged option to show unchanged files too,
293 * otherwise just the changed and deleted files are shown.
295 * @param string $short_name
296 * The project short name.
298 function drush_hacked_details($short_name) {
299 $project = new hackedProject($short_name);
300 $report = $project->compute_details();
302 drush_print(dt('Details for project: @name', ['@name' => $project->title()]));
303 drush_print(dt('Total files: @total_files, files changed: @changed_files, deleted files: @deleted_files', [
304 '@total_files' => count($report['files']),
305 '@changed_files' => $report['counts']['different'],
306 '@deleted_files' => $report['counts']['missing'],
310 drush_print(dt('Detailed results:'));
312 arsort($report['files']);
318 $show_unchanged = drush_get_option('include-unchanged', FALSE);
319 foreach ($report['files'] as $file => $status) {
320 if (!$show_unchanged && $status == HACKED_STATUS_UNHACKED) {
325 // Now add the status:
327 case HACKED_STATUS_UNHACKED:
328 $row[] = dt('Unchanged');
331 case HACKED_STATUS_HACKED:
332 $row[] = t('Changed');
335 case HACKED_STATUS_DELETED:
336 $row[] = t('Deleted');
339 case HACKED_STATUS_UNCHECKED:
341 $row[] = t('Unchecked');
349 drush_print_table($rows, TRUE);
353 * Validate hook for the hacked_diff drush command.
355 * @param string $short_name
356 * The project short name.
358 function drush_hacked_diff_validate($short_name = '') {
359 return drush_hacked_drush_command_validate($short_name);
363 * Drush callback that shows the list of changes/unchanged files in a project.
365 * You may specify the --include-unchanged option to show unchanged files too,
366 * otherwise just the changed and deleted files are shown.
368 * @param string $short_name
369 * The project short name.
371 function drush_hacked_diff($short_name) {
372 $project = new hackedProject($short_name);
374 $local_location = $project->file_get_location('local', '');
375 $clean_location = $project->file_get_location('remote', '');
377 // If the hasher is our ignore line endings one, then ignore line endings.
378 $hasher = \Drupal::config('hacked.settings')->get('selected_file_hasher');
379 $hasher = is_null($hasher) ? HACKED_DEFAULT_FILE_HASHER : $hasher;
380 if ($hasher == 'hacked_ignore_line_endings') {
381 $default_options = '-uprb';
384 $default_options = '-upr';
387 $diff_options = drush_get_option('diff-options', $default_options);
388 drush_shell_exec("diff $diff_options $clean_location $local_location");
390 $lines = drush_shell_exec_output();
391 $local_location_trim = dirname($local_location . '/dummy.file') . '/';
392 $clean_location_trim = dirname($clean_location . '/dummy.file') . '/';
393 foreach ($lines as $line) {
394 if (strpos($line, '+++') === 0) {
395 $line = str_replace($local_location_trim, '', $line);
397 if (strpos($line, '---') === 0) {
398 $line = str_replace($clean_location_trim, '', $line);
400 if (strpos($line, 'diff -upr') === 0) {
401 $line = str_replace($clean_location_trim, 'a/', $line);
402 $line = str_replace($local_location_trim, 'b/', $line);