primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,67 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Base controller class for Finder.
*
* @since 2.5
*/
class DisplayController extends BaseController
{
/**
* The default view.
*
* @var string
* @since 2.5
*/
protected $default_view = 'index';
/**
* Method to display a view.
*
* @param boolean $cachable If true, the view output will be cached
* @param array $urlparams An array of safe URL parameters and their variable types
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
*
* @return static|boolean A Controller object to support chaining or false on failure.
*
* @since 2.5
*/
public function display($cachable = false, $urlparams = [])
{
$view = $this->input->get('view', 'index', 'word');
$layout = $this->input->get('layout', 'index', 'word');
$filterId = $this->input->get('filter_id', null, 'int');
// Check for edit form.
if ($view === 'filter' && $layout === 'edit' && !$this->checkEditId('com_finder.edit.filter', $filterId)) {
// Somehow the person just went to the form - we don't allow that.
if (!\count($this->app->getMessageQueue())) {
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $filterId), 'error');
}
$this->setRedirect(Route::_('index.php?option=com_finder&view=filters', false));
return false;
}
return parent::display();
}
}

View File

@ -0,0 +1,230 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Controller;
use Joomla\CMS\Application\CMSWebApplicationInterface;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\FormController;
use Joomla\CMS\Router\Route;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Indexer controller class for Finder.
*
* @since 2.5
*/
class FilterController extends FormController
{
/**
* Method to save a record.
*
* @param string $key The name of the primary key of the URL variable.
* @param string $urlVar The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
*
* @return boolean True if successful, false otherwise.
*
* @since 2.5
*/
public function save($key = null, $urlVar = null)
{
// Check for request forgeries.
$this->checkToken();
/** @var \Joomla\Component\Finder\Administrator\Model\FilterModel $model */
$model = $this->getModel();
$table = $model->getTable();
$data = $this->input->post->get('jform', [], 'array');
$checkin = $table->hasField('checked_out');
$context = "$this->option.edit.$this->context";
$task = $this->getTask();
// Determine the name of the primary key for the data.
if (empty($key)) {
$key = $table->getKeyName();
}
// To avoid data collisions the urlVar may be different from the primary key.
if (empty($urlVar)) {
$urlVar = $key;
}
$recordId = $this->input->get($urlVar, '', 'int');
if (!$this->checkEditId($context, $recordId)) {
// Somehow the person just went to the form and tried to save it. We don't allow that.
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $recordId), 'error');
$this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false));
return false;
}
// Populate the row id from the session.
$data[$key] = $recordId;
// The save2copy task needs to be handled slightly differently.
if ($task === 'save2copy') {
// Check-in the original row.
if ($checkin && $model->checkin($data[$key]) === false) {
// Check-in failed. Go back to the item and display a notice.
if (!\count($this->app->getMessageQueue())) {
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'error');
}
$this->setRedirect('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $urlVar));
return false;
}
// Reset the ID and then treat the request as for Apply.
$data[$key] = 0;
$task = 'apply';
}
// Access check.
if (!$this->allowSave($data, $key)) {
$this->setMessage(Text::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'), 'error');
$this->setRedirect(Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false));
return false;
}
// Validate the posted data.
// Sometimes the form needs some posted data, such as for plugins and modules.
$form = $model->getForm($data, false);
if (!$form) {
$this->app->enqueueMessage($model->getError(), 'error');
return false;
}
// Test whether the data is valid.
$validData = $model->validate($form, $data);
// Check for validation errors.
if ($validData === false) {
// Get the validation messages.
$errors = $model->getErrors();
// Push up to three validation messages out to the user.
for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++) {
if ($errors[$i] instanceof \Exception) {
$this->app->enqueueMessage($errors[$i]->getMessage(), CMSWebApplicationInterface::MSG_ERROR);
} else {
$this->app->enqueueMessage($errors[$i], CMSWebApplicationInterface::MSG_ERROR);
}
}
// Save the data in the session.
$this->app->setUserState($context . '.data', $data);
// Redirect back to the edit screen.
$this->setRedirect(
Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key), false)
);
return false;
}
// Get and sanitize the filter data.
$validData['data'] = $this->input->post->get('t', [], 'array');
$validData['data'] = array_unique($validData['data']);
$validData['data'] = ArrayHelper::toInteger($validData['data']);
// Remove any values of zero.
if (array_search(0, $validData['data'], true)) {
unset($validData['data'][array_search(0, $validData['data'], true)]);
}
// Attempt to save the data.
if (!$model->save($validData)) {
// Save the data in the session.
$this->app->setUserState($context . '.data', $validData);
// Redirect back to the edit screen.
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error');
$this->setRedirect(
Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key), false)
);
return false;
}
// Save succeeded, so check-in the record.
if ($checkin && $model->checkin($validData[$key]) === false) {
// Save the data in the session.
$this->app->setUserState($context . '.data', $validData);
// Check-in failed, so go back to the record and display a notice.
$this->setMessage(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()), 'error');
$this->setRedirect('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key));
return false;
}
$this->setMessage(
Text::_(
($this->app->getLanguage()->hasKey($this->text_prefix . ($recordId === 0 && $this->app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS')
? $this->text_prefix : 'JLIB_APPLICATION') . ($recordId === 0 && $this->app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS'
)
);
// Redirect the user and adjust session state based on the chosen task.
switch ($task) {
case 'apply':
// Set the record data in the session.
$recordId = $model->getState($this->context . '.id');
$this->holdEditId($context, $recordId);
$this->app->setUserState($context . '.data', null);
$model->checkout($recordId);
// Redirect back to the edit screen.
$this->setRedirect(
Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend($recordId, $key), false)
);
break;
case 'save2new':
// Clear the record id and data from the session.
$this->releaseEditId($context, $recordId);
$this->app->setUserState($context . '.data', null);
// Redirect back to the edit screen.
$this->setRedirect(
Route::_('index.php?option=' . $this->option . '&view=' . $this->view_item . $this->getRedirectToItemAppend(null, $key), false)
);
break;
default:
// Clear the record id and data from the session.
$this->releaseEditId($context, $recordId);
$this->app->setUserState($context . '.data', null);
// Redirect to the list screen.
$this->setRedirect(
Route::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $this->getRedirectToListAppend(), false)
);
break;
}
// Invoke the postSave method to allow for the child class to access the model.
$this->postSaveHook($model, $validData);
return true;
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Controller;
use Joomla\CMS\MVC\Controller\AdminController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Filters controller class for Finder.
*
* @since 2.5
*/
class FiltersController extends AdminController
{
/**
* The prefix to use with controller messages.
*
* @var string
* @since 4.0.0
*/
protected $text_prefix = 'COM_FINDER_FILTERS';
/**
* Method to get a model object, loading it if required.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
*
* @since 2.5
*/
public function getModel($name = 'Filter', $prefix = 'Administrator', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, $config);
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Controller;
use Joomla\CMS\Event\Finder\GarbageCollectionEvent;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Component\Finder\Administrator\Indexer\Indexer;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Index controller class for Finder.
*
* @since 2.5
*/
class IndexController extends AdminController
{
/**
* Method to get a model object, loading it if required.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
*
* @since 2.5
*/
public function getModel($name = 'Index', $prefix = 'Administrator', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, $config);
}
/**
* Method to optimise the index by removing orphaned entries.
*
* @return boolean True on success.
*
* @since 4.2.0
*/
public function optimise()
{
$this->checkToken();
$dispatcher = $this->getDispatcher();
// Optimise the index by first running the garbage collection
PluginHelper::importPlugin('finder', null, true, $dispatcher);
$dispatcher->dispatch('onFinderGarbageCollection', new GarbageCollectionEvent('onFinderGarbageCollection', []));
// Now run the optimisation method from the indexer
$indexer = new Indexer();
$indexer->optimize();
$message = Text::_('COM_FINDER_INDEX_OPTIMISE_FINISHED');
$this->setRedirect('index.php?option=com_finder&view=index', $message);
return true;
}
/**
* Method to purge all indexed links from the database.
*
* @return boolean True on success.
*
* @since 2.5
*/
public function purge()
{
$this->checkToken();
// Remove the script time limit.
if (\function_exists('set_time_limit')) {
set_time_limit(0);
}
/** @var \Joomla\Component\Finder\Administrator\Model\IndexModel $model */
$model = $this->getModel('Index', 'Administrator');
// Attempt to purge the index.
$return = $model->purge();
if (!$return) {
$message = Text::_('COM_FINDER_INDEX_PURGE_FAILED', $model->getError());
$this->setRedirect('index.php?option=com_finder&view=index', $message);
return false;
}
$message = Text::_('COM_FINDER_INDEX_PURGE_SUCCESS');
$this->setRedirect('index.php?option=com_finder&view=index', $message);
return true;
}
}

View File

@ -0,0 +1,424 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Controller;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Event\Finder\BeforeIndexEvent;
use Joomla\CMS\Event\Finder\BuildIndexEvent;
use Joomla\CMS\Event\Finder\StartIndexEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Session\Session;
use Joomla\Component\Finder\Administrator\Indexer\Adapter;
use Joomla\Component\Finder\Administrator\Indexer\DebugAdapter;
use Joomla\Component\Finder\Administrator\Indexer\DebugIndexer;
use Joomla\Component\Finder\Administrator\Indexer\Indexer;
use Joomla\Component\Finder\Administrator\Response\Response;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Indexer controller class for Finder.
*
* @since 2.5
*/
class IndexerController extends BaseController
{
/**
* Method to start the indexer.
*
* @return void
*
* @since 2.5
*/
public function start()
{
// Check for a valid token. If invalid, send a 403 with the error message.
if (!Session::checkToken('request')) {
static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));
return;
}
$params = ComponentHelper::getParams('com_finder');
$dispatcher = $this->getDispatcher();
if ($params->get('enable_logging', '0')) {
$options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
$options['text_file'] = 'indexer.php';
Log::addLogger($options);
}
// Log the start
try {
Log::add('Starting the indexer', Log::INFO);
} catch (\RuntimeException $exception) {
// Informational log only
}
// We don't want this form to be cached.
$this->app->allowCache(false);
// Put in a buffer to silence noise.
ob_start();
// Reset the indexer state.
Indexer::resetState();
// Import the finder plugins.
PluginHelper::importPlugin('finder', null, true, $dispatcher);
// Add the indexer language to \JS
Text::script('COM_FINDER_AN_ERROR_HAS_OCCURRED');
Text::script('COM_FINDER_NO_ERROR_RETURNED');
// Start the indexer.
try {
// Trigger the onStartIndex event.
$dispatcher->dispatch('onStartIndex', new StartIndexEvent('onStartIndex', []));
// Get the indexer state.
$state = Indexer::getState();
$state->start = 1;
$output = ob_get_contents();
// Finder plugins should not create output of any kind. If there is output, that very likely is the result of a PHP error.
if (trim($output)) {
throw new \Exception(Text::_('COM_FINDER_AN_ERROR_HAS_OCCURRED'));
}
// Send the response.
static::sendResponse($state);
} catch (\Exception $e) {
// Catch an exception and return the response.
static::sendResponse($e);
}
}
/**
* Method to run the next batch of content through the indexer.
*
* @return void
*
* @since 2.5
*/
public function batch()
{
// Check for a valid token. If invalid, send a 403 with the error message.
if (!Session::checkToken('request')) {
static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));
return;
}
$params = ComponentHelper::getParams('com_finder');
$dispatcher = $this->getDispatcher();
if ($params->get('enable_logging', '0')) {
$options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
$options['text_file'] = 'indexer.php';
Log::addLogger($options);
}
// Log the start
try {
Log::add('Starting the indexer batch process', Log::INFO);
} catch (\RuntimeException $exception) {
// Informational log only
}
// We don't want this form to be cached.
$this->app->allowCache(false);
// Put in a buffer to silence noise.
ob_start();
// Remove the script time limit.
if (\function_exists('set_time_limit')) {
set_time_limit(0);
}
// Get the indexer state.
$state = Indexer::getState();
// Reset the batch offset.
$state->batchOffset = 0;
// Update the indexer state.
Indexer::setState($state);
// Import the finder plugins.
PluginHelper::importPlugin('finder', null, true, $dispatcher);
/*
* We are going to swap out the raw document object with an HTML document
* in order to work around some plugins that don't do proper environment
* checks before trying to use HTML document functions.
*/
$lang = $this->app->getLanguage();
// Get the document properties.
$attributes = [
'charset' => 'utf-8',
'lineend' => 'unix',
'tab' => ' ',
'language' => $lang->getTag(),
'direction' => $lang->isRtl() ? 'rtl' : 'ltr',
];
// Start the indexer.
try {
// Trigger the onBeforeIndex event.
$dispatcher->dispatch('onBeforeIndex', new BeforeIndexEvent('onBeforeIndex', []));
// Trigger the onBuildIndex event.
$dispatcher->dispatch('onBuildIndex', new BuildIndexEvent('onBuildIndex', []));
// Get the indexer state.
$state = Indexer::getState();
$state->start = 0;
$state->complete = 0;
// Log batch completion and memory high-water mark.
try {
Log::add('Batch completed, peak memory usage: ' . number_format(memory_get_peak_usage(true)) . ' bytes', Log::INFO);
} catch (\RuntimeException $exception) {
// Informational log only
}
$output = ob_get_contents();
// Finder plugins should not create output of any kind. If there is output, that very likely is the result of a PHP error.
if (trim($output)) {
throw new \Exception(Text::_('COM_FINDER_INDEXER_ERROR_PLUGIN_FAILURE'));
}
// Send the response.
static::sendResponse($state);
} catch (\Exception $e) {
// Catch an exception and return the response.
// Send the response.
static::sendResponse($e);
}
}
/**
* Method to optimize the index and perform any necessary cleanup.
*
* @return void
*
* @since 2.5
*/
public function optimize()
{
// Check for a valid token. If invalid, send a 403 with the error message.
if (!Session::checkToken('request')) {
static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));
return;
}
// We don't want this form to be cached.
$this->app->allowCache(false);
// Put in a buffer to silence noise.
ob_start();
// Import the finder plugins.
PluginHelper::importPlugin('finder', null, true, $this->getDispatcher());
try {
// Optimize the index
$indexer = new Indexer();
$indexer->optimize();
// Get the indexer state.
$state = Indexer::getState();
$state->start = 0;
$state->complete = 1;
$output = ob_get_contents();
// Finder plugins should not create output of any kind. If there is output, that very likely is the result of a PHP error.
if (trim($output)) {
throw new \Exception(Text::_('COM_FINDER_AN_ERROR_HAS_OCCURRED'));
}
// Send the response.
static::sendResponse($state);
} catch (\Exception $e) {
// Catch an exception and return the response.
static::sendResponse($e);
}
}
/**
* Method to handle a send a \JSON response. The body parameter
* can be an \Exception object for when an error has occurred or
* a CMSObject for a good response.
*
* @param \Joomla\CMS\Object\CMSObject|\Exception $data CMSObject on success, \Exception on error. [optional]
*
* @return void
*
* @since 2.5
*/
public static function sendResponse($data = null)
{
$app = Factory::getApplication();
$params = ComponentHelper::getParams('com_finder');
if ($params->get('enable_logging', '0')) {
$options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
$options['text_file'] = 'indexer.php';
Log::addLogger($options);
}
// Send the assigned error code if we are catching an exception.
if ($data instanceof \Exception) {
try {
Log::add($data->getMessage(), Log::ERROR);
} catch (\RuntimeException $exception) {
// Informational log only
}
$app->setHeader('status', $data->getCode());
}
// Create the response object.
$response = new Response($data);
if (\JDEBUG) {
// Add the buffer and memory usage
$response->buffer = ob_get_contents();
$response->memory = memory_get_usage(true);
}
ob_clean();
// Send the JSON response.
echo json_encode($response);
}
/**
* Method to call a specific indexing plugin and return debug info
*
* @return void
*
* @since 5.0.0
* @internal
*/
public function debug()
{
// Check for a valid token. If invalid, send a 403 with the error message.
if (!Session::checkToken('request')) {
static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));
return;
}
// We don't want this form to be cached.
$this->app->allowCache(false);
// Put in a buffer to silence noise.
ob_start();
// Remove the script time limit.
@set_time_limit(0);
// Get the indexer state.
Indexer::resetState();
$state = Indexer::getState();
// Reset the batch offset.
$state->batchOffset = 0;
// Update the indexer state.
Indexer::setState($state);
// Start the indexer.
try {
// Import the finder plugins.
class_alias(DebugAdapter::class, Adapter::class);
$plugin = $this->app->bootPlugin($this->app->getInput()->get('plugin'), 'finder');
$plugin->setIndexer(new DebugIndexer());
$plugin->debug($this->app->getInput()->get('id'));
$output = '';
// Create list of attributes
$output .= '<fieldset><legend>' . Text::_('COM_FINDER_INDEXER_FIELDSET_ATTRIBUTES') . '</legend>';
$output .= '<dl class="row">';
foreach (DebugIndexer::$item as $key => $value) {
$output .= '<dt class="col-sm-2">' . $key . '</dt><dd class="col-sm-10 text-break">' . $value . '</dd>';
}
$output .= '</dl>';
$output .= '</fieldset>';
$output .= '<fieldset><legend>' . Text::_('COM_FINDER_INDEXER_FIELDSET_ELEMENTS') . '</legend>';
$output .= '<dl class="row">';
foreach (DebugIndexer::$item->getElements() as $key => $element) {
$output .= '<dt class="col-sm-2">' . $key . '</dt><dd class="col-sm-10 text-break">' . $element . '</dd>';
}
$output .= '</dl>';
$output .= '</fieldset>';
$output .= '<fieldset><legend>' . Text::_('COM_FINDER_INDEXER_FIELDSET_INSTRUCTIONS') . '</legend>';
$output .= '<dl class="row">';
$contexts = [
1 => 'Title context',
2 => 'Text context',
3 => 'Meta context',
4 => 'Path context',
5 => 'Misc context',
];
foreach (DebugIndexer::$item->getInstructions() as $key => $element) {
$output .= '<dt class="col-sm-2">' . $contexts[$key] . '</dt><dd class="col-sm-10 text-break">' . json_encode($element) . '</dd>';
}
$output .= '</dl>';
$output .= '</fieldset>';
$output .= '<fieldset><legend>' . Text::_('COM_FINDER_INDEXER_FIELDSET_TAXONOMIES') . '</legend>';
$output .= '<dl class="row">';
foreach (DebugIndexer::$item->getTaxonomy() as $key => $element) {
$output .= '<dt class="col-sm-2">' . $key . '</dt><dd class="col-sm-10 text-break">' . json_encode($element) . '</dd>';
}
$output .= '</dl>';
$output .= '</fieldset>';
// Get the indexer state.
$state = Indexer::getState();
$state->start = 0;
$state->complete = 0;
$state->rendered = $output;
echo json_encode($state);
} catch (\Exception $e) {
// Catch an exception and return the response.
// Send the response.
static::sendResponse($e);
}
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Controller;
use Joomla\CMS\MVC\Controller\AdminController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Maps controller class for Finder.
*
* @since 2.5
*/
class MapsController extends AdminController
{
/**
* The prefix to use with controller messages.
*
* @var string
* @since 4.0.0
*/
protected $text_prefix = 'COM_FINDER_MAPS';
/**
* Method to get a model object, loading it if required.
*
* @param string $name The model name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return \Joomla\CMS\MVC\Model\BaseDatabaseModel The model.
*
* @since 1.6
*/
public function getModel($name = 'Maps', $prefix = 'Administrator', $config = ['ignore_request' => true])
{
return parent::getModel($name, $prefix, $config);
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Controller;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Session\Session;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of search terms.
*
* @since 4.0.0
*/
class SearchesController extends BaseController
{
/**
* Method to reset the search log table.
*
* @return void
*/
public function reset()
{
// Check for request forgeries.
Session::checkToken() or jexit(Text::_('JINVALID_TOKEN'));
$model = $this->getModel('Searches');
if (!$model->reset()) {
$this->app->enqueueMessage($model->getError(), 'error');
}
$this->setRedirect('index.php?option=com_finder&view=searches');
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Extension;
use Joomla\CMS\Component\Router\RouterServiceInterface;
use Joomla\CMS\Component\Router\RouterServiceTrait;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use Joomla\Component\Finder\Administrator\Service\HTML\Filter;
use Joomla\Component\Finder\Administrator\Service\HTML\Finder;
use Joomla\Component\Finder\Administrator\Service\HTML\Query;
use Joomla\Database\DatabaseInterface;
use Psr\Container\ContainerInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Component class for com_finder
*
* @since 4.0.0
*/
class FinderComponent extends MVCComponent implements BootableExtensionInterface, RouterServiceInterface
{
use RouterServiceTrait;
use HTMLRegistryAwareTrait;
/**
* Booting the extension. This is the function to set up the environment of the extension like
* registering new class loaders, etc.
*
* If required, some initial set up can be done from services of the container, eg.
* registering HTML services.
*
* @param ContainerInterface $container The container
*
* @return void
*
* @since 4.0.0
*/
public function boot(ContainerInterface $container)
{
$finder = new Finder();
$finder->setDatabase($container->get(DatabaseInterface::class));
$this->getRegistry()->register('finder', $finder);
$filter = new Filter();
$filter->setDatabase($container->get(DatabaseInterface::class));
$this->getRegistry()->register('filter', $filter);
$this->getRegistry()->register('query', new Query());
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2015 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Field;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\HTML\HTMLHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Search Branches field for the Finder package.
*
* @since 3.5
*/
class BranchesField extends ListField
{
/**
* The form field type.
*
* @var string
* @since 3.5
*/
protected $type = 'Branches';
/**
* Method to get the field options.
*
* @return array The field option objects.
*
* @since 3.5
*/
public function getOptions()
{
Factory::getApplication()->bootComponent('com_finder');
return HTMLHelper::_('finder.mapslist');
}
}

View File

@ -0,0 +1,128 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Field;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\GroupedlistField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Supports a select grouped list of finder content map.
*
* @since 3.6.0
*/
class ContentmapField extends GroupedlistField
{
/**
* The form field type.
*
* @var string
* @since 3.6.0
*/
public $type = 'ContentMap';
/**
* Method to get the list of content map options grouped by first level.
*
* @return array The field option objects as a nested array in groups.
*
* @since 3.6.0
*/
protected function getGroups()
{
$groups = [];
// Get the database object and a new query object.
$db = $this->getDatabase();
// Main query.
$query = $db->getQuery(true)
->select($db->quoteName('a.title', 'text'))
->select($db->quoteName('a.id', 'value'))
->select($db->quoteName('a.parent_id'))
->select($db->quoteName('a.level'))
->from($db->quoteName('#__finder_taxonomy', 'a'))
->where($db->quoteName('a.parent_id') . ' <> 0')
->order('a.title ASC');
$db->setQuery($query);
try {
$contentMap = $db->loadObjectList();
} catch (\RuntimeException $e) {
return [];
}
// Build the grouped list array.
if ($contentMap) {
$parents = [];
foreach ($contentMap as $item) {
if (!isset($parents[$item->parent_id])) {
$parents[$item->parent_id] = [];
}
$parents[$item->parent_id][] = $item;
}
foreach ($parents[1] as $branch) {
$text = Text::_(LanguageHelper::branchSingular($branch->text));
$groups[$text] = $this->prepareLevel($branch->value, $parents);
}
}
// Merge any additional groups in the XML definition.
$groups = array_merge(parent::getGroups(), $groups);
return $groups;
}
/**
* Indenting and translating options for the list
*
* @param int $parent Parent ID to process
* @param array $parents Array of arrays of items with parent IDs as keys
*
* @return array The indented list of entries for this branch
*
* @since 4.1.5
*/
private function prepareLevel($parent, $parents)
{
$lang = Factory::getLanguage();
$entries = [];
foreach ($parents[$parent] as $item) {
$levelPrefix = str_repeat('- ', $item->level - 1);
if (trim($item->text, '*') === 'Language') {
$text = LanguageHelper::branchLanguageTitle($item->text);
} else {
$key = LanguageHelper::branchSingular($item->text);
$text = $lang->hasKey($key) ? Text::_($key) : $item->text;
}
$entries[] = HTMLHelper::_('select.option', $item->value, $levelPrefix . $text);
if (isset($parents[$item->value])) {
$entries = array_merge($entries, $this->prepareLevel($item->value, $parents));
}
}
return $entries;
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2016 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Field;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Content Types Filter field for the Finder package.
*
* @since 3.6.0
*/
class ContenttypesField extends ListField
{
/**
* The form field type.
*
* @var string
* @since 3.6.0
*/
protected $type = 'ContentTypes';
/**
* Method to get the field options.
*
* @return array The field option objects.
*
* @since 3.6.0
*/
public function getOptions()
{
$lang = Factory::getLanguage();
$options = [];
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName('id', 'value'))
->select($db->quoteName('title', 'text'))
->from($db->quoteName('#__finder_types'));
// Get the options.
$db->setQuery($query);
try {
$contentTypes = $db->loadObjectList();
} catch (\RuntimeException $e) {
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
}
// Translate.
foreach ($contentTypes as $contentType) {
$key = LanguageHelper::branchSingular($contentType->text);
$contentType->translatedText = $lang->hasKey($key) ? Text::_($key) : $contentType->text;
}
// Order by title.
$contentTypes = ArrayHelper::sortObjects($contentTypes, 'translatedText', 1, true, true);
// Convert the values to options.
foreach ($contentTypes as $contentType) {
$options[] = HTMLHelper::_('select.option', $contentType->value, $contentType->translatedText);
}
// Merge any additional options in the XML definition.
$options = array_merge(parent::getOptions(), $options);
return $options;
}
}

View File

@ -0,0 +1,59 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Field;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Search Filter field for the Finder package.
*
* @since 2.5
*/
class SearchfilterField extends ListField
{
/**
* The form field type.
*
* @var string
* @since 2.5
*/
protected $type = 'SearchFilter';
/**
* Method to get the field options.
*
* @return array The field option objects.
*
* @since 2.5
*/
public function getOptions()
{
// Build the query.
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('f.title AS text, f.filter_id AS value')
->from($db->quoteName('#__finder_filters') . ' AS f')
->where('f.state = 1')
->order('f.title ASC');
$db->setQuery($query);
$options = $db->loadObjectList();
array_unshift($options, HTMLHelper::_('select.option', '', Text::_('COM_FINDER_SELECT_SEARCH_FILTER'), 'value', 'text'));
return $options;
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Field;
use Joomla\CMS\Form\Field\CheckboxesField;
/**
* Taxonomy Types field for the Finder package.
* This is a helper to allow to save an empty set of
* options by having a hidden field with a "none" value.
*
* @since 5.0.0
*/
class TaxonomytypesField extends CheckboxesField
{
/**
* The form field type.
*
* @var string
* @since 5.0.0
*/
protected $type = 'TaxonomyTypes';
/**
* Method to get the field input markup for a generic list.
* Use the multiple attribute to enable multiselect.
*
* @return string The field input markup.
*
* @since 5.0.0
*/
protected function getInput()
{
$html = parent::getInput();
$data = $this->getLayoutData();
$data['id'] .= '_hidden';
$data['value'] = 'none';
return $html . $this->getRenderer('joomla.form.field.hidden')->render($data);
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Helper;
use Joomla\CMS\Extension\ExtensionHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Helper class for Finder.
*
* @since 2.5
*/
class FinderHelper
{
/**
* The extension name.
*
* @var string
* @since 2.5
*/
public static $extension = 'com_finder';
/**
* Gets the finder system plugin extension id.
*
* @return integer The finder system plugin extension id.
*
* @since 3.6.0
*/
public static function getFinderPluginId()
{
$pluginRecord = ExtensionHelper::getExtensionRecord('finder', 'plugin', null, 'content');
return $pluginRecord !== null ? $pluginRecord->extension_id : 0;
}
}

View File

@ -0,0 +1,151 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Helper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\LanguageHelper as CMSLanguageHelper;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Finder language helper class.
*
* @since 2.5
*/
class LanguageHelper
{
/**
* Method to return a plural language code for a taxonomy branch.
*
* @param string $branchName Branch title.
*
* @return string Language key code.
*
* @since 2.5
*/
public static function branchPlural($branchName)
{
$return = preg_replace('/[^a-zA-Z0-9]+/', '_', strtoupper($branchName));
if ($return !== '_') {
return 'PLG_FINDER_QUERY_FILTER_BRANCH_P_' . $return;
}
return $branchName;
}
/**
* Method to return a singular language code for a taxonomy branch.
*
* @param string $branchName Branch name.
*
* @return string Language key code.
*
* @since 2.5
*/
public static function branchSingular($branchName)
{
$return = preg_replace('/[^a-zA-Z0-9]+/', '_', strtoupper($branchName));
$language = Factory::getApplication()->getLanguage();
$debug = Factory::getApplication()->get('debug_lang');
if ($language->hasKey('PLG_FINDER_QUERY_FILTER_BRANCH_S_' . $return) || $debug) {
return 'PLG_FINDER_QUERY_FILTER_BRANCH_S_' . $return;
}
return $branchName;
}
/**
* Method to return the language name for a language taxonomy branch.
*
* @param string $branchName Language branch name.
*
* @return string The language title.
*
* @since 3.6.0
*/
public static function branchLanguageTitle($branchName)
{
$title = $branchName;
if ($branchName === '*') {
$title = Text::_('JALL_LANGUAGE');
} else {
$languages = CMSLanguageHelper::getLanguages('lang_code');
if (isset($languages[$branchName])) {
$title = $languages[$branchName]->title;
}
}
return $title;
}
/**
* Method to load Smart Search component language file.
*
* @return void
*
* @since 2.5
*/
public static function loadComponentLanguage()
{
Factory::getLanguage()->load('com_finder', JPATH_SITE);
}
/**
* Method to load Smart Search plugin language files.
*
* @return void
*
* @since 2.5
*/
public static function loadPluginLanguage()
{
static $loaded = false;
// If already loaded, don't load again.
if ($loaded) {
return;
}
$loaded = true;
// Get array of all the enabled Smart Search plugin names.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select([$db->quoteName('name'), $db->quoteName('element')])
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('finder'))
->where($db->quoteName('enabled') . ' = 1');
$db->setQuery($query);
$plugins = $db->loadObjectList();
if (empty($plugins)) {
return;
}
// Load generic language strings.
$lang = Factory::getLanguage();
$lang->load('plg_content_finder', JPATH_ADMINISTRATOR);
// Load language file for each plugin.
foreach ($plugins as $plugin) {
$lang->load($plugin->name, JPATH_ADMINISTRATOR)
|| $lang->load($plugin->name, JPATH_PLUGINS . '/finder/' . $plugin->element);
}
}
}

View File

@ -0,0 +1,942 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Indexer;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseInterface;
use Joomla\Database\QueryInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Prototype adapter class for the Finder indexer package.
*
* @since 2.5
*/
abstract class Adapter extends CMSPlugin
{
/**
* The context is somewhat arbitrary but it must be unique or there will be
* conflicts when managing plugin/indexer state. A good best practice is to
* use the plugin name suffix as the context. For example, if the plugin is
* named 'plgFinderContent', the context could be 'Content'.
*
* @var string
* @since 2.5
*/
protected $context;
/**
* The extension name.
*
* @var string
* @since 2.5
*/
protected $extension;
/**
* The sublayout to use when rendering the results.
*
* @var string
* @since 2.5
*/
protected $layout;
/**
* The mime type of the content the adapter indexes.
*
* @var string
* @since 2.5
*/
protected $mime;
/**
* The access level of an item before save.
*
* @var integer
* @since 2.5
*/
protected $old_access;
/**
* The access level of a category before save.
*
* @var integer
* @since 2.5
*/
protected $old_cataccess;
/**
* The type of content the adapter indexes.
*
* @var string
* @since 2.5
*/
protected $type_title;
/**
* The type id of the content.
*
* @var integer
* @since 2.5
*/
protected $type_id;
/**
* The database object.
*
* @var DatabaseInterface
* @since 2.5
*/
protected $db;
/**
* The table name.
*
* @var string
* @since 2.5
*/
protected $table;
/**
* The indexer object.
*
* @var Indexer
* @since 3.0
*/
protected $indexer;
/**
* The field the published state is stored in.
*
* @var string
* @since 2.5
*/
protected $state_field = 'state';
/**
* Method to instantiate the indexer adapter.
*
* @param DispatcherInterface $dispatcher The object to observe.
* @param array $config An array that holds the plugin configuration.
*
* @since 2.5
*/
public function __construct(DispatcherInterface $dispatcher, array $config)
{
// Call the parent constructor.
parent::__construct($dispatcher, $config);
// Get the type id.
$this->type_id = $this->getTypeId();
// Add the content type if it doesn't exist and is set.
if (empty($this->type_id) && !empty($this->type_title)) {
$this->type_id = Helper::addContentType($this->type_title, $this->mime);
}
// Check for a layout override.
if ($this->params->get('layout')) {
$this->layout = $this->params->get('layout');
}
// Get the indexer object
$this->indexer = new Indexer($this->db);
}
/**
* Returns an array of events this subscriber will listen to.
*
* @return array
*
* @since 5.0.0
*/
public static function getSubscribedEvents(): array
{
return [
'onBeforeIndex' => 'onBeforeIndex',
'onBuildIndex' => 'onBuildIndex',
'onFinderGarbageCollection' => 'onFinderGarbageCollection',
'onStartIndex' => 'onStartIndex',
];
}
/**
* Method to get the adapter state and push it into the indexer.
*
* @return void
*
* @since 2.5
* @throws \Exception on error.
*/
public function onStartIndex()
{
// Get the indexer state.
$iState = Indexer::getState();
// Get the number of content items.
$total = (int) $this->getContentCount();
// Add the content count to the total number of items.
$iState->totalItems += $total;
// Populate the indexer state information for the adapter.
$iState->pluginState[$this->context]['total'] = $total;
$iState->pluginState[$this->context]['offset'] = 0;
// Set the indexer state.
Indexer::setState($iState);
}
/**
* Method to prepare for the indexer to be run. This method will often
* be used to include dependencies and things of that nature.
*
* @return boolean True on success.
*
* @since 2.5
* @throws \Exception on error.
*/
public function onBeforeIndex()
{
// Get the indexer and adapter state.
$iState = Indexer::getState();
$aState = $iState->pluginState[$this->context];
// Check the progress of the indexer and the adapter.
if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total']) {
return true;
}
// Run the setup method.
return $this->setup();
}
/**
* Method to index a batch of content items. This method can be called by
* the indexer many times throughout the indexing process depending on how
* much content is available for indexing. It is important to track the
* progress correctly so we can display it to the user.
*
* @return boolean True on success.
*
* @since 2.5
* @throws \Exception on error.
*/
public function onBuildIndex()
{
// Get the indexer and adapter state.
$iState = Indexer::getState();
$aState = $iState->pluginState[$this->context];
// Check the progress of the indexer and the adapter.
if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total']) {
return true;
}
// Get the batch offset and size.
$offset = (int) $aState['offset'];
$limit = (int) ($iState->batchSize - $iState->batchOffset);
// Get the content items to index.
$items = $this->getItems($offset, $limit);
// Iterate through the items and index them.
foreach ($items as $item) {
// Index the item.
$this->index($item);
// Adjust the offsets.
$offset++;
$iState->batchOffset++;
$iState->totalItems--;
}
// Update the indexer state.
$aState['offset'] = $offset;
$iState->pluginState[$this->context] = $aState;
Indexer::setState($iState);
return true;
}
/**
* Method to remove outdated index entries
*
* @return integer
*
* @since 4.2.0
*/
public function onFinderGarbageCollection()
{
$db = $this->db;
$type_id = $this->getTypeId();
$query = $db->getQuery(true);
$subquery = $db->getQuery(true);
$subquery->select('CONCAT(' . $db->quote($this->getUrl('', $this->extension, $this->layout)) . ', id)')
->from($db->quoteName($this->table));
$query->select($db->quoteName('l.link_id'))
->from($db->quoteName('#__finder_links', 'l'))
->where($db->quoteName('l.type_id') . ' = ' . $type_id)
->where($db->quoteName('l.url') . ' LIKE ' . $db->quote($this->getUrl('%', $this->extension, $this->layout)))
->where($db->quoteName('l.url') . ' NOT IN (' . $subquery . ')');
$db->setQuery($query);
$items = $db->loadColumn();
foreach ($items as $item) {
$this->indexer->remove($item);
}
return \count($items);
}
/**
* Method to change the value of a content item's property in the links
* table. This is used to synchronize published and access states that
* are changed when not editing an item directly.
*
* @param string $id The ID of the item to change.
* @param string $property The property that is being changed.
* @param integer $value The new value of that property.
*
* @return boolean True on success.
*
* @since 2.5
* @throws \Exception on database error.
*/
protected function change($id, $property, $value)
{
// Check for a property we know how to handle.
if ($property !== 'state' && $property !== 'access') {
return true;
}
// Get the URL for the content id.
$item = $this->db->quote($this->getUrl($id, $this->extension, $this->layout));
// Update the content items.
$query = $this->db->getQuery(true)
->update($this->db->quoteName('#__finder_links'))
->set($this->db->quoteName($property) . ' = ' . (int) $value)
->where($this->db->quoteName('url') . ' = ' . $item);
$this->db->setQuery($query);
$this->db->execute();
return true;
}
/**
* Method to index an item.
*
* @param Result $item The item to index as a Result object.
*
* @return boolean True on success.
*
* @since 2.5
* @throws \Exception on database error.
*/
abstract protected function index(Result $item);
/**
* Method to reindex an item.
*
* @param integer $id The ID of the item to reindex.
*
* @return void
*
* @since 2.5
* @throws \Exception on database error.
*/
protected function reindex($id)
{
// Run the setup method.
$this->setup();
// Get the item.
$item = $this->getItem($id);
// Index the item.
$this->index($item);
Taxonomy::removeOrphanNodes();
}
/**
* Method to remove an item from the index.
*
* @param string $id The ID of the item to remove.
* @param bool $removeTaxonomies Remove empty taxonomies
*
* @return boolean True on success.
*
* @since 2.5
* @throws \Exception on database error.
*/
protected function remove($id, $removeTaxonomies = true)
{
// Get the item's URL
$url = $this->db->quote($this->getUrl($id, $this->extension, $this->layout));
// Get the link ids for the content items.
$query = $this->db->getQuery(true)
->select($this->db->quoteName('link_id'))
->from($this->db->quoteName('#__finder_links'))
->where($this->db->quoteName('url') . ' = ' . $url);
$this->db->setQuery($query);
$items = $this->db->loadColumn();
// Check the items.
if (empty($items)) {
Factory::getApplication()->triggerEvent('onFinderIndexAfterDelete', [$id]);
return true;
}
// Remove the items.
foreach ($items as $item) {
$this->indexer->remove($item, $removeTaxonomies);
}
return true;
}
/**
* Method to setup the adapter before indexing.
*
* @return boolean True on success, false on failure.
*
* @since 2.5
* @throws \Exception on database error.
*/
abstract protected function setup();
/**
* Method to update index data on category access level changes
*
* @param Table $row A Table object
*
* @return void
*
* @since 2.5
*/
protected function categoryAccessChange($row)
{
$query = clone $this->getStateQuery();
$query->where('c.id = ' . (int) $row->id);
// Get the access level.
$this->db->setQuery($query);
$items = $this->db->loadObjectList();
// Adjust the access level for each item within the category.
foreach ($items as $item) {
// Set the access level.
$temp = max($item->access, $row->access);
// Update the item.
$this->change((int) $item->id, 'access', $temp);
}
}
/**
* Method to update index data on category access level changes
*
* @param array $pks A list of primary key ids of the content that has changed state.
* @param integer $value The value of the state that the content has been changed to.
*
* @return void
*
* @since 2.5
*/
protected function categoryStateChange($pks, $value)
{
/*
* The item's published state is tied to the category
* published state so we need to look up all published states
* before we change anything.
*/
foreach ($pks as $pk) {
$query = clone $this->getStateQuery();
$query->where('c.id = ' . (int) $pk);
// Get the published states.
$this->db->setQuery($query);
$items = $this->db->loadObjectList();
// Adjust the state for each item within the category.
foreach ($items as $item) {
// Translate the state.
$temp = $this->translateState($item->state, $value);
// Update the item.
$this->change($item->id, 'state', $temp);
}
}
}
/**
* Method to check the existing access level for categories
*
* @param Table $row A Table object
*
* @return void
*
* @since 2.5
*/
protected function checkCategoryAccess($row)
{
$query = $this->db->getQuery(true)
->select($this->db->quoteName('access'))
->from($this->db->quoteName('#__categories'))
->where($this->db->quoteName('id') . ' = ' . (int) $row->id);
$this->db->setQuery($query);
// Store the access level to determine if it changes
$this->old_cataccess = $this->db->loadResult();
}
/**
* Method to check the existing access level for items
*
* @param Table $row A Table object
*
* @return void
*
* @since 2.5
*/
protected function checkItemAccess($row)
{
$query = $this->db->getQuery(true)
->select($this->db->quoteName('access'))
->from($this->db->quoteName($this->table))
->where($this->db->quoteName('id') . ' = ' . (int) $row->id);
$this->db->setQuery($query);
// Store the access level to determine if it changes
$this->old_access = $this->db->loadResult();
}
/**
* Method to get the number of content items available to index.
*
* @return integer The number of content items available to index.
*
* @since 2.5
* @throws \Exception on database error.
*/
protected function getContentCount()
{
$return = 0;
// Get the list query.
$query = $this->getListQuery();
// Check if the query is valid.
if (empty($query)) {
return $return;
}
// Tweak the SQL query to make the total lookup faster.
if ($query instanceof QueryInterface) {
$query = clone $query;
$query->clear('select')
->select('COUNT(*)')
->clear('order');
}
// Get the total number of content items to index.
$this->db->setQuery($query);
return (int) $this->db->loadResult();
}
/**
* Method to get a content item to index.
*
* @param integer $id The id of the content item.
*
* @return Result A Result object.
*
* @since 2.5
* @throws \Exception on database error.
*/
protected function getItem($id)
{
// Get the list query and add the extra WHERE clause.
$query = $this->getListQuery();
$query->where('a.id = ' . (int) $id);
// Get the item to index.
$this->db->setQuery($query);
$item = $this->db->loadAssoc();
// Convert the item to a result object.
$item = ArrayHelper::toObject((array) $item, Result::class);
// Set the item type.
$item->type_id = $this->type_id;
// Set the item layout.
$item->layout = $this->layout;
return $item;
}
/**
* Method to get a list of content items to index.
*
* @param integer $offset The list offset.
* @param integer $limit The list limit.
* @param QueryInterface $query A QueryInterface object. [optional]
*
* @return Result[] An array of Result objects.
*
* @since 2.5
* @throws \Exception on database error.
*/
protected function getItems($offset, $limit, $query = null)
{
// Get the content items to index.
$this->db->setQuery($this->getListQuery($query)->setLimit($limit, $offset));
$items = $this->db->loadAssocList();
foreach ($items as &$item) {
$item = ArrayHelper::toObject($item, Result::class);
// Set the item type.
$item->type_id = $this->type_id;
// Set the mime type.
$item->mime = $this->mime;
// Set the item layout.
$item->layout = $this->layout;
}
return $items;
}
/**
* Method to get the SQL query used to retrieve the list of content items.
*
* @param mixed $query A QueryInterface object. [optional]
*
* @return QueryInterface A database object.
*
* @since 2.5
*/
protected function getListQuery($query = null)
{
// Check if we can use the supplied SQL query.
return $query instanceof QueryInterface ? $query : $this->db->getQuery(true);
}
/**
* Method to get the plugin type
*
* @param integer $id The plugin ID
*
* @return string|null The plugin type
*
* @since 2.5
*/
protected function getPluginType($id)
{
// Prepare the query
$query = $this->db->getQuery(true)
->select($this->db->quoteName('element'))
->from($this->db->quoteName('#__extensions'))
->where($this->db->quoteName('folder') . ' = ' . $this->db->quote('finder'))
->where($this->db->quoteName('extension_id') . ' = ' . (int) $id);
$this->db->setQuery($query);
return $this->db->loadResult();
}
/**
* Method to get a SQL query to load the published and access states for
* an article and category.
*
* @return QueryInterface A database object.
*
* @since 2.5
*/
protected function getStateQuery()
{
$query = $this->db->getQuery(true);
// Item ID
$query->select('a.id');
// Item and category published state
$query->select('a.' . $this->state_field . ' AS state, c.published AS cat_state');
// Item and category access levels
$query->select('a.access, c.access AS cat_access')
->from($this->table . ' AS a')
->join('LEFT', '#__categories AS c ON c.id = a.catid');
return $query;
}
/**
* Method to get the query clause for getting items to update by time.
*
* @param string $time The modified timestamp.
*
* @return QueryInterface A database object.
*
* @since 2.5
*/
protected function getUpdateQueryByTime($time)
{
// Build an SQL query based on the modified time.
$query = $this->db->getQuery(true)
->where('a.modified >= ' . $this->db->quote($time));
return $query;
}
/**
* Method to get the query clause for getting items to update by id.
*
* @param array $ids The ids to load.
*
* @return QueryInterface A database object.
*
* @since 2.5
*/
protected function getUpdateQueryByIds($ids)
{
// Build an SQL query based on the item ids.
$query = $this->db->getQuery(true)
->where('a.id IN(' . implode(',', $ids) . ')');
return $query;
}
/**
* Method to get the type id for the adapter content.
*
* @return integer The numeric type id for the content.
*
* @since 2.5
* @throws \Exception on database error.
*/
protected function getTypeId()
{
// Get the type id from the database.
$query = $this->db->getQuery(true)
->select($this->db->quoteName('id'))
->from($this->db->quoteName('#__finder_types'))
->where($this->db->quoteName('title') . ' = ' . $this->db->quote($this->type_title));
$this->db->setQuery($query);
return (int) $this->db->loadResult();
}
/**
* Method to get the URL for the item. The URL is how we look up the link
* in the Finder index.
*
* @param integer $id The id of the item.
* @param string $extension The extension the category is in.
* @param string $view The view for the URL.
*
* @return string The URL of the item.
*
* @since 2.5
*/
protected function getUrl($id, $extension, $view)
{
return 'index.php?option=' . $extension . '&view=' . $view . '&id=' . $id;
}
/**
* Method to get the page title of any menu item that is linked to the
* content item, if it exists and is set.
*
* @param string $url The URL of the item.
*
* @return mixed The title on success, null if not found.
*
* @since 2.5
* @throws \Exception on database error.
*/
protected function getItemMenuTitle($url)
{
$return = null;
// Set variables
$user = Factory::getUser();
$groups = implode(',', $user->getAuthorisedViewLevels());
// Build a query to get the menu params.
$query = $this->db->getQuery(true)
->select($this->db->quoteName('params'))
->from($this->db->quoteName('#__menu'))
->where($this->db->quoteName('link') . ' = ' . $this->db->quote($url))
->where($this->db->quoteName('published') . ' = 1')
->where($this->db->quoteName('access') . ' IN (' . $groups . ')');
// Get the menu params from the database.
$this->db->setQuery($query);
$params = $this->db->loadResult();
// Check the results.
if (empty($params)) {
return $return;
}
// Instantiate the params.
$params = json_decode($params);
// Get the page title if it is set.
if (isset($params->page_title) && $params->page_title) {
$return = $params->page_title;
}
return $return;
}
/**
* Method to update index data on access level changes
*
* @param Table $row A Table object
*
* @return void
*
* @since 2.5
*/
protected function itemAccessChange($row)
{
$query = clone $this->getStateQuery();
$query->where('a.id = ' . (int) $row->id);
// Get the access level.
$this->db->setQuery($query);
$item = $this->db->loadObject();
// Set the access level.
$temp = max($row->access, $item->cat_access);
// Update the item.
$this->change((int) $row->id, 'access', $temp);
}
/**
* Method to update index data on published state changes
*
* @param array $pks A list of primary key ids of the content that has changed state.
* @param integer $value The value of the state that the content has been changed to.
*
* @return void
*
* @since 2.5
*/
protected function itemStateChange($pks, $value)
{
/*
* The item's published state is tied to the category
* published state so we need to look up all published states
* before we change anything.
*/
foreach ($pks as $pk) {
$query = clone $this->getStateQuery();
$query->where('a.id = ' . (int) $pk);
// Get the published states.
$this->db->setQuery($query);
$item = $this->db->loadObject();
// Translate the state.
$temp = $this->translateState($value, $item->cat_state);
// Update the item.
$this->change($pk, 'state', $temp);
}
}
/**
* Method to update index data when a plugin is disabled
*
* @param array $pks A list of primary key ids of the content that has changed state.
*
* @return void
*
* @since 2.5
*/
protected function pluginDisable($pks)
{
// Since multiple plugins may be disabled at a time, we need to check first
// that we're handling the appropriate one for the context
foreach ($pks as $pk) {
if ($this->getPluginType($pk) == strtolower($this->context)) {
// Get all of the items to unindex them
$query = clone $this->getStateQuery();
$this->db->setQuery($query);
$items = $this->db->loadColumn();
// Remove each item
foreach ($items as $item) {
$this->remove($item);
}
// Stop processing plugins
break;
}
}
}
/**
* Method to translate the native content states into states that the
* indexer can use.
*
* @param integer $item The item state.
* @param integer $category The category state. [optional]
*
* @return integer The translated indexer state.
*
* @since 2.5
*/
protected function translateState($item, $category = null)
{
// If category is present, factor in its states as well
if ($category !== null && $category == 0) {
$item = 0;
}
// Translate the state
switch ($item) {
case 1:
// Published items should always show up in search results
return 1;
case 2:
// Archived items should only show up when option is enabled
if ($this->params->get('search_archived', 1) == 0) {
return 0;
}
return 1;
default:
// All other states should return an unpublished state
return 0;
}
}
}

View File

@ -0,0 +1,969 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Indexer;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseInterface;
use Joomla\Database\QueryInterface;
use Joomla\Event\DispatcherInterface;
use Joomla\Utilities\ArrayHelper;
/**
* Prototype debug adapter class for the Finder indexer package.
* THIS CLASS IS ONLY TO BE USED FOR DEBUGGING PURPOSES! DON'T
* USE IT FOR PRODUCTIVE USE!
*
* @since 5.0.0
* @internal
*/
abstract class DebugAdapter extends CMSPlugin
{
/**
* The context is somewhat arbitrary but it must be unique or there will be
* conflicts when managing plugin/indexer state. A good best practice is to
* use the plugin name suffix as the context. For example, if the plugin is
* named 'plgFinderContent', the context could be 'Content'.
*
* @var string
* @since 5.0.0
*/
protected $context;
/**
* The extension name.
*
* @var string
* @since 5.0.0
*/
protected $extension;
/**
* The sublayout to use when rendering the results.
*
* @var string
* @since 5.0.0
*/
protected $layout;
/**
* The mime type of the content the adapter indexes.
*
* @var string
* @since 5.0.0
*/
protected $mime;
/**
* The access level of an item before save.
*
* @var integer
* @since 5.0.0
*/
protected $old_access;
/**
* The access level of a category before save.
*
* @var integer
* @since 5.0.0
*/
protected $old_cataccess;
/**
* The type of content the adapter indexes.
*
* @var string
* @since 5.0.0
*/
protected $type_title;
/**
* The type id of the content.
*
* @var integer
* @since 5.0.0
*/
protected $type_id;
/**
* The database object.
*
* @var DatabaseInterface
* @since 5.0.0
*/
protected $db;
/**
* The table name.
*
* @var string
* @since 5.0.0
*/
protected $table;
/**
* The indexer object.
*
* @var Indexer
* @since 5.0.0
*/
protected $indexer;
/**
* The field the published state is stored in.
*
* @var string
* @since 5.0.0
*/
protected $state_field = 'state';
/**
* Method to instantiate the indexer adapter.
*
* @param DispatcherInterface $dispatcher The object to observe.
* @param array $config An array that holds the plugin configuration.
*
* @since 5.0.0
*/
public function __construct(DispatcherInterface $dispatcher, array $config)
{
// Call the parent constructor.
parent::__construct($dispatcher, $config);
// Get the type id.
$this->type_id = $this->getTypeId();
// Add the content type if it doesn't exist and is set.
if (empty($this->type_id) && !empty($this->type_title)) {
$this->type_id = Helper::addContentType($this->type_title, $this->mime);
}
// Check for a layout override.
if ($this->params->get('layout')) {
$this->layout = $this->params->get('layout');
}
// Get the indexer object
$this->indexer = new Indexer($this->db);
}
/**
* Returns an array of events this subscriber will listen to.
*
* @return array
*
* @since 5.0.0
*/
public static function getSubscribedEvents(): array
{
return [
'onBeforeIndex' => 'onBeforeIndex',
'onBuildIndex' => 'onBuildIndex',
'onFinderGarbageCollection' => 'onFinderGarbageCollection',
'onStartIndex' => 'onStartIndex',
];
}
/**
* Method to get the adapter state and push it into the indexer.
*
* @return void
*
* @since 5.0.0
* @throws \Exception on error.
*/
public function onStartIndex()
{
// Get the indexer state.
$iState = Indexer::getState();
// Get the number of content items.
$total = (int) $this->getContentCount();
// Add the content count to the total number of items.
$iState->totalItems += $total;
// Populate the indexer state information for the adapter.
$iState->pluginState[$this->context]['total'] = $total;
$iState->pluginState[$this->context]['offset'] = 0;
// Set the indexer state.
Indexer::setState($iState);
}
/**
* Method to prepare for the indexer to be run. This method will often
* be used to include dependencies and things of that nature.
*
* @return boolean True on success.
*
* @since 5.0.0
* @throws \Exception on error.
*/
public function onBeforeIndex()
{
// Get the indexer and adapter state.
$iState = Indexer::getState();
$aState = $iState->pluginState[$this->context];
// Check the progress of the indexer and the adapter.
if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total']) {
return true;
}
// Run the setup method.
return $this->setup();
}
/**
* Method to index a batch of content items. This method can be called by
* the indexer many times throughout the indexing process depending on how
* much content is available for indexing. It is important to track the
* progress correctly so we can display it to the user.
*
* @return boolean True on success.
*
* @since 5.0.0
* @throws \Exception on error.
*/
public function onBuildIndex()
{
// Get the indexer and adapter state.
$iState = Indexer::getState();
$aState = $iState->pluginState[$this->context];
// Check the progress of the indexer and the adapter.
if ($iState->batchOffset == $iState->batchSize || $aState['offset'] == $aState['total']) {
return true;
}
// Get the batch offset and size.
$offset = (int) $aState['offset'];
$limit = (int) ($iState->batchSize - $iState->batchOffset);
// Get the content items to index.
$items = $this->getItems($offset, $limit);
// Iterate through the items and index them.
foreach ($items as $item) {
// Index the item.
$this->index($item);
// Adjust the offsets.
$offset++;
$iState->batchOffset++;
$iState->totalItems--;
}
// Update the indexer state.
$aState['offset'] = $offset;
$iState->pluginState[$this->context] = $aState;
Indexer::setState($iState);
return true;
}
/**
* Method to remove outdated index entries
*
* @return integer
*
* @since 5.0.0
*/
public function onFinderGarbageCollection()
{
$db = $this->db;
$type_id = $this->getTypeId();
$query = $db->getQuery(true);
$subquery = $db->getQuery(true);
$subquery->select('CONCAT(' . $db->quote($this->getUrl('', $this->extension, $this->layout)) . ', id)')
->from($db->quoteName($this->table));
$query->select($db->quoteName('l.link_id'))
->from($db->quoteName('#__finder_links', 'l'))
->where($db->quoteName('l.type_id') . ' = ' . $type_id)
->where($db->quoteName('l.url') . ' LIKE ' . $db->quote($this->getUrl('%', $this->extension, $this->layout)))
->where($db->quoteName('l.url') . ' NOT IN (' . $subquery . ')');
$db->setQuery($query);
$items = $db->loadColumn();
foreach ($items as $item) {
$this->indexer->remove($item);
}
return \count($items);
}
/**
* Method to change the value of a content item's property in the links
* table. This is used to synchronize published and access states that
* are changed when not editing an item directly.
*
* @param string $id The ID of the item to change.
* @param string $property The property that is being changed.
* @param integer $value The new value of that property.
*
* @return boolean True on success.
*
* @since 5.0.0
* @throws \Exception on database error.
*/
protected function change($id, $property, $value)
{
// Check for a property we know how to handle.
if ($property !== 'state' && $property !== 'access') {
return true;
}
// Get the URL for the content id.
$item = $this->db->quote($this->getUrl($id, $this->extension, $this->layout));
// Update the content items.
$query = $this->db->getQuery(true)
->update($this->db->quoteName('#__finder_links'))
->set($this->db->quoteName($property) . ' = ' . (int) $value)
->where($this->db->quoteName('url') . ' = ' . $item);
$this->db->setQuery($query);
$this->db->execute();
return true;
}
/**
* Method to index an item.
*
* @param Result $item The item to index as a Result object.
*
* @return boolean True on success.
*
* @since 5.0.0
* @throws \Exception on database error.
*/
abstract protected function index(Result $item);
/**
* Method to reindex an item.
*
* @param integer $id The ID of the item to reindex.
*
* @return void
*
* @since 5.0.0
* @throws \Exception on database error.
*/
protected function reindex($id)
{
// Run the setup method.
$this->setup();
// Remove the old item.
$this->remove($id, false);
// Get the item.
$item = $this->getItem($id);
// Index the item.
$this->index($item);
Taxonomy::removeOrphanNodes();
}
/**
* Method to remove an item from the index.
*
* @param string $id The ID of the item to remove.
* @param bool $removeTaxonomies Remove empty taxonomies
*
* @return boolean True on success.
*
* @since 5.0.0
* @throws \Exception on database error.
*/
protected function remove($id, $removeTaxonomies = true)
{
// Get the item's URL
$url = $this->db->quote($this->getUrl($id, $this->extension, $this->layout));
// Get the link ids for the content items.
$query = $this->db->getQuery(true)
->select($this->db->quoteName('link_id'))
->from($this->db->quoteName('#__finder_links'))
->where($this->db->quoteName('url') . ' = ' . $url);
$this->db->setQuery($query);
$items = $this->db->loadColumn();
// Check the items.
if (empty($items)) {
$this->getApplication()->triggerEvent('onFinderIndexAfterDelete', [$id]);
return true;
}
// Remove the items.
foreach ($items as $item) {
$this->indexer->remove($item, $removeTaxonomies);
}
return true;
}
/**
* Method to setup the adapter before indexing.
*
* @return boolean True on success, false on failure.
*
* @since 5.0.0
* @throws \Exception on database error.
*/
abstract protected function setup();
/**
* Method to update index data on category access level changes
*
* @param Table $row A Table object
*
* @return void
*
* @since 5.0.0
*/
protected function categoryAccessChange($row)
{
$query = clone $this->getStateQuery();
$query->where('c.id = ' . (int) $row->id);
// Get the access level.
$this->db->setQuery($query);
$items = $this->db->loadObjectList();
// Adjust the access level for each item within the category.
foreach ($items as $item) {
// Set the access level.
$temp = max($item->access, $row->access);
// Update the item.
$this->change((int) $item->id, 'access', $temp);
}
}
/**
* Method to update index data on category access level changes
*
* @param array $pks A list of primary key ids of the content that has changed state.
* @param integer $value The value of the state that the content has been changed to.
*
* @return void
*
* @since 5.0.0
*/
protected function categoryStateChange($pks, $value)
{
/*
* The item's published state is tied to the category
* published state so we need to look up all published states
* before we change anything.
*/
foreach ($pks as $pk) {
$query = clone $this->getStateQuery();
$query->where('c.id = ' . (int) $pk);
// Get the published states.
$this->db->setQuery($query);
$items = $this->db->loadObjectList();
// Adjust the state for each item within the category.
foreach ($items as $item) {
// Translate the state.
$temp = $this->translateState($item->state, $value);
// Update the item.
$this->change($item->id, 'state', $temp);
}
}
}
/**
* Method to check the existing access level for categories
*
* @param Table $row A Table object
*
* @return void
*
* @since 5.0.0
*/
protected function checkCategoryAccess($row)
{
$query = $this->db->getQuery(true)
->select($this->db->quoteName('access'))
->from($this->db->quoteName('#__categories'))
->where($this->db->quoteName('id') . ' = ' . (int) $row->id);
$this->db->setQuery($query);
// Store the access level to determine if it changes
$this->old_cataccess = $this->db->loadResult();
}
/**
* Method to check the existing access level for items
*
* @param Table $row A Table object
*
* @return void
*
* @since 5.0.0
*/
protected function checkItemAccess($row)
{
$query = $this->db->getQuery(true)
->select($this->db->quoteName('access'))
->from($this->db->quoteName($this->table))
->where($this->db->quoteName('id') . ' = ' . (int) $row->id);
$this->db->setQuery($query);
// Store the access level to determine if it changes
$this->old_access = $this->db->loadResult();
}
/**
* Method to get the number of content items available to index.
*
* @return integer The number of content items available to index.
*
* @since 5.0.0
* @throws \Exception on database error.
*/
protected function getContentCount()
{
$return = 0;
// Get the list query.
$query = $this->getListQuery();
// Check if the query is valid.
if (empty($query)) {
return $return;
}
// Tweak the SQL query to make the total lookup faster.
if ($query instanceof QueryInterface) {
$query = clone $query;
$query->clear('select')
->select('COUNT(*)')
->clear('order');
}
// Get the total number of content items to index.
$this->db->setQuery($query);
return (int) $this->db->loadResult();
}
/**
* Method to get a content item to index.
*
* @param integer $id The id of the content item.
*
* @return Result A Result object.
*
* @since 5.0.0
* @throws \Exception on database error.
*/
protected function getItem($id)
{
// Get the list query and add the extra WHERE clause.
$query = $this->getListQuery();
$query->where('a.id = ' . (int) $id);
// Get the item to index.
$this->db->setQuery($query);
$item = $this->db->loadAssoc();
// Convert the item to a result object.
$item = ArrayHelper::toObject((array) $item, Result::class);
// Set the item type.
$item->type_id = $this->type_id;
// Set the item layout.
$item->layout = $this->layout;
return $item;
}
/**
* Method to get a list of content items to index.
*
* @param integer $offset The list offset.
* @param integer $limit The list limit.
* @param QueryInterface $query A QueryInterface object. [optional]
*
* @return Result[] An array of Result objects.
*
* @since 5.0.0
* @throws \Exception on database error.
*/
protected function getItems($offset, $limit, $query = null)
{
// Get the content items to index.
$this->db->setQuery($this->getListQuery($query)->setLimit($limit, $offset));
$items = $this->db->loadAssocList();
foreach ($items as &$item) {
$item = ArrayHelper::toObject($item, Result::class);
// Set the item type.
$item->type_id = $this->type_id;
// Set the mime type.
$item->mime = $this->mime;
// Set the item layout.
$item->layout = $this->layout;
}
return $items;
}
/**
* Method to get the SQL query used to retrieve the list of content items.
*
* @param mixed $query A QueryInterface object. [optional]
*
* @return QueryInterface A database object.
*
* @since 5.0.0
*/
protected function getListQuery($query = null)
{
// Check if we can use the supplied SQL query.
return $query instanceof QueryInterface ? $query : $this->db->getQuery(true);
}
/**
* Method to get the plugin type
*
* @param integer $id The plugin ID
*
* @return string The plugin type
*
* @since 5.0.0
*/
protected function getPluginType($id)
{
// Prepare the query
$query = $this->db->getQuery(true)
->select($this->db->quoteName('element'))
->from($this->db->quoteName('#__extensions'))
->where($this->db->quoteName('extension_id') . ' = ' . (int) $id);
$this->db->setQuery($query);
return $this->db->loadResult();
}
/**
* Method to get a SQL query to load the published and access states for
* an article and category.
*
* @return QueryInterface A database object.
*
* @since 5.0.0
*/
protected function getStateQuery()
{
$query = $this->db->getQuery(true);
// Item ID
$query->select('a.id');
// Item and category published state
$query->select('a.' . $this->state_field . ' AS state, c.published AS cat_state');
// Item and category access levels
$query->select('a.access, c.access AS cat_access')
->from($this->table . ' AS a')
->join('LEFT', '#__categories AS c ON c.id = a.catid');
return $query;
}
/**
* Method to get the query clause for getting items to update by time.
*
* @param string $time The modified timestamp.
*
* @return QueryInterface A database object.
*
* @since 5.0.0
*/
protected function getUpdateQueryByTime($time)
{
// Build an SQL query based on the modified time.
$query = $this->db->getQuery(true)
->where('a.modified >= ' . $this->db->quote($time));
return $query;
}
/**
* Method to get the query clause for getting items to update by id.
*
* @param array $ids The ids to load.
*
* @return QueryInterface A database object.
*
* @since 5.0.0
*/
protected function getUpdateQueryByIds($ids)
{
// Build an SQL query based on the item ids.
$query = $this->db->getQuery(true)
->where('a.id IN(' . implode(',', $ids) . ')');
return $query;
}
/**
* Method to get the type id for the adapter content.
*
* @return integer The numeric type id for the content.
*
* @since 5.0.0
* @throws \Exception on database error.
*/
protected function getTypeId()
{
// Get the type id from the database.
$query = $this->db->getQuery(true)
->select($this->db->quoteName('id'))
->from($this->db->quoteName('#__finder_types'))
->where($this->db->quoteName('title') . ' = ' . $this->db->quote($this->type_title));
$this->db->setQuery($query);
return (int) $this->db->loadResult();
}
/**
* Method to get the URL for the item. The URL is how we look up the link
* in the Finder index.
*
* @param integer $id The id of the item.
* @param string $extension The extension the category is in.
* @param string $view The view for the URL.
*
* @return string The URL of the item.
*
* @since 5.0.0
*/
protected function getUrl($id, $extension, $view)
{
return 'index.php?option=' . $extension . '&view=' . $view . '&id=' . $id;
}
/**
* Method to get the page title of any menu item that is linked to the
* content item, if it exists and is set.
*
* @param string $url The URL of the item.
*
* @return mixed The title on success, null if not found.
*
* @since 5.0.0
* @throws \Exception on database error.
*/
protected function getItemMenuTitle($url)
{
$return = null;
// Set variables
$user = $this->getApplication()->getIdentity();
$groups = implode(',', $user->getAuthorisedViewLevels());
// Build a query to get the menu params.
$query = $this->db->getQuery(true)
->select($this->db->quoteName('params'))
->from($this->db->quoteName('#__menu'))
->where($this->db->quoteName('link') . ' = ' . $this->db->quote($url))
->where($this->db->quoteName('published') . ' = 1')
->where($this->db->quoteName('access') . ' IN (' . $groups . ')');
// Get the menu params from the database.
$this->db->setQuery($query);
$params = $this->db->loadResult();
// Check the results.
if (empty($params)) {
return $return;
}
// Instantiate the params.
$params = json_decode($params);
// Get the page title if it is set.
if (isset($params->page_title) && $params->page_title) {
$return = $params->page_title;
}
return $return;
}
/**
* Method to update index data on access level changes
*
* @param Table $row A Table object
*
* @return void
*
* @since 5.0.0
*/
protected function itemAccessChange($row)
{
$query = clone $this->getStateQuery();
$query->where('a.id = ' . (int) $row->id);
// Get the access level.
$this->db->setQuery($query);
$item = $this->db->loadObject();
// Set the access level.
$temp = max($row->access, $item->cat_access);
// Update the item.
$this->change((int) $row->id, 'access', $temp);
}
/**
* Method to update index data on published state changes
*
* @param array $pks A list of primary key ids of the content that has changed state.
* @param integer $value The value of the state that the content has been changed to.
*
* @return void
*
* @since 5.0.0
*/
protected function itemStateChange($pks, $value)
{
/*
* The item's published state is tied to the category
* published state so we need to look up all published states
* before we change anything.
*/
foreach ($pks as $pk) {
$query = clone $this->getStateQuery();
$query->where('a.id = ' . (int) $pk);
// Get the published states.
$this->db->setQuery($query);
$item = $this->db->loadObject();
// Translate the state.
$temp = $this->translateState($value, $item->cat_state);
// Update the item.
$this->change($pk, 'state', $temp);
}
}
/**
* Method to update index data when a plugin is disabled
*
* @param array $pks A list of primary key ids of the content that has changed state.
*
* @return void
*
* @since 5.0.0
*/
protected function pluginDisable($pks)
{
// Since multiple plugins may be disabled at a time, we need to check first
// that we're handling the appropriate one for the context
foreach ($pks as $pk) {
if ($this->getPluginType($pk) == strtolower($this->context)) {
// Get all of the items to unindex them
$query = clone $this->getStateQuery();
$this->db->setQuery($query);
$items = $this->db->loadColumn();
// Remove each item
foreach ($items as $item) {
$this->remove($item);
}
}
}
}
/**
* Method to translate the native content states into states that the
* indexer can use.
*
* @param integer $item The item state.
* @param integer $category The category state. [optional]
*
* @return integer The translated indexer state.
*
* @since 5.0.0
*/
protected function translateState($item, $category = null)
{
// If category is present, factor in its states as well
if ($category !== null && $category == 0) {
$item = 0;
}
// Translate the state
switch ($item) {
case 1:
case 2:
// Published and archived items only should return a published state
return 1;
default:
// All other states should return an unpublished state
return 0;
}
}
/**
* Debug method to set the used indexer
*
* @param Indexer $indexer Indexer object
*
* @return void
*
* @since 5.0.0
*/
public function setIndexer(Indexer $indexer)
{
$this->indexer = $indexer;
}
/**
* Debug method to run a specific plugin to prepare a result object.
* The object is then stored in the indexer object to debug further.
*
* @param mixed $id ID to index
*
* @return void
*
* @since 5.0.0
*/
public function debug($id)
{
// Run the setup method.
$this->setup();
// Get the item.
$item = $this->getItem($id);
// Index the item.
$this->index($item);
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Indexer;
/**
* Debugging indexer class for the Finder indexer package.
*
* @since 5.0.0
* @internal
*/
class DebugIndexer extends Indexer
{
/**
* The result object from the last call to self::index()
*
* @var Result
*
* @since 5.0.0
*/
public static $item;
/**
* Stub for index() in indexer class
*
* @param Result $item Result object to index
* @param string $format Format to index
*
* @return void
*
* @since 5.0.0
*/
public function index($item, $format = 'html')
{
self::$item = $item;
}
}

View File

@ -0,0 +1,492 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Indexer;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Event\Finder\PrepareContentEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Table;
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Helper class for the Finder indexer package.
*
* @since 2.5
*/
class Helper
{
public const CUSTOMFIELDS_DONT_INDEX = 0;
public const CUSTOMFIELDS_ADD_TO_INDEX = 1;
public const CUSTOMFIELDS_ADD_TO_TAXONOMY = 2;
public const CUSTOMFIELDS_ADD_TO_BOTH = 3;
/**
* Method to parse input into plain text.
*
* @param string $input The raw input.
* @param string $format The format of the input. [optional]
*
* @return string The parsed input.
*
* @since 2.5
* @throws \Exception on invalid parser.
*/
public static function parse($input, $format = 'html')
{
// Get a parser for the specified format and parse the input.
return Parser::getInstance($format)->parse($input);
}
/**
* Method to tokenize a text string.
*
* @param string $input The input to tokenize.
* @param string $lang The language of the input.
* @param boolean $phrase Flag to indicate whether input could be a phrase. [optional]
*
* @return Token[] An array of Token objects.
*
* @since 2.5
*/
public static function tokenize($input, $lang, $phrase = false)
{
static $cache = [], $tuplecount;
static $multilingual;
static $defaultLanguage;
if (!$tuplecount) {
$params = ComponentHelper::getParams('com_finder');
$tuplecount = $params->get('tuplecount', 1);
}
if (\is_null($multilingual)) {
$multilingual = Multilanguage::isEnabled();
$config = ComponentHelper::getParams('com_finder');
if ($config->get('language_default', '') == '') {
$defaultLang = '*';
} elseif ($config->get('language_default', '') == '-1') {
$defaultLang = self::getDefaultLanguage();
} else {
$defaultLang = $config->get('language_default');
}
/*
* The default language always has the language code '*'.
* In order to not overwrite the language code of the language
* object that we are using, we are cloning it here.
*/
$obj = Language::getInstance($defaultLang);
$defaultLanguage = clone $obj;
$defaultLanguage->language = '*';
}
if (!$multilingual || $lang == '*') {
$language = $defaultLanguage;
} else {
$language = Language::getInstance($lang);
}
if (!isset($cache[$lang])) {
$cache[$lang] = [];
}
$tokens = [];
$terms = $language->tokenise($input);
// @todo: array_filter removes any number 0's from the terms. Not sure this is entirely intended
$terms = array_filter($terms);
$terms = array_values($terms);
/*
* If we have to handle the input as a phrase, that means we don't
* tokenize the individual terms and we do not create the two and three
* term combinations. The phrase must contain more than one word!
*/
if ($phrase === true && \count($terms) > 1) {
// Create tokens from the phrase.
$tokens[] = new Token($terms, $language->language, $language->spacer);
} else {
// Create tokens from the terms.
for ($i = 0, $n = \count($terms); $i < $n; $i++) {
if (isset($cache[$lang][$terms[$i]])) {
$tokens[] = $cache[$lang][$terms[$i]];
} else {
$token = new Token($terms[$i], $language->language);
$tokens[] = $token;
$cache[$lang][$terms[$i]] = $token;
}
}
// Create multi-word phrase tokens from the individual words.
if ($tuplecount > 1) {
for ($i = 0, $n = \count($tokens); $i < $n; $i++) {
$temp = [$tokens[$i]->term];
// Create tokens for 2 to $tuplecount length phrases
for ($j = 1; $j < $tuplecount; $j++) {
if ($i + $j >= $n || !isset($tokens[$i + $j])) {
break;
}
$temp[] = $tokens[$i + $j]->term;
$key = implode('::', $temp);
if (isset($cache[$lang][$key])) {
$tokens[] = $cache[$lang][$key];
} else {
$token = new Token($temp, $language->language, $language->spacer);
$token->derived = true;
$tokens[] = $token;
$cache[$lang][$key] = $token;
}
}
}
}
}
// Prevent the cache to fill up the memory
while (\count($cache[$lang]) > 1024) {
/**
* We want to cache the most common words/tokens. At the same time
* we don't want to cache too much. The most common words will also
* be early in the text, so we are dropping all terms/tokens which
* have been cached later.
*/
array_pop($cache[$lang]);
}
return $tokens;
}
/**
* Method to get the base word of a token.
*
* @param string $token The token to stem.
* @param string $lang The language of the token.
*
* @return string The root token.
*
* @since 2.5
*/
public static function stem($token, $lang)
{
static $multilingual;
static $defaultStemmer;
if (\is_null($multilingual)) {
$multilingual = Multilanguage::isEnabled();
$config = ComponentHelper::getParams('com_finder');
if ($config->get('language_default', '') == '') {
$defaultStemmer = Language::getInstance('*');
} elseif ($config->get('language_default', '') == '-1') {
$defaultStemmer = Language::getInstance(self::getDefaultLanguage());
} else {
$defaultStemmer = Language::getInstance($config->get('language_default'));
}
}
if (!$multilingual || $lang == '*') {
$language = $defaultStemmer;
} else {
$language = Language::getInstance($lang);
}
return $language->stem($token);
}
/**
* Method to add a content type to the database.
*
* @param string $title The type of content. For example: PDF
* @param string $mime The mime type of the content. For example: PDF [optional]
*
* @return integer The id of the content type.
*
* @since 2.5
* @throws \Exception on database error.
*/
public static function addContentType($title, $mime = null)
{
static $types;
$db = Factory::getDbo();
$query = $db->getQuery(true);
// Check if the types are loaded.
if (empty($types)) {
// Build the query to get the types.
$query->select('*')
->from($db->quoteName('#__finder_types'));
// Get the types.
$db->setQuery($query);
$types = $db->loadObjectList('title');
}
// Check if the type already exists.
if (isset($types[$title])) {
return (int) $types[$title]->id;
}
// Add the type.
$query->clear()
->insert($db->quoteName('#__finder_types'))
->columns([$db->quoteName('title'), $db->quoteName('mime')])
->values($db->quote($title) . ', ' . $db->quote($mime ?? ''));
$db->setQuery($query);
$db->execute();
// Cache the result
$type = new \stdClass();
$type->title = $title;
$type->mime = $mime ?? '';
$type->id = (int) $db->insertid();
$types[$title] = $type;
// Return the new id.
return $type->id;
}
/**
* Method to check if a token is common in a language.
*
* @param string $token The token to test.
* @param string $lang The language to reference.
*
* @return boolean True if common, false otherwise.
*
* @since 2.5
*/
public static function isCommon($token, $lang)
{
static $data = [], $default, $multilingual;
if (\is_null($multilingual)) {
$multilingual = Multilanguage::isEnabled();
$config = ComponentHelper::getParams('com_finder');
if ($config->get('language_default', '') == '') {
$default = '*';
} elseif ($config->get('language_default', '') == '-1') {
$default = self::getPrimaryLanguage(self::getDefaultLanguage());
} else {
$default = self::getPrimaryLanguage($config->get('language_default'));
}
}
if (!$multilingual || $lang == '*') {
$lang = $default;
}
// Load the common tokens for the language if necessary.
if (!isset($data[$lang])) {
$data[$lang] = self::getCommonWords($lang);
}
// Check if the token is in the common array.
return \in_array($token, $data[$lang], true);
}
/**
* Method to get an array of common terms for a language.
*
* @param string $lang The language to use.
*
* @return array Array of common terms.
*
* @since 2.5
* @throws \Exception on database error.
*/
public static function getCommonWords($lang)
{
$db = Factory::getDbo();
// Create the query to load all the common terms for the language.
$query = $db->getQuery(true)
->select($db->quoteName('term'))
->from($db->quoteName('#__finder_terms_common'))
->where($db->quoteName('language') . ' = ' . $db->quote($lang));
// Load all of the common terms for the language.
$db->setQuery($query);
return $db->loadColumn();
}
/**
* Method to get the default language for the site.
*
* @return string The default language string.
*
* @since 2.5
*/
public static function getDefaultLanguage()
{
static $lang;
// We need to go to com_languages to get the site default language, it's the best we can guess.
if (empty($lang)) {
$lang = ComponentHelper::getParams('com_languages')->get('site', 'en-GB');
}
return $lang;
}
/**
* Method to parse a language/locale key and return a simple language string.
*
* @param string $lang The language/locale key. For example: en-GB
*
* @return string The simple language string. For example: en
*
* @since 2.5
*/
public static function getPrimaryLanguage($lang)
{
static $data = [];
// Only parse the identifier if necessary.
if (!isset($data[$lang])) {
if (\is_callable(['Locale', 'getPrimaryLanguage'])) {
// Get the language key using the Locale package.
$data[$lang] = \Locale::getPrimaryLanguage($lang);
} else {
// Get the language key using string position.
$data[$lang] = StringHelper::substr($lang, 0, StringHelper::strpos($lang, '-'));
}
}
return $data[$lang];
}
/**
* Method to get extra data for a content before being indexed. This is how
* we add Comments, Tags, Labels, etc. that should be available to Finder.
*
* @param Result $item The item to index as a Result object.
*
* @return boolean True on success, false on failure.
*
* @since 2.5
* @throws \Exception on database error.
*/
public static function getContentExtras(Result $item)
{
$dispatcher = Factory::getApplication()->getDispatcher();
// Load the finder plugin group.
PluginHelper::importPlugin('finder', null, true, $dispatcher);
$dispatcher->dispatch('onPrepareFinderContent', new PrepareContentEvent('onPrepareFinderContent', [
'subject' => $item,
]));
return true;
}
/**
* Add custom fields for the item to the Result object
*
* @param Result $item Result object to add the custom fields to
* @param string $context Context of the item in the custom fields
*
* @return void
*
* @since 5.0.0
*/
public static function addCustomFields(Result $item, $context)
{
if (!ComponentHelper::getParams(strstr($context, '.', true))->get('custom_fields_enable', 1)) {
return;
}
$obj = new \stdClass();
$obj->id = $item->id;
$fields = FieldsHelper::getFields($context, $obj, true);
foreach ($fields as $field) {
$searchindex = $field->params->get('searchindex', 0);
// We want to add this field to the search index
if ($searchindex == self::CUSTOMFIELDS_ADD_TO_INDEX || $searchindex == self::CUSTOMFIELDS_ADD_TO_BOTH) {
$name = 'jsfield_' . $field->name;
$item->$name = $field->value;
$item->addInstruction(Indexer::META_CONTEXT, $name);
}
// We want to add this field as a taxonomy
if (
($searchindex == self::CUSTOMFIELDS_ADD_TO_TAXONOMY || $searchindex == self::CUSTOMFIELDS_ADD_TO_BOTH)
&& $field->value
) {
$item->addTaxonomy($field->title, $field->value, $field->state, $field->access, $field->language);
}
}
}
/**
* Method to process content text using the onContentPrepare event trigger.
*
* @param string $text The content to process.
* @param Registry $params The parameters object. [optional]
* @param ?Result $item The item which get prepared. [optional]
*
* @return string The processed content.
*
* @since 2.5
*/
public static function prepareContent($text, $params = null, ?Result $item = null)
{
static $loaded;
// Load the content plugins if necessary.
if (empty($loaded)) {
PluginHelper::importPlugin('content');
$loaded = true;
}
// Instantiate the parameter object if necessary.
if (!($params instanceof Registry)) {
$registry = new Registry($params);
$params = $registry;
}
// Create a mock content object.
$content = Table::getInstance('Content');
$content->text = $text;
if ($item) {
$content->bind((array) $item);
$content->bind($item->getElements());
}
if ($item && !empty($item->context)) {
$content->context = $item->context;
}
// Fire the onContentPrepare event.
Factory::getApplication()->triggerEvent('onContentPrepare', ['com_finder.indexer', &$content, &$params, 0]);
return $content->text;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,182 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Indexer;
use Joomla\String\StringHelper;
use Wamania\Snowball\NotFoundException;
use Wamania\Snowball\Stemmer\Stemmer;
use Wamania\Snowball\StemmerFactory;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Language support class for the Finder indexer package.
*
* @since 4.0.0
*/
class Language
{
/**
* Language support instances container.
*
* @var Language[]
* @since 4.0.0
*/
protected static $instances = [];
/**
* Language locale of the class
*
* @var string
* @since 4.0.0
*/
public $language;
/**
* Spacer to use between terms
*
* @var string
* @since 4.0.0
*/
public $spacer = ' ';
/**
* The stemmer object.
*
* @var Stemmer
* @since 4.0.0
*/
protected $stemmer = null;
/**
* Method to construct the language object.
*
* @since 4.0.0
*/
public function __construct($locale = null)
{
if ($locale !== null) {
$this->language = $locale;
}
// Use our generic language handler if no language is set
if ($this->language === null) {
$this->language = '*';
}
try {
foreach (StemmerFactory::LANGS as $classname => $isoCodes) {
if (\in_array($this->language, $isoCodes)) {
$this->stemmer = StemmerFactory::create($this->language);
break;
}
}
} catch (NotFoundException $e) {
// We don't have a stemmer for the language
}
}
/**
* Method to get a language support object.
*
* @param string $language The language of the support object.
*
* @return Language A Language instance.
*
* @since 4.0.0
*/
public static function getInstance($language)
{
if (isset(self::$instances[$language])) {
return self::$instances[$language];
}
$locale = '*';
if ($language !== '*') {
$locale = Helper::getPrimaryLanguage($language);
$class = '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Language\\' . ucfirst($locale);
if (class_exists($class)) {
self::$instances[$language] = new $class();
return self::$instances[$language];
}
}
self::$instances[$language] = new self($locale);
return self::$instances[$language];
}
/**
* Method to tokenise a text string.
*
* @param string $input The input to tokenise.
*
* @return array An array of term strings.
*
* @since 4.0.0
*/
public function tokenise($input)
{
$quotes = html_entity_decode('&#8216;&#8217;&#39;', ENT_QUOTES, 'UTF-8');
/*
* Parsing the string input into terms is a multi-step process.
*
* Regexes:
* 1. Remove everything except letters, numbers, quotes, apostrophe, plus, dash, period, and comma.
* 2. Remove plus, dash, and comma characters located before letter characters.
* 3. Remove plus, dash, period, and comma characters located after other characters.
* 4. Remove plus, period, and comma characters enclosed in alphabetical characters. Ungreedy.
* 5. Remove orphaned apostrophe, plus, dash, period, and comma characters.
* 6. Remove orphaned quote characters.
* 7. Replace the assorted single quotation marks with the ASCII standard single quotation.
* 8. Remove multiple space characters and replaces with a single space.
*/
$input = StringHelper::strtolower($input);
$input = preg_replace('#[^\pL\pM\pN\p{Pi}\p{Pf}\'+-.,]+#mui', ' ', $input);
$input = preg_replace('#(^|\s)[+-,]+([\pL\pM]+)#mui', ' $1', $input);
$input = preg_replace('#([\pL\pM\pN]+)[+-.,]+(\s|$)#mui', '$1 ', $input);
$input = preg_replace('#([\pL\pM]+)[+.,]+([\pL\pM]+)#muiU', '$1 $2', $input);
$input = preg_replace('#(^|\s)[\'+-.,]+(\s|$)#mui', ' ', $input);
$input = preg_replace('#(^|\s)[\p{Pi}\p{Pf}]+(\s|$)#mui', ' ', $input);
$input = preg_replace('#[' . $quotes . ']+#mui', '\'', $input);
$input = preg_replace('#\s+#mui', ' ', $input);
$input = trim($input);
// Explode the normalized string to get the terms.
$terms = explode(' ', $input);
return $terms;
}
/**
* Method to stem a token.
*
* @param string $token The token to stem.
*
* @return string The stemmed token.
*
* @since 4.0.0
*/
public function stem($token)
{
if ($this->stemmer !== null) {
return $this->stemmer->stem($token);
}
return $token;
}
}

View File

@ -0,0 +1,934 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*
* The Greek stemmer was adapted for Joomla! 4 by Nicholas K. Dionysopoulos <nicholas@akeebabackup.com>. This is
* derivative work, based on the Greek stemmer for Drupal, see
* https://github.com/magaras/greek_stemmer/blob/master/mod_stemmer.php
*/
namespace Joomla\Component\Finder\Administrator\Indexer\Language;
use Joomla\Component\Finder\Administrator\Indexer\Language;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Greek language support class for the Finder indexer package.
*
* @since 4.0.0
*/
class El extends Language
{
/**
* Language locale of the class
*
* @var string
* @since 4.0.0
*/
public $language = 'el';
/**
* Method to construct the language object.
*
* @since 4.0.0
*/
public function __construct($locale = null)
{
// Override parent constructor since we don't need to load an external stemmer
}
/**
* Method to tokenise a text string. It takes into account the odd punctuation commonly used in Greek text, mapping
* it to ASCII punctuation.
*
* Reference: http://www.teicrete.gr/users/kutrulis/Glosika/Stixi.htm
*
* @param string $input The input to tokenise.
*
* @return array An array of term strings.
*
* @since 4.0.0
*/
public function tokenise($input)
{
// Replace Greek calligraphic double quotes (various styles) to dumb double quotes
$input = str_replace(['“', '”', '„', '«' ,'»'], '"', $input);
// Replace Greek calligraphic single quotes (various styles) to dumb single quotes
$input = str_replace(['','',''], "'", $input);
// Replace the middle dot (ano teleia) with a comma, adequate for the purpose of stemming
$input = str_replace('·', ',', $input);
// Dot and dash (τελεία και παύλα), used to denote the end of a context at the end of a paragraph.
$input = str_replace('.', '.', $input);
// Ellipsis, two styles (separate dots or single glyph)
$input = str_replace(['...', '…'], '.', $input);
// Cross. Marks the death date of a person. Removed.
$input = str_replace('†', '', $input);
// Star. Reference, supposition word (in philology), birth date of a person.
$input = str_replace('*', '', $input);
// Paragraph. Indicates change of subject.
$input = str_replace('§', '.', $input);
// Plus/minus. Shows approximation. Not relevant for the stemmer, hence its conversion to a space.
$input = str_replace('±', ' ', $input);
return parent::tokenise($input);
}
/**
* Method to stem a token.
*
* @param string $token The token to stem.
*
* @return string The stemmed token.
*
* @since 4.0.0
*/
public function stem($token)
{
$token = $this->toUpperCase($token, $wCase);
// Stop-word removal
$stop_words = '/^(ΕΚΟ|ΑΒΑ|ΑΓΑ|ΑΓΗ|ΑΓΩ|ΑΔΗ|ΑΔΩ|ΑΕ|ΑΕΙ|ΑΘΩ|ΑΙ|ΑΙΚ|ΑΚΗ|ΑΚΟΜΑ|ΑΚΟΜΗ|ΑΚΡΙΒΩΣ|ΑΛΑ|ΑΛΗΘΕΙΑ|ΑΛΗΘΙΝΑ|ΑΛΛΑΧΟΥ|ΑΛΛΙΩΣ|ΑΛΛΙΩΤΙΚΑ|'
. 'ΑΛΛΟΙΩΣ|ΑΛΛΟΙΩΤΙΚΑ|ΑΛΛΟΤΕ|ΑΛΤ|ΑΛΩ|ΑΜΑ|ΑΜΕ|ΑΜΕΣΑ|ΑΜΕΣΩΣ|ΑΜΩ|ΑΝ|ΑΝΑ|ΑΝΑΜΕΣΑ|ΑΝΑΜΕΤΑΞΥ|ΑΝΕΥ|ΑΝΤΙ|ΑΝΤΙΠΕΡΑ|ΑΝΤΙΣ|ΑΝΩ|ΑΝΩΤΕΡΩ|ΑΞΑΦΝΑ|'
. 'ΑΠ|ΑΠΕΝΑΝΤΙ|ΑΠΟ|ΑΠΟΨΕ|ΑΠΩ|ΑΡΑ|ΑΡΑΓΕ|ΑΡΕ|ΑΡΚ|ΑΡΚΕΤΑ|ΑΡΛ|ΑΡΜ|ΑΡΤ|ΑΡΥ|ΑΡΩ|ΑΣ|ΑΣΑ|ΑΣΟ|ΑΤΑ|ΑΤΕ|ΑΤΗ|ΑΤΙ|ΑΤΜ|ΑΤΟ|ΑΥΡΙΟ|ΑΦΗ|ΑΦΟΤΟΥ|ΑΦΟΥ|'
. 'ΑΧ|ΑΧΕ|ΑΧΟ|ΑΨΑ|ΑΨΕ|ΑΨΗ|ΑΨΥ|ΑΩΕ|ΑΩΟ|ΒΑΝ|ΒΑΤ|ΒΑΧ|ΒΕΑ|ΒΕΒΑΙΟΤΑΤΑ|ΒΗΞ|ΒΙΑ|ΒΙΕ|ΒΙΗ|ΒΙΟ|ΒΟΗ|ΒΟΩ|ΒΡΕ|ΓΑ|ΓΑΒ|ΓΑΡ|ΓΕΝ|ΓΕΣ||ΓΗ|ΓΗΝ|ΓΙ|ΓΙΑ|'
. 'ΓΙΕ|ΓΙΝ|ΓΙΟ|ΓΚΙ|ΓΙΑΤΙ|ΓΚΥ|ΓΟΗ|ΓΟΟ|ΓΡΗΓΟΡΑ|ΓΡΙ|ΓΡΥ|ΓΥΗ|ΓΥΡΩ|ΔΑ|ΔΕ|ΔΕΗ|ΔΕΙ|ΔΕΝ|ΔΕΣ|ΔΗ|ΔΗΘΕΝ|ΔΗΛΑΔΗ|ΔΗΩ|ΔΙ|ΔΙΑ|ΔΙΑΡΚΩΣ|ΔΙΟΛΟΥ|ΔΙΣ|'
. 'ΔΙΧΩΣ|ΔΟΛ|ΔΟΝ|ΔΡΑ|ΔΡΥ|ΔΡΧ|ΔΥΕ|ΔΥΟ|ΔΩ|ΕΑΜ|ΕΑΝ|ΕΑΡ|ΕΘΗ|ΕΙ|ΕΙΔΕΜΗ|ΕΙΘΕ|ΕΙΜΑΙ|ΕΙΜΑΣΤΕ|ΕΙΝΑΙ|ΕΙΣ|ΕΙΣΑΙ|ΕΙΣΑΣΤΕ|ΕΙΣΤΕ|ΕΙΤΕ|ΕΙΧΑ|ΕΙΧΑΜΕ|'
. 'ΕΙΧΑΝ|ΕΙΧΑΤΕ|ΕΙΧΕ|ΕΙΧΕΣ|ΕΚ|ΕΚΕΙ|ΕΛΑ|ΕΛΙ|ΕΜΠ|ΕΝ|ΕΝΤΕΛΩΣ|ΕΝΤΟΣ|ΕΝΤΩΜΕΤΑΞΥ|ΕΝΩ|ΕΞ|ΕΞΑΦΝΑ|ΕΞΙ|ΕΞΙΣΟΥ|ΕΞΩ|ΕΟΚ|ΕΠΑΝΩ|ΕΠΕΙΔΗ|ΕΠΕΙΤΑ|ΕΠΗ|'
. 'ΕΠΙ|ΕΠΙΣΗΣ|ΕΠΟΜΕΝΩΣ|ΕΡΑ|ΕΣ|ΕΣΑΣ|ΕΣΕ|ΕΣΕΙΣ|ΕΣΕΝΑ|ΕΣΗ|ΕΣΤΩ|ΕΣΥ|ΕΣΩ|ΕΤΙ|ΕΤΣΙ|ΕΥ|ΕΥΑ|ΕΥΓΕ|ΕΥΘΥΣ|ΕΥΤΥΧΩΣ|ΕΦΕ|ΕΦΕΞΗΣ|ΕΦΤ|ΕΧΕ|ΕΧΕΙ|'
. 'ΕΧΕΙΣ|ΕΧΕΤΕ|ΕΧΘΕΣ|ΕΧΟΜΕ|ΕΧΟΥΜΕ|ΕΧΟΥΝ|ΕΧΤΕΣ|ΕΧΩ|ΕΩΣ|ΖΕΑ|ΖΕΗ|ΖΕΙ|ΖΕΝ|ΖΗΝ|ΖΩ|Η|ΗΔΗ|ΗΔΥ|ΗΘΗ|ΗΛΟ|ΗΜΙ|ΗΠΑ|ΗΣΑΣΤΕ|ΗΣΟΥΝ|ΗΤΑ|ΗΤΑΝ|ΗΤΑΝΕ|'
. 'ΗΤΟΙ|ΗΤΤΟΝ|ΗΩ|ΘΑ|ΘΥΕ|ΘΩΡ|Ι|ΙΑ|ΙΒΟ|ΙΔΗ|ΙΔΙΩΣ|ΙΕ|ΙΙ|ΙΙΙ|ΙΚΑ|ΙΛΟ|ΙΜΑ|ΙΝΑ|ΙΝΩ|ΙΞΕ|ΙΞΟ|ΙΟ|ΙΟΙ|ΙΣΑ|ΙΣΑΜΕ|ΙΣΕ|ΙΣΗ|ΙΣΙΑ|ΙΣΟ|ΙΣΩΣ|ΙΩΒ|ΙΩΝ|'
. 'ΙΩΣ|ΙΑΝ|ΚΑΘ|ΚΑΘΕ|ΚΑΘΕΤΙ|ΚΑΘΟΛΟΥ|ΚΑΘΩΣ|ΚΑΙ|ΚΑΝ|ΚΑΠΟΤΕ|ΚΑΠΟΥ|ΚΑΠΩΣ|ΚΑΤ|ΚΑΤΑ|ΚΑΤΙ|ΚΑΤΙΤΙ|ΚΑΤΟΠΙΝ|ΚΑΤΩ|ΚΑΩ|ΚΒΟ|ΚΕΑ|ΚΕΙ|ΚΕΝ|ΚΙ|ΚΙΜ|'
. 'ΚΙΟΛΑΣ|ΚΙΤ|ΚΙΧ|ΚΚΕ|ΚΛΙΣΕ|ΚΛΠ|ΚΟΚ|ΚΟΝΤΑ|ΚΟΧ|ΚΤΛ|ΚΥΡ|ΚΥΡΙΩΣ|ΚΩ|ΚΩΝ|ΛΑ|ΛΕΑ|ΛΕΝ|ΛΕΟ|ΛΙΑ|ΛΙΓΑΚΙ|ΛΙΓΟΥΛΑΚΙ|ΛΙΓΟ|ΛΙΓΩΤΕΡΟ|ΛΙΟ|ΛΙΡ|ΛΟΓΩ|'
. 'ΛΟΙΠΑ|ΛΟΙΠΟΝ|ΛΟΣ|ΛΣ|ΛΥΩ|ΜΑ|ΜΑΖΙ|ΜΑΚΑΡΙ|ΜΑΛΙΣΤΑ|ΜΑΛΛΟΝ|ΜΑΝ|ΜΑΞ|ΜΑΣ|ΜΑΤ|ΜΕ|ΜΕΘΑΥΡΙΟ|ΜΕΙ|ΜΕΙΟΝ|ΜΕΛ|ΜΕΛΕΙ|ΜΕΛΛΕΤΑΙ|ΜΕΜΙΑΣ|ΜΕΝ|ΜΕΣ|'
. 'ΜΕΣΑ|ΜΕΤ|ΜΕΤΑ|ΜΕΤΑΞΥ|ΜΕΧΡΙ|ΜΗ|ΜΗΔΕ|ΜΗΝ|ΜΗΠΩΣ|ΜΗΤΕ|ΜΙ|ΜΙΞ|ΜΙΣ|ΜΜΕ|ΜΝΑ|ΜΟΒ|ΜΟΛΙΣ|ΜΟΛΟΝΟΤΙ|ΜΟΝΑΧΑ|ΜΟΝΟΜΙΑΣ|ΜΙΑ|ΜΟΥ|ΜΠΑ|ΜΠΟΡΕΙ|'
. 'ΜΠΟΡΟΥΝ|ΜΠΡΑΒΟ|ΜΠΡΟΣ|ΜΠΩ|ΜΥ|ΜΥΑ|ΜΥΝ|ΝΑ|ΝΑΕ|ΝΑΙ|ΝΑΟ|ΝΔ|ΝΕΐ|ΝΕΑ|ΝΕΕ|ΝΕΟ|ΝΙ|ΝΙΑ|ΝΙΚ|ΝΙΛ|ΝΙΝ|ΝΙΟ|ΝΤΑ|ΝΤΕ|ΝΤΙ|ΝΤΟ|ΝΥΝ|ΝΩΕ|ΝΩΡΙΣ|ΞΑΝΑ|'
. 'ΞΑΦΝΙΚΑ|ΞΕΩ|ΞΙ|Ο|ΟΑ|ΟΑΠ|ΟΔΟ|ΟΕ|ΟΖΟ|ΟΗΕ|ΟΙ|ΟΙΑ|ΟΙΗ|ΟΚΑ|ΟΛΟΓΥΡΑ|ΟΛΟΝΕΝ|ΟΛΟΤΕΛΑ|ΟΛΩΣΔΙΟΛΟΥ|ΟΜΩΣ|ΟΝ|ΟΝΕ|ΟΝΟ|ΟΠΑ|ΟΠΕ|ΟΠΗ|ΟΠΟ|'
. 'ΟΠΟΙΑΔΗΠΟΤΕ|ΟΠΟΙΑΝΔΗΠΟΤΕ|ΟΠΟΙΑΣΔΗΠΟΤΕ|ΟΠΟΙΔΗΠΟΤΕ|ΟΠΟΙΕΣΔΗΠΟΤΕ|ΟΠΟΙΟΔΗΠΟΤΕ|ΟΠΟΙΟΝΔΗΠΟΤΕ|ΟΠΟΙΟΣΔΗΠΟΤΕ|ΟΠΟΙΟΥΔΗΠΟΤΕ|ΟΠΟΙΟΥΣΔΗΠΟΤΕ|'
. 'ΟΠΟΙΩΝΔΗΠΟΤΕ|ΟΠΟΤΕΔΗΠΟΤΕ|ΟΠΟΥ|ΟΠΟΥΔΗΠΟΤΕ|ΟΠΩΣ|ΟΡΑ|ΟΡΕ|ΟΡΗ|ΟΡΟ|ΟΡΦ|ΟΡΩ|ΟΣΑ|ΟΣΑΔΗΠΟΤΕ|ΟΣΕ|ΟΣΕΣΔΗΠΟΤΕ|ΟΣΗΔΗΠΟΤΕ|ΟΣΗΝΔΗΠΟΤΕ|'
. 'ΟΣΗΣΔΗΠΟΤΕ|ΟΣΟΔΗΠΟΤΕ|ΟΣΟΙΔΗΠΟΤΕ|ΟΣΟΝΔΗΠΟΤΕ|ΟΣΟΣΔΗΠΟΤΕ|ΟΣΟΥΔΗΠΟΤΕ|ΟΣΟΥΣΔΗΠΟΤΕ|ΟΣΩΝΔΗΠΟΤΕ|ΟΤΑΝ|ΟΤΕ|ΟΤΙ|ΟΤΙΔΗΠΟΤΕ|ΟΥ|ΟΥΔΕ|ΟΥΚ|ΟΥΣ|'
. 'ΟΥΤΕ|ΟΥΦ|ΟΧΙ|ΟΨΑ|ΟΨΕ|ΟΨΗ|ΟΨΙ|ΟΨΟ|ΠΑ|ΠΑΛΙ|ΠΑΝ|ΠΑΝΤΟΤΕ|ΠΑΝΤΟΥ|ΠΑΝΤΩΣ|ΠΑΠ|ΠΑΡ|ΠΑΡΑ|ΠΕΙ|ΠΕΡ|ΠΕΡΑ|ΠΕΡΙ|ΠΕΡΙΠΟΥ|ΠΕΡΣΙ|ΠΕΡΥΣΙ|ΠΕΣ|ΠΙ|'
. 'ΠΙΑ|ΠΙΘΑΝΟΝ|ΠΙΚ|ΠΙΟ|ΠΙΣΩ|ΠΙΤ|ΠΙΩ|ΠΛΑΙ|ΠΛΕΟΝ|ΠΛΗΝ|ΠΛΩ|ΠΜ|ΠΟΑ|ΠΟΕ|ΠΟΛ|ΠΟΛΥ|ΠΟΠ|ΠΟΤΕ|ΠΟΥ|ΠΟΥΘΕ|ΠΟΥΘΕΝΑ|ΠΡΕΠΕΙ|ΠΡΙ|ΠΡΙΝ|ΠΡΟ|'
. 'ΠΡΟΚΕΙΜΕΝΟΥ|ΠΡΟΚΕΙΤΑΙ|ΠΡΟΠΕΡΣΙ|ΠΡΟΣ|ΠΡΟΤΟΥ|ΠΡΟΧΘΕΣ|ΠΡΟΧΤΕΣ|ΠΡΩΤΥΤΕΡΑ|ΠΥΑ|ΠΥΞ|ΠΥΟ|ΠΥΡ|ΠΧ|ΠΩ|ΠΩΛ|ΠΩΣ|ΡΑ|ΡΑΙ|ΡΑΠ|ΡΑΣ|ΡΕ|ΡΕΑ|ΡΕΕ|ΡΕΙ|'
. 'ΡΗΣ|ΡΘΩ|ΡΙΟ|ΡΟ|ΡΟΐ|ΡΟΕ|ΡΟΖ|ΡΟΗ|ΡΟΘ|ΡΟΙ|ΡΟΚ|ΡΟΛ|ΡΟΝ|ΡΟΣ|ΡΟΥ|ΣΑΙ|ΣΑΝ|ΣΑΟ|ΣΑΣ|ΣΕ|ΣΕΙΣ|ΣΕΚ|ΣΕΞ|ΣΕΡ|ΣΕΤ|ΣΕΦ|ΣΗΜΕΡΑ|ΣΙ|ΣΙΑ|ΣΙΓΑ|ΣΙΚ|'
. 'ΣΙΧ|ΣΚΙ|ΣΟΙ|ΣΟΚ|ΣΟΛ|ΣΟΝ|ΣΟΣ|ΣΟΥ|ΣΡΙ|ΣΤΑ|ΣΤΗ|ΣΤΗΝ|ΣΤΗΣ|ΣΤΙΣ|ΣΤΟ|ΣΤΟΝ|ΣΤΟΥ|ΣΤΟΥΣ|ΣΤΩΝ|ΣΥ|ΣΥΓΧΡΟΝΩΣ|ΣΥΝ|ΣΥΝΑΜΑ|ΣΥΝΕΠΩΣ|ΣΥΝΗΘΩΣ|'
. 'ΣΧΕΔΟΝ|ΣΩΣΤΑ|ΤΑ|ΤΑΔΕ|ΤΑΚ|ΤΑΝ|ΤΑΟ|ΤΑΥ|ΤΑΧΑ|ΤΑΧΑΤΕ|ΤΕ|ΤΕΙ|ΤΕΛ|ΤΕΛΙΚΑ|ΤΕΛΙΚΩΣ|ΤΕΣ|ΤΕΤ|ΤΖΟ|ΤΗ|ΤΗΛ|ΤΗΝ|ΤΗΣ|ΤΙ|ΤΙΚ|ΤΙΜ|ΤΙΠΟΤΑ|ΤΙΠΟΤΕ|'
. 'ΤΙΣ|ΤΝΤ|ΤΟ|ΤΟΙ|ΤΟΚ|ΤΟΜ|ΤΟΝ|ΤΟΠ|ΤΟΣ|ΤΟΣ?Ν|ΤΟΣΑ|ΤΟΣΕΣ|ΤΟΣΗ|ΤΟΣΗΝ|ΤΟΣΗΣ|ΤΟΣΟ|ΤΟΣΟΙ|ΤΟΣΟΝ|ΤΟΣΟΣ|ΤΟΣΟΥ|ΤΟΣΟΥΣ|ΤΟΤΕ|ΤΟΥ|ΤΟΥΛΑΧΙΣΤΟ|'
. 'ΤΟΥΛΑΧΙΣΤΟΝ|ΤΟΥΣ|ΤΣ|ΤΣΑ|ΤΣΕ|ΤΥΧΟΝ|ΤΩ|ΤΩΝ|ΤΩΡΑ|ΥΑΣ|ΥΒΑ|ΥΒΟ|ΥΙΕ|ΥΙΟ|ΥΛΑ|ΥΛΗ|ΥΝΙ|ΥΠ|ΥΠΕΡ|ΥΠΟ|ΥΠΟΨΗ|ΥΠΟΨΙΝ|ΥΣΤΕΡΑ|ΥΦΗ|ΥΨΗ|ΦΑ|ΦΑΐ|ΦΑΕ|'
. 'ΦΑΝ|ΦΑΞ|ΦΑΣ|ΦΑΩ|ΦΕΖ|ΦΕΙ|ΦΕΤΟΣ|ΦΕΥ|ΦΙ|ΦΙΛ|ΦΙΣ|ΦΟΞ|ΦΠΑ|ΦΡΙ|ΧΑ|ΧΑΗ|ΧΑΛ|ΧΑΝ|ΧΑΦ|ΧΕ|ΧΕΙ|ΧΘΕΣ|ΧΙ|ΧΙΑ|ΧΙΛ|ΧΙΟ|ΧΛΜ|ΧΜ|ΧΟΗ|ΧΟΛ|ΧΡΩ|ΧΤΕΣ|'
. 'ΧΩΡΙΣ|ΧΩΡΙΣΤΑ|ΨΕΣ|ΨΗΛΑ|ΨΙ|ΨΙΤ|Ω|ΩΑ|ΩΑΣ|ΩΔΕ|ΩΕΣ|ΩΘΩ|ΩΜΑ|ΩΜΕ|ΩΝ|ΩΟ|ΩΟΝ|ΩΟΥ|ΩΣ|ΩΣΑΝ|ΩΣΗ|ΩΣΟΤΟΥ|ΩΣΠΟΥ|ΩΣΤΕ|ΩΣΤΟΣΟ|ΩΤΑ|ΩΧ|ΩΩΝ)$/';
if (preg_match($stop_words, $token)) {
return $this->toLowerCase($token, $wCase);
}
// Vowels
$v = '(Α|Ε|Η|Ι|Ο|Υ|Ω)';
// Vowels without Y
$v2 = '(Α|Ε|Η|Ι|Ο|Ω)';
$test1 = true;
// Step S1. 14 stems
$re = '/^(.+?)(ΙΖΑ|ΙΖΕΣ|ΙΖΕ|ΙΖΑΜΕ|ΙΖΑΤΕ|ΙΖΑΝ|ΙΖΑΝΕ|ΙΖΩ|ΙΖΕΙΣ|ΙΖΕΙ|ΙΖΟΥΜΕ|ΙΖΕΤΕ|ΙΖΟΥΝ|ΙΖΟΥΝΕ)$/';
$exceptS1 = '/^(ΑΝΑΜΠΑ|ΕΜΠΑ|ΕΠΑ|ΞΑΝΑΠΑ|ΠΑ|ΠΕΡΙΠΑ|ΑΘΡΟ|ΣΥΝΑΘΡΟ|ΔΑΝΕ)$/';
$exceptS2 = '/^(ΜΑΡΚ|ΚΟΡΝ|ΑΜΠΑΡ|ΑΡΡ|ΒΑΘΥΡΙ|ΒΑΡΚ|Β|ΒΟΛΒΟΡ|ΓΚΡ|ΓΛΥΚΟΡ|ΓΛΥΚΥΡ|ΙΜΠ|Λ|ΛΟΥ|ΜΑΡ|Μ|ΠΡ|ΜΠΡ|ΠΟΛΥΡ|Π|Ρ|ΠΙΠΕΡΟΡ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token .= 'I';
}
if (preg_match($exceptS2, $token)) {
$token .= 'IΖ';
}
return $this->toLowerCase($token, $wCase);
}
// Step S2. 7 stems
$re = '/^(.+?)(ΩΘΗΚΑ|ΩΘΗΚΕΣ|ΩΘΗΚΕ|ΩΘΗΚΑΜΕ|ΩΘΗΚΑΤΕ|ΩΘΗΚΑΝ|ΩΘΗΚΑΝΕ)$/';
$exceptS1 = '/^(ΑΛ|ΒΙ|ΕΝ|ΥΨ|ΛΙ|ΖΩ|Σ|Χ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token .= 'ΩΝ';
}
return $this->toLowerCase($token, $wCase);
}
// Step S3. 7 stems
$re = '/^(.+?)(ΙΣΑ|ΙΣΕΣ|ΙΣΕ|ΙΣΑΜΕ|ΙΣΑΤΕ|ΙΣΑΝ|ΙΣΑΝΕ)$/';
$exceptS1 = '/^(ΑΝΑΜΠΑ|ΑΘΡΟ|ΕΜΠΑ|ΕΣΕ|ΕΣΩΚΛΕ|ΕΠΑ|ΞΑΝΑΠΑ|ΕΠΕ|ΠΕΡΙΠΑ|ΑΘΡΟ|ΣΥΝΑΘΡΟ|ΔΑΝΕ|ΚΛΕ|ΧΑΡΤΟΠΑ|ΕΞΑΡΧΑ|ΜΕΤΕΠΕ|ΑΠΟΚΛΕ|ΑΠΕΚΛΕ|ΕΚΛΕ|ΠΕ|ΠΕΡΙΠΑ)$/';
$exceptS2 = '/^(ΑΝ|ΑΦ|ΓΕ|ΓΙΓΑΝΤΟΑΦ|ΓΚΕ|ΔΗΜΟΚΡΑΤ|ΚΟΜ|ΓΚ|Μ|Π|ΠΟΥΚΑΜ|ΟΛΟ|ΛΑΡ)$/';
if ($token == "ΙΣΑ") {
$token = "ΙΣ";
return $token;
}
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token .= 'Ι';
}
if (preg_match($exceptS2, $token)) {
$token .= 'ΙΣ';
}
return $this->toLowerCase($token, $wCase);
}
// Step S4. 7 stems
$re = '/^(.+?)(ΙΣΩ|ΙΣΕΙΣ|ΙΣΕΙ|ΙΣΟΥΜΕ|ΙΣΕΤΕ|ΙΣΟΥΝ|ΙΣΟΥΝΕ)$/';
$exceptS1 = '/^(ΑΝΑΜΠΑ|ΕΜΠΑ|ΕΣΕ|ΕΣΩΚΛΕ|ΕΠΑ|ΞΑΝΑΠΑ|ΕΠΕ|ΠΕΡΙΠΑ|ΑΘΡΟ|ΣΥΝΑΘΡΟ|ΔΑΝΕ|ΚΛΕ|ΧΑΡΤΟΠΑ|ΕΞΑΡΧΑ|ΜΕΤΕΠΕ|ΑΠΟΚΛΕ|ΑΠΕΚΛΕ|ΕΚΛΕ|ΠΕ|ΠΕΡΙΠΑ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token .= 'Ι';
}
return $this->toLowerCase($token, $wCase);
}
// Step S5. 11 stems
$re = '/^(.+?)(ΙΣΤΟΣ|ΙΣΤΟΥ|ΙΣΤΟ|ΙΣΤΕ|ΙΣΤΟΙ|ΙΣΤΩΝ|ΙΣΤΟΥΣ|ΙΣΤΗ|ΙΣΤΗΣ|ΙΣΤΑ|ΙΣΤΕΣ)$/';
$exceptS1 = '/^(Μ|Π|ΑΠ|ΑΡ|ΗΔ|ΚΤ|ΣΚ|ΣΧ|ΥΨ|ΦΑ|ΧΡ|ΧΤ|ΑΚΤ|ΑΟΡ|ΑΣΧ|ΑΤΑ|ΑΧΝ|ΑΧΤ|ΓΕΜ|ΓΥΡ|ΕΜΠ|ΕΥΠ|ΕΧΘ|ΗΦΑ|ΚΑΘ|ΚΑΚ|ΚΥΛ|ΛΥΓ|ΜΑΚ|ΜΕΓ|ΤΑΧ|ΦΙΛ|ΧΩΡ)$/';
$exceptS2 = '/^(ΔΑΝΕ|ΣΥΝΑΘΡΟ|ΚΛΕ|ΣΕ|ΕΣΩΚΛΕ|ΑΣΕ|ΠΛΕ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token .= 'ΙΣΤ';
}
if (preg_match($exceptS2, $token)) {
$token .= 'Ι';
}
return $this->toLowerCase($token, $wCase);
}
// Step S6. 6 stems
$re = '/^(.+?)(ΙΣΜΟ|ΙΣΜΟΙ|ΙΣΜΟΣ|ΙΣΜΟΥ|ΙΣΜΟΥΣ|ΙΣΜΩΝ)$/';
$exceptS1 = '/^(ΑΓΝΩΣΤΙΚ|ΑΤΟΜΙΚ|ΓΝΩΣΤΙΚ|ΕΘΝΙΚ|ΕΚΛΕΚΤΙΚ|ΣΚΕΠΤΙΚ|ΤΟΠΙΚ)$/';
$exceptS2 = '/^(ΣΕ|ΜΕΤΑΣΕ|ΜΙΚΡΟΣΕ|ΕΓΚΛΕ|ΑΠΟΚΛΕ)$/';
$exceptS3 = '/^(ΔΑΝΕ|ΑΝΤΙΔΑΝΕ)$/';
$exceptS4 = '/^(ΑΛΕΞΑΝΔΡΙΝ|ΒΥΖΑΝΤΙΝ|ΘΕΑΤΡΙΝ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token = str_replace('ΙΚ', "", $token);
}
if (preg_match($exceptS2, $token)) {
$token .= "ΙΣΜ";
}
if (preg_match($exceptS3, $token)) {
$token .= "Ι";
}
if (preg_match($exceptS4, $token)) {
$token = str_replace('ΙΝ', "", $token);
}
return $this->toLowerCase($token, $wCase);
}
// Step S7. 4 stems
$re = '/^(.+?)(ΑΡΑΚΙ|ΑΡΑΚΙΑ|ΟΥΔΑΚΙ|ΟΥΔΑΚΙΑ)$/';
$exceptS1 = '/^(Σ|Χ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token .= "AΡΑΚ";
}
return $this->toLowerCase($token, $wCase);
}
// Step S8. 8 stems
$re = '/^(.+?)(ΑΚΙ|ΑΚΙΑ|ΙΤΣΑ|ΙΤΣΑΣ|ΙΤΣΕΣ|ΙΤΣΩΝ|ΑΡΑΚΙ|ΑΡΑΚΙΑ)$/';
$exceptS1 = '/^(ΑΝΘΡ|ΒΑΜΒ|ΒΡ|ΚΑΙΜ|ΚΟΝ|ΚΟΡ|ΛΑΒΡ|ΛΟΥΛ|ΜΕΡ|ΜΟΥΣΤ|ΝΑΓΚΑΣ|ΠΛ|Ρ|ΡΥ|Σ|ΣΚ|ΣΟΚ|ΣΠΑΝ|ΤΖ|ΦΑΡΜ|Χ|'
. 'ΚΑΠΑΚ|ΑΛΙΣΦ|ΑΜΒΡ|ΑΝΘΡ|Κ|ΦΥΛ|ΚΑΤΡΑΠ|ΚΛΙΜ|ΜΑΛ|ΣΛΟΒ|Φ|ΣΦ|ΤΣΕΧΟΣΛΟΒ)$/';
$exceptS2 = '/^(Β|ΒΑΛ|ΓΙΑΝ|ΓΛ|Ζ|ΗΓΟΥΜΕΝ|ΚΑΡΔ|ΚΟΝ|ΜΑΚΡΥΝ|ΝΥΦ|ΠΑΤΕΡ|Π|ΣΚ|ΤΟΣ|ΤΡΙΠΟΛ)$/';
// For words like ΠΛΟΥΣΙΟΚΟΡΙΤΣΑ, ΠΑΛΙΟΚΟΡΙΤΣΑ etc
$exceptS3 = '/(ΚΟΡ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token .= "ΑΚ";
}
if (preg_match($exceptS2, $token)) {
$token .= "ΙΤΣ";
}
if (preg_match($exceptS3, $token)) {
$token .= "ΙΤΣ";
}
return $this->toLowerCase($token, $wCase);
}
// Step S9. 3 stems
$re = '/^(.+?)(ΙΔΙΟ|ΙΔΙΑ|ΙΔΙΩΝ)$/';
$exceptS1 = '/^(ΑΙΦΝ|ΙΡ|ΟΛΟ|ΨΑΛ)$/';
$exceptS2 = '/(Ε|ΠΑΙΧΝ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token .= "ΙΔ";
}
if (preg_match($exceptS2, $token)) {
$token .= "ΙΔ";
}
return $this->toLowerCase($token, $wCase);
}
// Step S10. 4 stems
$re = '/^(.+?)(ΙΣΚΟΣ|ΙΣΚΟΥ|ΙΣΚΟ|ΙΣΚΕ)$/';
$exceptS1 = '/^(Δ|ΙΒ|ΜΗΝ|Ρ|ΦΡΑΓΚ|ΛΥΚ|ΟΒΕΛ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
if (preg_match($exceptS1, $token)) {
$token .= "ΙΣΚ";
}
return $this->toLowerCase($token, $wCase);
}
// Step 1
// step1list is used in Step 1. 41 stems
$step1list = [];
$step1list["ΦΑΓΙΑ"] = "ΦΑ";
$step1list["ΦΑΓΙΟΥ"] = "ΦΑ";
$step1list["ΦΑΓΙΩΝ"] = "ΦΑ";
$step1list["ΣΚΑΓΙΑ"] = "ΣΚΑ";
$step1list["ΣΚΑΓΙΟΥ"] = "ΣΚΑ";
$step1list["ΣΚΑΓΙΩΝ"] = "ΣΚΑ";
$step1list["ΟΛΟΓΙΟΥ"] = "ΟΛΟ";
$step1list["ΟΛΟΓΙΑ"] = "ΟΛΟ";
$step1list["ΟΛΟΓΙΩΝ"] = "ΟΛΟ";
$step1list["ΣΟΓΙΟΥ"] = "ΣΟ";
$step1list["ΣΟΓΙΑ"] = "ΣΟ";
$step1list["ΣΟΓΙΩΝ"] = "ΣΟ";
$step1list["ΤΑΤΟΓΙΑ"] = "ΤΑΤΟ";
$step1list["ΤΑΤΟΓΙΟΥ"] = "ΤΑΤΟ";
$step1list["ΤΑΤΟΓΙΩΝ"] = "ΤΑΤΟ";
$step1list["ΚΡΕΑΣ"] = "ΚΡΕ";
$step1list["ΚΡΕΑΤΟΣ"] = "ΚΡΕ";
$step1list["ΚΡΕΑΤΑ"] = "ΚΡΕ";
$step1list["ΚΡΕΑΤΩΝ"] = "ΚΡΕ";
$step1list["ΠΕΡΑΣ"] = "ΠΕΡ";
$step1list["ΠΕΡΑΤΟΣ"] = "ΠΕΡ";
// Added by Spyros. Also at $re in step1
$step1list["ΠΕΡΑΤΗ"] = "ΠΕΡ";
$step1list["ΠΕΡΑΤΑ"] = "ΠΕΡ";
$step1list["ΠΕΡΑΤΩΝ"] = "ΠΕΡ";
$step1list["ΤΕΡΑΣ"] = "ΤΕΡ";
$step1list["ΤΕΡΑΤΟΣ"] = "ΤΕΡ";
$step1list["ΤΕΡΑΤΑ"] = "ΤΕΡ";
$step1list["ΤΕΡΑΤΩΝ"] = "ΤΕΡ";
$step1list["ΦΩΣ"] = "ΦΩ";
$step1list["ΦΩΤΟΣ"] = "ΦΩ";
$step1list["ΦΩΤΑ"] = "ΦΩ";
$step1list["ΦΩΤΩΝ"] = "ΦΩ";
$step1list["ΚΑΘΕΣΤΩΣ"] = "ΚΑΘΕΣΤ";
$step1list["ΚΑΘΕΣΤΩΤΟΣ"] = "ΚΑΘΕΣΤ";
$step1list["ΚΑΘΕΣΤΩΤΑ"] = "ΚΑΘΕΣΤ";
$step1list["ΚΑΘΕΣΤΩΤΩΝ"] = "ΚΑΘΕΣΤ";
$step1list["ΓΕΓΟΝΟΣ"] = "ΓΕΓΟΝ";
$step1list["ΓΕΓΟΝΟΤΟΣ"] = "ΓΕΓΟΝ";
$step1list["ΓΕΓΟΝΟΤΑ"] = "ΓΕΓΟΝ";
$step1list["ΓΕΓΟΝΟΤΩΝ"] = "ΓΕΓΟΝ";
$re = '/(.*)(ΦΑΓΙΑ|ΦΑΓΙΟΥ|ΦΑΓΙΩΝ|ΣΚΑΓΙΑ|ΣΚΑΓΙΟΥ|ΣΚΑΓΙΩΝ|ΟΛΟΓΙΟΥ|ΟΛΟΓΙΑ|ΟΛΟΓΙΩΝ|ΣΟΓΙΟΥ|ΣΟΓΙΑ|ΣΟΓΙΩΝ|ΤΑΤΟΓΙΑ|ΤΑΤΟΓΙΟΥ|ΤΑΤΟΓΙΩΝ|ΚΡΕΑΣ|ΚΡΕΑΤΟΣ|'
. 'ΚΡΕΑΤΑ|ΚΡΕΑΤΩΝ|ΠΕΡΑΣ|ΠΕΡΑΤΟΣ|ΠΕΡΑΤΗ|ΠΕΡΑΤΑ|ΠΕΡΑΤΩΝ|ΤΕΡΑΣ|ΤΕΡΑΤΟΣ|ΤΕΡΑΤΑ|ΤΕΡΑΤΩΝ|ΦΩΣ|ΦΩΤΟΣ|ΦΩΤΑ|ΦΩΤΩΝ|ΚΑΘΕΣΤΩΣ|ΚΑΘΕΣΤΩΤΟΣ|'
. 'ΚΑΘΕΣΤΩΤΑ|ΚΑΘΕΣΤΩΤΩΝ|ΓΕΓΟΝΟΣ|ΓΕΓΟΝΟΤΟΣ|ΓΕΓΟΝΟΤΑ|ΓΕΓΟΝΟΤΩΝ)$/';
if (preg_match($re, $token, $match)) {
$stem = $match[1];
$suffix = $match[2];
$token = $stem . (\array_key_exists($suffix, $step1list) ? $step1list[$suffix] : '');
$test1 = false;
}
// Step 2a. 2 stems
$re = '/^(.+?)(ΑΔΕΣ|ΑΔΩΝ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1];
$re = '/(ΟΚ|ΜΑΜ|ΜΑΝ|ΜΠΑΜΠ|ΠΑΤΕΡ|ΓΙΑΓΙ|ΝΤΑΝΤ|ΚΥΡ|ΘΕΙ|ΠΕΘΕΡ)$/';
if (!preg_match($re, $token)) {
$token .= "ΑΔ";
}
}
// Step 2b. 2 stems
$re = '/^(.+?)(ΕΔΕΣ|ΕΔΩΝ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$exept2 = '/(ΟΠ|ΙΠ|ΕΜΠ|ΥΠ|ΓΗΠ|ΔΑΠ|ΚΡΑΣΠ|ΜΙΛ)$/';
if (preg_match($exept2, $token)) {
$token .= 'ΕΔ';
}
}
// Step 2c
$re = '/^(.+?)(ΟΥΔΕΣ|ΟΥΔΩΝ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$exept3 = '/(ΑΡΚ|ΚΑΛΙΑΚ|ΠΕΤΑΛ|ΛΙΧ|ΠΛΕΞ|ΣΚ|Σ|ΦΛ|ΦΡ|ΒΕΛ|ΛΟΥΛ|ΧΝ|ΣΠ|ΤΡΑΓ|ΦΕ)$/';
if (preg_match($exept3, $token)) {
$token .= 'ΟΥΔ';
}
}
// Step 2d
$re = '/^(.+?)(ΕΩΣ|ΕΩΝ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept4 = '/^(Θ|Δ|ΕΛ|ΓΑΛ|Ν|Π|ΙΔ|ΠΑΡ)$/';
if (preg_match($exept4, $token)) {
$token .= 'Ε';
}
}
// Step 3
$re = '/^(.+?)(ΙΑ|ΙΟΥ|ΙΩΝ)$/';
if (preg_match($re, $token, $fp)) {
$stem = $fp[1];
$token = $stem;
$re = '/' . $v . '$/';
$test1 = false;
if (preg_match($re, $token)) {
$token = $stem . 'Ι';
}
}
// Step 4
$re = '/^(.+?)(ΙΚΑ|ΙΚΟ|ΙΚΟΥ|ΙΚΩΝ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$re = '/' . $v . '$/';
$exept5 = '/^(ΑΛ|ΑΔ|ΕΝΔ|ΑΜΑΝ|ΑΜΜΟΧΑΛ|ΗΘ|ΑΝΗΘ|ΑΝΤΙΔ|ΦΥΣ|ΒΡΩΜ|ΓΕΡ|ΕΞΩΔ|ΚΑΛΠ|ΚΑΛΛΙΝ|ΚΑΤΑΔ|ΜΟΥΛ|ΜΠΑΝ|ΜΠΑΓΙΑΤ|ΜΠΟΛ|ΜΠΟΣ|ΝΙΤ|ΞΙΚ|ΣΥΝΟΜΗΛ|ΠΕΤΣ|'
. 'ΠΙΤΣ|ΠΙΚΑΝΤ|ΠΛΙΑΤΣ|ΠΟΣΤΕΛΝ|ΠΡΩΤΟΔ|ΣΕΡΤ|ΣΥΝΑΔ|ΤΣΑΜ|ΥΠΟΔ|ΦΙΛΟΝ|ΦΥΛΟΔ|ΧΑΣ)$/';
if (preg_match($re, $token) || preg_match($exept5, $token)) {
$token .= 'ΙΚ';
}
}
// Step 5a
$re = '/^(.+?)(ΑΜΕ)$/';
$re2 = '/^(.+?)(ΑΓΑΜΕ|ΗΣΑΜΕ|ΟΥΣΑΜΕ|ΗΚΑΜΕ|ΗΘΗΚΑΜΕ)$/';
if ($token == "ΑΓΑΜΕ") {
$token = "ΑΓΑΜ";
}
if (preg_match($re2, $token)) {
preg_match($re2, $token, $match);
$token = $match[1];
$test1 = false;
}
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept6 = '/^(ΑΝΑΠ|ΑΠΟΘ|ΑΠΟΚ|ΑΠΟΣΤ|ΒΟΥΒ|ΞΕΘ|ΟΥΛ|ΠΕΘ|ΠΙΚΡ|ΠΟΤ|ΣΙΧ|Χ)$/';
if (preg_match($exept6, $token)) {
$token .= "ΑΜ";
}
}
// Step 5b
$re2 = '/^(.+?)(ΑΝΕ)$/';
$re3 = '/^(.+?)(ΑΓΑΝΕ|ΗΣΑΝΕ|ΟΥΣΑΝΕ|ΙΟΝΤΑΝΕ|ΙΟΤΑΝΕ|ΙΟΥΝΤΑΝΕ|ΟΝΤΑΝΕ|ΟΤΑΝΕ|ΟΥΝΤΑΝΕ|ΗΚΑΝΕ|ΗΘΗΚΑΝΕ)$/';
if (preg_match($re3, $token)) {
preg_match($re3, $token, $match);
$token = $match[1];
$test1 = false;
$re3 = '/^(ΤΡ|ΤΣ)$/';
if (preg_match($re3, $token)) {
$token .= "ΑΓΑΝ";
}
}
if (preg_match($re2, $token)) {
preg_match($re2, $token, $match);
$token = $match[1];
$test1 = false;
$re2 = '/' . $v2 . '$/';
$exept7 = '/^(ΒΕΤΕΡ|ΒΟΥΛΚ|ΒΡΑΧΜ|Γ|ΔΡΑΔΟΥΜ|Θ|ΚΑΛΠΟΥΖ|ΚΑΣΤΕΛ|ΚΟΡΜΟΡ|ΛΑΟΠΛ|ΜΩΑΜΕΘ|Μ|ΜΟΥΣΟΥΛΜ|Ν|ΟΥΛ|Π|ΠΕΛΕΚ|ΠΛ|ΠΟΛΙΣ|ΠΟΡΤΟΛ|ΣΑΡΑΚΑΤΣ|ΣΟΥΛΤ|'
. 'ΤΣΑΡΛΑΤ|ΟΡΦ|ΤΣΙΓΓ|ΤΣΟΠ|ΦΩΤΟΣΤΕΦ|Χ|ΨΥΧΟΠΛ|ΑΓ|ΟΡΦ|ΓΑΛ|ΓΕΡ|ΔΕΚ|ΔΙΠΛ|ΑΜΕΡΙΚΑΝ|ΟΥΡ|ΠΙΘ|ΠΟΥΡΙΤ|Σ|ΖΩΝΤ|ΙΚ|ΚΑΣΤ|ΚΟΠ|ΛΙΧ|ΛΟΥΘΗΡ|ΜΑΙΝΤ|'
. 'ΜΕΛ|ΣΙΓ|ΣΠ|ΣΤΕΓ|ΤΡΑΓ|ΤΣΑΓ|Φ|ΕΡ|ΑΔΑΠ|ΑΘΙΓΓ|ΑΜΗΧ|ΑΝΙΚ|ΑΝΟΡΓ|ΑΠΗΓ|ΑΠΙΘ|ΑΤΣΙΓΓ|ΒΑΣ|ΒΑΣΚ|ΒΑΘΥΓΑΛ|ΒΙΟΜΗΧ|ΒΡΑΧΥΚ|ΔΙΑΤ|ΔΙΑΦ|ΕΝΟΡΓ|'
. 'ΘΥΣ|ΚΑΠΝΟΒΙΟΜΗΧ|ΚΑΤΑΓΑΛ|ΚΛΙΒ|ΚΟΙΛΑΡΦ|ΛΙΒ|ΜΕΓΛΟΒΙΟΜΗΧ|ΜΙΚΡΟΒΙΟΜΗΧ|ΝΤΑΒ|ΞΗΡΟΚΛΙΒ|ΟΛΙΓΟΔΑΜ|ΟΛΟΓΑΛ|ΠΕΝΤΑΡΦ|ΠΕΡΗΦ|ΠΕΡΙΤΡ|ΠΛΑΤ|'
. 'ΠΟΛΥΔΑΠ|ΠΟΛΥΜΗΧ|ΣΤΕΦ|ΤΑΒ|ΤΕΤ|ΥΠΕΡΗΦ|ΥΠΟΚΟΠ|ΧΑΜΗΛΟΔΑΠ|ΨΗΛΟΤΑΒ)$/';
if (preg_match($re2, $token) || preg_match($exept7, $token)) {
$token .= "ΑΝ";
}
}
// Step 5c
$re3 = '/^(.+?)(ΕΤΕ)$/';
$re4 = '/^(.+?)(ΗΣΕΤΕ)$/';
if (preg_match($re4, $token)) {
preg_match($re4, $token, $match);
$token = $match[1];
$test1 = false;
}
if (preg_match($re3, $token)) {
preg_match($re3, $token, $match);
$token = $match[1];
$test1 = false;
$re3 = '/' . $v2 . '$/';
$exept8 = '/(ΟΔ|ΑΙΡ|ΦΟΡ|ΤΑΘ|ΔΙΑΘ|ΣΧ|ΕΝΔ|ΕΥΡ|ΤΙΘ|ΥΠΕΡΘ|ΡΑΘ|ΕΝΘ|ΡΟΘ|ΣΘ|ΠΥΡ|ΑΙΝ|ΣΥΝΔ|ΣΥΝ|ΣΥΝΘ|ΧΩΡ|ΠΟΝ|ΒΡ|ΚΑΘ|ΕΥΘ|ΕΚΘ|ΝΕΤ|ΡΟΝ|ΑΡΚ|ΒΑΡ|ΒΟΛ|ΩΦΕΛ)$/';
$exept9 = '/^(ΑΒΑΡ|ΒΕΝ|ΕΝΑΡ|ΑΒΡ|ΑΔ|ΑΘ|ΑΝ|ΑΠΛ|ΒΑΡΟΝ|ΝΤΡ|ΣΚ|ΚΟΠ|ΜΠΟΡ|ΝΙΦ|ΠΑΓ|ΠΑΡΑΚΑΛ|ΣΕΡΠ|ΣΚΕΛ|ΣΥΡΦ|ΤΟΚ|Υ|Δ|ΕΜ|ΘΑΡΡ|Θ)$/';
if (preg_match($re3, $token) || preg_match($exept8, $token) || preg_match($exept9, $token)) {
$token .= "ΕΤ";
}
}
// Step 5d
$re = '/^(.+?)(ΟΝΤΑΣ|ΩΝΤΑΣ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept10 = '/^(ΑΡΧ)$/';
$exept11 = '/(ΚΡΕ)$/';
if (preg_match($exept10, $token)) {
$token .= "ΟΝΤ";
}
if (preg_match($exept11, $token)) {
$token .= "ΩΝΤ";
}
}
// Step 5e
$re = '/^(.+?)(ΟΜΑΣΤΕ|ΙΟΜΑΣΤΕ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept11 = '/^(ΟΝ)$/';
if (preg_match($exept11, $token)) {
$token .= "ΟΜΑΣΤ";
}
}
// Step 5f
$re = '/^(.+?)(ΕΣΤΕ)$/';
$re2 = '/^(.+?)(ΙΕΣΤΕ)$/';
if (preg_match($re2, $token)) {
preg_match($re2, $token, $match);
$token = $match[1];
$test1 = false;
$re2 = '/^(Π|ΑΠ|ΣΥΜΠ|ΑΣΥΜΠ|ΑΚΑΤΑΠ|ΑΜΕΤΑΜΦ)$/';
if (preg_match($re2, $token)) {
$token .= "ΙΕΣΤ";
}
}
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept12 = '/^(ΑΛ|ΑΡ|ΕΚΤΕΛ|Ζ|Μ|Ξ|ΠΑΡΑΚΑΛ|ΠΡΟ|ΝΙΣ)$/';
if (preg_match($exept12, $token)) {
$token .= "ΕΣΤ";
}
}
// Step 5g
$re = '/^(.+?)(ΗΚΑ|ΗΚΕΣ|ΗΚΕ)$/';
$re2 = '/^(.+?)(ΗΘΗΚΑ|ΗΘΗΚΕΣ|ΗΘΗΚΕ)$/';
if (preg_match($re2, $token)) {
preg_match($re2, $token, $match);
$token = $match[1];
$test1 = false;
}
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept13 = '/(ΣΚΩΛ|ΣΚΟΥΛ|ΝΑΡΘ|ΣΦ|ΟΘ|ΠΙΘ)$/';
$exept14 = '/^(ΔΙΑΘ|Θ|ΠΑΡΑΚΑΤΑΘ|ΠΡΟΣΘ|ΣΥΝΘ|)$/';
if (preg_match($exept13, $token) || preg_match($exept14, $token)) {
$token .= "ΗΚ";
}
}
// Step 5h
$re = '/^(.+?)(ΟΥΣΑ|ΟΥΣΕΣ|ΟΥΣΕ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept15 = '/^(ΦΑΡΜΑΚ|ΧΑΔ|ΑΓΚ|ΑΝΑΡΡ|ΒΡΟΜ|ΕΚΛΙΠ|ΛΑΜΠΙΔ|ΛΕΧ|Μ|ΠΑΤ|Ρ|Λ|ΜΕΔ|ΜΕΣΑΖ|ΥΠΟΤΕΙΝ|ΑΜ|ΑΙΘ|ΑΝΗΚ|ΔΕΣΠΟΖ|ΕΝΔΙΑΦΕΡ|ΔΕ|ΔΕΥΤΕΡΕΥ|ΚΑΘΑΡΕΥ|ΠΛΕ|ΤΣΑ)$/';
$exept16 = '/(ΠΟΔΑΡ|ΒΛΕΠ|ΠΑΝΤΑΧ|ΦΡΥΔ|ΜΑΝΤΙΛ|ΜΑΛΛ|ΚΥΜΑΤ|ΛΑΧ|ΛΗΓ|ΦΑΓ|ΟΜ|ΠΡΩΤ)$/';
if (preg_match($exept15, $token) || preg_match($exept16, $token)) {
$token .= "ΟΥΣ";
}
}
// Step 5i
$re = '/^(.+?)(ΑΓΑ|ΑΓΕΣ|ΑΓΕ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept17 = '/^(ΨΟΦ|ΝΑΥΛΟΧ)$/';
$exept20 = '/(ΚΟΛΛ)$/';
$exept18 = '/^(ΑΒΑΣΤ|ΠΟΛΥΦ|ΑΔΗΦ|ΠΑΜΦ|Ρ|ΑΣΠ|ΑΦ|ΑΜΑΛ|ΑΜΑΛΛΙ|ΑΝΥΣΤ|ΑΠΕΡ|ΑΣΠΑΡ|ΑΧΑΡ|ΔΕΡΒΕΝ|ΔΡΟΣΟΠ|ΞΕΦ|ΝΕΟΠ|ΝΟΜΟΤ|ΟΛΟΠ|ΟΜΟΤ|ΠΡΟΣΤ|ΠΡΟΣΩΠΟΠ|'
. 'ΣΥΜΠ|ΣΥΝΤ|Τ|ΥΠΟΤ|ΧΑΡ|ΑΕΙΠ|ΑΙΜΟΣΤ|ΑΝΥΠ|ΑΠΟΤ|ΑΡΤΙΠ|ΔΙΑΤ|ΕΝ|ΕΠΙΤ|ΚΡΟΚΑΛΟΠ|ΣΙΔΗΡΟΠ|Λ|ΝΑΥ|ΟΥΛΑΜ|ΟΥΡ|Π|ΤΡ|Μ)$/';
$exept19 = '/(ΟΦ|ΠΕΛ|ΧΟΡΤ|ΛΛ|ΣΦ|ΡΠ|ΦΡ|ΠΡ|ΛΟΧ|ΣΜΗΝ)$/';
if (
(preg_match($exept18, $token) || preg_match($exept19, $token))
&& !(preg_match($exept17, $token) || preg_match($exept20, $token))
) {
$token .= "ΑΓ";
}
}
// Step 5j
$re = '/^(.+?)(ΗΣΕ|ΗΣΟΥ|ΗΣΑ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept21 = '/^(Ν|ΧΕΡΣΟΝ|ΔΩΔΕΚΑΝ|ΕΡΗΜΟΝ|ΜΕΓΑΛΟΝ|ΕΠΤΑΝ)$/';
if (preg_match($exept21, $token)) {
$token .= "ΗΣ";
}
}
// Step 5k
$re = '/^(.+?)(ΗΣΤΕ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept22 = '/^(ΑΣΒ|ΣΒ|ΑΧΡ|ΧΡ|ΑΠΛ|ΑΕΙΜΝ|ΔΥΣΧΡ|ΕΥΧΡ|ΚΟΙΝΟΧΡ|ΠΑΛΙΜΨ)$/';
if (preg_match($exept22, $token)) {
$token .= "ΗΣΤ";
}
}
// Step 5l
$re = '/^(.+?)(ΟΥΝΕ|ΗΣΟΥΝΕ|ΗΘΟΥΝΕ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept23 = '/^(Ν|Ρ|ΣΠΙ|ΣΤΡΑΒΟΜΟΥΤΣ|ΚΑΚΟΜΟΥΤΣ|ΕΞΩΝ)$/';
if (preg_match($exept23, $token)) {
$token .= "ΟΥΝ";
}
}
// Step 5m
$re = '/^(.+?)(ΟΥΜΕ|ΗΣΟΥΜΕ|ΗΘΟΥΜΕ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
$test1 = false;
$exept24 = '/^(ΠΑΡΑΣΟΥΣ|Φ|Χ|ΩΡΙΟΠΛ|ΑΖ|ΑΛΛΟΣΟΥΣ|ΑΣΟΥΣ)$/';
if (preg_match($exept24, $token)) {
$token .= "ΟΥΜ";
}
}
// Step 6
$re = '/^(.+?)(ΜΑΤΑ|ΜΑΤΩΝ|ΜΑΤΟΣ)$/';
$re2 = '/^(.+?)(Α|ΑΓΑΤΕ|ΑΓΑΝ|ΑΕΙ|ΑΜΑΙ|ΑΝ|ΑΣ|ΑΣΑΙ|ΑΤΑΙ|ΑΩ|Ε|ΕΙ|ΕΙΣ|ΕΙΤΕ|ΕΣΑΙ|ΕΣ|ΕΤΑΙ|Ι|ΙΕΜΑΙ|ΙΕΜΑΣΤΕ|ΙΕΤΑΙ|ΙΕΣΑΙ|ΙΕΣΑΣΤΕ|ΙΟΜΑΣΤΑΝ|ΙΟΜΟΥΝ|'
. 'ΙΟΜΟΥΝΑ|ΙΟΝΤΑΝ|ΙΟΝΤΟΥΣΑΝ|ΙΟΣΑΣΤΑΝ|ΙΟΣΑΣΤΕ|ΙΟΣΟΥΝ|ΙΟΣΟΥΝΑ|ΙΟΤΑΝ|ΙΟΥΜΑ|ΙΟΥΜΑΣΤΕ|ΙΟΥΝΤΑΙ|ΙΟΥΝΤΑΝ|Η|ΗΔΕΣ|ΗΔΩΝ|ΗΘΕΙ|ΗΘΕΙΣ|ΗΘΕΙΤΕ|'
. 'ΗΘΗΚΑΤΕ|ΗΘΗΚΑΝ|ΗΘΟΥΝ|ΗΘΩ|ΗΚΑΤΕ|ΗΚΑΝ|ΗΣ|ΗΣΑΝ|ΗΣΑΤΕ|ΗΣΕΙ|ΗΣΕΣ|ΗΣΟΥΝ|ΗΣΩ|Ο|ΟΙ|ΟΜΑΙ|ΟΜΑΣΤΑΝ|ΟΜΟΥΝ|ΟΜΟΥΝΑ|ΟΝΤΑΙ|ΟΝΤΑΝ|ΟΝΤΟΥΣΑΝ|ΟΣ|'
. 'ΟΣΑΣΤΑΝ|ΟΣΑΣΤΕ|ΟΣΟΥΝ|ΟΣΟΥΝΑ|ΟΤΑΝ|ΟΥ|ΟΥΜΑΙ|ΟΥΜΑΣΤΕ|ΟΥΝ|ΟΥΝΤΑΙ|ΟΥΝΤΑΝ|ΟΥΣ|ΟΥΣΑΝ|ΟΥΣΑΤΕ|Υ|ΥΣ|Ω|ΩΝ)$/';
if (preg_match($re, $token, $match)) {
$token = $match[1] . "ΜΑ";
}
if (preg_match($re2, $token) && $test1) {
preg_match($re2, $token, $match);
$token = $match[1];
}
// Step 7 (ΠΑΡΑΘΕΤΙΚΑ)
$re = '/^(.+?)(ΕΣΤΕΡ|ΕΣΤΑΤ|ΟΤΕΡ|ΟΤΑΤ|ΥΤΕΡ|ΥΤΑΤ|ΩΤΕΡ|ΩΤΑΤ)$/';
if (preg_match($re, $token)) {
preg_match($re, $token, $match);
$token = $match[1];
}
return $this->toLowerCase($token, $wCase);
}
/**
* Converts the token to uppercase, suppressing accents and diaeresis. The array $wCase contains a special map of
* the uppercase rule used to convert each character at each position.
*
* @param string $token Token to process
* @param array &$wCase Map of uppercase rules
*
* @return string
*
* @since 4.0.0
*/
protected function toUpperCase($token, &$wCase)
{
$wCase = array_fill(0, mb_strlen($token, 'UTF-8'), 0);
$caseConvert = [
"α" => 'Α',
"β" => 'Β',
"γ" => 'Γ',
"δ" => 'Δ',
"ε" => 'Ε',
"ζ" => 'Ζ',
"η" => 'Η',
"θ" => 'Θ',
"ι" => 'Ι',
"κ" => 'Κ',
"λ" => 'Λ',
"μ" => 'Μ',
"ν" => 'Ν',
"ξ" => 'Ξ',
"ο" => 'Ο',
"π" => 'Π',
"ρ" => 'Ρ',
"σ" => 'Σ',
"τ" => 'Τ',
"υ" => 'Υ',
"φ" => 'Φ',
"χ" => 'Χ',
"ψ" => 'Ψ',
"ω" => 'Ω',
"ά" => 'Α',
"έ" => 'Ε',
"ή" => 'Η',
"ί" => 'Ι',
"ό" => 'Ο',
"ύ" => 'Υ',
"ώ" => 'Ω',
"ς" => 'Σ',
"ϊ" => 'Ι',
"ϋ" => 'Ι',
"ΐ" => 'Ι',
"ΰ" => 'Υ',
];
$newToken = '';
for ($i = 0; $i < mb_strlen($token); $i++) {
$char = mb_substr($token, $i, 1);
$isLower = \array_key_exists($char, $caseConvert);
if (!$isLower) {
$newToken .= $char;
continue;
}
$upperCase = $caseConvert[$char];
$newToken .= $upperCase;
$wCase[$i] = 1;
if (\in_array($char, ['ά', 'έ', 'ή', 'ί', 'ό', 'ύ', 'ώ', 'ς'])) {
$wCase[$i] = 2;
}
if (\in_array($char, ['ϊ', 'ϋ'])) {
$wCase[$i] = 3;
}
if (\in_array($char, ['ΐ', 'ΰ'])) {
$wCase[$i] = 4;
}
}
return $newToken;
}
/**
* Converts the suppressed uppercase token back to lowercase, using the $wCase map to add back the accents,
* diaeresis and handle the special case of final sigma (different lowercase glyph than the regular sigma, only
* used at the end of words).
*
* @param string $token Token to process
* @param array $wCase Map of lowercase rules
*
* @return string
*
* @since 4.0.0
*/
protected function toLowerCase($token, $wCase)
{
$newToken = '';
for ($i = 0; $i < mb_strlen($token); $i++) {
$char = mb_substr($token, $i, 1);
// Is $wCase not set at this position? We assume no case conversion ever took place.
if (!isset($wCase[$i])) {
$newToken .= $char;
continue;
}
// The character was not case-converted
if ($wCase[$i] == 0) {
$newToken .= $char;
continue;
}
// Case 1: Unaccented letter
if ($wCase[$i] == 1) {
$newToken .= mb_strtolower($char);
continue;
}
// Case 2: Vowel with accent (tonos); or the special case of final sigma
if ($wCase[$i] == 2) {
$charMap = [
'Α' => 'ά',
'Ε' => 'έ',
'Η' => 'ή',
'Ι' => 'ί',
'Ο' => 'ό',
'Υ' => 'ύ',
'Ω' => 'ώ',
'Σ' => 'ς',
];
$newToken .= $charMap[$char];
continue;
}
// Case 3: vowels with diaeresis (dialytika)
if ($wCase[$i] == 3) {
$charMap = [
'Ι' => 'ϊ',
'Υ' => 'ϋ',
];
$newToken .= $charMap[$char];
continue;
}
// Case 4: vowels with both diaeresis (dialytika) and accent (tonos)
if ($wCase[$i] == 4) {
$charMap = [
'Ι' => 'ΐ',
'Υ' => 'ΰ',
];
$newToken .= $charMap[$char];
continue;
}
// This should never happen!
$newToken .= $char;
}
return $newToken;
}
}

View File

@ -0,0 +1,71 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Indexer\Language;
use Joomla\Component\Finder\Administrator\Indexer\Language;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Chinese (simplified) language support class for the Finder indexer package.
*
* @since 4.0.0
*/
class Zh extends Language
{
/**
* Language locale of the class
*
* @var string
* @since 4.0.0
*/
public $language = 'zh';
/**
* Spacer between terms
*
* @var string
* @since 4.0.0
*/
public $spacer = '';
/**
* Method to construct the language object.
*
* @since 4.0.0
*/
public function __construct($locale = null)
{
// Override parent constructor since we don't need to load an external stemmer
}
/**
* Method to tokenise a text string.
*
* @param string $input The input to tokenise.
*
* @return array An array of term strings.
*
* @since 4.0.0
*/
public function tokenise($input)
{
// We first add whitespace around each Chinese character, so that our later code can easily split on this.
$input = preg_replace('#\p{Han}#mui', ' $0 ', $input);
// Now we split up the input into individual terms
$terms = parent::tokenise($input);
return $terms;
}
}

View File

@ -0,0 +1,125 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Indexer;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Language\Text;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Parser base class for the Finder indexer package.
*
* @since 2.5
*/
abstract class Parser
{
/**
* Parser support instances container.
*
* @var Parser[]
* @since 4.0.0
*/
protected static $instances = [];
/**
* Method to get a parser, creating it if necessary.
*
* @param string $format The type of parser to load.
*
* @return Parser A Parser instance.
*
* @since 2.5
* @throws \Exception on invalid parser.
*/
public static function getInstance($format)
{
$format = InputFilter::getInstance()->clean($format, 'cmd');
// Only create one parser for each format.
if (isset(self::$instances[$format])) {
return self::$instances[$format];
}
// Setup the adapter for the parser.
$class = '\\Joomla\\Component\\Finder\\Administrator\\Indexer\\Parser\\' . ucfirst($format);
// Check if a parser exists for the format.
if (class_exists($class)) {
self::$instances[$format] = new $class();
return self::$instances[$format];
}
// Throw invalid format exception.
throw new \Exception(Text::sprintf('COM_FINDER_INDEXER_INVALID_PARSER', $format));
}
/**
* Method to parse input and extract the plain text. Because this method is
* called from both inside and outside the indexer, it needs to be able to
* batch out its parsing functionality to deal with the inefficiencies of
* regular expressions. We will parse recursively in 2KB chunks.
*
* @param string $input The input to parse.
*
* @return string The plain text input.
*
* @since 2.5
*/
public function parse($input)
{
// If the input is less than 2KB we can parse it in one go.
if (\strlen($input) <= 2048) {
return $this->process($input);
}
// Input is longer than 2Kb so parse it in chunks of 2Kb or less.
$start = 0;
$end = \strlen($input);
$chunk = 2048;
$return = null;
while ($start < $end) {
// Setup the string.
$string = substr($input, $start, $chunk);
// Find the last space character if we aren't at the end.
$ls = (($start + $chunk) < $end ? strrpos($string, ' ') : false);
// Truncate to the last space character (but include it in the string).
if ($ls !== false) {
$string = substr($string, 0, $ls + 1);
}
// Adjust the start position for the next iteration.
$start += $ls !== false ? $ls + 1 : $chunk;
// Parse the chunk.
$return .= $this->process($string);
}
return $return;
}
/**
* Method to process input and extract the plain text.
*
* @param string $input The input to process.
*
* @return string The plain text input.
*
* @since 2.5
*/
abstract protected function process($input);
}

View File

@ -0,0 +1,158 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Indexer\Parser;
use Joomla\Component\Finder\Administrator\Indexer\Parser;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* HTML Parser class for the Finder indexer package.
*
* @since 2.5
*/
class Html extends Parser
{
/**
* Method to parse input and extract the plain text. Because this method is
* called from both inside and outside the indexer, it needs to be able to
* batch out its parsing functionality to deal with the inefficiencies of
* regular expressions. We will parse recursively in 2KB chunks.
*
* @param string $input The input to parse.
*
* @return string The plain text input.
*
* @since 2.5
*/
public function parse($input)
{
// Strip invalid UTF-8 characters.
$oldSetting = \ini_get('mbstring.substitute_character');
ini_set('mbstring.substitute_character', 'none');
$input = mb_convert_encoding($input, 'UTF-8', 'UTF-8');
ini_set('mbstring.substitute_character', $oldSetting);
// Remove anything between <head> and </head> tags. Do this first
// because there might be <script> or <style> tags nested inside.
$input = $this->removeBlocks($input, '<head>', '</head>');
// Convert <style> and <noscript> tags to <script> tags
// so we can remove them efficiently.
$search = [
'<style', '</style',
'<noscript', '</noscript',
];
$replace = [
'<script', '</script',
'<script', '</script',
];
$input = str_replace($search, $replace, $input);
// Strip all script blocks.
$input = $this->removeBlocks($input, '<script', '</script>');
// Decode HTML entities.
$input = html_entity_decode($input, ENT_QUOTES, 'UTF-8');
// Convert entities equivalent to spaces to actual spaces.
$input = str_replace(['&nbsp;', '&#160;'], ' ', $input);
// Add a space before both the OPEN and CLOSE tags of BLOCK and LINE BREAKING elements,
// e.g. 'all<h1><em>m</em>obile List</h1>' will become 'all mobile List'
$input = preg_replace('/(<|<\/)(' .
'address|article|aside|blockquote|br|canvas|dd|div|dl|dt|' .
'fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|li|' .
'main|nav|noscript|ol|output|p|pre|section|table|tfoot|ul|video' .
')\b/i', ' $1$2', $input);
// Strip HTML tags.
$input = strip_tags($input);
return parent::parse($input);
}
/**
* Method to process HTML input and extract the plain text.
*
* @param string $input The input to process.
*
* @return string The plain text input.
*
* @since 2.5
*/
protected function process($input)
{
// Replace any amount of white space with a single space.
return preg_replace('#\s+#u', ' ', $input);
}
/**
* Method to remove blocks of text between a start and an end tag.
* Each block removed is effectively replaced by a single space.
*
* Note: The start tag and the end tag must be different.
* Note: Blocks must not be nested.
* Note: This method will function correctly with multi-byte strings.
*
* @param string $input String to be processed.
* @param string $startTag String representing the start tag.
* @param string $endTag String representing the end tag.
*
* @return string with blocks removed.
*
* @since 3.4
*/
private function removeBlocks($input, $startTag, $endTag)
{
$return = '';
$offset = 0;
$startTagLength = \strlen($startTag);
$endTagLength = \strlen($endTag);
// Find the first start tag.
$start = stripos($input, $startTag);
// If no start tags were found, return the string unchanged.
if ($start === false) {
return $input;
}
// Look for all blocks defined by the start and end tags.
while ($start !== false) {
// Accumulate the substring up to the start tag.
$return .= substr($input, $offset, $start - $offset) . ' ';
// Look for an end tag corresponding to the start tag.
$end = stripos($input, $endTag, $start + $startTagLength);
// If no corresponding end tag, leave the string alone.
if ($end === false) {
// Fix the offset so part of the string is not duplicated.
$offset = $start;
break;
}
// Advance the start position.
$offset = $end + $endTagLength;
// Look for the next start tag and loop.
$start = stripos($input, $startTag, $offset);
}
// Add in the final substring after the last end tag.
$return .= substr($input, $offset);
return $return;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Indexer\Parser;
use Joomla\Component\Finder\Administrator\Indexer\Parser;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* RTF Parser class for the Finder indexer package.
*
* @since 2.5
*/
class Rtf extends Parser
{
/**
* Method to process RTF input and extract the plain text.
*
* @param string $input The input to process.
*
* @return string The plain text input.
*
* @since 2.5
*/
protected function process($input)
{
// Remove embedded pictures.
$input = preg_replace('#{\\\pict[^}]*}#mi', '', $input);
// Remove control characters.
$input = str_replace(['{', '}', "\\\n"], [' ', ' ', "\n"], $input);
$input = preg_replace('#\\\([^;]+?);#m', ' ', $input);
$input = preg_replace('#\\\[\'a-zA-Z0-9]+#mi', ' ', $input);
return $input;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Indexer\Parser;
use Joomla\Component\Finder\Administrator\Indexer\Parser;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Text Parser class for the Finder indexer package.
*
* @since 2.5
*/
class Txt extends Parser
{
/**
* Method to process Text input and extract the plain text.
*
* @param string $input The input to process.
*
* @return string The plain text input.
*
* @since 2.5
*/
protected function process($input)
{
return $input;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,582 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Indexer;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Tree\ImmutableNodeInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Result class for the Finder indexer package.
*
* This class uses magic __get() and __set() methods to prevent properties
* being added that might confuse the system. All properties not explicitly
* declared will be pushed into the elements array and can be accessed
* explicitly using the getElement() method.
*
* @since 2.5
*/
class Result implements \Serializable
{
/**
* An array of extra result properties.
*
* @var array
* @since 2.5
*/
protected $elements = [];
/**
* This array tells the indexer which properties should be indexed and what
* weights to use for those properties.
*
* @var array
* @since 2.5
*/
protected $instructions = [
Indexer::TITLE_CONTEXT => ['title', 'subtitle', 'id'],
Indexer::TEXT_CONTEXT => ['summary', 'body'],
Indexer::META_CONTEXT => ['meta', 'list_price', 'sale_price'],
Indexer::PATH_CONTEXT => ['path', 'alias'],
Indexer::MISC_CONTEXT => ['comments'],
];
/**
* The indexer will use this data to create taxonomy mapping entries for
* the item so that it can be filtered by type, label, category,
* or whatever.
*
* @var array
* @since 2.5
*/
protected $taxonomy = [];
/**
* The content URL.
*
* @var string
* @since 2.5
*/
public $url;
/**
* The content route.
*
* @var string
* @since 2.5
*/
public $route;
/**
* The content title.
*
* @var string
* @since 2.5
*/
public $title;
/**
* The content description.
*
* @var string
* @since 2.5
*/
public $description;
/**
* The published state of the result.
*
* @var integer
* @since 2.5
*/
public $published;
/**
* The content published state.
*
* @var integer
* @since 2.5
*/
public $state;
/**
* The content access level.
*
* @var integer
* @since 2.5
*/
public $access;
/**
* The content language.
*
* @var string
* @since 2.5
*/
public $language = '*';
/**
* The publishing start date.
*
* @var string
* @since 2.5
*/
public $publish_start_date;
/**
* The publishing end date.
*
* @var string
* @since 2.5
*/
public $publish_end_date;
/**
* The generic start date.
*
* @var string
* @since 2.5
*/
public $start_date;
/**
* The generic end date.
*
* @var string
* @since 2.5
*/
public $end_date;
/**
* The item list price.
*
* @var mixed
* @since 2.5
*/
public $list_price;
/**
* The item sale price.
*
* @var mixed
* @since 2.5
*/
public $sale_price;
/**
* The content type id. This is set by the adapter.
*
* @var integer
* @since 2.5
*/
public $type_id;
/**
* The default language for content.
*
* @var string
* @since 3.0.2
*/
public $defaultLanguage;
/**
* Constructor
*
* @since 3.0.3
*/
public function __construct()
{
$this->defaultLanguage = ComponentHelper::getParams('com_languages')->get('site', 'en-GB');
}
/**
* The magic set method is used to push additional values into the elements
* array in order to preserve the cleanliness of the object.
*
* @param string $name The name of the element.
* @param mixed $value The value of the element.
*
* @return void
*
* @since 2.5
*/
public function __set($name, $value)
{
$this->setElement($name, $value);
}
/**
* The magic get method is used to retrieve additional element values from the elements array.
*
* @param string $name The name of the element.
*
* @return mixed The value of the element if set, null otherwise.
*
* @since 2.5
*/
public function __get($name)
{
return $this->getElement($name);
}
/**
* The magic isset method is used to check the state of additional element values in the elements array.
*
* @param string $name The name of the element.
*
* @return boolean True if set, false otherwise.
*
* @since 2.5
*/
public function __isset($name)
{
return isset($this->elements[$name]);
}
/**
* The magic unset method is used to unset additional element values in the elements array.
*
* @param string $name The name of the element.
*
* @return void
*
* @since 2.5
*/
public function __unset($name)
{
unset($this->elements[$name]);
}
/**
* Method to retrieve additional element values from the elements array.
*
* @param string $name The name of the element.
*
* @return mixed The value of the element if set, null otherwise.
*
* @since 2.5
*/
public function getElement($name)
{
// Get the element value if set.
if (\array_key_exists($name, $this->elements)) {
return $this->elements[$name];
}
return null;
}
/**
* Method to retrieve all elements.
*
* @return array The elements
*
* @since 3.8.3
*/
public function getElements()
{
return $this->elements;
}
/**
* Method to set additional element values in the elements array.
*
* @param string $name The name of the element.
* @param mixed $value The value of the element.
*
* @return void
*
* @since 2.5
*/
public function setElement($name, $value)
{
$this->elements[$name] = $value;
}
/**
* Method to get all processing instructions.
*
* @return array An array of processing instructions.
*
* @since 2.5
*/
public function getInstructions()
{
return $this->instructions;
}
/**
* Method to add a processing instruction for an item property.
*
* @param string $group The group to associate the property with.
* @param string $property The property to process.
*
* @return void
*
* @since 2.5
*/
public function addInstruction($group, $property)
{
// Check if the group exists. We can't add instructions for unknown groups.
// Check if the property exists in the group.
if (\array_key_exists($group, $this->instructions) && !\in_array($property, $this->instructions[$group], true)) {
// Add the property to the group.
$this->instructions[$group][] = $property;
}
}
/**
* Method to remove a processing instruction for an item property.
*
* @param string $group The group to associate the property with.
* @param string $property The property to process.
*
* @return void
*
* @since 2.5
*/
public function removeInstruction($group, $property)
{
// Check if the group exists. We can't remove instructions for unknown groups.
if (\array_key_exists($group, $this->instructions)) {
// Search for the property in the group.
$key = array_search($property, $this->instructions[$group]);
// If the property was found, remove it.
if ($key !== false) {
unset($this->instructions[$group][$key]);
}
}
}
/**
* Method to get the taxonomy maps for an item.
*
* @param string $branch The taxonomy branch to get. [optional]
*
* @return array An array of taxonomy maps.
*
* @since 2.5
*/
public function getTaxonomy($branch = null)
{
// Get the taxonomy branch if available.
if ($branch !== null && isset($this->taxonomy[$branch])) {
return $this->taxonomy[$branch];
}
return $this->taxonomy;
}
/**
* Method to add a taxonomy map for an item.
*
* @param string $branch The title of the taxonomy branch to add the node to.
* @param string $title The title of the taxonomy node.
* @param integer $state The published state of the taxonomy node. [optional]
* @param integer $access The access level of the taxonomy node. [optional]
* @param string $language The language of the taxonomy. [optional]
*
* @return void
*
* @since 2.5
*/
public function addTaxonomy($branch, $title, $state = 1, $access = 1, $language = '*')
{
// We can't add taxonomies with empty titles
if (!trim($title)) {
return;
}
// Filter the input.
$branch = preg_replace('#[^\pL\pM\pN\p{Pi}\p{Pf}\'+-.,_]+#mui', ' ', $branch);
// Create the taxonomy node.
$node = new \stdClass();
$node->title = $title;
$node->state = (int) $state;
$node->access = (int) $access;
$node->language = $language;
$node->nested = false;
// Add the node to the taxonomy branch.
$this->taxonomy[$branch][] = $node;
}
/**
* Method to add a nested taxonomy map for an item.
*
* @param string $branch The title of the taxonomy branch to add the node to.
* @param ImmutableNodeInterface $contentNode The node object.
* @param integer $state The published state of the taxonomy node. [optional]
* @param integer $access The access level of the taxonomy node. [optional]
* @param string $language The language of the taxonomy. [optional]
*
* @return void
*
* @since 4.0.0
*/
public function addNestedTaxonomy($branch, ImmutableNodeInterface $contentNode, $state = 1, $access = 1, $language = '*')
{
// We can't add taxonomies with empty titles
if (!trim($contentNode->title)) {
return;
}
// Filter the input.
$branch = preg_replace('#[^\pL\pM\pN\p{Pi}\p{Pf}\'+-.,_]+#mui', ' ', $branch);
// Create the taxonomy node.
$node = new \stdClass();
$node->title = $contentNode->title;
$node->state = (int) $state;
$node->access = (int) $access;
$node->language = $language;
$node->nested = true;
$node->node = $contentNode;
// Add the node to the taxonomy branch.
$this->taxonomy[$branch][] = $node;
}
/**
* Method to set the item language
*
* @return void
*
* @since 3.0
*/
public function setLanguage()
{
if ($this->language == '') {
$this->language = $this->defaultLanguage;
}
}
/**
* Helper function to serialise the data of a Result object
*
* @return string The serialised data
*
* @since 4.0.0
*/
public function serialize()
{
return serialize($this->__serialize());
}
/**
* Helper function to unserialise the data for this object
*
* @param string $serialized Serialised data to unserialise
*
* @return void
*
* @since 4.0.0
*/
public function unserialize($serialized): void
{
$this->__unserialize(unserialize($serialized));
}
/**
* Magic method used for serializing.
*
* @since 4.1.3
*/
public function __serialize(): array
{
$taxonomy = [];
foreach ($this->taxonomy as $branch => $nodes) {
$taxonomy[$branch] = [];
foreach ($nodes as $node) {
if ($node->nested) {
$n = clone $node;
unset($n->node);
$taxonomy[$branch][] = $n;
} else {
$taxonomy[$branch][] = $node;
}
}
}
// This order must match EXACTLY the order of the $properties in the self::__unserialize method
return [
$this->access,
$this->defaultLanguage,
$this->description,
$this->elements,
$this->end_date,
$this->instructions,
$this->language,
$this->list_price,
$this->publish_end_date,
$this->publish_start_date,
$this->published,
$this->route,
$this->sale_price,
$this->start_date,
$this->state,
$taxonomy,
$this->title,
$this->type_id,
$this->url,
];
}
/**
* Magic method used for unserializing.
*
* @since 4.1.3
*/
public function __unserialize(array $serialized): void
{
// This order must match EXACTLY the order of the array in the self::__serialize method
$properties = [
'access',
'defaultLanguage',
'description',
'elements',
'end_date',
'instructions',
'language',
'list_price',
'publish_end_date',
'publish_start_date',
'published',
'route',
'sale_price',
'start_date',
'state',
'taxonomy',
'title',
'type_id',
'url',
];
foreach ($properties as $k => $v) {
$this->$v = $serialized[$k];
}
foreach ($this->taxonomy as $nodes) {
foreach ($nodes as $node) {
$curTaxonomy = Taxonomy::getTaxonomy($node->id);
$node->state = $curTaxonomy->state;
$node->access = $curTaxonomy->access;
}
}
}
}

View File

@ -0,0 +1,514 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Indexer;
use Joomla\CMS\Factory;
use Joomla\CMS\Tree\NodeInterface;
use Joomla\Component\Finder\Administrator\Table\MapTable;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Taxonomy base class for the Finder indexer package.
*
* @since 2.5
*/
class Taxonomy
{
/**
* An internal cache of taxonomy data.
*
* @var object[]
* @since 4.0.0
*/
public static $taxonomies = [];
/**
* An internal cache of branch data.
*
* @var object[]
* @since 4.0.0
*/
public static $branches = [];
/**
* An internal cache of taxonomy node data for inserting it.
*
* @var object[]
* @since 2.5
*/
public static $nodes = [];
/**
* Method to add a branch to the taxonomy tree.
*
* @param string $title The title of the branch.
* @param integer $state The published state of the branch. [optional]
* @param integer $access The access state of the branch. [optional]
*
* @return integer The id of the branch.
*
* @since 2.5
* @throws \RuntimeException on database error.
*/
public static function addBranch($title, $state = 1, $access = 1)
{
$node = new \stdClass();
$node->title = $title;
$node->access = $access;
$node->parent_id = 1;
$node->language = '*';
return self::storeNode($node, 1);
}
/**
* Method to add a node to the taxonomy tree.
*
* @param string $branch The title of the branch to store the node in.
* @param string $title The title of the node.
* @param integer $state The published state of the node. [optional]
* @param integer $access The access state of the node. [optional]
* @param string $language The language of the node. [optional]
*
* @return integer The id of the node.
*
* @since 2.5
* @throws \RuntimeException on database error.
*/
public static function addNode($branch, $title, $state = 1, $access = 1, $language = '*')
{
if ($state != 1) {
return 0;
}
// Get the branch id, insert it if it does not exist.
$branchId = static::addBranch($branch);
$node = new \stdClass();
$node->title = $title;
$node->access = $access;
$node->parent_id = $branchId;
$node->language = $language;
return self::storeNode($node, $branchId);
}
/**
* Method to add a nested node to the taxonomy tree.
*
* @param string $branch The title of the branch to store the node in.
* @param NodeInterface $node The source-node of the taxonomy node.
* @param integer $state The published state of the node. [optional]
* @param integer $access The access state of the node. [optional]
* @param string $language The language of the node. [optional]
* @param integer $branchId ID of a branch if known. [optional]
*
* @return integer The id of the node.
*
* @since 4.0.0
*/
public static function addNestedNode($branch, NodeInterface $node, $state = 1, $access = 1, $language = '*', $branchId = null)
{
if ($state != 1) {
return 0;
}
if (!$branchId) {
// Get the branch id, insert it if it does not exist.
$branchId = static::addBranch($branch);
}
$parent = $node->getParent();
$pstate = $node->state ?? ($node->published ?? $state);
$paccess = $node->access ?? $access;
$planguage = $node->language ?? $language;
if ($parent && $parent->title != 'ROOT') {
$parentId = self::addNestedNode($branch, $parent, $pstate, $paccess, $planguage, $branchId);
} else {
$parentId = $branchId;
}
if (!$parentId) {
return 0;
}
$temp = new \stdClass();
$temp->title = $node->title;
$temp->access = $access;
$temp->parent_id = $parentId;
$temp->language = $language;
return self::storeNode($temp, $parentId);
}
/**
* A helper method to store a node in the taxonomy
*
* @param object $node The node data to include
* @param integer $parentId The parent id of the node to add.
*
* @return integer The id of the inserted node.
*
* @since 4.0.0
* @throws \RuntimeException
*/
protected static function storeNode($node, $parentId)
{
// Check to see if the node is in the cache.
if (isset(static::$nodes[$parentId . ':' . $node->title])) {
return static::$nodes[$parentId . ':' . $node->title]->id;
}
// Check to see if the node is in the table.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__finder_taxonomy'))
->where($db->quoteName('parent_id') . ' = ' . $db->quote($parentId))
->where($db->quoteName('title') . ' = ' . $db->quote($node->title))
->where($db->quoteName('language') . ' = ' . $db->quote($node->language));
$db->setQuery($query);
// Get the result.
$result = $db->loadObject();
// Check if the database matches the input data.
if ((bool) $result && $result->access == $node->access) {
// The data matches, add the item to the cache.
static::$nodes[$parentId . ':' . $node->title] = $result;
return static::$nodes[$parentId . ':' . $node->title]->id;
}
/*
* The database did not match the input. This could be because the
* state has changed or because the node does not exist. Let's figure
* out which case is true and deal with it.
* @todo: use factory?
*/
$nodeTable = new MapTable($db);
if (empty($result)) {
// Prepare the node object.
$nodeTable->title = $node->title;
$nodeTable->access = (int) $node->access;
$nodeTable->language = $node->language;
$nodeTable->setLocation((int) $parentId, 'last-child');
} else {
// Prepare the node object.
$nodeTable->id = (int) $result->id;
$nodeTable->title = $result->title;
$nodeTable->access = (int) $result->access;
$nodeTable->language = $node->language;
$nodeTable->setLocation($result->parent_id, 'last-child');
}
// Check the data.
if (!$nodeTable->check()) {
$error = $nodeTable->getError();
if ($error instanceof \Exception) {
// \Joomla\CMS\Table\NestedTable sets errors of exceptions, so in this case we can pass on more
// information
throw new \RuntimeException(
$error->getMessage(),
$error->getCode(),
$error
);
}
// Standard string returned. Probably from the \Joomla\CMS\Table\Table class
throw new \RuntimeException($error, 500);
}
// Store the data.
if (!$nodeTable->store()) {
$error = $nodeTable->getError();
if ($error instanceof \Exception) {
// \Joomla\CMS\Table\NestedTable sets errors of exceptions, so in this case we can pass on more
// information
throw new \RuntimeException(
$error->getMessage(),
$error->getCode(),
$error
);
}
// Standard string returned. Probably from the \Joomla\CMS\Table\Table class
throw new \RuntimeException($error, 500);
}
$nodeTable->rebuildPath($nodeTable->id);
// Add the node to the cache.
static::$nodes[$parentId . ':' . $nodeTable->title] = (object) $nodeTable->getProperties();
return static::$nodes[$parentId . ':' . $nodeTable->title]->id;
}
/**
* Method to add a map entry between a link and a taxonomy node.
*
* @param integer $linkId The link to map to.
* @param integer $nodeId The node to map to.
*
* @return boolean True on success.
*
* @since 2.5
* @throws \RuntimeException on database error.
*/
public static function addMap($linkId, $nodeId)
{
// Insert the map.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('link_id'))
->from($db->quoteName('#__finder_taxonomy_map'))
->where($db->quoteName('link_id') . ' = ' . (int) $linkId)
->where($db->quoteName('node_id') . ' = ' . (int) $nodeId);
$db->setQuery($query);
$db->execute();
$id = (int) $db->loadResult();
if (!$id) {
$map = new \stdClass();
$map->link_id = (int) $linkId;
$map->node_id = (int) $nodeId;
$db->insertObject('#__finder_taxonomy_map', $map);
}
return true;
}
/**
* Method to get the title of all taxonomy branches.
*
* @return array An array of branch titles.
*
* @since 2.5
* @throws \RuntimeException on database error.
*/
public static function getBranchTitles()
{
$db = Factory::getDbo();
// Set user variables
$groups = implode(',', Factory::getUser()->getAuthorisedViewLevels());
// Create a query to get the taxonomy branch titles.
$query = $db->getQuery(true)
->select($db->quoteName('title'))
->from($db->quoteName('#__finder_taxonomy'))
->where($db->quoteName('parent_id') . ' = 1')
->where($db->quoteName('state') . ' = 1')
->where($db->quoteName('access') . ' IN (' . $groups . ')');
// Get the branch titles.
$db->setQuery($query);
return $db->loadColumn();
}
/**
* Method to find a taxonomy node in a branch.
*
* @param string $branch The branch to search.
* @param string $title The title of the node.
*
* @return mixed Integer id on success, null on no match.
*
* @since 2.5
* @throws \RuntimeException on database error.
*/
public static function getNodeByTitle($branch, $title)
{
$db = Factory::getDbo();
// Set user variables
$groups = implode(',', Factory::getUser()->getAuthorisedViewLevels());
// Create a query to get the node.
$query = $db->getQuery(true)
->select('t1.*')
->from($db->quoteName('#__finder_taxonomy') . ' AS t1')
->join('INNER', $db->quoteName('#__finder_taxonomy') . ' AS t2 ON t2.id = t1.parent_id')
->where('t1.access IN (' . $groups . ')')
->where('t1.state = 1')
->where('t1.title LIKE ' . $db->quote($db->escape($title) . '%'))
->where('t2.access IN (' . $groups . ')')
->where('t2.state = 1')
->where('t2.title = ' . $db->quote($branch));
// Get the node.
$query->setLimit(1);
$db->setQuery($query);
return $db->loadObject();
}
/**
* Method to remove map entries for a link.
*
* @param integer $linkId The link to remove.
*
* @return boolean True on success.
*
* @since 2.5
* @throws \RuntimeException on database error.
*/
public static function removeMaps($linkId)
{
// Delete the maps.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->delete($db->quoteName('#__finder_taxonomy_map'))
->where($db->quoteName('link_id') . ' = ' . (int) $linkId);
$db->setQuery($query);
$db->execute();
return true;
}
/**
* Method to remove orphaned taxonomy maps
*
* @return integer The number of deleted rows.
*
* @since 4.2.0
* @throws \RuntimeException on database error.
*/
public static function removeOrphanMaps()
{
// Delete all orphaned maps
$db = Factory::getDbo();
$query2 = $db->getQuery(true)
->select($db->quoteName('link_id'))
->from($db->quoteName('#__finder_links'));
$query = $db->getQuery(true)
->delete($db->quoteName('#__finder_taxonomy_map'))
->where($db->quoteName('link_id') . ' NOT IN (' . $query2 . ')');
$db->setQuery($query);
$db->execute();
$count = $db->getAffectedRows();
return $count;
}
/**
* Method to remove orphaned taxonomy nodes and branches.
*
* @return integer The number of deleted rows.
*
* @since 2.5
* @throws \RuntimeException on database error.
*/
public static function removeOrphanNodes()
{
// Delete all orphaned nodes.
$affectedRows = 0;
$db = Factory::getDbo();
$nodeTable = new MapTable($db);
$query = $db->getQuery(true);
$query->select($db->quoteName('t.id'))
->from($db->quoteName('#__finder_taxonomy', 't'))
->join('LEFT', $db->quoteName('#__finder_taxonomy_map', 'm') . ' ON ' . $db->quoteName('m.node_id') . '=' . $db->quoteName('t.id'))
->where($db->quoteName('t.parent_id') . ' > 1 ')
->where('t.lft + 1 = t.rgt')
->where($db->quoteName('m.link_id') . ' IS NULL');
do {
$db->setQuery($query);
$nodes = $db->loadColumn();
foreach ($nodes as $node) {
$nodeTable->delete($node);
$affectedRows++;
}
} while ($nodes);
return $affectedRows;
}
/**
* Get a taxonomy based on its id or all taxonomies
*
* @param integer $id Id of the taxonomy
*
* @return object|object[] A taxonomy object or an array of all taxonomies
*
* @since 4.0.0
*/
public static function getTaxonomy($id = 0)
{
if (!\count(self::$taxonomies)) {
$db = Factory::getDbo();
$query = $db->getQuery(true);
$query->select(['id','parent_id','lft','rgt','level','path','title','alias','state','access','language'])
->from($db->quoteName('#__finder_taxonomy'))
->order($db->quoteName('lft'));
$db->setQuery($query);
self::$taxonomies = $db->loadObjectList('id');
}
if ($id == 0) {
return self::$taxonomies;
}
if (isset(self::$taxonomies[$id])) {
return self::$taxonomies[$id];
}
return false;
}
/**
* Get a taxonomy branch object based on its title or all branches
*
* @param string $title Title of the branch
*
* @return object|object[] The object with the branch data or an array of all branches
*
* @since 4.0.0
*/
public static function getBranch($title = '')
{
if (!\count(self::$branches)) {
$taxonomies = self::getTaxonomy();
foreach ($taxonomies as $t) {
if ($t->level == 1) {
self::$branches[$t->title] = $t;
}
}
}
if ($title == '') {
return self::$branches;
}
if (isset(self::$branches[$title])) {
return self::$branches[$title];
}
return false;
}
}

View File

@ -0,0 +1,186 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Indexer;
use Joomla\String\StringHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Token class for the Finder indexer package.
*
* @since 2.5
*/
class Token
{
/**
* This is the term that will be referenced in the terms table and the
* mapping tables.
*
* @var string
* @since 2.5
*/
public $term;
/**
* The stem is used to match the root term and produce more potential
* matches when searching the index.
*
* @var string
* @since 2.5
*/
public $stem;
/**
* If the token is numeric, it is likely to be short and uncommon so the
* weight is adjusted to compensate for that situation.
*
* @var boolean
* @since 2.5
*/
public $numeric;
/**
* If the token is a common term, the weight is adjusted to compensate for
* the higher frequency of the term in relation to other terms.
*
* @var boolean
* @since 2.5
*/
public $common;
/**
* Flag for phrase tokens.
*
* @var boolean
* @since 2.5
*/
public $phrase;
/**
* The length is used to calculate the weight of the token.
*
* @var integer
* @since 2.5
*/
public $length;
/**
* The weight is calculated based on token size and whether the token is
* considered a common term.
*
* @var integer
* @since 2.5
*/
public $weight;
/**
* The simple language identifier for the token.
*
* @var string
* @since 2.5
*/
public $language;
/**
* The container for matches.
*
* @var array
* @since 3.8.12
*/
public $matches = [];
/**
* Is derived token (from individual words)
*
* @var boolean
* @since 3.8.12
*/
public $derived;
/**
* The suggested term
*
* @var string
* @since 3.8.12
*/
public $suggestion;
/**
* The token required flag
*
* @var boolean
* @since 4.3.0
*/
public $required;
/**
* Method to construct the token object.
*
* @param mixed $term The term as a string for words or an array for phrases.
* @param string $lang The simple language identifier.
* @param string $spacer The space separator for phrases. [optional]
*
* @since 2.5
*/
public function __construct($term, $lang, $spacer = ' ')
{
if (!$lang) {
$this->language = '*';
} else {
$this->language = $lang;
}
// Tokens can be a single word or an array of words representing a phrase.
if (\is_array($term)) {
// Populate the token instance.
$langs = array_fill(0, \count($term), $lang);
$this->term = implode($spacer, $term);
$this->stem = implode($spacer, array_map([Helper::class, 'stem'], $term, $langs));
$this->numeric = false;
$this->common = false;
$this->phrase = true;
$this->length = StringHelper::strlen($this->term);
/*
* Calculate the weight of the token.
*
* 1. Length of the token up to 30 and divide by 30, add 1.
* 2. Round weight to 4 decimal points.
*/
$this->weight = (min($this->length, 30) / 30) + 1;
$this->weight = round($this->weight, 4);
} else {
// Populate the token instance.
$this->term = $term;
$this->stem = Helper::stem($this->term, $lang);
$this->numeric = (is_numeric($this->term) || (bool) preg_match('#^[0-9,.\-\+]+$#', $this->term));
$this->common = $this->numeric ? false : Helper::isCommon($this->term, $lang);
$this->phrase = false;
$this->length = StringHelper::strlen($this->term);
/*
* Calculate the weight of the token.
*
* 1. Length of the token up to 15 and divide by 15.
* 2. If common term, divide weight by 8.
* 3. If numeric, multiply weight by 1.5.
* 4. Round weight to 4 decimal points.
*/
$this->weight = min($this->length, 15) / 15;
$this->weight = $this->common === true ? $this->weight / 8 : $this->weight;
$this->weight = $this->numeric === true ? $this->weight * 1.5 : $this->weight;
$this->weight = round($this->weight, 4);
}
}
}

View File

@ -0,0 +1,154 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Model;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\Component\Finder\Administrator\Table\FilterTable;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Filter model class for Finder.
*
* @since 2.5
*/
class FilterModel extends AdminModel
{
/**
* The prefix to use with controller messages.
*
* @var string
* @since 2.5
*/
protected $text_prefix = 'COM_FINDER';
/**
* Model context string.
*
* @var string
* @since 2.5
*/
protected $context = 'com_finder.filter';
/**
* Custom clean cache method.
*
* @param string $group The component name. [optional]
* @param integer $clientId No longer used, will be removed without replacement
* @deprecated 4.3 will be removed in 6.0
*
* @return void
*
* @since 2.5
*/
protected function cleanCache($group = 'com_finder', $clientId = 0)
{
parent::cleanCache($group);
}
/**
* Method to get the filter data.
*
* @return FilterTable|boolean The filter data or false on a failure.
*
* @since 2.5
*/
public function getFilter()
{
$filter_id = (int) $this->getState('filter.id');
// Get a FinderTableFilter instance.
$filter = $this->getTable();
// Attempt to load the row.
$return = $filter->load($filter_id);
// Check for a database error.
if ($return === false && $filter->getError()) {
$this->setError($filter->getError());
return false;
}
// Process the filter data.
if (!empty($filter->data)) {
$filter->data = explode(',', $filter->data);
} elseif (empty($filter->data)) {
$filter->data = [];
}
return $filter;
}
/**
* Method to get the record form.
*
* @param array $data Data for the form. [optional]
* @param boolean $loadData True if the form is to load its own data (default case), false if not. [optional]
*
* @return Form|boolean A Form object on success, false on failure
*
* @since 2.5
*/
public function getForm($data = [], $loadData = true)
{
// Get the form.
$form = $this->loadForm('com_finder.filter', 'filter', ['control' => 'jform', 'load_data' => $loadData]);
if (empty($form)) {
return false;
}
return $form;
}
/**
* Method to get the data that should be injected in the form.
*
* @return mixed The data for the form.
*
* @since 2.5
*/
protected function loadFormData()
{
// Check the session for previously entered form data.
$data = Factory::getApplication()->getUserState('com_finder.edit.filter.data', []);
if (empty($data)) {
$data = $this->getItem();
}
$this->preprocessData('com_finder.filter', $data);
return $data;
}
/**
* Method to get the total indexed items
*
* @return integer The count of indexed items
*
* @since 3.5
*/
public function getTotal()
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('MAX(link_id)')
->from('#__finder_links');
return $db->setQuery($query)->loadResult();
}
}

View File

@ -0,0 +1,138 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Filters model class for Finder.
*
* @since 2.5
*/
class FiltersModel extends ListModel
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param ?MVCFactoryInterface $factory The factory.
*
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
* @since 3.7
*/
public function __construct($config = [], ?MVCFactoryInterface $factory = null)
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'filter_id', 'a.filter_id',
'title', 'a.title',
'state', 'a.state',
'created_by_alias', 'a.created_by_alias',
'created', 'a.created',
'map_count', 'a.map_count',
];
}
parent::__construct($config, $factory);
}
/**
* Build an SQL query to load the list data.
*
* @return QueryInterface
*
* @since 2.5
*/
protected function getListQuery()
{
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Select all fields from the table.
$query->select('a.*')
->from($db->quoteName('#__finder_filters', 'a'));
// Join over the users for the checked out user.
$query->select($db->quoteName('uc.name', 'editor'))
->join('LEFT', $db->quoteName('#__users', 'uc') . ' ON ' . $db->quoteName('uc.id') . ' = ' . $db->quoteName('a.checked_out'));
// Join over the users for the author.
$query->select($db->quoteName('ua.name', 'user_name'))
->join('LEFT', $db->quoteName('#__users', 'ua') . ' ON ' . $db->quoteName('ua.id') . ' = ' . $db->quoteName('a.created_by'));
// Check for a search filter.
if ($search = $this->getState('filter.search')) {
$search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%'));
$query->where($db->quoteName('a.title') . ' LIKE ' . $search);
}
// If the model is set to check item state, add to the query.
$state = $this->getState('filter.state');
if (is_numeric($state)) {
$query->where($db->quoteName('a.state') . ' = ' . (int) $state);
}
// Add the list ordering clause.
$query->order($db->escape($this->getState('list.ordering', 'a.title') . ' ' . $db->escape($this->getState('list.direction', 'ASC'))));
return $query;
}
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id. [optional]
*
* @return string A store id.
*
* @since 2.5
*/
protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('filter.search');
$id .= ':' . $this->getState('filter.state');
return parent::getStoreId($id);
}
/**
* Method to auto-populate the model state. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field. [optional]
* @param string $direction An optional direction. [optional]
*
* @return void
*
* @since 2.5
*/
protected function populateState($ordering = 'a.title', $direction = 'asc')
{
// Load the parameters.
$params = ComponentHelper::getParams('com_finder');
$this->setState('params', $params);
// List state information.
parent::populateState($ordering, $direction);
}
}

View File

@ -0,0 +1,460 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\QueryInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Index model class for Finder.
*
* @since 2.5
*/
class IndexModel extends ListModel
{
/**
* The event to trigger after deleting the data.
*
* @var string
* @since 2.5
*/
protected $event_after_delete = 'onContentAfterDelete';
/**
* The event to trigger before deleting the data.
*
* @var string
* @since 2.5
*/
protected $event_before_delete = 'onContentBeforeDelete';
/**
* The event to trigger after purging the data.
*
* @var string
* @since 4.0.0
*/
protected $event_after_purge = 'onFinderIndexAfterPurge';
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param ?MVCFactoryInterface $factory The factory.
*
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
* @since 3.7
*/
public function __construct($config = [], ?MVCFactoryInterface $factory = null)
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'state', 'published', 'l.published',
'title', 'l.title',
'type', 'type_id', 'l.type_id',
't.title', 't_title',
'url', 'l.url',
'language', 'l.language',
'indexdate', 'l.indexdate',
'content_map',
];
}
parent::__construct($config, $factory);
}
/**
* Method to test whether a record can be deleted.
*
* @param object $record A record object.
*
* @return boolean True if allowed to delete the record. Defaults to the permission for the component.
*
* @since 2.5
*/
protected function canDelete($record)
{
return $this->getCurrentUser()->authorise('core.delete', $this->option);
}
/**
* Method to test whether a record can have its state changed.
*
* @param object $record A record object.
*
* @return boolean True if allowed to change the state of the record. Defaults to the permission for the component.
*
* @since 2.5
*/
protected function canEditState($record)
{
return $this->getCurrentUser()->authorise('core.edit.state', $this->option);
}
/**
* Method to delete one or more records.
*
* @param array $pks An array of record primary keys.
*
* @return boolean True if successful, false if an error occurs.
*
* @since 2.5
*/
public function delete(&$pks)
{
$pks = (array) $pks;
$table = $this->getTable();
// Include the content plugins for the on delete events.
PluginHelper::importPlugin('content');
// Iterate the items to delete each one.
foreach ($pks as $i => $pk) {
if ($table->load($pk)) {
if ($this->canDelete($table)) {
$context = $this->option . '.' . $this->name;
// Trigger the onContentBeforeDelete event.
$result = Factory::getApplication()->triggerEvent($this->event_before_delete, [$context, $table]);
if (\in_array(false, $result, true)) {
$this->setError($table->getError());
return false;
}
if (!$table->delete($pk)) {
$this->setError($table->getError());
return false;
}
// Trigger the onContentAfterDelete event.
Factory::getApplication()->triggerEvent($this->event_after_delete, [$context, $table]);
} else {
// Prune items that you can't change.
unset($pks[$i]);
$error = $this->getError();
if ($error) {
$this->setError($error);
} else {
$this->setError(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'));
}
}
} else {
$this->setError($table->getError());
return false;
}
}
// Clear the component's cache
$this->cleanCache();
return true;
}
/**
* Build an SQL query to load the list data.
*
* @return QueryInterface
*
* @since 2.5
*/
protected function getListQuery()
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('l.*')
->select($db->quoteName('t.title', 't_title'))
->from($db->quoteName('#__finder_links', 'l'))
->join('INNER', $db->quoteName('#__finder_types', 't') . ' ON ' . $db->quoteName('t.id') . ' = ' . $db->quoteName('l.type_id'));
// Check the type filter.
$type = $this->getState('filter.type');
// Join over the language
$query->select('la.title AS language_title, la.image AS language_image')
->join('LEFT', $db->quoteName('#__languages') . ' AS la ON la.lang_code = l.language');
if (is_numeric($type)) {
$query->where($db->quoteName('l.type_id') . ' = ' . (int) $type);
}
// Check the map filter.
$contentMapId = $this->getState('filter.content_map');
if (is_numeric($contentMapId)) {
$query->join('INNER', $db->quoteName('#__finder_taxonomy_map', 'm') . ' ON ' . $db->quoteName('m.link_id') . ' = ' . $db->quoteName('l.link_id'))
->where($db->quoteName('m.node_id') . ' = ' . (int) $contentMapId);
}
// Check for state filter.
$state = $this->getState('filter.state');
if (is_numeric($state)) {
$query->where($db->quoteName('l.published') . ' = ' . (int) $state);
}
// Filter on the language.
if ($language = $this->getState('filter.language')) {
$query->where($db->quoteName('l.language') . ' = ' . $db->quote($language));
}
// Check the search phrase.
$search = $this->getState('filter.search');
if (!empty($search)) {
$search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%'));
$orSearchSql = $db->quoteName('l.title') . ' LIKE ' . $search . ' OR ' . $db->quoteName('l.url') . ' LIKE ' . $search;
// Filter by indexdate only if $search doesn't contains non-ascii characters
if (!preg_match('/[^\x00-\x7F]/', $search)) {
$orSearchSql .= ' OR ' . $query->castAsChar($db->quoteName('l.indexdate')) . ' LIKE ' . $search;
}
$query->where('(' . $orSearchSql . ')');
}
// Handle the list ordering.
$listOrder = $this->getState('list.ordering', 'l.title');
$listDir = $this->getState('list.direction', 'ASC');
if ($listOrder === 't.title') {
$ordering = $db->quoteName('t.title') . ' ' . $db->escape($listDir) . ', ' . $db->quoteName('l.title') . ' ' . $db->escape($listDir);
} else {
$ordering = $db->escape($listOrder) . ' ' . $db->escape($listDir);
}
$query->order($ordering);
return $query;
}
/**
* Method to get the state of the Smart Search Plugins.
*
* @return array Array of relevant plugins and whether they are enabled or not.
*
* @since 2.5
*/
public function getPluginState()
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('name, enabled')
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' IN (' . $db->quote('system') . ',' . $db->quote('content') . ')')
->where($db->quoteName('element') . ' = ' . $db->quote('finder'));
$db->setQuery($query);
return $db->loadObjectList('name');
}
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id. [optional]
*
* @return string A store id.
*
* @since 2.5
*/
protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('filter.search');
$id .= ':' . $this->getState('filter.state');
$id .= ':' . $this->getState('filter.type');
$id .= ':' . $this->getState('filter.content_map');
return parent::getStoreId($id);
}
/**
* Gets the total of indexed items.
*
* @return integer The total of indexed items.
*
* @since 3.6.0
*/
public function getTotalIndexed()
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('COUNT(link_id)')
->from($db->quoteName('#__finder_links'));
$db->setQuery($query);
return (int) $db->loadResult();
}
/**
* Returns a Table object, always creating it.
*
* @param string $type The table type to instantiate. [optional]
* @param string $prefix A prefix for the table class name. [optional]
* @param array $config Configuration array for model. [optional]
*
* @return \Joomla\CMS\Table\Table A database object
*
* @since 2.5
*/
public function getTable($type = 'Link', $prefix = 'Administrator', $config = [])
{
return parent::getTable($type, $prefix, $config);
}
/**
* Method to purge the index, deleting all links.
*
* @return boolean True on success, false on failure.
*
* @since 2.5
* @throws \Exception on database error
*/
public function purge()
{
$db = $this->getDatabase();
// Truncate the links table.
$db->truncateTable('#__finder_links');
// Truncate the links terms tables.
$db->truncateTable('#__finder_links_terms');
// Truncate the terms table.
$db->truncateTable('#__finder_terms');
// Truncate the taxonomy map table.
$db->truncateTable('#__finder_taxonomy_map');
// Truncate the taxonomy table and insert the root node.
$db->truncateTable('#__finder_taxonomy');
$root = (object) [
'id' => 1,
'parent_id' => 0,
'lft' => 0,
'rgt' => 1,
'level' => 0,
'path' => '',
'title' => 'ROOT',
'alias' => 'root',
'state' => 1,
'access' => 1,
'language' => '*',
];
$db->insertObject('#__finder_taxonomy', $root);
// Truncate the tokens tables.
$db->truncateTable('#__finder_tokens');
// Truncate the tokens aggregate table.
$db->truncateTable('#__finder_tokens_aggregate');
// Include the finder plugins for the on purge events.
PluginHelper::importPlugin('finder');
Factory::getApplication()->triggerEvent($this->event_after_purge);
return true;
}
/**
* Method to auto-populate the model state. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field. [optional]
* @param string $direction An optional direction. [optional]
*
* @return void
*
* @since 2.5
*/
protected function populateState($ordering = 'l.title', $direction = 'asc')
{
// Load the parameters.
$params = ComponentHelper::getParams('com_finder');
$this->setState('params', $params);
// List state information.
parent::populateState($ordering, $direction);
}
/**
* Method to change the published state of one or more records.
*
* @param array $pks A list of the primary keys to change.
* @param integer $value The value of the published state. [optional]
*
* @return boolean True on success.
*
* @since 2.5
*/
public function publish(&$pks, $value = 1)
{
$user = $this->getCurrentUser();
$table = $this->getTable();
$pks = (array) $pks;
// Include the content plugins for the change of state event.
PluginHelper::importPlugin('content');
// Access checks.
foreach ($pks as $i => $pk) {
$table->reset();
if ($table->load($pk) && !$this->canEditState($table)) {
// Prune items that you can't change.
unset($pks[$i]);
$this->setError(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
return false;
}
}
// Attempt to change the state of the records.
if (!$table->publish($pks, $value, $user->id)) {
$this->setError($table->getError());
return false;
}
$context = $this->option . '.' . $this->name;
// Trigger the onContentChangeState event.
$result = Factory::getApplication()->triggerEvent('onContentChangeState', [$context, $pks, $value]);
if (\in_array(false, $result, true)) {
$this->setError($table->getError());
return false;
}
// Clear the component's cache
$this->cleanCache();
return true;
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Model;
use Joomla\CMS\Form\Form;
use Joomla\CMS\MVC\Model\FormModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Indexer model class for Finder.
*
* @since 2.5
*/
class IndexerModel extends FormModel
{
/**
* Method for getting a form.
*
* @param array $data Data for the form.
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
*
* @return Form
*
* @since 5.0.0
*
* @throws \Exception
*/
public function getForm($data = [], $loadData = true)
{
// Get the form.
$form = $this->loadForm('com_finder.indexer', 'indexer', ['control' => '', 'load_data' => $loadData]);
if (empty($form)) {
return false;
}
return $form;
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Model;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\Database\ParameterType;
/**
* Index Item model class for Finder.
*
* @since 5.0.0
*/
class ItemModel extends BaseDatabaseModel
{
/**
* Stock method to auto-populate the model state.
*
* @return void
*
* @since 5.0.0
*/
protected function populateState()
{
// Get the pk of the record from the request.
$pk = Factory::getApplication()->getInput()->getInt('id');
$this->setState('item.link_id', $pk);
}
/**
* Get a finder link object
*
* @return object
*
* @since 5.0.0
*/
public function getItem()
{
$link_id = (int) $this->getState('item.link_id');
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__finder_links', 'l'))
->where($db->quoteName('l.link_id') . ' = :link_id')
->bind(':link_id', $link_id, ParameterType::INTEGER);
$db->setQuery($query);
return $db->loadObject();
}
/**
* Get terms associated with a finder link
*
* @return object[]
*
* @since 5.0.0
*/
public function getTerms()
{
$link_id = (int) $this->getState('item.link_id');
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('t.*, l.*')
->from($db->quoteName('#__finder_links_terms', 'l'))
->leftJoin($db->quoteName('#__finder_terms', 't') . ' ON ' . $db->quoteName('t.term_id') . ' = ' . $db->quoteName('l.term_id'))
->where($db->quoteName('l.link_id') . ' = :link_id')
->order('l.weight')
->bind(':link_id', $link_id, ParameterType::INTEGER);
$db->setQuery($query);
return $db->loadObjectList();
}
/**
* Get taxonomies associated with a finder link
*
* @return \stdClass[]
*
* @since 5.0.0
*/
public function getTaxonomies()
{
$link_id = (int) $this->getState('item.link_id');
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('t.*, m.*')
->from($db->quoteName('#__finder_taxonomy_map', 'm'))
->leftJoin($db->quoteName('#__finder_taxonomy', 't') . ' ON ' . $db->quoteName('t.id') . ' = ' . $db->quoteName('m.node_id'))
->where($db->quoteName('m.link_id') . ' = :link_id')
->order('t.title')
->bind(':link_id', $link_id, ParameterType::INTEGER);
$db->setQuery($query);
return $db->loadObjectList();
}
}

View File

@ -0,0 +1,398 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseQuery;
use Joomla\Database\QueryInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Maps model for the Finder package.
*
* @since 2.5
*/
class MapsModel extends ListModel
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param ?MVCFactoryInterface $factory The factory.
*
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
* @since 3.7
*/
public function __construct($config = [], ?MVCFactoryInterface $factory = null)
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'state', 'a.state',
'title', 'a.title',
'branch',
'branch_title', 'd.branch_title',
'level', 'd.level',
'language', 'a.language',
];
}
parent::__construct($config, $factory);
}
/**
* Method to test whether a record can be deleted.
*
* @param object $record A record object.
*
* @return boolean True if allowed to delete the record. Defaults to the permission for the component.
*
* @since 2.5
*/
protected function canDelete($record)
{
return $this->getCurrentUser()->authorise('core.delete', $this->option);
}
/**
* Method to test whether a record can have its state changed.
*
* @param object $record A record object.
*
* @return boolean True if allowed to change the state of the record. Defaults to the permission for the component.
*
* @since 2.5
*/
protected function canEditState($record)
{
return $this->getCurrentUser()->authorise('core.edit.state', $this->option);
}
/**
* Method to delete one or more records.
*
* @param array $pks An array of record primary keys.
*
* @return boolean True if successful, false if an error occurs.
*
* @since 2.5
*/
public function delete(&$pks)
{
$pks = (array) $pks;
$table = $this->getTable();
// Include the content plugins for the on delete events.
PluginHelper::importPlugin('content');
// Iterate the items to check if all of them exist.
foreach ($pks as $i => $pk) {
if (!$table->load($pk)) {
// Item is not in the table.
$this->setError($table->getError());
return false;
}
}
// Iterate the items to delete each one.
foreach ($pks as $i => $pk) {
if ($table->load($pk)) {
if ($this->canDelete($table)) {
$context = $this->option . '.' . $this->name;
// Trigger the onContentBeforeDelete event.
$result = Factory::getApplication()->triggerEvent('onContentBeforeDelete', [$context, $table]);
if (\in_array(false, $result, true)) {
$this->setError($table->getError());
return false;
}
if (!$table->delete($pk)) {
$this->setError($table->getError());
return false;
}
// Trigger the onContentAfterDelete event.
Factory::getApplication()->triggerEvent('onContentAfterDelete', [$context, $table]);
} else {
// Prune items that you can't change.
unset($pks[$i]);
$error = $this->getError();
if ($error) {
$this->setError($error);
} else {
$this->setError(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'));
}
}
}
}
// Clear the component's cache
$this->cleanCache();
return true;
}
/**
* Build an SQL query to load the list data.
*
* @return QueryInterface
*
* @since 2.5
*/
protected function getListQuery()
{
$db = $this->getDatabase();
// Select all fields from the table.
$query = $db->getQuery(true)
->select('a.id, a.parent_id, a.lft, a.rgt, a.level, a.path, a.title, a.alias, a.state, a.access, a.language')
->from($db->quoteName('#__finder_taxonomy', 'a'))
->where('a.parent_id != 0');
// Join to get the branch title
$query->select([$db->quoteName('b.id', 'branch_id'), $db->quoteName('b.title', 'branch_title')])
->leftJoin($db->quoteName('#__finder_taxonomy', 'b') . ' ON b.level = 1 AND b.lft <= a.lft AND a.rgt <= b.rgt');
// Join to get the map links.
$stateQuery = $db->getQuery(true)
->select('m.node_id')
->select('COUNT(NULLIF(l.published, 0)) AS count_published')
->select('COUNT(NULLIF(l.published, 1)) AS count_unpublished')
->from($db->quoteName('#__finder_taxonomy_map', 'm'))
->leftJoin($db->quoteName('#__finder_links', 'l') . ' ON l.link_id = m.link_id')
->group('m.node_id');
$query->select('COALESCE(s.count_published, 0) AS count_published');
$query->select('COALESCE(s.count_unpublished, 0) AS count_unpublished');
$query->leftJoin('(' . $stateQuery . ') AS s ON s.node_id = a.id');
// If the model is set to check item state, add to the query.
$state = $this->getState('filter.state');
if (is_numeric($state)) {
$query->where('a.state = ' . (int) $state);
}
// Filter over level.
$level = $this->getState('filter.level');
if (is_numeric($level) && (int) $level === 1) {
$query->where('a.parent_id = 1');
}
// Filter the maps over the branch if set.
$branchId = $this->getState('filter.branch');
if (is_numeric($branchId)) {
$query->where('a.parent_id = ' . (int) $branchId);
}
// Filter the maps over the search string if set.
if ($search = $this->getState('filter.search')) {
$search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%'));
$query->where('a.title LIKE ' . $search);
}
// Add the list ordering clause.
$query->order($db->escape($this->getState('list.ordering', 'branch_title, a.lft')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
/**
* Returns a record count for the query.
*
* @param \Joomla\Database\DatabaseQuery|string
*
* @return integer Number of rows for query.
*
* @since 3.0
*/
protected function _getListCount($query)
{
$query = clone $query;
$query->clear('select')->clear('join')->clear('order')->clear('limit')->clear('offset')->select('COUNT(*)');
return (int) $this->getDatabase()->setQuery($query)->loadResult();
}
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id. [optional]
*
* @return string A store id.
*
* @since 2.5
*/
protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('filter.search');
$id .= ':' . $this->getState('filter.state');
$id .= ':' . $this->getState('filter.branch');
$id .= ':' . $this->getState('filter.level');
return parent::getStoreId($id);
}
/**
* Returns a Table object, always creating it.
*
* @param string $type The table type to instantiate. [optional]
* @param string $prefix A prefix for the table class name. [optional]
* @param array $config Configuration array for model. [optional]
*
* @return \Joomla\CMS\Table\Table A database object
*
* @since 2.5
*/
public function getTable($type = 'Map', $prefix = 'Administrator', $config = [])
{
return parent::getTable($type, $prefix, $config);
}
/**
* Method to auto-populate the model state. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field. [optional]
* @param string $direction An optional direction. [optional]
*
* @return void
*
* @since 2.5
*/
protected function populateState($ordering = 'branch_title, a.lft', $direction = 'ASC')
{
// Load the parameters.
$params = ComponentHelper::getParams('com_finder');
$this->setState('params', $params);
// List state information.
parent::populateState($ordering, $direction);
}
/**
* Method to change the published state of one or more records.
*
* @param array $pks A list of the primary keys to change.
* @param integer $value The value of the published state. [optional]
*
* @return boolean True on success.
*
* @since 2.5
*/
public function publish(&$pks, $value = 1)
{
$user = $this->getCurrentUser();
$table = $this->getTable();
$pks = (array) $pks;
// Include the content plugins for the change of state event.
PluginHelper::importPlugin('content');
// Access checks.
foreach ($pks as $i => $pk) {
$table->reset();
if ($table->load($pk) && !$this->canEditState($table)) {
// Prune items that you can't change.
unset($pks[$i]);
$this->setError(Text::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
return false;
}
}
// Attempt to change the state of the records.
if (!$table->publish($pks, $value, $user->id)) {
$this->setError($table->getError());
return false;
}
$context = $this->option . '.' . $this->name;
// Trigger the onContentChangeState event.
$result = Factory::getApplication()->triggerEvent('onContentChangeState', [$context, $pks, $value]);
if (\in_array(false, $result, true)) {
$this->setError($table->getError());
return false;
}
// Clear the component's cache
$this->cleanCache();
return true;
}
/**
* Method to purge all maps from the taxonomy.
*
* @return boolean Returns true on success, false on failure.
*
* @since 2.5
*/
public function purge()
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->delete($db->quoteName('#__finder_taxonomy'))
->where($db->quoteName('parent_id') . ' > 1');
$db->setQuery($query);
$db->execute();
$query->clear()
->delete($db->quoteName('#__finder_taxonomy_map'));
$db->setQuery($query);
$db->execute();
return true;
}
/**
* Manipulate the query to be used to evaluate if this is an Empty State to provide specific conditions for this extension.
*
* @return DatabaseQuery
*
* @since 4.0.0
*/
protected function getEmptyStateQuery()
{
$query = parent::getEmptyStateQuery();
$title = 'ROOT';
$query->where($this->getDatabase()->quoteName('title') . ' <> :title')
->bind(':title', $title);
return $query;
}
}

View File

@ -0,0 +1,174 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Model;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Methods supporting a list of search terms.
*
* @since 4.0.0
*/
class SearchesModel extends ListModel
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
* @param ?MVCFactoryInterface $factory The factory.
*
* @see \Joomla\CMS\MVC\Model\BaseDatabaseModel
* @since 4.0.0
*/
public function __construct($config = [], ?MVCFactoryInterface $factory = null)
{
if (empty($config['filter_fields'])) {
$config['filter_fields'] = [
'searchterm', 'a.searchterm',
'hits', 'a.hits',
];
}
parent::__construct($config, $factory);
}
/**
* Method to auto-populate the model state.
*
* Note. Calling getState in this method will result in recursion.
*
* @param string $ordering An optional ordering field.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @since 4.0.0
*/
protected function populateState($ordering = 'a.hits', $direction = 'asc')
{
// Special state for toggle results button.
$this->setState('show_results', $this->getUserStateFromRequest($this->context . '.show_results', 'show_results', 1, 'int'));
// Load the parameters.
$params = ComponentHelper::getParams('com_finder');
$this->setState('params', $params);
// List state information.
parent::populateState($ordering, $direction);
}
/**
* Method to get a store id based on model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id A prefix for the store id.
*
* @return string A store id.
*
* @since 4.0.0
*/
protected function getStoreId($id = '')
{
// Compile the store id.
$id .= ':' . $this->getState('show_results');
$id .= ':' . $this->getState('filter.search');
return parent::getStoreId($id);
}
/**
* Build an SQL query to load the list data.
*
* @return QueryInterface
*
* @since 4.0.0
*/
protected function getListQuery()
{
// Create a new query object.
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Select the required fields from the table.
$query->select(
$this->getState(
'list.select',
'a.*'
)
);
$query->from($db->quoteName('#__finder_logging', 'a'));
// Filter by search in title
if ($search = $this->getState('filter.search')) {
$search = $db->quote('%' . str_replace(' ', '%', $db->escape(trim($search), true) . '%'));
$query->where($db->quoteName('a.searchterm') . ' LIKE ' . $search);
}
// Add the list ordering clause.
$query->order($db->escape($this->getState('list.ordering', 'a.hits')) . ' ' . $db->escape($this->getState('list.direction', 'ASC')));
return $query;
}
/**
* Override the parent getItems to inject optional data.
*
* @return mixed An array of objects on success, false on failure.
*
* @since 4.0.0
*/
public function getItems()
{
$items = parent::getItems();
foreach ($items as $item) {
if (\is_resource($item->query)) {
$item->query = unserialize(stream_get_contents($item->query));
} else {
$item->query = unserialize($item->query);
}
}
return $items;
}
/**
* Method to reset the search log table.
*
* @return boolean
*
* @since 4.0.0
*/
public function reset()
{
$db = $this->getDatabase();
try {
$db->truncateTable('#__finder_logging');
} catch (\RuntimeException $e) {
$this->setError($e->getMessage());
return false;
}
return true;
}
}

View File

@ -0,0 +1,87 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Model;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Plugin\PluginHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Statistics model class for Finder.
*
* @since 2.5
*/
class StatisticsModel extends BaseDatabaseModel
{
/**
* Method to get the component statistics
*
* @return CMSObject The component statistics
*
* @since 2.5
*/
public function getData()
{
// Initialise
$db = $this->getDatabase();
$query = $db->getQuery(true);
$data = new CMSObject();
$query->select('COUNT(term_id)')
->from($db->quoteName('#__finder_terms'));
$db->setQuery($query);
$data->term_count = $db->loadResult();
$query->clear()
->select('COUNT(link_id)')
->from($db->quoteName('#__finder_links'));
$db->setQuery($query);
$data->link_count = $db->loadResult();
$query->clear()
->select('COUNT(id)')
->from($db->quoteName('#__finder_taxonomy'))
->where($db->quoteName('parent_id') . ' = 1');
$db->setQuery($query);
$data->taxonomy_branch_count = $db->loadResult();
$query->clear()
->select('COUNT(id)')
->from($db->quoteName('#__finder_taxonomy'))
->where($db->quoteName('parent_id') . ' > 1');
$db->setQuery($query);
$data->taxonomy_node_count = $db->loadResult();
$query->clear()
->select('t.title AS type_title, COUNT(a.link_id) AS link_count')
->from($db->quoteName('#__finder_links') . ' AS a')
->join('INNER', $db->quoteName('#__finder_types') . ' AS t ON t.id = a.type_id')
->group('a.type_id, t.title')
->order($db->quoteName('type_title') . ' ASC');
$db->setQuery($query);
$data->type_list = $db->loadObjectList();
$lang = Factory::getLanguage();
$plugins = PluginHelper::getPlugin('finder');
foreach ($plugins as $plugin) {
$lang->load('plg_finder_' . $plugin->name . '.sys', JPATH_ADMINISTRATOR)
|| $lang->load('plg_finder_' . $plugin->name . '.sys', JPATH_PLUGINS . '/finder/' . $plugin->name);
}
return $data;
}
}

View File

@ -0,0 +1,189 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Response;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Finder Indexer JSON Response Class
*
* @since 2.5
*/
class Response
{
/**
* The buffer
*
* @var string
* @since 4.3.0
*/
public $buffer;
/**
* The memory
*
* @var string
* @since 4.3.0
*/
public $memory;
/**
* If it has an error
*
* @var bool
* @since 4.3.0
*/
public $error;
/**
* The header
*
* @var string
* @since 4.3.0
*/
public $header;
/**
* The message
*
* @var string
* @since 4.3.0
*/
public $message;
/**
* The batch size
*
* @var int
* @since 4.3.0
*/
public $batchSize;
/**
* The batch offset
*
* @var int
* @since 4.3.0
*/
public $batchOffset;
/**
* The total items
*
* @var int
* @since 4.3.0
*/
public $totalItems;
/**
* The plugin state
*
* @var string
* @since 4.3.0
*/
public $pluginState;
/**
* The start time
*
* @var string
* @since 4.3.0
*/
public $startTime;
/**
* The end time
*
* @var string
* @since 4.3.0
*/
public $endTime;
/**
* The start
*
* @var int
* @since 4.3.0
*/
public $start;
/**
* The complete
*
* @var int
* @since 4.3.0
*/
public $complete;
/**
* Class Constructor
*
* @param mixed $state The processing state for the indexer
*
* @since 2.5
*/
public function __construct($state)
{
$params = ComponentHelper::getParams('com_finder');
if ($params->get('enable_logging', '0')) {
$options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
$options['text_file'] = 'indexer.php';
Log::addLogger($options);
}
// Check if we are dealing with an error.
if ($state instanceof \Exception) {
// Log the error
try {
Log::add($state->getMessage(), Log::ERROR);
} catch (\RuntimeException $exception) {
// Informational log only
}
// Prepare the error response.
$this->error = true;
$this->header = Text::_('COM_FINDER_INDEXER_HEADER_ERROR');
$this->message = $state->getMessage();
} else {
// Prepare the response data.
$this->batchSize = (int) $state->batchSize;
$this->batchOffset = (int) $state->batchOffset;
$this->totalItems = (int) $state->totalItems;
$this->pluginState = $state->pluginState;
$this->startTime = $state->startTime;
$this->endTime = Factory::getDate()->toSql();
$this->start = !empty($state->start) ? (int) $state->start : 0;
$this->complete = !empty($state->complete) ? (int) $state->complete : 0;
// Set the appropriate messages.
if ($this->totalItems <= 0 && $this->complete) {
$this->header = Text::_('COM_FINDER_INDEXER_HEADER_COMPLETE');
$this->message = Text::_('COM_FINDER_INDEXER_MESSAGE_COMPLETE');
} elseif ($this->totalItems <= 0) {
$this->header = Text::_('COM_FINDER_INDEXER_HEADER_OPTIMIZE');
$this->message = Text::_('COM_FINDER_INDEXER_MESSAGE_OPTIMIZE');
} else {
$this->header = Text::_('COM_FINDER_INDEXER_HEADER_RUNNING');
$this->message = Text::_('COM_FINDER_INDEXER_MESSAGE_RUNNING');
}
}
}
}

View File

@ -0,0 +1,530 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Service\HTML;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
use Joomla\Component\Finder\Administrator\Indexer\Query;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\ParameterType;
use Joomla\Filter\OutputFilter;
use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Filter HTML Behaviors for Finder.
*
* @since 2.5
*/
class Filter
{
use DatabaseAwareTrait;
/**
* Method to generate filters using the slider widget and decorated
* with the FinderFilter JavaScript behaviors.
*
* @param array $options An array of configuration options. [optional]
*
* @return mixed A rendered HTML widget on success, null otherwise.
*
* @since 2.5
*/
public function slider($options = [])
{
$db = $this->getDatabase();
$query = $db->getQuery(true);
$user = Factory::getUser();
$groups = implode(',', $user->getAuthorisedViewLevels());
$html = '';
$filter = null;
// Get the configuration options.
$filterId = $options['filter_id'] ?? null;
$activeNodes = \array_key_exists('selected_nodes', $options) ? $options['selected_nodes'] : [];
$classSuffix = \array_key_exists('class_suffix', $options) ? $options['class_suffix'] : '';
// Load the predefined filter if specified.
if (!empty($filterId)) {
$query->select('f.data, f.params')
->from($db->quoteName('#__finder_filters') . ' AS f')
->where('f.filter_id = ' . (int) $filterId);
// Load the filter data.
$db->setQuery($query);
try {
$filter = $db->loadObject();
} catch (\RuntimeException $e) {
return null;
}
// Initialize the filter parameters.
if ($filter) {
$filter->params = new Registry($filter->params);
}
}
// Build the query to get the branch data and the number of child nodes.
$query->clear()
->select('t.*, count(c.id) AS children')
->from($db->quoteName('#__finder_taxonomy') . ' AS t')
->join('INNER', $db->quoteName('#__finder_taxonomy') . ' AS c ON c.parent_id = t.id')
->where('t.parent_id = 1')
->where('t.state = 1')
->where('t.access IN (' . $groups . ')')
->group('t.id, t.parent_id, t.state, t.access, t.title, c.parent_id')
->order('t.lft, t.title');
// Limit the branch children to a predefined filter.
if ($filter) {
$query->where('c.id IN(' . $filter->data . ')');
}
// Load the branches.
$db->setQuery($query);
try {
$branches = $db->loadObjectList('id');
} catch (\RuntimeException $e) {
return null;
}
// Check that we have at least one branch.
if (\count($branches) === 0) {
return null;
}
$branch_keys = array_keys($branches);
$html .= HTMLHelper::_('bootstrap.startAccordion', 'accordion', ['active' => 'accordion-' . $branch_keys[0]]);
// Load plugin language files.
LanguageHelper::loadPluginLanguage();
// Iterate through the branches and build the branch groups.
foreach ($branches as $bk => $bv) {
// If the multi-lang plugin is enabled then drop the language branch.
if ($bv->title === 'Language' && Multilanguage::isEnabled()) {
continue;
}
// Build the query to get the child nodes for this branch.
$query->clear()
->select('t.*')
->from($db->quoteName('#__finder_taxonomy') . ' AS t')
->where('t.lft > ' . (int) $bv->lft)
->where('t.rgt < ' . (int) $bv->rgt)
->where('t.state = 1')
->where('t.access IN (' . $groups . ')')
->order('t.lft, t.title');
// Self-join to get the parent title.
$query->select('e.title AS parent_title')
->join('LEFT', $db->quoteName('#__finder_taxonomy', 'e') . ' ON ' . $db->quoteName('e.id') . ' = ' . $db->quoteName('t.parent_id'));
// Load the branches.
$db->setQuery($query);
try {
$nodes = $db->loadObjectList('id');
} catch (\RuntimeException $e) {
return null;
}
// Translate node titles if possible.
$lang = Factory::getLanguage();
foreach ($nodes as $nv) {
if (trim($nv->parent_title, '*') === 'Language') {
$title = LanguageHelper::branchLanguageTitle($nv->title);
} else {
$key = LanguageHelper::branchPlural($nv->title);
$title = $lang->hasKey($key) ? Text::_($key) : $nv->title;
}
$nv->title = $title;
}
// Adding slides
$html .= HTMLHelper::_(
'bootstrap.addSlide',
'accordion',
Text::sprintf(
'COM_FINDER_FILTER_BRANCH_LABEL',
Text::_(LanguageHelper::branchSingular($bv->title)) . ' - ' . \count($nodes)
),
'accordion-' . $bk
);
// Populate the toggle button.
$html .= '<button class="btn btn-secondary js-filter" type="button" data-id="tax-' . $bk . '"><span class="icon-square" aria-hidden="true"></span> '
. Text::_('JGLOBAL_SELECTION_INVERT') . '</button><hr>';
// Populate the group with nodes.
foreach ($nodes as $nk => $nv) {
// Determine if the node should be checked.
$checked = \in_array($nk, $activeNodes) ? ' checked="checked"' : '';
// Build a node.
$html .= '<div class="form-check">';
$html .= '<label class="form-check-label">';
$html .= '<input type="checkbox" class="form-check-input selector filter-node' . $classSuffix
. ' tax-' . $bk . '" value="' . $nk . '" name="t[]"' . $checked . '> ' . str_repeat('&mdash;', $nv->level - 2) . $nv->title;
$html .= '</label>';
$html .= '</div>';
}
$html .= HTMLHelper::_('bootstrap.endSlide');
}
$html .= HTMLHelper::_('bootstrap.endAccordion');
return $html;
}
/**
* Method to generate filters using select box dropdown controls.
*
* @param Query $idxQuery A Query object.
* @param Registry $options An array of options.
*
* @return string|null A rendered HTML widget on success, null otherwise.
*
* @since 2.5
*/
public function select($idxQuery, $options)
{
$user = Factory::getUser();
$groups = implode(',', $user->getAuthorisedViewLevels());
$filter = null;
// Get the configuration options.
$classSuffix = $options->get('class_suffix', null);
$showDates = $options->get('show_date_filters', false);
// Try to load the results from cache.
$cache = Factory::getCache('com_finder', '');
$cacheId = 'filter_select_' . serialize([$idxQuery->filter, $options, $groups, Factory::getLanguage()->getTag()]);
// Check the cached results.
if ($cache->contains($cacheId)) {
$branches = $cache->get($cacheId);
} else {
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Load the predefined filter if specified.
if (!empty($idxQuery->filter)) {
$query->select('f.data, ' . $db->quoteName('f.params'))
->from($db->quoteName('#__finder_filters') . ' AS f')
->where('f.filter_id = ' . (int) $idxQuery->filter);
// Load the filter data.
$db->setQuery($query);
try {
$filter = $db->loadObject();
} catch (\RuntimeException $e) {
return null;
}
// Initialize the filter parameters.
if ($filter) {
$filter->params = new Registry($filter->params);
}
}
// Build the query to get the branch data and the number of child nodes.
$query->clear()
->select('t.*, count(c.id) AS children')
->from($db->quoteName('#__finder_taxonomy') . ' AS t')
->join('INNER', $db->quoteName('#__finder_taxonomy') . ' AS c ON c.parent_id = t.id')
->where('t.parent_id = 1')
->where('t.state = 1')
->where('t.access IN (' . $groups . ')')
->where('c.state = 1')
->where('c.access IN (' . $groups . ')')
->group($db->quoteName('t.id'))
->group($db->quoteName('t.parent_id'))
->group('t.title, t.state, t.access, t.lft')
->order('t.lft, t.title');
// Limit the branch children to a predefined filter.
if (!empty($filter->data)) {
$query->where('c.id IN(' . $filter->data . ')');
}
// Load the branches.
$db->setQuery($query);
try {
$branches = $db->loadObjectList('id');
} catch (\RuntimeException $e) {
return null;
}
// Check that we have at least one branch.
if (\count($branches) === 0) {
return null;
}
// Iterate through the branches and build the branch groups.
foreach ($branches as $bk => $bv) {
// If the multi-lang plugin is enabled then drop the language branch.
if ($bv->title === 'Language' && Multilanguage::isEnabled()) {
continue;
}
// Build the query to get the child nodes for this branch.
$query->clear()
->select('t.*')
->from($db->quoteName('#__finder_taxonomy') . ' AS t')
->where('t.lft > :lft')
->where('t.rgt < :rgt')
->where('t.state = 1')
->whereIn('t.access', $user->getAuthorisedViewLevels())
->order('t.level, t.parent_id, t.title')
->bind(':lft', $bv->lft, ParameterType::INTEGER)
->bind(':rgt', $bv->rgt, ParameterType::INTEGER);
// Apply multilanguage filter
if (Multilanguage::isEnabled()) {
$language = [Factory::getLanguage()->getTag(), '*'];
$query->whereIn($db->quoteName('t.language'), $language, ParameterType::STRING);
}
// Self-join to get the parent title.
$query->select('e.title AS parent_title')
->join('LEFT', $db->quoteName('#__finder_taxonomy', 'e') . ' ON ' . $db->quoteName('e.id') . ' = ' . $db->quoteName('t.parent_id'));
// Limit the nodes to a predefined filter.
if (!empty($filter->data)) {
$query->whereIn('t.id', explode(",", $filter->data));
}
// Load the branches.
$db->setQuery($query);
try {
$bv->nodes = $db->loadObjectList('id');
} catch (\RuntimeException $e) {
return null;
}
// Translate branch nodes if possible.
$language = Factory::getLanguage();
$root = [];
foreach ($bv->nodes as $node_id => $node) {
if (trim($node->parent_title, '*') === 'Language') {
$title = LanguageHelper::branchLanguageTitle($node->title);
} else {
$key = LanguageHelper::branchPlural($node->title);
$title = $language->hasKey($key) ? Text::_($key) : $node->title;
}
if ($node->level > 2) {
$node->title = str_repeat('-', $node->level - 2) . $title;
} else {
$node->title = $title;
$root[] = $branches[$bk]->nodes[$node_id];
}
if ($node->parent_id && isset($branches[$bk]->nodes[$node->parent_id])) {
if (!isset($branches[$bk]->nodes[$node->parent_id]->children)) {
$branches[$bk]->nodes[$node->parent_id]->children = [];
}
$branches[$bk]->nodes[$node->parent_id]->children[] = $node;
}
}
$branches[$bk]->nodes = $this->reduce($root);
// Add the Search All option to the branch.
array_unshift($bv->nodes, ['id' => null, 'title' => Text::_('COM_FINDER_FILTER_SELECT_ALL_LABEL')]);
}
// Store the data in cache.
$cache->store($branches, $cacheId);
}
$html = '';
// Add the dates if enabled.
if ($showDates) {
$html .= HTMLHelper::_('filter.dates', $idxQuery, $options);
}
$html .= '<div class="filter-branch' . $classSuffix . '">';
// Iterate through all branches and build code.
foreach ($branches as $bv) {
// If the multi-lang plugin is enabled then drop the language branch.
if ($bv->title === 'Language' && Multilanguage::isEnabled()) {
continue;
}
$active = null;
// Check if the branch is in the filter.
if (\array_key_exists($bv->title, $idxQuery->filters)) {
// Get the request filters.
$temp = Factory::getApplication()->getInput()->request->get('t', [], 'array');
// Search for active nodes in the branch and get the active node.
$active = array_intersect($temp, $idxQuery->filters[$bv->title]);
$active = \count($active) === 1 ? array_shift($active) : null;
}
// Build a node.
$html .= '<div class="control-group">';
$html .= '<div class="control-label">';
$html .= '<label for="tax-' . OutputFilter::stringURLSafe($bv->title) . '">';
$html .= Text::sprintf('COM_FINDER_FILTER_BRANCH_LABEL', Text::_(LanguageHelper::branchSingular($bv->title)));
$html .= '</label>';
$html .= '</div>';
$html .= '<div class="controls">';
$html .= HTMLHelper::_(
'select.genericlist',
$bv->nodes,
't[]',
'class="form-select advancedSelect"',
'id',
'title',
$active,
'tax-' . OutputFilter::stringURLSafe($bv->title)
);
$html .= '</div>';
$html .= '</div>';
}
$html .= '</div>';
return $html;
}
/**
* Method to generate fields for filtering dates
*
* @param Query $idxQuery A Query object.
* @param Registry $options An array of options.
*
* @return string A rendered HTML widget.
*
* @since 2.5
*/
public function dates($idxQuery, $options)
{
$html = '';
// Get the configuration options.
$classSuffix = $options->get('class_suffix', null);
$loadMedia = $options->get('load_media', true);
$showDates = $options->get('show_date_filters', false);
if (!empty($showDates)) {
// Build the date operators options.
$operators = [];
$operators[] = HTMLHelper::_('select.option', 'before', Text::_('COM_FINDER_FILTER_DATE_BEFORE'));
$operators[] = HTMLHelper::_('select.option', 'exact', Text::_('COM_FINDER_FILTER_DATE_EXACTLY'));
$operators[] = HTMLHelper::_('select.option', 'after', Text::_('COM_FINDER_FILTER_DATE_AFTER'));
// Load the CSS/JS resources.
if ($loadMedia) {
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useStyle('com_finder.dates');
}
// Open the widget.
$html .= '<ul id="finder-filter-select-dates">';
// Start date filter.
$attribs['class'] = 'input-medium';
$html .= '<li class="filter-date float-start' . $classSuffix . '">';
$html .= '<label for="filter_date1" class="hasTooltip" title ="' . Text::_('COM_FINDER_FILTER_DATE1_DESC') . '">';
$html .= Text::_('COM_FINDER_FILTER_DATE1');
$html .= '</label>';
$html .= '<br>';
$html .= '<label for="finder-filter-w1" class="visually-hidden">';
$html .= Text::_('COM_FINDER_FILTER_DATE1_OPERATOR');
$html .= '</label>';
$html .= HTMLHelper::_(
'select.genericlist',
$operators,
'w1',
'class="inputbox filter-date-operator advancedSelect form-select w-auto mb-2"',
'value',
'text',
$idxQuery->when1,
'finder-filter-w1'
);
$html .= HTMLHelper::_('calendar', $idxQuery->date1, 'd1', 'filter_date1', '%Y-%m-%d', $attribs);
$html .= '</li>';
// End date filter.
$html .= '<li class="filter-date float-end' . $classSuffix . '">';
$html .= '<label for="filter_date2" class="hasTooltip" title ="' . Text::_('COM_FINDER_FILTER_DATE2_DESC') . '">';
$html .= Text::_('COM_FINDER_FILTER_DATE2');
$html .= '</label>';
$html .= '<br>';
$html .= '<label for="finder-filter-w2" class="visually-hidden">';
$html .= Text::_('COM_FINDER_FILTER_DATE2_OPERATOR');
$html .= '</label>';
$html .= HTMLHelper::_(
'select.genericlist',
$operators,
'w2',
'class="inputbox filter-date-operator advancedSelect form-select w-auto mb-2"',
'value',
'text',
$idxQuery->when2,
'finder-filter-w2'
);
$html .= HTMLHelper::_('calendar', $idxQuery->date2, 'd2', 'filter_date2', '%Y-%m-%d', $attribs);
$html .= '</li>';
// Close the widget.
$html .= '</ul>';
}
return $html;
}
/**
* Method to flatten a tree to a sorted array
*
* @param \stdClass[] $array
*
* @return \stdClass[] Flat array of all nodes of a tree with the children after each parent
*
* @since 5.1.0
*/
private function reduce(array $array)
{
$return = [];
foreach ($array as $item) {
$return[] = $item;
if (isset($item->children)) {
$return = array_merge($return, $this->reduce($item->children));
}
}
return $return;
}
}

View File

@ -0,0 +1,131 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Service\HTML;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* HTML behavior class for Finder.
*
* @since 2.5
*/
class Finder
{
use DatabaseAwareTrait;
/**
* Creates a list of types to filter on.
*
* @return array An array containing the types that can be selected.
*
* @since 2.5
*/
public function typeslist()
{
// Load the finder types.
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('DISTINCT t.title AS text, t.id AS value')
->from($db->quoteName('#__finder_types') . ' AS t')
->join('LEFT', $db->quoteName('#__finder_links') . ' AS l ON l.type_id = t.id')
->order('t.title ASC');
$db->setQuery($query);
try {
$rows = $db->loadObjectList();
} catch (\RuntimeException $e) {
return [];
}
// Compile the options.
$options = [];
$lang = Factory::getLanguage();
foreach ($rows as $row) {
$key = $lang->hasKey(LanguageHelper::branchPlural($row->text)) ? LanguageHelper::branchPlural($row->text) : $row->text;
$options[] = HTMLHelper::_('select.option', $row->value, Text::sprintf('COM_FINDER_ITEM_X_ONLY', Text::_($key)));
}
return $options;
}
/**
* Creates a list of maps.
*
* @return array An array containing the maps that can be selected.
*
* @since 2.5
*/
public function mapslist()
{
// Load the finder types.
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName('title', 'text'))
->select($db->quoteName('id', 'value'))
->from($db->quoteName('#__finder_taxonomy'))
->where($db->quoteName('parent_id') . ' = 1');
$db->setQuery($query);
try {
$branches = $db->loadObjectList();
} catch (\RuntimeException $e) {
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
}
// Translate.
$lang = Factory::getLanguage();
foreach ($branches as $branch) {
$key = LanguageHelper::branchPlural($branch->text);
$branch->translatedText = $lang->hasKey($key) ? Text::_($key) : $branch->text;
}
// Order by title.
$branches = ArrayHelper::sortObjects($branches, 'translatedText', 1, true, true);
// Compile the options.
$options = [];
$options[] = HTMLHelper::_('select.option', '', Text::_('COM_FINDER_MAPS_SELECT_BRANCH'));
// Convert the values to options.
foreach ($branches as $branch) {
$options[] = HTMLHelper::_('select.option', $branch->value, $branch->translatedText);
}
return $options;
}
/**
* Creates a list of published states.
*
* @return array An array containing the states that can be selected.
*
* @since 2.5
*/
public static function statelist()
{
return [
HTMLHelper::_('select.option', '1', Text::sprintf('COM_FINDER_ITEM_X_ONLY', Text::_('JPUBLISHED'))),
HTMLHelper::_('select.option', '0', Text::sprintf('COM_FINDER_ITEM_X_ONLY', Text::_('JUNPUBLISHED'))),
];
}
}

View File

@ -0,0 +1,156 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Service\HTML;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
use Joomla\Component\Finder\Administrator\Indexer\Query as IndexerQuery;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Query HTML behavior class for Finder.
*
* @since 2.5
*/
class Query
{
/**
* Method to get the explained (human-readable) search query.
*
* @param IndexerQuery $query A IndexerQuery object to explain.
*
* @return mixed String if there is data to explain, null otherwise.
*
* @since 2.5
*/
public static function explained(IndexerQuery $query)
{
$parts = [];
// Process the required tokens.
foreach ($query->included as $token) {
if ($token->required && (!isset($token->derived) || $token->derived == false)) {
$parts[] = '<span class="query-required">' . Text::sprintf('COM_FINDER_QUERY_TOKEN_REQUIRED', $token->term) . '</span>';
}
}
// Process the optional tokens.
foreach ($query->included as $token) {
if (!$token->required && (!isset($token->derived) || $token->derived == false)) {
$parts[] = '<span class="query-optional">' . Text::sprintf('COM_FINDER_QUERY_TOKEN_OPTIONAL', $token->term) . '</span>';
}
}
// Process the excluded tokens.
foreach ($query->excluded as $token) {
if (!isset($token->derived) || $token->derived === false) {
$parts[] = '<span class="query-excluded">' . Text::sprintf('COM_FINDER_QUERY_TOKEN_EXCLUDED', $token->term) . '</span>';
}
}
// Process the start date.
if ($query->date1) {
$date = Factory::getDate($query->date1)->format(Text::_('DATE_FORMAT_LC'));
$datecondition = Text::_('COM_FINDER_QUERY_DATE_CONDITION_' . strtoupper($query->when1));
$parts[] = '<span class="query-start-date">' . Text::sprintf('COM_FINDER_QUERY_START_DATE', $datecondition, $date) . '</span>';
}
// Process the end date.
if ($query->date2) {
$date = Factory::getDate($query->date2)->format(Text::_('DATE_FORMAT_LC'));
$datecondition = Text::_('COM_FINDER_QUERY_DATE_CONDITION_' . strtoupper($query->when2));
$parts[] = '<span class="query-end-date">' . Text::sprintf('COM_FINDER_QUERY_END_DATE', $datecondition, $date) . '</span>';
}
// Process the taxonomy filters.
if (!empty($query->filters)) {
// Get the filters in the request.
$t = Factory::getApplication()->getInput()->request->get('t', [], 'array');
// Process the taxonomy branches.
foreach ($query->filters as $branch => $nodes) {
// Process the taxonomy nodes.
$lang = Factory::getLanguage();
foreach ($nodes as $title => $id) {
// Translate the title for Types
$key = LanguageHelper::branchPlural($title);
if ($lang->hasKey($key)) {
$title = Text::_($key);
}
// Don't include the node if it is not in the request.
if (!\in_array($id, $t)) {
continue;
}
// Add the node to the explanation.
$parts[] = '<span class="query-taxonomy">'
. Text::sprintf('COM_FINDER_QUERY_TAXONOMY_NODE', $title, Text::_(LanguageHelper::branchSingular($branch)))
. '</span>';
}
}
}
// Build the interpreted query.
return \count($parts) ? implode(Text::_('COM_FINDER_QUERY_TOKEN_GLUE'), $parts) : null;
}
/**
* Method to get the suggested search query.
*
* @param IndexerQuery $query A IndexerQuery object.
*
* @return mixed String if there is a suggestion, false otherwise.
*
* @since 2.5
*/
public static function suggested(IndexerQuery $query)
{
$suggested = false;
// Check if the query input is empty.
if (empty($query->input)) {
return $suggested;
}
// Check if there were any ignored or included keywords.
if (\count($query->ignored) || \count($query->included)) {
$suggested = $query->input;
// Replace the ignored keyword suggestions.
foreach (array_reverse($query->ignored) as $token) {
if (isset($token->suggestion)) {
$suggested = str_ireplace($token->term, $token->suggestion, $suggested);
}
}
// Replace the included keyword suggestions.
foreach (array_reverse($query->included) as $token) {
if (isset($token->suggestion)) {
$suggested = str_ireplace($token->term, $token->suggestion, $suggested);
}
}
// Check if we made any changes.
if ($suggested == $query->input) {
$suggested = false;
}
}
return $suggested;
}
}

View File

@ -0,0 +1,173 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Table;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Table\Table;
use Joomla\CMS\User\CurrentUserInterface;
use Joomla\CMS\User\CurrentUserTrait;
use Joomla\Database\DatabaseDriver;
use Joomla\Event\DispatcherInterface;
use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Filter table class for the Finder package.
*
* @since 2.5
*/
class FilterTable extends Table implements CurrentUserInterface
{
use CurrentUserTrait;
/**
* Indicates that columns fully support the NULL value in the database
*
* @var boolean
* @since 4.0.0
*/
protected $_supportNullValue = true;
/**
* Ensure the params are json encoded in the bind method
*
* @var array
* @since 4.0.0
*/
protected $_jsonEncode = ['params'];
/**
* Constructor
*
* @param DatabaseDriver $db Database connector object
* @param ?DispatcherInterface $dispatcher Event dispatcher for this table
*
* @since 2.5
*/
public function __construct(DatabaseDriver $db, ?DispatcherInterface $dispatcher = null)
{
parent::__construct('#__finder_filters', 'filter_id', $db, $dispatcher);
$this->setColumnAlias('published', 'state');
}
/**
* Method to perform sanity checks on the \Joomla\CMS\Table\Table instance properties to ensure
* they are safe to store in the database. Child classes should override this
* method to make sure the data they are storing in the database is safe and
* as expected before storage.
*
* @return boolean True if the instance is sane and able to be stored in the database.
*
* @since 2.5
*/
public function check()
{
try {
parent::check();
} catch (\Exception $e) {
$this->setError($e->getMessage());
return false;
}
if (trim($this->alias) === '') {
$this->alias = $this->title;
}
$this->alias = ApplicationHelper::stringURLSafe($this->alias);
if (trim(str_replace('-', '', $this->alias)) === '') {
$this->alias = Factory::getDate()->format('Y-m-d-H-i-s');
}
$params = new Registry($this->params);
$d1 = $params->get('d1', '');
$d2 = $params->get('d2', '');
// Check the end date is not earlier than the start date.
if (!empty($d1) && !empty($d2) && $d2 < $d1) {
// Swap the dates.
$params->set('d1', $d2);
$params->set('d2', $d1);
$this->params = (string) $params;
}
return true;
}
/**
* Method to store a row in the database from the \Joomla\CMS\Table\Table instance properties.
* If a primary key value is set the row with that primary key value will be
* updated with the instance property values. If no primary key value is set
* a new row will be inserted into the database with the properties from the
* \Joomla\CMS\Table\Table instance.
*
* @param boolean $updateNulls True to update fields even if they are null. [optional]
*
* @return boolean True on success.
*
* @since 2.5
*/
public function store($updateNulls = true)
{
$date = Factory::getDate()->toSql();
$userId = $this->getCurrentUser()->id;
// Set created date if not set.
if (!(int) $this->created) {
$this->created = $date;
}
if ($this->filter_id) {
// Existing item
$this->modified_by = $userId;
$this->modified = $date;
} else {
if (empty($this->created_by)) {
$this->created_by = $userId;
}
if (!(int) $this->modified) {
$this->modified = $this->created;
}
if (empty($this->modified_by)) {
$this->modified_by = $this->created_by;
}
}
if (\is_array($this->data)) {
$this->map_count = \count($this->data);
$this->data = implode(',', $this->data);
} else {
$this->map_count = 0;
$this->data = implode(',', []);
}
// Verify that the alias is unique
$table = new self($this->getDbo(), $this->getDispatcher());
if ($table->load(['alias' => $this->alias]) && ($table->filter_id != $this->filter_id || $this->filter_id == 0)) {
$this->setError(Text::_('COM_FINDER_FILTER_ERROR_UNIQUE_ALIAS'));
return false;
}
return parent::store($updateNulls);
}
}

View File

@ -0,0 +1,63 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Table;
use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseDriver;
use Joomla\Event\DispatcherInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Link table class for the Finder package.
*
* @since 2.5
*/
class LinkTable extends Table
{
/**
* Indicates that columns fully support the NULL value in the database
*
* @var boolean
* @since 4.0.0
*/
protected $_supportNullValue = true;
/**
* Constructor
*
* @param DatabaseDriver $db Database connector object
* @param ?DispatcherInterface $dispatcher Event dispatcher for this table
*
* @since 2.5
*/
public function __construct(DatabaseDriver $db, ?DispatcherInterface $dispatcher = null)
{
parent::__construct('#__finder_links', 'link_id', $db, $dispatcher);
}
/**
* Overloaded store function
*
* @param boolean $updateNulls True to update fields even if they are null.
*
* @return mixed False on failure, positive integer on success.
*
* @see Table::store()
* @since 4.0.0
*/
public function store($updateNulls = true)
{
return parent::store($updateNulls);
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\Table;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Table\Nested;
use Joomla\Database\DatabaseDriver;
use Joomla\Event\DispatcherInterface;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Map table class for the Finder package.
*
* @since 2.5
*/
class MapTable extends Nested
{
/**
* Constructor
*
* @param DatabaseDriver $db Database connector object
* @param ?DispatcherInterface $dispatcher Event dispatcher for this table
*
* @since 2.5
*/
public function __construct(DatabaseDriver $db, ?DispatcherInterface $dispatcher = null)
{
parent::__construct('#__finder_taxonomy', 'id', $db, $dispatcher);
$this->setColumnAlias('published', 'state');
$this->access = (int) Factory::getApplication()->get('access');
}
/**
* Override check function
*
* @return boolean
*
* @see Table::check()
* @since 4.0.0
*/
public function check()
{
try {
parent::check();
} catch (\Exception $e) {
$this->setError($e->getMessage());
return false;
}
// Check for a title.
if (trim($this->title) == '') {
$this->setError(Text::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_CATEGORY'));
return false;
}
$this->alias = ApplicationHelper::stringURLSafe($this->title, $this->language);
if (trim($this->alias) == '') {
$this->alias = md5(serialize($this->getProperties()));
}
return true;
}
}

View File

@ -0,0 +1,186 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\View\Filter;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Toolbar\ToolbarHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Filter view class for Finder.
*
* @since 2.5
*/
class HtmlView extends BaseHtmlView
{
/**
* The filter object
*
* @var \Joomla\Component\Finder\Administrator\Table\FilterTable
*
* @since 3.6.2
*/
protected $filter;
/**
* The Form object
*
* @var \Joomla\CMS\Form\Form
*
* @since 3.6.2
*/
protected $form;
/**
* The active item
*
* @var \stdClass
*
* @since 3.6.2
*/
protected $item;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*
* @since 3.6.2
*/
protected $state;
/**
* The total indexed items
*
* @var integer
*
* @since 3.8.0
*/
protected $total;
/**
* Array of fieldsets not to display
*
* @var string[]
*
* @since 5.2.0
*/
public $ignore_fieldsets = [];
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 2.5
*/
public function display($tpl = null)
{
// Load the view data.
$this->filter = $this->get('Filter');
$this->item = $this->get('Item');
$this->form = $this->get('Form');
$this->state = $this->get('State');
$this->total = $this->get('Total');
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
// Configure the toolbar.
$this->addToolbar();
parent::display($tpl);
}
/**
* Method to configure the toolbar for this view.
*
* @return void
*
* @since 2.5
*/
protected function addToolbar()
{
Factory::getApplication()->getInput()->set('hidemainmenu', true);
$isNew = ($this->item->filter_id == 0);
$checkedOut = !(\is_null($this->item->checked_out) || $this->item->checked_out == $this->getCurrentUser()->id);
$canDo = ContentHelper::getActions('com_finder');
$toolbar = $this->getDocument()->getToolbar();
// Configure the toolbar.
ToolbarHelper::title(
$isNew ? Text::_('COM_FINDER_FILTER_NEW_TOOLBAR_TITLE') : Text::_('COM_FINDER_FILTER_EDIT_TOOLBAR_TITLE'),
'zoom-in finder'
);
// Set the actions for new and existing records.
if ($isNew) {
// For new records, check the create permission.
if ($canDo->get('core.create')) {
$toolbar->apply('filter.apply');
$saveGroup = $toolbar->dropdownButton('save-group');
$saveGroup->configure(
function (Toolbar $childBar) {
$childBar->save('filter.save');
$childBar->save2new('filter.save2new');
}
);
}
$toolbar->cancel('filter.cancel', 'JTOOLBAR_CANCEL');
} else {
// Can't save the record if it's checked out.
// Since it's an existing record, check the edit permission.
if (!$checkedOut && $canDo->get('core.edit')) {
$toolbar->apply('filter.apply');
}
$saveGroup = $toolbar->dropdownButton('save-group');
$saveGroup->configure(
function (Toolbar $childBar) use ($checkedOut, $canDo) {
// Can't save the record if it's checked out.
// Since it's an existing record, check the edit permission.
if (!$checkedOut && $canDo->get('core.edit')) {
$childBar->save('filter.save');
// We can save this record, but check the create permission to see if we can return to make a new one.
if ($canDo->get('core.create')) {
$childBar->save2new('filter.save2new');
}
}
// If an existing item, can save as a copy
if ($canDo->get('core.create')) {
$childBar->save2copy('filter.save2copy');
}
}
);
$toolbar->cancel('filter.cancel');
}
$toolbar->divider();
$toolbar->help('Smart_Search:_New_or_Edit_Filter');
}
}

View File

@ -0,0 +1,183 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\View\Filters;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\Button\DropdownButton;
use Joomla\CMS\Toolbar\ToolbarHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Filters view class for Finder.
*
* @since 2.5
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items
*
* @var array
*
* @since 3.6.1
*/
protected $items;
/**
* The pagination object
*
* @var \Joomla\CMS\Pagination\Pagination
*
* @since 3.6.1
*/
protected $pagination;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*
* @since 3.6.1
*/
protected $state;
/**
* The total number of items
*
* @var integer
*
* @since 3.6.1
*/
protected $total;
/**
* Form object for search filters
*
* @var \Joomla\CMS\Form\Form
*
* @since 4.0.0
*/
public $filterForm;
/**
* The active search filters
*
* @var array
*
* @since 4.0.0
*/
public $activeFilters;
/**
* @var boolean
*
* @since 4.0.0
*/
private $isEmptyState = false;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 2.5
*/
public function display($tpl = null)
{
// Load the view data.
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
$this->total = $this->get('Total');
$this->state = $this->get('State');
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
if (\count($this->items) === 0 && $this->isEmptyState = $this->get('IsEmptyState')) {
$this->setLayout('emptystate');
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
// Configure the toolbar.
$this->addToolbar();
parent::display($tpl);
}
/**
* Method to configure the toolbar for this view.
*
* @return void
*
* @since 2.5
*/
protected function addToolbar()
{
$canDo = ContentHelper::getActions('com_finder');
$toolbar = $this->getDocument()->getToolbar();
ToolbarHelper::title(Text::_('COM_FINDER_FILTERS_TOOLBAR_TITLE'), 'search-plus finder');
if ($canDo->get('core.create')) {
$toolbar->addNew('filter.add');
$toolbar->divider();
}
if ($this->isEmptyState === false) {
if ($canDo->get('core.edit.state')) {
/** @var DropdownButton $dropdown */
$dropdown = $toolbar->dropdownButton('status-group', 'JTOOLBAR_CHANGE_STATUS')
->toggleSplit(false)
->icon('icon-ellipsis-h')
->buttonClass('btn btn-action')
->listCheck(true);
$childBar = $dropdown->getChildToolbar();
$childBar->publish('filters.publish')->listCheck(true);
$childBar->unpublish('filters.unpublish')->listCheck(true);
$childBar->checkin('filters.checkin')->listCheck(true);
}
if ($canDo->get('core.delete')) {
$toolbar->standardButton('delete', 'JTOOLBAR_DELETE', 'filters.delete')
->listCheck(true);
$toolbar->divider();
}
$toolbar->divider();
$toolbar->popupButton('bars', 'COM_FINDER_STATISTICS')
->url('index.php?option=com_finder&view=statistics&tmpl=component')
->iframeWidth(550)
->iframeHeight(350)
->title(Text::_('COM_FINDER_STATISTICS_TITLE'))
->icon('icon-bars');
$toolbar->divider();
}
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
$toolbar->preferences('com_finder');
}
$toolbar->help('Smart_Search:_Search_Filters');
}
}

View File

@ -0,0 +1,263 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\View\Index;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Toolbar\Button\DropdownButton;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\Finder\Administrator\Helper\FinderHelper;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Index view class for Finder.
*
* @since 2.5
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items
*
* @var array
*
* @since 3.6.1
*/
protected $items;
/**
* The pagination object
*
* @var \Joomla\CMS\Pagination\Pagination
*
* @since 3.6.1
*/
protected $pagination;
/**
* The state of core Smart Search plugins
*
* @var array
*
* @since 3.6.1
*/
protected $pluginState;
/**
* The id of the content - finder plugin in mysql
*
* @var integer
*
* @since 4.0.0
*/
protected $finderPluginId = 0;
/**
* The model state
*
* @var mixed
*
* @since 3.6.1
*/
protected $state;
/**
* The total number of items
*
* @var integer
*
* @since 3.6.1
*/
protected $total;
/**
* Form object for search filters
*
* @var \Joomla\CMS\Form\Form
*
* @since 4.0.0
*/
public $filterForm;
/**
* The active search filters
*
* @var array
*
* @since 4.0.0
*/
public $activeFilters;
/**
* @var mixed
*
* @since 4.0.0
*/
private $isEmptyState = false;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 2.5
*/
public function display($tpl = null)
{
// Load plugin language files.
LanguageHelper::loadPluginLanguage();
$this->items = $this->get('Items');
$this->total = $this->get('Total');
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
$this->pluginState = $this->get('pluginState');
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
if ($this->get('TotalIndexed') === 0 && $this->isEmptyState = $this->get('IsEmptyState')) {
$this->setLayout('emptystate');
}
// We do not need to filter by language when multilingual is disabled
if (!Multilanguage::isEnabled()) {
unset($this->activeFilters['language']);
$this->filterForm->removeField('language', 'filter');
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
// Check that the content - finder plugin is enabled
if (!PluginHelper::isEnabled('content', 'finder')) {
$this->finderPluginId = FinderHelper::getFinderPluginId();
}
// Configure the toolbar.
$this->addToolbar();
parent::display($tpl);
}
/**
* Method to configure the toolbar for this view.
*
* @return void
*
* @since 2.5
*/
protected function addToolbar()
{
$canDo = ContentHelper::getActions('com_finder');
$toolbar = $this->getDocument()->getToolbar();
ToolbarHelper::title(Text::_('COM_FINDER_INDEX_TOOLBAR_TITLE'), 'search-plus finder');
if (JDEBUG) {
$dropdown = $toolbar->dropdownButton('indexing-group');
$dropdown->text('COM_FINDER_INDEX')
->toggleSplit(false)
->icon('icon-archive')
->buttonClass('btn btn-action');
$childBar = $dropdown->getChildToolbar();
$childBar->popupButton('index', 'COM_FINDER_INDEX')
->popupType('iframe')
->textHeader(Text::_('COM_FINDER_HEADING_INDEXER'))
->url('index.php?option=com_finder&view=indexer&tmpl=component')
->modalWidth('800px')
->modalHeight('400px')
->icon('icon-archive')
->title(Text::_('COM_FINDER_HEADING_INDEXER'));
$childBar->linkButton('indexdebug', 'COM_FINDER_INDEX_TOOLBAR_INDEX_DEBUGGING')
->url('index.php?option=com_finder&view=indexer&layout=debug')
->icon('icon-tools');
} else {
$toolbar->popupButton('index', 'COM_FINDER_INDEX')
->popupType('iframe')
->textHeader(Text::_('COM_FINDER_HEADING_INDEXER'))
->url('index.php?option=com_finder&view=indexer&tmpl=component')
->modalWidth('800px')
->modalHeight('400px')
->icon('icon-archive')
->title(Text::_('COM_FINDER_HEADING_INDEXER'));
}
if (!$this->isEmptyState) {
if ($canDo->get('core.edit.state')) {
$dropdown = $toolbar->dropdownButton('status-group')
->text('JTOOLBAR_CHANGE_STATUS')
->toggleSplit(false)
->icon('icon-ellipsis-h')
->buttonClass('btn btn-action')
->listCheck(true);
$childBar = $dropdown->getChildToolbar();
$childBar->publish('index.publish')->listCheck(true);
$childBar->unpublish('index.unpublish')->listCheck(true);
}
if ($canDo->get('core.delete')) {
$toolbar->confirmButton('delete', 'JTOOLBAR_DELETE', 'index.delete')
->message('COM_FINDER_INDEX_CONFIRM_DELETE_PROMPT')
->icon('icon-delete')
->listCheck(true);
$toolbar->divider();
}
if ($canDo->get('core.edit.state')) {
/** @var DropdownButton $dropdown */
$dropdown = $toolbar->dropdownButton('maintenance-group', 'COM_FINDER_INDEX_TOOLBAR_MAINTENANCE')
->toggleSplit(false)
->icon('icon-wrench')
->buttonClass('btn btn-action');
$childBar = $dropdown->getChildToolbar();
$childBar->standardButton('cog', 'COM_FINDER_INDEX_TOOLBAR_OPTIMISE', 'index.optimise');
$childBar->confirmButton('index-purge', 'COM_FINDER_INDEX_TOOLBAR_PURGE', 'index.purge')
->message('COM_FINDER_INDEX_CONFIRM_PURGE_PROMPT')
->icon('icon-trash');
}
$toolbar->popupButton('statistics', 'COM_FINDER_STATISTICS')
->popupType('iframe')
->textHeader(Text::_('COM_FINDER_STATISTICS_TITLE'))
->url('index.php?option=com_finder&view=statistics&tmpl=component')
->modalWidth('800px')
->modalHeight('500px')
->title(Text::_('COM_FINDER_STATISTICS_TITLE'))
->icon('icon-bars');
}
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
$toolbar->preferences('com_finder');
}
$toolbar->help('Smart_Search:_Indexed_Content');
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\View\Indexer;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Toolbar\ToolbarHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Indexer view class for Finder.
*
* @since 2.5
*/
class HtmlView extends BaseHtmlView
{
/**
* @var Form $form
*
* @since 5.0.0
*/
public $form;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 5.0.0
*/
public function display($tpl = null)
{
if ($this->getLayout() == 'debug') {
$this->form = $this->get('Form');
$this->addToolbar();
}
parent::display($tpl);
}
/**
* Method to configure the toolbar for this view.
*
* @return void
*
* @since 5.0.0
*/
protected function addToolbar()
{
/** @var Toolbar $toolbar */
$toolbar = $this->getDocument()->getToolbar();
ToolbarHelper::title(Text::_('COM_FINDER_INDEXER_TOOLBAR_TITLE'), 'search-plus finder');
$toolbar->linkButton('back', 'JTOOLBAR_BACK')
->icon('icon-arrow-' . ($this->getLanguage()->isRtl() ? 'right' : 'left'))
->url(Route::_('index.php?option=com_finder&view=index'));
$toolbar->standardButton('index', 'COM_FINDER_INDEX')
->icon('icon-play')
->onclick('Joomla.debugIndexing();');
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\View\Item;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Toolbar\ToolbarHelper;
/**
* Index view class for Finder.
*
* @since 5.0.0
*/
class HtmlView extends BaseHtmlView
{
/**
* The indexed item
*
* @var object
*
* @since 5.0.0
*/
protected $item;
/**
* The associated terms
*
* @var object[]
*
* @since 5.0.0
*/
protected $terms;
/**
* The associated taxonomies
*
* @var object[]
*
* @since 5.0.0
*/
protected $taxonomies;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 5.0.0
*/
public function display($tpl = null)
{
$this->item = $this->get('Item');
$this->terms = $this->get('Terms');
$this->taxonomies = $this->get('Taxonomies');
// Configure the toolbar.
$this->addToolbar();
parent::display($tpl);
}
/**
* Method to configure the toolbar for this view.
*
* @return void
*
* @since 5.0.0
*/
protected function addToolbar()
{
/** @var Toolbar $toolbar */
$toolbar = $this->getDocument()->getToolbar();
ToolbarHelper::title(Text::_('COM_FINDER_INDEX_TOOLBAR_TITLE'), 'search-plus finder');
$toolbar->linkButton('back', 'JTOOLBAR_BACK')
->icon('icon-arrow-' . ($this->getLanguage()->isRtl() ? 'right' : 'left'))
->url(Route::_('index.php?option=com_finder&view=index'));
}
}

View File

@ -0,0 +1,183 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\View\Maps;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\Button\DropdownButton;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Groups view class for Finder.
*
* @since 2.5
*/
class HtmlView extends BaseHtmlView
{
/**
* An array of items
*
* @var array
*
* @since 3.6.1
*/
protected $items;
/**
* The pagination object
*
* @var \Joomla\CMS\Pagination\Pagination
*
* @since 3.6.1
*/
protected $pagination;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*
* @since 3.6.1
*/
protected $state;
/**
* The total number of items
*
* @var integer
*
* @since 3.6.1
*/
protected $total;
/**
* Form object for search filters
*
* @var \Joomla\CMS\Form\Form
*
* @since 4.0.0
*/
public $filterForm;
/**
* The active search filters
*
* @var array
*
* @since 4.0.0
*/
public $activeFilters;
/**
* @var boolean
*
* @since 4.0.0
*/
private $isEmptyState = false;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 2.5
*/
public function display($tpl = null)
{
// Load plugin language files.
LanguageHelper::loadPluginLanguage();
// Load the view data.
$this->items = $this->get('Items');
$this->total = $this->get('Total');
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
if ($this->total === 0 && $this->isEmptyState = $this->get('isEmptyState')) {
$this->setLayout('emptystate');
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
// Prepare the view.
$this->addToolbar();
parent::display($tpl);
}
/**
* Method to configure the toolbar for this view.
*
* @return void
*
* @since 2.5
*/
protected function addToolbar()
{
$canDo = ContentHelper::getActions('com_finder');
$toolbar = $this->getDocument()->getToolbar();
ToolbarHelper::title(Text::_('COM_FINDER_MAPS_TOOLBAR_TITLE'), 'search-plus finder');
if (!$this->isEmptyState) {
if ($canDo->get('core.edit.state')) {
/** @var DropdownButton $dropdown */
$dropdown = $toolbar->dropdownButton('status-group', 'JTOOLBAR_CHANGE_STATUS')
->toggleSplit(false)
->icon('icon-ellipsis-h')
->buttonClass('btn btn-action')
->listCheck(true);
$childBar = $dropdown->getChildToolbar();
$childBar->publish('maps.publish')->listCheck(true);
$childBar->unpublish('maps.unpublish')->listCheck(true);
}
if ($canDo->get('core.delete')) {
$toolbar->standardButton('delete', 'JTOOLBAR_DELETE', 'maps.delete')
->listCheck(true);
$toolbar->divider();
}
$toolbar->divider();
$toolbar->popupButton('bars', 'COM_FINDER_STATISTICS')
->popupType('iframe')
->textHeader(Text::_('COM_FINDER_STATISTICS_TITLE'))
->url('index.php?option=com_finder&view=statistics&tmpl=component')
->modalWidth('800px')
->modalHeight('500px')
->title(Text::_('COM_FINDER_STATISTICS_TITLE'))
->icon('icon-bars');
$toolbar->divider();
}
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
$toolbar->preferences('com_finder');
}
$toolbar->help('Smart_Search:_Content_Maps');
}
}

View File

@ -0,0 +1,172 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\View\Searches;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Toolbar\ToolbarHelper;
use Joomla\CMS\Uri\Uri;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* View class for a list of search terms.
*
* @since 4.0.0
*/
class HtmlView extends BaseHtmlView
{
/**
* True if gathering search statistics is enabled
*
* @var boolean
*/
protected $enabled;
/**
* An array of items
*
* @var array
*/
protected $items;
/**
* The pagination object
*
* @var \Joomla\CMS\Pagination\Pagination
*/
protected $pagination;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*/
protected $state;
/**
* Form object for search filters
*
* @var \Joomla\CMS\Form\Form
*
* @since 4.0.0
*/
public $filterForm;
/**
* The active search filters
*
* @var array
*
* @since 4.0.0
*/
public $activeFilters;
/**
* The actions the user is authorised to perform
*
* @var \Joomla\Registry\Registry
*
* @since 4.0.0
*/
protected $canDo;
/**
* @var boolean
*
* @since 4.0.0
*/
private $isEmptyState = false;
/**
* Display the view.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return void
*/
public function display($tpl = null)
{
$app = Factory::getApplication();
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
$this->state = $this->get('State');
$this->filterForm = $this->get('FilterForm');
$this->activeFilters = $this->get('ActiveFilters');
$this->enabled = $this->state->params->get('gather_search_statistics', 0);
$this->canDo = ContentHelper::getActions('com_finder');
$uri = Uri::getInstance();
$link = 'index.php?option=com_config&view=component&component=com_finder&return=' . base64_encode($uri);
$output = HTMLHelper::_('link', Route::_($link), Text::_('JOPTIONS'));
if (!\count($this->items) && $this->isEmptyState = $this->get('IsEmptyState')) {
$this->setLayout('emptystate');
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
// Check if component is enabled
if (!$this->enabled) {
// Check if the user has access to the component options
if ($this->canDo->get('core.admin') || $this->canDo->get('core.options')) {
$app->enqueueMessage(Text::sprintf('COM_FINDER_LOGGING_DISABLED', $output), 'warning');
} else {
$app->enqueueMessage(Text::_('COM_FINDER_LOGGING_DISABLED_NO_AUTH'), 'warning');
}
}
// Prepare the view.
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 1.6
*/
protected function addToolbar()
{
$canDo = $this->canDo;
$toolbar = $this->getDocument()->getToolbar();
ToolbarHelper::title(Text::_('COM_FINDER_MANAGER_SEARCHES'), 'search');
if (!$this->isEmptyState) {
if ($canDo->get('core.edit.state')) {
$toolbar->standardButton('reset', 'JSEARCH_RESET', 'searches.reset')
->icon('icon-refresh')
->listCheck(false);
}
$toolbar->divider();
}
if ($canDo->get('core.admin') || $canDo->get('core.options')) {
$toolbar->preferences('com_finder');
}
$toolbar->help('Smart_Search:_Search_Term_Analysis');
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @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\Component\Finder\Administrator\View\Statistics;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Statistics view class for Finder.
*
* @since 2.5
*/
class HtmlView extends BaseHtmlView
{
/**
* The index statistics
*
* @var \Joomla\CMS\Object\CMSObject
*
* @since 3.6.1
*/
protected $data;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 2.5
*/
public function display($tpl = null)
{
// Load the view data.
$this->data = $this->get('Data');
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
parent::display($tpl);
}
}