Closed joomlapl-bot closed 7 months ago
PR w związku ze zmianą oryginału https://github.com/joomla/joomla-cms/pull/36753 Poniżej zmiany w oryginale:
PR w związku ze zmianą oryginału https://github.com/joomla/joomla-cms/pull/36753 Poniżej zmiany w oryginale:
Click to expand the diff!
```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 @@ + + 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 .= ''; + + $output .= ''; + + $output .= ''; + + $output .= ''; + + // 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 @@ + + * @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 @@ + + * @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 @@ + + * @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 @@ + + * @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'); - ?>