Index: trunk/extensions/IndexFunction/IndexFunction_body.php |
— | — | @@ -0,0 +1,211 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class IndexFunction { |
| 5 | + |
| 6 | + // Utility function to get the target pageid for a possible index-title |
| 7 | + static function getIndexTarget( Title $title ) { |
| 8 | + $ns = $title->getNamespace(); |
| 9 | + $t = $title->getDBkey(); |
| 10 | + $dbr = wfGetDB( DB_SLAVE ); |
| 11 | + $res = $dbr->select( 'indexes', 'in_from', |
| 12 | + array( 'in_namespace' => $ns, 'in_title' => $t ), |
| 13 | + __METHOD__ |
| 14 | + ); |
| 15 | + return $res; |
| 16 | + } |
| 17 | + |
| 18 | + // Makes "Go" searches for an index title go directly to their target |
| 19 | + static function redirectSearch( $term, &$title ) { |
| 20 | + $title = Title::newFromText( $term ); |
| 21 | + if ( is_null($title) ) { |
| 22 | + return true; |
| 23 | + } |
| 24 | + $res = self::getIndexTarget( $title ); |
| 25 | + if ( $res->numRows() == 0 ) { |
| 26 | + return true; |
| 27 | + } elseif ( $res->numRows() > 1 ) { |
| 28 | + global $wgOut; |
| 29 | + $title = SpecialPage::getTitleFor( 'Index', $title->getPrefixedText() ); |
| 30 | + $wgOut->redirect( $title->getLocalURL() ); |
| 31 | + return true; |
| 32 | + } |
| 33 | + $res = $res->fetchRow(); |
| 34 | + $title = Title::newFromId( $res ); |
| 35 | + return false; |
| 36 | + } |
| 37 | + |
| 38 | + // Make indexes work like redirects |
| 39 | + static function doRedirect( &$title, &$request,& $ignoreRedirect, &$target, &$article ) { |
| 40 | + if ( $article->exists() ) { |
| 41 | + return true; |
| 42 | + } |
| 43 | + $res = self::getIndexTarget( $title ); |
| 44 | + if ( $res->numRows() == 0 ) { |
| 45 | + return true; |
| 46 | + } elseif ( $res->numRows() > 1 ) { |
| 47 | + global $wgOut; |
| 48 | + $t = SpecialPage::getTitleFor( 'Index', $title->getPrefixedText() ); |
| 49 | + $wgOut->redirect( $t->getLocalURL() ); |
| 50 | + return true; |
| 51 | + } else { |
| 52 | + $res = $res->fetchRow(); |
| 53 | + $redir = Title::newFromID( $res ); |
| 54 | + } |
| 55 | + $target = $redir; |
| 56 | + $article->mIsRedirect = true; |
| 57 | + $ignoreRedirect = false; |
| 58 | + return true; |
| 59 | + } |
| 60 | + |
| 61 | + // Turn links to indexes into blue links |
| 62 | + static function blueLinkIndexes( $skin, $target, $options, &$text, &$attribs, &$ret ) { |
| 63 | + if ( in_array( 'known', $options ) ) { |
| 64 | + return true; |
| 65 | + } |
| 66 | + $res = self::getIndexTarget( $target ); |
| 67 | + if ( $res->numRows() == 0 ) { |
| 68 | + return true; |
| 69 | + } |
| 70 | + $attribs['class'] = str_replace( 'new', 'mw-index', $attribs['class'] ); |
| 71 | + $attribs['href'] = $target->getLinkUrl(); |
| 72 | + $attribs['title'] = $target->getEscapedText(); |
| 73 | + return true; |
| 74 | + } |
| 75 | + |
| 76 | + // Register the function name |
| 77 | + static function addIndexFunction( &$magicWords, $langCode ) { |
| 78 | + $magicWords['index-func'] = array( 0, 'index' ); |
| 79 | + return true; |
| 80 | + } |
| 81 | + |
| 82 | + // Function called to render the parser function |
| 83 | + // Output is an empty string unless there are errors |
| 84 | + static function indexRender( &$parser ) { |
| 85 | + if ( !isset($parser->mOutput->mIndexes) ) { |
| 86 | + $parser->mOutput->mIndexes = array(); |
| 87 | + } |
| 88 | + wfLoadExtensionMessages( 'IndexFunction' ); |
| 89 | + static $indexCount = 0; |
| 90 | + static $indexes = array(); |
| 91 | + $args = func_get_args(); |
| 92 | + unset( $args[0] ); |
| 93 | + if ( $parser->mOptions->getIsPreview() ) { |
| 94 | + # This is kind of hacky, but it seems that we only |
| 95 | + # know if its a preview during parse, not when its |
| 96 | + # done, which is when it matters for this |
| 97 | + $parser->mOutput->setProperty( 'preview', 1 ); |
| 98 | + } |
| 99 | + $errors = array(); |
| 100 | + $pageid = $parser->mTitle->getArticleID(); |
| 101 | + foreach ( $args as $name ) { |
| 102 | + $t = Title::newFromText( $name ); |
| 103 | + if( is_null( $t ) ) { |
| 104 | + $errors[] = wfMsg( 'indexfunc-badtitle', $name ); |
| 105 | + continue; |
| 106 | + } |
| 107 | + $ns = $t->getNamespace(); |
| 108 | + $dbkey = $t->getDBkey(); |
| 109 | + $entry = array( $ns, $dbkey ); |
| 110 | + if ( in_array( $entry, $indexes ) ) { |
| 111 | + continue; |
| 112 | + } |
| 113 | + if ( $t->exists() ) { |
| 114 | + $errors[] = wfMsg( 'indexfunc-index-exists', $name ); |
| 115 | + continue; |
| 116 | + } |
| 117 | + $indexCount++; |
| 118 | + $parser->mOutput->mIndexes[$indexCount] = $entry; |
| 119 | + } |
| 120 | + if ( !$errors ) { |
| 121 | + return ''; |
| 122 | + } |
| 123 | + $out = Xml::openElement( 'ul', array( 'class'=>'error' ) ); |
| 124 | + foreach( $errors as $e ) { |
| 125 | + $out .= Xml::element( 'li', null, $e ); |
| 126 | + } |
| 127 | + $out .= Xml::closeElement( 'ul' ); |
| 128 | + return $out; |
| 129 | + } |
| 130 | + |
| 131 | + // Called after parse, updates the index table |
| 132 | + static function doIndexes( &$out, &$parseroutput ) { |
| 133 | + global $wgTitle; |
| 134 | + if ( !isset($parseroutput->mIndexes) ) { |
| 135 | + return true; |
| 136 | + } |
| 137 | + if ( $parseroutput->getProperty( 'preview' ) ) { |
| 138 | + return true; |
| 139 | + } |
| 140 | + $pageid = $wgTitle->getArticleID(); |
| 141 | + $dbw = wfGetDB( DB_MASTER ); |
| 142 | + $res = $dbw->select( 'indexes', |
| 143 | + array( 'in_namespace', 'in_title' ), |
| 144 | + array( 'in_from' => $pageid ), |
| 145 | + __METHOD__ |
| 146 | + ); |
| 147 | + $current = array(); |
| 148 | + foreach( $res as $row ) { |
| 149 | + $current[] = array( $row->in_namespace, $row->in_title ); |
| 150 | + } |
| 151 | + $toAdd = wfArrayDiff2( $parseroutput->mIndexes, $current ); |
| 152 | + $toRem = wfArrayDiff2( $current, $parseroutput->mIndexes ); |
| 153 | + if ( true ) { |
| 154 | + $dbw->begin( __METHOD__ ); |
| 155 | + if ( $toRem ) { |
| 156 | + $delCond = "in_from = $pageid AND ("; |
| 157 | + $parts = array(); |
| 158 | + # Looking at Database::delete, it seems to turn arrays into AND statements |
| 159 | + # but we need to chain together groups of ANDs with ORs |
| 160 | + foreach ( $toRem as $entry ) { |
| 161 | + $parts[] = "(in_namespace = " . $entry[0] . " AND in_title = " . $dbw->addQuotes($entry[1]) . ")"; |
| 162 | + } |
| 163 | + $delCond .= implode( ' OR ', $parts ) . ")"; |
| 164 | + $dbw->delete( 'indexes', array($delCond), __METHOD__ ); |
| 165 | + } |
| 166 | + if ( $toAdd ) { |
| 167 | + $ins = array(); |
| 168 | + foreach ( $toAdd as $entry ) { |
| 169 | + $ins[] = array( 'in_from' => $pageid, 'in_namespace' => $entry[0], 'in_title' => $entry[1] ); |
| 170 | + } |
| 171 | + $dbw->insert( 'indexes', $ins, __METHOD__ ); |
| 172 | + } |
| 173 | + $dbw->commit( __METHOD__ ); |
| 174 | + } |
| 175 | + return true; |
| 176 | + } |
| 177 | + |
| 178 | + // When deleting a page, delete all rows from the index table that point to it |
| 179 | + static function onDelete( &$article, &$user, $reason, $id ) { |
| 180 | + $dbw = wfGetDB( DB_MASTER ); |
| 181 | + $dbw->delete( 'indexes', array( 'in_from'=>$id ), __METHOD__ ); |
| 182 | + return true; |
| 183 | + } |
| 184 | + |
| 185 | + // When creating an article, delete its title from the index table |
| 186 | + static function onCreate( &$article, &$user, &$text, &$summary, &$minoredit, &$watchthis, &$sectionanchor, &$flags, &$revision ) { |
| 187 | + $t = $article->mTitle; |
| 188 | + $ns = $t->getNamespace(); |
| 189 | + $dbkey = $t->getDBkey(); |
| 190 | + $dbw = wfGetDB( DB_MASTER ); |
| 191 | + $dbw->delete( 'indexes', |
| 192 | + array( 'in_namespace'=>$ns, 'in_title'=>$dbkey ), |
| 193 | + __METHOD__ ); |
| 194 | + return true; |
| 195 | + } |
| 196 | + |
| 197 | + // Show a warning when editing an index-title |
| 198 | + static function editWarning( $editpage ) { |
| 199 | + $t = $editpage->mTitle; |
| 200 | + $target = self::getIndexTarget( $t ); |
| 201 | + if ( $target->numRows() != 1 ) { # FIXME |
| 202 | + return true; |
| 203 | + } |
| 204 | + $target = $target->fetchRow(); |
| 205 | + $page = Title::newFromID( $target['in_from'] ); |
| 206 | + wfLoadExtensionMessages( 'IndexFunction' ); |
| 207 | + $warn = wfMsgExt( 'indexfunc-editwarn', array( 'parse' ), $page->getPrefixedText() ); |
| 208 | + $editpage->editFormTextBeforeContent .= "<span class='error'>$warn</span>"; |
| 209 | + return true; |
| 210 | + } |
| 211 | +} |
| 212 | + |
Index: trunk/extensions/IndexFunction/IndexFunction.i18n.php |
— | — | @@ -0,0 +1,22 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +$messages = array(); |
| 5 | + |
| 6 | +$messages['en'] = array( |
| 7 | + 'indexfunc-desc' => 'Parser function to create automatic redirects and disambiguation pages', |
| 8 | + |
| 9 | + 'indexfunc-badtitle' => 'Invalid title: "$1"', |
| 10 | + 'indexfunc-editwarn' => 'Warning: This title is an index title for [[$1]]. |
| 11 | +Be sure the page you are about to create does not already exist under a different title. |
| 12 | +If you create this page, remove this title from the <nowiki>{{#index:}}</nowiki> on $1.', |
| 13 | + 'indexfunc-index-exists' => 'The page "$1" already exists', |
| 14 | + 'indexfunc-index-taken' => '"$1" is already used as an index by "$2"', |
| 15 | + |
| 16 | + 'index' => 'Index', |
| 17 | + 'index-legend' => 'Search the index', |
| 18 | + 'index-search' => 'Search: ', |
| 19 | + 'index-submit' => 'Submit', |
| 20 | + 'index-disambig-start' => "'''$1''' may refer to several pages:", |
| 21 | + 'index-exclude-categories' => '', # List of categories to exclude from the auto-disambig pages |
| 22 | + 'index-missing-param' => 'This page cannot be used with no parameters', |
| 23 | +); |
Index: trunk/extensions/IndexFunction/IndexFunction.php |
— | — | @@ -0,0 +1,53 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +$wgExtensionCredits['other'][] = array( |
| 5 | + 'path' => __FILE__, |
| 6 | + 'name' => 'IndexFunction', |
| 7 | + 'author' =>'Alex Zaddach', |
| 8 | + 'url' => 'http://www.mediawiki.org/wiki/Extension:IndexFunction', |
| 9 | + 'descriptionmsg' => 'indexfunc-desc', |
| 10 | + 'description' => 'Parser function to create automatic redirects and disambiguation pages' |
| 11 | +); |
| 12 | + |
| 13 | +$dir = dirname(__FILE__) . '/'; |
| 14 | + |
| 15 | +# Register function |
| 16 | +$wgHooks['ParserFirstCallInit'][] = 'efIndexSetup'; |
| 17 | +$wgHooks['LanguageGetMagic'][] = 'IndexFunction::addIndexFunction'; |
| 18 | +# Add to database |
| 19 | +$wgHooks['OutputPageParserOutput'][] = 'IndexFunction::doIndexes'; |
| 20 | +# Make links to indexes blue |
| 21 | +$wgHooks['LinkEnd'][] = 'IndexFunction::blueLinkIndexes'; |
| 22 | +# Make links to indexes redirect |
| 23 | +$wgHooks['InitializeArticleMaybeRedirect'][] = 'IndexFunction::doRedirect'; |
| 24 | +# Make "go" searches for indexes redirect |
| 25 | +$wgHooks['SearchGetNearMatch'][] = 'IndexFunction::redirectSearch'; |
| 26 | +# Remove things from the index table when a page is deleted |
| 27 | +$wgHooks['ArticleDeleteComplete'][] = 'IndexFunction::onDelete'; |
| 28 | +# Remove things from the index table when creating a new page |
| 29 | +$wgHooks['ArticleInsertComplete'][] = 'IndexFunction::onCreate'; |
| 30 | +# Show a warning when editing an index title |
| 31 | +$wgHooks['EditPage::showEditForm:initial'][] = 'IndexFunction::editWarning'; |
| 32 | + |
| 33 | +$wgHooks['LoadExtensionSchemaUpdates'][] = 'efIndexUpdateSchema'; |
| 34 | + |
| 35 | +# Setup the special page |
| 36 | +$wgSpecialPages['Index'] = 'SpecialIndex'; |
| 37 | +$wgAutoloadClasses['SpecialIndex'] = $dir . 'SpecialIndex.php'; |
| 38 | + |
| 39 | +$wgExtensionMessagesFiles['IndexFunction'] = $dir . 'IndexFunction.i18n.php'; |
| 40 | +$wgAutoloadClasses['IndexFunction'] = $dir . 'IndexFunction_body.php'; |
| 41 | + |
| 42 | +function efIndexSetup( &$parser ) { |
| 43 | + $parser->setFunctionHook( 'index-func', array( 'IndexFunction', 'indexRender' ) ); |
| 44 | + return true; |
| 45 | +} |
| 46 | + |
| 47 | +function efIndexUpdateSchema() { |
| 48 | + global $wgExtNewTables; |
| 49 | + $wgExtNewTables[] = array( |
| 50 | + 'indexes', |
| 51 | + dirname( __FILE__ ) . '/indexes.sql' ); |
| 52 | + return true; |
| 53 | +} |
| 54 | + |
Index: trunk/extensions/IndexFunction/indexes.sql |
— | — | @@ -0,0 +1,12 @@ |
| 2 | +CREATE TABLE /*$wgDBprefix*/indexes ( |
| 3 | + -- The pageid of the page containing the parser function |
| 4 | + in_from int unsigned NOT NULL default 0, |
| 5 | + |
| 6 | + -- The NS/title that should redirect to the page |
| 7 | + in_namespace int NOT NULL default 0, |
| 8 | + in_title varchar(255) binary NOT NULL default '', |
| 9 | + |
| 10 | + KEY (in_from), |
| 11 | + KEY in_target (in_namespace, in_title) |
| 12 | +) ENGINE=InnoDB; |
| 13 | + |
Index: trunk/extensions/IndexFunction/SpecialIndex.php |
— | — | @@ -0,0 +1,167 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class SpecialIndex extends SpecialPage { |
| 5 | + function __construct() { |
| 6 | + parent::__construct( 'Index' ); |
| 7 | + wfLoadExtensionMessages('IndexFunction'); |
| 8 | + } |
| 9 | + |
| 10 | + function execute( $par ) { |
| 11 | + global $wgOut; |
| 12 | + |
| 13 | + $this->setHeaders(); |
| 14 | + if ($par) { |
| 15 | + $t1 = Title::newFromText( $par ); |
| 16 | + $this->showDabPage( $t1 ); |
| 17 | + } else { |
| 18 | + $wgOut->addWikiMsg( 'index-missing-param' ); |
| 19 | + } |
| 20 | + # Will eventually be some sort of a search form |
| 21 | + |
| 22 | + //$form = Xml::openElement( 'fieldset' ) . |
| 23 | + // Xml::element( 'legend', array(), wfMsgHtml( 'index-legend' ) ) . |
| 24 | + // Xml::openElement( 'form', array( 'method'=>'GET' ) ) . |
| 25 | + |
| 26 | + // Xml::label( wfMsg( 'index-search' ), 'mw-index-searchtext' ) . |
| 27 | + // Xml::input( 'searchtext', 100, false, array( 'id' => 'mw-index-searchtext' ) ) . |
| 28 | + // '<br />' . |
| 29 | + // Xml::submitButton( wfMsg( 'index-submit' ) ) . |
| 30 | + |
| 31 | + // Xml::closeElement( 'form' ) . |
| 32 | + // Xml::closeElement( 'fieldset' ); |
| 33 | + |
| 34 | + //$wgOut->addHTML( $form ); |
| 35 | + |
| 36 | + } |
| 37 | + |
| 38 | + function showDabPage( Title $t1 ) { |
| 39 | + global $wgOut, $wgUser; |
| 40 | + $sk = $wgUser->getSkin(); |
| 41 | + $wgOut->setPagetitle( $t1->getPrefixedText() ); |
| 42 | + $wgOut->addWikiMsg( 'index-disambig-start', $t1->getPrefixedText() ); |
| 43 | + $dbr = wfGetDB( DB_SLAVE ); |
| 44 | + $pages = $dbr->select( array('page', 'indexes'), |
| 45 | + array( 'page_id', 'page_namespace', 'page_title' ), |
| 46 | + array( 'in_namespace'=>$t1->getNamespace(), 'in_title'=>$t1->getDBkey() ), |
| 47 | + __METHOD__, |
| 48 | + array('ORDER BY'=> 'page_namespace, page_title'), |
| 49 | + array( 'indexes' => array('JOIN', 'in_from=page_id') ) |
| 50 | + ); |
| 51 | + |
| 52 | + $list = array(); |
| 53 | + foreach( $pages as $row ) { |
| 54 | + $t = Title::newFromRow( $row ); |
| 55 | + $list[strval($row->page_id)] = array( 'title' => $t, 'cats' => array() ); |
| 56 | + } |
| 57 | + $keys = array_keys( $list ); |
| 58 | + $set = '(' . implode(',', $keys) . ')'; |
| 59 | + |
| 60 | + $excludecats = wfMsg('index-exclude-categories'); |
| 61 | + if ($excludecats) { |
| 62 | + $excludecats = str_replace(' ', '_', $excludecats); |
| 63 | + $excludecats = explode( '\n', $excludecats ); |
| 64 | + foreach( $excludecats as $index => $cat ) { |
| 65 | + $excludecats[$index] = $dbr->addQuotes( $cat ); |
| 66 | + } |
| 67 | + $excludecats = 'AND cl_to NOT IN (' . implode(',', $excludecats) . ')'; |
| 68 | + } else { |
| 69 | + $excludecats = ''; |
| 70 | + } |
| 71 | + |
| 72 | + $categories = $dbr->select( 'categorylinks', |
| 73 | + array('cl_from', 'cl_to'), |
| 74 | + "cl_from IN $set $excludecats", |
| 75 | + __METHOD__, |
| 76 | + array('ORDER BY' => 'cl_from') |
| 77 | + ); |
| 78 | + $groups = array(); |
| 79 | + $catlist = array(); |
| 80 | + foreach( $categories as $row ) { |
| 81 | + $ct = Title::newFromText( $row->cl_to, NS_CATEGORY ); |
| 82 | + $textform = $ct->getText(); |
| 83 | + $list[strval($row->cl_from)]['cats'][] = $textform; |
| 84 | + if ( array_key_exists( $textform, $catlist ) ) { |
| 85 | + $catlist[$textform][] = strval($row->cl_from); |
| 86 | + } else { |
| 87 | + $catlist[$textform] = array ( strval($row->cl_from) ); |
| 88 | + } |
| 89 | + } |
| 90 | + if (count($catlist) > 2) { |
| 91 | + while (true) { |
| 92 | + arsort($catlist); |
| 93 | + $group = reset( $catlist ); |
| 94 | + if (count($group) == 0) { |
| 95 | + break; |
| 96 | + } |
| 97 | + $keys = array_keys($catlist, $group); |
| 98 | + $heading = $keys[0]; |
| 99 | + $grouphtml = Xml::element('h2', null, $heading); |
| 100 | + $grouphtml .= Xml::openElement( 'ul' ); |
| 101 | + foreach( $group as $pageid ) { |
| 102 | + $t = $list[$pageid]['title']; |
| 103 | + $cats = $list[$pageid]['cats']; |
| 104 | + $link = $sk->link( $t, null, array(), array(), array( 'known', 'noclasses' ) ); |
| 105 | + $grouphtml .= Xml::tags( 'li', array(), $link . ' &ndash ' . implode( ', ', $cats ) ); |
| 106 | + unset( $list[$pageid] ); |
| 107 | + ksort($list); |
| 108 | + foreach($catlist as $remaining) { |
| 109 | + $key = array_search( $pageid, $remaining ); |
| 110 | + if ( $key !== false ) { |
| 111 | + $masterkeys = array_keys($catlist, $remaining); |
| 112 | + $heading = $masterkeys[0]; |
| 113 | + unset($catlist[$heading][$key]); |
| 114 | + sort($catlist[$heading]); |
| 115 | + } |
| 116 | + } |
| 117 | + } |
| 118 | + $grouphtml .= Xml::closeElement( 'ul' ); |
| 119 | + $groups[] = $grouphtml; |
| 120 | + unset( $catlist[$heading] ); |
| 121 | + if (count($catlist) == 0) { |
| 122 | + break; |
| 123 | + } |
| 124 | + } |
| 125 | + if (count($list) != 0) { //Pages w/ no cats |
| 126 | + $grouphtml = Xml::openElement( 'ul' ); |
| 127 | + foreach( $list as $pageid => $info ) { |
| 128 | + $link = $sk->link( $info['title'], null, array(), array(), array( 'known', 'noclasses' ) ); |
| 129 | + $grouphtml .= Xml::tags( 'li', array(), $link ); |
| 130 | + } |
| 131 | + $grouphtml .= Xml::closeElement('ul'); |
| 132 | + $groups = array_merge( array($grouphtml), $groups); |
| 133 | + } |
| 134 | + $out = implode( "\n", $groups ); |
| 135 | + } else { |
| 136 | + $out = Xml::openElement( 'ul' ); |
| 137 | + foreach( $list as $pageid => $info ) { |
| 138 | + $link = $sk->link( $info['title'], null, array(), array(), array( 'known', 'noclasses' ) ); |
| 139 | + if ( $info['cats'] ) { |
| 140 | + $line = $link . ' &ndash ' . implode( ', ', $info['cats'] ); |
| 141 | + $line = Xml::tags( 'li', array(), $line ); |
| 142 | + } else { |
| 143 | + $line = Xml::tags( 'li', array(), $link ); |
| 144 | + } |
| 145 | + $out .= $line; |
| 146 | + } |
| 147 | + $out .= Xml::closeElement('ul'); |
| 148 | + } |
| 149 | + |
| 150 | + $wgOut->addHtml($out); |
| 151 | + //$wgOut->addHTML( Xml::openElement( 'ul' ) ); |
| 152 | + # TODO: Group into sections based on most used categories. |
| 153 | + # When iterating over the category results above, use another array |
| 154 | + # to record the most frequently used ones and add some h2's in the |
| 155 | + # list for some (all?) |
| 156 | + //foreach( $list as $pageid => $info ) { |
| 157 | + // $link = $sk->link( $info['title'], null, array(), array(), array( 'known', 'noclasses' ) ); |
| 158 | + // if ( $info['cats'] ) { |
| 159 | + // $line = $link . ' &ndash ' . implode( ', ', $info['cats'] ); |
| 160 | + // } else { |
| 161 | + // $line = $link; |
| 162 | + // } |
| 163 | + // $wgOut->addHtml( Xml::tags( 'li', array(), $line) ); |
| 164 | + //} |
| 165 | + //$wgOut->addHTML( Xml::closeElement('ul') ); |
| 166 | + } |
| 167 | +} |
| 168 | + |