fox-1.1.2/src/Drush/Commands/FoxCommands.php

src/Drush/Commands/FoxCommands.php
<?php

namespace Drupal\fox\Drush\Commands;

use Consolidation\SiteAlias\SiteAliasManagerAwareInterface;
use Consolidation\SiteAlias\SiteAliasManagerAwareTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\fox\FoxCommandsHelper as Helper;
use Drupal\fox\FoxCommandsHelperTrait;
use Drupal\fox\FoxEntityQuery;
use Drupal\fox\Plugin\FoxCommandsManager;
use Drush\Attributes as CLI;
use Drush\Commands\DrushCommands;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * A Drush commandfile.
 *
 * In addition to this file, you need a drush.services.yml
 * in root of your module, and a composer.json file that provides the name
 * of the services file to use.
 */
class FoxCommands extends DrushCommands implements SiteAliasManagerAwareInterface {

  use FoxCommandsHelperTrait;
  use SiteAliasManagerAwareTrait;

  /**
   * Options list.
   *
   * @var array
   */
  private $options;

  /**
   * Variables list.
   *
   * @var array
   */
  private array $variables = [];

  /**
   * Commands history.
   *
   * @var array
   */
  private array $history = [];

  /**
   * Constructs a FoxCommands object.
   */
  public function __construct(
    private readonly EntityTypeManagerInterface $entityTypeManager,
    private readonly FoxCommandsManager $foxCommandsManager,
    private readonly FoxEntityQuery $foxEntityQuery,
  ) {
    parent::__construct();
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager'),
      $container->get('plugin.manager.fox_commands'),
      $container->get('fox.query.entity'),
    );
  }

  #[CLI\Command(name: 'fox:console', aliases: ['fox'])]
  #[CLI\Option(name: 'input', description: 'Input commands: "command1;command2;..." or FILE')]
  #[CLI\Option(name: 'output', description: 'Output command history [FILE]')]
  #[CLI\Option(name: 'mode', description: 'Command mode: [default]|debug')]
  #[CLI\Option(name: 'quit', description: 'Non-interactive mode')]

  /**
   * Console function.
   */
  public function console(
    array $options = [
      'input' => '',
      'output' => '',
      'mode' => 'default',
    ],
  ) {
    $this->options = $options;
    $quit_mode = !empty($options['quit']);
    if (!$quit_mode) {
      $this->io()->info(dt("Welcome to the Fox console!\nType \"exit\" or \"quit\" to quit."));
    }

    // Variables for executing external drush commands.
    $this->variables['self'] = $this->siteAliasManager()->getSelf();
    $this->variables['process_manager'] = $this->processManager();

    // Enter the interactive loop.
    while (TRUE) {
      $rows = $this->foxInput();

      $need_exit = FALSE;
      foreach ($rows as $row) {
        if ($need_exit = $this->runCommands($row)) {
          break;
        }
      }

      if ($need_exit || $quit_mode) {
        break;
      }
    }

    // Exiting.
    if (!$quit_mode) {
      $this->io()->info(dt('Exiting the Fox console.'));
      $this->foxOutput();
    }
  }

  /**
   * Input by user, commands or file.
   *
   * @return array
   *   Commands rows.
   */
  protected function foxInput(): array {
    $rows = [];
    if (is_string($this->options['input'])) {
      $file = Helper::getFilePath($this->options['input']);
      if (file_exists($file)) {
        $rows = Helper::loadFile($file);
      }
      else {
        $rows[] = htmlspecialchars_decode(trim($this->options['input']));
      }
      $this->options['input'] = NULL;
    }
    else {
      $input = $this->readlineWithHistory('Enter a command:');
      $params = $this->getFoxCommandParameters($input);
      $command = strtolower($params[0]);

      // Load command.
      if ($command === 'load') {
        $result = $this->parseFoxCommands($input);
        if (!empty($result)) {
          if (isset($result['commands'])) {
            $rows = $result['commands'];
          }
          $success = $this->printResult($result);
          if ($success) {
            $this->history[] = $input;
          }
        }
      }
      else {
        $rows[] = $input;
      }
    }

    return $rows;
  }

  /**
   * Output commands.
   */
  protected function foxOutput() {
    if (!empty($this->options['output'])) {
      if (is_string($this->options['output'])) {
        // Output to file.
        $file = Helper::getFilePath($this->options['output']);
        if (file_exists($file)) {
          $confirmed = $this->io()->confirm(dt('File @file is exist, can overwrite it?', ['@file' => $file]));
          if (!$confirmed) {
            return;
          }
        }

        $output = implode("\n", $this->history);
        file_put_contents($file, $output);
        $this->io()->info(dt('Command history was saved to @file', ['@file' => $file]));
      }
      else {
        $header = [
          '#',
          dt('Command'),
        ];
        $output = [];
        foreach ($this->history as $number => $data) {
          $output[] = [$number + 1, $data];
        }
        $this->io()->table($header, $output);
      }
    }
  }

  /**
   * Run readline.
   *
   * @param string $prompt
   *   Readline prompt.
   *
   * @return string
   *   Readline input.
   */
  protected function readlineWithHistory(string $prompt): string {
    readline_completion_function([$this, 'autocompleteHistory']);
    $this->io()->writeln("\033[1;32m" . $prompt . "\033[0m");
    $input = readline('> ');

    if ($input !== FALSE && $input !== '') {
      readline_add_history($input);
    }
    return trim($input);
  }

  /**
   * History autocomplete.
   *
   * @param string $input
   *   Readline input.
   * @param int $index
   *   History index.
   *
   * @return array
   *   Autocomplete result.
   */
  protected function autocompleteHistory(string $input, int $index): array {
    if ($input === '') {
      return $this->history;
    }

    $matches = array_values(array_filter($this->history, fn($cmd) => str_starts_with($cmd, $input)));
    return $matches;
  }

  /**
   * Run Fox commands.
   *
   * @param string $row
   *   Input string with commands.
   *
   * @return bool
   *   Need to exit.
   */
  protected function runCommands($row) {
    // All commands.
    $commands = array_filter(explode(';', $row));
    foreach ($commands as $command) {
      $command = trim($command);

      // Evaluate condition.
      $this->parseCondition($command);

      if (empty($command)) {
        continue;
      }

      // Check for exit condition.
      if (in_array(strtolower($command), ['exit', 'quit'])) {
        return TRUE;
      }

      $completed = FALSE;

      // Fox commands.
      if (!$completed) {
        $result = $this->parseFoxCommands($command);
        if (!empty($result)) {
          $this->setVariables($result);
          $success = $this->printResult($result);
          if ($success) {
            $this->history[] = $command;
          }
          $completed = TRUE;
        }
      }

      // Run Entity Query.
      if (!$completed) {
        $result = $this->foxEntityQuery->doEntityQuery($command, $this->variables, $this->options);
        if (!empty($result)) {
          $this->setVariables($result);
          $success = $this->printResult($result);
          if ($success) {
            $this->history[] = $command;
          }
          $completed = TRUE;
        }
      }

      // Print variables.
      if (empty($result['hide_variables'])) {
        $this->printVariables();
      }
    }
  }

  /**
   * Fox commands parse.
   *
   * @param string $input
   *   User input.
   *
   * @return mixed
   *   Result of parsing.
   */
  protected function parseFoxCommands(string $input) {
    $foxCommands = $this->foxCommandsManager->getDefinitions();
    $params = $this->getFoxCommandParameters($input);
    $command = strtolower($params[0]);
    if (!empty($foxCommands[$command])) {
      array_shift($params);
      $instance = $this->foxCommandsManager->createInstance($command);
      $result = $instance->execute($params, $this->variables, $this->options);
      return $result;
    }

    return NULL;
  }

  /**
   * Get Fox command parameters.
   *
   * @param string $input
   *   Input string.
   *
   * @return array
   *   Parameters array.
   */
  protected function getFoxCommandParameters($input): array {
    $params = array_filter(explode(' ', $input), function ($value) {
      if (is_numeric($value)) {
        return TRUE;
      }
      return !empty($value);
    });
    return $params;
  }

  /**
   * Print variables.
   */
  protected function printVariables(): void {
    $message = '';
    foreach ($this->variables as $name => $value) {
      if (!is_object($value) && !is_array($value) && !is_null($value)) {
        $message .= "$name=" . Helper::getVariable($value) . ' ';
      }
    }
    $this->output->writeln($message);
  }

  /**
   * Print result.
   *
   * @param array $result
   *   Command result.
   *
   * @return bool
   *   Success result output.
   */
  protected function printResult(array $result): bool {
    if (empty($result)) {
      return TRUE;
    }

    $this->variables['last_error'] = NULL;
    if (isset($result['error'])) {
      $this->variables['last_error'] = TRUE;
      $this->io()->error($result['error']);
      return FALSE;
    }

    if (isset($result['warning'])) {
      $this->io()->warning($result['warning']);
    }

    if (isset($result['debug'])) {
      $this->io()->info($result['debug']);
    }

    $message = $result['message'] ?? NULL;
    if (!empty($message)) {
      if (is_array($message)) {
        $keys = array_keys($message);
        if (count($keys) >= 2 && in_array('header', $keys) && in_array('data', $keys)) {
          $message = [$message];
        }
        $this->printTable($message);
      }
      else {
        $this->output()->writeln($message);
      }

      return TRUE;
    }

    return TRUE;
  }

  /**
   * Print table.
   *
   * @param array $arr
   *   Data for table output.
   */
  protected function printTable(array $arr): void {
    foreach ($arr as $index => $data) {
      if (is_string($index)) {
        $this->output()->writeln($index);
      }

      if (is_object($data)) {
        $data = (array) $data;
      }

      if (!isset($data['data'])) {
        if (is_string($data)) {
          $this->output()->writeln($data);
        }
      }
      else {
        if (!empty($data['horizontal'])) {
          $this->io()->horizontalTable($data['header'], $data['data']);
        }
        else {
          $this->io()->table($data['header'], $data['data']);
        }
      }
    }
  }

  /**
   * Set local variables.
   *
   * @param array $result
   *   Command result.
   */
  protected function setVariables(array $result): void {
    $variables = $result['variables'] ?? NULL;
    if (!$variables) {
      return;
    }

    foreach ($variables as $name => $value) {
      if (is_array($value)) {
        foreach ($value as $name2 => $value2) {
          $this->variables[$name2] = $value2;
        }
      }
      elseif (!is_object($value)) {
        $this->variables[$name] = $value;
      }
    }
  }

  /**
   * Parse condition expression.
   *
   * @param string $input
   *   Input value.
   */
  protected function parseCondition(&$input) {
    // Condition IF THEN ELSE.
    $pattern = '/\bIF\s+(.*?)\s+THEN\s+(.*?)(?:\s+ELSE\s+(.*))?$/i';
    if (preg_match($pattern, $input, $matches)) {
      $condition = trim($matches[1]);
      $then = trim($matches[2]);
      $else = isset($matches[3]) ? trim($matches[3]) : NULL;

      $eval_condition = $this->evalCondition($condition);
      if (is_bool($eval_condition)) {
        if ($eval_condition === TRUE) {
          $input = $then;
        }
        elseif ($eval_condition === FALSE) {
          $input = $else ? $else : '';
        }
      }
      else {
        $this->io()->error(dt('Error condition parsing'));
      }
    }
  }

  /**
   * Evaluate condition.
   *
   * @param string $condition
   *   Condition value.
   *
   * @return bool|int
   *   Boolean expression or error status.
   */
  protected function evalCondition($condition) {
    preg_match_all('/@([a-zA-Z0-9_.]+)/', $condition, $matches);
    $vars = $matches[0];
    if (empty($vars)) {
      return 0;
    }

    $params = [];
    foreach ($vars as $var) {
      $new_var = $this->foxCommandsHelper()->stringRender($var, $this->variables);
      if ($new_var != $var) {
        $key = ':' . substr($var, 1);
        $key = str_replace('.', '_', $key);
        $params[$key] = $new_var;
      }
    }

    if (empty($params)) {
      return 0;
    }

    $condition = str_replace('@', ':', $condition);
    $condition = str_replace('.', '_', $condition);

    $database = \Drupal::database();
    try {
      $query = $database->query("SELECT uid FROM {users} WHERE uid=0 AND ($condition)", $params);
      $result = $query->fetchAll();
    }
    catch (\Exception $e) {
      $this->io()->error($e->getMessage());
      return 0;
    }

    return count($result) > 0;
  }

}

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

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