[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |