From 16e1e45caca3d0a1b1e2eb361d36e2f45932a7a1 Mon Sep 17 00:00:00 2001
From: Colan Schwartz
Date: Mon, 29 Feb 2016 18:47:44 -0500
Subject: [PATCH] Issue #2066371 by FluxSauce, codi, colan: Added Drush support
without installation & integrated with Site Audit.
---
hacked.drush.inc | 185 +++++++++++++++++++++++++----------
hacked.module | 10 +-
hacked.report.inc | 12 ++-
src/SiteAuditCheckSecurityHacked.php | 114 +++++++++++++++++++++
src/hackedFileHasher.php | 21 +++-
5 files changed, 287 insertions(+), 55 deletions(-)
create mode 100644 src/SiteAuditCheckSecurityHacked.php
diff --git a/hacked.drush.inc b/hacked.drush.inc
index ece0d4c..caa7b23 100644
--- a/hacked.drush.inc
+++ b/hacked.drush.inc
@@ -32,39 +32,35 @@ function hacked_drush_help($section) {
*/
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.',
+ 'description' => dt('List all projects that can be analysed by Hacked!'),
+ 'options' => [
+ 'force-rebuild' => dt('Rebuild the Hacked! report instead of getting a cached version.'),
],
- 'aliases' => ['hlp'],
+ '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'],
+ 'description' => dt('Lock all projects that Hacked! detects are modified, so that drush pm-updatecode will not touch them. (drush-4.x+ only)'),
];
$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.',
+ 'description' => dt('Show the Hacked! report about a specific project.'),
+ 'arguments' => [
+ 'project' => dt('The machine name of the project to report on.'),
],
- 'options' => [
- 'include-unchanged' => 'Show the files that are unchanged too.',
+ 'options' => [
+ 'include-unchanged' => dt('Show the files that are unchanged too.'),
],
- 'aliases' => ['hd'],
+ '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.',
+ 'description' => dt('Output a unified diff of the project specified.'),
+ 'arguments' => [
+ 'project' => dt('The machine name of the project to report on.'),
],
- 'options' => [
- 'diff-options' => 'Command line options to pass through to the diff command.',
+ 'options' => [
+ 'diff-options' => dt('Command line options to pass through to the diff command.'),
],
];
@@ -83,10 +79,10 @@ function hacked_drush_command() {
* If TRUE, force rebuild of project data.
*/
function hacked_calculate_project_data_drush($projects, $force = FALSE) {
- include_once DRUPAL_ROOT . '/core/includes/batch.inc';
+ hacked_load_drush_dependencies();
// Try to get the report form cache if we can.
- $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get('hacked:drush:full-report');
+ $cache = drush_cache_get('hacked:drush:full-report', HACKED_CACHE_TABLE);
if (!empty($cache->data) && !$force) {
return $cache->data;
}
@@ -95,7 +91,7 @@ function hacked_calculate_project_data_drush($projects, $force = FALSE) {
$operations = [];
foreach ($projects as $project) {
$operations[] = [
- 'hacked_build_report_batch',
+ 'hacked_build_report_batch_drush',
[$project['name']],
];
}
@@ -103,8 +99,13 @@ function hacked_calculate_project_data_drush($projects, $force = FALSE) {
$batch = [
'operations' => $operations,
'finished' => 'hacked_build_report_batch_finished_drush',
- 'file' => drupal_get_path('module', 'hacked') . '/hacked.report.inc',
- 'title' => t('Building report'),
+ // As this module isn't guaranteed to be installed, we need to override the
+ // assumption that we're relative to base_path(). So traverse up the
+ // directory tree until we get to the root, and then provide the absolute
+ // file system path to the include file. This will only work up to a
+ // certain depth, however.
+ 'file' => '../../../../../../../../../../../../..' . __DIR__ . '/hacked.report.inc',
+ 'title' => dt('Building report'),
];
drush_print('Rebuilding Hacked! report');
@@ -115,12 +116,33 @@ function hacked_calculate_project_data_drush($projects, $force = FALSE) {
drush_print('Done.');
// Now we can get the data from the cache.
- $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get('hacked:drush:full-report');
+ $cache = drush_cache_get('hacked:drush:full-report', HACKED_CACHE_TABLE);
if (!empty($cache->data)) {
return $cache->data;
}
}
+ /**
+ * Batch callback to build the hacked report.
+ *
+ * @param string $project_name
+ * The name of the project.
+ * @param array $context
+ * The current context (modified to include the results on return).
+ */
+function hacked_build_report_batch_drush($project_name, &$context) {
+ hacked_load_drush_dependencies();
+ if (!isset($context['results']['report'])) {
+ $context['results']['report'] = array();
+ }
+
+ $project = new hackedProject($project_name);
+ $context['results']['report'][$project_name] = $project->compute_report();
+ $context['message'] = dt('Finished processing: @name', array(
+ '@name' => $project->title(),
+ ));
+}
+
/**
* Completion callback for the report batch.
*
@@ -134,22 +156,34 @@ function hacked_build_report_batch_finished_drush($success, $results) {
// 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_cache_set('hacked:drush:full-report', $results['report'], HACKED_CACHE_TABLE, strtotime('+1 day'));
+ }
+}
+
+ /**
+ * Validation callback for drush hacked_list_projects.
+ */
+function drush_hacked_list_projects_validate() {
+ if (!function_exists('update_get_available')) {
+ $message = dt('Cannot list projects without the Drupal core update module enabled.');
+ return drush_set_error('HACKED_UPDATE_DISABLED', $message);
}
}
/**
- * Drush command callback that shows the listing of changed/unchanged projects.
+ * Command callback for drush hacked_list_projects.
*/
function drush_hacked_list_projects() {
- // Go get the data:
+ hacked_load_drush_dependencies();
module_load_include('inc', 'update', 'update.report');
+
if ($available = update_get_available(TRUE)) {
+ // Get the data.
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'),
@@ -173,12 +207,12 @@ function drush_hacked_list_projects() {
break;
case HACKED_STATUS_HACKED:
- $row[] = t('Changed');
+ $row[] = dt('Changed');
break;
case HACKED_STATUS_UNCHECKED:
default:
- $row[] = t('Unchecked');
+ $row[] = dt('Unchecked');
break;
}
@@ -218,11 +252,15 @@ function drush_hacked_lock_modified() {
$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']]));
+ 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']]));
+ drush_print(dt('@project is modified and already locked', [
+ '@project' => $project['name'],
+ ]));
}
break;
@@ -253,7 +291,7 @@ function drush_hacked_pre_pm_updatecode() {
*/
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.";
+ $command['sub-options']['--lock']['--lock-modified'] = dt('Lock any project that Hacked! determines is modified.');
}
}
@@ -268,29 +306,36 @@ function drush_hacked_details_validate($short_name = '') {
}
/**
- * Validate hook for the hacked drush commands that need a project.
+ * Generic validator for hacked drush commands.
*
* @param string $short_name
* The project short name.
+ *
+ * @return null|boolean
+ * Return FALSE if there's an error.
*/
function drush_hacked_drush_command_validate($short_name = '') {
+ hacked_load_drush_dependencies();
+
if (empty($short_name)) {
- return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('A valid project must be specified', ['@project' => $short_name]));
+ return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('You must specify the name of a project.'));
}
$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]));
+ return drush_set_error('HACKED_PROJECT_NOT_FOUND', dt('Unable to find "@project".', [
+ '@project' => $short_name,
+ ]));
}
- $project = NULL;
}
/**
- * Drush callback that shows the list of changes/unchanged files in a project.
+ * Command callback for drush hacked_details.
*
- * You may specify the --include-unchanged option to show unchanged files too,
- * otherwise just the changed and deleted files are shown.
+ * 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.
@@ -329,16 +374,16 @@ function drush_hacked_details($short_name) {
break;
case HACKED_STATUS_HACKED:
- $row[] = t('Changed');
+ $row[] = dt('Changed');
break;
case HACKED_STATUS_DELETED:
- $row[] = t('Deleted');
+ $row[] = dt('Deleted');
break;
case HACKED_STATUS_UNCHECKED:
default:
- $row[] = t('Unchecked');
+ $row[] = dt('Unchecked');
break;
}
@@ -350,7 +395,7 @@ function drush_hacked_details($short_name) {
}
/**
- * Validate hook for the hacked_diff drush command.
+ * Validation callback for drush hacked_diff.
*
* @param string $short_name
* The project short name.
@@ -360,10 +405,11 @@ function drush_hacked_diff_validate($short_name = '') {
}
/**
- * Drush callback that shows the list of changes/unchanged files in a project.
+ * Command callback for drush hacked_diff.
*
- * You may specify the --include-unchanged option to show unchanged files too,
- * otherwise just the changed and deleted files are shown.
+ * 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.
@@ -405,3 +451,42 @@ function drush_hacked_diff($short_name) {
drush_print($line);
}
}
+
+/**
+ * Add missing dependencies for Drush (if module not installed).
+ *
+ * These inclusions would normally be available automatically when the module
+ * is installed, but they must be explicitly included if it isn't.
+ */
+function hacked_load_drush_dependencies() {
+ if (!\Drupal::moduleHandler()->moduleExists('hacked')) {
+ drush_log(dt('Loading Hacked! dependencies.'));
+
+ // Get the command path.
+ $path = __DIR__;
+
+ // Use it to include missing dependencies.
+ require_once $path . '/hacked.module';
+ require_once $path . '/src/hackedFileGroup.php';
+ require_once $path . '/src/hackedFileHasher.php';
+ require_once $path . '/src/hackedFileIgnoreEndingsHasher.php';
+ require_once $path . '/src/hackedFileIncludeEndingsHasher.php';
+ require_once $path . '/src/hackedProject.php';
+ require_once $path . '/src/hackedProjectWebDownloader.php';
+ require_once $path . '/src/hackedProjectWebDevDownloader.php';
+ require_once $path . '/src/hackedProjectWebFilesDownloader.php';
+ require_once $path . '/hacked.report.inc';
+ }
+}
+
+/**
+ * Implements hook_drush_command_alter().
+ */
+function hacked_drush_command_alter(&$command) {
+ if ($command['command'] == 'audit_security') {
+ $command['checks'][] = array(
+ 'name' => 'Hacked',
+ 'location' => __DIR__ . '/hacked.site_audit.inc',
+ );
+ }
+ }
\ No newline at end of file
diff --git a/hacked.module b/hacked.module
index 274f7bd..4a49602 100644
--- a/hacked.module
+++ b/hacked.module
@@ -138,8 +138,16 @@ function hacked_file_scan_directory($dir, $mask, $nomask = ['.', '..', 'CVS'], $
function hacked_get_file_hasher($name = NULL) {
if (is_null($name)) {
$name = \Drupal::config('hacked.settings')->get('selected_file_hasher');
+ $name = $name ? $name : HACKED_DEFAULT_FILE_HASHER;
}
- $hashers = hacked_get_file_hashers();
+
+ if (!\Drupal::moduleHandler()->moduleExists('hacked')) {
+ $hashers = hacked_hacked_file_hashers_info();
+ }
+ else {
+ $hashers = hacked_get_file_hashers();
+ }
+
$class_name = $hashers[$name]['class'];
return new $class_name;
}
diff --git a/hacked.report.inc b/hacked.report.inc
index 862e7ca..c3c613d 100644
--- a/hacked.report.inc
+++ b/hacked.report.inc
@@ -1,4 +1,8 @@
loadInclude('hacked', 'inc', 'includes/hacked_project');
+
$project = new hackedProject($project_name);
$context['results']['report'][$project_name] = $project->compute_report();
$context['message'] = t('Finished processing: @name', array('@name' => $project->title()));
diff --git a/src/SiteAuditCheckSecurityHacked.php b/src/SiteAuditCheckSecurityHacked.php
new file mode 100644
index 0000000..340a216
--- /dev/null
+++ b/src/SiteAuditCheckSecurityHacked.php
@@ -0,0 +1,114 @@
+' . $ret_val . '
';
+ $ret_val .= '';
+ $ret_val .= 'Name | Title | Version | Changed |
';
+ $ret_val .= '';
+ foreach ($this->registry['hacked'] as $row) {
+ $ret_val .= '' . implode(' | ', $row) . ' |
';
+ }
+ $ret_val .= '';
+ $ret_val .= '
';
+ }
+ else {
+ foreach ($this->registry['hacked'] as $row) {
+ $ret_val .= PHP_EOL . str_repeat(' ', 6);
+ $ret_val .= $row['project_name'] . ' v' . $row['project_version'];
+ $ret_val .= ': ' . $row['lines_different'] . ' line(s)';
+ }
+ }
+ return $ret_val;
+ }
+
+ /**
+ * Implements \SiteAudit\Check\Abstract\getAction().
+ */
+ public function getAction() {}
+
+ /**
+ * Implements \SiteAudit\Check\Abstract\calculateScore().
+ */
+ public function calculateScore() {
+ $result = drush_invoke_process('@self', 'hacked-list-projects', array(), array('--include-unchanged=0', '--strict=0'), FALSE);
+ if ($result === FALSE) {
+ return SiteAuditCheckAbstract::AUDIT_CHECK_SCORE_INFO;
+ }
+
+ $rows = array();
+ foreach ($result['object'] as $info) {
+ if ($info['counts']['different'] != 0) {
+ $rows[] = array(
+ 'project_name' => $info['project_name'],
+ 'project_title' => $info['title'],
+ 'project_version' => $info['existing_version'],
+ 'lines_different' => $info['counts']['different'],
+ );
+ }
+ }
+
+ $this->registry['hacked'] = $result;
+ if (empty($result['object'])) {
+ return SiteAuditCheckAbstract::AUDIT_CHECK_SCORE_FAIL;
+ }
+ else {
+ if (!empty($rows)) {
+ $this->registry['hacked'] = $rows;
+ return SiteAuditCheckAbstract::AUDIT_CHECK_SCORE_WARN;
+ }
+ else {
+ return SiteAuditCheckAbstract::AUDIT_CHECK_SCORE_PASS;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/hackedFileHasher.php b/src/hackedFileHasher.php
index 3fc6600..c318418 100644
--- a/src/hackedFileHasher.php
+++ b/src/hackedFileHasher.php
@@ -30,11 +30,28 @@ abstract class hackedFileHasher {
}
function cache_set($filename, $hash) {
- \Drupal::cache(HACKED_CACHE_TABLE)->set($this->cache_key($filename), $hash, strtotime('+7 days'));
+ // Check if we're running as a Drush command.
+ if (function_exists('drush_verify_cli') && drush_verify_cli()) {
+ // Use the Drush cache.
+ drush_cache_set($this->cache_key($filename), $hash, HACKED_CACHE_TABLE, strtotime('+7 days'));
+ }
+ else {
+ // Use the Drupal cache.
+ \Drupal::cache(HACKED_CACHE_TABLE)->set($this->cache_key($filename), $hash, strtotime('+7 days'));
+ }
}
function cache_get($filename) {
- $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get($this->cache_key($filename));
+ // Check if we're running as a Drush command.
+ if (function_exists('drush_verify_cli') && drush_verify_cli()) {
+ // Use the Drush cache.
+ $cache = drush_cache_get($this->cache_key($filename), HACKED_CACHE_TABLE);
+ }
+ else {
+ // Use the Drupal cache.
+ $cache = \Drupal::cache(HACKED_CACHE_TABLE)->get($this->cache_key($filename));
+ }
+
if (!empty($cache->data)) {
return $cache->data;
}
--
2.5.0