User:Socoljam/DynamicArticleList (enhanced)/CategoryTravelerBase.php
Save this code as CategoryTravelerBase.php in your extensions or includes directory.
<?php require_once( 'CategoryNode.php' ); /** * Base Class of Category Traveler * * @package MediaWiki * @author Zeng Ji(zengji@gmail.com) */ abstract class CategoryTravelerBase { var $dbr; var $sPageTable; var $sCategorylinksTable; // All category nodes organized by a list. Key is CategoryName, Content is CategoryNode. // This variable is used to quick-orientation while constructing category tree. var $categoryList; // All category nodes organized by a tree. Key is CategoryName, Content is CategoryNode. var $categoryTree; function __construct() { $this->dbr =& wfGetDB( DB_SLAVE ); $this->sPageTable = $this->dbr->tableName( 'page' ); $this->sCategorylinksTable = $this->dbr->tableName( 'categorylinks' ); $this->categoryList = array(); $this->categoryTree = array(); } // Allow user to define which category should the traveler start from. // If not defined, traveler will start from all level 1 (no parent) categories in datebase. function buildCategoryTree($categoryRoot=false) { unset($this->categoryList); unset($this->categoryTree); $this->categoryList = array(); $this->categoryTree = array(); $fname = get_class($this) . '::BuildCategoryTree'; $dbr = $this->dbr; // Construct all category nodes, and fill in categoryList. $sql = $this->genSQLcategoryAll(); $res = $dbr->query($sql, $fname); while( $obj = $dbr->fetchObject( $res ) ) { $cId = $obj->categoryId; $cName = $obj->categoryName; $this->categoryList[$cName] = new CategoryNode($cId, $cName); } $dbr->freeResult( $res ); if ($categoryRoot == false) { // Get level 1 (no parent) categories to travel from. $sql = $this->genSQLcategoryTopLevel($this->getNotTopCategoryList()); } else { // Get defined category to travel from. $categoryRoot = str_replace( ' ', '_', $categoryRoot); $sql = $this->genSQLcategoryByName($categoryRoot); } $res = $dbr->query($sql, $fname); while( $obj = $dbr->fetchObject( $res ) ) { $cId = $obj->categoryId; $cName = $obj->categoryName; // Generate first level categories in tree. $categoryNode = $this->categoryList[$cName]; if (isset($categoryNode)) { $this->categoryTree[$cName] = $categoryNode; } // Recursion to build children tree. $this->buildOneCategory(false, $cName); } $dbr->freeResult( $res ); } // Recursion Function£ºProcess current category and all its children. // $cParent: The parent of current processing category node. // If current processing category node is top level node, "cParent" should be set to false. // $cName: The name of current processing category node. function buildOneCategory($cParent, $cName) { $categoryNode = $this->categoryList[$cName]; if (isset($categoryNode)) { // Avoid the same category node to be travelled more than once. // For view of "CategoryTravelerBase", all categories should only has one parent or not. // Although maybe in fact some categories will have more than one parents. $categoryNode->hasTravelled = true; // Get all children categories, establish children/parent relationship. $categoryNode->parent = $cParent; $fname = get_class($this) . '::buildOneCategory'; $dbr = $this->dbr; $sql = $this->genSQLcategoryChildren($cName); $res = $dbr->query($sql, $fname); if ($dbr->numRows( $res ) == 0) { // No children categories, recursion ends here. $dbr->freeResult( $res ); return; } $categoryNode->children = array(); while( $obj = $dbr->fetchObject( $res ) ) { $childName = $obj->categoryName; $childNode = $this->categoryList[$childName]; if ($childNode->hasTravelled == true) continue; $categoryNode->children[$childName] = $childNode; $this->buildOneCategory($childNode, $childName); } $dbr->freeResult( $res ); } } // Deep recursion to build category tree. function travelCategoryTree() { $this->travelStart(); // "0" means "Travel from Root" $this->travelOneCategory(0); $this->travelEnd(); } function travelOneCategory($level, $categoryNode=false) { if ($level==0) { // Get TOP Level Categories $categoryList = $this->categoryTree; } else { // Get Next Level Categories $categoryList = $categoryNode->children; } // Process Current Category if ($categoryNode != false) $this->travel($level, $categoryNode); // Process Children of Current Category if ($categoryList != false) { $categoryCount = count($categoryList); $index = 0; foreach($categoryList as $cName => $cNode) { if ($index == 0) $this->travelBeforeFirst($level+1, $cNode); $this->travelOneCategory($level+1, $cNode); $index = $index + 1; if ($index == $categoryCount) $this->travelAfterLast($level+1, $cNode); } } } abstract function travelStart(); abstract function travelEnd(); abstract function travelBeforeFirst($level, $categoryNode); // Before travel the first category in one level abstract function travel($level, $categoryNode); abstract function travelAfterLast($level, $categoryNode); // After travel the last category in one level /////////// Utility Function //////////////// // Generate SQL: Retrieve category information by name function genSQLcategoryByName($cName) { $sql = "SELECT page_id AS categoryId, page_title AS categoryName "; $sql.= "FROM $this->sPageTable "; $sql.= "WHERE page_namespace=" . NS_CATEGORY . " AND page_title=\"" . $cName . "\""; return $sql; } // Generate SQL: Retrieve children categories by name function genSQLcategoryChildren($cName) { $sql = "SELECT cl.cl_from AS categoryId, page_title AS categoryName "; $sql.= "FROM $this->sPageTable INNER JOIN $this->sCategorylinksTable AS cl "; $sql.= "ON page_id=cl.cl_from AND (page_namespace=" . NS_CATEGORY . ") "; $sql.= "AND (cl.cl_to='" . $cName . "') "; $sql.= "ORDER BY page_title"; return $sql; } // Generate SQL: Retrieve all "non-TOP level" categories. // "non-TOP level" means has parents. function genSQLcategoryNotTopLevel() { $sql = "SELECT cl.cl_from AS categoryId, page_title AS categoryName "; $sql.= "FROM $this->sPageTable INNER JOIN $this->sCategorylinksTable AS cl "; $sql.= "ON page_id=cl.cl_from AND (page_namespace=" . NS_CATEGORY . ") "; $sql.= "ORDER BY page_title"; return $sql; } // Generate SQL: Retrieve all categories. function genSQLcategoryAll() { $sql = "SELECT page_id AS categoryId, page_title AS categoryName "; $sql.= "FROM $this->sPageTable "; $sql.= "WHERE page_namespace=" . NS_CATEGORY . " "; $sql.= "ORDER BY page_title"; return $sql; } // Generate SQL: Retrieve all "TOP level" categories. // Use "NOT IN" clause in SQL query. "excludeList" should be all "non-TOP level" categories' ID. function genSQLcategoryTopLevel($excludeList) { $sql = "SELECT page_id AS categoryId, page_title AS categoryName "; $sql.= "FROM $this->sPageTable "; if ($excludeList) { $sql.= "WHERE page_namespace=" . NS_CATEGORY . " AND (page_id NOT IN (" . $excludeList . "))"; } else { $sql.= "WHERE page_namespace=" . NS_CATEGORY; } return $sql; } // Return a string which contains all "non-TOP level" categories' ID. function getNotTopCategoryList() { $fname = get_class($this) . '::getNotTopCategoryList'; $sql = $this->genSQLcategoryNotTopLevel(); $dbr = $this->dbr; $res = $dbr->query($sql, $fname); // Query result is blank. if ($dbr->numRows( $res ) == 0) { $dbr->freeResult( $res ); return false; } // Generate a string of categories' ID list. // For example: 1234,3721,4567 $ret = ''; while( $obj = $dbr->fetchObject( $res ) ) { if( isset( $obj->categoryId ) ) { $ret .= $obj->categoryId . ','; } } $ret = substr($ret, 0, strlen($ret)-1); // Delete tail comma $dbr->freeResult( $res ); return $ret; } } ?>