--- /dev/null
+<?php
+
+namespace Drupal\security_review;
+
+use Drupal\Core\Access\AccessException;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+
+/**
+ * Contains static functions for handling checks throughout every module.
+ */
+class Checklist {
+
+ use DependencySerializationTrait;
+
+ /**
+ * The current user.
+ *
+ * @var \Drupal\Core\Session\AccountProxyInterface
+ */
+ protected $currentUser;
+
+ /**
+ * The security_review service.
+ *
+ * @var \Drupal\security_review\SecurityReview
+ */
+ protected $securityReview;
+
+ /**
+ * The module handler.
+ *
+ * @var \Drupal\Core\Extension\ModuleHandlerInterface
+ */
+ protected $moduleHandler;
+
+ /**
+ * Constructs a Checklist instance.
+ *
+ * @param \Drupal\security_review\SecurityReview $security_review
+ * The SecurityReview service.
+ * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+ * The module handler.
+ * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+ * The current user.
+ */
+ public function __construct(SecurityReview $security_review, ModuleHandlerInterface $module_handler, AccountProxyInterface $current_user) {
+ $this->securityReview = $security_review;
+ $this->moduleHandler = $module_handler;
+ $this->currentUser = $current_user;
+ }
+
+ /**
+ * Array of cached Checks.
+ *
+ * @var \Drupal\security_review\Check[]
+ */
+ private static $cachedChecks = [];
+
+ /**
+ * Clears the cached checks.
+ */
+ public static function clearCache() {
+ static::$cachedChecks = [];
+ }
+
+ /**
+ * Returns every Check.
+ *
+ * @return \Drupal\security_review\Check[]
+ * Array of Checks.
+ */
+ public function getChecks() {
+ $checks = &static::$cachedChecks;
+ if (!empty($checks)) {
+ return $checks;
+ }
+
+ // Get checks.
+ $raw_checks = $this->moduleHandler->invokeAll('security_review_checks');
+
+ // Filter invalid checks.
+ $checks = [];
+ foreach ($raw_checks as $raw_check) {
+ if ($raw_check instanceof Check) {
+ $checks[] = $raw_check;
+ }
+ }
+
+ // Sort the checks.
+ usort($checks, [$this, 'compareChecks']);
+
+ return $checks;
+ }
+
+ /**
+ * Returns the enabled Checks.
+ *
+ * @return \Drupal\security_review\Check[]
+ * Array of enabled Checks.
+ */
+ public function getEnabledChecks() {
+ $enabled = [];
+
+ foreach (static::getChecks() as $check) {
+ if (!$check->isSkipped()) {
+ $enabled[] = $check;
+ }
+ }
+
+ return $enabled;
+ }
+
+ /**
+ * Groups an array of checks by their namespaces.
+ *
+ * @param \Drupal\security_review\Check[] $checks
+ * The array of Checks to group.
+ *
+ * @return array
+ * Array containing Checks grouped by their namespaces.
+ */
+ public function groupChecksByNamespace(array $checks) {
+ $output = [];
+
+ foreach ($checks as $check) {
+ $output[$check->getMachineNamespace()][] = $check;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Runs enabled checks and stores their results.
+ */
+ public function runChecklist() {
+ if ($this->currentUser->hasPermission('run security checks')) {
+ $checks = $this->getEnabledChecks();
+ $results = $this->runChecks($checks);
+ $this->storeResults($results);
+ $this->securityReview->setLastRun(time());
+ }
+ else {
+ throw new AccessException();
+ }
+ }
+
+ /**
+ * Runs an array of checks.
+ *
+ * @param \Drupal\security_review\Check[] $checks
+ * The array of Checks to run.
+ * @param bool $cli
+ * Whether to call runCli() instead of run().
+ *
+ * @return \Drupal\security_review\CheckResult[]
+ * The array of CheckResults generated.
+ */
+ public function runChecks(array $checks, $cli = FALSE) {
+ $results = [];
+
+ foreach ($checks as $check) {
+ if ($cli) {
+ $result = $check->runCli();
+ }
+ else {
+ $result = $check->run();
+ }
+ $this->securityReview->logCheckResult($result);
+ $results[] = $result;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Stores an array of CheckResults.
+ *
+ * @param \Drupal\security_review\CheckResult[] $results
+ * The CheckResults to store.
+ */
+ public function storeResults(array $results) {
+ foreach ($results as $result) {
+ $result->check()->storeResult($result);
+ }
+ }
+
+ /**
+ * Finds a check by its namespace and title.
+ *
+ * @param string $namespace
+ * The machine namespace of the requested check.
+ * @param string $title
+ * The machine title of the requested check.
+ *
+ * @return null|\Drupal\security_review\Check
+ * The Check or null if it doesn't exist.
+ */
+ public function getCheck($namespace, $title) {
+ foreach (static::getChecks() as $check) {
+ $same_namespace = $check->getMachineNamespace() == $namespace;
+ $same_title = $check->getMachineTitle() == $title;
+ if ($same_namespace && $same_title) {
+ return $check;
+ }
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Finds a Check by its id.
+ *
+ * @param string $id
+ * The machine namespace of the requested check.
+ *
+ * @return null|\Drupal\security_review\Check
+ * The Check or null if it doesn't exist.
+ */
+ public function getCheckById($id) {
+ foreach (static::getChecks() as $check) {
+ if ($check->id() == $id) {
+ return $check;
+ }
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Helper function for sorting checks.
+ *
+ * @param \Drupal\security_review\Check $a
+ * Check A.
+ * @param \Drupal\security_review\Check $b
+ * Check B.
+ *
+ * @return int
+ * The comparison's result.
+ */
+ public function compareChecks(Check $a, Check $b) {
+ // If one comes from security_review and the other doesn't, prefer the one
+ // with the security_review namespace.
+ $a_is_local = $a->getMachineNamespace() == 'security_review';
+ $b_is_local = $b->getMachineNamespace() == 'security_review';
+ if ($a_is_local && !$b_is_local) {
+ return -1;
+ }
+ elseif (!$a_is_local && $b_is_local) {
+ return 1;
+ }
+ else {
+ if ($a->getNamespace() == $b->getNamespace()) {
+ // If the namespaces match, sort by title.
+ return strcmp($a->getTitle(), $b->getTitle());
+ }
+ else {
+ // If the namespaces don't mach, sort by namespace.
+ return strcmp($a->getNamespace(), $b->getNamespace());
+ }
+ }
+ }
+
+}