diff --git a/acquia_agent/acquia_agent.module b/acquia_agent/acquia_agent.module index eb865af..d25e640 100644 --- a/acquia_agent/acquia_agent.module +++ b/acquia_agent/acquia_agent.module @@ -529,6 +529,8 @@ function acquia_agent_help($path, $arg) { $output .= '
' . t('Enables secure communication between your Drupal sites and the Acquia Network.') . ''; $output .= '
Acquia SPI
'; $output .= '
' . t('Automates the collection of site information. Required for use with the Acquia Insight service.') . ''; + $output .= '
Acquia SPI Custom Tests
'; + $output .= '
' . t('Acquia Insight supports custom tests for your site. See acquia_spi.api.php for information on the custom test hook and validate your tests for inclusion in outgoing SPI data with the Drush command, spi-test-validate.') . ''; $output .= '
Acquia Search
'; $output .= '
' . t('Provides authentication service to the Apache Solr Search Integration module to enable use of Acquia\'s hosted Solr search indexes.') . ''; $output .= ''; diff --git a/acquia_spi/acquia_spi.api.php b/acquia_spi/acquia_spi.api.php index bae4a6b..20e7ace 100644 --- a/acquia_spi/acquia_spi.api.php +++ b/acquia_spi/acquia_spi.api.php @@ -33,3 +33,42 @@ function hook_acquia_spi_get() { ); return $data; } + +/** + * Include data to be sent to Acquia Insight as part of the SPI process. This + * data will be stored on Acquia's servers in an unencrypted database, so be + * careful not to send sensitive information in any field. Multiple tests can + * also be added per callback provided that each test has a unique identifier. + * + * @return + * An array of user-contributed test data keyed by unique identifier. + * - (string) description: Detailed information regarding test, its impact, + * and other relevant details. Cannot exceed 1024 characters. + * - (string) solved_message: The message to display when the test has + * succeeded. Cannot exceed 1024 characters. + * - (string) failed_message: The message to display when the test has failed. + * Cannot exceed 1024 characters. + * - (boolean) solved: A flag indicating whether or not the test was + * successful. + * - (string) fix_details: Information on how to fix or resolve the test if + * failed. Cannot exceed 1024 characters. + * - (string) category: The category to place the test within. Must be either + * 'performance', 'security, or 'best_practices'. + * - (int) severity: The priority level of the custom test. Must be either + * 0, 1, 2, 4, 8, 16, 32, 64, or 128. Higher severities impact + * the Insight score proportionally. + * + */ +function hook_acquia_spi_test() { + return array( + 'unique_example' => array( + 'description' => 'This example test is useful.', + 'solved_message' => 'The test was successful', + 'failed_message' => 'The test has failed', + 'solved' => TRUE, + 'fix_details' => 'Please resolve this issue using this fix information.', + 'category' => 'best_practices', + 'severity' => 0, + ), + ); +} diff --git a/acquia_spi/acquia_spi.drush.inc b/acquia_spi/acquia_spi.drush.inc index 7d6690c..06eefb5 100644 --- a/acquia_spi/acquia_spi.drush.inc +++ b/acquia_spi/acquia_spi.drush.inc @@ -19,6 +19,11 @@ function acquia_spi_drush_command() { 'spi-get --format=json --outfile=spi.json' => 'Write JSON encoded SPI data to spi.json in current directory.', ), ); + $items['spi-test-validate'] = array( + 'callback' => 'drush_acquia_spi_custom_test_validate', + 'description' => dt('Perform a validation check on any modules with Acquia SPI custom tests.'), + 'aliases' => array('spi-tv'), + ); return $items; } @@ -84,3 +89,62 @@ function _acquia_spi_drush_get() { } return acquia_spi_get(); } + +/** + * A command callback and drush wrapper for custom test validation. + * + */ +function drush_acquia_spi_custom_test_validate() { + + $pass = array(); + $failure = array(); + + $modules = module_implements('acquia_spi_test'); + drush_print_r(dt("Acquia SPI custom tests were detected in: @modules \n", array('@modules' => implode(', ', $modules)))); + + foreach (module_implements('acquia_spi_test') as $module) { + $function = $module . '_acquia_spi_test'; + if (function_exists($function)) { + $result = acquia_spi_test_validate($function()); + + if (!$result['result']) { + $failure[] = $module; + drush_print_r(dt("[FAILED] Validation failed for '@module' and has been logged.", array('@module' => $module))); + $custom_data[$module] = $result; + + foreach ($result['failure'] as $test_name => $test_failures) { + foreach ($test_failures as $test_param => $test_value) { + $variables = array( + '!module_name' => $module, + '@message' => $test_value['message'], + '!param_name' => $test_param, + '!test_name' => $test_name, + '!value' => $test_value['value'], + ); + drush_print_r(dt("[DETAILS] @message for parameter '!param_name'; current value '!value'. (Test !test_name in module !module_name)", $variables)); + watchdog('acquia spi test', "Custom test validation failed: @message for parameter '!param_name'; current value '!value'. (Test '!test_name' in module '!module_name')", $variables); + } + } + } + else { + $pass[] = $module; + drush_print_r(dt("[PASSED] Validation passed for '@module.'", array('@module' => $module))); + } + } + drush_print_r(""); + } + + $message = 'Validation checks completed.'; + if (count($pass) > 0) { + $variables['@passes'] = implode(', ', $pass); + $variables['@pass_count'] = count($pass); + $message .= "\n@pass_count module(s) passed validation: @passes."; + } + if (count($failure) > 0) { + $variables['@failures'] = implode(', ', $failure); + $variables['@fail_count'] = count($failure); + $message .= "\n@fail_count module(s) failed validation: @failures."; + } + + drush_print_r(dt($message, $variables)); +} diff --git a/acquia_spi/acquia_spi.install b/acquia_spi/acquia_spi.install index 652ef4a..f9f116d 100644 --- a/acquia_spi/acquia_spi.install +++ b/acquia_spi/acquia_spi.install @@ -79,6 +79,43 @@ function acquia_spi_requirements($phase) { 'description' => t($description, array('!interval' => $interval, '!config-page' => $config_url, '!spi-send' => url('system/acquia-spi-send', array('query' => array('destination' => 'admin/reports/status', 'key' => $key))))), ); } + + // Acquia SPI custom tests status. + $variables = array( + '@help' => url('admin/help/acquia_agent'), + '@validate' => url('system/acquia-spi-custom-test-validate') + ); + + $modules = module_implements('acquia_spi_test'); + if (empty($modules)) { + $description = 'No custom tests were detected in any module.
'; + $value = 'Not implemented (more information)'; + $severity = REQUIREMENT_OK; + } + else { + $result = acquia_spi_test_status(); + + if (!empty($result)) { + $modules = implode(', ', array_keys($result)); + $description = 'Custom tests within the following module(s) have failed validation and will not be sent: %modules.
Please check the error logs for more information regarding how to pass validation or perform another validation check. (A validation check can also be performed via the Drush command, "spi-test-validate.")'; + $value = 'Failed (more information)'; + $severity = REQUIREMENT_ERROR; + } + else { + $modules = implode(', ', $modules); + $description = 'Custom test data is structured properly and is sending from: %modules'; + $value = 'Passed'; + $severity = REQUIREMENT_OK; + } + + $variables['%modules'] = $modules; + } + $requirements['acquia_spi_test'] = array( + 'title' => t('Acquia Network SPI Custom Tests'), + 'description' => t($description, $variables), + 'value' => t($value, $variables), + 'severity' => $severity, + ); break; } diff --git a/acquia_spi/acquia_spi.module b/acquia_spi/acquia_spi.module index 944ef64..f95d2ce 100644 --- a/acquia_spi/acquia_spi.module +++ b/acquia_spi/acquia_spi.module @@ -64,6 +64,13 @@ function acquia_spi_menu() { 'page callback' => '_acquia_spi_send', 'access callback' => '_acquia_spi_send_access', ); + $items['system/acquia-spi-custom-test-validate'] = array( + 'title' => 'Acquia SPI Custom Test Validation', + 'description' => 'Perform a validation check on all Acquia SPI custom tests.', + 'page callback' => 'acquia_spi_test_status', + 'page arguments' => array(TRUE), + 'access callback' => '_acquia_spi_send_access', + ); return $items; } @@ -558,6 +565,13 @@ function acquia_spi_get() { if (!empty($security_review_results)) { $additional_data['security_review'] = $security_review_results['security_review']; } + + // Collect all user-contributed custom tests that pass validation. + $custom_tests_results = acquia_spi_test_collect(); + if (!empty($custom_tests_results)) { + $additional_data['custom_tests'] = $custom_tests_results; + } + $spi_data = module_invoke_all('acquia_spi_get'); if (!empty($spi_data)) { foreach ($spi_data as $name => $data) { @@ -669,6 +683,193 @@ function _acquia_spi_security_review_compatible() { } /** + * Collects all user-contributed test results that pass validation. + * + * @return array $custom_data + * An associative array containing properly formatted user-contributed tests. + * + */ +function acquia_spi_test_collect() { + + $custom_data = array(); + + // Collect all custom data provided by hook_insight_custom_data(). + $collections = module_invoke_all('acquia_spi_test'); + + foreach ($collections as $test_name => $test_params) { + $result = acquia_spi_test_validate(array($test_name => $test_params)); + + if ($result['result']) { + $custom_data[$test_name] = $test_params; + } + } + + return $custom_data; +} + +/** + * Implements hook_enable(). + * + */ +function acquia_spi_test_enable() { + + $modules = module_implements('acquia_spi_test'); + foreach ($modules as $module) { + if (function_exists($module . '_acquia_spi_test')) { + drupal_set_message(t("An invokation of hook_acquia_spi_test() has been detected in @module.", array('@module' => $module))); + watchdog('acquia agent', "An invokation of hook_acquia_spi_test() has been detected in @module.", array('@module' => $module)); + } + } +} + +/** + * Implements hook_modules_enabled(). + * + */ +function acquia_spi_test_modules_enabled($modules) { + + foreach ($modules as $module) { + if (function_exists($module . '_acquia_spi_test')) { + drupal_set_message(t("A new invokation of hook_acquia_spi_test() has been detected in @module.", array('@module' => $module))); + watchdog('acquia spi test', "A new invokation of hook_acquia_spi_test() has been detected in @module.", array('@module' => $module)); + } + } +} + + +/** + * Determines the status of all user-contributed tests and logs any failures to + * a tracking table. + * + * @param boolean $log + * (Optional) If TRUE, log all failures. + * + * @return array $custom_data + * An associative array containing any tests which failed validation. + * + */ +function acquia_spi_test_status($log = FALSE) { + + $custom_data = array(); + + // Iterate through modules which contain hook_acquia_spi_test(). + foreach (module_implements('acquia_spi_test') as $module) { + $function = $module . '_acquia_spi_test'; + if (function_exists($function)) { + $result = acquia_spi_test_validate($function()); + + if (!$result['result']) { + $custom_data[$module] = $result; + + foreach ($result['failure'] as $test_name => $test_failures) { + foreach ($test_failures as $test_param => $test_value) { + $variables = array( + '!module' => $module, + '@message' => $test_value['message'], + '!param_name' => $test_param, + '!test' => $test_name, + '!value' => $test_value['value'], + ); + + // Only log if we're performing a full validation check. + if ($log) { + drupal_set_message(t("Custom test validation failed for !test in !module and has been logged: @message for parameter '!param_name'; current value '!value'.", $variables), 'error'); + watchdog('acquia spi test', "Custom test validation failed: @message for parameter '!param_name'; current value '!value'. (Test '!test_name' in module '!module_name')", $variables, WATCHDOG_WARNING); + } + } + } + } + } + } + + // If a full validation check is being performed, go to the status page to + // show the results. + if ($log) { + drupal_goto('admin/reports/status'); + } + + return $custom_data; +} + +/** + * Validates data from custom test callbacks. + * + * @param array $collection + * An associative array containing a collection of user-contributed tests. + * + * @return array + * An associative array containing the validation result of the given tests, + * along with any failed parameters. + * + */ +function acquia_spi_test_validate($collection) { + + $result = TRUE; + $check_result_value = array(); + + // Load valid categories and severities. + $categories = array('performance', 'security', 'best_practices'); + $severities = array(0, 1, 2, 4, 8, 16, 32, 64, 128); + + foreach ($collection as $machine_name => $tests) { + foreach ($tests as $check_name => $check_value) { + $fail_value = ''; + $message = ''; + + $check_name = strtolower($check_name); + $check_value = (is_string($check_value)) ? strtolower($check_value) : $check_value; + + // Validate the data inputs for each check. + switch ($check_name) { + case 'category': + if (!is_string($check_value) || !in_array($check_value, $categories)) { + $type = gettype($check_value); + $fail_value = "$check_value ($type)"; + $message = 'Value must be a string and one of ' . implode(', ', $categories); + } + break; + + case 'solved': + if (!is_bool($check_value)) { + $type = gettype($check_value); + $fail_value = "$check_value ($type)"; + $message = 'Value must be a boolean'; + } + break; + + case 'severity': + if (!is_int($check_value) || !in_array($check_value, $severities)) { + $type = gettype($check_value); + $fail_value = "$check_value ($type)"; + $message = 'Value must be an integer and set to one of ' . implode(', ', $severities); + } + break; + + default: + if (!is_string($check_value) || strlen($check_value) > 1024) { + $type = gettype($check_value); + $fail_value = "$check_value ($type)"; + $message = 'Value must be a string'; + } + break; + } + + if (!empty($fail_value) && !empty($message)) { + $check_result_value['failed'][$machine_name][$check_name]['value'] = $fail_value; + $check_result_value['failed'][$machine_name][$check_name]['message'] = $message; + } + } + } + + // If there were any failures, the test has failed. Into exile it must go. + if (!empty($check_result_value)) { + $result = FALSE; + } + + return array('result' => $result, 'failure' => (isset($check_result_value['failed'])) ? $check_result_value['failed'] : array()); +} + +/** * Attempt to determine the version of Drupal being used. * Note, there is better information on this in the common.inc file. *