RulesCommands.php

Namespace

Drupal\rules\Commands

File

src/Commands/RulesCommands.php

View source
<?php

namespace Drupal\rules\Commands;

// cspell:ignore rlst renb rdis rdel rexp rrev
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Drupal\Core\Config\CachedStorage;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Serialization\Yaml;
use Drupal\rules\Core\RulesEventManager;
use Drush\Commands\DrushCommands;

/**
 * Drush 9+ commands for the Rules module.
 */
class RulesCommands extends DrushCommands {
    
    /**
     * The config factory service.
     *
     * @var \Drupal\Core\Config\ConfigFactoryInterface
     */
    protected $configFactory;
    
    /**
     * The config storage service.
     *
     * @var \Drupal\Core\Config\CachedStorage
     */
    protected $configStorage;
    
    /**
     * The rules event manager.
     *
     * @var \Drupal\rules\Core\RulesEventManager
     */
    protected $rulesEventManager;
    
    /**
     * RulesCommands constructor.
     *
     * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
     *   The config factory service.
     * @param \Drupal\Core\Config\CachedStorage $config_storage
     *   The config storage service.
     * @param \Drupal\rules\Core\RulesEventManager $rules_event_manager
     *   The rules event manager.
     */
    public function __construct(ConfigFactoryInterface $config_factory, CachedStorage $config_storage, RulesEventManager $rules_event_manager) {
        parent::__construct();
        $this->configFactory = $config_factory;
        $this->configStorage = $config_storage;
        $this->rulesEventManager = $rules_event_manager;
    }
    
    /**
     * Lists all the active and inactive rules for your site.
     *
     * @param string $type
     *   (optional) Either 'rule' or 'component'. Any other value (or no value)
     *   will list both Reaction Rules and Rules Components.
     * @param array $options
     *   (optional) The options.
     *
     * @command rules:list
     * @aliases rlst,rules-list
     *
     * @usage drush rules:list
     *   Lists both Reaction Rules and Rules Components.
     * @usage drush rules:list component
     *   Lists only Rules Components.
     * @usage drush rules:list --fields=machine-name
     *   Lists just the machine names.
     * @usage drush rules:list --fields=machine-name --pipe
     *   Outputs machine names in a format suitable for piping.
     *
     * @table-style default
     * @field-labels
     *   machine-name: Rule
     *   label: Label
     *   event: Event
     *   active: Active
     * @default-fields machine-name,label,event,active
     *
     * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
     *   The data.
     *
     * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
     */
    public function listAll($type = '', array $options = [
        'format' => 'table',
        'fields' => '',
    ]) {
        // Type is 'rule', or 'component'. Any other value (or no value) will
        // list both Reaction Rules and Rules Components.
        switch ($type) {
            case 'rule':
                $types = [
                    'reaction',
                ];
                break;
            case 'component':
                $types = [
                    'component',
                ];
                break;
            default:
                $types = [
                    'reaction',
                    'component',
                ];
                break;
        }
        // Loop over type parameter.
        $rows = [];
        foreach ($types as $item) {
            $rules = $this->configFactory
                ->listAll('rules.' . $item);
            // Loop over configuration entities for this $item.
            foreach ($rules as $config) {
                $rule = $this->configFactory
                    ->get($config);
                if (!empty($rule->get('id')) && !empty($rule->get('label'))) {
                    $events = [];
                    $active = '';
                    // Components don't have events and can't be enabled/disabled.
                    if ($item == 'reaction') {
                        foreach ($rule->get('events') as $event) {
                            $plugin = $this->rulesEventManager
                                ->getDefinition($event['event_name']);
                            $events[] = (string) $plugin['label'];
                        }
                        $active = $rule->get('status') ? dt('Enabled') : dt('Disabled');
                    }
                    $rows[(string) $rule->get('id')] = [
                        'machine-name' => (string) $rule->get('id'),
                        'label' => (string) $rule->get('label'),
                        'event' => implode(', ', $events),
                        'active' => (string) $active,
                    ];
                }
            }
        }
        return new RowsOfFields($rows);
    }
    
    /**
     * Enables a Reaction Rule on your site.
     *
     * @param string $rule
     *   Reaction rule name (machine name) to enable.
     *
     * @command rules:enable
     * @interact-disabled-rules
     * @aliases renb,rules-enable
     *
     * @usage drush rules:enable
     *   Displays all disabled rules and allows you to select one to enable.
     * @usage drush rules:enable test_rule
     *   Enables the rule with machine name 'test_rule'.
     *
     * @throws \Exception
     */
    public function enable($rule) {
        // The $rule argument must be a Reaction Rule.
        if ($this->configStorage
            ->exists('rules.reaction.' . $rule)) {
            $config = $this->configFactory
                ->getEditable('rules.reaction.' . $rule);
        }
        elseif ($this->configStorage
            ->exists($rule)) {
            $config = $this->configFactory
                ->getEditable($rule);
        }
        else {
            throw new \Exception(dt('Could not find a Reaction Rule named @name', [
                '@name' => $rule,
            ]));
        }
        if (!$config->get('status')) {
            $config->set('status', TRUE);
            $config->save();
            $this->logger
                ->success(dt('The rule @name has been enabled.', [
                '@name' => $rule,
            ]));
        }
        else {
            $this->logger
                ->warning(dt('The rule @name is already enabled', [
                '@name' => $rule,
            ]));
        }
    }
    
    /**
     * Disables a Reaction Rule on your site.
     *
     * @param string $rule
     *   Reaction rule name (machine name) to disable.
     *
     * @command rules:disable
     * @interact-enabled-rules
     * @aliases rdis,rules-disable
     *
     * @usage drush rules:disable
     *   Displays all enabled rules and allows you to select one to disable.
     * @usage drush rules:disable test_rule
     *   Disables the rule with machine name 'test_rule'.
     *
     * @throws \Exception
     */
    public function disable($rule) {
        // The $rule argument must be a Reaction Rule.
        if ($this->configStorage
            ->exists('rules.reaction.' . $rule)) {
            $config = $this->configFactory
                ->getEditable('rules.reaction.' . $rule);
        }
        elseif ($this->configStorage
            ->exists($rule)) {
            $config = $this->configFactory
                ->getEditable($rule);
        }
        else {
            throw new \Exception(dt('Could not find a Reaction Rule named @name', [
                '@name' => $rule,
            ]));
        }
        if ($config->get('status')) {
            $config->set('status', FALSE);
            $config->save();
            $this->logger
                ->success(dt('The rule @name has been disabled.', [
                '@name' => $rule,
            ]));
        }
        else {
            $this->logger
                ->warning(dt('The rule @name is already disabled', [
                '@name' => $rule,
            ]));
        }
    }
    
    /**
     * Deletes a rule on your site.
     *
     * @param string $rule
     *   Rule name (machine id) to delete.
     *
     * @command rules:delete
     * @interact-rule-names
     * @aliases rdel,rules-delete
     *
     * @usage drush rules:delete
     *   Displays all rules and allows you to select one to delete.
     * @usage drush rules:delete test_rule
     *   Permanently deletes the rule with machine name 'test_rule'.
     *
     * @throws \Exception
     */
    public function delete($rule) {
        // The $rule argument could refer to a Reaction Rule or a Rules Component.
        if ($this->configStorage
            ->exists('rules.reaction.' . $rule)) {
            $config = $this->configFactory
                ->getEditable('rules.reaction.' . $rule);
        }
        elseif ($this->configStorage
            ->exists('rules.component.' . $rule)) {
            $config = $this->configFactory
                ->getEditable('rules.component.' . $rule);
        }
        elseif ($this->configStorage
            ->exists($rule)) {
            $config = $this->configFactory
                ->getEditable($rule);
        }
        else {
            throw new \Exception(dt('Could not find a Reaction Rule or a Rules Component named @name', [
                '@name' => $rule,
            ]));
        }
        if ($this->confirm(dt('Are you sure you want to delete the rule named "@name"? This action cannot be undone.', [
            '@name' => $rule,
        ]))) {
            $config->delete();
            $this->logger
                ->success(dt('The rule @name has been deleted.', [
                '@name' => $rule,
            ]));
        }
    }
    
    /**
     * Exports a single rule configuration, in YAML format.
     *
     * @param string $rule
     *   Rule name (machine id) to export.
     *
     * @command rules:export
     * @interact-rule-names
     * @aliases rexp,rules-export
     *
     * @codingStandardsIgnoreStart
     * @usage drush rules:export
     *   Displays all rules and allows you to select one to export.
     * @usage drush rules:export test_rule > rules.reaction.test_rule.yml
     *   Exports the Rule with machine name 'test_rule' and saves it in a .yml file.
     * @usage drush rules:list rule --fields=machine-name --pipe | xargs -I{}  sh -c "drush rules:export '{}' > 'rules.reaction.{}.yml'"
     *   Exports all Reaction Rules into individual YAML files.
     * @codingStandardsIgnoreEnd
     *
     * @throws \Exception
     */
    public function export($rule) {
        // The $rule argument could refer to a Reaction Rule or a Rules Component.
        $config = $this->configStorage
            ->read('rules.reaction.' . $rule);
        if (empty($config)) {
            $config = $this->configStorage
                ->read('rules.component.' . $rule);
            if (empty($config)) {
                // The @interact-rule-names hook returns fully-qualified names.
                $config = $this->configStorage
                    ->read($rule);
                if (empty($config)) {
                    throw new \Exception(dt('Could not find a Reaction Rule or a Rules Component named @name', [
                        '@name' => $rule,
                    ]));
                }
            }
        }
        $this->output
            ->write(Yaml::encode($config), FALSE);
        $this->logger
            ->success(dt('The rule @name has been exported.', [
            '@name' => $rule,
        ]));
    }
    
    /**
     * Reverts a rule to its original state on your site.
     *
     * @param string $rule
     *   Rule name (machine id) to revert.
     *
     * @command rules:revert
     * @interact-rule-names
     * @aliases rrev,rules-revert
     *
     * @usage drush rules:revert test_rule
     *   Restores the module-provided Rule with machine id 'test_rule' to its
     *   original state. If the Rule hasn't been customized on the site, this has
     *   no effect.
     *
     * @throws \Exception
     */
    public function revert($rule) {
        // @todo Implement this function.
        // The $rule argument could refer to a Reaction Rule or a Rules Component.
        $config = $this->configStorage
            ->read('rules.reaction.' . $rule);
        if (empty($config)) {
            $config = $this->configStorage
                ->read('rules.component.' . $rule);
            if (empty($config)) {
                // The @interact-rule-names hook returns fully-qualified names.
                $config = $this->configStorage
                    ->read($rule);
                if (empty($config)) {
                    throw new \Exception(dt('Could not find a Reaction Rule or a Rules Component named @name', [
                        '@name' => $rule,
                    ]));
                }
            }
        }
        if (($rule->status & ENTITY_OVERRIDDEN) == ENTITY_OVERRIDDEN) {
            if ($this->confirm(dt('Are you sure you want to revert the rule named "@name"? This action cannot be undone.', [
                '@name' => $rule,
            ]))) {
                // $config->delete();
                $this->logger
                    ->success(dt('The rule @name has been reverted to its default state.', [
                    '@name' => $rule,
                ]));
            }
        }
        else {
            $this->logger
                ->warning(dt('The rule "@name" has not been overridden and can\'t be reverted.', [
                '@name' => $rule,
            ]));
        }
    }
    
    /**
     * Show a list of Rules events.
     *
     * @command rules:events
     * @aliases rules-events
     */
    public function listEvents() {
        $this->formatOutput('plugin.manager.rules_event', 'Available Rules Events:');
    }
    
    /**
     * Show a list of Rules conditions.
     *
     * @command rules:conditions
     * @aliases rules-conditions
     */
    public function listConditions() {
        $this->formatOutput('plugin.manager.condition', 'Available Rules Conditions:');
    }
    
    /**
     * Show a list of Rules actions.
     *
     * @command rules:actions
     * @aliases rules-actions
     */
    public function listActions() {
        $this->formatOutput('plugin.manager.rules_action', 'Available Rules Actions:');
    }
    
    /**
     * Show a list of Rules expressions.
     *
     * @command rules:expressions
     * @aliases rules-expressions
     */
    public function listExpressions() {
        $this->formatOutput('plugin.manager.rules_expression', 'Available Rules Expressions:');
    }
    
    /**
     * Helper function to format command output.
     */
    protected function formatOutput($plugin_manager_service, $title, $categories = TRUE, $short = FALSE) {
        // Dependency injection deliberately not used because we don't know
        // a priori what service will be needed. So ignore the phpcs message.
        // @phpcs:ignore DrupalPractice.Objects.GlobalDrupal.GlobalDrupal
        $definitions = \Drupal::service($plugin_manager_service)->getDefinitions();
        $plugins = [];
        foreach ($definitions as $plugin) {
            if ($categories) {
                if ($short) {
                    $plugins[(string) $plugin['category']][] = $plugin['id'];
                }
                else {
                    $plugins[(string) $plugin['category']][] = $plugin['label'] . '   (' . $plugin['id'] . ')';
                }
            }
            else {
                if ($short) {
                    $plugins[] = $plugin['id'];
                }
                else {
                    $plugins[] = $plugin['label'] . '   (' . $plugin['id'] . ')';
                }
            }
        }
        $this->output()
            ->writeln(dt($title));
        if ($categories) {
            ksort($plugins);
            foreach ($plugins as $category => $plugin_list) {
                $this->output()
                    ->writeln('  ' . $category);
                sort($plugin_list);
                $this->output()
                    ->writeln('    ' . implode(PHP_EOL . '    ', $plugin_list));
                $this->output()
                    ->writeln('');
            }
        }
        else {
            $unique = array_unique($plugins);
            sort($unique);
            $this->output()
                ->writeln('  ' . implode(PHP_EOL . '  ', $unique) . PHP_EOL);
        }
    }
    
    /**
     * @hook interact @interact-enabled-rules
     */
    public function interactEnabledRules($input, $output) {
        if (empty($input->getArgument('rule'))) {
            $rules = $this->configFactory
                ->listAll('rules.reaction');
            // Loop over configuration entities for this $item.
            foreach ($rules as $index => $config) {
                $rule = $this->configFactory
                    ->get($config);
                if ($rule->get('status') === FALSE) {
                    unset($rules[$index]);
                }
            }
            $choice = $this->io()
                ->choice('Choose a Reaction Rule', drush_map_assoc($rules));
            $input->setArgument('rule', $choice);
        }
    }
    
    /**
     * @hook interact @interact-disabled-rules
     */
    public function interactDisabledRules($input, $output) {
        if (empty($input->getArgument('rule'))) {
            $rules = $this->configFactory
                ->listAll('rules.reaction');
            // Loop over configuration entities for this $item.
            foreach ($rules as $index => $config) {
                $rule = $this->configFactory
                    ->get($config);
                if ($rule->get('status') === TRUE) {
                    unset($rules[$index]);
                }
            }
            $choice = $this->io()
                ->choice('Choose a Reaction Rule', drush_map_assoc($rules));
            $input->setArgument('rule', $choice);
        }
    }
    
    /**
     * @hook interact @interact-rule-names
     */
    public function interactRuleNames($input, $output) {
        if (empty($input->getArgument('rule'))) {
            $rule_names = $this->configFactory
                ->listAll('rules.reaction');
            $component_names = $this->configFactory
                ->listAll('rules.component');
            $config_names = array_merge($rule_names, $component_names);
            $choice = $this->io()
                ->choice('Choose a Rule', drush_map_assoc($config_names));
            $input->setArgument('rule', $choice);
        }
    }

}

Classes

Title Deprecated Summary
RulesCommands Drush 9+ commands for the Rules module.