[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/src/Console/ -> FinderIndexCommand.php (source)

   1  <?php
   2  
   3  /**
   4   * Joomla! CLI
   5   *
   6   * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
   7   * @license    GNU General Public License version 2 or later; see LICENSE.txt
   8   */
   9  
  10  namespace Joomla\CMS\Console;
  11  
  12  use Exception;
  13  use Joomla\CMS\Factory;
  14  use Joomla\CMS\Language\Text;
  15  use Joomla\CMS\Plugin\PluginHelper;
  16  use Joomla\Component\Finder\Administrator\Indexer\Indexer;
  17  use Joomla\Console\Command\AbstractCommand;
  18  use Joomla\Database\DatabaseInterface;
  19  use Symfony\Component\Console\Command\Command;
  20  use Symfony\Component\Console\Input\InputArgument;
  21  use Symfony\Component\Console\Input\InputInterface;
  22  use Symfony\Component\Console\Input\InputOption;
  23  use Symfony\Component\Console\Output\OutputInterface;
  24  use Symfony\Component\Console\Style\SymfonyStyle;
  25  
  26  // phpcs:disable PSR1.Files.SideEffects
  27  \defined('JPATH_PLATFORM') or die;
  28  // phpcs:enable PSR1.Files.SideEffects
  29  
  30  /**
  31   * Console command Purges and rebuilds the index (search filters are preserved)
  32   *
  33   * @since  4.0.0
  34   */
  35  class FinderIndexCommand extends AbstractCommand
  36  {
  37      /**
  38       * The default command name
  39       *
  40       * @var    string
  41       * @since  4.0.0
  42       */
  43      protected static $defaultName = 'finder:index';
  44  
  45      /**
  46       * Stores the Input Object
  47       *
  48       * @var    InputInterface
  49       * @since  4.0.0
  50       */
  51      private $cliInput;
  52  
  53      /**
  54       * SymfonyStyle Object
  55       *
  56       * @var    SymfonyStyle
  57       * @since  4.0.0
  58       */
  59      private $ioStyle;
  60  
  61      /**
  62       * Database connector
  63       *
  64       * @var    DatabaseInterface
  65       * @since  4.0.0
  66       */
  67      private $db;
  68  
  69      /**
  70       * Start time for the index process
  71       *
  72       * @var    string
  73       * @since  2.5
  74       */
  75      private $time;
  76  
  77      /**
  78       * Start time for each batch
  79       *
  80       * @var    string
  81       * @since  2.5
  82       */
  83      private $qtime;
  84  
  85      /**
  86       * Static filters information.
  87       *
  88       * @var    array
  89       * @since  3.3
  90       */
  91      private $filters = array();
  92  
  93      /**
  94       * Pausing type or defined pause time in seconds.
  95       * One pausing type is implemented: 'division' for dynamic calculation of pauses
  96       *
  97       * Defaults to 'division'
  98       *
  99       * @var    string|integer
 100       * @since  3.9.12
 101       */
 102      private $pause = 'division';
 103  
 104      /**
 105       * The divisor of the division: batch-processing time / divisor.
 106       * This is used together with --pause=division in order to pause dynamically
 107       * in relation to the processing time
 108       * Defaults to 5
 109       *
 110       * @var    integer
 111       * @since  3.9.12
 112       */
 113      private $divisor = 5;
 114  
 115      /**
 116       * Minimum processing time in seconds, in order to apply a pause
 117       * Defaults to 1
 118       *
 119       * @var    integer
 120       * @since  3.9.12
 121       */
 122      private $minimumBatchProcessingTime = 1;
 123  
 124      /**
 125       * Instantiate the command.
 126       *
 127       * @param   DatabaseInterface  $db  Database connector
 128       *
 129       * @since   4.0.0
 130       */
 131      public function __construct(DatabaseInterface $db)
 132      {
 133          $this->db = $db;
 134          parent::__construct();
 135      }
 136  
 137      /**
 138       * Initialise the command.
 139       *
 140       * @return  void
 141       *
 142       * @since   4.0.0
 143       */
 144      protected function configure(): void
 145      {
 146          $this->addArgument('purge', InputArgument::OPTIONAL, 'Purge the index and rebuilds');
 147          $this->addOption('minproctime', null, InputOption::VALUE_REQUIRED, 'Minimum processing time in seconds, in order to apply a pause', 1);
 148          $this->addOption('pause', null, InputOption::VALUE_REQUIRED, 'Pausing type or defined pause time in seconds', 'division');
 149          $this->addOption('divisor', null, InputOption::VALUE_REQUIRED, 'The divisor of the division: batch-processing time / divisor', 5);
 150          $help = <<<'EOF'
 151  The <info>%command.name%</info> Purges and rebuilds the index (search filters are preserved).
 152  
 153    <info>php %command.full_name%</info>
 154  EOF;
 155          $this->setDescription('Purges and rebuild the index');
 156          $this->setHelp($help);
 157      }
 158  
 159      /**
 160       * Internal function to execute the command.
 161       *
 162       * @param   InputInterface   $input   The input to inject into the command.
 163       * @param   OutputInterface  $output  The output to inject into the command.
 164       *
 165       * @return  integer  The command exit code
 166       *
 167       * @since   4.0.0
 168       */
 169      protected function doExecute(InputInterface $input, OutputInterface $output): int
 170      {
 171  
 172          // Initialize the time value.
 173          $this->time = microtime(true);
 174          $this->configureIO($input, $output);
 175  
 176          $this->ioStyle->writeln(
 177              [
 178              '<info>Finder Indexer</>',
 179              '<info>==========================</>',
 180              '',
 181              ]
 182          );
 183  
 184          if ($this->cliInput->getOption('minproctime')) {
 185              $this->minimumBatchProcessingTime = $this->cliInput->getOption('minproctime');
 186          }
 187  
 188          if ($this->cliInput->getOption('pause')) {
 189              $this->pause = $this->cliInput->getOption('pause');
 190          }
 191  
 192          if ($this->cliInput->getOption('divisor')) {
 193              $this->divisor = $this->cliInput->getOption('divisor');
 194          }
 195  
 196          if ($this->cliInput->getArgument('purge')) {
 197              // Taxonomy ids will change following a purge/index, so save filter information first.
 198              $this->getFilters();
 199  
 200              // Purge the index.
 201              $this->purge();
 202  
 203              // Run the indexer.
 204              $this->index();
 205  
 206              // Restore the filters again.
 207              $this->putFilters();
 208          } else {
 209              $this->index();
 210          }
 211  
 212          $this->ioStyle->newLine(1);
 213  
 214          // Total reporting.
 215          $this->ioStyle->writeln(
 216              [
 217              '<info>' . Text::sprintf('FINDER_CLI_PROCESS_COMPLETE', round(microtime(true) - $this->time, 3)) . '</>',
 218              '<info>' . Text::sprintf('FINDER_CLI_PEAK_MEMORY_USAGE', number_format(memory_get_peak_usage(true))) . '</>',
 219              ]
 220          );
 221  
 222          $this->ioStyle->newLine(1);
 223  
 224          return Command::SUCCESS;
 225      }
 226  
 227      /**
 228       * Configures the IO
 229       *
 230       * @param   InputInterface   $input   Console Input
 231       * @param   OutputInterface  $output  Console Output
 232       *
 233       * @return void
 234       *
 235       * @since 4.0.0
 236       *
 237       */
 238      private function configureIO(InputInterface $input, OutputInterface $output): void
 239      {
 240          $this->cliInput = $input;
 241          $this->ioStyle = new SymfonyStyle($input, $output);
 242          $language = Factory::getLanguage();
 243          $language->load('', JPATH_ADMINISTRATOR, null, false, false) ||
 244          $language->load('', JPATH_ADMINISTRATOR, null, true);
 245          $language->load('finder_cli', JPATH_SITE, null, false, false) ||
 246          $language->load('finder_cli', JPATH_SITE, null, true);
 247      }
 248  
 249      /**
 250       * Save static filters.
 251       *
 252       * Since a purge/index cycle will cause all the taxonomy ids to change,
 253       * the static filters need to be updated with the new taxonomy ids.
 254       * The static filter information is saved prior to the purge/index
 255       * so that it can later be used to update the filters with new ids.
 256       *
 257       * @return  void
 258       *
 259       * @since   4.0.0
 260       */
 261      private function getFilters(): void
 262      {
 263          $this->ioStyle->text(Text::_('FINDER_CLI_SAVE_FILTERS'));
 264  
 265          // Get the taxonomy ids used by the filters.
 266          $db    = $this->db;
 267          $query = $db->getQuery(true);
 268          $query
 269              ->select('filter_id, title, data')
 270              ->from($db->quoteName('#__finder_filters'));
 271          $filters = $db->setQuery($query)->loadObjectList();
 272  
 273          // Get the name of each taxonomy and the name of its parent.
 274          foreach ($filters as $filter) {
 275              // Skip empty filters.
 276              if ($filter->data === '') {
 277                  continue;
 278              }
 279  
 280              // Get taxonomy records.
 281              $query = $db->getQuery(true);
 282              $query
 283                  ->select('t.title, p.title AS parent')
 284                  ->from($db->quoteName('#__finder_taxonomy') . ' AS t')
 285                  ->leftJoin($db->quoteName('#__finder_taxonomy') . ' AS p ON p.id = t.parent_id')
 286                  ->where($db->quoteName('t.id') . ' IN (' . $filter->data . ')');
 287              $taxonomies = $db->setQuery($query)->loadObjectList();
 288  
 289              // Construct a temporary data structure to hold the filter information.
 290              foreach ($taxonomies as $taxonomy) {
 291                  $this->filters[$filter->filter_id][] = array(
 292                      'filter' => $filter->title,
 293                      'title'  => $taxonomy->title,
 294                      'parent' => $taxonomy->parent,
 295                  );
 296              }
 297          }
 298  
 299          $this->ioStyle->text(Text::sprintf('FINDER_CLI_SAVE_FILTER_COMPLETED', count($filters)));
 300      }
 301  
 302      /**
 303       * Purge the index.
 304       *
 305       * @return  void
 306       *
 307       * @since   3.3
 308       */
 309      private function purge()
 310      {
 311          $this->ioStyle->text(Text::_('FINDER_CLI_INDEX_PURGE'));
 312  
 313          // Load the model.
 314          $app = $this->getApplication();
 315          $model = $app->bootComponent('com_finder')->getMVCFactory($app)->createModel('Index', 'Administrator');
 316  
 317          // Attempt to purge the index.
 318          $return = $model->purge();
 319  
 320          // If unsuccessful then abort.
 321          if (!$return) {
 322              $message = Text::_('FINDER_CLI_INDEX_PURGE_FAILED', $model->getError());
 323              $this->ioStyle->error($message);
 324              exit();
 325          }
 326  
 327          $this->ioStyle->text(Text::_('FINDER_CLI_INDEX_PURGE_SUCCESS'));
 328      }
 329  
 330      /**
 331       * Run the indexer.
 332       *
 333       * @return  void
 334       *
 335       * @since   2.5
 336       */
 337      private function index()
 338      {
 339  
 340          // Disable caching.
 341          $app = $this->getApplication();
 342          $app->set('caching', 0);
 343          $app->set('cache_handler', 'file');
 344  
 345          // Reset the indexer state.
 346          Indexer::resetState();
 347  
 348          // Import the plugins.
 349          PluginHelper::importPlugin('system');
 350          PluginHelper::importPlugin('finder');
 351  
 352          // Starting Indexer.
 353          $this->ioStyle->text(Text::_('FINDER_CLI_STARTING_INDEXER'));
 354  
 355          // Trigger the onStartIndex event.
 356          $app->triggerEvent('onStartIndex');
 357  
 358          // Remove the script time limit.
 359          @set_time_limit(0);
 360  
 361          // Get the indexer state.
 362          $state = Indexer::getState();
 363  
 364          // Setting up plugins.
 365          $this->ioStyle->text(Text::_('FINDER_CLI_SETTING_UP_PLUGINS'));
 366  
 367          // Trigger the onBeforeIndex event.
 368          $app->triggerEvent('onBeforeIndex');
 369  
 370          // Startup reporting.
 371          $this->ioStyle->text(Text::sprintf('FINDER_CLI_SETUP_ITEMS', $state->totalItems, round(microtime(true) - $this->time, 3)));
 372  
 373          // Get the number of batches.
 374          $t = (int) $state->totalItems;
 375          $c = (int) ceil($t / $state->batchSize);
 376          $c = $c === 0 ? 1 : $c;
 377  
 378          try {
 379              // Process the batches.
 380              for ($i = 0; $i < $c; $i++) {
 381                  // Set the batch start time.
 382                  $this->qtime = microtime(true);
 383  
 384                  // Reset the batch offset.
 385                  $state->batchOffset = 0;
 386  
 387                  // Trigger the onBuildIndex event.
 388                  Factory::getApplication()->triggerEvent('onBuildIndex');
 389  
 390                  // Batch reporting.
 391                  $text = Text::sprintf('FINDER_CLI_BATCH_COMPLETE', $i + 1, $processingTime = round(microtime(true) - $this->qtime, 3));
 392                  $this->ioStyle->text($text);
 393  
 394                  if ($this->pause !== 0) {
 395                      // Pausing Section
 396                      $skip  = !($processingTime >= $this->minimumBatchProcessingTime);
 397                      $pause = 0;
 398  
 399                      if ($this->pause === 'division' && $this->divisor > 0) {
 400                          if (!$skip) {
 401                              $pause = round($processingTime / $this->divisor);
 402                          } else {
 403                              $pause = 1;
 404                          }
 405                      } elseif ($this->pause > 0) {
 406                          $pause = $this->pause;
 407                      }
 408  
 409                      if ($pause > 0 && !$skip) {
 410                          $this->ioStyle->text(Text::sprintf('FINDER_CLI_BATCH_PAUSING', $pause));
 411                          sleep($pause);
 412                          $this->ioStyle->text(Text::_('FINDER_CLI_BATCH_CONTINUING'));
 413                      }
 414  
 415                      if ($skip) {
 416                          $this->ioStyle->text(
 417                              Text::sprintf(
 418                                  'FINDER_CLI_SKIPPING_PAUSE_LOW_BATCH_PROCESSING_TIME',
 419                                  $processingTime,
 420                                  $this->minimumBatchProcessingTime
 421                              )
 422                          );
 423                      }
 424  
 425                      // End of Pausing Section
 426                  }
 427              }
 428          } catch (Exception $e) {
 429              // Display the error
 430              $this->ioStyle->error($e->getMessage());
 431  
 432              // Reset the indexer state.
 433              Indexer::resetState();
 434  
 435              // Close the app
 436              $app->close($e->getCode());
 437          }
 438  
 439          // Reset the indexer state.
 440          Indexer::resetState();
 441      }
 442  
 443      /**
 444       * Restore static filters.
 445       *
 446       * Using the saved filter information, update the filter records
 447       * with the new taxonomy ids.
 448       *
 449       * @return  void
 450       *
 451       * @since   3.3
 452       */
 453      private function putFilters()
 454      {
 455          $this->ioStyle->text(Text::_('FINDER_CLI_RESTORE_FILTERS'));
 456  
 457          $db = $this->db;
 458  
 459          // Use the temporary filter information to update the filter taxonomy ids.
 460          foreach ($this->filters as $filter_id => $filter) {
 461              $tids = array();
 462  
 463              foreach ($filter as $element) {
 464                  // Look for the old taxonomy in the new taxonomy table.
 465                  $query = $db->getQuery(true);
 466                  $query
 467                      ->select('t.id')
 468                      ->from($db->quoteName('#__finder_taxonomy') . ' AS t')
 469                      ->leftJoin($db->quoteName('#__finder_taxonomy') . ' AS p ON p.id = t.parent_id')
 470                      ->where($db->quoteName('t.title') . ' = ' . $db->quote($element['title']))
 471                      ->where($db->quoteName('p.title') . ' = ' . $db->quote($element['parent']));
 472                  $taxonomy = $db->setQuery($query)->loadResult();
 473  
 474                  // If we found it then add it to the list.
 475                  if ($taxonomy) {
 476                      $tids[] = $taxonomy;
 477                  } else {
 478                      $text = Text::sprintf('FINDER_CLI_FILTER_RESTORE_WARNING', $element['parent'], $element['title'], $element['filter']);
 479                      $this->ioStyle->text($text);
 480                  }
 481              }
 482  
 483              // Construct a comma-separated string from the taxonomy ids.
 484              $taxonomyIds = empty($tids) ? '' : implode(',', $tids);
 485  
 486              // Update the filter with the new taxonomy ids.
 487              $query = $db->getQuery(true);
 488              $query
 489                  ->update($db->quoteName('#__finder_filters'))
 490                  ->set($db->quoteName('data') . ' = ' . $db->quote($taxonomyIds))
 491                  ->where($db->quoteName('filter_id') . ' = ' . (int) $filter_id);
 492              $db->setQuery($query)->execute();
 493          }
 494  
 495          $this->ioStyle->text(Text::sprintf('FINDER_CLI_RESTORE_FILTER_COMPLETED', count($this->filters)));
 496      }
 497  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer