Skip to content
Snippets Groups Projects
Commit 1c4bde34 authored by Shelane French's avatar Shelane French
Browse files

Merge branch '3466401-update-drush-command' into '2.x'

Draft: Resolve #3466401 "Update drush command"

See merge request !8
parents a2a7f173 894628b9
No related branches found
No related tags found
No related merge requests found
Pipeline #260424 passed
......@@ -48,7 +48,8 @@ include:
#
# Docs at https://git.drupalcode.org/project/gitlab_templates/-/blob/1.0.x/includes/include.drupalci.variables.yml
################
# variables:
variables:
_CSPELL_WORDS: 'narp'
# SKIP_ESLINT: '1'
......
......@@ -9,14 +9,7 @@
}
],
"require": {
"php": ">=5.6.0",
"php": ">=8.0",
"consolidation/site-alias": "*"
},
"extra": {
"drush": {
"services": {
"drush.services.yml": "^9"
}
}
}
}
services:
node_access_rebuild_progressive.commands:
class: \Drupal\node_access_rebuild_progressive\Commands\NodeAccessRebuildProgressiveCommands
tags:
- { name: drush.command }
<?php
/**
* @file
* Defines a node access rebuild command.
*/
use Consolidation\AnnotatedCommand\CommandResult;
/**
* Implements hook_drush_command().
*
* {@inheritdoc}
*/
function node_access_rebuild_progressive_drush_command() {
$items['node-access-rebuild-progressive'] = [
'description' => "Fully rebuild node access.",
'callback' => 'drush_node_access_rebuild_progressive_rebuild',
];
return $items;
}
/**
* Rebuilds the node access grants table.
*/
function drush_node_access_rebuild_progressive_rebuild(): CommandResult {
return _drush_node_access_rebuild_progressive_rebuild();
}
......@@ -5,27 +5,19 @@
* Provides ability to rebuild node access silently.
*/
use Consolidation\AnnotatedCommand\CommandResult;
use Consolidation\SiteAlias\SiteAliasManager;
use Consolidation\SiteAlias\SiteAliasManagerInterface;
use Drush\Drush;
/**
* Implements hook_cron().
*/
function node_access_rebuild_progressive_cron(): void {
$config = \Drupal::config('node_access_rebuild_progressive.settings');
// Not enabled on cron, nothing to do.
if (!$config->get('cron')) {
return;
}
// Trigger the processing.
if (node_access_needs_rebuild()) {
node_access_rebuild_progressive_trigger();
\Drupal::service('node_access_rebuild_progressive.trigger_service')->trigger();
}
// Process a batch of nodes if needed.
if (\Drupal::state()->get('node_access_rebuild_progressive.current') > 0) {
node_access_rebuild_progressive_process_cron();
\Drupal::service('node_access_rebuild_progressive.trigger_service')->processNodeAccessRebuildCron();
}
}
......@@ -140,67 +132,6 @@ function node_access_rebuild_progressive_finished(): void {
\Drupal::logger('node_access_rebuild_progressive')->notice('Node access rebuild finished.', []);
}
/**
* Rebuilds the node access grants table.
*
* @param \Consolidation\SiteAlias\SiteAliasManagerInterface|null $aliasManager
* The site alias manager.
*/
function _drush_node_access_rebuild_progressive_rebuild(SiteAliasManagerInterface $aliasManager = NULL): CommandResult {
node_access_rebuild_progressive_trigger();
if (!isset($aliasManager)) {
// Fallback for drush_node_access_rebuild_progressive_rebuild().
$aliasManager = new SiteAliasManager();
}
$exitCode = 0;
$finished = FALSE;
$total = \Drupal::database()->select('node', 'n')
->countQuery()
->execute()
->fetchField();
while (!$finished) {
$cmd = '_drush_node_access_rebuild_progressive_process(' . $total . ');';
$process = Drush::drush($aliasManager->getSelf(), 'php-eval', [$cmd], ['format' => 'json']);
// We capture the output to print it here and check if it finished.
$process->run($process->showRealtime()->hideStdout());
if (!$process->isSuccessful() || empty($process->getOutput())) {
$finished = TRUE;
$exitCode = $process->getExitCode();
}
else {
\Drupal::logger('node_access_rebuild_progressive')->notice($process->getOutput(), []);
}
}
return CommandResult::exitCode($exitCode);
}
/**
* Processes a pass of nodes.
*
* @param int $total
* Number of nodes to process.
*/
function _drush_node_access_rebuild_progressive_process(int $total): void {
$processed = \Drupal::state()->get('node_access_rebuild_progressive.processed', 0);
$pass = node_access_rebuild_progressive_process_chunk();
$processed += $pass['processed'];
\Drupal::state()->set('node_access_rebuild_progressive.processed', $processed);
$figures = [
'@pass' => $pass['processed'],
'@nodes' => $pass['total'],
'@processed' => $processed,
'@total' => $total,
];
if (empty($pass['total'])) {
return;
}
print dt('Processed @pass of @nodes nodes (@processed/@total).', $figures);
}
/**
* Implements hook_form_FORM_ID_alter().
*
......
services:
node_access_rebuild_progressive.trigger_service:
class: Drupal\node_access_rebuild_progressive\Service\NodeAccessRebuildProgressiveService
arguments: ['@entity_type.manager', '@logger.channel.node_access_rebuild_progressive', '@state', '@database', '@config.factory']
<?php
namespace Drupal\node_access_rebuild_progressive\Commands;
use Consolidation\AnnotatedCommand\CommandResult;
use Consolidation\SiteAlias\SiteAliasManagerAwareInterface;
use Consolidation\SiteAlias\SiteAliasManagerAwareTrait;
use Drush\Commands\DrushCommands;
/**
* A Drush command file.
*/
class NodeAccessRebuildProgressiveCommands extends DrushCommands implements SiteAliasManagerAwareInterface {
use SiteAliasManagerAwareTrait;
/**
* Fully rebuild node access.
*
* @command node-access-rebuild-progressive:rebuild
* @aliases node-access-rebuild-progressive
*/
public function accessRebuildProgressive(): CommandResult {
return _drush_node_access_rebuild_progressive_rebuild($this->siteAliasManager());
}
}
<?php
namespace Drupal\node_access_rebuild_progressive\Drush\Commands;
use Drupal\Core\Database\Connection;
use Drupal\Core\State\StateInterface;
use Drupal\node_access_rebuild_progressive\Service\NodeAccessRebuildProgressiveService;
use Drush\Attributes as CLI;
use Drush\Commands\AutowireTrait;
use Drush\Commands\DrushCommands;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Provides Drush commands for node access rebuild.
*/
class NodeAccessRebuildProgressiveCommands extends DrushCommands {
use AutowireTrait;
public function __construct(
private readonly NodeAccessRebuildProgressiveService $triggerService,
private readonly Connection $database,
private readonly StateInterface $state,
) {
parent::__construct();
}
/**
* The command will rebuild node access as a batched process.
*/
#[CLI\Command(name: 'node-access-rebuild-progressive:rebuild', aliases: ['node-access-rebuild-progressive', 'narp'])]
#[CLI\Usage(name: 'node-access-rebuild-progressive:rebuild', description: 'Rebuilds node access through progressive batch process.')]
public function rebuildNodeAccess(OutputInterface $output): int {
// Trigger the rebuild process.
$this->triggerService->trigger();
$exitCode = 0;
$finished = FALSE;
$total = $this->database->select('node', 'n')
->countQuery()
->execute()
->fetchField();
while (!$finished) {
// Process a chunk of nodes.
$this->triggerService->processNodeAccessRebuild($total);
// Check progress.
$processed = $this->state->get('node_access_rebuild_progressive.processed', 0);
if ($processed >= $total) {
$finished = TRUE;
}
else {
$output->writeln(dt('Progress: @processed/@total nodes processed.', [
'@processed' => $processed,
'@total' => $total,
]));
}
}
return $exitCode;
}
}
<?php
namespace Drupal\node_access_rebuild_progressive\Service;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\State\StateInterface;
use Drupal\node\NodeInterface;
/**
* Service for handling the node access rebuild logic.
*/
class NodeAccessRebuildProgressiveService {
public function __construct(
protected EntityTypeManagerInterface $entityTypeManager,
protected LoggerChannelInterface $logger,
protected StateInterface $state,
protected Connection $database,
protected ConfigFactoryInterface $configFactory,
protected ModuleHandlerInterface $moduleHandler,
) {}
/**
* Triggers the node access rebuild process.
*/
public function trigger(): void {
$database = $this->database;
node_access_needs_rebuild(FALSE);
if (!$this->moduleHandler->hasImplementations('node_grants')) {
$this->setDefaultGrants();
$this->finishRebuild();
return;
}
$subquery = $database->select('node', 'n')
->fields('n', ['nid']);
$database->delete('node_access')
->condition('nid', $subquery, 'NOT IN')
->execute();
$highest = $database->select('node', 'n')
->fields('n', ['nid'])
->orderBy('nid', 'DESC')
->range(0, 1)
->execute()
->fetchField();
$count = $database->select('node', 'n')
->fields('n', ['nid'])
->countQuery()
->execute()
->fetchField();
$this->state->set('node_access_rebuild_progressive.current', $highest + 1);
$this->state->set('node_access_rebuild_progressive.processed', 0);
$this->logger->info('%count nodes queued for node access rebuild.', ['%count' => $count]);
}
/**
* Processes a chunk of nodes.
*
* @param int $total
* Total number of nodes to process.
*/
public function processNodeAccessRebuild(int $total): void {
$processed = $this->state->get('node_access_rebuild_progressive.processed', 0);
$pass = $this->processChunk();
$processed += $pass['processed'];
$this->state->set('node_access_rebuild_progressive.processed', $processed);
if (empty($pass['total'])) {
return;
}
$figures = [
'@pass' => $pass['processed'],
'@nodes' => $pass['total'],
'@processed' => $processed,
'@total' => $total,
];
$this->logger->notice('Processed @pass of @nodes nodes (@processed/@total).', $figures);
}
/**
* Processes a chunk of nodes.
*
* @return string[]
* An array containing the number of nodes processed and total nodes.
*/
protected function processChunk(): array {
$current = $this->state->get('node_access_rebuild_progressive.current');
$chunk_size = $this->configFactory->get('node_access_rebuild_progressive.settings')->get('chunk');
$nids = $this->database->select('node', 'n')
->fields('n', ['nid'])
->condition('nid', $current, '<')
->orderBy('nid', 'DESC')
->range(0, $chunk_size)
->execute()
->fetchCol();
$total = count($nids);
$processed = 0;
if ($total) {
$access_control_handler = $this->entityTypeManager->getAccessControlHandler('node');
$node_storage = $this->entityTypeManager->getStorage('node');
$node_storage->resetCache($nids);
$nodes = $node_storage->loadMultiple($nids);
foreach ($nodes as $node) {
// Ensure the loaded entity is a NodeInterface.
if ($node instanceof NodeInterface && !empty($node->id())) {
$grants = $access_control_handler->acquireGrants($node);
$grant_storage = $this->entityTypeManager->getStorage('node.grant_storage');
// Save each grant individually.
foreach ($grants as $grant) {
$grant_storage->save($grant);
}
$current = $node->id();
$processed++;
}
}
$this->state->set('node_access_rebuild_progressive.current', $current);
}
return [
'total' => $total,
'processed' => $processed,
];
}
/**
* Resets grants to a clean state.
*/
protected function setDefaultGrants(): void {
$access_control_handler = $this->entityTypeManager->getAccessControlHandler('node');
$access_control_handler->deleteGrants();
$access_control_handler->writeDefaultGrant();
}
/**
* Cleans up after the queue completion.
*/
protected function finishRebuild(): void {
$this->state->set('node_access_rebuild_progressive.current', 0);
$this->logger->notice('Node access rebuild finished.');
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment