Current File : /home/pacjaorg/public_html/nsa/libraries/src/Console/FinderIndexCommand.php
<?php
/**
 * Joomla! CLI
 *
 * @copyright  (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Console;

\defined('JPATH_PLATFORM') or die;

use Exception;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Component\Finder\Administrator\Indexer\Indexer;
use Joomla\Console\Command\AbstractCommand;
use Joomla\Database\DatabaseInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Console command Purges and rebuilds the index (search filters are preserved)
 *
 * @since  4.0.0
 */
class FinderIndexCommand extends AbstractCommand
{
	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  4.0.0
	 */
	protected static $defaultName = 'finder:index';

	/**
	 * Stores the Input Object
	 *
	 * @var    InputInterface
	 * @since  4.0.0
	 */
	private $cliInput;

	/**
	 * SymfonyStyle Object
	 *
	 * @var    SymfonyStyle
	 * @since  4.0.0
	 */
	private $ioStyle;

	/**
	 * Database connector
	 *
	 * @var    DatabaseInterface
	 * @since  4.0.0
	 */
	private $db;

	/**
	 * Start time for the index process
	 *
	 * @var    string
	 * @since  2.5
	 */
	private $time;

	/**
	 * Start time for each batch
	 *
	 * @var    string
	 * @since  2.5
	 */
	private $qtime;

	/**
	 * Static filters information.
	 *
	 * @var    array
	 * @since  3.3
	 */
	private $filters = array();

	/**
	 * Pausing type or defined pause time in seconds.
	 * One pausing type is implemented: 'division' for dynamic calculation of pauses
	 *
	 * Defaults to 'division'
	 *
	 * @var    string|integer
	 * @since  3.9.12
	 */
	private $pause = 'division';

	/**
	 * The divisor of the division: batch-processing time / divisor.
	 * This is used together with --pause=division in order to pause dynamically
	 * in relation to the processing time
	 * Defaults to 5
	 *
	 * @var    integer
	 * @since  3.9.12
	 */
	private $divisor = 5;

	/**
	 * Minimum processing time in seconds, in order to apply a pause
	 * Defaults to 1
	 *
	 * @var    integer
	 * @since  3.9.12
	 */
	private $minimumBatchProcessingTime = 1;

	/**
	 * Instantiate the command.
	 *
	 * @param   DatabaseInterface  $db  Database connector
	 *
	 * @since   4.0.0
	 */
	public function __construct(DatabaseInterface $db)
	{
		$this->db = $db;
		parent::__construct();
	}

	/**
	 * Initialise the command.
	 *
	 * @return  void
	 *
	 * @since   4.0.0
	 */
	protected function configure(): void
	{
		$this->addArgument('purge', InputArgument::OPTIONAL, 'Purge the index and rebuilds');
		$this->addOption('minproctime', null, InputOption::VALUE_REQUIRED, 'Minimum processing time in seconds, in order to apply a pause', 1);
		$this->addOption('pause', null, InputOption::VALUE_REQUIRED, 'Pausing type or defined pause time in seconds', 'division');
		$this->addOption('divisor', null, InputOption::VALUE_REQUIRED, 'The divisor of the division: batch-processing time / divisor', 5);
		$help = <<<'EOF'
The <info>%command.name%</info> Purges and rebuilds the index (search filters are preserved).

  <info>php %command.full_name%</info>
EOF;
		$this->setDescription('Purges and rebuild the index');
		$this->setHelp($help);
	}

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   4.0.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{

		// Initialize the time value.
		$this->time = microtime(true);
		$this->configureIO($input, $output);

		$this->ioStyle->writeln(
			[
			'<info>Finder Indexer</>',
			'<info>==========================</>',
			'',
			]
		);

		if ($this->cliInput->getOption('minproctime'))
		{
			$this->minimumBatchProcessingTime = $this->cliInput->getOption('minproctime');
		}

		if ($this->cliInput->getOption('pause'))
		{
			$this->pause = $this->cliInput->getOption('pause');
		}

		if ($this->cliInput->getOption('divisor'))
		{
			$this->divisor = $this->cliInput->getOption('divisor');
		}

		if ($this->cliInput->getArgument('purge'))
		{
			// Taxonomy ids will change following a purge/index, so save filter information first.
			$this->getFilters();

			// Purge the index.
			$this->purge();

			// Run the indexer.
			$this->index();

			// Restore the filters again.
			$this->putFilters();
		}
		else
		{
			$this->index();
		}

		$this->ioStyle->newLine(1);

		// Total reporting.
		$this->ioStyle->writeln(
			[
			'<info>' . Text::sprintf('FINDER_CLI_PROCESS_COMPLETE', round(microtime(true) - $this->time, 3)) . '</>',
			'<info>' . Text::sprintf('FINDER_CLI_PEAK_MEMORY_USAGE', number_format(memory_get_peak_usage(true))) . '</>',
			]
		);

		$this->ioStyle->newLine(1);

		return Command::SUCCESS;
	}

	/**
	 * Configures the IO
	 *
	 * @param   InputInterface   $input   Console Input
	 * @param   OutputInterface  $output  Console Output
	 *
	 * @return void
	 *
	 * @since 4.0.0
	 *
	 */
	private function configureIO(InputInterface $input, OutputInterface $output): void
	{
		$this->cliInput = $input;
		$this->ioStyle = new SymfonyStyle($input, $output);
		$language = Factory::getLanguage();
		$language->load('', JPATH_ADMINISTRATOR, null, false, false) ||
		$language->load('', JPATH_ADMINISTRATOR, null, true);
		$language->load('finder_cli', JPATH_SITE, null, false, false)||
		$language->load('finder_cli', JPATH_SITE, null, true);
	}

	/**
	 * Save static filters.
	 *
	 * Since a purge/index cycle will cause all the taxonomy ids to change,
	 * the static filters need to be updated with the new taxonomy ids.
	 * The static filter information is saved prior to the purge/index
	 * so that it can later be used to update the filters with new ids.
	 *
	 * @return  void
	 *
	 * @since   4.0.0
	 */
	private function getFilters(): void
	{
		$this->ioStyle->text(Text::_('FINDER_CLI_SAVE_FILTERS'));

		// Get the taxonomy ids used by the filters.
		$db    = $this->db;
		$query = $db->getQuery(true);
		$query
			->select('filter_id, title, data')
			->from($db->quoteName('#__finder_filters'));
		$filters = $db->setQuery($query)->loadObjectList();

		// Get the name of each taxonomy and the name of its parent.
		foreach ($filters as $filter)
		{
			// Skip empty filters.
			if ($filter->data === '')
			{
				continue;
			}

			// Get taxonomy records.
			$query = $db->getQuery(true);
			$query
				->select('t.title, p.title AS parent')
				->from($db->quoteName('#__finder_taxonomy') . ' AS t')
				->leftJoin($db->quoteName('#__finder_taxonomy') . ' AS p ON p.id = t.parent_id')
				->where($db->quoteName('t.id') . ' IN (' . $filter->data . ')');
			$taxonomies = $db->setQuery($query)->loadObjectList();

			// Construct a temporary data structure to hold the filter information.
			foreach ($taxonomies as $taxonomy)
			{
				$this->filters[$filter->filter_id][] = array(
					'filter' => $filter->title,
					'title'  => $taxonomy->title,
					'parent' => $taxonomy->parent,
				);
			}
		}

		$this->ioStyle->text(Text::sprintf('FINDER_CLI_SAVE_FILTER_COMPLETED', count($filters)));
	}

	/**
	 * Purge the index.
	 *
	 * @return  void
	 *
	 * @since   3.3
	 */
	private function purge()
	{
		$this->ioStyle->text(Text::_('FINDER_CLI_INDEX_PURGE'));

		// Load the model.
		$app = $this->getApplication();
		$model = $app->bootComponent('com_finder')->getMVCFactory($app)->createModel('Index', 'Administrator');

		// Attempt to purge the index.
		$return = $model->purge();

		// If unsuccessful then abort.
		if (!$return)
		{
			$message = Text::_('FINDER_CLI_INDEX_PURGE_FAILED', $model->getError());
			$this->ioStyle->error($message);
			exit();
		}

		$this->ioStyle->text(Text::_('FINDER_CLI_INDEX_PURGE_SUCCESS'));
	}

	/**
	 * Run the indexer.
	 *
	 * @return  void
	 *
	 * @since   2.5
	 */
	private function index()
	{

		// Disable caching.
		$app = $this->getApplication();
		$app->set('caching', 0);
		$app->set('cache_handler', 'file');

		// Reset the indexer state.
		Indexer::resetState();

		// Import the plugins.
		PluginHelper::importPlugin('system');
		PluginHelper::importPlugin('finder');

		// Starting Indexer.
		$this->ioStyle->text(Text::_('FINDER_CLI_STARTING_INDEXER'));

		// Trigger the onStartIndex event.
		$app->triggerEvent('onStartIndex');

		// Remove the script time limit.
		@set_time_limit(0);

		// Get the indexer state.
		$state = Indexer::getState();

		// Setting up plugins.
		$this->ioStyle->text(Text::_('FINDER_CLI_SETTING_UP_PLUGINS'));

		// Trigger the onBeforeIndex event.
		$app->triggerEvent('onBeforeIndex');

		// Startup reporting.
		$this->ioStyle->text(Text::sprintf('FINDER_CLI_SETUP_ITEMS', $state->totalItems, round(microtime(true) - $this->time, 3)));

		// Get the number of batches.
		$t = (int) $state->totalItems;
		$c = (int) ceil($t / $state->batchSize);
		$c = $c === 0 ? 1 : $c;

		try
		{
			// Process the batches.
			for ($i = 0; $i < $c; $i++)
			{
				// Set the batch start time.
				$this->qtime = microtime(true);

				// Reset the batch offset.
				$state->batchOffset = 0;

				// Trigger the onBuildIndex event.
				Factory::getApplication()->triggerEvent('onBuildIndex');

				// Batch reporting.
				$text = Text::sprintf('FINDER_CLI_BATCH_COMPLETE', $i + 1, $processingTime = round(microtime(true) - $this->qtime, 3));
				$this->ioStyle->text($text);

				if ($this->pause !== 0)
				{
					// Pausing Section
					$skip  = !($processingTime >= $this->minimumBatchProcessingTime);
					$pause = 0;

					if ($this->pause === 'division' && $this->divisor > 0)
					{
						if (!$skip)
						{
							$pause = round($processingTime / $this->divisor);
						}
						else
						{
							$pause = 1;
						}
					}
					elseif ($this->pause > 0)
					{
						$pause = $this->pause;
					}

					if ($pause > 0 && !$skip)
					{
						$this->ioStyle->text(Text::sprintf('FINDER_CLI_BATCH_PAUSING', $pause));
						sleep($pause);
						$this->ioStyle->text(Text::_('FINDER_CLI_BATCH_CONTINUING'));
					}

					if ($skip)
					{
						$this->ioStyle->text(
							Text::sprintf(
								'FINDER_CLI_SKIPPING_PAUSE_LOW_BATCH_PROCESSING_TIME',
								$processingTime,
								$this->minimumBatchProcessingTime
							)
						);
					}

					// End of Pausing Section
				}
			}
		}
		catch (Exception $e)
		{
			// Display the error
			$this->ioStyle->error($e->getMessage());

			// Reset the indexer state.
			Indexer::resetState();

			// Close the app
			$app->close($e->getCode());
		}

		// Reset the indexer state.
		Indexer::resetState();
	}

	/**
	 * Restore static filters.
	 *
	 * Using the saved filter information, update the filter records
	 * with the new taxonomy ids.
	 *
	 * @return  void
	 *
	 * @since   3.3
	 */
	private function putFilters()
	{
		$this->ioStyle->text(Text::_('FINDER_CLI_RESTORE_FILTERS'));

		$db = $this->db;

		// Use the temporary filter information to update the filter taxonomy ids.
		foreach ($this->filters as $filter_id => $filter)
		{
			$tids = array();

			foreach ($filter as $element)
			{
				// Look for the old taxonomy in the new taxonomy table.
				$query = $db->getQuery(true);
				$query
					->select('t.id')
					->from($db->quoteName('#__finder_taxonomy') . ' AS t')
					->leftJoin($db->quoteName('#__finder_taxonomy') . ' AS p ON p.id = t.parent_id')
					->where($db->quoteName('t.title') . ' = ' . $db->quote($element['title']))
					->where($db->quoteName('p.title') . ' = ' . $db->quote($element['parent']));
				$taxonomy = $db->setQuery($query)->loadResult();

				// If we found it then add it to the list.
				if ($taxonomy)
				{
					$tids[] = $taxonomy;
				}
				else
				{
					$text = Text::sprintf('FINDER_CLI_FILTER_RESTORE_WARNING', $element['parent'], $element['title'], $element['filter']);
					$this->ioStyle->text($text);
				}
			}

			// Construct a comma-separated string from the taxonomy ids.
			$taxonomyIds = empty($tids) ? '' : implode(',', $tids);

			// Update the filter with the new taxonomy ids.
			$query = $db->getQuery(true);
			$query
				->update($db->quoteName('#__finder_filters'))
				->set($db->quoteName('data') . ' = ' . $db->quote($taxonomyIds))
				->where($db->quoteName('filter_id') . ' = ' . (int) $filter_id);
			$db->setQuery($query)->execute();
		}

		$this->ioStyle->text(Text::sprintf('FINDER_CLI_RESTORE_FILTER_COMPLETED', count($this->filters)));
	}

}
Site is undergoing maintenance

PACJA Events

Maintenance mode is on

Site will be available soon. Thank you for your patience!