title: Excluding all categories except one from indexing - joomla smart search
body: <p>I would like to have joomla automatically re-index all the articles that are in a certain category (and subcategory) without indexing the rest. I am not looking for a cron job. I want joomla to do this when I hit the indexing button. </p>
  
  <p>In other words, all the articles that are not in that particular category or its child subcategories should not be indexed and thus not show up in the search results.</p>
  
  <p>EDIT (more info): This is for a specific site where I know the category id(s). I should probably also add that it is a bilingual site, the search module is appearing and indexing twice on the same page for each language. I.e. it indexes and appears on the English 'blog' page and the German 'blog' page.</p>
  
  <p>Is this possible or do I need to manually delete the indexed files I don't want to show up?</p>
  
  <p><strong>EDIT:</strong> The discover tool is not working, here is the xml file to try and find out why.</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;extension version="3.1" type="plugin" group="finder" method="upgrade"&gt;
      &lt;name&gt;plg_finder_content&lt;/name&gt;
      &lt;author&gt;Joomla! Project&lt;/author&gt;
      &lt;creationDate&gt;August 2011&lt;/creationDate&gt;
      &lt;copyright&gt;(C) 2005 - 2014 Open Source Matters. All rights reserved.&lt;/copyright&gt;
      &lt;license&gt;GNU General Public License version 2 or later; see LICENSE.txt&lt;/license&gt;
      &lt;authorEmail&gt;admin@joomla.org&lt;/authorEmail&gt;
      &lt;authorUrl&gt;www.joomla.org&lt;/authorUrl&gt;
      &lt;version&gt;3.0.0&lt;/version&gt;
      &lt;description&gt;PLG_FINDER_CONTENT_XML_DESCRIPTION&lt;/description&gt;
      &lt;scriptfile&gt;script.php&lt;/scriptfile&gt;
      &lt;files&gt;
          &lt;file plugin="content"&gt;article_selectedcategories.php&lt;/file&gt;
          &lt;filename&gt;index.html&lt;/filename&gt;
      &lt;/files&gt;
      &lt;languages&gt;
          &lt;language tag="en-GB"&gt;language/en-GB/en-GB.plg_finder_content.ini&lt;/language&gt;
          &lt;language tag="en-GB"&gt;language/en-GB/en-GB.plg_finder_content.sys.ini&lt;/language&gt;
      &lt;/languages&gt;
  &lt;/extension&gt;
  </code></pre>
  
  <p><strong>EDIT</strong> The code from the php to find out why it indexes everything and not just category 31.</p>
  
  <pre><code>&lt;?php
  /**
   * @package     Joomla.Plugin
   * @subpackage  Finder.Content
   *
   * @copyright   Copyright (C) 2005 - 2014 Open Source Matters, Inc. All rights reserved.
   * @license     GNU General Public License version 2 or later; see LICENSE
   */
  
  defined('JPATH_BASE') or die;
  
  require_once JPATH_ADMINISTRATOR . '/components/com_finder/helpers/indexer/adapter.php';
  
  /**
   * Smart Search adapter for com_content.
   *
   * @package     Joomla.Plugin
   * @subpackage  Finder.Content
   * @since       2.5
   */
  class PlgFinderContent extends FinderIndexerAdapter
  {
      /**
       * The plugin identifier.
       *
       * @var    string
       * @since  2.5
       */
      protected $context = 'Content';
  
      /**
       * The extension name.
       *
       * @var    string
       * @since  2.5
       */
      protected $extension = 'com_content';
  
      /**
       * The sublayout to use when rendering the results.
       *
       * @var    string
       * @since  2.5
       */
      protected $layout = 'article';
  
      /**
       * The type of content that the adapter indexes.
       *
       * @var    string
       * @since  2.5
       */
      protected $type_title = 'Article';
  
      /**
       * The table name.
       *
       * @var    string
       * @since  2.5
       */
      protected $table = '#__content';
  
      /**
       * Load the language file on instantiation.
       *
       * @var    boolean
       * @since  3.1
       */
      protected $autoloadLanguage = true;
  
      /**
       * Method to update the item link information when the item category is
       * changed. This is fired when the item category is published or unpublished
       * from the list view.
       *
       * @param   string   $extension  The extension whose category has been updated.
       * @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
       */
      public function onFinderCategoryChangeState($extension, $pks, $value)
      {
          // Make sure we're handling com_content categories.
          if ($extension == 'com_content')
          {
              $this-&gt;categoryStateChange($pks, $value);
          }
      }
  
      /**
       * Method to remove the link information for items that have been deleted.
       *
       * @param   string  $context  The context of the action being performed.
       * @param   JTable  $table    A JTable object containing the record to be deleted
       *
       * @return  boolean  True on success.
       *
       * @since   2.5
       * @throws  Exception on database error.
       */
      public function onFinderAfterDelete($context, $table)
      {
          if ($context == 'com_content.article')
          {
              $id = $table-&gt;id;
          }
          elseif ($context == 'com_finder.index')
          {
              $id = $table-&gt;link_id;
          }
          else
          {
              return true;
          }
  
          // Remove item from the index.
          return $this-&gt;remove($id);
      }
  
      /**
       * Smart Search after save content method.
       * Reindexes the link information for an article that has been saved.
       * It also makes adjustments if the access level of an item or the
       * category to which it belongs has changed.
       *
       * @param   string   $context  The context of the content passed to the plugin.
       * @param   JTable   $row      A JTable object.
       * @param   boolean  $isNew    True if the content has just been created.
       *
       * @return  boolean  True on success.
       *
       * @since   2.5
       * @throws  Exception on database error.
       */
      public function onFinderAfterSave($context, $row, $isNew)
      {
          // We only want to handle articles here.
          if ($context == 'com_content.article' || $context == 'com_content.form')
          {
              // Check if the access levels are different.
              if (!$isNew &amp;&amp; $this-&gt;old_access != $row-&gt;access)
              {
                  // Process the change.
                  $this-&gt;itemAccessChange($row);
              }
  
              // Reindex the item.
              $this-&gt;reindex($row-&gt;id);
          }
  
          // Check for access changes in the category.
          if ($context == 'com_categories.category')
          {
              // Check if the access levels are different.
              if (!$isNew &amp;&amp; $this-&gt;old_cataccess != $row-&gt;access)
              {
                  $this-&gt;categoryAccessChange($row);
              }
          }
  
          return true;
      }
  
      /**
       * Smart Search before content save method.
       * This event is fired before the data is actually saved.
       *
       * @param   string   $context  The context of the content passed to the plugin.
       * @param   JTable   $row      A JTable object.
       * @param   boolean  $isNew    If the content is just about to be created.
       *
       * @return  boolean  True on success.
       *
       * @since   2.5
       * @throws  Exception on database error.
       */
      public function onFinderBeforeSave($context, $row, $isNew)
      {
          // We only want to handle articles here.
          if ($context == 'com_content.article' || $context == 'com_content.form')
          {
              // Query the database for the old access level if the item isn't new.
              if (!$isNew)
              {
                  $this-&gt;checkItemAccess($row);
              }
          }
  
          // Check for access levels from the category.
          if ($context == 'com_categories.category')
          {
              // Query the database for the old access level if the item isn't new.
              if (!$isNew)
              {
                  $this-&gt;checkCategoryAccess($row);
              }
          }
  
          return true;
      }
  
      /**
       * Method to update the link information for items that have been changed
       * from outside the edit screen. This is fired when the item is published,
       * unpublished, archived, or unarchived from the list view.
       *
       * @param   string   $context  The context for the content passed to the plugin.
       * @param   array    $pks      An array 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
       */
      public function onFinderChangeState($context, $pks, $value)
      {
          // We only want to handle articles here.
          if ($context == 'com_content.article' || $context == 'com_content.form')
          {
              $this-&gt;itemStateChange($pks, $value);
          }
  
          // Handle when the plugin is disabled.
          if ($context == 'com_plugins.plugin' &amp;&amp; $value === 0)
          {
              $this-&gt;pluginDisable($pks);
          }
      }
  
      /**
       * Method to index an item. The item must be a FinderIndexerResult object.
       *
       * @param   FinderIndexerResult  $item    The item to index as an FinderIndexerResult object.
       * @param   string               $format  The item format.  Not used.
       *
       * @return  void
       *
       * @since   2.5
       * @throws  Exception on database error.
       */
      protected function index(FinderIndexerResult $item, $format = 'html')
      {
          $item-&gt;setLanguage();
  
          // Check if the extension is enabled.
          if (JComponentHelper::isEnabled($this-&gt;extension) == false)
          {
              return;
          }
  
          // Initialise the item parameters.
          $registry = new JRegistry;
          $registry-&gt;loadString($item-&gt;params);
          $item-&gt;params = JComponentHelper::getParams('com_content', true);
          $item-&gt;params-&gt;merge($registry);
  
          $registry = new JRegistry;
          $registry-&gt;loadString($item-&gt;metadata);
          $item-&gt;metadata = $registry;
  
          // Trigger the onContentPrepare event.
          $item-&gt;summary = FinderIndexerHelper::prepareContent($item-&gt;summary, $item-&gt;params);
          $item-&gt;body = FinderIndexerHelper::prepareContent($item-&gt;body, $item-&gt;params);
  
          // Build the necessary route and path information.
          $item-&gt;url = $this-&gt;getURL($item-&gt;id, $this-&gt;extension, $this-&gt;layout);
          $item-&gt;route = ContentHelperRoute::getArticleRoute($item-&gt;slug, $item-&gt;catslug, $item-&gt;language);
          $item-&gt;path = FinderIndexerHelper::getContentPath($item-&gt;route);
  
          // Get the menu title if it exists.
          $title = $this-&gt;getItemMenuTitle($item-&gt;url);
  
          // Adjust the title if necessary.
          if (!empty($title) &amp;&amp; $this-&gt;params-&gt;get('use_menu_title', true))
          {
              $item-&gt;title = $title;
          }
  
          // Add the meta-author.
          $item-&gt;metaauthor = $item-&gt;metadata-&gt;get('author');
  
          // Add the meta-data processing instructions.
          $item-&gt;addInstruction(FinderIndexer::META_CONTEXT, 'metakey');
          $item-&gt;addInstruction(FinderIndexer::META_CONTEXT, 'metadesc');
          $item-&gt;addInstruction(FinderIndexer::META_CONTEXT, 'metaauthor');
          $item-&gt;addInstruction(FinderIndexer::META_CONTEXT, 'author');
          $item-&gt;addInstruction(FinderIndexer::META_CONTEXT, 'created_by_alias');
  
          // Translate the state. Articles should only be published if the category is published.
          $item-&gt;state = $this-&gt;translateState($item-&gt;state, $item-&gt;cat_state);
  
          // Add the type taxonomy data.
          $item-&gt;addTaxonomy('Type', 'Article');
  
          // Add the author taxonomy data.
          if (!empty($item-&gt;author) || !empty($item-&gt;created_by_alias))
          {
              $item-&gt;addTaxonomy('Author', !empty($item-&gt;created_by_alias) ? $item-&gt;created_by_alias : $item-&gt;author);
          }
  
          // Add the category taxonomy data.
          $item-&gt;addTaxonomy('Category', $item-&gt;category, $item-&gt;cat_state, $item-&gt;cat_access);
  
          // Add the language taxonomy data.
          $item-&gt;addTaxonomy('Language', $item-&gt;language);
  
          // Get content extras.
          FinderIndexerHelper::getContentExtras($item);
  
          // Index the item.
          $this-&gt;indexer-&gt;index($item);
      }
  
      /**
       * Method to setup the indexer to be run.
       *
       * @return  boolean  True on success.
       *
       * @since   2.5
       */
      protected function setup()
      {
          // Load dependent classes.
          include_once JPATH_SITE . '/components/com_content/helpers/route.php';
  
          return true;
      }
  
      /**
       * Method to get the SQL query used to retrieve the list of content items.
       *
       * @param   mixed  $query  A JDatabaseQuery object or null.
       *
       * @return  JDatabaseQuery  A database object.
       *
       * @since   2.5
       */
      protected function getListQuery($query = null)
      {
          $db = JFactory::getDbo();
  
          // Check if we can use the supplied SQL query.
          $query = $query instanceof JDatabaseQuery ? $query : $db-&gt;getQuery(true)
              -&gt;select('a.id, a.title, a.alias, a.introtext AS summary, a.fulltext AS body')
              -&gt;select('a.state, a.catid, a.created AS start_date, a.created_by')
              -&gt;select('a.created_by_alias, a.modified, a.modified_by, a.attribs AS params')
              -&gt;select('a.metakey, a.metadesc, a.metadata, a.language, a.access, a.version, a.ordering')
              -&gt;select('a.publish_up AS publish_start_date, a.publish_down AS publish_end_date')
              -&gt;select('c.title AS category, c.published AS cat_state, c.access AS cat_access');
          $query-&gt;where('a.catid = 31');
  
          // Handle the alias CASE WHEN portion of the query
          $case_when_item_alias = ' CASE WHEN ';
          $case_when_item_alias .= $query-&gt;charLength('a.alias', '!=', '0');
          $case_when_item_alias .= ' THEN ';
          $a_id = $query-&gt;castAsChar('a.id');
          $case_when_item_alias .= $query-&gt;concatenate(array($a_id, 'a.alias'), ':');
          $case_when_item_alias .= ' ELSE ';
          $case_when_item_alias .= $a_id.' END as slug';
          $query-&gt;select($case_when_item_alias);
  
          $case_when_category_alias = ' CASE WHEN ';
          $case_when_category_alias .= $query-&gt;charLength('c.alias', '!=', '0');
          $case_when_category_alias .= ' THEN ';
          $c_id = $query-&gt;castAsChar('c.id');
          $case_when_category_alias .= $query-&gt;concatenate(array($c_id, 'c.alias'), ':');
          $case_when_category_alias .= ' ELSE ';
          $case_when_category_alias .= $c_id.' END as catslug';
          $query-&gt;select($case_when_category_alias)
  
              -&gt;select('u.name AS author')
              -&gt;from('#__content AS a')
              -&gt;join('LEFT', '#__categories AS c ON c.id = a.catid')
              -&gt;join('LEFT', '#__users AS u ON u.id = a.created_by');
  
          return $query;
      }
  }
  </code></pre>
  
  <p>I added the whole thing, just search for "where" to find the part that I changed. Everything else I did not touch.</p>