-
-
Notifications
You must be signed in to change notification settings - Fork 46
Closed
Description
New language relevant PR in upstream repo: joomla/joomla-cms#36753 Here are the upstream changes:
Click to expand the diff!
diff --git a/administrator/components/com_finder/forms/indexer.xml b/administrator/components/com_finder/forms/indexer.xml
new file mode 100644
index 000000000000..a9559102a0c4
--- /dev/null
+++ b/administrator/components/com_finder/forms/indexer.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<form>
+ <fieldset name="form">
+ <field
+ name="plugin"
+ type="plugins"
+ label="COM_FINDER_FIELD_FINDER_PLUGIN_LABEL"
+ folder="finder"
+ required="true"
+ />
+
+ <field
+ name="id"
+ type="text"
+ label="JGLOBAL_FIELD_ID_LABEL"
+ required="true"
+ />
+ </fieldset>
+</form>
diff --git a/administrator/components/com_finder/src/Controller/IndexerController.php b/administrator/components/com_finder/src/Controller/IndexerController.php
index 509750020562..51a398129a6f 100644
--- a/administrator/components/com_finder/src/Controller/IndexerController.php
+++ b/administrator/components/com_finder/src/Controller/IndexerController.php
@@ -17,6 +17,9 @@
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;
@@ -147,22 +150,6 @@ public function batch()
// Import the finder plugins.
PluginHelper::importPlugin('finder');
- /*
- * 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 = Factory::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.
@@ -281,4 +268,112 @@ public static function sendResponse($data = null)
// Send the JSON response.
echo json_encode($response);
}
+
+ /**
+ * Method to call a specific indexing plugin and return debug info
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ * @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 = Factory::getApplication()->bootPlugin($this->app->input->get('plugin'), 'finder');
+ $plugin->setIndexer(new DebugIndexer());
+ $plugin->debug($this->app->input->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">' . $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">' . $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">' . 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">' . 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);
+ }
+ }
}
diff --git a/administrator/components/com_finder/src/Indexer/DebugAdapter.php b/administrator/components/com_finder/src/Indexer/DebugAdapter.php
new file mode 100644
index 000000000000..3e0ffa74b5e3
--- /dev/null
+++ b/administrator/components/com_finder/src/Indexer/DebugAdapter.php
@@ -0,0 +1,952 @@
+<?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 Exception;
+use Joomla\CMS\Plugin\CMSPlugin;
+use Joomla\CMS\Table\Table;
+use Joomla\Database\DatabaseInterface;
+use Joomla\Database\QueryInterface;
+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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ */
+ protected $context;
+
+ /**
+ * The extension name.
+ *
+ * @var string
+ * @since __DEPLOY_VERSION__
+ */
+ protected $extension;
+
+ /**
+ * The sublayout to use when rendering the results.
+ *
+ * @var string
+ * @since __DEPLOY_VERSION__
+ */
+ protected $layout;
+
+ /**
+ * The mime type of the content the adapter indexes.
+ *
+ * @var string
+ * @since __DEPLOY_VERSION__
+ */
+ protected $mime;
+
+ /**
+ * The access level of an item before save.
+ *
+ * @var integer
+ * @since __DEPLOY_VERSION__
+ */
+ protected $old_access;
+
+ /**
+ * The access level of a category before save.
+ *
+ * @var integer
+ * @since __DEPLOY_VERSION__
+ */
+ protected $old_cataccess;
+
+ /**
+ * The type of content the adapter indexes.
+ *
+ * @var string
+ * @since __DEPLOY_VERSION__
+ */
+ protected $type_title;
+
+ /**
+ * The type id of the content.
+ *
+ * @var integer
+ * @since __DEPLOY_VERSION__
+ */
+ protected $type_id;
+
+ /**
+ * The database object.
+ *
+ * @var DatabaseInterface
+ * @since __DEPLOY_VERSION__
+ */
+ protected $db;
+
+ /**
+ * The table name.
+ *
+ * @var string
+ * @since __DEPLOY_VERSION__
+ */
+ protected $table;
+
+ /**
+ * The indexer object.
+ *
+ * @var Indexer
+ * @since __DEPLOY_VERSION__
+ */
+ protected $indexer;
+
+ /**
+ * The field the published state is stored in.
+ *
+ * @var string
+ * @since __DEPLOY_VERSION__
+ */
+ protected $state_field = 'state';
+
+ /**
+ * Method to instantiate the indexer adapter.
+ *
+ * @param object $subject The object to observe.
+ * @param array $config An array that holds the plugin configuration.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function __construct(&$subject, $config)
+ {
+ // Call the parent constructor.
+ parent::__construct($subject, $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);
+ }
+
+ /**
+ * Method to get the adapter state and push it into the indexer.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ * @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.
+ for ($i = 0, $n = count($items); $i < $n; $i++) {
+ // Index the item.
+ $this->index($items[$i]);
+
+ // 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 ___DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ * @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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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) {
+ // Published and archived items only should return a published state
+ case 1:
+ case 2:
+ return 1;
+
+ // All other states should return an unpublished state
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Debug method to set the used indexer
+ *
+ * @param Indexer $indexer Indexer object
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ public function debug($id)
+ {
+ // Run the setup method.
+ $this->setup();
+
+ // Get the item.
+ $item = $this->getItem($id);
+
+ // Index the item.
+ $this->index($item);
+ }
+}
diff --git a/administrator/components/com_finder/src/Indexer/DebugIndexer.php b/administrator/components/com_finder/src/Indexer/DebugIndexer.php
new file mode 100644
index 000000000000..03f5743bf00d
--- /dev/null
+++ b/administrator/components/com_finder/src/Indexer/DebugIndexer.php
@@ -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 __DEPLOY_VERSION__
+ * @internal
+ */
+class DebugIndexer extends Indexer
+{
+ /**
+ * The result object from the last call to self::index()
+ *
+ * @var Result
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ public function index($item, $format = 'html')
+ {
+ self::$item = $item;
+ }
+}
diff --git a/administrator/components/com_finder/src/Model/IndexerModel.php b/administrator/components/com_finder/src/Model/IndexerModel.php
index 46e8b6cd0d99..3328ce94788e 100644
--- a/administrator/components/com_finder/src/Model/IndexerModel.php
+++ b/administrator/components/com_finder/src/Model/IndexerModel.php
@@ -10,7 +10,8 @@
namespace Joomla\Component\Finder\Administrator\Model;
-use Joomla\CMS\MVC\Model\BaseDatabaseModel;
+use Joomla\CMS\Form\Form;
+use Joomla\CMS\MVC\Model\FormModel;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
@@ -21,6 +22,29 @@
*
* @since 2.5
*/
-class IndexerModel extends BaseDatabaseModel
+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 __DEPLOY_VERSION__
+ *
+ * @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;
+ }
}
diff --git a/administrator/components/com_finder/src/Model/ItemModel.php b/administrator/components/com_finder/src/Model/ItemModel.php
new file mode 100644
index 000000000000..66360f75f61b
--- /dev/null
+++ b/administrator/components/com_finder/src/Model/ItemModel.php
@@ -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 __DEPLOY_VERSION__
+ */
+class ItemModel extends BaseDatabaseModel
+{
+ /**
+ * Stock method to auto-populate the model state.
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function populateState()
+ {
+ // Get the pk of the record from the request.
+ $pk = Factory::getApplication()->input->getInt('id');
+ $this->setState('item.link_id', $pk);
+ }
+
+ /**
+ * Get a finder link object
+ *
+ * @return object
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ 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();
+ }
+}
diff --git a/administrator/components/com_finder/src/View/Index/HtmlView.php b/administrator/components/com_finder/src/View/Index/HtmlView.php
index 5cd83861e7bf..5c611a8607ee 100644
--- a/administrator/components/com_finder/src/View/Index/HtmlView.php
+++ b/administrator/components/com_finder/src/View/Index/HtmlView.php
@@ -174,13 +174,35 @@ protected function addToolbar()
ToolbarHelper::title(Text::_('COM_FINDER_INDEX_TOOLBAR_TITLE'), 'search-plus finder');
- $toolbar->popupButton('archive', 'COM_FINDER_INDEX')
- ->url('index.php?option=com_finder&view=indexer&tmpl=component')
- ->iframeWidth(550)
- ->iframeHeight(210)
- ->onclose('window.parent.location.reload()')
- ->icon('icon-archive')
- ->title(Text::_('COM_FINDER_HEADING_INDEXER'));
+ 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')
+ ->url('index.php?option=com_finder&view=indexer&tmpl=component')
+ ->icon('icon-archive')
+ ->iframeWidth(500)
+ ->iframeHeight(210)
+ ->onclose('window.parent.location.reload()')
+ ->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')
+ ->url('index.php?option=com_finder&view=indexer&tmpl=component')
+ ->icon('icon-archive')
+ ->iframeWidth(500)
+ ->iframeHeight(210)
+ ->onclose('window.parent.location.reload()')
+ ->title(Text::_('COM_FINDER_HEADING_INDEXER'));
+ }
if (!$this->isEmptyState) {
if ($canDo->get('core.edit.state')) {
diff --git a/administrator/components/com_finder/src/View/Indexer/HtmlView.php b/administrator/components/com_finder/src/View/Indexer/HtmlView.php
index e13214d3d24c..a65d408621be 100644
--- a/administrator/components/com_finder/src/View/Indexer/HtmlView.php
+++ b/administrator/components/com_finder/src/View/Indexer/HtmlView.php
@@ -10,7 +10,14 @@
namespace Joomla\Component\Finder\Administrator\View\Indexer;
+use Joomla\CMS\Factory;
+use Joomla\CMS\Form\Form;
+use Joomla\CMS\Helper\ContentHelper;
+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;
@@ -23,4 +30,55 @@
*/
class HtmlView extends BaseHtmlView
{
+ /**
+ * @var Form $form
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public $form;
+
+ /**
+ * Method to display the view.
+ *
+ * @param string $tpl A template file to load. [optional]
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ protected function addToolbar()
+ {
+ $toolbar = Toolbar::getInstance('toolbar');
+
+ ToolbarHelper::title(Text::_('COM_FINDER_INDEXER_TOOLBAR_TITLE'), 'search-plus finder');
+
+ $arrow = Factory::getLanguage()->isRtl() ? 'arrow-right' : 'arrow-left';
+
+ ToolbarHelper::link(
+ Route::_('index.php?option=com_finder&view=index'),
+ 'JTOOLBAR_BACK',
+ $arrow
+ );
+
+ $toolbar->standardButton('index', 'COM_FINDER_INDEX')
+ ->icon('icon-play')
+ ->onclick('Joomla.debugIndexing();');
+ }
}
diff --git a/administrator/components/com_finder/src/View/Item/HtmlView.php b/administrator/components/com_finder/src/View/Item/HtmlView.php
new file mode 100644
index 000000000000..b3c8624b400f
--- /dev/null
+++ b/administrator/components/com_finder/src/View/Item/HtmlView.php
@@ -0,0 +1,84 @@
+<?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\Toolbar\ToolbarHelper;
+
+/**
+ * Index view class for Finder.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class HtmlView extends BaseHtmlView
+{
+ /**
+ * The indexed item
+ *
+ * @var object
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected $item;
+
+ /**
+ * The associated terms
+ *
+ * @var object[]
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected $terms;
+
+ /**
+ * The associated taxonomies
+ *
+ * @var object[]
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected $taxonomies;
+
+ /**
+ * Method to display the view.
+ *
+ * @param string $tpl A template file to load. [optional]
+ *
+ * @return void
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ 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 __DEPLOY_VERSION__
+ */
+ protected function addToolbar()
+ {
+ ToolbarHelper::title(Text::_('COM_FINDER_INDEX_TOOLBAR_TITLE'), 'search-plus finder');
+ ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_finder&view=index');
+ }
+}
diff --git a/administrator/components/com_finder/tmpl/index/default.php b/administrator/components/com_finder/tmpl/index/default.php
index c51294871e46..b30d081cf782 100644
--- a/administrator/components/com_finder/tmpl/index/default.php
+++ b/administrator/components/com_finder/tmpl/index/default.php
@@ -26,7 +26,6 @@
$wa = $this->document->getWebAssetManager();
$wa->useScript('multiselect')
->useScript('table.columns');
-
?>
<form action="<?php echo Route::_('index.php?option=com_finder&view=index'); ?>" method="post" name="adminForm" id="adminForm">
<div class="row">
@@ -111,7 +110,13 @@
<?php echo HTMLHelper::_('jgrid.published', $item->published, $i, 'index.', $canChange, 'cb'); ?>
</td>
<th scope="row">
- <?php echo $this->escape($item->title); ?>
+ <?php if (JDEBUG) : ?>
+ <a href="index.php?option=com_finder&view=item&id=<?php echo $item->link_id; ?>">
+ <?php echo $this->escape($item->title); ?>
+ </a>
+ <?php else : ?>
+ <?php echo $this->escape($item->title); ?>
+ <?php endif; ?>
</th>
<td class="small d-none d-md-table-cell">
<?php
diff --git a/administrator/components/com_finder/tmpl/indexer/debug.php b/administrator/components/com_finder/tmpl/indexer/debug.php
new file mode 100644
index 000000000000..b4247b72025e
--- /dev/null
+++ b/administrator/components/com_finder/tmpl/indexer/debug.php
@@ -0,0 +1,61 @@
+<?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
+ */
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Factory;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\Router\Route;
+
+/** @var Joomla\Component\Finder\Administrator\View\Indexer\HtmlView $this */
+
+Text::script('COM_FINDER_INDEXER_MESSAGE_COMPLETE', true);
+
+/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
+$wa = $this->document->getWebAssetManager();
+$wa->useScript('keepalive')
+ ->useScript('com_finder.debug');
+
+?>
+
+<form action="<?php echo Route::_('index.php?option=com_finder&layout=debug'); ?>" method="post" name="adminForm" id="debug-form">
+ <div class="form-horizontal">
+ <div class="card mt-3">
+ <div class="card-body">
+ <fieldset class="adminform p-4">
+ <div class="alert alert-info">
+ <h2 class="alert-heading"><?php echo Text::_('COM_FINDER_INDEXER_MSG_DEBUGGING_INDEXING'); ?></h2>
+ <?php echo Text::_('COM_FINDER_INDEXER_MSG_DEBUGGING_INDEXING_TEXT'); ?>
+ </div>
+ <?php echo $this->form->renderField('plugin'); ?>
+ <?php echo $this->form->renderField('id'); ?>
+
+ <input id="finder-indexer-token" type="hidden" name="<?php echo Factory::getSession()->getFormToken(); ?>" value="1">
+ </fieldset>
+ </div>
+ </div>
+ </div>
+</form>
+
+<div class="form-horizontal">
+ <div class="card mt-3">
+ <div class="card-body">
+ <fieldset class="adminform">
+ <legend><?php echo Text::_('COM_FINDER_INDEXER_OUTPUT_AREA_TITLE'); ?></legend>
+ <div id="indexer-output" class="border p-3" style="min-height:200px;">
+
+ </div>
+ </fieldset>
+ </div>
+ </div>
+</div>
+
+
+
diff --git a/administrator/components/com_finder/tmpl/item/default.php b/administrator/components/com_finder/tmpl/item/default.php
new file mode 100644
index 000000000000..c76160c693c0
--- /dev/null
+++ b/administrator/components/com_finder/tmpl/item/default.php
@@ -0,0 +1,100 @@
+<?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
+ */
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Language\Text;
+?>
+<div role="main">
+ <h1 class="mb-3"><?php echo $this->item->title; ?></h1>
+ <div class="card mb-3">
+ <div class="card-header"><h2><?php echo Text::_('COM_FINDER_ITEM_FIELDSET_ITEM_TITLE'); ?></h2></div>
+ <div class="card-body">
+ <dl class="row">
+ <?php foreach ($this->item as $key => $value) : ?>
+ <dt class="col-sm-3"><?php echo $key; ?></dt>
+ <dd class="col-sm-9<?php echo $key == 'object' ? ' text-break' : '';?>"><?php echo $value; ?></dd>
+ <?php endforeach; ?>
+ </dl>
+ </div>
+ </div>
+ <div class="card mb-3">
+ <div class="card-header"><h2><?php echo Text::_('COM_FINDER_ITEM_FIELDSET_TERMS_TITLE'); ?></h2></div>
+ <div class="card-body">
+ <table class="table">
+ <caption class="visually-hidden">
+ <?php echo Text::_('COM_FINDER_ITEM_TERMS_TABLE_CAPTION'); ?>,
+ </caption>
+ <thead>
+ <tr>
+ <th scope="col">id</th>
+ <th scope="col">term</th>
+ <th scope="col">stem</th>
+ <th scope="col">common</th>
+ <th scope="col">phrase</th>
+ <th scope="col">weight</th>
+ <th scope="col">links</th>
+ <th scope="col">language</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($this->terms as $term) : ?>
+ <tr>
+ <th scope="row"><?php echo $term->term_id; ?></th>
+ <td><?php echo $term->term; ?></td>
+ <td><?php echo $term->stem; ?></td>
+ <td><?php echo $term->common; ?></td>
+ <td><?php echo $term->phrase; ?></td>
+ <td><?php echo $term->weight; ?></td>
+ <td><?php echo $term->links; ?></td>
+ <td><?php echo $term->language; ?></td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ <div class="card mb-3">
+ <div class="card-header"><h2><?php echo Text::_('COM_FINDER_ITEM_FIELDSET_TAXONOMIES_TITLE'); ?></h2></div>
+ <div class="card-body">
+ <table class="table">
+ <caption class="visually-hidden">
+ <?php echo Text::_('COM_FINDER_ITEM_TAXONOMIES_TABLE_CAPTION'); ?>,
+ </caption>
+ <thead>
+ <tr>
+ <th scope="col">id</th>
+ <th scope="col">title</th>
+ <th scope="col">alias</th>
+ <th scope="col">lft</th>
+ <th scope="col">path</th>
+ <th scope="col">state</th>
+ <th scope="col">access</th>
+ <th scope="col">language</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($this->taxonomies as $taxonomy) : ?>
+ <tr>
+ <th scope="row"><?php echo $taxonomy->id; ?></th>
+ <td><?php echo $taxonomy->title; ?></td>
+ <td><?php echo $taxonomy->alias; ?></td>
+ <td><?php echo $taxonomy->lft; ?></td>
+ <td><?php echo $taxonomy->path; ?></td>
+ <td><?php echo $taxonomy->state; ?></td>
+ <td><?php echo $taxonomy->access; ?></td>
+ <td><?php echo $taxonomy->language; ?></td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ </div>
+ </div>
+</div>
diff --git a/administrator/language/en-GB/com_finder.ini b/administrator/language/en-GB/com_finder.ini
index 5630de653ae4..1bf0cf4df979 100644
--- a/administrator/language/en-GB/com_finder.ini
+++ b/administrator/language/en-GB/com_finder.ini
@@ -72,6 +72,7 @@ COM_FINDER_EMPTYSTATE_CONTENT="No content has been indexed or you have deleted a
COM_FINDER_EMPTYSTATE_SEARCHES_CONTENT="There are no phrases used for site searching to view yet."
COM_FINDER_FIELD_CREATED_BY_ALIAS_LABEL="Alias"
COM_FINDER_FIELD_CREATED_BY_LABEL="Created By"
+COM_FINDER_FIELD_FINDER_PLUGIN_LABEL="Finder Plugin"
COM_FINDER_FIELDSET_INDEX_OPTIONS_DESCRIPTION="These options influence how the content is indexed. After changing settings here, the index needs to be rebuilt."
COM_FINDER_FIELDSET_INDEX_OPTIONS_LABEL="Index"
COM_FINDER_FIELDSET_SEARCH_OPTIONS_LABEL="Smart Search"
@@ -152,11 +153,16 @@ COM_FINDER_INDEX_SEARCH_DESC="Search in title, URL and last updated date."
COM_FINDER_INDEX_SEARCH_LABEL="Search Indexed Content"
COM_FINDER_INDEX_TABLE_CAPTION="Indexed Content"
COM_FINDER_INDEX_TIP="Start the indexer by pressing the button below, or in the toolbar."
+COM_FINDER_INDEX_TOOLBAR_INDEX_DEBUGGING="Index Debugging"
COM_FINDER_INDEX_TOOLBAR_MAINTENANCE="Maintenance"
COM_FINDER_INDEX_TOOLBAR_OPTIMISE="Optimise"
COM_FINDER_INDEX_TOOLBAR_PURGE="Clear Index"
COM_FINDER_INDEX_TOOLBAR_TITLE="Smart Search: Indexed Content"
COM_FINDER_INDEX_TYPE_FILTER="Any Type of Content"
+COM_FINDER_INDEXER_FIELDSET_ATTRIBUTES="Result Object"
+COM_FINDER_INDEXER_FIELDSET_ELEMENTS="Additional Elements"
+COM_FINDER_INDEXER_FIELDSET_INSTRUCTIONS="Instructions"
+COM_FINDER_INDEXER_FIELDSET_TAXONOMIES="Taxonomies"
COM_FINDER_INDEXER_HEADER_COMPLETE="Indexing Complete"
COM_FINDER_INDEXER_HEADER_ERROR="An Error Has Occurred"
COM_FINDER_INDEXER_HEADER_INIT="Starting Indexer"
@@ -169,6 +175,15 @@ COM_FINDER_INDEXER_MESSAGE_COMPLETE="The indexing process is complete. It is now
COM_FINDER_INDEXER_MESSAGE_INIT="The indexer is starting. Do not close this window."
COM_FINDER_INDEXER_MESSAGE_OPTIMIZE="The index tables are being optimised for the best possible performance. Do not close this window."
COM_FINDER_INDEXER_MESSAGE_RUNNING="Your content is being indexed. Do not close this window."
+COM_FINDER_INDEXER_MSG_DEBUGGING_INDEXING="Debugging Smart Search indexing plugins"
+COM_FINDER_INDEXER_MSG_DEBUGGING_INDEXING_TEXT="Select a Smart Search plugin and provide an ID to index. The result of that plugin for that ID will then be displayed in the below area."
+COM_FINDER_INDEXER_OUTPUT_AREA_TITLE="Output"
+COM_FINDER_INDEXER_TOOLBAR_TITLE="Indexer: Debug Mode"
+COM_FINDER_ITEM_FIELDSET_ITEM_TITLE="Item attributes"
+COM_FINDER_ITEM_FIELDSET_TAXONOMIES_TITLE="Taxonomies"
+COM_FINDER_ITEM_FIELDSET_TERMS_TITLE="Terms"
+COM_FINDER_ITEM_TAXONOMIES_TABLE_CAPTION="Table of taxonomies"
+COM_FINDER_ITEM_TERMS_TABLE_CAPTION="Table of terms"
COM_FINDER_ITEM_X_ONLY="%s Only"
COM_FINDER_ITEMS="Content"
COM_FINDER_LOGGING_DISABLED="Gathering of statistics is disabled. Enable it in the %s."
diff --git a/build/media_source/com_finder/joomla.asset.json b/build/media_source/com_finder/joomla.asset.json
index 8d562f35ec7d..6f964164c45d 100644
--- a/build/media_source/com_finder/joomla.asset.json
+++ b/build/media_source/com_finder/joomla.asset.json
@@ -10,6 +10,29 @@
"type": "style",
"uri": "com_finder/dates.min.css"
},
+ {
+ "name": "com_finder.debug.es5",
+ "type": "script",
+ "uri": "com_finder/debug-es5.min.js",
+ "dependencies": [
+ "core"
+ ],
+ "attributes": {
+ "nomodule": true,
+ "defer": true
+ }
+ },
+ {
+ "name": "com_finder.debug",
+ "type": "script",
+ "uri": "com_finder/debug.min.js",
+ "dependencies": [
+ "com_finder.debug.es5"
+ ],
+ "attributes": {
+ "type": "module"
+ }
+ },
{
"name": "com_finder.filters.es5",
"type": "script",
diff --git a/build/media_source/com_finder/js/debug.es6.js b/build/media_source/com_finder/js/debug.es6.js
new file mode 100644
index 000000000000..274ba59c49d3
--- /dev/null
+++ b/build/media_source/com_finder/js/debug.es6.js
@@ -0,0 +1,46 @@
+/**
+ * @copyright (C) 2022 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+// eslint-disable no-alert
+((Joomla, document) => {
+ 'use strict';
+
+ if (!Joomla) {
+ throw new Error('core.js was not properly initialised');
+ }
+
+ Joomla.finderIndexer = () => {
+ const path = 'index.php?option=com_finder&task=indexer.debug&tmpl=component&format=json';
+ const token = `&${document.getElementById('finder-indexer-token').getAttribute('name')}=1`;
+
+ Joomla.debugIndexing = () => {
+ const formEls = new URLSearchParams(Array.from(new FormData(document.getElementById('debug-form')))).toString();
+ Joomla.request({
+ url: `${path}${token}&${formEls}`,
+ method: 'GET',
+ data: '',
+ perform: true,
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ onSuccess: (response) => {
+ const output = document.getElementById('indexer-output');
+ try {
+ const parsed = JSON.parse(response);
+ output.innerHTML = parsed.rendered;
+ } catch (e) {
+ output.innerHTML = response;
+ }
+ },
+ onError: (xhr) => {
+ const output = document.getElementById('indexer-output');
+ output.innerHTML = xhr.response;
+ },
+ });
+ };
+ };
+})(Joomla, document);
+
+// @todo use directly the Joomla.finderIndexer() instead of the Indexer()!!!
+document.addEventListener('DOMContentLoaded', () => {
+ window.Indexer = Joomla.finderIndexer();
+});