[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> CategoryFinder.php (source)

   1  <?php
   2  /**
   3   * Recent changes filtering by category.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   */
  22  
  23  /**
  24   * The "CategoryFinder" class takes a list of articles, creates an internal
  25   * representation of all their parent categories (as well as parents of
  26   * parents etc.). From this representation, it determines which of these
  27   * articles are in one or all of a given subset of categories.
  28   *
  29   * Example use :
  30   * <code>
  31   *     # Determines whether the article with the page_id 12345 is in both
  32   *     # "Category 1" and "Category 2" or their subcategories, respectively
  33   *
  34   *     $cf = new CategoryFinder;
  35   *     $cf->seed(
  36   *         array( 12345 ),
  37   *         array( 'Category 1', 'Category 2' ),
  38   *         'AND'
  39   *     );
  40   *     $a = $cf->run();
  41   *     print implode( ',' , $a );
  42   * </code>
  43   *
  44   */
  45  class CategoryFinder {
  46      /** @var int[] The original article IDs passed to the seed function */
  47      protected $articles = array();
  48  
  49      /** @var array Array of DBKEY category names for categories that don't have a page */
  50      protected $deadend = array();
  51  
  52      /** @var array Array of [ID => array()] */
  53      protected $parents = array();
  54  
  55      /** @var array Array of article/category IDs */
  56      protected $next = array();
  57  
  58      /** @var array Array of DBKEY category names */
  59      protected $targets = array();
  60  
  61      /** @var array */
  62      protected $name2id = array();
  63  
  64      /** @var string "AND" or "OR" */
  65      protected $mode;
  66  
  67      /** @var DatabaseBase Read-DB slave */
  68      protected $dbr;
  69  
  70      /**
  71       * Initializes the instance. Do this prior to calling run().
  72       * @param array $articleIds Array of article IDs
  73       * @param array $categories FIXME
  74       * @param string $mode FIXME, default 'AND'.
  75       * @todo FIXME: $categories/$mode
  76       */
  77  	public function seed( $articleIds, $categories, $mode = 'AND' ) {
  78          $this->articles = $articleIds;
  79          $this->next = $articleIds;
  80          $this->mode = $mode;
  81  
  82          # Set the list of target categories; convert them to DBKEY form first
  83          $this->targets = array();
  84          foreach ( $categories as $c ) {
  85              $ct = Title::makeTitleSafe( NS_CATEGORY, $c );
  86              if ( $ct ) {
  87                  $c = $ct->getDBkey();
  88                  $this->targets[$c] = $c;
  89              }
  90          }
  91      }
  92  
  93      /**
  94       * Iterates through the parent tree starting with the seed values,
  95       * then checks the articles if they match the conditions
  96       * @return array Array of page_ids (those given to seed() that match the conditions)
  97       */
  98  	public function run() {
  99          $this->dbr = wfGetDB( DB_SLAVE );
 100          while ( count( $this->next ) > 0 ) {
 101              $this->scanNextLayer();
 102          }
 103  
 104          # Now check if this applies to the individual articles
 105          $ret = array();
 106  
 107          foreach ( $this->articles as $article ) {
 108              $conds = $this->targets;
 109              if ( $this->check( $article, $conds ) ) {
 110                  # Matches the conditions
 111                  $ret[] = $article;
 112              }
 113          }
 114          return $ret;
 115      }
 116  
 117      /**
 118       * Get the parents. Only really useful if run() has been called already
 119       * @return array
 120       */
 121  	public function getParents() {
 122          return $this->parents;
 123      }
 124  
 125      /**
 126       * This functions recurses through the parent representation, trying to match the conditions
 127       * @param int $id The article/category to check
 128       * @param array $conds The array of categories to match
 129       * @param array $path Used to check for recursion loops
 130       * @return bool Does this match the conditions?
 131       */
 132  	private function check( $id, &$conds, $path = array() ) {
 133          // Check for loops and stop!
 134          if ( in_array( $id, $path ) ) {
 135              return false;
 136          }
 137  
 138          $path[] = $id;
 139  
 140          # Shortcut (runtime paranoia): No conditions=all matched
 141          if ( count( $conds ) == 0 ) {
 142              return true;
 143          }
 144  
 145          if ( !isset( $this->parents[$id] ) ) {
 146              return false;
 147          }
 148  
 149          # iterate through the parents
 150          foreach ( $this->parents[$id] as $p ) {
 151              $pname = $p->cl_to;
 152  
 153              # Is this a condition?
 154              if ( isset( $conds[$pname] ) ) {
 155                  # This key is in the category list!
 156                  if ( $this->mode == 'OR' ) {
 157                      # One found, that's enough!
 158                      $conds = array();
 159                      return true;
 160                  } else {
 161                      # Assuming "AND" as default
 162                      unset( $conds[$pname] );
 163                      if ( count( $conds ) == 0 ) {
 164                          # All conditions met, done
 165                          return true;
 166                      }
 167                  }
 168              }
 169  
 170              # Not done yet, try sub-parents
 171              if ( !isset( $this->name2id[$pname] ) ) {
 172                  # No sub-parent
 173                  continue;
 174              }
 175              $done = $this->check( $this->name2id[$pname], $conds, $path );
 176              if ( $done || count( $conds ) == 0 ) {
 177                  # Subparents have done it!
 178                  return true;
 179              }
 180          }
 181          return false;
 182      }
 183  
 184      /**
 185       * Scans a "parent layer" of the articles/categories in $this->next
 186       */
 187  	private function scanNextLayer() {
 188          $profiler = new ProfileSection( __METHOD__ );
 189  
 190          # Find all parents of the article currently in $this->next
 191          $layer = array();
 192          $res = $this->dbr->select(
 193              /* FROM   */ 'categorylinks',
 194              /* SELECT */ '*',
 195              /* WHERE  */ array( 'cl_from' => $this->next ),
 196              __METHOD__ . '-1'
 197          );
 198          foreach ( $res as $o ) {
 199              $k = $o->cl_to;
 200  
 201              # Update parent tree
 202              if ( !isset( $this->parents[$o->cl_from] ) ) {
 203                  $this->parents[$o->cl_from] = array();
 204              }
 205              $this->parents[$o->cl_from][$k] = $o;
 206  
 207              # Ignore those we already have
 208              if ( in_array( $k, $this->deadend ) ) {
 209                  continue;
 210              }
 211  
 212              if ( isset( $this->name2id[$k] ) ) {
 213                  continue;
 214              }
 215  
 216              # Hey, new category!
 217              $layer[$k] = $k;
 218          }
 219  
 220          $this->next = array();
 221  
 222          # Find the IDs of all category pages in $layer, if they exist
 223          if ( count( $layer ) > 0 ) {
 224              $res = $this->dbr->select(
 225                  /* FROM   */ 'page',
 226                  /* SELECT */ array( 'page_id', 'page_title' ),
 227                  /* WHERE  */ array( 'page_namespace' => NS_CATEGORY, 'page_title' => $layer ),
 228                  __METHOD__ . '-2'
 229              );
 230              foreach ( $res as $o ) {
 231                  $id = $o->page_id;
 232                  $name = $o->page_title;
 233                  $this->name2id[$name] = $id;
 234                  $this->next[] = $id;
 235                  unset( $layer[$name] );
 236              }
 237          }
 238  
 239          # Mark dead ends
 240          foreach ( $layer as $v ) {
 241              $this->deadend[$v] = $v;
 242          }
 243      }
 244  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1