accessibility_scanner-8.x-1.0-alpha8/src/Plugin/CaptureUtility/AxeCoreCliCaptureUtility.php

src/Plugin/CaptureUtility/AxeCoreCliCaptureUtility.php
<?php

namespace Drupal\accessibility_scanner\Plugin\CaptureUtility;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\accessibility_scanner\Plugin\CaptureResponse\AxeCoreCliCaptureResponse;
use Drupal\web_page_archive\Plugin\ConfigurableCaptureUtilityBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Process\Process;
use Psr\Log\LoggerInterface;

/**
 * The @axe-core/cli accessibility scanner capture utility.
 *
 * @CaptureUtility(
 *   id = "wpa_axecore_cli_capture",
 *   label = @Translation("@axe-core/cli - Accessibility Scanner", context = "Web Page Archive"),
 *   description = @Translation("Scans URLs for accessibility issues using @axe-core/cli.", context = "Web Page Archive")
 * )
 */
class AxeCoreCliCaptureUtility extends ConfigurableCaptureUtilityBase {

  use DependencySerializationTrait;
  use MessengerTrait;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * Constructs a new AxeCoreCliCaptureUtility.
   *
   * @param array $configuration
   *   The plugin configuration, i.e. an array with configuration values keyed
   *   by configuration option name. The special key 'context' may be used to
   *   initialize the defined contexts by setting it to an array of context
   *   values keyed by context names.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param string $plugin_definition
   *   The plugin implementation definition.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory.
   * @param \Drupal\Core\File\FileSystemInterface $file_system
   *   The file system service.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger, ConfigFactoryInterface $config_factory, FileSystemInterface $file_system) {
    $this->configFactory = $config_factory;
    $this->fileSystem = $file_system;
    parent::__construct($configuration, $plugin_id, $plugin_definition, $logger);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('logger.factory')->get('accessibility_scanner'),
      $container->get('config.factory'),
      $container->get('file_system')
    );
  }

  /**
   * Most recent response.
   *
   * @var string|null
   */
  private $response = NULL;

  /**
   * {@inheritdoc}
   */
  public function capture(array $data = []) {
    // Attempt to validate the axe binary.
    $this->checkValidAxeBinary();

    // Retrieve the capture utility settings.
    $global_settings = $this->configFactory->get('web_page_archive.settings')->get('system');
    $capture_utility_settings = $this->configFactory->get('web_page_archive.wpa_axecore_cli_capture.settings')->get('system');

    // Handle missing URLs.
    if (!isset($data['url'])) {
      throw new \Exception('Capture URL is required');
    }

    // Get relative path.
    $filename = $this->getFileName($data, 'json');
    $real_path = $this->fileSystem->realpath($filename);
    $file_parts = pathinfo($real_path);

    $command = [
      $global_settings['node_path'],
      $capture_utility_settings['axecore_cli_binary'],
      '--tags',
      implode(',', array_filter($this->configuration['guidelines'])),
      $data['url'],
      '--save',
      $file_parts['basename'],
      '--dir',
      $file_parts['dirname'],
    ];

    if (!empty($this->configuration['selectors_exclude'])) {
      $command[] = '--exclude';
      $command[] = $this->configuration['selectors_exclude'];
    }

    if (!empty($this->configuration['selectors_include'])) {
      $command[] = '--include';
      $command[] = $this->configuration['selectors_include'];
    }

    $process = new Process($command);
    if (!empty($capture_utility_settings['node_modules_parent_path'])) {
      $process->setWorkingDirectory($capture_utility_settings['node_modules_parent_path']);
    }
    $process->run();

    if (!$process->isSuccessful()) {
      throw new \Exception("Failed to execute @axe-core/cli binary." . $process->getErrorOutput());
    }

    $this->response = new AxeCoreCliCaptureResponse($filename, $data['url']);

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function getResponse() {
    return $this->response;
  }

  /**
   * Encodes selectors list.
   */
  public function encodeSelectors($string) {
    $eol = PHP_EOL;
    $selectors = preg_split("/(${eol}|,)/", trim($string));
    $list = [];
    foreach ($selectors as $selector) {
      $full_selector = trim($selector);
      if (!empty($full_selector)) {
        $list[] = $full_selector;
      }
    }
    return implode(',', $list);
  }

  /**
   * Decodes selectors list.
   */
  public function decodeSelectors($string) {
    $eol = PHP_EOL;
    $selectors = preg_split("/(${eol}|,)/", trim($string));
    $list = [];
    foreach ($selectors as $selector) {
      $full_selector = trim($selector);
      if (!empty($full_selector)) {
        $list[] = $full_selector;
      }
    }
    return implode(PHP_EOL, $list);
  }

  /**
   * Returns a list of valid guidelines.
   *
   * @var array[]
   */
  public function getGuidelines() {
    return [
      'wcag2a' => $this->t('WCAG 2.0 Level A'),
      'wcag2aa' => $this->t('WCAG 2.0 Level AA'),
      'wcag21a' => $this->t('WCAG 2.1 Level A'),
      'wcag21aa' => $this->t('WCAG 2.1 Level AA'),
      'best-practice' => $this->t('Common accessibility best practices'),
      'ACT' => $this->t('W3C approved Accessibility Conformance Testing rules'),
      'section508' => $this->t('Old Section 508 rules'),
      'experimental' => $this->t('Cutting-edge rules'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'guidelines' => $this->configFactory->get('web_page_archive.wpa_axecore_cli_capture.settings')->get('defaults.guidelines'),
      'selectors_exclude' => '',
      'selectors_include' => '',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    try {
      $this->checkValidAxeBinary();
    }
    catch (\Exception $e) {
      $this->messenger()->addError($e->getMessage());
    }

    // Use the Form API to create fields. Each field should have corresponding
    // entry in your config/module.schema.yml file.
    $url = 'https://www.deque.com/axe/core-documentation/api-documentation/#axe-core-tags';
    $label = $this->t('Axe-core tags documentation');
    $axe_cli_documentation_link = $this->getFormDescriptionLinkFromUrl($url, $label);
    $form['guidelines'] = [
      '#type' => 'checkboxes',
      '#title' => $this->t('Guidelines'),
      '#description' => $this->t('For details about guidelines read the @documentation_link.', ['@documentation_link' => $axe_cli_documentation_link]),
      '#options' => $this->getGuidelines(),
      '#default_value' => $this->configuration['guidelines'],
    ];
    $form['selectors_exclude'] = [
      '#type' => 'textarea',
      '#title' => $this->t('CSS Selectors to Exclude'),
      '#description' => $this->t('You can specify any CSS selectors you would like excluded from the scan. One per line. Leave blank if you do not wish to exclude any selectors.'),
      '#default_value' => $this->decodeSelectors($this->configuration['selectors_exclude']),
    ];
    $form['selectors_include'] = [
      '#type' => 'textarea',
      '#title' => $this->t('CSS Selectors to Include'),
      '#description' => $this->t('You can specify CSS selectors if you only want to test specific areas of a page. One per line. Leave blank if you want to test the entire page.'),
      '#default_value' => $this->decodeSelectors($this->configuration['selectors_include']),
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    parent::submitConfigurationForm($form, $form_state);

    $this->configuration['guidelines'] = $form_state->getValue('guidelines');
    $this->configuration['selectors_exclude'] = $this->encodeSelectors($form_state->getValue('selectors_exclude'));
    $this->configuration['selectors_include'] = $this->encodeSelectors($form_state->getValue('selectors_include'));
  }

  /**
   * Helper function to determine if the axe binary is specified and working.
   */
  public function checkValidAxeBinary() {
    $settings = $this->configFactory->get('web_page_archive.wpa_axecore_cli_capture.settings')->get('system');
    if (empty($settings['axecore_cli_binary'])) {
      throw new \Exception('No @axe-core/cli binary provided.');
    }

    if (!file_exists($settings['axecore_cli_binary'])) {
      throw new \Exception('Provided @axe-core/cli binary does not exist. Please check the specified path is correct.');
    }

    if (!is_executable($settings['axecore_cli_binary'])) {
      throw new \Exception('Provided @axe-core/cli binary is not executable. Please check permissions.');
    }

    if (!empty($settings['axecore_cli_binary_verify_checksum'])) {
      $expected_checksum = $settings['axecore_cli_binary_checksum'];
      $actual_checksum = md5_file($settings['axecore_cli_binary']);
      if ($expected_checksum != $actual_checksum) {
        throw new \Exception("Provided @axe-code/cli binary does not match expected checksum: ${expected_checksum}");
      }
    }

    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function buildSystemSettingsForm(array &$form, FormStateInterface $form_state) {
    $settings = $this->configFactory->get('web_page_archive.wpa_axecore_cli_capture.settings')->get('system');
    $url = 'https://github.com/dequelabs/axe-core-npm/blob/develop/packages/cli/README.md';
    $label = $this->t('@axe-core/cli README');
    $axe_cli_details_link = $this->getFormDescriptionLinkFromUrl($url, $label);
    $form['axecore_cli_binary'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Path to @axe-core/cli binary.'),
      '#description' => $this->t('You must install @axe-core/cli on your server. See @details_link for more information about how to install. Please be sure your path is correct as you will be allowing the web server to execute a binary directly on your system.', ['@details_link' => $axe_cli_details_link]),
      '#default_value' => $settings['axecore_cli_binary'],
    ];
    if (!empty($settings['axecore_cli_binary']) && file_exists($settings['axecore_cli_binary'])) {
      $checksum = md5_file($settings['axecore_cli_binary']);
    }
    else {
      $checksum = $this->t('n/a');
    }
    $form['current_checksum'] = [
      '#type' => 'markup',
      '#markup' => $this->t('Current Checksum: @checksum', ['@checksum' => $checksum]),
    ];
    $form['axecore_cli_binary_verify_checksum'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Verify binary checksum before executing?'),
      '#description' => $this->t('For security purposes, it is highly recommended that you verify the checksum of the @axe-core/cli binary prior to execution. Please note that if you ever update the binary, you will need to update the checksum accordingly.'),
      '#default_value' => $settings['axecore_cli_binary_verify_checksum'],
    ];
    $form['axecore_cli_binary_checksum'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Expected checksum'),
      '#description' => $this->t('Specify the expected checksum for the @axe-core/cli binary.'),
      '#maxlength' => 32,
      '#default_value' => $settings['axecore_cli_binary_checksum'],
    ];
    $form['node_modules_parent_path'] = [
      '#type' => 'textfield',
      '#title' => $this->t('node_modules path'),
      '#description' => $this->t('Path to the directory that contains the node_modules/ directory where @axe-core/cli has been installed. Leave empty if @axe-core/cli is installed globally. This is important to help axe find other dependencies.'),
      '#default_value' => $settings['node_modules_parent_path'],
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function cleanupRevision($revision_id) {
    AxeCoreCliCaptureResponse::cleanupRevision($revision_id);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc