tosfos has submitted this change and it was merged. Change subject: initial commit ......................................................................
initial commit Change-Id: Id26042493c12f9f31495bb8be86ced53625afbee --- A .gitmodules A .jshintrc A PagesList.class.php A PagesList.hooks.php A PagesList.i18n.alias.php A PagesList.magic.php A PagesList.php A i18n/en.json A i18n/qqq.json A modules/DataTables A modules/ext.PagesList.datatables.js A specials/PagesListOptions.php A specials/SpecialPagesList.php A specials/SpecialPagesListQueryPage.php 14 files changed, 1,038 insertions(+), 0 deletions(-) Approvals: tosfos: Verified; Looks good to me, approved diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2087e08 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "modules/DataTables"] + path = modules/DataTables + url = https://github.com/DataTables/DataTables.git diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..4cd914b --- /dev/null +++ b/.jshintrc @@ -0,0 +1,8 @@ +{ + "predef": [ + "mediaWiki", + "jQuery" + ], + "browser": true, + "smarttabs": true +} diff --git a/PagesList.class.php b/PagesList.class.php new file mode 100644 index 0000000..4256ed3 --- /dev/null +++ b/PagesList.class.php @@ -0,0 +1,409 @@ +<?php + +/** + * + * + * @author Ike Hecht + */ +class PagesList extends ContextSource { + /** + * Namespace to target + * + * @var int Namespace constant + */ + private $namespace; + + /** + * Invert the namesace selection - select every namespace other than the one stored in + * $namespace + * + * @var boolean + */ + private $nsInvert; + + /** + * Include the associated namespace of $namespace - its Talk page + * + * @var boolean + */ + private $associated; + + /** + * Category name to target + * + * @var Title + */ + private $category; + + /** + * Base Page to target + * + * @var Title + */ + private $basePage; + + /** + * A read-only database object + * + * @var DatabaseBase + */ + private $db; + + /** + * Flag to indicated that the DataTables JS & CSS should later be loaded because there is a + * DataTables item on this page + * + * @var Boolean + */ + public static $loadDataTables = false; + + /** + * The index to actually be used for ordering. This is a single column, + * for one ordering, even if multiple orderings are supported. + * @todo fixme + * @var string + */ + protected $indexField = 'rev_timestamp'; + + /** + * Result object for the query. Warning: seek before use. + * + * @var ResultWrapper + */ + public $result; + + /** + * + * @param DatabaseBase $db + * @param string $namespace + * @param boolean $nsInvert + * @param boolean $associated + * @param Title $category + * @param Title $basePage + * @param IContextSource $context + */ + function __construct( DatabaseBase $db, $namespace = null, $nsInvert = false, $associated = false, + Title $category = null, Title $basePage = null, IContextSource $context = null ) { + + if ( $context ) { + $this->setContext( $context ); + } + + $this->db = $db; + $this->namespace = $namespace; + $this->nsInvert = $nsInvert; + $this->associated = $associated; + $this->category = $category; + $this->basePage = $basePage; + + $this->doQuery(); + } + + /** + * Perform the db query + * The query code is based on ContribsPager + */ + protected function doQuery() { + # Use the child class name for profiling + $fname = __METHOD__ . ' (' . get_class( $this ) . ')'; + wfProfileIn( $fname ); + + list( $tables, $fields, $conds, $fname, $options, $join_conds ) = $this->buildQueryInfo(); + $this->result = $this->db->select( + $tables, $fields, $conds, $fname, $options, $join_conds + ); + $this->result->rewind(); // Paranoia + + wfProfileOut( $fname ); + } + + /** + * Generate an array to be turned into the full and final query. + * + * @return array + */ + protected function buildQueryInfo() { + $fname = __METHOD__ . ' (' . $this->getSqlComment() . ')'; + $info = $this->getQueryInfo(); + $tables = $info['tables']; + $fields = $info['fields']; + $conds = isset( $info['conds'] ) ? $info['conds'] : array(); + $options = isset( $info['options'] ) ? $info['options'] : array(); + $join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array(); + + #$options['ORDER BY'] = $this->indexField . ' DESC'; + + return array( $tables, $fields, $conds, $fname, $options, $join_conds ); + } + + /** + * Generate an array of basic query info. + * + * @return array + */ + function getQueryInfo() { + $tables = array( 'revision', 'page' ); + $fields = array( + 'namespace' => 'page_namespace', + 'title' => 'page_title', + 'value' => 'rev_timestamp', + 'userId' => 'rev_user', + 'userName' => 'rev_user_text' + ); + $join_cond = array(); + + $conds = array( + 'page_is_redirect' => 0, + 'page_latest=rev_id' + ); + + if ( is_int( $this->namespace ) ) { + $conds = array_merge( $conds, $this->getNamespaceCond() ); + } + + if ( $this->category instanceof Title ) { + $tables[] = 'categorylinks'; + $conds = array_merge( $conds, array( 'cl_to' => $this->category->getDBkey() ) ); + $join_cond['categorylinks'] = array( 'INNER JOIN', 'cl_from = page_id' ); + } + + if ( $this->basePage instanceof Title ) { + $conds = array_merge( $conds, + array( 'page_title' . $this->db->buildLike( $this->basePage->getDBkey() . + '/', $this->db->anyString() ), + 'page_namespace' => $this->basePage->getNamespace() ) + ); + } + + $options = array(); + $index = false; #todo + if ( $index ) { + $options['USE INDEX'] = array( 'revision' => $index ); + } + $queryInfo = array( + 'tables' => $tables, + 'fields' => $fields, + 'conds' => $conds, + 'options' => $options, + 'join_conds' => $join_cond + ); + + return $queryInfo; + } + + /** + * Limit the results to specific namespaces + * + * @return array + */ + function getNamespaceCond() { + $selectedNS = $this->db->addQuotes( $this->namespace ); + $eq_op = $this->nsInvert ? '!=' : '='; + $bool_op = $this->nsInvert ? 'AND' : 'OR'; + + if ( !$this->associated ) { + return array( "page_namespace $eq_op $selectedNS" ); + } + + $associatedNS = $this->db->addQuotes( + MWNamespace::getAssociated( $this->namespace ) + ); + + return array( + "page_namespace $eq_op $selectedNS " . + $bool_op . + " page_namespace $eq_op $associatedNS" + ); + } + + /** + * @return string + */ + function getSqlComment() { + return get_class( $this ); + } + + function getList( $format, $showLastUser, $showLastModification, OutputPage $out = null ) { + if ( !isset( $out ) ) { + $out = $this->getOutput(); + } + switch ( $format ) { + case 'datatable': + /** @todo modify to check if DataTables submodule exists */ + self::$loadDataTables = true; + case 'table': + return $this->getResultTable( $showLastUser, $showLastModification ); + case 'ol': + case 'ul': + return $this->getResultList( $format, $showLastUser, $showLastModification ); + case 'plain': + return $this->getResultPlain( $showLastUser, $showLastModification ); + default: + /** @todo maybe return an error or something */ + return 'invalid format'; + } + } + + /** + * Get the fields to sort by title + * + * @return array + */ + public function getOrderFields() { + return array( 'page_namespace', 'page_title' ); + } + + /** + * Borrowed from AncientPagesPage + * + * @param Skin $skin Unused + * @param object $result Result row + * @param boolean $showLastUser + * @param boolean $showLastModification + * @return string + */ + protected function getResultTableRow( $skin, $result, $showLastUser = false, + $showLastModification = false ) { + $output = Html::openElement( 'tr' ); + $output .= Html::rawElement( 'td', array(), $this->getLinkedTitle( $result ) ); + if ( $showLastUser ) { + $output .= Html::rawElement( 'td', array(), $this->getLastUser( $result ) ); + } + if ( $showLastModification ) { + $output .= Html::element( 'td', array(), $this->getLastModification( $result ) ); + } + $output .= Html::closeElement( 'tr' ); + return $output; + } + + /** + * + * @param boolean $showLastUser + * @param boolean $showLastModification + * @return string HTML table + */ + protected function getResultTable( $showLastUser = false, $showLastModification = false ) { + $output = Html::openElement( 'table', + array( 'class' => 'pages-list stripe row-border hover' ) ); + $output .= Html::openElement( 'thead' ); + $output .= Html::openElement( 'tr' ); + $output .= Html::rawElement( 'th', array(), $this->msg( 'pageslist-title' ) ); + if ( $showLastUser ) { + $output .= Html::rawElement( 'th', array(), $this->msg( 'pageslist-last-user' ) ); + } + if ( $showLastModification ) { + $output .= Html::rawElement( 'th', array(), $this->msg( 'pageslist-last-modified' ) ); + } + $output .= Html::closeElement( 'tr' ); + $output .= Html::closeElement( 'thead' ); + $output .= Html::openElement( 'tbody' ); + while ( $resultRow = $this->result->fetchObject() ) { + $output .= $this->getResultTableRow( $this->getSkin(), $resultRow, $showLastUser, + $showLastModification ); + } + $output .= Html::closeElement( 'tbody' ); + $output .= Html::closeElement( 'table' ); + return $output; + } + + + /** + * Add all necessary DataTables scripts and styles to output + * + * @param OutputPage $out + */ + public static function addDataTablesToOutput( OutputPage &$out ) { + $out->addModules( 'ext.PagesList' ); + } + + /** + * Get an HTML link to the user page + * + * @param object $result + * @return string HTML + */ + protected function getLastUser( $result ) { + return Linker::userLink( $result->userId, $result->userName ); + } + + /** + * Get a linked page title + * + * @global Language $wgContLang + * @param object $result + * @return string HTML + */ + protected function getLinkedTitle( $result ) { + global $wgContLang; + $title = Title::makeTitle( $result->namespace, $result->title ); + return Linker::linkKnown( + $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) + ); + } + + /** + * Get the date of the last modification + * + * @param object $result + * @return string HTML + */ + protected function getLastModification( $result ) { + return $this->getLanguage()->userDate( $result->value, $this->getUser() ); + } + + /** + * + * @param string $format + * @param boolean $showLastUser + * @param boolean $showLastModification + * @return string HTML list + */ + protected function getResultList( $format, $showLastUser = false, $showLastModification = false ) { + $html = Html::openElement( $format, array( 'class' => 'pageslist' ) ); + while ( $resultRow = $this->result->fetchObject() ) { + $html .= Html::rawElement( 'li', array(), + $this->getListItem( $resultRow, $showLastUser, $showLastModification ) ); + } + $html .= Html::closeElement( $format ); + + return $html; + } + + /** + * + * @param boolean $showLastUser + * @param boolean $showLastModification + * @return string HTML comma separated list + */ + protected function getResultPlain( $showLastUser = false, $showLastModification = false ) { + $html = Html::openElement( 'div', array( 'class' => 'pageslist' ) ); + $list = array(); + while ( $resultRow = $this->result->fetchObject() ) { + $list[] = $this->getListItem( $resultRow, $showLastUser, $showLastModification ); + } + $html .= $this->getLanguage()->commaList( $list ); + $html .= Html::closeElement( 'div' ); + + return $html; + } + + /** + * Get a single list item + * + * @param object $resultRow + * @param boolean $showLastUser + * @param boolean $showLastModification + * @return string HTML list item + */ + protected function getListItem( $resultRow, $showLastUser, $showLastModification ) { + $extras = array(); + if ( $showLastUser ) { + $extras[] = $this->getLastUser( $resultRow ); + } + if ( $showLastModification ) { + $extras[] = $this->getLastModification( $resultRow ); + } + $commaList = $this->getLanguage()->commaList( $extras ); + return $this->getLanguage()->specialList( $this->getLinkedTitle( $resultRow ), $commaList ); + } +} diff --git a/PagesList.hooks.php b/PagesList.hooks.php new file mode 100644 index 0000000..5723a48 --- /dev/null +++ b/PagesList.hooks.php @@ -0,0 +1,135 @@ +<?php + +/** + * Hooks for PagesList extension + * + * @file + * @ingroup Extensions + */ +class PagesListHooks { + + /** + * Make PagesList vars available to JS + * + * @global array $wgPagesListDataTablesOptions + * @param array $vars + * @return boolean + */ + public static function onResourceLoaderGetConfigVars( Array &$vars ) { + global $wgPagesListDataTablesOptions; + + $vars['wgPagesList'] = array( + 'dataTablesOptions' => FormatJson::encode( $wgPagesListDataTablesOptions ) + ); + + return true; + } + + /** + * If there is a DataTables format on this page, load those modules + * + * @param OutputPage $out + * @param Skin $skin Unused + * @return boolean + */ + public static function onBeforePageDisplay( OutputPage &$out, Skin &$skin ) { + if ( PagesList::$loadDataTables ) { + PagesList::addDataTablesToOutput( $out ); + } + + return true; + } + + /** + * Set up the #pageslist parser function + * + * @param Parser $parser + * @return boolean + */ + public static function setupParserFunction( Parser &$parser ) { + $parser->setFunctionHook( 'pageslist', __CLASS__ . '::pageslistParserFunction', SFH_OBJECT_ARGS ); + + return true; + } + + /** + * The parser function is called with the form: + * {{#pageslist: + * namespace=namespacename | invert=yes/NO | associated=yes/NO | category=categoryname | + * format=plain/ol/ul/table/DATATABLE }} + * + * For the main namespace, use namespace=main. + * + * @global boolean $wgPagesListShowLastUser + * @global boolean $wgPagesListShowLastModification + * @param Parser $parser Unused + * @param PPFrame $frame + * @param array $args + * @return string + */ + public static function pageslistParserFunction( + Parser $parser, PPFrame $frame, array $args ) { + global $wgPagesListShowLastUser, $wgPagesListShowLastModification; + + $params = self::extractOptions( $args, $frame ); + $options = array( 'namespace', 'invert', 'associated', 'category', 'basepage', 'format' ); + foreach ( $options as $option ) { + $params[$option] = isset( $params[$option] ) ? $params[$option] : null; + } + + $categoryTitle = Title::makeTitleSafe( NS_CATEGORY, $params['category'] ); + $basePageTitle = Title::newFromText( $params['basepage'] ); + + if ( strtolower( $params['namespace'] ) === 'main' ) { + $params['namespace'] = ''; + } + if ( is_null( $params['namespace'] ) ) { + $namespaceId = null; + } else { + $namespaceId = MWNamespace::getCanonicalIndex( strtolower( $params['namespace'] ) ); + } + + $bools = array( 'invert', 'associated' ); + foreach ( $bools as $bool ) { + if ( strtolower( $params[$bool] ) == 'yes' ) { + $params[$bool] = true; + } else { + $params[$bool] = false; + } + } + + $pagesList = new PagesList( wfGetDB( DB_SLAVE ), $namespaceId, $params['invert'], + $params['associated'], $categoryTitle, $basePageTitle ); + + if ( is_null( $params['format'] ) ) { + $params['format'] = 'datatable'; + } + + $output = $pagesList->getList( $params['format'], $wgPagesListShowLastUser, + $wgPagesListShowLastModification ); + return array( $output, 'noparse' => true, 'isHTML' => true ); + } + + /** + * Converts an array of values in form [0] => "name=value" into a real + * associative array in form [name] => value + * + * @param array $options + * @param PPFrame $frame + * @return array + */ + public static function extractOptions( array $options, PPFrame $frame ) { + $results = array(); + + foreach ( $options as $option ) { + $pair = explode( '=', $frame->expand( $option ), 2 ); + if ( count( $pair ) == 2 ) { + $name = strtolower( trim( $pair[0] ) ); + $value = trim( $pair[1] ); + $results[$name] = $value; + } + } + + return $results; + } +} diff --git a/PagesList.i18n.alias.php b/PagesList.i18n.alias.php new file mode 100644 index 0000000..e47e554 --- /dev/null +++ b/PagesList.i18n.alias.php @@ -0,0 +1,15 @@ +<?php +/** + * Aliases for special pages of the PagesList extension + * + * @file + * @ingroup Extensions + */ + +$specialPageAliases = array(); + +/** English (English) */ +$specialPageAliases['en'] = array( + 'PagesList' => array( 'PagesList' ), + 'PagesListQueryPage' => array( 'PagesListQueryPage' ), +); diff --git a/PagesList.magic.php b/PagesList.magic.php new file mode 100644 index 0000000..37c72ce --- /dev/null +++ b/PagesList.magic.php @@ -0,0 +1,14 @@ +<?php +/** + * Magic words + * + * @author Ike Hecht + */ +$magicWords = array(); + +/** English + * @author Ike Hecht + */ +$magicWords['en'] = array( + 'pageslist' => array( 0, 'pageslist' ), +); diff --git a/PagesList.php b/PagesList.php new file mode 100644 index 0000000..c332498 --- /dev/null +++ b/PagesList.php @@ -0,0 +1,74 @@ +<?php +/** + * PagesList extension + * + * For more info see http://mediawiki.org/wiki/Extension:PagesList + * + * @file + * @ingroup Extensions + * @author Ike Hecht, 2014 + * @license GNU General Public Licence 2.0 or later + */ +$wgExtensionCredits['other'][] = array( + 'path' => __FILE__, + 'name' => 'PagesList', + 'author' => array( + 'Ike Hecht', + ), + 'version' => '0.1.0', + 'url' => 'https://www.mediawiki.org/wiki/Extension:PagesList', + 'descriptionmsg' => 'pageslist-desc', +); + +$wgAutoloadClasses['PagesList'] = __DIR__ . '/PagesList.class.php'; +$wgAutoloadClasses['PagesListHooks'] = __DIR__ . '/PagesList.hooks.php'; +$wgAutoloadClasses['PagesListOptions'] = __DIR__ . '/specials/PagesListOptions.php'; +$wgAutoloadClasses['SpecialPagesList'] = __DIR__ . '/specials/SpecialPagesList.php'; +$wgAutoloadClasses['SpecialPagesListQueryPage'] = __DIR__ . + '/specials/SpecialPagesListQueryPage.php'; +$wgMessagesDirs['PagesList'] = __DIR__ . '/i18n'; +$wgExtensionMessagesFiles['PagesListAlias'] = __DIR__ . '/PagesList.i18n.alias.php'; +$wgExtensionMessagesFiles['PagesListMagic'] = __DIR__ . '/PagesList.magic.php'; + +$wgSpecialPages['PagesList'] = 'SpecialPagesList'; +$wgSpecialPages['PagesListQueryPage'] = 'SpecialPagesListQueryPage'; + +$wgHooks['ParserFirstCallInit'][] = 'PagesListHooks::setupParserFunction'; +$wgHooks['BeforePageDisplay'][] = 'PagesListHooks::onBeforePageDisplay'; +$wgHooks['ResourceLoaderGetConfigVars'][] = 'PagesListHooks::onResourceLoaderGetConfigVars'; + +$wgResourceModules['ext.PagesList'] = array( + 'scripts' => 'modules/ext.PagesList.datatables.js', + 'localBasePath' => __DIR__, + 'remoteExtPath' => 'PagesList', + 'dependencies' => 'ext.PagesList.datatables' +); + +$wgResourceModules['ext.PagesList.datatables'] = array( + 'scripts' => 'modules/DataTables/media/js/jquery.dataTables.js', + 'styles' => 'modules/DataTables/media/css/jquery.dataTables.css', + 'localBasePath' => __DIR__, + 'remoteExtPath' => 'PagesList' +); + +/* Configuration */ +/** + * Show a column on Special:PagesList that shows who the page was last modified by + */ +$wgPagesListShowLastUser = false; + +/** + * Show a column on Special:PagesList that shows when the page was last modified + */ +$wgPagesListShowLastModification = false; + +/** + * An array of options for the DataTables plugin. See https://datatables.net/reference/option/ for + * more information. + * + * Example: + * $wgPagesListDataTablesOptions = array( + * 'iDisplayLength' => 25 + * ); + */ +$wgPagesListDataTablesOptions = array(); diff --git a/i18n/en.json b/i18n/en.json new file mode 100644 index 0000000..2784328 --- /dev/null +++ b/i18n/en.json @@ -0,0 +1,16 @@ +{ + "@metadata": { + "authors": [ + "Ike Hecht" + ] + }, + "pageslist-desc": "Shows a list of pages", + "pageslist": "Pages List", + "pageslistquerypage": "Pages List", + "pageslist-legend": "Pages list options", + "pageslist-categories": "Category:", + "pageslist-title": "Title", + "pageslist-last-modified": "Last modified", + "pageslist-last-user": "Last modified by", + "pageslist-basepage": "Subpages of (include namespace):" +} \ No newline at end of file diff --git a/i18n/qqq.json b/i18n/qqq.json new file mode 100644 index 0000000..97eecd7 --- /dev/null +++ b/i18n/qqq.json @@ -0,0 +1,16 @@ +{ + "@metadata": { + "authors": [ + "Ike Hecht" + ] + }, + "pageslist-desc": "{{desc|name=PagesList|url=http://www.mediawiki.org/wiki/Extension:PagesList}}", + "pageslist": "Title of Special:PagesList", + "pageslistquerypage": "Title of Special:PagesListQueryPage", + "pageslist-legend": "Legend for PagesList special page options header", + "pageslist-categories": "{{Identical|Categories}}", + "pageslist-basepage": "Label for the basepage field", + "pageslist-title": "Table header for the Page Title column", + "pageslist-last-modified": "Table header for the Last modified column", + "pageslist-last-user": "Table header for the Last modified by column" +} diff --git a/modules/DataTables b/modules/DataTables new file mode 160000 index 0000000..24b2d11 --- /dev/null +++ b/modules/DataTables +Subproject commit 24b2d114606725d05c1e79155284babc47d728ca diff --git a/modules/ext.PagesList.datatables.js b/modules/ext.PagesList.datatables.js new file mode 100644 index 0000000..5ab1cfe --- /dev/null +++ b/modules/ext.PagesList.datatables.js @@ -0,0 +1,4 @@ +jQuery( document ).ready( function( $ ) { + conf = mw.config.get( 'wgPagesList' ); + $( 'table.pages-list' ).dataTable( JSON.parse( conf.dataTablesOptions ) ); +} ); diff --git a/specials/PagesListOptions.php b/specials/PagesListOptions.php new file mode 100644 index 0000000..e452bef --- /dev/null +++ b/specials/PagesListOptions.php @@ -0,0 +1,175 @@ +<?php + +/** + * PagesListOptions + * + * @author Ike Hecht + */ +class PagesListOptions extends ContextSource { + /** + * + * @var Title + */ + private $pageTitle; + + /** + * + * @var FormOptions + */ + private $opts; + + /** + * + * @param Title $pageTitle + * @param FormOptions $opts + * @param IContextSource $context + */ + function __construct( Title $pageTitle, FormOptions $opts, IContextSource $context = null ) { + $this->pageTitle = $pageTitle; + $this->opts = $opts; + + if ( $context ) { + $this->setContext( $context ); + } + } + + /** + * Get a header for this page that allows selection of namespace & category + * borrowed from SpecialRecentChanges + * + * @global string $wgScript + * @return string HTML header + */ + public function getPageHeader() { + global $wgScript; + $opts = $this->opts; + + $extraOpts = $this->getExtraOptions( $opts ); + $extraOptsCount = count( $extraOpts ); + $count = 0; + $submit = ' ' . Xml::submitbutton( $this->msg( 'allpagessubmit' )->text() ); + + $out = Xml::openElement( 'table', array( 'id' => 'pageslist-header' ) ); + foreach ( $extraOpts as $name => $optionRow ) { + # Add submit button to the last row only + ++$count; + $addSubmit = ( $count === $extraOptsCount ) ? $submit : ''; + + $out .= Xml::openElement( 'tr' ); + if ( is_array( $optionRow ) ) { + $out .= Xml::tags( + 'td', array( 'class' => 'mw-label mw-' . $name . '-label' ), $optionRow[0] + ); + $out .= Xml::tags( + 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit + ); + } else { + $out .= Xml::tags( + 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit + ); + } + $out .= Xml::closeElement( 'tr' ); + } + $out .= Xml::closeElement( 'table' ); + + $unconsumed = $opts->getUnconsumedValues(); + foreach ( $unconsumed as $key => $value ) { + $out .= Html::hidden( $key, $value ); + } + + $t = $this->pageTitle; + $out .= Html::hidden( 'title', $t->getPrefixedText() ); + + $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out ); + + return Xml::fieldset( + $this->msg( 'pageslist-legend' )->text(), $form, array( 'class' => 'ploptions' ) + ); + } + + /** + * Get options to be displayed in a form + * + * @param FormOptions $opts + * @return array + */ + function getExtraOptions( $opts ) { + $opts->consumeValues( array( + 'namespace', 'invert', 'associated', 'categories', 'basepage' + ) ); + + $extraOpts = array(); + $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); + $extraOpts['category'] = $this->categoryFilterForm( $opts ); + $extraOpts['basepage'] = $this->basepageFilterForm( $opts ); + + return $extraOpts; + } + + /** + * Creates the choose namespace selection + * + * @param FormOptions $opts + * @return string + */ + protected function namespaceFilterForm( FormOptions $opts ) { + $nsSelect = Html::namespaceSelector( + array( 'selected' => $opts['namespace'], 'all' => '' ), + array( 'name' => 'namespace', 'id' => 'namespace' ) + ); + $nsLabel = Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ); + $invert = Xml::checkLabel( + $this->msg( 'invert' )->text(), 'invert', 'nsinvert', $opts['invert'], + array( 'title' => $this->msg( 'tooltip-invert' )->text() ) + ); + $associated = Xml::checkLabel( + $this->msg( 'namespace_association' )->text(), 'associated', 'nsassociated', + $opts['associated'], array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() ) + ); + + return array( $nsLabel, "$nsSelect $invert $associated" ); + } + + /** + * Create an input to filter changes by categories + * Borrowed from SpecialRecentChanges + * + * @param FormOptions $opts + * @return array + */ + protected function categoryFilterForm( FormOptions $opts ) { + list( $label, $input ) = Xml::inputLabelSep( $this->msg( 'pageslist-categories' )->text(), + 'categories', 'mw-categories', false, $opts['categories'] ); + + return array( $label, $input ); + } + + /** + * @param FormOptions $opts + * @return array + */ + protected function basepageFilterForm( FormOptions $opts ) { + list( $label, $input ) = Xml::inputLabelSep( $this->msg( 'pageslist-basepage' )->text(), + 'basepage', 'mw-basepage', false, $opts['basepage'] ); + + return array( $label, $input ); + } + + /** + * Get a FormOptions object containing the default options + * + * @return FormOptions + */ + public static function getDefaultOptions() { + $opts = new FormOptions(); + + $opts->add( 'namespace', '', FormOptions::INTNULL ); + $opts->add( 'invert', false ); + $opts->add( 'associated', false ); + + $opts->add( 'categories', '' ); + $opts->add( 'basepage', '' ); + + return $opts; + } +} diff --git a/specials/SpecialPagesList.php b/specials/SpecialPagesList.php new file mode 100644 index 0000000..774055c --- /dev/null +++ b/specials/SpecialPagesList.php @@ -0,0 +1,66 @@ +<?php + +/** + * SpecialPage for PagesList extension + * + * @file + * @ingroup Extensions + */ +class SpecialPagesList extends IncludableSpecialPage { + /** + * + * @var PagesList + */ + private $pagesList; + + /** + * + * @var PagesListOptions + */ + private $pagesListOptions; + + public function __construct() { + parent::__construct( 'PagesList' ); + + $opts = $this->fetchOptionsFromRequest( PagesListOptions::getDefaultOptions() ); + $categoryTitle = Title::makeTitleSafe( NS_CATEGORY, $opts['categories'] ); + $basePageTitle = Title::newFromText( $opts['basepage'] ); + $this->pagesList = new PagesList( wfGetDB( DB_SLAVE ), $opts['namespace'], $opts['invert'], + $opts['associated'], $categoryTitle, $basePageTitle ); + $this->pagesListOptions = new PagesListOptions( + $this->getPageTitle(), $opts, $this->getContext() ); + } + + /** + * Shows the page to the user. + * @param string $sub Unused + */ + public function execute( $sub ) { + global $wgPagesListShowLastUser, $wgPagesListShowLastModification, $wgPagesListDataTablesOptions; + + $out = $this->getOutput(); + $out->setPageTitle( $this->msg( 'pageslist' ) ); + $out->addHTML( $this->pagesListOptions->getPageHeader() ); + /** @todo modify to check if DataTables submodule exists */ + $out->addHTML( $this->pagesList->getList( 'datatable', $wgPagesListShowLastUser, + $wgPagesListShowLastModification, $wgPagesListDataTablesOptions, $out ) ); + } + + /** + * Fetch values for a FormOptions object from the WebRequest associated with this instance. + * + * Intended for subclassing, e.g. to add a backwards-compatibility layer. + * + * @param FormOptions $opts + * @return FormOptions + */ + protected function fetchOptionsFromRequest( $opts ) { + $opts->fetchValuesFromRequest( $this->getRequest() ); + + return $opts; + } + + protected function getGroupName() { + return 'pages'; + } +} diff --git a/specials/SpecialPagesListQueryPage.php b/specials/SpecialPagesListQueryPage.php new file mode 100644 index 0000000..7353ada --- /dev/null +++ b/specials/SpecialPagesListQueryPage.php @@ -0,0 +1,103 @@ +<?php +/** + * SpecialPage for PagesList extension + * + * @file + * @ingroup Extensions + */ + +class SpecialPagesListQueryPage extends QueryPage { + /** + * + * @var PagesList + */ + private $pagesList; + + /** + * + * @var PagesListOptions + */ + private $pagesListOptions; + + public function __construct( ) { + parent::__construct( 'PagesListQueryPage' ); + + $opts = $this->fetchOptionsFromRequest( PagesListOptions::getDefaultOptions() ); + $categoryTitle = Title::makeTitleSafe( NS_CATEGORY, $opts['categories'] ); + $basePageTitle = Title::newFromText( $opts['basepage'] ); + $this->pagesList = new PagesList( wfGetDB( DB_SLAVE ), $opts['namespace'], $opts['invert'], + $opts['associated'], $categoryTitle, $basePageTitle ); + $this->pagesListOptions = new PagesListOptions( + $this->getPageTitle(), $opts, $this->getContext() ); + } + + /** + * Fetch values for a FormOptions object from the WebRequest associated with this instance. + * + * Intended for subclassing, e.g. to add a backwards-compatibility layer. + * + * @param FormOptions $opts + * @return FormOptions + */ + protected function fetchOptionsFromRequest( $opts ) { + $opts->fetchValuesFromRequest( $this->getRequest() ); + + return $opts; + } + + function isExpensive() { + return true; + } + + function getQueryInfo() { + return $this->pagesList->getQueryInfo(); + } + + function usesTimestamps() { + return true; + } + + function getOrderFields() { + return $this->pagesList->getOrderFields(); + } + + function sortDescending() { + return false; + } + + protected function getGroupName() { + return 'pages'; + } + + /** + * Borrowed from AncientPagesPage + * + * @global Language $wgContLang + * @global boolean $wgPagesListShowLastModification + * @param Skin $skin Unused + * @param object $result Result row + * @return string + */ + function formatResult( $skin, $result ) { + global $wgContLang, $wgPagesListShowLastModification; + + if ( $wgPagesListShowLastModification ) { + $d = $this->getLanguage()->userDate( $result->value, $this->getUser() ); + } else { + $d = null; + } + $title = Title::makeTitle( $result->namespace, $result->title ); + $link = Linker::linkKnown( + $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) + ); + + return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) ); + } + + public function getPageHeader() { + return $this->pagesListOptions->getPageHeader(); + } + public function isIncludable() { + return true; + } +} -- To view, visit https://gerrit.wikimedia.org/r/177675 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: Id26042493c12f9f31495bb8be86ced53625afbee Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/PagesList Gerrit-Branch: master Gerrit-Owner: tosfos <[email protected]> Gerrit-Reviewer: Siebrand <[email protected]> Gerrit-Reviewer: tosfos <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
