editoria11y-1.0.0-alpha8/modules/editoria11y_csa/src/Form/DashboardActions.php
modules/editoria11y_csa/src/Form/DashboardActions.php
<?php
declare(strict_types=1);
namespace Drupal\editoria11y_csa\Form;
use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\views\Views;
/**
* Provides a Editoria11y form.
*/
final class DashboardActions extends FormBase {
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'editoria11y_csa_dashboard_actions';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$crawler = Views::getView('editoria11y_crawler');
if ($crawler) {
$crawler->setDisplay('crawl');
$url = $crawler->getUrl();
$form['Manage results'] = [
'#type' => 'fieldset',
'#title' => t('Refresh results'),
[
'#theme' => 'item_list',
'#list_type' => 'ul',
'#items' => [
[
'#type' => 'link',
// @todo query param to not load images and drop stale results.
'#title' => t('Set up a site-wide recheck'),
'#url' => $url,
],
],
],
];
}
$exporter = Views::getView('editoria11y_export');
if ($exporter) {
$exporter->setDisplay('pages');
$pageUrl = $exporter->getUrl();
$exporter->setDisplay('dismissals');
$dismissUrl = $exporter->getUrl();
$exporter->setDisplay('results');
$alertUrl = $exporter->getUrl();
$form['export'] = [
'#type' => 'fieldset',
'#title' => t('Export'),
[
'#theme' => 'item_list',
'#list_type' => 'ul',
'#items' => [
[
'#type' => 'link',
'#title' => t('Export pages'),
'#url' => $pageUrl,
],
[
'#type' => 'link',
'#title' => t('Export dismissals'),
'#url' => $dismissUrl,
],
[
'#type' => 'link',
'#title' => t('Export alerts'),
'#url' => $alertUrl,
],
],
],
];
}
$pageView = Link::createFromRoute(
$this->t('Paths with parameters'),
'view.editoria11y_pages.page_1',
['page_path' => '?']
)->toString();
$form['maintenance'] = [
'#type' => 'fieldset',
'#title' => 'Routine maintenance',
];
$form['maintenance']['check_deleted'] = [
'#title' => $this->t("Check for deleted pages"),
'#type' => 'checkbox',
'#default_value' => TRUE,
'#description' => $this->t('Removes results for pages with invalid paths or entity references.'),
];
// @todo remove stale dismissals.
/*
$form['maintenance']['remove_stale'] = [
'#title' => $this->t("Check for stale dismissals"),
'#type' => 'checkbox',
'#default_value' => TRUE,
'#description' => $this->t('Discards dismissals for alerts where the element disappeared or was fixed more than a year ago.'),
];
*/
$form['maintenance']['update_titles'] = [
'#title' => $this->t("Update recorded titles"),
'#type' => 'checkbox',
'#default_value' => FALSE,
'#description' => $this->t('Replaces recorded titles for Nodes, Terms and Users with the value on their edit page.'),
];
$form['maintenance']['update_paths'] = [
'#title' => $this->t("Update recorded paths"),
'#type' => 'checkbox',
'#default_value' => FALSE,
'#description' => $this->t('Replaces recorded URLs for Nodes, Terms and Users with their current canonical path. @ReviewPaths (node/1?node=2) will not be updated.', ['@ReviewPaths' => $pageView]),
];
$form['maintenance']['danger'] = [
'#title' => $this->t('Risky deletions'),
'#type' => 'details',
];
$form['maintenance']['danger']['remove_pages_with_params'] = [
'#title' => $this->t("Delete all alerts for paths with parameters"),
'#type' => 'select',
'#options' => [
'none' => $this->t('None'),
'entities' => $this->t('Only for nodes, terms & users'),
'all' => $this->t('All, including search and views'),
],
'#default_value' => 'none',
'#description' => $this->t('<strong>There is no undo</strong>. @ReviewPaths should be reviewed before proceeding.', ['@ReviewPaths' => $pageView]),
];
$form['maintenance']['actions'] = [
'#type' => 'actions',
'submit' => [
'#type' => 'submit',
'#value' => $this->t('Start'),
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state): void {
// @todo Validate the form here.
// Example:
// @code
// if (mb_strlen($form_state->getValue('message')) < 10) {
// $form_state->setErrorByName(
// 'message',
// $this->t('Message should be at least 10 characters.'),
// );
// }
// @endcode
}
/**
* Compares Editoria11y DB tables to Drupal tables and routes.
*
* @param int $batch_id
* Incremented integer.
* @param int $batch_size
* Records to process.
* @param int $max
* The last batch_id.
* @param array $form_values
* User choices for the batch process.
* @param array $context
* The Drupal batch environment variables.
*/
public static function batchProcess(int $batch_id, int $batch_size, int $max, array $form_values, array &$context): void {
if (!isset($context['results']['progress'])) {
// First round; initialize variables.
$context['results']['last_record'] = 0;
$context['results']['max'] = $max;
$context['results']['updated'] = 0;
$context['results']['deleted'] = 0;
$context['results']['progress'] = 0;
$context['results']['process'] = 'Form batch completed';
}
// Message above progress bar.
$context['message'] = t('Processing batch #@batch_id of @count.', [
'@batch_id' => number_format($batch_id),
'@count' => number_format($context['results']['max']),
]);
// @phpstan-ignore-next-line
$database = \Drupal::database();
$query = $database->select('editoria11y_pages');
$query->fields(
'editoria11y_pages',
[
'pid',
'page_path',
'page_language',
'entity_id',
'entity_type',
'page_title',
'route_name',
],
);
$query->leftJoin(
'node_field_data',
'node_field_data',
"editoria11y_pages.route_name = 'entity.node.canonical' AND node_field_data.nid = editoria11y_pages.entity_id AND node_field_data.langcode = editoria11y_pages.page_language"
);
$query->fields('node_field_data',
[
'nid',
'title',
],
);
$query->leftJoin(
'users_field_data',
'users_field_data',
"editoria11y_pages.route_name = 'entity.user.canonical' AND users_field_data.uid = editoria11y_pages.entity_id AND users_field_data.langcode = editoria11y_pages.page_language"
);
$query->fields('users_field_data',
[
'uid',
'name',
],
);
$query->leftJoin(
'taxonomy_term_field_data',
'taxonomy_term_field_data',
"editoria11y_pages.route_name = 'entity.taxonomy_term.canonical' AND taxonomy_term_field_data.tid = editoria11y_pages.entity_id AND taxonomy_term_field_data.langcode = editoria11y_pages.page_language"
);
$query->fields('taxonomy_term_field_data',
[
'tid',
'name',
],
);
$query->orderBy('pid');
$query->condition('pid', $context['results']['last_record'], '>');
$query->range(0, $batch_size);
$results = $query->execute()->fetchAll();
$pages_to_delete = [];
$paths_to_update = [];
$titles_to_update = [];
$counter = 0;
$count = count($results);
$path_validator = \Drupal::service('path.validator');
foreach ($results as $record) {
$counter++;
$path_params = FALSE;
if (str_contains($record->page_path, '?')) {
$path_params = explode("?", $record->page_path, 2)[1];
}
switch ($record->route_name) {
case 'entity.node.canonical':
if (empty($record->nid) && $form_values['check_deleted']
) {
$pages_to_delete[] = $record->pid;
break;
}
elseif (
$path_params &&
$form_values['remove_pages_with_params'] !== 'none'
) {
$pages_to_delete[] = $record->pid;
break;
}
if ($form_values['update_titles'] === 1 &&
$record->page_title !== $record->title
) {
$titles_to_update[$record->pid] = $record->title;
}
if ($form_values['update_paths'] === 1) {
$internal_path = '/node/' . $record->entity_id;
$alias = \Drupal::service('path_alias.manager')->getAliasByPath($internal_path, $record->page_language);
if ($path_params) {
$alias = $alias . '?' . $path_params;
}
if (
$alias !== $record->page_path &&
$alias !== str_replace('/' . $record->page_language, '', $record->page_path)
) {
$paths_to_update[$record->pid] = $alias;
}
}
break;
case 'entity.taxonomy_term.canonical':
if (empty($record->tid) && $form_values['check_deleted']
) {
$pages_to_delete[] = $record->pid;
break;
}
elseif (
$path_params &&
$form_values['remove_pages_with_params'] !== 'none'
) {
$pages_to_delete[] = $record->pid;
break;
}
if ($form_values['update_titles'] === 1 &&
$record->page_title !== $record->taxonomy_term_field_data_name
) {
$titles_to_update[$record->pid] = $record->taxonomy_term_field_data_name;
}
if ($form_values['update_paths'] === 1) {
// Replace 123 with your entity's ID.
$internal_path = '/term/' . $record->entity_id;
$alias = \Drupal::service('path_alias.manager')->getAliasByPath($internal_path, $record->page_language);
if ($path_params) {
$alias = $alias . '?' . $path_params;
}
if (
$alias !== $record->page_path &&
$alias !== str_replace('/' . $record->page_language, '', $record->page_path)
) {
$paths_to_update[$record->pid] = $alias;
}
}
break;
case 'entity.user.canonical':
if (empty($record->uid) && $form_values['check_deleted']
) {
$pages_to_delete[] = $record->pid;
break;
}
elseif (
$path_params &&
$form_values['remove_pages_with_params'] !== 'none'
) {
$pages_to_delete[] = $record->pid;
break;
}
if ($form_values['update_titles'] === 1 &&
$record->page_title !== $record->name
) {
$titles_to_update[$record->pid] = $record->name;
}
if ($form_values['update_paths'] === 1) {
// Replace 123 with your entity's ID.
$internal_path = '/term/' . $record->entity_id;
$alias = \Drupal::service('path_alias.manager')->getAliasByPath($internal_path, $record->page_language);
if ($path_params) {
$alias = $alias . '?' . $path_params;
}
if (
$alias !== $record->page_path &&
$alias !== str_replace('/' . $record->page_language, '', $record->page_path)
) {
$paths_to_update[$record->pid] = $alias;
}
}
break;
default:
// Views and other entity types.
// Delete if it has parameters we want to drop.
if ($form_values['remove_pages_with_params'] === 'all' &&
$path_params
) {
$pages_to_delete[] = $record->pid;
break;
}
$url_object = $path_validator->getUrlIfValid($record->page_path);
// Delete if the path is gone.
if (!$url_object instanceof Url) {
if ($form_values['check_deleted']) {
$pages_to_delete[] = $record->pid;
}
break;
}
if (!$path_params && $form_values['update_paths'] === 1) {
$alias = $url_object->toString();
if (
$alias !== $record->page_path &&
$alias !== str_replace('/' . $record->page_language, '', $record->page_path)
) {
$paths_to_update[$record->pid] = $alias;
}
}
break;
}
if ($counter === $count) {
$context['results']['last_record'] = $record->pid;
}
}
if (count($pages_to_delete) > 0) {
$delete = $database->delete('editoria11y_results');
$delete->condition('pid', $pages_to_delete, 'IN');
$delete->execute();
$delete = $database->delete('editoria11y_dismissals');
$delete->condition('pid', $pages_to_delete, 'IN');
$delete->execute();
$delete = $database->delete('editoria11y_pages');
$delete->condition('pid', $pages_to_delete, 'IN');
$delete->execute();
$context['results']['deleted']++;
}
if ($form_values['update_paths'] === 1 && count($paths_to_update) > 0) {
foreach ($paths_to_update as $key => $path) {
$paths = $database->update('editoria11y_pages');
$paths->condition('pid', $key, '=');
$paths->fields([
'page_path' => $path,
]);
$paths->execute();
$context['results']['updated']++;
}
}
if ($form_values['update_titles'] === 1 && count($titles_to_update) > 0) {
foreach ($titles_to_update as $key => $title) {
$titles = $database->update('editoria11y_pages');
$titles->condition('pid', $key, '=');
$titles->fields([
'page_title' => $title,
]);
$titles->execute();
$context['results']['updated']++;
}
}
// Keep track of progress.
$context['results']['progress'] += $batch_size;
}
/**
* Sends messages to UI and logs on complete.
*
* @param bool $success
* Did it work?
* @param array $results
* Values forwarded from the batch process.
* @param array $operations
* Where we were when things blew up.
* @param string $elapsed
* How long the batch took.
*/
public static function batchFinished(bool $success, array $results, array $operations, string $elapsed): void {
// Grab the messenger service, this will be needed if the batch was a
// success or a failure.
$messenger = \Drupal::messenger();
if ($success) {
// The success variable was true, which indicates that the batch process
// was successful (i.e. no errors occurred).
// Show success message to the user.
$messenger->addMessage(t('@process processed @count, deleted @deleted, updated @updated.', [
'@process' => $results['process'],
'@count' => $results['progress'],
'@deleted' => $results['deleted'],
'@updated' => $results['updated'],
'@elapsed' => $elapsed,
]));
// Log the batch success.
\Drupal::logger('Editoria11y database maintenance')->info(
'@process processed @count, deleted @deleted, updated @updated in @elapsed.', [
'@process' => $results['process'],
'@count' => $results['progress'],
'@deleted' => $results['deleted'],
'@updated' => $results['updated'],
'@elapsed' => $elapsed,
]);
}
else {
// An error occurred. $operations contains the operations that remained
// unprocessed. Pick the last operation and report on what happened.
$error_operation = reset($operations);
if ($error_operation) {
$message = t('An error occurred while processing %error_operation with arguments: @arguments', [
'%error_operation' => print_r($error_operation[0], TRUE),
'@arguments' => print_r($error_operation[1], TRUE),
]);
$messenger->addError($message);
}
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$batch = new BatchBuilder();
$batch->setTitle('Running batch process.')
->setFinishCallback([self::class, 'batchFinished'])
->setInitMessage('Commencing')
->setProgressMessage('Processing...')
->setErrorMessage('An error occurred during processing.');
// Create 10 chunks of 100 items.
// @phpstan-ignore-next-line
$database = \Drupal::database();
$batch_size = 100;
$results_query = $database->select('editoria11y_pages');
$result_count = (int) $results_query->countQuery()->execute()->fetchField();
$batches = (int) ceil((int) $result_count / $batch_size);
// @todo Stop if there is 0;
for ($i = 0; $i < $batches; $i++) {
$args = [
$i,
$batch_size,
$result_count,
$form_state->getValues(),
];
$batch->addOperation([self::class, 'batchProcess'], $args);
}
batch_set($batch->toArray());
// Set the redirect for the form submission back to the form itself.
$form_state->setRedirectUrl(new Url('editoria11y_csa.dashboard_actions'));
}
}
