Extension:Taxonomy
From MediaWiki.org
|
Release status: beta |
|||
|---|---|---|---|
| Implementation | Parser function, Tag, Database | ||
| Description | Provides hierarchical taxonomy for article management | ||
| Author(s) | Eko Mursito Budi (MursitoTalk) | ||
| Last Version | 0.9 (2009/07/30) | ||
| MediaWiki | 1.15 | ||
| License | GPL | ||
| Download | no link | ||
| Example | http://computational.engineering.or.id/Taxonomy:Utama | ||
|
|||
|
|||
|
check usage (experimental) |
|||
Contents |
[edit] What can this extension do?
To manage the structure of the articles, MediaWiki uses categories, which excels at the web structure. It can also be used to represent hierarchies, using internal links and subcategories, but it has some weakness:
- The hierarchy must be constructed in bottom up fashion.
- Selecting all articles belongs to a top category is slow.
This extension solve the problems by :
- Adding a new table for storing the taxonomy in nested hierarchy structure.
- Providing a taxonomy-tree tag to define the taxonomy tree in a page using a top down approach.
- Providing a taxonomy tag to show the dynamic article list in any page, using the faster nested hierarchy technique.
[edit] Usage
After installation (see below), there are three general steps to use it.
[edit] Define The Taxonomy Tree
- Create a new taxonomy page (e.g: Taxonomy:Main)
- In the page, write the taxonomy definition, for example :
This is the definition of "Main" taxonomy. <taxonomy-tree show=default> * Information Technology ** Hardware *** Computer *** Networking ** Software *** Operating System *** Middle ware *** Applications </taxonomy-tree>
Note that each line contains the bulleted tags (*) to express the hierarchy depth, then the category name. When the page is saved, the extension creates the standard Mediawiki categories as needed (including the Main category). However, it won't necessarilly create the category page. (Technically speaking, it insert new records in the 'taxonomy' and 'category' tables, but not in the 'page' nor in the 'categorylinks' table).
The taxonomy-tree tag support the following parameters:
| parameter | possible values | description |
| show | default, hide | show or hide the tree when rendered |
[edit] Attach Pages to the Categories
Attach any page to the category using the standard Mediawiki tag, e.g:
[[Category:Hardware]] [[Category:Operating System]]
[edit] Show the dynamic page list
In any page, just add the taxonomy tag as follow:
<taxonomy type=hot category=Hardware count=10>Main</taxonomy>
or use the parser instead (recomended for a template):
{{#tag:taxonomy | Main | type=new | count=5 | category=Hardware }}
The following parameters might be used
| parameter | possible values | description |
| category | one of the category name | parent category to be listed |
| count | integer number | number of pages listed |
| type | hot, new | show the hottest articles, or the newest articles. |
| sort | asc, desc | show the articles in ascending or descending order |
[edit] Download instructions
The source code can be found below.
[edit] Installation
This extension requires 4 steps of installation.
[edit] Add the Taxonomy Table
Please create the taxonomy table in your Mediawiki database, using the following SQL code (This is for MySQL database).
CREATE TABLE IF NOT EXISTS `taxonomy` ( `tax_page` int(10) NOT NULL, `tax_category` int(10) NOT NULL, `tax_parent` int(10) NOT NULL, `tax_left` int(11) NOT NULL, `tax_right` int(11) NOT NULL, PRIMARY KEY (`tax_page`,`tax_category`), KEY `tax_parent` (`tax_parent`), KEY `tax_left` (`tax_left`), KEY `tax_right` (`tax_right`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
[edit] Put the Taxonomy.php file
Copy the Taxonomy.php source code below, and place it in $IP/extensions/Taxonomy/Taxonomy.php. Note: $IP stands for the root directory of your MediaWiki installation, the same directory that holds LocalSettings.php.
[edit] Put the Taxonomy.i18n.php file
Copy the Taxonomy.i18n.php source code below, and place it in $IP/extensions/Taxonomy/Taxonomy.i18n.php. If necessary, edit that file to add your localization.
[edit] Edit the LocalSettings.php
Add the following lines
# Define a new Taxonomy name space # Fell free to adjust the namespace number as appropriate, but keep it forever define("NS_TAXONOMY", 1000); define("NS_TAXONOMY_TALK", 1001); $wgExtraNamespaces[NS_TAXONOMY] = "Taxonomy"; $wgExtraNamespaces[NS_TAXONOMY_TALK] = "Taxonomy_talk"; # Include the Taxonomy extension require_once("$IP/extensions/Taxonomy/Taxonomy.php");
[edit] Code
[edit] Taxonomy.php
<?php /* Taxonomy MediaWiki extension for managing articles hierarchically * @ingroup Extensions * @author Eko M. Budi This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. http://www.gnu.org/copyleft/gpl.html Revision: 2009/06/28 : version 0.1 - Internasionalisation for EN and ID - taxonomy tag hook, support type={hot|new} - taxonomy-tree tag hook, support show={hide|default} - taxonomy-tree articlesavecomplete hook, support saving to the table - database, define a new taxonomy table, links to category, categorylinks, and page */ //Avoid unstubbing $wgParser too early on modern (1.12+) MW versions, as per r35980 if ( defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' ) ) { $wgHooks['ParserFirstCallInit'][] = 'wfTaxonomyInit'; } else { $wgExtensionFunctions[] = 'wfTaxonomyInit'; } //$wgHooks['UnknownAction'][] = 'actionCreate'; $wgExtensionCredits['parserhook'][] = array( 'path' => __FILE__, 'name' => 'Taxonomy', 'url' => 'http://www.mediawiki.org/wiki/Extension:Taxonomy', 'description' => 'Taxonomy of categories using nested hierarchy', 'author' => 'Eko M. Budi', 'version' => '1.0', 'descriptionmsg' => 'taxonomy-desc', ); $dir = dirname(__FILE__) . '/'; $wgExtensionMessagesFiles['Taxonomy'] = $dir . 'Taxonomy.i18n.php'; // for debugging from save hook function debug($msg) { $myFile = 'debug.log'; $fh = fopen($myFile, 'a') or die("can't open file"); fwrite($fh, $msg . "\n"); fclose($fh); } // Register on Save hook $wgHooks['ArticleSaveComplete'][] = 'wfTaxonomySaveCompleteHook'; function wfTaxonomyInit() { // register messages // global $wgMessageCache, $wgHierarchyMessages; // register the extension with the WikiText parser global $wgParser; $wgParser->setHook( "taxonomy-tree", "wfTaxonomyTreeHook" ); $wgParser->setHook( "taxonomy", "wfTaxonomyHook" ); return true; } // called whe an article is saved function wfTaxonomySaveCompleteHook(&$article, &$user, &$text, &$summary, &$minoredit, &$watchthis, &$sectionanchor, &$flags) { // if not in the TAXONOMY namespace, don't bother $namespace = $article->mTitle->getNamespace(); //debug("SaveCompleteHook " . $article->getTitle() . "(" . $namespace . ")" . NS_TAXONOMY); if (($namespace !== NS_TAXONOMY) && ($namespace !== NS_CATEGORY)) return true; // search for a <taxonomy-tree> tag // a category article may contain only one taxonomy-tree tag $pattern = '@<taxonomy-tree>(.*?)</taxonomy-tree>@is'; if (preg_match($pattern, $text, $matches, PREG_OFFSET_CAPTURE)) { // found one, lets make a taxonomy $text = $matches[1][0]; $tt = TaxonomyTree::newFromText($article, $text); $tt->saveTree(); } else { // no taxonomy-tree, just make sure to delete one belongs to the article TaxonomyTree::removeTree($article); } return false; } // called when an article is rendered and contains the <taxonomy-tree> tag // Render each item to a Category link function wfTaxonomyTreeHook ($input, $args, &$parser) { // get the arguments $show = 'default'; if ( $args['show'] === 'hide' ) $show = 'hide'; switch ($show) { case 'hide' : break; default: return TaxonomyTreeRenderer::renderDefault($input, $parser); } return true; } function wfTaxonomyHook($input, $args, &$parser) { $parser->disableCache(); wfLoadExtensionMessages('Taxonomy'); // get the root, must be one, bailout if not specified if( !empty($args['root'])) { $root = $args['root']; } else if(!empty($input)) { $root=$input; } else { return wfMsgHtml('taxonomy-no-root'); } // get the other arguments $category=$root; if (! empty($args['category'] )) $category=$args['category']; $type = 'new'; if ( in_array($args['type'], array('new','hot')) ) $type = $args['type']; $sort = 'DESC'; /*if ( in_array($type, array('hot')) ) $sort = 'ASC';*/ if ( in_array($args['sort'], array('desc', 'descending')) ) $sort = 'DESC'; else if ( in_array($args['sort'], array('asc', 'ascending')) ) $sort = 'ASC'; $count = 5; if (is_numeric($args['count'])) $count = $args['count']; $title = ''; if ( ! empty($args['title'])) $title = $args['title']; if ( ! empty($args['namespace'])) $namespace = $args['namespace']; //print("Debug args: $root | $category | $namespace | $type | $count | $sort \n "); // do the initialization $t=Title::newFromText($root, NS_CATEGORY); $root=$t->getDBkey(); $t=Title::newFromText($category, NS_CATEGORY); $category=$t->getDBkey(); if (empty($namespace)) $namespace = NS_MAIN; // else, find the right NS $dbr =& wfGetDB( DB_SLAVE ); $root_id = Taxonomy::getRootId($dbr, $root); if (empty($root_id)) { return "<p>" . wfMsgHtml('taxonomy-no-taxonomy') . "<p>"; } //print("Debug Root : $root # $root_id\n"); // find the $category $tax = Taxonomy::getTaxonomy($dbr, $root_id, $category); if (empty($tax)) { return "<p>" . wfMsgHtml('taxonomy-no-category') . "<p>"; } //print_r($tax); $tr = new TaxonomyRenderer(); switch ($type) { case 'hot' : return $tr->renderHot($dbr, $tax, $namespace, $count, $sort, $title); break; case 'new' : return $tr->renderNew($dbr, $tax, $namespace, $count, $sort, $title); break; case 'update' : return $tr->renderNew($dbr, $tax, $namespace, $count, $sort, $title); break; } } /****************************************************************** * Classes Implementation ******************************************************************/ class TaxonomyRenderer { // render newest articles below a category function renderNew($dbr, $tax, $namespace, $count, $sort, $title) { // find all pages under the category $list = Taxonomy::getNewArticles($dbr, $tax, $namespace, $count, $sort); if (empty ($list)) return "<p>" . wfMsgHtml('taxonomy-no-article') . "<p>"; return $this->renderList($title, $list); } function renderHot($dbr, $tax, $namespace, $count, $sort, $title) { // find all pages under the category $list = Taxonomy::getHotArticles($dbr, $tax, $namespace, $count, $sort); if (empty($list)) return "<p>" . wfMsgHtml('taxonomy-no-article') . "<p>"; return $this->renderList($title, $list); } function renderList($title, $list) { global $wgUser; global $wgContLang; $sk =& $wgUser->getSkin(); $output = "<b>$title</b>\n\n"; foreach ($list as $item) { $t = Title::makeTitle($item->namespace, $item->title); $l = $sk->makeKnownLinkObj($t, $wgContLang->convertHtml($t->getText())); $output .= "* " . $l . " <font size=-2>(" . $item->note . ")</font>\n"; } return $output; } } class TaxonomyTreeRenderer { static function renderDefault($input, &$parser) { $output=''; $tmp = preg_replace('/([\#|\*]+?) (.*?)[\r\n|\r|\n]/', '$1[[:Category:$2|$2]]' . "\n", $input); /* for debugging $output .= '<pre>'; $output .= $tmp; $output .= '</pre>'; */ $localParser = new Parser(); $tmp = $localParser->parse($tmp, $parser->mTitle, $parser->mOptions); $output .= $tmp->getText(); return $output; } } #---------------------------------------------------------------------------- # TaxonomyItem class for extension #---------------------------------------------------------------------------- class TaxonomyItem { var $tax_page; var $tax_category; var $tax_parent; var $tax_left; var $tax_right; var $category; public static function newTop($cat_id, $page_id) { $item = new self(); $item->tax_page = $page_id; $item->tax_category = $cat_id; $item->tax_parent = 0; $item->tax_left = 1; $item->tax_right = 2; return $item; } public static function newChild($cat_id, &$parent) { $item = new self(); $item->tax_category = $cat_id; $item->tax_page = $parent->tax_page; $item->tax_parent = $parent->tax_category; $item->tax_left = $parent->tax_right; $item->tax_right = $parent->tax_right+1; return $item; } public function saveItem($dbw) { $dbw->insert( 'taxonomy', array( 'tax_page' => $this->tax_page, 'tax_category' => $this->tax_category, 'tax_parent' => $this->tax_parent, 'tax_left' => $this->tax_left, 'tax_right' => $this->tax_right), __METHOD__, 'IGNORE' ); return true; } } class TaxonomyTree { var $tree; // array to store the nested tree structure public static function newFromText($article, $text) { preg_match_all('/([\#|\*]+?) (.*?) *[\r\n|\r|\n]/', $text, $matches, PREG_SET_ORDER); if (empty ($matches)) return null; $tax = new self(); $page_id = $article->getId(); $top_id = $tax->getCategoryId($article->getTitle()); //debug("Top : " . $article->getTitle() . " # $top_id | $page_id\n"); $tax->tree = array(); $tax->tree[] = TaxonomyItem::newTop($top_id, $page_id); //$top = $tax->tree[0]; //debug("Top : $top->tax_page | $top->tax_category | \n"); $tax->traverse(1, $matches, $tax->tree); return $tax; } function traverse($level, &$list, &$tree) { $top = $tree[0]; //debug("Top : $top->tax_page | $top->tax_category | \n"); $index = count($tree)-1; $parent = $tree[$index]; //debug("Parent ($index): $parent->tax_page | $parent->tax_category | $parent->tax_left | $parent->tax_right |\n"); while ($index <= count($list)) { $len = strlen($list[$index][1]); if ($len < $level) break; if ($len == $level) { $catTitle=Title::newFromText($list[$index][2], NS_CATEGORY); $catId = $this->getCategoryId($catTitle); //debug("$catTitle # $catId \n"); $current = TaxonomyItem::newChild($catId, $parent); //debug("Item : $current->tax_page | $current->tax_category | $current->tax_left | $current->tax_right |\n"); $parent->tax_right = $current->tax_right+1; $tree[]=$current; } else { $this->traverse($len, $list, $tree); if (isset($current)) { $parent->tax_right=$current->tax_right+1; } } $index = count($tree)-1; } $parent->tax_right=$current->tax_right+1; return true; } function addCategory($dbw, $cat_title) { $values = array('cat_title' => $cat_title ); $dbw->insert('category', $values, __METHOD__, 'IGNORE'); } function getCategory($dbr, $cat_title) { $fields = array( '*'); $where = array( 'cat_title' => $cat_title ); $row = $dbr->selectRow('category', $fields, $where, __METHOD__); return $row; } // get the category from category table // if not exist, create a new one function getCategoryId($ctitle) { $dbr=wfGetDB( DB_SLAVE ); $cat_title = $ctitle->getDBkey(); $cat = $this->getCategory($dbr, $cat_title); if (! empty($cat) ) { return $cat->cat_id; } $dbw=wfGetDB( DB_MASTER ); $dbw->begin(); $this->addCategory($dbw, $cat_title); $dbw->commit(); $cat = $this->getCategory($dbr, $cat_title); return $cat->cat_id; } static function deleteTree($dbw, $page_id) { $dbw->delete('taxonomy', array( 'tax_page' => $page_id ), __METHOD__ ); } public function saveTree() { if (! isset ($this->tree)) return true; if (count($this->tree) == 0) return true; $dbw=wfGetDB( DB_MASTER ); $dbw->begin(); // need to lock it // remove them all, then rebuild them all $this->deleteTree($dbw, $this->tree[0]->tax_page); foreach ($this->tree as $item) { $item->saveItem($dbw); } $dbw->commit(); } public static function removeTree($article) { $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); TaxonomyTree::deleteTree($dbw, $article->getId()); $dbw->commit(); } } class Taxonomy { static function getRootId($dbr, $root) { $fields = array( 'page_id'); $where = array( 'page_title' => $root ); $row = $dbr->selectRow('page', $fields, $where, __METHOD__); return $row->page_id; } static function getTaxonomy($dbr, $page_id, $category) { $sql = "SELECT tax_page, tax_category, tax_parent, tax_left, tax_right, cat_title as category " . "FROM taxonomy INNER JOIN category " . "ON (tax_category = cat_id) " . "WHERE (cat_title = '$category') AND (tax_page=$page_id) " . "LIMIT 1"; $res = $dbr->query($sql); if ($dbr->numRows( $res ) != 0) { $row = $dbr->fetchObject ( $res ); } $dbr->freeResult($res); return $row; } static function getNewArticles($dbr, $tax, $namespace, $count, $sort) { global $wgLang; $tax_left = $tax->tax_left; $tax_right = $tax->tax_right; $root_id = $tax->tax_page; $sql="SELECT page_namespace as namespace, page_title as title, page_touched as note " . "FROM page " . "INNER JOIN categorylinks ON page_id = cl_from " . "INNER JOIN category ON cl_to = cat_title " . "INNER JOIN taxonomy ON cat_id = tax_category " . "WHERE (tax_page=$root_id) AND (tax_left>$tax_left) AND (tax_right<$tax_right) " . "AND (page_namespace=$namespace) AND (page_is_redirect=0) " . "ORDER BY page_touched $sort " . "LIMIT $count"; //print($sql); $res = $dbr->query($sql); while ( ( $row = $dbr->fetchObject($res)) ) { $row->note=$wgLang->date($row->note, true); $list[] = $row; //print_r($row); } $dbr->freeResult($res); return $list; } static function getHotArticles($dbr, $tax, $namespace, $count, $sort) { $tax_left = $tax->tax_left; $tax_right = $tax->tax_right; $root_id = $tax->tax_page; $sql="SELECT page_namespace as namespace, page_title as title, page_counter as note " . "FROM page " . "INNER JOIN categorylinks ON page_id = cl_from " . "INNER JOIN category ON cl_to = cat_title " . "INNER JOIN taxonomy ON cat_id = tax_category " . "WHERE (tax_page=$root_id) AND (tax_left>$tax_left) AND (tax_right<$tax_right) " . "AND (page_namespace=$namespace) AND (page_is_redirect=0) " . "ORDER BY page_counter $sort " . "LIMIT $count"; //print($sql); $res = $dbr->query($sql); while ( ( $row = $dbr->fetchObject($res)) ) { $list[] = $row; //print_r($row); } $dbr->freeResult($res); return $list; } }
[edit] Taxonomy.i18n.php
<?php /** * Internationalisation file for the Taxonomy extension * * @ingroup Extensions * @author Eko M. Budi */ $messages = array(); /** English * @author Eko M. Budi */ $messages['en'] = array( 'taxonomy-tag' => 'taxonomy', 'taxonomy-tree' => 'taxonomy-tree', 'taxonomy-no-root' => 'missing root paramater in the taxonomy tag', 'taxonomy-no-taxonomy' => 'taxonomy is not exist', 'taxonomy-no-category' => 'category is not part of the taxonomy', 'taxonomy-no-article' => 'no article in this taxonomy', ); /** English * @author Eko M. Budi */ $messages['id'] = array( 'taxonomy-tag' => 'taksonomi', 'taxonomy-tree' => 'pohon-taksonomi', 'taxonomy-no-root' => 'parameter root tidak terdefinisi', 'taxonomy-no-taxonomy' => 'taksonomi tidak ada', 'taxonomy-no-category' => 'kategori tidak ada', 'taxonomy-no-article' => 'tidak ada artikel', );
[edit] See also
This extension was inspired by :
[edit] Acknowledgments
This program took some code from Extension:Hierarchy by Fernando Correia and Extension:Dynamic_Article_List by Shannon McNaught.