reviewer-1.2.x-dev/modules/reviewer_ui/src/Form/Results.php

modules/reviewer_ui/src/Form/Results.php
<?php

declare(strict_types=1);

namespace Drupal\reviewer_ui\Form;

use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\reviewer\Reviewer\Action;
use Drupal\reviewer\Reviewer\IgnorerInterface;
use Drupal\reviewer\Reviewer\Result\IndividualResultInterface;
use Drupal\reviewer\Reviewer\Review\ReviewRunnerInterface;
use Drupal\reviewer\Reviewer\Status\Status;
use Drupal\reviewer\Reviewer\Status\StatusEvaluatorInterface;

/**
 * Form for viewing, ignoring, and fixing results.
 */
final class Results extends ConfirmFormBase {

  use AutowireTrait;

  /**
   * Fixes, keyed by ID with label values.
   *
   * @var array<string, string>
   */
  private array $fixes = [];

  // phpcs:ignore Drupal.Commenting.FunctionComment.Missing
  public function __construct(
    private readonly IgnorerInterface $ignorer,
    private readonly ReviewRunnerInterface $runner,
    private readonly StatusEvaluatorInterface $evaluator,
  ) {}

  /**
   * {@inheritdoc}
   */
  public function getFormId(): string {
    return 'reviewer_ui_reviews';
  }

  /**
   * {@inheritdoc}
   */
  public function getQuestion(): TranslatableMarkup {
    return $this->t('Let Reviewer fix configuration issues?');
  }

  /**
   * {@inheritdoc}
   */
  public function getDescription(): TranslatableMarkup {
    return $this->t('Fixing issus will alter configuration on your site, and cannot be undone.');
  }

  /**
   * {@inheritdoc}
   */
  public function getCancelUrl(): Url {
    return Url::fromRoute('reviewer.ui.results');
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function buildForm(
    array $form,
    FormStateInterface $form_state,
  ): array {
    if ($this->fixes) {
      $form = parent::buildForm($form, $form_state);

      $form['message'] = [
        '#type' => 'html_tag',
        '#tag' => 'p',
        '#value' => $this->t('Fixes will be run for the following reviews:'),
      ];

      $form['review_list'] = [
        '#type' => 'html_tag',
        '#tag' => 'ul',
        'reviews' => array_map(
          fn(string $review): array => ['#type' => 'html_tag', '#tag' => 'li', '#value' => $review],
          $this->fixes,
        ),
      ];

      return $form;
    }

    $form = [
      'filters' => [
        '#type' => 'fieldset',
        '#title' => $this->t('Filter Results'),
        'show' => [
          '#type' => 'checkboxes',
          '#title' => $this->t('Show:'),
          '#options' => [
            'all' => $this->t('All Results'),
            $this->stateFromStatus(Status::Fail) => $this->t('Failed Results'),
            $this->stateFromStatus(Status::IgnoredFailure) => $this->t('Ignored Results'),
            $this->stateFromStatus(Status::Pass) => $this->t('Passed Results'),
            $this->stateFromStatus(Status::NotRun) => $this->t('Not Run'),
          ],
          '#default_value' => array_values(array_filter(
            (array) $this->getRequest()->getSession()->get('reviewer_ui.show', []),
            fn(mixed $value) => !\is_null($value),
          )) ?: ['failed'],
        ],
      ],
      '#attached' => [
        'library' => [
          'core/drupal.states',
          'reviewer_ui/admin',
        ],
      ],
    ];

    foreach ($this->runner->runPlugins(Action::Check) as $review) {
      $status_label = (string) $review->getStatus()->label();
      $review_label = $review->getLabel();
      $status_class = $this->classFromStatus($review->getStatus());
      $form[$review->getId()] = [
        '#type' => 'details',
        '#title' => "<span>$status_label</span> $review_label",
        '#attributes' => [
          'class' => [
            'js-form-wrapper',
            'reviewer-details',
            "reviewer--$status_class",
          ],
        ],
        '#summary_attributes' => [
          'class' => [
            'reviewer-summary',
          ],
        ],
      ];

      $failures = \count(array_filter(
          $review->getResults()->getIndividualResults(),
          fn(IndividualResultInterface $result): bool => $this->evaluator->isFailure($result->getStatus()),
        )) !== 0;
      $fixable = \count(array_filter(
        $review->getResults()->getIndividualResults(),
        fn(IndividualResultInterface $result): bool => $result->isFixable(),
      )) !== 0;
      $form[$review->getId()]['fix'] = [
        '#type' => 'checkbox',
        '#name' => "fix[{$review->getId()}]",
        '#title' => $this->t('Fix Issues'),
        '#description' => !$failures
          ? $this->t('There are no issues identified by this review.')
          : ($fixable
            ? $this->t('Attempt to fix all issues identified by this review.')
            : $this->t('No issues identified by this review are fixable.')
        ),
        '#attributes' => [
          'disabled' => !$failures || !$fixable,
        ],
      ];

      $contained_statuses = [];
      foreach ($review->getResults()->getIndividualResults() as $result) {
        if (!\in_array($result->getStatus(), $contained_statuses, TRUE)) {
          $contained_statuses[] = $result->getStatus();
        }
      }
      $states = [];
      foreach ($contained_statuses as $contained_status) {
        $states[] = 'or';
        $states[] = ["[name='show[{$this->stateFromStatus($contained_status)}]']" => ['checked' => TRUE]];
      }
      array_shift($states);
      $form[$review->getId()]['#attributes']['data-drupal-states'] = Json::encode([
        'visible' => $states,
      ]);

      $form[$review->getId()]['table'] = [
        '#type' => 'table',
        '#header' => [
          'status' => $this->t('Status'),
          'id' => $this->t('ID'),
          'message' => $this->t('Message'),
          'ignore' => $this->t('Ignore Result'),
        ],
        '#rows' => $this->createRows($review->getResults()->getIndividualResults()),
        '#attributes' => [
          'class' => [
            'reviewer-results-table',
          ],
        ],
      ];
    }

    $form['actions'] = [
      '#type' => 'actions',
      'ignore' => [
        '#type' => 'submit',
        '#button_type' => 'primary',
        '#value' => $this->t('Update Ignored'),
        '#name' => 'ignore',
      ],
      'unignore_all' => [
        '#type' => 'submit',
        '#button_type' => 'danger',
        '#value' => $this->t('Unignore All'),
        '#name' => 'unignore_all',
      ],
      'fix_issues' => [
        '#type' => 'submit',
        '#button_type' => 'fix',
        '#value' => $this->t('Fix Issues'),
        '#name' => 'fix_issues',
        '#attributes' => [
          'class' => ['use-ajax'],
          'data-dialog-type' => 'use-modal',
        ],
        '#attached' => [
          'library' => [
            'core/drupal.ajax',
          ],
        ],
      ],
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\reviewer\Exception\NoChecklistsException
   */
  public function submitForm(
    array &$form,
    FormStateInterface $form_state,
  ): void {
    $form_state->cleanValues();

    if ($show = $form_state->getUserInput()['show'] ?? []) {
      $this->getRequest()->getSession()->set('reviewer_ui.show', $show);
    }

    $trigger = $form_state->getTriggeringElement()['#name'] ?? '';

    if ($trigger === 'fix_issues') {
      $fixes = [];

      foreach (array_map(strval(...), array_keys($form_state->getUserInput()['fix'] ?? [])) as $fix) {
        // Injected services are not available here as the form must be
        // serialized.
        // @phpstan-ignore-next-line
        $reviews = \Drupal::service('reviewer.builder.review')->fromId(explode('.', $fix)[0]);
        $fixes[$fix] = $reviews[$fix]->getLabel();
      }

      $form_state->set('fixes', $this->fixes = $fixes);
      $form_state->setRebuild();

      return;
    }
    if ($form_state->has('fixes')) {
      // Injected services are not available here as the form must be
      // serialized.
      // @phpstan-ignore-next-line
      $runner = \Drupal::service('reviewer.review_runner');
      /** @var array<string, string> $fixes */
      $fixes = $form_state->get('fixes');
      foreach ($fixes as $fix => $label) {
        [$id, $bundle] = explode('.', $fix);
        $runner->runIds(Action::Fix, [$id], (array) $bundle);
      }
    }

    if ($trigger === 'ignore') {
      $reasons = $form_state->getUserInput()['reasons'] ?? [];
      $ignored = [];
      foreach ($form_state->getUserInput()['ignored'] ?? [] as $id => $value) {
        $ignored[] = $id;
        $this->ignorer->ignore($id, $reasons[$id]);
      }

      // Checkboxes only submit values when they are checked, so get all the
      // config ignored result IDs and filter them against the submitted IDs to
      // figure out what ignored results should be removed.
      $unignored = array_map(
        fn(array $unignore) => $unignore['id'],
        $this->ignorer->getConfigIgnored(),
      );
      $unignored = array_filter(
        $unignored,
        fn(string $id) => !\in_array($id, $ignored, TRUE),
      );
      $this->ignorer->unignore($unignored);
    }

    if ($trigger === 'unignore_all') {
      $this->ignorer->unignoreAll();
    }
  }

  /**
   * Generate table rows for a result.
   *
   * @param \Drupal\reviewer\Reviewer\Result\IndividualResultInterface[] $results
   *
   * @return array<string, mixed>
   */
  private function createRows(array $results): array {
    $rows = [];

    foreach ($results as $result) {
      $status_class = $this->classFromStatus($result->getStatus());
      $status_state = $this->stateFromStatus($result->getStatus());

      $rows[$result->getId()] = [
        'class' => [
          'reviewer-result',
          "reviewer--$status_class",
          'js-form-wrapper',
        ],
        'data-reviewer-status' => $status_class,
        'data-drupal-states' => Json::encode([
          'visible' => [
            ["[name='show[$status_state]']" => ['checked' => TRUE]],
          ],
        ]),
        'data' => [
          'status' => [
            'data' => [
              '#type' => 'html_tag',
              '#tag' => 'span',
              '#value' => (string) $result->getStatus()->label(),
            ],
          ],
          'id' => $result->getId(),
          'message' => $result->getMessage(),
          'ignore' => '',
        ],
      ];

      if (
        !$this->evaluator->isPass($result->getStatus())
        && !$this->evaluator->isNotRun($result->getStatus())
      ) {
        $result_id = $result->getId();
        $rows[$result_id]['data']['ignore'] = [
          'data' => [
            '#type' => 'checkbox',
            '#name' => "ignored[$result_id]",
            '#title' => $this->t('Ignore'),
            '#title_display' => 'invisible',
            '#attributes' => [
              'checked' => $this->ignorer->isIgnored($result),
              'disabled' => $this->ignorer->isCodeIgnored($result),
            ],
          ],
        ];

        $rows["{$result_id}_reason"] = [
          'class' => [
            'js-form-wrapper',
          ],
          'data-drupal-states' => Json::encode([
            'visible' => [
              ["[name='show[{$this->stateFromStatus($result->getStatus())}]']" => ['checked' => TRUE]],
            ],
          ]),
          'data' => [
            'reason' => [
              'data' => [
                '#type' => 'textfield',
                '#name' => "reasons[$result_id]",
                '#title' => $this->t('Reason'),
                '#title_display' => 'invisible',
                '#maxlength' => 255,
                '#value' => $this->ignorer->isIgnored($result)
                  ? $this->ignorer->ignoredReason($result)
                  : '',
                '#attributes' => [
                  'disabled' => $this->ignorer->isCodeIgnored($result),
                ],
              ],
              'colspan' => 4,
              'class' => [
                'js-form-wrapper',
              ],
              'data-drupal-states' => Json::encode([
                'visible' => [
                  "[name='ignored[$result_id]']" => ['checked' => TRUE],
                ],
              ]),
            ],
          ],
        ];
      }
    }

    return $rows;
  }

  /**
   * Get a class string from a status.
   */
  private function classFromStatus(Status $status): string {
    return Html::cleanCssIdentifier(match ($status) {
      Status::NotRun => 'not-run',
      Status::Pass, Status::Fixed => 'pass',
      Status::IgnoredFailure => 'ignored-failure',
      Status::IgnoredError => 'ignored-error',
      Status::Fail => 'fail',
      Status::Error => 'error',
    });
  }

  /**
   * Get the state string from a status.
   */
  private function stateFromStatus(Status $status): string {
    return match ($status) {
      Status::NotRun => 'not-run',
      Status::IgnoredFailure, Status::IgnoredError => 'ignored',
      Status::Fail, Status::Error => 'failed',
      default => 'passed',
    };
  }

}

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

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