2006-07-26 17:12:30 +00:00
|
|
|
<?php
|
|
|
|
/**
|
2018-04-16 06:50:28 +00:00
|
|
|
* © 2006-2007 Daniel Kinzler
|
|
|
|
*
|
|
|
|
* 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.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
2006-07-26 17:12:30 +00:00
|
|
|
*
|
2010-06-06 15:12:22 +00:00
|
|
|
* @file
|
|
|
|
* @ingroup Extensions
|
2007-03-13 11:39:12 +00:00
|
|
|
* @author Daniel Kinzler, brightbyte.de
|
2006-07-26 17:12:30 +00:00
|
|
|
*/
|
2021-04-07 22:35:52 +00:00
|
|
|
|
|
|
|
namespace MediaWiki\Extension\CategoryTree;
|
|
|
|
|
|
|
|
use Category;
|
|
|
|
use Exception;
|
|
|
|
use ExtensionRegistry;
|
|
|
|
use FormatJson;
|
|
|
|
use Html;
|
|
|
|
use IContextSource;
|
|
|
|
use LinkBatch;
|
2016-05-30 10:01:38 +00:00
|
|
|
use MediaWiki\Linker\LinkRenderer;
|
2019-07-31 05:46:31 +00:00
|
|
|
use MediaWiki\MediaWikiServices;
|
2021-04-07 22:35:52 +00:00
|
|
|
use OutputPage;
|
|
|
|
use Parser;
|
|
|
|
use RequestContext;
|
|
|
|
use SpecialPage;
|
|
|
|
use Title;
|
|
|
|
use Xml;
|
2019-07-31 05:46:31 +00:00
|
|
|
|
2018-04-16 06:50:28 +00:00
|
|
|
/**
|
|
|
|
* Core functions for the CategoryTree extension, an AJAX based gadget
|
|
|
|
* to display the category structure of a wiki
|
|
|
|
*/
|
2006-08-24 17:12:13 +00:00
|
|
|
class CategoryTree {
|
2016-10-14 16:11:52 +00:00
|
|
|
public $mOptions = [];
|
2016-05-30 10:01:38 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var LinkRenderer
|
|
|
|
*/
|
2019-07-31 05:46:31 +00:00
|
|
|
private $linkRenderer;
|
2008-06-28 20:13:20 +00:00
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
/**
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param array $options
|
2012-02-09 01:23:31 +00:00
|
|
|
*/
|
2018-12-16 09:47:07 +00:00
|
|
|
public function __construct( array $options ) {
|
2008-06-28 20:13:20 +00:00
|
|
|
global $wgCategoryTreeDefaultOptions;
|
2019-07-31 05:46:31 +00:00
|
|
|
$this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
|
2008-06-28 20:13:20 +00:00
|
|
|
|
2017-05-30 18:21:54 +00:00
|
|
|
// ensure default values and order of options.
|
|
|
|
// Order may become important, it may influence the cache key!
|
2008-06-28 20:13:20 +00:00
|
|
|
foreach ( $wgCategoryTreeDefaultOptions as $option => $default ) {
|
2018-04-11 03:40:17 +00:00
|
|
|
if ( isset( $options[$option] ) ) {
|
2010-04-20 22:00:34 +00:00
|
|
|
$this->mOptions[$option] = $options[$option];
|
|
|
|
} else {
|
|
|
|
$this->mOptions[$option] = $default;
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->mOptions['mode'] = self::decodeMode( $this->mOptions['mode'] );
|
2008-07-02 09:49:28 +00:00
|
|
|
|
2016-02-05 06:45:46 +00:00
|
|
|
if ( $this->mOptions['mode'] == CategoryTreeMode::PARENTS ) {
|
2017-05-30 18:21:54 +00:00
|
|
|
// namespace filter makes no sense with CategoryTreeMode::PARENTS
|
|
|
|
$this->mOptions['namespaces'] = false;
|
2008-07-02 09:49:28 +00:00
|
|
|
}
|
|
|
|
|
2008-06-30 21:22:03 +00:00
|
|
|
$this->mOptions['hideprefix'] = self::decodeHidePrefix( $this->mOptions['hideprefix'] );
|
2017-04-17 13:11:25 +00:00
|
|
|
$this->mOptions['showcount'] = self::decodeBoolean( $this->mOptions['showcount'] );
|
|
|
|
$this->mOptions['namespaces'] = self::decodeNamespaces( $this->mOptions['namespaces'] );
|
2008-07-01 21:40:42 +00:00
|
|
|
|
|
|
|
if ( $this->mOptions['namespaces'] ) {
|
|
|
|
# automatically adjust mode to match namespace filter
|
2017-05-24 18:00:48 +00:00
|
|
|
if ( count( $this->mOptions['namespaces'] ) === 1
|
2008-07-01 21:40:42 +00:00
|
|
|
&& $this->mOptions['namespaces'][0] == NS_CATEGORY ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
$this->mOptions['mode'] = CategoryTreeMode::CATEGORIES;
|
2016-09-28 03:31:04 +00:00
|
|
|
} elseif ( !in_array( NS_FILE, $this->mOptions['namespaces'] ) ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
$this->mOptions['mode'] = CategoryTreeMode::PAGES;
|
2008-07-01 21:40:42 +00:00
|
|
|
} else {
|
2016-02-05 06:45:46 +00:00
|
|
|
$this->mOptions['mode'] = CategoryTreeMode::ALL;
|
2008-07-01 21:40:42 +00:00
|
|
|
}
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
}
|
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
/**
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param string $name
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return mixed
|
|
|
|
*/
|
2018-04-11 03:47:26 +00:00
|
|
|
public function getOption( $name ) {
|
2008-06-28 20:13:20 +00:00
|
|
|
return $this->mOptions[$name];
|
|
|
|
}
|
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
2018-04-11 03:47:26 +00:00
|
|
|
private function isInverse() {
|
2016-02-05 06:45:46 +00:00
|
|
|
return $this->getOption( 'mode' ) == CategoryTreeMode::PARENTS;
|
2008-07-02 09:49:28 +00:00
|
|
|
}
|
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
/**
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param mixed $nn
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return array|bool
|
|
|
|
*/
|
2018-04-11 03:47:26 +00:00
|
|
|
private static function decodeNamespaces( $nn ) {
|
2020-01-14 03:58:41 +00:00
|
|
|
if ( $nn === false || $nn === null ) {
|
2008-07-01 21:40:42 +00:00
|
|
|
return false;
|
2012-02-09 01:23:31 +00:00
|
|
|
}
|
2008-07-01 21:40:42 +00:00
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
if ( !is_array( $nn ) ) {
|
2008-07-01 21:40:42 +00:00
|
|
|
$nn = preg_split( '![\s#:|]+!', $nn );
|
2012-02-09 01:23:31 +00:00
|
|
|
}
|
2008-07-01 21:40:42 +00:00
|
|
|
|
2016-10-14 16:11:52 +00:00
|
|
|
$namespaces = [];
|
2019-09-01 13:59:59 +00:00
|
|
|
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
|
2008-07-01 21:40:42 +00:00
|
|
|
foreach ( $nn as $n ) {
|
|
|
|
if ( is_int( $n ) ) {
|
|
|
|
$ns = $n;
|
2012-02-09 01:23:31 +00:00
|
|
|
} else {
|
2008-07-01 21:40:42 +00:00
|
|
|
$n = trim( $n );
|
2012-02-09 01:23:31 +00:00
|
|
|
if ( $n === '' ) {
|
|
|
|
continue;
|
|
|
|
}
|
2010-04-20 22:00:34 +00:00
|
|
|
|
2008-07-01 21:40:42 +00:00
|
|
|
$lower = strtolower( $n );
|
2010-04-20 22:00:34 +00:00
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
if ( is_numeric( $n ) ) {
|
|
|
|
$ns = (int)$n;
|
|
|
|
} elseif ( $n == '-' || $n == '_' || $n == '*' || $lower == 'main' ) {
|
|
|
|
$ns = NS_MAIN;
|
|
|
|
} else {
|
2019-09-01 13:59:59 +00:00
|
|
|
$ns = $contLang->getNsIndex( $n );
|
2012-02-09 01:23:31 +00:00
|
|
|
}
|
2008-07-01 21:40:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( is_int( $ns ) ) {
|
|
|
|
$namespaces[] = $ns;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-05 14:57:56 +00:00
|
|
|
# get elements into canonical order
|
|
|
|
sort( $namespaces );
|
2008-07-01 21:40:42 +00:00
|
|
|
return $namespaces;
|
|
|
|
}
|
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
/**
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param mixed $mode
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return int|string
|
|
|
|
*/
|
2018-04-11 03:47:26 +00:00
|
|
|
public static function decodeMode( $mode ) {
|
2008-06-28 20:13:20 +00:00
|
|
|
global $wgCategoryTreeDefaultOptions;
|
|
|
|
|
2020-01-14 03:58:41 +00:00
|
|
|
if ( $mode === null ) {
|
2012-02-09 01:23:31 +00:00
|
|
|
return $wgCategoryTreeDefaultOptions['mode'];
|
|
|
|
}
|
|
|
|
if ( is_int( $mode ) ) {
|
|
|
|
return $mode;
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
|
|
|
|
$mode = trim( strtolower( $mode ) );
|
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
if ( is_numeric( $mode ) ) {
|
|
|
|
return (int)$mode;
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
if ( $mode == 'all' ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
$mode = CategoryTreeMode::ALL;
|
2012-02-09 01:23:31 +00:00
|
|
|
} elseif ( $mode == 'pages' ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
$mode = CategoryTreeMode::PAGES;
|
2012-02-09 01:23:31 +00:00
|
|
|
} elseif ( $mode == 'categories' || $mode == 'sub' ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
$mode = CategoryTreeMode::CATEGORIES;
|
2012-02-09 01:23:31 +00:00
|
|
|
} elseif ( $mode == 'parents' || $mode == 'super' || $mode == 'inverse' ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
$mode = CategoryTreeMode::PARENTS;
|
2012-02-09 01:23:31 +00:00
|
|
|
} elseif ( $mode == 'default' ) {
|
|
|
|
$mode = $wgCategoryTreeDefaultOptions['mode'];
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
|
|
|
|
return (int)$mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-02-09 01:23:31 +00:00
|
|
|
* Helper function to convert a string to a boolean value.
|
|
|
|
* Perhaps make this a global function in MediaWiki proper
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param mixed $value
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return bool|null|string
|
|
|
|
*/
|
2018-04-11 03:47:26 +00:00
|
|
|
public static function decodeBoolean( $value ) {
|
2020-01-14 03:58:41 +00:00
|
|
|
if ( $value === null ) {
|
2012-02-09 01:23:31 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if ( is_bool( $value ) ) {
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
if ( is_int( $value ) ) {
|
|
|
|
return ( $value > 0 );
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
|
|
|
|
$value = trim( strtolower( $value ) );
|
2012-02-09 01:23:31 +00:00
|
|
|
if ( is_numeric( $value ) ) {
|
|
|
|
return ( (int)$value > 0 );
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
|
2017-05-30 18:21:54 +00:00
|
|
|
if ( $value == 'yes' || $value == 'y'
|
|
|
|
|| $value == 'true' || $value == 't' || $value == 'on'
|
|
|
|
) {
|
2012-02-09 01:23:31 +00:00
|
|
|
return true;
|
2017-05-30 18:21:54 +00:00
|
|
|
} elseif ( $value == 'no' || $value == 'n'
|
|
|
|
|| $value == 'false' || $value == 'f' || $value == 'off'
|
|
|
|
) {
|
2012-02-09 01:23:31 +00:00
|
|
|
return false;
|
|
|
|
} elseif ( $value == 'null' || $value == 'default' || $value == 'none' || $value == 'x' ) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
}
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
/**
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param mixed $value
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return int|string
|
|
|
|
*/
|
2018-04-11 03:47:26 +00:00
|
|
|
public static function decodeHidePrefix( $value ) {
|
2008-06-30 21:22:03 +00:00
|
|
|
global $wgCategoryTreeDefaultOptions;
|
|
|
|
|
2020-01-14 03:58:41 +00:00
|
|
|
if ( $value === null ) {
|
2012-02-09 01:23:31 +00:00
|
|
|
return $wgCategoryTreeDefaultOptions['hideprefix'];
|
|
|
|
}
|
|
|
|
if ( is_int( $value ) ) {
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
if ( $value === true ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
return CategoryTreeHidePrefix::ALWAYS;
|
2012-02-09 01:23:31 +00:00
|
|
|
}
|
|
|
|
if ( $value === false ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
return CategoryTreeHidePrefix::NEVER;
|
2012-02-09 01:23:31 +00:00
|
|
|
}
|
2008-06-30 21:22:03 +00:00
|
|
|
|
|
|
|
$value = trim( strtolower( $value ) );
|
|
|
|
|
2017-05-30 18:21:54 +00:00
|
|
|
if ( $value == 'yes' || $value == 'y'
|
|
|
|
|| $value == 'true' || $value == 't' || $value == 'on'
|
|
|
|
) {
|
2016-02-05 06:45:46 +00:00
|
|
|
return CategoryTreeHidePrefix::ALWAYS;
|
2017-05-30 18:21:54 +00:00
|
|
|
} elseif ( $value == 'no' || $value == 'n'
|
|
|
|
|| $value == 'false' || $value == 'f' || $value == 'off'
|
|
|
|
) {
|
2016-02-05 06:45:46 +00:00
|
|
|
return CategoryTreeHidePrefix::NEVER;
|
2012-02-09 01:23:31 +00:00
|
|
|
} elseif ( $value == 'always' ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
return CategoryTreeHidePrefix::ALWAYS;
|
2012-02-09 01:23:31 +00:00
|
|
|
} elseif ( $value == 'never' ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
return CategoryTreeHidePrefix::NEVER;
|
2012-02-09 01:23:31 +00:00
|
|
|
} elseif ( $value == 'auto' ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
return CategoryTreeHidePrefix::AUTO;
|
2012-02-09 01:23:31 +00:00
|
|
|
} elseif ( $value == 'categories' || $value == 'category' || $value == 'smart' ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
return CategoryTreeHidePrefix::CATEGORIES;
|
2012-02-09 01:23:31 +00:00
|
|
|
} else {
|
|
|
|
return $wgCategoryTreeDefaultOptions['hideprefix'];
|
|
|
|
}
|
2008-06-30 21:22:03 +00:00
|
|
|
}
|
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
/**
|
2014-10-31 05:40:56 +00:00
|
|
|
* Add ResourceLoader modules to the OutputPage object
|
2008-02-04 09:22:12 +00:00
|
|
|
* @param OutputPage $outputPage
|
2006-08-24 17:12:13 +00:00
|
|
|
*/
|
2018-12-16 09:47:07 +00:00
|
|
|
public static function setHeaders( OutputPage $outputPage ) {
|
2012-02-08 14:07:10 +00:00
|
|
|
# Add the modules
|
2018-12-08 23:12:18 +00:00
|
|
|
$outputPage->addModuleStyles( 'ext.categoryTree.styles' );
|
2011-09-30 02:51:53 +00:00
|
|
|
$outputPage->addModules( 'ext.categoryTree' );
|
2008-02-04 09:22:12 +00:00
|
|
|
}
|
2006-07-29 09:18:34 +00:00
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
/**
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param array $options
|
|
|
|
* @param string $enc
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return mixed
|
2015-01-10 01:47:02 +00:00
|
|
|
* @throws Exception
|
2012-02-09 01:23:31 +00:00
|
|
|
*/
|
2018-12-16 09:47:07 +00:00
|
|
|
protected static function encodeOptions( array $options, $enc ) {
|
2008-06-28 20:13:20 +00:00
|
|
|
if ( $enc == 'mode' || $enc == '' ) {
|
2010-04-20 22:00:34 +00:00
|
|
|
$opt = $options['mode'];
|
2008-06-28 20:13:20 +00:00
|
|
|
} elseif ( $enc == 'json' ) {
|
2012-03-01 01:17:55 +00:00
|
|
|
$opt = FormatJson::encode( $options );
|
2008-06-28 20:13:20 +00:00
|
|
|
} else {
|
2015-01-10 01:47:02 +00:00
|
|
|
throw new Exception( 'Unknown encoding for CategoryTree options: ' . $enc );
|
2008-06-28 20:13:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $opt;
|
|
|
|
}
|
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
/**
|
2019-12-08 21:11:08 +00:00
|
|
|
* @param int|null $depth
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2018-04-11 03:47:26 +00:00
|
|
|
public function getOptionsAsCacheKey( $depth = null ) {
|
2008-06-28 20:13:20 +00:00
|
|
|
$key = "";
|
|
|
|
|
|
|
|
foreach ( $this->mOptions as $k => $v ) {
|
2017-05-30 18:21:54 +00:00
|
|
|
if ( is_array( $v ) ) {
|
|
|
|
$v = implode( '|', $v );
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
$key .= $k . ':' . $v . ';';
|
|
|
|
}
|
|
|
|
|
2020-01-14 03:58:41 +00:00
|
|
|
if ( $depth !== null ) {
|
2012-02-09 01:23:31 +00:00
|
|
|
$key .= ";depth=" . $depth;
|
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
return $key;
|
|
|
|
}
|
|
|
|
|
2012-02-09 01:23:31 +00:00
|
|
|
/**
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param int|null $depth
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return mixed
|
|
|
|
*/
|
2018-02-17 03:29:26 +00:00
|
|
|
public function getOptionsAsJsStructure( $depth = null ) {
|
|
|
|
if ( $depth !== null ) {
|
2008-06-28 20:13:20 +00:00
|
|
|
$opt = $this->mOptions;
|
|
|
|
$opt['depth'] = $depth;
|
|
|
|
$s = self::encodeOptions( $opt, 'json' );
|
|
|
|
} else {
|
|
|
|
$s = self::encodeOptions( $this->mOptions, 'json' );
|
|
|
|
}
|
|
|
|
|
|
|
|
return $s;
|
|
|
|
}
|
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
/**
|
2021-04-07 22:35:52 +00:00
|
|
|
* Custom tag implementation. This is called by Hooks::parserHook, which is used to
|
2012-01-14 15:27:30 +00:00
|
|
|
* load CategoryTreeFunctions.php on demand.
|
2019-10-12 20:14:41 +00:00
|
|
|
* @param ?Parser $parser
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param string $category
|
|
|
|
* @param bool $hideroot
|
2018-04-09 01:50:31 +00:00
|
|
|
* @param array $attr
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param int $depth
|
|
|
|
* @param bool $allowMissing
|
2012-01-14 15:27:30 +00:00
|
|
|
* @return bool|string
|
|
|
|
*/
|
2019-10-12 20:14:41 +00:00
|
|
|
public function getTag( ?Parser $parser, $category, $hideroot = false, array $attr = [],
|
2018-12-16 09:47:07 +00:00
|
|
|
$depth = 1, $allowMissing = false
|
2017-05-30 18:21:54 +00:00
|
|
|
) {
|
2014-02-19 13:10:24 +00:00
|
|
|
global $wgCategoryTreeDisableCache;
|
2006-08-24 17:12:13 +00:00
|
|
|
|
|
|
|
$category = trim( $category );
|
|
|
|
if ( $category === '' ) {
|
|
|
|
return false;
|
|
|
|
}
|
2010-04-20 22:00:34 +00:00
|
|
|
|
2014-02-19 13:10:24 +00:00
|
|
|
if ( $parser ) {
|
2018-04-11 03:48:03 +00:00
|
|
|
if ( $wgCategoryTreeDisableCache === true ) {
|
2019-04-20 15:00:53 +00:00
|
|
|
$parser->getOutput()->updateCacheExpiry( 0 );
|
2014-02-19 13:10:24 +00:00
|
|
|
} elseif ( is_int( $wgCategoryTreeDisableCache ) ) {
|
|
|
|
$parser->getOutput()->updateCacheExpiry( $wgCategoryTreeDisableCache );
|
|
|
|
}
|
2006-08-24 17:12:13 +00:00
|
|
|
}
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
$title = self::makeTitle( $category );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
if ( $title === false || $title === null ) {
|
|
|
|
return false;
|
|
|
|
}
|
2008-06-30 21:35:01 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
if ( isset( $attr['class'] ) ) {
|
|
|
|
$attr['class'] .= ' CategoryTreeTag';
|
|
|
|
} else {
|
|
|
|
$attr['class'] = ' CategoryTreeTag';
|
|
|
|
}
|
2008-07-04 20:05:29 +00:00
|
|
|
|
2011-09-30 21:08:28 +00:00
|
|
|
$attr['data-ct-mode'] = $this->mOptions['mode'];
|
2012-03-02 00:18:09 +00:00
|
|
|
$attr['data-ct-options'] = $this->getOptionsAsJsStructure();
|
2011-09-30 21:08:28 +00:00
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
$html = '';
|
2012-03-02 00:18:09 +00:00
|
|
|
$html .= Html::openElement( 'div', $attr );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2008-07-02 20:19:54 +00:00
|
|
|
if ( !$allowMissing && !$title->getArticleID() ) {
|
2016-10-14 16:11:52 +00:00
|
|
|
$html .= Html::openElement( 'span', [ 'class' => 'CategoryTreeNotice' ] );
|
2010-04-20 22:00:34 +00:00
|
|
|
if ( $parser ) {
|
2017-05-30 18:21:54 +00:00
|
|
|
$html .= $parser->recursiveTagParse(
|
|
|
|
wfMessage( 'categorytree-not-found', $category )->plain() );
|
2009-04-14 17:44:55 +00:00
|
|
|
} else {
|
2012-08-28 22:35:39 +00:00
|
|
|
$html .= wfMessage( 'categorytree-not-found', $category )->parse();
|
2009-04-14 17:44:55 +00:00
|
|
|
}
|
2012-03-02 00:18:09 +00:00
|
|
|
$html .= Html::closeElement( 'span' );
|
2017-05-30 18:21:54 +00:00
|
|
|
} else {
|
2010-04-20 22:00:34 +00:00
|
|
|
if ( !$hideroot ) {
|
2018-04-09 01:50:31 +00:00
|
|
|
$html .= $this->renderNode( $title, $depth );
|
2010-04-20 22:00:34 +00:00
|
|
|
} else {
|
2014-02-19 13:10:24 +00:00
|
|
|
$html .= $this->renderChildren( $title, $depth );
|
2006-08-24 17:12:13 +00:00
|
|
|
}
|
2006-07-29 09:18:34 +00:00
|
|
|
}
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2008-03-18 17:38:32 +00:00
|
|
|
$html .= Xml::closeElement( 'div' );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
return $html;
|
2006-07-26 17:12:30 +00:00
|
|
|
}
|
2006-08-24 17:12:13 +00:00
|
|
|
|
|
|
|
/**
|
2012-02-09 01:23:31 +00:00
|
|
|
* Returns a string with an HTML representation of the children of the given category.
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param Title $title
|
|
|
|
* @param int $depth
|
2019-07-29 19:19:36 +00:00
|
|
|
* @suppress PhanUndeclaredClassMethod,PhanUndeclaredClassInstanceof
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2018-12-16 09:47:07 +00:00
|
|
|
public function renderChildren( Title $title, $depth = 1 ) {
|
2008-07-02 09:49:28 +00:00
|
|
|
global $wgCategoryTreeMaxChildren, $wgCategoryTreeUseCategoryTable;
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
if ( $title->getNamespace() != NS_CATEGORY ) {
|
2007-09-18 15:52:30 +00:00
|
|
|
// Non-categories can't have children. :)
|
|
|
|
return '';
|
|
|
|
}
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2017-09-02 00:03:53 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2006-08-24 17:12:13 +00:00
|
|
|
|
2008-07-02 09:49:28 +00:00
|
|
|
$inverse = $this->isInverse();
|
2010-04-20 22:00:34 +00:00
|
|
|
$mode = $this->getOption( 'mode' );
|
|
|
|
$namespaces = $this->getOption( 'namespaces' );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2016-10-14 16:11:52 +00:00
|
|
|
$tables = [ 'page', 'categorylinks' ];
|
|
|
|
$fields = [ 'page_id', 'page_namespace', 'page_title',
|
2010-08-16 21:37:26 +00:00
|
|
|
'page_is_redirect', 'page_len', 'page_latest', 'cl_to',
|
2016-10-14 16:11:52 +00:00
|
|
|
'cl_from' ];
|
|
|
|
$where = [];
|
|
|
|
$joins = [];
|
|
|
|
$options = [ 'ORDER BY' => 'cl_type, cl_sortkey', 'LIMIT' => $wgCategoryTreeMaxChildren ];
|
2010-08-13 22:38:00 +00:00
|
|
|
|
2008-07-02 09:49:28 +00:00
|
|
|
if ( $inverse ) {
|
2017-05-30 18:21:54 +00:00
|
|
|
$joins['categorylinks'] = [ 'RIGHT JOIN', [
|
|
|
|
'cl_to = page_title', 'page_namespace' => NS_CATEGORY
|
|
|
|
] ];
|
2012-03-11 19:04:37 +00:00
|
|
|
$where['cl_from'] = $title->getArticleID();
|
2010-08-13 22:38:00 +00:00
|
|
|
} else {
|
2016-10-14 16:11:52 +00:00
|
|
|
$joins['categorylinks'] = [ 'JOIN', 'cl_from = page_id' ];
|
2010-08-13 22:38:00 +00:00
|
|
|
$where['cl_to'] = $title->getDBkey();
|
2011-01-20 18:55:06 +00:00
|
|
|
$options['USE INDEX']['categorylinks'] = 'cl_sortkey';
|
2008-07-02 09:49:28 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
# namespace filter.
|
2008-07-02 09:49:28 +00:00
|
|
|
if ( $namespaces ) {
|
2017-05-30 18:21:54 +00:00
|
|
|
// NOTE: we assume that the $namespaces array contains only integers!
|
|
|
|
// decodeNamepsaces makes it so.
|
2010-08-13 22:38:00 +00:00
|
|
|
$where['page_namespace'] = $namespaces;
|
2016-02-05 06:45:46 +00:00
|
|
|
} elseif ( $mode != CategoryTreeMode::ALL ) {
|
|
|
|
if ( $mode == CategoryTreeMode::PAGES ) {
|
2016-10-14 16:11:52 +00:00
|
|
|
$where['cl_type'] = [ 'page', 'subcat' ];
|
2010-04-20 22:00:34 +00:00
|
|
|
} else {
|
Adapt CategoryTree to the new schema
This should obsolete $wgCategoryTreeMaxScanRows, added in r67179, so I
removed it. Note that I only tested with very basic usage, since I
don't quite understand all the complicated things this extension can do,
and some code paths are certainly going to remain inefficient, since
arbitrary namespace filtering seems possible here (at least
renderChildren() has support for it). However, clicking the little plus
sign on category pages should now scan only as many rows as are actually
used, so no limit should be necessary.
Sorting is now by cl_type, cl_sortkey instead of cl_sortkey. This
change has to be made to all users for efficiency, since the old index
was dropped. It means the sort order might be somewhat unexpected in
some cases, but for basic CategoryTree use it makes no difference, since
all the results have cl_type = 'subcat' anyway.
Fixes bug 23682, I think.
2010-08-16 21:57:49 +00:00
|
|
|
$where['cl_type'] = 'subcat';
|
2010-04-20 22:00:34 +00:00
|
|
|
}
|
2008-07-02 09:49:28 +00:00
|
|
|
}
|
2008-07-01 21:40:42 +00:00
|
|
|
}
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2008-06-30 14:09:47 +00:00
|
|
|
# fetch member count if possible
|
2008-07-02 09:49:28 +00:00
|
|
|
$doCount = !$inverse && $wgCategoryTreeUseCategoryTable;
|
2008-06-30 14:09:47 +00:00
|
|
|
|
|
|
|
if ( $doCount ) {
|
2016-10-14 16:11:52 +00:00
|
|
|
$tables = array_merge( $tables, [ 'category' ] );
|
2017-05-30 18:21:54 +00:00
|
|
|
$fields = array_merge( $fields, [
|
|
|
|
'cat_id', 'cat_title', 'cat_subcats', 'cat_pages', 'cat_files'
|
|
|
|
] );
|
|
|
|
$joins['category'] = [ 'LEFT JOIN', [
|
|
|
|
'cat_title = page_title', 'page_namespace' => NS_CATEGORY ]
|
|
|
|
];
|
2008-06-30 14:09:47 +00:00
|
|
|
}
|
|
|
|
|
2011-01-20 18:55:06 +00:00
|
|
|
$res = $dbr->select( $tables, $fields, $where, __METHOD__, $options, $joins );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
# collect categories separately from other pages
|
|
|
|
$categories = '';
|
|
|
|
$other = '';
|
2019-07-29 19:19:36 +00:00
|
|
|
$suppressTranslations = self::decodeBoolean(
|
|
|
|
$this->getOption( 'notranslations' )
|
|
|
|
) && ExtensionRegistry::getInstance()->isLoaded( 'Translate' );
|
|
|
|
|
|
|
|
if ( $suppressTranslations ) {
|
|
|
|
$lb = new LinkBatch();
|
|
|
|
foreach ( $res as $row ) {
|
|
|
|
$title = Title::newFromText( $row->page_title, $row->page_namespace );
|
|
|
|
// Page name could have slashes, check the subpage for valid language built-in codes
|
|
|
|
$isValidLangCode = $title->getSubpageText();
|
|
|
|
|
|
|
|
if ( $title !== null && $isValidLangCode ) {
|
|
|
|
$lb->addObj( $title->getBaseTitle() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$lb->execute();
|
|
|
|
}
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-08-13 22:38:00 +00:00
|
|
|
foreach ( $res as $row ) {
|
2019-07-29 19:19:36 +00:00
|
|
|
if ( $suppressTranslations ) {
|
|
|
|
$title = Title::newFromRow( $row );
|
|
|
|
$baseTitle = $title->getBaseTitle();
|
|
|
|
$page = \TranslatablePage::isTranslationPage( $title );
|
|
|
|
|
|
|
|
if ( ( $page instanceof \TranslatablePage ) && $baseTitle->exists() ) {
|
|
|
|
// T229265: Render only the default pages created and ignore their
|
|
|
|
// translations.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
# NOTE: in inverse mode, the page record may be null, because we use a right join.
|
2008-07-02 20:19:54 +00:00
|
|
|
# happens for categories with no category page (red cat links)
|
2010-01-06 21:24:10 +00:00
|
|
|
if ( $inverse && $row->page_title === null ) {
|
2008-07-02 20:19:54 +00:00
|
|
|
$t = Title::makeTitle( NS_CATEGORY, $row->cl_to );
|
2010-04-20 22:00:34 +00:00
|
|
|
} else {
|
|
|
|
# TODO: translation support; ideally added to Title object
|
2008-07-02 20:19:54 +00:00
|
|
|
$t = Title::newFromRow( $row );
|
|
|
|
}
|
2007-09-01 11:26:00 +00:00
|
|
|
|
2010-01-06 21:24:10 +00:00
|
|
|
$cat = null;
|
2008-06-30 14:09:47 +00:00
|
|
|
|
|
|
|
if ( $doCount && $row->page_namespace == NS_CATEGORY ) {
|
|
|
|
$cat = Category::newFromRow( $row, $t );
|
|
|
|
}
|
|
|
|
|
2014-02-19 13:10:24 +00:00
|
|
|
$s = $this->renderNodeInfo( $t, $cat, $depth - 1 );
|
2007-09-01 11:26:00 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
if ( $row->page_namespace == NS_CATEGORY ) {
|
|
|
|
$categories .= $s;
|
|
|
|
} else {
|
|
|
|
$other .= $s;
|
|
|
|
}
|
2006-08-24 17:12:13 +00:00
|
|
|
}
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2007-09-01 11:26:00 +00:00
|
|
|
return $categories . $other;
|
2006-08-24 17:12:13 +00:00
|
|
|
}
|
2006-07-26 17:12:30 +00:00
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
/**
|
2012-02-09 01:23:31 +00:00
|
|
|
* Returns a string with an HTML representation of the parents of the given category.
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param Title $title
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2018-12-16 09:47:07 +00:00
|
|
|
public function renderParents( Title $title ) {
|
2006-08-24 17:12:13 +00:00
|
|
|
global $wgCategoryTreeMaxChildren;
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2017-09-02 00:03:53 +00:00
|
|
|
$dbr = wfGetDB( DB_REPLICA );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2012-11-21 08:31:37 +00:00
|
|
|
$res = $dbr->select(
|
|
|
|
'categorylinks',
|
2016-05-30 10:01:38 +00:00
|
|
|
[ 'cl_to' ],
|
2016-10-14 16:11:52 +00:00
|
|
|
[ 'cl_from' => $title->getArticleID() ],
|
2012-11-21 08:31:37 +00:00
|
|
|
__METHOD__,
|
2016-10-14 16:11:52 +00:00
|
|
|
[
|
2012-11-21 08:31:37 +00:00
|
|
|
'LIMIT' => $wgCategoryTreeMaxChildren,
|
|
|
|
'ORDER BY' => 'cl_to'
|
2016-10-14 16:11:52 +00:00
|
|
|
]
|
2012-11-21 08:31:37 +00:00
|
|
|
);
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-06-08 19:30:48 +00:00
|
|
|
$special = SpecialPage::getTitleFor( 'CategoryTree' );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
$s = '';
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-10-29 21:30:20 +00:00
|
|
|
foreach ( $res as $row ) {
|
2016-05-30 10:01:38 +00:00
|
|
|
$t = Title::makeTitle( NS_CATEGORY, $row->cl_to );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2012-08-28 22:35:39 +00:00
|
|
|
if ( $s !== '' ) {
|
|
|
|
$s .= wfMessage( 'pipe-separator' )->escaped();
|
|
|
|
}
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2016-10-14 16:11:52 +00:00
|
|
|
$s .= Xml::openElement( 'span', [ 'class' => 'CategoryTreeItem' ] );
|
2016-05-30 10:01:38 +00:00
|
|
|
$s .= $this->linkRenderer->makeLink(
|
|
|
|
$special,
|
|
|
|
$t->getText(),
|
|
|
|
[ 'class' => 'CategoryTreeLabel' ],
|
2020-05-27 07:50:33 +00:00
|
|
|
[ 'target' => $t->getDBkey() ] + $this->mOptions
|
2016-05-30 10:01:38 +00:00
|
|
|
);
|
2008-03-18 17:38:32 +00:00
|
|
|
$s .= Xml::closeElement( 'span' );
|
2006-08-24 17:12:13 +00:00
|
|
|
}
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
return $s;
|
|
|
|
}
|
2006-07-26 17:12:30 +00:00
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
/**
|
2012-02-09 01:23:31 +00:00
|
|
|
* Returns a string with a HTML represenation of the given page.
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param Title $title
|
2012-02-09 01:23:31 +00:00
|
|
|
* @param int $children
|
|
|
|
* @return string
|
|
|
|
*/
|
2018-12-16 09:47:07 +00:00
|
|
|
public function renderNode( Title $title, $children = 0 ) {
|
2008-07-02 09:49:28 +00:00
|
|
|
global $wgCategoryTreeUseCategoryTable;
|
|
|
|
|
2017-05-30 18:21:54 +00:00
|
|
|
if ( $wgCategoryTreeUseCategoryTable && $title->getNamespace() == NS_CATEGORY
|
|
|
|
&& !$this->isInverse()
|
|
|
|
) {
|
2008-07-02 09:49:28 +00:00
|
|
|
$cat = Category::newFromTitle( $title );
|
2010-04-20 22:00:34 +00:00
|
|
|
} else {
|
|
|
|
$cat = null;
|
2008-07-02 09:49:28 +00:00
|
|
|
}
|
2008-06-30 14:09:47 +00:00
|
|
|
|
2014-02-19 13:10:24 +00:00
|
|
|
return $this->renderNodeInfo( $title, $cat, $children );
|
2008-06-30 14:09:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-02-09 01:23:31 +00:00
|
|
|
* Returns a string with a HTML represenation of the given page.
|
|
|
|
* $info must be an associative array, containing at least a Title object under the 'title' key.
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param Title $title
|
2018-12-16 09:47:07 +00:00
|
|
|
* @param Category|null $cat
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param int $children
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return string
|
|
|
|
*/
|
2018-12-16 09:47:07 +00:00
|
|
|
public function renderNodeInfo( Title $title, Category $cat = null, $children = 0 ) {
|
2010-04-20 22:00:34 +00:00
|
|
|
$mode = $this->getOption( 'mode' );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
$ns = $title->getNamespace();
|
2008-01-14 10:09:08 +00:00
|
|
|
$key = $title->getDBkey();
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
$hideprefix = $this->getOption( 'hideprefix' );
|
2008-06-30 21:22:03 +00:00
|
|
|
|
2016-02-05 06:45:46 +00:00
|
|
|
if ( $hideprefix == CategoryTreeHidePrefix::ALWAYS ) {
|
2010-04-20 22:00:34 +00:00
|
|
|
$hideprefix = true;
|
2016-02-05 06:45:46 +00:00
|
|
|
} elseif ( $hideprefix == CategoryTreeHidePrefix::AUTO ) {
|
|
|
|
$hideprefix = ( $mode == CategoryTreeMode::CATEGORIES );
|
|
|
|
} elseif ( $hideprefix == CategoryTreeHidePrefix::CATEGORIES ) {
|
2010-04-20 22:00:34 +00:00
|
|
|
$hideprefix = ( $ns == NS_CATEGORY );
|
|
|
|
} else {
|
|
|
|
$hideprefix = true;
|
|
|
|
}
|
2008-06-30 21:22:03 +00:00
|
|
|
|
2017-05-30 18:21:54 +00:00
|
|
|
// when showing only categories, omit namespace in label unless we explicitely defined the
|
|
|
|
// configuration setting
|
|
|
|
// patch contributed by Manuel Schneider <manuel.schneider@wikimedia.ch>, Bug 8011
|
2010-04-20 22:00:34 +00:00
|
|
|
if ( $hideprefix ) {
|
2018-04-18 06:23:38 +00:00
|
|
|
$label = $title->getText();
|
2010-04-20 22:00:34 +00:00
|
|
|
} else {
|
2018-04-18 06:23:38 +00:00
|
|
|
$label = $title->getPrefixedText();
|
2010-04-20 22:00:34 +00:00
|
|
|
}
|
2009-05-27 06:17:56 +00:00
|
|
|
|
2019-07-31 05:46:31 +00:00
|
|
|
$link = $this->linkRenderer->makeLink( $title, $label );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2008-06-30 14:09:47 +00:00
|
|
|
$count = false;
|
2006-08-24 17:12:13 +00:00
|
|
|
$s = '';
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2010-04-20 22:00:34 +00:00
|
|
|
# NOTE: things in CategoryTree.js rely on the exact order of tags!
|
2006-08-24 17:12:13 +00:00
|
|
|
# Specifically, the CategoryTreeChildren div must be the first
|
|
|
|
# sibling with nodeName = DIV of the grandparent of the expland link.
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2016-10-14 16:11:52 +00:00
|
|
|
$s .= Xml::openElement( 'div', [ 'class' => 'CategoryTreeSection' ] );
|
|
|
|
$s .= Xml::openElement( 'div', [ 'class' => 'CategoryTreeItem' ] );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2016-10-14 16:11:52 +00:00
|
|
|
$attr = [ 'class' => 'CategoryTreeBullet' ];
|
2008-06-28 20:13:20 +00:00
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
if ( $ns == NS_CATEGORY ) {
|
2008-06-30 14:09:47 +00:00
|
|
|
if ( $cat ) {
|
2016-02-05 06:45:46 +00:00
|
|
|
if ( $mode == CategoryTreeMode::CATEGORIES ) {
|
2016-11-13 14:57:16 +00:00
|
|
|
$count = intval( $cat->getSubcatCount() );
|
2016-02-05 06:45:46 +00:00
|
|
|
} elseif ( $mode == CategoryTreeMode::PAGES ) {
|
2016-11-13 14:57:16 +00:00
|
|
|
$count = intval( $cat->getPageCount() ) - intval( $cat->getFileCount() );
|
2010-04-20 22:00:34 +00:00
|
|
|
} else {
|
2016-11-13 14:57:16 +00:00
|
|
|
$count = intval( $cat->getPageCount() );
|
2010-04-20 22:00:34 +00:00
|
|
|
}
|
|
|
|
}
|
Adapt CategoryTree to the new schema
This should obsolete $wgCategoryTreeMaxScanRows, added in r67179, so I
removed it. Note that I only tested with very basic usage, since I
don't quite understand all the complicated things this extension can do,
and some code paths are certainly going to remain inefficient, since
arbitrary namespace filtering seems possible here (at least
renderChildren() has support for it). However, clicking the little plus
sign on category pages should now scan only as many rows as are actually
used, so no limit should be necessary.
Sorting is now by cl_type, cl_sortkey instead of cl_sortkey. This
change has to be made to all users for efficiency, since the old index
was dropped. It means the sort order might be somewhat unexpected in
some cases, but for basic CategoryTree use it makes no difference, since
all the results have cl_type = 'subcat' anyway.
Fixes bug 23682, I think.
2010-08-16 21:57:49 +00:00
|
|
|
if ( $count === 0 ) {
|
2018-07-05 00:34:08 +00:00
|
|
|
$bullet = wfMessage( 'categorytree-empty-bullet' )->escaped() . ' ';
|
2010-05-28 02:15:44 +00:00
|
|
|
$attr['class'] = 'CategoryTreeEmptyBullet';
|
|
|
|
} else {
|
2016-10-14 16:11:52 +00:00
|
|
|
$linkattr = [];
|
2010-04-20 22:00:34 +00:00
|
|
|
|
2010-05-28 02:15:44 +00:00
|
|
|
$linkattr[ 'class' ] = "CategoryTreeToggle";
|
2011-09-30 21:08:28 +00:00
|
|
|
$linkattr['data-ct-title'] = $key;
|
2008-06-28 20:13:20 +00:00
|
|
|
|
2014-02-19 13:10:24 +00:00
|
|
|
if ( $children == 0 ) {
|
2019-02-08 14:34:27 +00:00
|
|
|
// Use ->plain() to ensure identical result as JS,
|
|
|
|
// which does:
|
|
|
|
// $link.text( mw.msg( 'categorytree-expand-bullet' ) );
|
|
|
|
$txt = wfMessage( 'categorytree-expand-bullet' )->plain();
|
2011-09-30 21:08:28 +00:00
|
|
|
$linkattr[ 'data-ct-state' ] = 'collapsed';
|
2010-05-28 02:15:44 +00:00
|
|
|
} else {
|
2019-02-08 14:34:27 +00:00
|
|
|
$txt = wfMessage( 'categorytree-collapse-bullet' )->plain();
|
2011-09-30 21:08:28 +00:00
|
|
|
$linkattr[ 'data-ct-loaded' ] = true;
|
|
|
|
$linkattr[ 'data-ct-state' ] = 'expanded';
|
2010-04-20 22:00:34 +00:00
|
|
|
}
|
2008-06-28 20:13:20 +00:00
|
|
|
|
2019-02-08 14:34:27 +00:00
|
|
|
$bullet = Html::element( 'span', $linkattr, $txt ) . ' ';
|
2010-04-20 22:00:34 +00:00
|
|
|
}
|
2006-08-24 17:12:13 +00:00
|
|
|
} else {
|
2018-07-05 00:34:08 +00:00
|
|
|
$bullet = wfMessage( 'categorytree-page-bullet' )->escaped();
|
2006-08-24 17:12:13 +00:00
|
|
|
}
|
2010-06-01 08:58:04 +00:00
|
|
|
$s .= Xml::tags( 'span', $attr, $bullet ) . ' ';
|
2008-06-28 20:13:20 +00:00
|
|
|
|
2019-07-31 05:46:31 +00:00
|
|
|
$s .= $link;
|
2008-06-30 14:09:47 +00:00
|
|
|
|
|
|
|
if ( $count !== false && $this->getOption( 'showcount' ) ) {
|
2017-07-23 07:08:05 +00:00
|
|
|
$s .= self::createCountString( RequestContext::getMain(), $cat, $count );
|
2008-06-30 14:09:47 +00:00
|
|
|
}
|
|
|
|
|
2008-03-18 17:38:32 +00:00
|
|
|
$s .= Xml::closeElement( 'div' );
|
2012-08-28 22:35:39 +00:00
|
|
|
$s .= Xml::openElement(
|
|
|
|
'div',
|
2016-10-14 16:11:52 +00:00
|
|
|
[
|
2012-08-28 22:35:39 +00:00
|
|
|
'class' => 'CategoryTreeChildren',
|
|
|
|
'style' => $children > 0 ? "display:block" : "display:none"
|
2016-10-14 16:11:52 +00:00
|
|
|
]
|
2012-08-28 22:35:39 +00:00
|
|
|
);
|
2010-04-20 22:00:34 +00:00
|
|
|
|
2014-02-19 13:10:24 +00:00
|
|
|
if ( $ns == NS_CATEGORY && $children > 0 ) {
|
2008-07-01 21:40:42 +00:00
|
|
|
$children = $this->renderChildren( $title, $children );
|
|
|
|
if ( $children == '' ) {
|
2016-10-14 16:11:52 +00:00
|
|
|
$s .= Xml::openElement( 'i', [ 'class' => 'CategoryTreeNotice' ] );
|
2016-02-05 06:45:46 +00:00
|
|
|
if ( $mode == CategoryTreeMode::CATEGORIES ) {
|
2018-07-05 00:34:08 +00:00
|
|
|
$s .= wfMessage( 'categorytree-no-subcategories' )->escaped();
|
2016-02-05 06:45:46 +00:00
|
|
|
} elseif ( $mode == CategoryTreeMode::PAGES ) {
|
2018-07-05 00:34:08 +00:00
|
|
|
$s .= wfMessage( 'categorytree-no-pages' )->escaped();
|
2016-02-05 06:45:46 +00:00
|
|
|
} elseif ( $mode == CategoryTreeMode::PARENTS ) {
|
2018-07-05 00:34:08 +00:00
|
|
|
$s .= wfMessage( 'categorytree-no-parent-categories' )->escaped();
|
2010-04-20 22:00:34 +00:00
|
|
|
} else {
|
2018-07-05 00:34:08 +00:00
|
|
|
$s .= wfMessage( 'categorytree-nothing-found' )->escaped();
|
2010-04-20 22:00:34 +00:00
|
|
|
}
|
2008-07-01 21:40:42 +00:00
|
|
|
$s .= Xml::closeElement( 'i' );
|
|
|
|
} else {
|
|
|
|
$s .= $children;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-10 02:38:34 +00:00
|
|
|
$s .= Xml::closeElement( 'div' ) . Xml::closeElement( 'div' );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
return $s;
|
|
|
|
}
|
|
|
|
|
2016-11-13 14:57:16 +00:00
|
|
|
/**
|
|
|
|
* Create a string which format the page, subcat and file counts of a category
|
|
|
|
* @param IContextSource $context
|
2019-10-12 20:14:41 +00:00
|
|
|
* @param ?Category $cat
|
2016-11-13 14:57:16 +00:00
|
|
|
* @param int $countMode
|
|
|
|
* @return string
|
|
|
|
*/
|
2019-10-12 20:14:41 +00:00
|
|
|
public static function createCountString( IContextSource $context, ?Category $cat,
|
2018-12-16 09:47:07 +00:00
|
|
|
$countMode
|
|
|
|
) {
|
2016-11-13 14:57:16 +00:00
|
|
|
# Get counts, with conversion to integer so === works
|
|
|
|
# Note: $allCount is the total number of cat members,
|
|
|
|
# not the count of how many members are normal pages.
|
|
|
|
$allCount = $cat ? intval( $cat->getPageCount() ) : 0;
|
|
|
|
$subcatCount = $cat ? intval( $cat->getSubcatCount() ) : 0;
|
|
|
|
$fileCount = $cat ? intval( $cat->getFileCount() ) : 0;
|
|
|
|
$pages = $allCount - $subcatCount - $fileCount;
|
|
|
|
|
|
|
|
$attr = [
|
|
|
|
'title' => $context->msg( 'categorytree-member-counts' )
|
2017-05-30 18:21:54 +00:00
|
|
|
->numParams( $subcatCount, $pages, $fileCount, $allCount, $countMode )->text(),
|
2019-02-05 14:57:56 +00:00
|
|
|
# numbers and commas get messed up in a mixed dir env
|
|
|
|
'dir' => $context->getLanguage()->getDir()
|
2016-11-13 14:57:16 +00:00
|
|
|
];
|
2019-09-01 13:59:59 +00:00
|
|
|
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
|
|
|
|
$s = $contLang->getDirMark() . ' ';
|
2016-11-13 14:57:16 +00:00
|
|
|
|
|
|
|
# Create a list of category members with only non-zero member counts
|
|
|
|
$memberNums = [];
|
|
|
|
if ( $subcatCount ) {
|
|
|
|
$memberNums[] = $context->msg( 'categorytree-num-categories' )
|
|
|
|
->numParams( $subcatCount )->text();
|
|
|
|
}
|
|
|
|
if ( $pages ) {
|
|
|
|
$memberNums[] = $context->msg( 'categorytree-num-pages' )->numParams( $pages )->text();
|
|
|
|
}
|
|
|
|
if ( $fileCount ) {
|
|
|
|
$memberNums[] = $context->msg( 'categorytree-num-files' )
|
|
|
|
->numParams( $fileCount )->text();
|
|
|
|
}
|
|
|
|
$memberNumsShort = $memberNums
|
|
|
|
? $context->getLanguage()->commaList( $memberNums )
|
|
|
|
: $context->msg( 'categorytree-num-empty' )->text();
|
|
|
|
|
|
|
|
# Only $5 is actually used in the default message.
|
|
|
|
# Other arguments can be used in a customized message.
|
|
|
|
$s .= Xml::tags(
|
|
|
|
'span',
|
|
|
|
$attr,
|
|
|
|
$context->msg( 'categorytree-member-num' )
|
|
|
|
// Do not use numParams on params 1-4, as they are only used for customisation.
|
|
|
|
->params( $subcatCount, $pages, $fileCount, $allCount, $memberNumsShort )
|
|
|
|
->escaped()
|
|
|
|
);
|
|
|
|
|
|
|
|
return $s;
|
|
|
|
}
|
|
|
|
|
2006-08-24 17:12:13 +00:00
|
|
|
/**
|
2012-02-09 01:23:31 +00:00
|
|
|
* Creates a Title object from a user provided (and thus unsafe) string
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param string $title
|
2012-02-09 01:23:31 +00:00
|
|
|
* @return null|Title
|
|
|
|
*/
|
2018-04-11 03:47:26 +00:00
|
|
|
public static function makeTitle( $title ) {
|
2010-04-20 22:00:34 +00:00
|
|
|
$title = trim( $title );
|
2008-02-04 09:22:12 +00:00
|
|
|
|
2012-12-13 20:36:50 +00:00
|
|
|
if ( strval( $title ) === '' ) {
|
2010-01-06 21:24:10 +00:00
|
|
|
return null;
|
2006-08-24 17:12:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# The title must be in the category namespace
|
|
|
|
# Ignore a leading Category: if there is one
|
|
|
|
$t = Title::newFromText( $title, NS_CATEGORY );
|
2014-01-23 16:20:15 +00:00
|
|
|
if ( !$t || $t->getNamespace() != NS_CATEGORY || $t->getInterwiki() != '' ) {
|
2011-02-13 22:19:18 +00:00
|
|
|
// If we were given something like "Wikipedia:Foo" or "Template:",
|
|
|
|
// try it again but forced.
|
2006-08-24 17:12:13 +00:00
|
|
|
$title = "Category:$title";
|
|
|
|
$t = Title::newFromText( $title );
|
|
|
|
}
|
|
|
|
return $t;
|
|
|
|
}
|
2016-02-05 05:31:34 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal function to cap depth
|
2019-03-17 15:42:15 +00:00
|
|
|
* @suppress PhanPluginDuplicateConditionalNullCoalescing until PHP7 is required
|
2017-10-11 18:25:13 +00:00
|
|
|
* @param string $mode
|
|
|
|
* @param int $depth
|
2016-02-05 05:31:34 +00:00
|
|
|
* @return int|mixed
|
|
|
|
*/
|
2018-04-11 03:47:26 +00:00
|
|
|
public static function capDepth( $mode, $depth ) {
|
2016-02-05 05:31:34 +00:00
|
|
|
global $wgCategoryTreeMaxDepth;
|
|
|
|
|
|
|
|
if ( is_numeric( $depth ) ) {
|
|
|
|
$depth = intval( $depth );
|
|
|
|
} else {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( is_array( $wgCategoryTreeMaxDepth ) ) {
|
|
|
|
$max = isset( $wgCategoryTreeMaxDepth[$mode] ) ? $wgCategoryTreeMaxDepth[$mode] : 1;
|
|
|
|
} elseif ( is_numeric( $wgCategoryTreeMaxDepth ) ) {
|
|
|
|
$max = $wgCategoryTreeMaxDepth;
|
|
|
|
} else {
|
|
|
|
wfDebug( 'CategoryTree::capDepth: $wgCategoryTreeMaxDepth is invalid.' );
|
|
|
|
$max = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return min( $depth, $max );
|
|
|
|
}
|
2006-08-24 17:12:13 +00:00
|
|
|
}
|