http://www.mediawiki.org/wiki/Special:Code/MediaWiki/93769
Revision: 93769 Author: flohack Date: 2011-08-02 16:56:29 +0000 (Tue, 02 Aug 2011) Log Message: ----------- Experimental support for memcached enhanced category tree building; Added an option to disable the recursive category scan; Changed the DB schema to avoid having to add a primary key to change_tag; Added a dummy test case Modified Paths: -------------- trunk/extensions/CollabWatchlist/CollabWatchlist.php trunk/extensions/CollabWatchlist/includes/CategoryTreeManip.php trunk/extensions/CollabWatchlist/includes/CollabWatchlistEditor.php trunk/extensions/CollabWatchlist/includes/SpecialCollabWatchlist.php trunk/extensions/CollabWatchlist/sql/collabwatchlistrevisiontag.sql Added Paths: ----------- trunk/extensions/CollabWatchlist/sql/patch-collabwatchlist_noctid.sql trunk/extensions/CollabWatchlist/tests/ trunk/extensions/CollabWatchlist/tests/CollabWatchlistTest.php Removed Paths: ------------- trunk/extensions/CollabWatchlist/sql/patch-change_tag_id.sql Modified: trunk/extensions/CollabWatchlist/CollabWatchlist.php =================================================================== --- trunk/extensions/CollabWatchlist/CollabWatchlist.php 2011-08-02 16:48:14 UTC (rev 93768) +++ trunk/extensions/CollabWatchlist/CollabWatchlist.php 2011-08-02 16:56:29 UTC (rev 93769) @@ -46,15 +46,16 @@ $wgHooks['GetPreferences'][] = 'fnCollabWatchlistPreferences'; function fnCollabWatchlistDbSchema() { + global $updater; $wgSql = dirname(__FILE__) . '/sql/'; if ( $updater === null ) { // <= 1.16 support - global $wgExtNewTables; + global $wgExtNewTables, $wgExtNewFields; $wgExtNewTables[] = array('collabwatchlist', $wgSql . 'collabwatchlist.sql'); $wgExtNewTables[] = array('collabwatchlistuser', $wgSql . 'collabwatchlistuser.sql'); $wgExtNewTables[] = array('collabwatchlistcategory', $wgSql . 'collabwatchlistcategory.sql'); $wgExtNewTables[] = array('collabwatchlistrevisiontag', $wgSql . 'collabwatchlistrevisiontag.sql'); $wgExtNewTables[] = array('collabwatchlisttag', $wgSql . 'collabwatchlisttag.sql'); - $wgExtNewFields[] = array('change_tag', 'ct_id', $wgSql . 'patch-change_tag_id.sql'); + $wgExtNewFields[] = array('collabwatchlistrevisiontag', 'ct_rc_id', $wgSql . 'patch-collabwatchlist_noctid.sql'); } else { // >= 1.17 support $updater->addExtensionUpdate( array ( 'addTable', 'collabwatchlist', $wgSql . 'collabwatchlist.sql', true ) ); @@ -66,8 +67,8 @@ $wgSql . 'collabwatchlistrevisiontag.sql', true ) ); $updater->addExtensionUpdate( array ( 'addTable', 'collabwatchlisttag', $wgSql . 'collabwatchlisttag.sql', true ) ); - $updater->addExtensionUpdate( array( 'modifyField', 'change_tag', 'ct_id', - $wgSql . 'patch-change_tag_id.sql', true ) ); + $updater->addExtensionUpdate( array( 'modifyField', 'collabwatchlistrevisiontag', 'ct_rc_id', + $wgSql . 'patch-collabwatchlist_noctid.sql', true ) ); } return true; } @@ -81,9 +82,23 @@ return true; } +// You might want to disable that, as it causes quite a bit of database +// and (if enabled) cache load and size +$wgCollabWatchlistRecursiveCatScan = true; +// The depth of the category tree we are building. -1 means infinite +// 0 fetches no child categories at all +$wgCollabWatchlistRecursiveCatMaxDepth = -1; $wgCollabWatchlistNSPrefix = 'CollabWatchlist'; $wgCollabWatchlistPermissionDeniedPage = 'CollabWatchlistPermissionDenied'; +# Unit tests +$wgHooks['UnitTestsList'][] = 'efCollabWatchlistUnitTests'; + +function efCollabWatchlistUnitTests( &$files ) { + $files[] = dirname( __FILE__ ) . '/tests/CollabWatchlistTest.php'; + return true; +} + /**#@+ * Collaborative watchlist user types * This defines constants for the collabwatchlistuser.rlu_type Modified: trunk/extensions/CollabWatchlist/includes/CategoryTreeManip.php =================================================================== --- trunk/extensions/CollabWatchlist/includes/CategoryTreeManip.php 2011-08-02 16:48:14 UTC (rev 93768) +++ trunk/extensions/CollabWatchlist/includes/CategoryTreeManip.php 2011-08-02 16:56:29 UTC (rev 93769) @@ -10,19 +10,31 @@ * @author fhackenberger */ class CategoryTreeManip { + const CACHE_KEY = 'categorytree'; + const CACHE_KEY_CHILDCATEGORIES = 'childcats'; + const CACHE_KEY_CATEGORYPAGEID = 'catpageid'; + // How long the cache for child categories and category page ids should last + protected $cacheDurationSec; + // Whether the cache should be used to speed up building the tree + // We always fill the cache, though + protected $cacheEnabled = true; + // The depth of the tree we are building. -1 means infinite + // 0 fetches no child categories at all + protected $maxDepth = -1; + protected $root; + public $name; + public $id; + // Maps from the page id of a category to instances of this class + protected $catPageIdToNode = array(); + protected $parents = array(); + protected $enabled = true; + protected $children = array(); - var $root; - var $name; - var $id; - var $catPageIdToNode = array(); - var $parents = array(); - var $enabled = true; - var $children = array(); - /** * Constructor */ function __construct( $id = null, $name = null, $root = null, $parents = array() ) { + $this->cacheDurationSec = 15 * 60; $this->id = $id; $this->name = $name; if ( !is_null( $root ) ) { @@ -32,10 +44,26 @@ } $this->parents = $parents; } + + public function getCacheEnabled() { + return $this->cacheEnabled; + } + + public function setCacheEnabled( $enabled ) { + $this->cacheEnabled = $enabled; + } + + public function getMaxDepth() { + return $this->maxDepth; + } + + public function setMaxDepth( $maxDepth ) { + $this->maxDepth = $maxDepth; + } private function addChildren( $children ) { if ( !is_array( $children ) ) - throw new Exception( 'Argument must be an array' ); + throw new Exception( 'Argument must be an array' ); foreach ( $children as $child ) { $this->children[$child->id] = $child; } @@ -43,7 +71,7 @@ private function addParents( $parents ) { if ( !is_array( $parents ) ) - throw new Exception( 'Argument must be an array' ); + throw new Exception( 'Argument must be an array' ); foreach ( $parents as $parent ) { $this->parents[$parent->id] = $parent; } @@ -71,7 +99,7 @@ private function recursiveDisable( $visitedNodeIds = array() ) { if ( !$this->enabled || array_key_exists( $this->id, $visitedNodeIds ) ) - return; # Break the recursion + return; # Break the recursion $this->enabled = false; $visitedNodeIds[] = $this->id; foreach ( $this->children as $cat ) { @@ -105,7 +133,7 @@ private function recursiveGetEnabledNodeMap( &$foundNodes = array() ) { if ( isset( $this->id ) ) { if ( !$this->enabled || array_key_exists( $this->id, $foundNodes ) ) - return $foundNodes; # Break the recursion + return $foundNodes; # Break the recursion $foundNodes[$this->id] = $this; } foreach ( $this->children as $cat ) { @@ -121,12 +149,29 @@ */ public function getNodeForCatPageId( $catPageId ) { if ( array_key_exists( $catPageId, $this->root->catPageIdToNode ) ) - return $this->root->catPageIdToNode[$catPageId]; + return $this->root->catPageIdToNode[$catPageId]; } private function addNode( $node ) { $this->root->catPageIdToNode[$node->id] = $node; } + + public function printTree() { + print "all categories:\n"; + foreach( $this->catPageIdToNode as $catPageId => $node ) { + print $catPageId . ": " . $node->name . " enabled: " . $node->enabled . "\n"; + } + print "child categories:\n"; + $this->printTreeRecursive( $this->root, '' ); + } + + protected function printTreeRecursive( $node, $prefix ) { + if( $node->id ) + print $node->id . ": " . $node->name . " enabled: " . $node->enabled . "\n"; + foreach( $node->children as $child ) { + $this->printTreeRecursive( $child, $prefix . ' ' ); + } + } /** Build the category tree, given a list of category names. * All categories and subcategories are enabled by default. @@ -135,47 +180,46 @@ * @return */ public function initialiseFromCategoryNames( $catNames ) { - $dbr = wfGetDB( DB_SLAVE ); + $currDepth = 0; + // This method builds the category tree breadth-first in an + // iterative fashion. while ( $catNames ) { - $res = $dbr->select( array( 'categorylinks', 'page' ), # Tables - array( 'cl_to AS parName', 'cl_from AS childId', 'page_title AS childName' ), # Fields - array( 'cl_to' => $catNames, 'page_namespace' => NS_CATEGORY ), # Conditions - __METHOD__, array(), - # Join conditions - array( 'page' => array( 'JOIN', 'page_id = cl_from' ) ) - ); + if($this->maxDepth > 0 && $currDepth > $this->maxDepth) + break; + // Get a list of child categories (array of hashmaps) + $res = $this->getChildCategories( $catNames ); + // Maps parent category name -> array ( array ( child id , child name ) ) $parentList = array(); + // Maps child category id -> array( child id, array ( parent name, ... ) ) $childList = array(); + // Build $parentList and $childList foreach ( $res as $row ) { - $parentList[$row->parName][] = array( $row->childId, $row->childName ); - if ( array_key_exists( $row->childId, $childList ) ) { - $childEntry = $childList[$row->childId]; - $childEntry[1][] = $row->parName; + $parentList[$row['parName']][] = array( $row['childId'], $row['childName'] ); + if ( array_key_exists( $row['childId'], $childList ) ) { + $childEntry = $childList[$row['childId']]; + $childEntry[1][] = $row['parName']; } else { - $childList[$row->childId] = array( $row->childName, array( $row->parName ) ); + $childList[$row['childId']] = array( $row['childName'], array( $row['parName'] ) ); } } + // Fetch the page ids of the $catNames and build the node objects for them if ( !isset( $parentNameToNode ) && !empty( $parentList ) ) { - // Fetch the page ids of the $catNames and add the parent categories if needed - $res = $dbr->select( array( 'page' ), # Tables - array( 'page_id, page_title' ), # Fields - array( 'page_title' => array_keys( $parentList ) ) # Conditions - ); + $res = $this->getCategoryPageIds( array_keys( $parentList ) ); $parentNameToNode = array(); foreach ( $res as $row ) { - $node = $this->getNodeForCatPageId( $row->page_id ); + $node = $this->getNodeForCatPageId( $row['page_id'] ); if ( !isset( $node ) ) { - $node = new CategoryTreeManip( $row->page_id, $row->page_title, $this->root ); + $node = new CategoryTreeManip( $row['page_id'], $row['page_title'], $this->root ); $this->addNode( $node ); $this->addChildren( array( $node ) ); } - $parentNameToNode[$row->page_title] = $node; + $parentNameToNode[$row['page_title']] = $node; } } $newChildNameToNode = array(); - // Add the new child nodes + // Create and add the new node objects for all child categories foreach ( $childList as $childPageId => $childInfo ) { $childNode = $this->getNodeForCatPageId( $childPageId ); if ( !isset( $childNode ) ) { @@ -193,6 +237,112 @@ // Prepare for the next loop $parentNameToNode = $newChildNameToNode; $catNames = array_keys( $parentNameToNode ); + $currDepth++; } } + + /** Retrieve a list of child categories for all the given category names + * Uses the cache if enabled + * @param $catNames Array: List of category names + * @return Mixed: Array of Maps with keys: parName, childId, childName + */ + protected function getChildCategories( $catNames ) { + global $wgMemc; + $dbr = wfGetDB( DB_SLAVE ); + $childList = array(); + $nonCachedCatNames = array(); + // Try cache first + if( $this->cacheEnabled ) { + foreach( $catNames as $catName ) { + $key = wfMemcKey( self::CACHE_KEY, self::CACHE_KEY_CHILDCATEGORIES, $catName ); + $res = $wgMemc->get( $key ); + if( $res != '' ) { + $childList = array_merge( $childList, $res ); + } else { + $nonCachedCatNames[$catName] = false; + } + } + } else { + $nonCachedCatNames = array_fill_keys( $catNames, false ); + } + + // Select the child categories of all categories we have not found in the cache + $res = array(); + if( !empty( $nonCachedCatNames ) ) { + // Select the direct child categories of all category names + // I.e. category name, child category id and child category name + $res = $dbr->select( array( 'categorylinks', 'page' ), # Tables + array( 'cl_to AS parName', 'cl_from AS childId', 'page_title AS childName' ), # Fields + array( 'cl_to' => array_keys($nonCachedCatNames), 'page_namespace' => NS_CATEGORY ), # Conditions + __METHOD__, array( 'GROUP BY' => 'cl_to' ), # Options + array( 'page' => array( 'JOIN', 'page_id = cl_from' ) ) # Join conditions + ); + } + + // Prepare and store cache objects + $cacheObj = array(); + foreach ( $res as $row ) { + unset($nonCachedCatNames[$row->parName]); + if( !isset( $currCatName ) ) + $currCatName = $row->parName; + if( $currCatName != $row->parName ) { + $key = wfMemcKey( self::CACHE_KEY, self::CACHE_KEY_CHILDCATEGORIES, $currCatName ); + $wgMemc->set( $key, $cacheObj, $this->cacheDurationSec ); + $childList = array_merge( $childList, $cacheObj ); + $cacheObj = array(); + $currCatName = $row->parName; + } + $cacheObj[] = array( 'parName' => $row->parName, 'childId' => $row->childId, 'childName' => $row->childName ); + } + // Store the last bunch + if( !empty( $cacheObj ) ) { + $key = wfMemcKey( self::CACHE_KEY, self::CACHE_KEY_CHILDCATEGORIES, $currCatName ); + $wgMemc->set( $key, $cacheObj, $this->cacheDurationSec ); + $childList = array_merge( $childList, $cacheObj ); + } + // Store empty values for leaf categories, otherwise we would query the DB because we did not find a cache entry + foreach( array_keys($nonCachedCatNames ) as $currCatName ) { + $key = wfMemcKey( self::CACHE_KEY, self::CACHE_KEY_CHILDCATEGORIES, $currCatName ); + $wgMemc->set( $key, array(), $this->cacheDurationSec ); + } + return $childList; + } + + /** Retrieve the page ids of the category pages for the given categories + * + * @param $catNames Array: A list of category names you would like to query for + * @return Array: An array of maps with keys: page_id, page_title + */ + protected function getCategoryPageIds( $catNames ) { + global $wgMemc; + $dbr = wfGetDB( DB_SLAVE ); + $pageInfo = array(); + $nonCachedCatNames = array(); + // Try cache first + if( $this->cacheEnabled ) { + foreach( $catNames as $catName ) { + $res = $wgMemc->get( wfMemcKey( self::CACHE_KEY, self::CACHE_KEY_CATEGORYPAGEID, $catName ) ); + if( $res != '' ) { + $pageInfo[] = array( 'page_id' => $res, 'page_title' => $catName ); + } else { + $nonCachedCatNames[] = $catName; + } + } + } + // Select the child categories of all categories we have not found in the cache + $res = array(); + if( !empty( $nonCachedCatNames ) ) { + $res = $dbr->select( array( 'page' ), # Tables + array( 'page_id, page_title' ), # Fields + array( 'page_title' => $nonCachedCatNames ) # Conditions + ); + } + // Prepare and store cache object + foreach ( $res as $row ) { + $pageInfo[] = array( 'page_id' => $row->page_id, 'page_title' => $row->page_title ); + $wgMemc->set( wfMemcKey( self::CACHE_KEY, self::CACHE_KEY_CATEGORYPAGEID, $row->page_title ), $row->page_id, $this->cacheDurationSec ); + } + return $pageInfo; + } + } Modified: trunk/extensions/CollabWatchlist/includes/CollabWatchlistEditor.php =================================================================== --- trunk/extensions/CollabWatchlist/includes/CollabWatchlistEditor.php 2011-08-02 16:48:14 UTC (rev 93768) +++ trunk/extensions/CollabWatchlist/includes/CollabWatchlistEditor.php 2011-08-02 16:56:29 UTC (rev 93769) @@ -789,8 +789,8 @@ } } // Add the tagged revisions to the collaborative watchlist - $sql = 'INSERT IGNORE INTO collabwatchlistrevisiontag (ct_id, rl_id, user_id, rrt_comment) - SELECT ct_id, ' . $dbw->strencode( $rlId ) . ',' . + $sql = 'INSERT IGNORE INTO collabwatchlistrevisiontag (ct_rc_id, ct_tag, rl_id, user_id, rrt_comment) + SELECT ct_rc_id, ct_tag, ' . $dbw->strencode( $rlId ) . ',' . $dbw->strencode( $userId ) . ',' . $dbw->addQuotes( $comment ) . ' FROM change_tag WHERE ct_tag = ? AND ct_rc_id '; if ( count( $rcIds ) > 1 ) { @@ -817,9 +817,7 @@ $rcIds[] = $info['rc_id']; } // Remove the tag from the collaborative watchlist - $sql = 'delete collabwatchlistrevisiontag from collabwatchlistrevisiontag JOIN change_tag - ON change_tag.ct_id = collabwatchlistrevisiontag.ct_id - WHERE ct_tag = ? AND ct_rc_id '; + $sql = 'DELETE FROM collabwatchlistrevisiontag WHERE ct_tag = ? AND ct_rc_id '; if ( count( $rcIds ) > 1 ) { $sql .= 'IN (' . $dbw->makeList( $rcIds ) . ')'; $params = array( $tag ); Modified: trunk/extensions/CollabWatchlist/includes/SpecialCollabWatchlist.php =================================================================== --- trunk/extensions/CollabWatchlist/includes/SpecialCollabWatchlist.php 2011-08-02 16:48:14 UTC (rev 93768) +++ trunk/extensions/CollabWatchlist/includes/SpecialCollabWatchlist.php 2011-08-02 16:56:29 UTC (rev 93769) @@ -260,9 +260,8 @@ } else { $filter = 'NOT EXISTS '; } - $filter .= '(select ct_rc_id from change_tag - JOIN collabwatchlistrevisiontag ON collabwatchlistrevisiontag.ct_id = change_tag.ct_id - WHERE ct_rc_id = recentchanges.rc_id AND ct_tag '; + $filter .= '(SELECT cwlrt.ct_rc_id FROM collabwatchlistrevisiontag cwlrt + WHERE cwlrt.ct_rc_id = recentchanges.rc_id AND cwlrt.ct_tag '; if ( count( $tagFilter ) > 1 ) $filter .= 'IN (' . $dbr->makeList( $tagFilter ) . '))'; else @@ -582,11 +581,11 @@ } // $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() $res = $dbr->select( array( 'change_tag', 'collabwatchlistrevisiontag', 'user' ), # Tables - array( 'rl_id', 'ct_tag', 'collabwatchlistrevisiontag.user_id', 'user_name', 'rrt_comment' ), # Fields - array( 'ct_rev_id' => $rev_id ) + $cond, # Conditions + array( 'rl_id', 'collabwatchlistrevisiontag.ct_tag', 'collabwatchlistrevisiontag.user_id', 'user_name', 'rrt_comment' ), # Fields + array( 'change_tag.ct_rev_id' => $rev_id ) + $cond, # Conditions __METHOD__, array(), # Join conditions - array( 'collabwatchlistrevisiontag' => array( 'JOIN', 'change_tag.ct_id = collabwatchlistrevisiontag.ct_id' ), + array( 'collabwatchlistrevisiontag' => array( 'JOIN', 'change_tag.ct_rc_id = collabwatchlistrevisiontag.ct_rc_id AND change_tag.ct_tag = collabwatchlistrevisiontag.ct_tag' ), 'user' => array( 'JOIN', 'collabwatchlistrevisiontag.user_id = user.user_id' ) ) ); @@ -608,6 +607,7 @@ * @return String: An SQL clause usable in the conditions parameter of $db->select() */ function wlGetFilterClauseForCollabWatchlistIds( $rl_ids, $catNameCol, $pageIdCol ) { + global $wgCollabWatchlistRecursiveCatScan; $excludedCatPageIds = array(); $includedCatPageIds = array(); $includedPageIds = array(); @@ -632,8 +632,9 @@ } } - if ( $includedCatPageIds ) { + if ( $wgCollabWatchlistRecursiveCatScan && $includedCatPageIds ) { $catTree = new CategoryTreeManip(); + $catTree->setMaxDepth($wgCollabWatchlistRecursiveCatScan); $catTree->initialiseFromCategoryNames( array_values( $includedCatPageIds ) ); $catTree->disableCategoryIds( array_keys( $excludedCatPageIds ) ); $enabledCategoryNames = $catTree->getEnabledCategoryNames(); Modified: trunk/extensions/CollabWatchlist/sql/collabwatchlistrevisiontag.sql =================================================================== --- trunk/extensions/CollabWatchlist/sql/collabwatchlistrevisiontag.sql 2011-08-02 16:48:14 UTC (rev 93768) +++ trunk/extensions/CollabWatchlist/sql/collabwatchlistrevisiontag.sql 2011-08-02 16:56:29 UTC (rev 93769) @@ -7,8 +7,10 @@ CREATE TABLE IF NOT EXISTS /*$wgDBprefix*/collabwatchlistrevisiontag ( -- The id of this entry rrt_id integer unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, - -- Foreign key to change_tag.ct_id - ct_id integer unsigned NOT NULL, + -- See change_tag.ct_tag + ct_tag varbinary(255) NOT NULL, + -- See change_tag.ct_rc_id + ct_rc_id int NOT NULL default 0, -- Foreign key to collabwatchlist.rl_id rl_id integer unsigned NOT NULL, -- Foreign key to user.user_id @@ -17,5 +19,5 @@ -- Comment for the tag rrt_comment varchar(255), - UNIQUE KEY (ct_id, rl_id) -) /*$wgDBTableOptions*/; \ No newline at end of file + UNIQUE KEY (ct_tag, ct_rc_id, rl_id) +) /*$wgDBTableOptions*/; Deleted: trunk/extensions/CollabWatchlist/sql/patch-change_tag_id.sql =================================================================== --- trunk/extensions/CollabWatchlist/sql/patch-change_tag_id.sql 2011-08-02 16:48:14 UTC (rev 93768) +++ trunk/extensions/CollabWatchlist/sql/patch-change_tag_id.sql 2011-08-02 16:56:29 UTC (rev 93769) @@ -1,5 +0,0 @@ --- Add a primary key to the change_tag table in order --- to enable us to build the review list extension - -ALTER TABLE /*$wgDBprefix*/change_tag - ADD ct_id integer unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT; \ No newline at end of file Added: trunk/extensions/CollabWatchlist/sql/patch-collabwatchlist_noctid.sql =================================================================== --- trunk/extensions/CollabWatchlist/sql/patch-collabwatchlist_noctid.sql (rev 0) +++ trunk/extensions/CollabWatchlist/sql/patch-collabwatchlist_noctid.sql 2011-08-02 16:56:29 UTC (rev 93769) @@ -0,0 +1,11 @@ +-- Remove the ct_id column which was introduced in a previous revision +-- This is intended to be used manually as there does not seem to be +-- support + +ALTER TABLE /*$wgDBprefix*/collabwatchlistrevisiontag + ADD ct_tag varbinary(255) NOT NULL; +ALTER TABLE /*$wgDBprefix*/collabwatchlistrevisiontag + ADD ct_rc_id int NOT NULL default 0; +UPDATE /*$wgDBprefix*/collabwatchlistrevisiontag cwlrt set ct_rc_id = (select ct.ct_rc_id from change_tag ct where ct.ct_id = cwlrt.ct_id), ct_tag = (select ct.ct_tag from change_tag ct where ct.ct_id = cwlrt.ct_id); +ALTER TABLE /*$wgDBprefix*/change_tag + DROP ct_id; Property changes on: trunk/extensions/CollabWatchlist/sql/patch-collabwatchlist_noctid.sql ___________________________________________________________________ Added: svn:eol-style + native Added: trunk/extensions/CollabWatchlist/tests/CollabWatchlistTest.php =================================================================== --- trunk/extensions/CollabWatchlist/tests/CollabWatchlistTest.php (rev 0) +++ trunk/extensions/CollabWatchlist/tests/CollabWatchlistTest.php 2011-08-02 16:56:29 UTC (rev 93769) @@ -0,0 +1,11 @@ +<?php + +class CollabWatchlistTest extends PHPUnit_Framework_TestCase { + public function testCategoryTree() { + $catTree = new CategoryTreeManip(); + $catNames = array( 'Test', 'Category:Root' ); + $catTree->initialiseFromCategoryNames( $catNames ); + //var_dump( $catTree->getEnabledCategoryNames() ); + $catTree->printTree(); + } +} Property changes on: trunk/extensions/CollabWatchlist/tests/CollabWatchlistTest.php ___________________________________________________________________ Added: svn:eol-style + native _______________________________________________ MediaWiki-CVS mailing list MediaWiki-CVS@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs