r78786 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r78785‎ | r78786 | r78787 >
Date:14:16, 22 December 2010
Author:catrope
Status:resolved (Comments)
Tags:
Comment:
Merge querypage-work2 branch from trunk. The most relevant changes are:
* QueryPage now uses array-based query building instead of raw SQL
* Converted all QueryPage-based special pages that were using old-style wfSpecialFoo functions to new-style SpecialPage subclasses; this is possible because QueryPage is changed to extend SpecialPage
* Backward compatibility for extensions is partly preserved: getSQL() is fallen back on for QueryPage subclasses that don't implement getQueryInfo(), but getOrder() will be ignored (implement getOrderFields() instead). This also means that dual compatibility (1.18 compat and b/c with pre-1.18) is trivial

Extension changes will be merged after this commit.

These changes make it easier to write an API module for QueryPages (bug 14869); this wasn't done in the branch but will be done in trunk soon.
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/docs/hooks.txt (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/ImageQueryPage.php (modified) (history)
  • /trunk/phase3/includes/Namespace.php (modified) (history)
  • /trunk/phase3/includes/PageQueryPage.php (modified) (history)
  • /trunk/phase3/includes/QueryPage.php (modified) (history)
  • /trunk/phase3/includes/SpecialPage.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialAncientpages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialBrokenRedirects.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialDeadendpages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialDisambiguations.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialDoubleRedirects.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialFewestrevisions.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialFileDuplicateSearch.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialLinkSearch.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialListfiles.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialListredirects.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialLonelypages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialLongpages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialMIMEsearch.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialMostcategories.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialMostimages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialMostlinked.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialMostlinkedcategories.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialMostlinkedtemplates.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialMostrevisions.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialNewpages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialPopularpages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialShortpages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialTags.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUncategorizedcategories.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUncategorizedimages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUncategorizedpages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUncategorizedtemplates.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUnusedcategories.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUnusedimages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUnusedtemplates.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialUnwatchedpages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialWantedcategories.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialWantedfiles.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialWantedpages.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialWantedtemplates.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialWithoutinterwiki.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesQqq.php (modified) (history)
  • /trunk/phase3/maintenance/language/messages.inc (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/language/messages.inc
@@ -1623,6 +1623,7 @@
16241624 'pager-newer-n',
16251625 'pager-older-n',
16261626 'suppress',
 1627+ 'querypage-disabled',
16271628 ),
16281629 'booksources' => array(
16291630 'booksources',
Index: trunk/phase3/docs/hooks.txt
@@ -1900,10 +1900,10 @@
19011901 &$timestamp: new timestamp, change this to override local email
19021902 authentification timestamp
19031903
1904 -'WantedPages::getSQL': called in WantedPagesPage::getSQL(), can be used to
1905 -alter the SQL query which gets the list of wanted pages
 1904+'WantedPages::getQueryInfo': called in WantedPagesPage::getQueryInfo(), can be
 1905+used to alter the SQL query which gets the list of wanted pages
19061906 &$wantedPages: WantedPagesPage object
1907 -&$sql: raw SQL query used to get the list of wanted pages
 1907+&$query: query array, see QueryPage::getQueryInfo() for format documentation
19081908
19091909 'WatchArticle': before a watch is added to an article
19101910 $user: user that will watch
Index: trunk/phase3/includes/ImageQueryPage.php
@@ -7,7 +7,7 @@
88 * @ingroup SpecialPage
99 * @author Rob Church <robchur@gmail.com>
1010 */
11 -class ImageQueryPage extends QueryPage {
 11+abstract class ImageQueryPage extends QueryPage {
1212
1313 /**
1414 * Format and output report results using the given information plus
@@ -37,6 +37,9 @@
3838 $out->addHTML( $gallery->toHtml() );
3939 }
4040 }
 41+
 42+ // Gotta override this since it's abstract
 43+ function formatResult( $skin, $result ) { }
4144
4245 /**
4346 * Prepare an image object given a result row
Index: trunk/phase3/includes/AutoLoader.php
@@ -594,6 +594,7 @@
595595 'MostimagesPage' => 'includes/specials/SpecialMostimages.php',
596596 'MostlinkedCategoriesPage' => 'includes/specials/SpecialMostlinkedcategories.php',
597597 'MostlinkedPage' => 'includes/specials/SpecialMostlinked.php',
 598+ 'MostlinkedTemplatesPage' => 'includes/specials/SpecialMostlinkedtemplates.php',
598599 'MostrevisionsPage' => 'includes/specials/SpecialMostrevisions.php',
599600 'MovePageForm' => 'includes/specials/SpecialMovepage.php',
600601 'SpecialNewpages' => 'includes/specials/SpecialNewpages.php',
@@ -653,8 +654,8 @@
654655 'SpecialUserlogout' => 'includes/specials/SpecialUserlogout.php',
655656 'SpecialVersion' => 'includes/specials/SpecialVersion.php',
656657 'SpecialWhatlinkshere' => 'includes/specials/SpecialWhatlinkshere.php',
657 - 'SpecialWhatLinksHere' => 'includes/specials/SpecialWhatlinkshere.php',
658658 'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php',
 659+ 'UncategorizedImagesPage' => 'includes/specials/SpecialUncategorizedimages.php',
659660 'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php',
660661 'UncategorizedTemplatesPage' => 'includes/specials/SpecialUncategorizedtemplates.php',
661662 'UndeleteForm' => 'includes/specials/SpecialUndelete.php',
@@ -670,7 +671,6 @@
671672 'WantedFilesPage' => 'includes/specials/SpecialWantedfiles.php',
672673 'WantedPagesPage' => 'includes/specials/SpecialWantedpages.php',
673674 'WantedTemplatesPage' => 'includes/specials/SpecialWantedtemplates.php',
674 - 'WhatLinksHerePage' => 'includes/specials/SpecialWhatlinkshere.php',
675675 'WikiImporter' => 'includes/ImportXMLReader.php',
676676 'WikiRevision' => 'includes/Import.php',
677677 'WithoutInterwikiPage' => 'includes/specials/SpecialWithoutinterwiki.php',
Index: trunk/phase3/includes/PageQueryPage.php
@@ -5,7 +5,7 @@
66 *
77 * @ingroup SpecialPage
88 */
9 -class PageQueryPage extends QueryPage {
 9+abstract class PageQueryPage extends QueryPage {
1010
1111 /**
1212 * Format the result as a simple link to the page
Index: trunk/phase3/includes/Namespace.php
@@ -239,6 +239,21 @@
240240 }
241241
242242 /**
 243+ * Get a list of all namespace indices which are considered to contain content
 244+ * @return array of namespace indices
 245+ */
 246+ public static function getContentNamespaces() {
 247+ global $wgContentNamespaces;
 248+ if( !is_array( $wgContentNamespaces ) || $wgContentNamespaces === array() ) {
 249+ return NS_MAIN;
 250+ } elseif ( !in_array( NS_MAIN, $wgContentNamespaces ) ) {
 251+ // always force NS_MAIN to be part of array (to match the algorithm used by isContent)
 252+ return array_merge( array( NS_MAIN ), $wgContentNamespaces );
 253+ } else {
 254+ return $wgContentNamespaces;
 255+ }
 256+ }
 257+ /**
243258 * Is the namespace first-letter capitalized?
244259 *
245260 * @param $index int Index to check
Index: trunk/phase3/includes/specials/SpecialFewestrevisions.php
@@ -29,10 +29,10 @@
3030 */
3131 class FewestrevisionsPage extends QueryPage {
3232
33 - function getName() {
34 - return 'Fewestrevisions';
 33+ function __construct( $name = 'Fewestrevisions' ) {
 34+ parent::__construct( $name );
3535 }
36 -
 36+
3737 function isExpensive() {
3838 return true;
3939 }
@@ -41,31 +41,35 @@
4242 return false;
4343 }
4444
45 - function getSql() {
46 - $dbr = wfGetDB( DB_SLAVE );
47 - list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' );
48 -
49 - return "SELECT 'Fewestrevisions' as type,
50 - page_namespace as namespace,
51 - page_title as title,
52 - page_is_redirect as redirect,
53 - COUNT(*) as value
54 - FROM $revision
55 - JOIN $page ON page_id = rev_page
56 - WHERE page_namespace = " . NS_MAIN . "
57 - GROUP BY page_namespace, page_title, page_is_redirect
58 - HAVING COUNT(*) > 1";
 45+ function getQueryInfo() {
 46+ return array (
 47+ 'tables' => array ( 'revision', 'page' ),
 48+ 'fields' => array ( 'page_namespace AS namespace',
 49+ 'page_title AS title',
 50+ 'COUNT(*) AS value',
 51+ 'page_is_redirect AS redirect' ),
 52+ 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces(),
 53+ 'page_id = rev_page' ),
 54+ 'options' => array ( 'HAVING' => 'COUNT(*) > 1',
5955 // ^^^ This was probably here to weed out redirects.
6056 // Since we mark them as such now, it might be
6157 // useful to remove this. People _do_ create pages
6258 // and never revise them, they aren't necessarily
6359 // redirects.
 60+ 'GROUP BY' => 'page_namespace, page_title, ' .
 61+ 'page_is_redirect' )
 62+ );
6463 }
6564
 65+
6666 function sortDescending() {
6767 return false;
6868 }
6969
 70+ /**
 71+ * @param $skin Skin object
 72+ * @param $result Object: database row
 73+ */
7074 function formatResult( $skin, $result ) {
7175 global $wgLang, $wgContLang;
7276
@@ -94,9 +98,3 @@
9599 return wfSpecialList( $plink, $nlink );
96100 }
97101 }
98 -
99 -function wfSpecialFewestrevisions() {
100 - list( $limit, $offset ) = wfCheckLimits();
101 - $frp = new FewestrevisionsPage();
102 - $frp->doQuery( $offset, $limit );
103 -}
Index: trunk/phase3/includes/specials/SpecialWantedtemplates.php
@@ -33,35 +33,23 @@
3434 */
3535 class WantedTemplatesPage extends WantedQueryPage {
3636
37 - function getName() {
38 - return 'Wantedtemplates';
 37+ function __construct( $name = 'Wantedtemplates' ) {
 38+ parent::__construct( $name );
3939 }
4040
41 - function getSQL() {
42 - $dbr = wfGetDB( DB_SLAVE );
43 - list( $templatelinks, $page ) = $dbr->tableNamesN( 'templatelinks', 'page' );
44 - $name = $dbr->addQuotes( $this->getName() );
45 - return
46 - "
47 - SELECT $name as type,
48 - tl_namespace as namespace,
49 - tl_title as title,
50 - COUNT(*) as value
51 - FROM $templatelinks LEFT JOIN
52 - $page ON tl_title = page_title AND tl_namespace = page_namespace
53 - WHERE page_title IS NULL AND tl_namespace = ". NS_TEMPLATE ."
54 - GROUP BY tl_namespace, tl_title
55 - ";
 41+ function getQueryInfo() {
 42+ return array (
 43+ 'tables' => array ( 'templatelinks', 'page' ),
 44+ 'fields' => array ( 'tl_namespace AS namespace',
 45+ 'tl_title AS title',
 46+ 'COUNT(*) AS value' ),
 47+ 'conds' => array ( 'page_title IS NULL',
 48+ 'tl_namespace' => NS_TEMPLATE ),
 49+ 'options' => array (
 50+ 'GROUP BY' => 'tl_namespace, tl_title' ),
 51+ 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
 52+ array ( 'page_namespace = tl_namespace',
 53+ 'page_title = tl_title' ) ) )
 54+ );
5655 }
5756 }
58 -
59 -/**
60 - * constructor
61 - */
62 -function wfSpecialWantedTemplates() {
63 - list( $limit, $offset ) = wfCheckLimits();
64 -
65 - $wpp = new WantedTemplatesPage();
66 -
67 - $wpp->doQuery( $offset, $limit );
68 -}
Index: trunk/phase3/includes/specials/SpecialNewpages.php
@@ -477,8 +477,8 @@
478478
479479 $info = array(
480480 'tables' => array( 'recentchanges', 'page' ),
481 - 'fields' => 'rc_namespace,rc_title, rc_cur_id, rc_user,rc_user_text,rc_comment,
482 - rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id, ts_tags',
 481+ 'fields' => array( 'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text', 'rc_comment',
 482+ 'rc_timestamp', 'rc_patrolled', 'rc_id', 'page_len AS length', 'page_latest AS rev_id', 'ts_tags'),
483483 'conds' => $conds,
484484 'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ),
485485 'join_conds' => array(
Index: trunk/phase3/includes/specials/SpecialDisambiguations.php
@@ -28,32 +28,27 @@
2929 */
3030 class DisambiguationsPage extends PageQueryPage {
3131
32 - function getName() {
33 - return 'Disambiguations';
 32+ function __construct( $name = 'Disambiguations' ) {
 33+ parent::__construct( $name );
3434 }
3535
36 - function isExpensive( ) { return true; }
 36+ function isExpensive() { return true; }
3737 function isSyndicated() { return false; }
3838
39 -
40 - function getPageHeader( ) {
 39+ function getPageHeader() {
4140 return wfMsgExt( 'disambiguations-text', array( 'parse' ) );
4241 }
4342
44 - function getSQL() {
45 - global $wgContentNamespaces;
46 -
 43+ function getQueryInfo() {
4744 $dbr = wfGetDB( DB_SLAVE );
48 -
49 - $dMsgText = wfMsgForContent('disambiguationspage');
50 -
 45+ $dMsgText = wfMsgForContent( 'disambiguationspage' );
5146 $linkBatch = new LinkBatch;
5247
5348 # If the text can be treated as a title, use it verbatim.
5449 # Otherwise, pull the titles from the links table
5550 $dp = Title::newFromText($dMsgText);
5651 if( $dp ) {
57 - if($dp->getNamespace() != NS_TEMPLATE) {
 52+ if( $dp->getNamespace() != NS_TEMPLATE ) {
5853 # FIXME we assume the disambiguation message is a template but
5954 # the page can potentially be from another namespace :/
6055 wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n");
@@ -65,47 +60,64 @@
6661 $res = $dbr->select(
6762 array('pagelinks', 'page'),
6863 'pl_title',
69 - array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE,
70 - 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()),
 64+ array('page_id = pl_from',
 65+ 'pl_namespace' => NS_TEMPLATE,
 66+ 'page_namespace' => $disPageObj->getNamespace(),
 67+ 'page_title' => $disPageObj->getDBkey()),
7168 __METHOD__ );
7269
7370 foreach ( $res as $row ) {
7471 $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title ));
7572 }
7673 }
77 -
78 - $set = $linkBatch->constructSet( 'lb.tl', $dbr );
 74+ $set = $linkBatch->constructSet( 'tl', $dbr );
7975 if( $set === false ) {
80 - # We must always return a valid sql query, but this way DB will always quicly return an empty result
 76+ # We must always return a valid SQL query, but this way
 77+ # the DB will always quickly return an empty result
8178 $set = 'FALSE';
8279 wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n");
8380 }
 81+
 82+ // FIXME: What are pagelinks and p2 doing here?
 83+ return array (
 84+ 'tables' => array( 'templatelinks', 'p1' => 'page', 'pagelinks', 'p2' => 'page' ),
 85+ 'fields' => array( 'p1.page_namespace AS namespace',
 86+ 'p1.page_title AS title',
 87+ 'pl_from AS value' ),
 88+ 'conds' => array( $set,
 89+ 'p1.page_id = tl_from',
 90+ 'pl_namespace = p1.page_namespace',
 91+ 'pl_title = p1.page_title',
 92+ 'p2.page_id = pl_from',
 93+ 'p2.page_namespace' => MWNamespace::getContentNamespaces() )
 94+ );
 95+ }
8496
85 - list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' );
 97+ function getOrderFields() {
 98+ return array( 'tl_namespace', 'tl_title', 'value' );
 99+ }
 100+
 101+ function sortDescending() {
 102+ return false;
 103+ }
 104+
 105+ /**
 106+ * Fetch links and cache their existence
 107+ */
 108+ function preprocessResults( $db, $res ) {
 109+ $batch = new LinkBatch;
 110+ foreach ( $res as $row ) {
 111+ $batch->add( $row->namespace, $row->title );
 112+ }
 113+ $batch->execute();
86114
87 - if ( $wgContentNamespaces ) {
88 - $nsclause = 'IN (' . $dbr->makeList( $wgContentNamespaces ) . ')';
89 - } else {
90 - $nsclause = '= ' . NS_MAIN;
 115+ // Back to start for display
 116+ if ( $db->numRows( $res ) > 0 ) {
 117+ // If there are no rows we get an error seeking.
 118+ $db->dataSeek( $res, 0 );
91119 }
92 -
93 - $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace,"
94 - ." pb.page_title AS title, la.pl_from AS value"
95 - ." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa"
96 - ." WHERE $set" # disambiguation template(s)
97 - .' AND pa.page_id = la.pl_from'
98 - .' AND pa.page_namespace ' . $nsclause
99 - .' AND pb.page_id = lb.tl_from'
100 - .' AND pb.page_namespace = la.pl_namespace'
101 - .' AND pb.page_title = la.pl_title'
102 - .' ORDER BY lb.tl_namespace, lb.tl_title';
103 -
104 - return $sql;
105120 }
106121
107 - function getOrder() {
108 - return '';
109 - }
110122
111123 function formatResult( $skin, $result ) {
112124 global $wgContLang;
@@ -113,21 +125,11 @@
114126 $dp = Title::makeTitle( $result->namespace, $result->title );
115127
116128 $from = $skin->link( $title );
117 - $edit = $skin->link( $title, wfMsgExt( 'parentheses', array( 'escape' ), wfMsg( 'editlink' ) ) , array(), array( 'redirect' => 'no', 'action' => 'edit' ) );
 129+ $edit = $skin->link( $title, wfMsgExt( 'parentheses', array( 'escape' ), wfMsg( 'editlink' ) ) ,
 130+ array(), array( 'redirect' => 'no', 'action' => 'edit' ) );
118131 $arr = $wgContLang->getArrow();
119132 $to = $skin->link( $dp );
120133
121134 return "$from $edit $arr $to";
122135 }
123136 }
124 -
125 -/**
126 - * Constructor
127 - */
128 -function wfSpecialDisambiguations() {
129 - list( $limit, $offset ) = wfCheckLimits();
130 -
131 - $sd = new DisambiguationsPage();
132 -
133 - return $sd->doQuery( $offset, $limit );
134 -}
Index: trunk/phase3/includes/specials/SpecialMostrevisions.php
@@ -23,64 +23,12 @@
2424 * @ingroup SpecialPage
2525 * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
2626 */
27 -
28 -/**
29 - * A special page to show pages with highest revision count
30 - *
31 - * @ingroup SpecialPage
32 - */
33 -class MostrevisionsPage extends QueryPage {
34 -
35 - function getName() { return 'Mostrevisions'; }
36 - function isExpensive() { return true; }
37 - function isSyndicated() { return false; }
38 -
39 - function getSQL() {
40 - $dbr = wfGetDB( DB_SLAVE );
41 - list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' );
42 - return
43 - "
44 - SELECT
45 - 'Mostrevisions' as type,
46 - page_namespace as namespace,
47 - page_title as title,
48 - COUNT(*) as value
49 - FROM $revision
50 - JOIN $page ON page_id = rev_page
51 - WHERE page_namespace = " . NS_MAIN . "
52 - GROUP BY page_namespace, page_title
53 - HAVING COUNT(*) > 1
54 - ";
 27+class MostrevisionsPage extends FewestrevisionsPage {
 28+ function __construct( $name = 'Mostrevisions' ) {
 29+ parent::__construct( $name );
5530 }
56 -
57 - function formatResult( $skin, $result ) {
58 - global $wgLang, $wgContLang;
59 -
60 - $nt = Title::makeTitle( $result->namespace, $result->title );
61 - $text = $wgContLang->convert( $nt->getPrefixedText() );
62 -
63 - $plink = $skin->linkKnown( $nt, $text );
64 -
65 - $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'),
66 - $wgLang->formatNum( $result->value ) );
67 - $nlink = $skin->linkKnown(
68 - $nt,
69 - $nl,
70 - array(),
71 - array( 'action' => 'history' )
72 - );
73 -
74 - return wfSpecialList($plink, $nlink);
 31+
 32+ function sortDescending() {
 33+ return true;
7534 }
7635 }
77 -
78 -/**
79 - * constructor
80 - */
81 -function wfSpecialMostrevisions() {
82 - list( $limit, $offset ) = wfCheckLimits();
83 -
84 - $wpp = new MostrevisionsPage();
85 -
86 - $wpp->doQuery( $offset, $limit );
87 -}
Index: trunk/phase3/includes/specials/SpecialBrokenRedirects.php
@@ -28,38 +28,44 @@
2929 * @ingroup SpecialPage
3030 */
3131 class BrokenRedirectsPage extends PageQueryPage {
32 - var $targets = array();
3332
34 - function getName() {
35 - return 'BrokenRedirects';
 33+ function __construct( $name = 'BrokenRedirects' ) {
 34+ parent::__construct( $name );
3635 }
37 -
38 - function isExpensive( ) { return true; }
 36+
 37+ function isExpensive() { return true; }
3938 function isSyndicated() { return false; }
 39+ function sortDescending() { return false; }
4040
41 - function getPageHeader( ) {
 41+ function getPageHeader() {
4242 return wfMsgExt( 'brokenredirectstext', array( 'parse' ) );
4343 }
4444
45 - function getSQL() {
46 - $dbr = wfGetDB( DB_SLAVE );
47 - list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' );
48 -
49 - $sql = "SELECT 'BrokenRedirects' AS type,
50 - p1.page_namespace AS namespace,
51 - p1.page_title AS title,
52 - rd_namespace,
53 - rd_title
54 - FROM $redirect AS rd
55 - JOIN $page p1 ON (rd.rd_from=p1.page_id)
56 - LEFT JOIN $page AS p2 ON (rd_namespace=p2.page_namespace AND rd_title=p2.page_title )
57 - WHERE rd_namespace >= 0
58 - AND p2.page_namespace IS NULL";
59 - return $sql;
 45+ function getQueryInfo() {
 46+ return array(
 47+ 'tables' => array( 'redirect', 'p1' => 'page',
 48+ 'p2' => 'page' ),
 49+ 'fields' => array( 'p1.page_namespace AS namespace',
 50+ 'p1.page_title AS title',
 51+ 'rd_namespace',
 52+ 'rd_title'
 53+ ),
 54+ 'conds' => array( 'rd_namespace >= 0',
 55+ 'p2.page_namespace IS NULL'
 56+ ),
 57+ 'join_conds' => array( 'p1' => array( 'LEFT JOIN', array(
 58+ 'rd_from=p1.page_id',
 59+ ) ),
 60+ 'p2' => array( 'LEFT JOIN', array(
 61+ 'rd_namespace=p2.page_namespace',
 62+ 'rd_title=p2.page_title'
 63+ ) )
 64+ )
 65+ );
6066 }
6167
62 - function getOrder() {
63 - return '';
 68+ function getOrderFields() {
 69+ return array ( 'rd_namespace', 'rd_title', 'rd_from' );
6470 }
6571
6672 function formatResult( $skin, $result ) {
@@ -120,14 +126,3 @@
121127 return $out;
122128 }
123129 }
124 -
125 -/**
126 - * constructor
127 - */
128 -function wfSpecialBrokenRedirects() {
129 - list( $limit, $offset ) = wfCheckLimits();
130 -
131 - $sbr = new BrokenRedirectsPage();
132 -
133 - return $sbr->doQuery( $offset, $limit );
134 -}
Index: trunk/phase3/includes/specials/SpecialMostcategories.php
@@ -31,26 +31,25 @@
3232 */
3333 class MostcategoriesPage extends QueryPage {
3434
35 - function getName() { return 'Mostcategories'; }
 35+ function __construct( $name = 'Mostcategories' ) {
 36+ parent::__construct( $name );
 37+ }
 38+
3639 function isExpensive() { return true; }
3740 function isSyndicated() { return false; }
3841
39 - function getSQL() {
40 - $dbr = wfGetDB( DB_SLAVE );
41 - list( $categorylinks, $page) = $dbr->tableNamesN( 'categorylinks', 'page' );
42 - return
43 - "
44 - SELECT
45 - 'Mostcategories' as type,
46 - page_namespace as namespace,
47 - page_title as title,
48 - COUNT(*) as value
49 - FROM $categorylinks
50 - LEFT JOIN $page ON cl_from = page_id
51 - WHERE page_namespace = " . NS_MAIN . "
52 - GROUP BY page_namespace, page_title
53 - HAVING COUNT(*) > 1
54 - ";
 42+ function getQueryInfo() {
 43+ return array (
 44+ 'tables' => array ( 'categorylinks', 'page' ),
 45+ 'fields' => array ( 'page_namespace AS namespace',
 46+ 'page_title AS title',
 47+ 'COUNT(*) AS value' ),
 48+ 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces() ),
 49+ 'options' => array ( 'HAVING' => 'COUNT(*) > 1',
 50+ 'GROUP BY' => 'page_namespace, page_title' ),
 51+ 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
 52+ 'page_id = cl_from' ) )
 53+ );
5554 }
5655
5756 function formatResult( $skin, $result ) {
@@ -62,14 +61,3 @@
6362 return wfSpecialList( $link, $count );
6463 }
6564 }
66 -
67 -/**
68 - * constructor
69 - */
70 -function wfSpecialMostcategories() {
71 - list( $limit, $offset ) = wfCheckLimits();
72 -
73 - $wpp = new MostcategoriesPage();
74 -
75 - $wpp->doQuery( $offset, $limit );
76 -}
Index: trunk/phase3/includes/specials/SpecialListfiles.php
@@ -101,7 +101,7 @@
102102 $tables = array( 'image' );
103103 $fields = array_keys( $this->getFieldNames() );
104104 $fields[] = 'img_user';
105 - $fields[array_search('thumb', $fields)] = 'img_name as thumb';
 105+ $fields[array_search('thumb', $fields)] = 'img_name AS thumb';
106106 $options = $join_conds = array();
107107
108108 # Depends on $wgMiserMode
@@ -111,7 +111,7 @@
112112 # Need to rewrite this one
113113 foreach ( $fields as &$field ) {
114114 if ( $field == 'count' ) {
115 - $field = 'COUNT(oi_archive_name) as count';
 115+ $field = 'COUNT(oi_archive_name) AS count';
116116 }
117117 }
118118 unset( $field );
Index: trunk/phase3/includes/specials/SpecialFileDuplicateSearch.php
@@ -29,34 +29,94 @@
3030 * @ingroup SpecialPage
3131 */
3232 class FileDuplicateSearchPage extends QueryPage {
33 - var $hash, $filename;
 33+ protected $hash, $filename;
3434
35 - function __construct( $hash, $filename ) {
36 - $this->hash = $hash;
37 - $this->filename = $filename;
 35+ function __construct( $name = 'FileDuplicateSearch' ) {
 36+ parent::__construct( $name );
3837 }
3938
40 - function getName() { return 'FileDuplicateSearch'; }
41 - function isExpensive() { return false; }
4239 function isSyndicated() { return false; }
 40+ function isCacheable() { return false; }
4341
4442 function linkParameters() {
4543 return array( 'filename' => $this->filename );
4644 }
4745
48 - function getSQL() {
49 - $dbr = wfGetDB( DB_SLAVE );
50 - $image = $dbr->tableName( 'image' );
51 - $hash = $dbr->addQuotes( $this->hash );
 46+ function getQueryInfo() {
 47+ return array(
 48+ 'tables' => array( 'image' ),
 49+ 'fields' => array(
 50+ 'img_name AS title',
 51+ 'img_sha1 AS value',
 52+ 'img_user_text',
 53+ 'img_timestamp'
 54+ ),
 55+ 'conds' => array( 'img_sha1' => $this->hash )
 56+ );
 57+ }
 58+
 59+ function execute( $par ) {
 60+ global $wgRequest, $wgOut, $wgLang, $wgContLang, $wgScript;
 61+
 62+ $this->setHeaders();
 63+ $this->outputHeader();
 64+
 65+ $this->filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' );
 66+ $this->hash = '';
 67+ $title = Title::makeTitleSafe( NS_FILE, $this->filename );
 68+ if( $title && $title->getText() != '' ) {
 69+ $dbr = wfGetDB( DB_SLAVE );
 70+ $this->hash = $dbr->selectField( 'image', 'img_sha1', array( 'img_name' => $title->getDBkey() ), __METHOD__ );
 71+ }
5272
53 - return "SELECT 'FileDuplicateSearch' AS type,
54 - img_name AS title,
55 - img_sha1 AS value,
56 - img_user_text,
57 - img_timestamp
58 - FROM $image
59 - WHERE img_sha1 = $hash
60 - ";
 73+ # Create the input form
 74+ $wgOut->addHTML(
 75+ Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) .
 76+ Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
 77+ Xml::openElement( 'fieldset' ) .
 78+ Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) .
 79+ Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $this->filename ) . ' ' .
 80+ Xml::submitButton( wfMsg( 'fileduplicatesearch-submit' ) ) .
 81+ Xml::closeElement( 'fieldset' ) .
 82+ Xml::closeElement( 'form' )
 83+ );
 84+
 85+ if( $this->hash != '' ) {
 86+ $align = $wgContLang->alignEnd();
 87+
 88+ # Show a thumbnail of the file
 89+ $img = wfFindFile( $title );
 90+ if ( $img ) {
 91+ $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
 92+ if( $thumb ) {
 93+ $wgOut->addHTML( '<div style="float:' . $align . '" id="mw-fileduplicatesearch-icon">' .
 94+ $thumb->toHtml( array( 'desc-link' => false ) ) . '<br />' .
 95+ wfMsgExt( 'fileduplicatesearch-info', array( 'parse' ),
 96+ $wgLang->formatNum( $img->getWidth() ),
 97+ $wgLang->formatNum( $img->getHeight() ),
 98+ $wgLang->formatSize( $img->getSize() ),
 99+ $img->getMimeType()
 100+ ) .
 101+ '</div>' );
 102+ }
 103+ }
 104+
 105+ parent::execute( $par );
 106+
 107+ # Show a short summary
 108+ if( $this->numRows == 1 ) {
 109+ $wgOut->wrapWikiMsg(
 110+ "<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>",
 111+ array( 'fileduplicatesearch-result-1', $this->filename )
 112+ );
 113+ } elseif ( $this->numRows > 1 ) {
 114+ $wgOut->wrapWikiMsg(
 115+ "<p class='mw-fileduplicatesearch-result-n'>\n$1\n</p>",
 116+ array( 'fileduplicatesearch-result-n', $this->filename,
 117+ $wgLang->formatNum( $this->numRows - 1 ) )
 118+ );
 119+ }
 120+ }
61121 }
62122
63123 function formatResult( $skin, $result ) {
@@ -75,77 +135,3 @@
76136 return "$plink . . $user . . $time";
77137 }
78138 }
79 -
80 -/**
81 - * Output the HTML search form, and constructs the FileDuplicateSearch object.
82 - */
83 -function wfSpecialFileDuplicateSearch( $par = null ) {
84 - global $wgRequest, $wgOut, $wgLang, $wgContLang, $wgScript;
85 -
86 - $hash = '';
87 - $filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' );
88 -
89 - $title = Title::newFromText( $filename );
90 - if( $title && $title->getText() != '' ) {
91 - $dbr = wfGetDB( DB_SLAVE );
92 - $image = $dbr->tableName( 'image' );
93 - $encFilename = $dbr->addQuotes( htmlspecialchars( $title->getDBkey() ) );
94 - $sql = "SELECT img_sha1 from $image where img_name = $encFilename";
95 - $res = $dbr->query( $sql );
96 - $row = $dbr->fetchRow( $res );
97 - if( $row !== false ) {
98 - $hash = $row[0];
99 - }
100 - }
101 -
102 - # Create the input form
103 - $wgOut->addHTML(
104 - Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) .
105 - Html::hidden( 'title', SpecialPage::getTitleFor( 'FileDuplicateSearch' )->getPrefixedDbKey() ) .
106 - Xml::openElement( 'fieldset' ) .
107 - Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) .
108 - Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $filename ) . ' ' .
109 - Xml::submitButton( wfMsg( 'fileduplicatesearch-submit' ) ) .
110 - Xml::closeElement( 'fieldset' ) .
111 - Xml::closeElement( 'form' )
112 - );
113 -
114 - if( $hash != '' ) {
115 - $align = $wgContLang->alignEnd();
116 -
117 - # Show a thumbnail of the file
118 - $img = wfFindFile( $title );
119 - if ( $img ) {
120 - $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
121 - if( $thumb ) {
122 - $wgOut->addHTML( '<div style="float:' . $align . '" id="mw-fileduplicatesearch-icon">' .
123 - $thumb->toHtml( array( 'desc-link' => false ) ) . '<br />' .
124 - wfMsgExt( 'fileduplicatesearch-info', array( 'parse' ),
125 - $wgLang->formatNum( $img->getWidth() ),
126 - $wgLang->formatNum( $img->getHeight() ),
127 - $wgLang->formatSize( $img->getSize() ),
128 - $img->getMimeType()
129 - ) .
130 - '</div>' );
131 - }
132 - }
133 -
134 - # Do the query
135 - $wpp = new FileDuplicateSearchPage( $hash, $filename );
136 - list( $limit, $offset ) = wfCheckLimits();
137 - $count = $wpp->doQuery( $offset, $limit );
138 -
139 - # Show a short summary
140 - if( $count == 1 ) {
141 - $wgOut->wrapWikiMsg(
142 - "<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>",
143 - array( 'fileduplicatesearch-result-1', $filename )
144 - );
145 - } elseif ( $count > 1 ) {
146 - $wgOut->wrapWikiMsg(
147 - "<p class='mw-fileduplicatesearch-result-n'>\n$1\n</p>",
148 - array( 'fileduplicatesearch-result-n', $filename, $wgLang->formatNum( $count - 1 ) )
149 - );
150 - }
151 - }
152 -}
Index: trunk/phase3/includes/specials/SpecialPopularpages.php
@@ -28,8 +28,8 @@
2929 */
3030 class PopularPagesPage extends QueryPage {
3131
32 - function getName() {
33 - return "Popularpages";
 32+ function __construct( $name = 'Popularpages' ) {
 33+ parent::__construct( $name );
3434 }
3535
3636 function isExpensive() {
@@ -38,29 +38,14 @@
3939 }
4040 function isSyndicated() { return false; }
4141
42 - function getSQL() {
43 - $dbr = wfGetDB( DB_SLAVE );
44 - $page = $dbr->tableName( 'page' );
45 -
46 - $query =
47 - "SELECT 'Popularpages' as type,
48 - page_namespace as namespace,
49 - page_title as title,
50 - page_counter as value
51 - FROM $page ";
52 - $where =
53 - "WHERE page_is_redirect=0 AND page_namespace";
54 -
55 - global $wgContentNamespaces;
56 - if( empty( $wgContentNamespaces ) ) {
57 - $where .= '='.NS_MAIN;
58 - } else if( count( $wgContentNamespaces ) > 1 ) {
59 - $where .= ' in (' . implode( ', ', $wgContentNamespaces ) . ')';
60 - } else {
61 - $where .= '='.$wgContentNamespaces[0];
62 - }
63 -
64 - return $query . $where;
 42+ function getQueryInfo() {
 43+ return array (
 44+ 'tables' => array( 'page' ),
 45+ 'fields' => array( 'page_namespace AS namespace',
 46+ 'page_title AS title',
 47+ 'page_counter AS value'),
 48+ 'conds' => array( 'page_is_redirect' => 0,
 49+ 'page_namespace' => MWNamespace::getContentNamespaces() ) );
6550 }
6651
6752 function formatResult( $skin, $result ) {
@@ -78,14 +63,3 @@
7964 return wfSpecialList($link, $nv);
8065 }
8166 }
82 -
83 -/**
84 - * Constructor
85 - */
86 -function wfSpecialPopularpages() {
87 - list( $limit, $offset ) = wfCheckLimits();
88 -
89 - $ppp = new PopularPagesPage();
90 -
91 - return $ppp->doQuery( $offset, $limit );
92 -}
Index: trunk/phase3/includes/specials/SpecialUnwatchedpages.php
@@ -31,28 +31,34 @@
3232 */
3333 class UnwatchedpagesPage extends QueryPage {
3434
35 - function getName() { return 'Unwatchedpages'; }
 35+ function __construct( $name = 'Unwatchedpages' ) {
 36+ parent::__construct( $name, 'unwatchedpages' );
 37+ }
 38+
3639 function isExpensive() { return true; }
3740 function isSyndicated() { return false; }
3841
39 - function getSQL() {
40 - $dbr = wfGetDB( DB_SLAVE );
41 - list( $page, $watchlist ) = $dbr->tableNamesN( 'page', 'watchlist' );
42 - $mwns = NS_MEDIAWIKI;
43 - return
44 - "
45 - SELECT
46 - 'Unwatchedpages' as type,
47 - page_namespace as namespace,
48 - page_title as title,
49 - page_namespace as value
50 - FROM $page
51 - LEFT JOIN $watchlist ON wl_namespace = page_namespace AND page_title = wl_title
52 - WHERE wl_title IS NULL AND page_is_redirect = 0 AND page_namespace<>$mwns
53 - ";
 42+ function getQueryInfo() {
 43+ return array (
 44+ 'tables' => array ( 'page', 'watchlist' ),
 45+ 'fields' => array ( 'page_namespace AS namespace',
 46+ 'page_title AS title',
 47+ 'page_namespace AS value' ),
 48+ 'conds' => array ( 'wl_title IS NULL',
 49+ 'page_is_redirect' => 0,
 50+ "page_namespace != '" . NS_MEDIAWIKI .
 51+ "'" ),
 52+ 'join_conds' => array ( 'watchlist' => array (
 53+ 'LEFT JOIN', array ( 'wl_title = page_title',
 54+ 'wl_namespace = page_namespace' ) ) )
 55+ );
5456 }
5557
5658 function sortDescending() { return false; }
 59+
 60+ function getOrderFields() {
 61+ return array( 'page_namespace', 'page_title' );
 62+ }
5763
5864 function formatResult( $skin, $result ) {
5965 global $wgContLang;
@@ -74,19 +80,3 @@
7581 return wfSpecialList( $plink, $wlink );
7682 }
7783 }
78 -
79 -/**
80 - * constructor
81 - */
82 -function wfSpecialUnwatchedpages() {
83 - global $wgUser, $wgOut;
84 -
85 - if ( ! $wgUser->isAllowed( 'unwatchedpages' ) )
86 - return $wgOut->permissionRequired( 'unwatchedpages' );
87 -
88 - list( $limit, $offset ) = wfCheckLimits();
89 -
90 - $wpp = new UnwatchedpagesPage();
91 -
92 - $wpp->doQuery( $offset, $limit );
93 -}
Index: trunk/phase3/includes/specials/SpecialWantedfiles.php
@@ -31,10 +31,10 @@
3232 */
3333 class WantedFilesPage extends WantedQueryPage {
3434
35 - function getName() {
36 - return 'Wantedfiles';
 35+ function __construct( $name = 'Wantedfiles' ) {
 36+ parent::__construct( $name );
3737 }
38 -
 38+
3939 /**
4040 * KLUGE: The results may contain false positives for files
4141 * that exist e.g. in a shared repo. Setting this at least
@@ -45,32 +45,19 @@
4646 return true;
4747 }
4848
49 - function getSQL() {
50 - $dbr = wfGetDB( DB_SLAVE );
51 - list( $imagelinks, $image ) = $dbr->tableNamesN( 'imagelinks', 'image' );
52 - $name = $dbr->addQuotes( $this->getName() );
53 - return
54 - "
55 - SELECT
56 - $name as type,
57 - " . NS_FILE . " as namespace,
58 - il_to as title,
59 - COUNT(*) as value
60 - FROM $imagelinks
61 - LEFT JOIN $image ON il_to = img_name
62 - WHERE img_name IS NULL
63 - GROUP BY il_to
64 - ";
 49+ function getQueryInfo() {
 50+ return array (
 51+ 'tables' => array ( 'imagelinks', 'image' ),
 52+ 'fields' => array ( "'" . NS_FILE . "' AS namespace",
 53+ 'il_to AS title',
 54+ 'COUNT(*) AS value' ),
 55+ 'conds' => array ( 'img_name IS NULL' ),
 56+ 'options' => array ( 'GROUP BY' => 'il_to' ),
 57+ 'join_conds' => array ( 'image' =>
 58+ array ( 'LEFT JOIN',
 59+ array ( 'il_to = img_name' )
 60+ )
 61+ )
 62+ );
6563 }
6664 }
67 -
68 -/**
69 - * constructor
70 - */
71 -function wfSpecialWantedFiles() {
72 - list( $limit, $offset ) = wfCheckLimits();
73 -
74 - $wpp = new WantedFilesPage();
75 -
76 - $wpp->doQuery( $offset, $limit );
77 -}
Index: trunk/phase3/includes/specials/SpecialMostimages.php
@@ -31,24 +31,22 @@
3232 */
3333 class MostimagesPage extends ImageQueryPage {
3434
35 - function getName() { return 'Mostimages'; }
 35+ function __construct( $name = 'Mostimages' ) {
 36+ parent::__construct( $name );
 37+ }
 38+
3639 function isExpensive() { return true; }
3740 function isSyndicated() { return false; }
3841
39 - function getSQL() {
40 - $dbr = wfGetDB( DB_SLAVE );
41 - $imagelinks = $dbr->tableName( 'imagelinks' );
42 - return
43 - "
44 - SELECT
45 - 'Mostimages' as type,
46 - " . NS_FILE . " as namespace,
47 - il_to as title,
48 - COUNT(*) as value
49 - FROM $imagelinks
50 - GROUP BY il_to
51 - HAVING COUNT(*) > 1
52 - ";
 42+ function getQueryInfo() {
 43+ return array (
 44+ 'tables' => array ( 'imagelinks' ),
 45+ 'fields' => array ( "'" . NS_FILE . "' AS namespace",
 46+ 'il_to AS title',
 47+ 'COUNT(*) AS value' ),
 48+ 'options' => array ( 'GROUP BY' => 'il_to',
 49+ 'HAVING' => 'COUNT(*) > 1' )
 50+ );
5351 }
5452
5553 function getCellHtml( $row ) {
@@ -58,14 +56,3 @@
5957 }
6058
6159 }
62 -
63 -/**
64 - * Constructor
65 - */
66 -function wfSpecialMostimages() {
67 - list( $limit, $offset ) = wfCheckLimits();
68 -
69 - $wpp = new MostimagesPage();
70 -
71 - $wpp->doQuery( $offset, $limit );
72 -}
Index: trunk/phase3/includes/specials/SpecialLongpages.php
@@ -27,22 +27,11 @@
2828 */
2929 class LongPagesPage extends ShortPagesPage {
3030
31 - function getName() {
32 - return "Longpages";
 31+ function __construct( $name = 'Longpages' ) {
 32+ parent::__construct( $name );
3333 }
3434
3535 function sortDescending() {
3636 return true;
3737 }
3838 }
39 -
40 -/**
41 - * constructor
42 - */
43 -function wfSpecialLongpages() {
44 - list( $limit, $offset ) = wfCheckLimits();
45 -
46 - $lpp = new LongPagesPage();
47 -
48 - $lpp->doQuery( $offset, $limit );
49 -}
Index: trunk/phase3/includes/specials/SpecialLonelypages.php
@@ -29,9 +29,10 @@
3030 */
3131 class LonelyPagesPage extends PageQueryPage {
3232
33 - function getName() {
34 - return "Lonelypages";
 33+ function __construct( $name = 'Lonelypages' ) {
 34+ parent::__construct( $name );
3535 }
 36+
3637 function getPageHeader() {
3738 return wfMsgExt( 'lonelypagestext', array( 'parse' ) );
3839 }
@@ -45,35 +46,36 @@
4647 }
4748 function isSyndicated() { return false; }
4849
49 - function getSQL() {
50 - $dbr = wfGetDB( DB_SLAVE );
51 - list( $page, $pagelinks, $templatelinks ) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' );
52 -
53 - return
54 - "SELECT 'Lonelypages' AS type,
55 - page_namespace AS namespace,
56 - page_title AS title,
57 - page_title AS value
58 - FROM $page
59 - LEFT JOIN $pagelinks
60 - ON page_namespace=pl_namespace AND page_title=pl_title
61 - LEFT JOIN $templatelinks
62 - ON page_namespace=tl_namespace AND page_title=tl_title
63 - WHERE pl_namespace IS NULL
64 - AND page_namespace=".NS_MAIN."
65 - AND page_is_redirect=0
66 - AND tl_namespace IS NULL";
67 -
 50+ function getQueryInfo() {
 51+ return array (
 52+ 'tables' => array ( 'page', 'pagelinks',
 53+ 'templatelinks' ),
 54+ 'fields' => array ( 'page_namespace AS namespace',
 55+ 'page_title AS title',
 56+ 'page_title AS value' ),
 57+ 'conds' => array ( 'pl_namespace IS NULL',
 58+ 'page_namespace' => MWNamespace::getContentNamespaces(),
 59+ 'page_is_redirect' => 0,
 60+ 'tl_namespace IS NULL' ),
 61+ 'join_conds' => array (
 62+ 'pagelinks' => array (
 63+ 'LEFT JOIN', array (
 64+ 'pl_namespace = page_namespace',
 65+ 'pl_title = page_title' ) ),
 66+ 'templatelinks' => array (
 67+ 'LEFT JOIN', array (
 68+ 'tl_namespace = page_namespace',
 69+ 'tl_title = page_title' ) ) )
 70+ );
6871 }
 72+
 73+ function getOrderFields() {
 74+ // For some crazy reason ordering by a constant
 75+ // causes a filesort in MySQL 5
 76+ if( count( MWNamespace::getContentNamespaces() ) > 1 ) {
 77+ return array( 'page_namespace', 'page_title' );
 78+ } else {
 79+ return array( 'page_title' );
 80+ }
 81+ }
6982 }
70 -
71 -/**
72 - * Constructor
73 - */
74 -function wfSpecialLonelypages() {
75 - list( $limit, $offset ) = wfCheckLimits();
76 -
77 - $lpp = new LonelyPagesPage();
78 -
79 - return $lpp->doQuery( $offset, $limit );
80 -}
Index: trunk/phase3/includes/specials/SpecialUncategorizedpages.php
@@ -26,11 +26,12 @@
2727 *
2828 * @ingroup SpecialPage
2929 */
 30+// FIXME: Make $requestedNamespace selectable, unify all subclasses into one
3031 class UncategorizedPagesPage extends PageQueryPage {
31 - var $requestedNamespace = NS_MAIN;
 32+ protected $requestedNamespace = false;
3233
33 - function getName() {
34 - return "Uncategorizedpages";
 34+ function __construct( $name = 'Uncategorizedpages' ) {
 35+ parent::__construct( $name );
3536 }
3637
3738 function sortDescending() {
@@ -42,32 +43,27 @@
4344 }
4445 function isSyndicated() { return false; }
4546
46 - function getSQL() {
47 - $dbr = wfGetDB( DB_SLAVE );
48 - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' );
49 - $name = $dbr->addQuotes( $this->getName() );
50 -
51 - return
52 - "
53 - SELECT
54 - $name as type,
55 - page_namespace AS namespace,
56 - page_title AS title,
57 - page_title AS value
58 - FROM $page
59 - LEFT JOIN $categorylinks ON page_id=cl_from
60 - WHERE cl_from IS NULL AND page_namespace={$this->requestedNamespace} AND page_is_redirect=0
61 - ";
 47+ function getQueryInfo() {
 48+ return array (
 49+ 'tables' => array ( 'page', 'categorylinks' ),
 50+ 'fields' => array ( 'page_namespace AS namespace',
 51+ 'page_title AS title',
 52+ 'page_title AS value' ),
 53+ // default for page_namespace is all content namespaces (if requestedNamespace is false)
 54+ // otherwise, page_namespace is requestedNamespace
 55+ 'conds' => array ( 'cl_from IS NULL',
 56+ 'page_namespace' => ( $this->requestedNamespace!==false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ),
 57+ 'page_is_redirect' => 0 ),
 58+ 'join_conds' => array ( 'categorylinks' => array (
 59+ 'LEFT JOIN', 'cl_from = page_id' ) )
 60+ );
6261 }
 62+
 63+ function getOrderFields() {
 64+ // For some crazy reason ordering by a constant
 65+ // causes a filesort
 66+ if( $this->requestedNamespace === false && count( MWNamespace::getContentNamespaces() ) > 1 )
 67+ return array( 'page_namespace', 'page_title' );
 68+ return array( 'page_title' );
 69+ }
6370 }
64 -
65 -/**
66 - * constructor
67 - */
68 -function wfSpecialUncategorizedpages() {
69 - list( $limit, $offset ) = wfCheckLimits();
70 -
71 - $lpp = new UncategorizedPagesPage();
72 -
73 - return $lpp->doQuery( $offset, $limit );
74 -}
Index: trunk/phase3/includes/specials/SpecialMostlinkedtemplates.php
@@ -28,15 +28,10 @@
2929 *
3030 * @ingroup SpecialPage
3131 */
32 -class SpecialMostlinkedtemplates extends QueryPage {
 32+class MostlinkedTemplatesPage extends QueryPage {
3333
34 - /**
35 - * Name of the report
36 - *
37 - * @return String
38 - */
39 - public function getName() {
40 - return 'Mostlinkedtemplates';
 34+ function __construct( $name = 'Mostlinkedtemplates' ) {
 35+ parent::__construct( $name );
4136 }
4237
4338 /**
@@ -66,22 +61,15 @@
6762 return true;
6863 }
6964
70 - /**
71 - * Generate SQL for the report
72 - *
73 - * @return String
74 - */
75 - public function getSql() {
76 - $dbr = wfGetDB( DB_SLAVE );
77 - $templatelinks = $dbr->tableName( 'templatelinks' );
78 - $name = $dbr->addQuotes( $this->getName() );
79 - return "SELECT {$name} AS type,
80 - " . NS_TEMPLATE . " AS namespace,
81 - tl_title AS title,
82 - COUNT(*) AS value
83 - FROM {$templatelinks}
84 - WHERE tl_namespace = " . NS_TEMPLATE . "
85 - GROUP BY tl_title";
 65+ public function getQueryInfo() {
 66+ return array (
 67+ 'tables' => array ( 'templatelinks' ),
 68+ 'fields' => array ( 'tl_namespace AS namespace',
 69+ 'tl_title AS title',
 70+ 'COUNT(*) AS value' ),
 71+ 'conds' => array ( 'tl_namespace' => NS_TEMPLATE ),
 72+ 'options' => array( 'GROUP BY' => 'tl_title' )
 73+ );
8674 }
8775
8876 /**
@@ -108,7 +96,7 @@
10997 * @return String
11098 */
11199 public function formatResult( $skin, $result ) {
112 - $title = Title::makeTitleSafe( $result->namespace, $result->title );
 100+ $title = Title::makeTitle( $result->namespace, $result->title );
113101
114102 return wfSpecialList(
115103 $skin->link( $title ),
@@ -133,13 +121,3 @@
134122 }
135123 }
136124
137 -/**
138 - * Execution function
139 - *
140 - * @param $par Mixed: parameters passed to the page
141 - */
142 -function wfSpecialMostlinkedtemplates( $par = false ) {
143 - list( $limit, $offset ) = wfCheckLimits();
144 - $mlt = new SpecialMostlinkedtemplates();
145 - $mlt->doQuery( $offset, $limit );
146 -}
Index: trunk/phase3/includes/specials/SpecialUnusedcategories.php
@@ -28,25 +28,26 @@
2929
3030 function isExpensive() { return true; }
3131
32 - function getName() {
33 - return 'Unusedcategories';
 32+ function __construct( $name = 'Unusedcategories' ) {
 33+ parent::__construct( $name );
3434 }
3535
3636 function getPageHeader() {
3737 return wfMsgExt( 'unusedcategoriestext', array( 'parse' ) );
3838 }
3939
40 - function getSQL() {
41 - $NScat = NS_CATEGORY;
42 - $dbr = wfGetDB( DB_SLAVE );
43 - list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' );
44 - return "SELECT 'Unusedcategories' as type,
45 - {$NScat} as namespace, page_title as title, page_title as value
46 - FROM $page
47 - LEFT JOIN $categorylinks ON page_title=cl_to
48 - WHERE cl_from IS NULL
49 - AND page_namespace = {$NScat}
50 - AND page_is_redirect = 0";
 40+ function getQueryInfo() {
 41+ return array (
 42+ 'tables' => array ( 'page', 'categorylinks' ),
 43+ 'fields' => array ( 'page_namespace AS namespace',
 44+ 'page_title AS title',
 45+ 'page_title AS value' ),
 46+ 'conds' => array ( 'cl_from IS NULL',
 47+ 'page_namespace' => NS_CATEGORY,
 48+ 'page_is_redirect' => 0 ),
 49+ 'join_conds' => array ( 'categorylinks' => array (
 50+ 'LEFT JOIN', 'cl_to = page_title' ) )
 51+ );
5152 }
5253
5354 function formatResult( $skin, $result ) {
@@ -54,10 +55,3 @@
5556 return $skin->link( $title, $title->getText() );
5657 }
5758 }
58 -
59 -/** constructor */
60 -function wfSpecialUnusedCategories() {
61 - list( $limit, $offset ) = wfCheckLimits();
62 - $uc = new UnusedCategoriesPage();
63 - return $uc->doQuery( $offset, $limit );
64 -}
Index: trunk/phase3/includes/specials/SpecialUncategorizedcategories.php
@@ -27,22 +27,8 @@
2828 * @ingroup SpecialPage
2929 */
3030 class UncategorizedCategoriesPage extends UncategorizedPagesPage {
31 - function __construct() {
 31+ function __construct( $name = 'Uncategorizedcategories' ) {
 32+ parent::__construct( $name );
3233 $this->requestedNamespace = NS_CATEGORY;
3334 }
34 -
35 - function getName() {
36 - return "Uncategorizedcategories";
37 - }
3835 }
39 -
40 -/**
41 - * constructor
42 - */
43 -function wfSpecialUncategorizedcategories() {
44 - list( $limit, $offset ) = wfCheckLimits();
45 -
46 - $lpp = new UncategorizedCategoriesPage();
47 -
48 - return $lpp->doQuery( $offset, $limit );
49 -}
Index: trunk/phase3/includes/specials/SpecialShortpages.php
@@ -29,10 +29,11 @@
3030 */
3131 class ShortPagesPage extends QueryPage {
3232
33 - function getName() {
34 - return 'Shortpages';
 33+ function __construct( $name = 'Shortpages' ) {
 34+ parent::__construct( $name );
3535 }
3636
 37+ // inexpensive?
3738 /**
3839 * This query is indexed as of 1.5
3940 */
@@ -44,27 +45,20 @@
4546 return false;
4647 }
4748
48 - function getSQL() {
49 - global $wgContentNamespaces;
 49+ function getQueryInfo() {
 50+ return array (
 51+ 'tables' => array ( 'page' ),
 52+ 'fields' => array ( 'page_namespace AS namespace',
 53+ 'page_title AS title',
 54+ 'page_len AS value' ),
 55+ 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces(),
 56+ 'page_is_redirect' => 0 ),
 57+ 'options' => array ( 'USE INDEX' => 'page_len' )
 58+ );
 59+ }
5060
51 - $dbr = wfGetDB( DB_SLAVE );
52 - $page = $dbr->tableName( 'page' );
53 - $name = $dbr->addQuotes( $this->getName() );
54 -
55 - $forceindex = $dbr->useIndexClause("page_len");
56 -
57 - if ($wgContentNamespaces)
58 - $nsclause = "page_namespace IN (" . $dbr->makeList($wgContentNamespaces) . ")";
59 - else
60 - $nsclause = "page_namespace = " . NS_MAIN;
61 -
62 - return
63 - "SELECT $name as type,
64 - page_namespace as namespace,
65 - page_title as title,
66 - page_len AS value
67 - FROM $page $forceindex
68 - WHERE $nsclause AND page_is_redirect=0";
 61+ function getOrderFields() {
 62+ return array( 'page_len' );
6963 }
7064
7165 function preprocessResults( $db, $res ) {
@@ -90,7 +84,7 @@
9185 global $wgLang, $wgContLang;
9286 $dm = $wgContLang->getDirMark();
9387
94 - $title = Title::makeTitleSafe( $result->namespace, $result->title );
 88+ $title = Title::makeTitle( $result->namespace, $result->title );
9589 if ( !$title ) {
9690 return '<!-- Invalid title ' . htmlspecialchars( "{$result->namespace}:{$result->title}" ). '-->';
9791 }
@@ -110,14 +104,3 @@
111105 : "<del>({$hlink}) {$dm}{$plink} {$dm}[{$size}]</del>";
112106 }
113107 }
114 -
115 -/**
116 - * constructor
117 - */
118 -function wfSpecialShortpages() {
119 - list( $limit, $offset ) = wfCheckLimits();
120 -
121 - $spp = new ShortPagesPage();
122 -
123 - return $spp->doQuery( $offset, $limit );
124 -}
Index: trunk/phase3/includes/specials/SpecialUnusedtemplates.php
@@ -31,24 +31,29 @@
3232 */
3333 class UnusedtemplatesPage extends QueryPage {
3434
35 - function getName() { return( 'Unusedtemplates' ); }
 35+ function __construct( $name = 'Unusedtemplates' ) {
 36+ parent::__construct( $name );
 37+ }
 38+
3639 function isExpensive() { return true; }
3740 function isSyndicated() { return false; }
3841 function sortDescending() { return false; }
3942
40 - function getSQL() {
41 - $dbr = wfGetDB( DB_SLAVE );
42 - list( $page, $templatelinks) = $dbr->tableNamesN( 'page', 'templatelinks' );
43 - $sql = "SELECT 'Unusedtemplates' AS type, page_title AS title,
44 - page_namespace AS namespace, 0 AS value
45 - FROM $page
46 - LEFT JOIN $templatelinks
47 - ON page_namespace = tl_namespace AND page_title = tl_title
48 - WHERE page_namespace = 10 AND tl_from IS NULL
49 - AND page_is_redirect = 0";
50 - return $sql;
 43+ function getQueryInfo() {
 44+ return array (
 45+ 'tables' => array ( 'page', 'templatelinks' ),
 46+ 'fields' => array ( 'page_namespace AS namespace',
 47+ 'page_title AS title',
 48+ 'page_title AS value' ),
 49+ 'conds' => array ( 'page_namespace' => NS_TEMPLATE,
 50+ 'tl_from IS NULL',
 51+ 'page_is_redirect' => 0 ),
 52+ 'join_conds' => array ( 'templatelinks' => array (
 53+ 'LEFT JOIN', array ( 'tl_title = page_title',
 54+ 'tl_namespace = page_namespace' ) ) )
 55+ );
5156 }
52 -
 57+
5358 function formatResult( $skin, $result ) {
5459 $title = Title::makeTitle( NS_TEMPLATE, $result->title );
5560 $pageLink = $skin->linkKnown(
@@ -72,8 +77,3 @@
7378
7479 }
7580
76 -function wfSpecialUnusedtemplates() {
77 - list( $limit, $offset ) = wfCheckLimits();
78 - $utp = new UnusedtemplatesPage();
79 - $utp->doQuery( $offset, $limit );
80 -}
Index: trunk/phase3/includes/specials/SpecialUncategorizedtemplates.php
@@ -29,20 +29,8 @@
3030 * @ingroup SpecialPage
3131 */
3232 class UncategorizedTemplatesPage extends UncategorizedPagesPage {
33 -
34 - var $requestedNamespace = NS_TEMPLATE;
35 -
36 - public function getName() {
37 - return 'Uncategorizedtemplates';
 33+ public function __construct( $name = 'Uncategorizedtemplates' ) {
 34+ parent::__construct( $name );
 35+ $this->requestedNamespace = NS_TEMPLATE;
3836 }
39 -
4037 }
41 -
42 -/**
43 - * Main execution point
44 - */
45 -function wfSpecialUncategorizedtemplates() {
46 - list( $limit, $offset ) = wfCheckLimits();
47 - $utp = new UncategorizedTemplatesPage();
48 - $utp->doQuery( $offset, $limit );
49 -}
Index: trunk/phase3/includes/specials/SpecialWantedcategories.php
@@ -30,26 +30,22 @@
3131 */
3232 class WantedCategoriesPage extends WantedQueryPage {
3333
34 - function getName() {
35 - return 'Wantedcategories';
 34+ function __construct( $name = 'Wantedcategories' ) {
 35+ parent::__construct( $name );
3636 }
3737
38 - function getSQL() {
39 - $dbr = wfGetDB( DB_SLAVE );
40 - list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' );
41 - $name = $dbr->addQuotes( $this->getName() );
42 - return
43 - "
44 - SELECT
45 - $name as type,
46 - " . NS_CATEGORY . " as namespace,
47 - cl_to as title,
48 - COUNT(*) as value
49 - FROM $categorylinks
50 - LEFT JOIN $page ON cl_to = page_title AND page_namespace = ". NS_CATEGORY ."
51 - WHERE page_title IS NULL
52 - GROUP BY cl_to
53 - ";
 38+ function getQueryInfo() {
 39+ return array (
 40+ 'tables' => array ( 'categorylinks', 'page' ),
 41+ 'fields' => array ( "'" . NS_CATEGORY . "' AS namespace",
 42+ 'cl_to AS title',
 43+ 'COUNT(*) AS value' ),
 44+ 'conds' => array ( 'page_title IS NULL' ),
 45+ 'options' => array ( 'GROUP BY' => 'cl_to' ),
 46+ 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
 47+ array ( 'page_title = cl_to',
 48+ 'page_namespace' => NS_CATEGORY ) ) )
 49+ );
5450 }
5551
5652 function formatResult( $skin, $result ) {
@@ -73,14 +69,3 @@
7470 return wfSpecialList($plink, $nlinks);
7571 }
7672 }
77 -
78 -/**
79 - * constructor
80 - */
81 -function wfSpecialWantedCategories() {
82 - list( $limit, $offset ) = wfCheckLimits();
83 -
84 - $wpp = new WantedCategoriesPage();
85 -
86 - $wpp->doQuery( $offset, $limit );
87 -}
Index: trunk/phase3/includes/specials/SpecialWithoutinterwiki.php
@@ -30,9 +30,15 @@
3131 class WithoutInterwikiPage extends PageQueryPage {
3232 private $prefix = '';
3333
34 - function getName() {
35 - return 'Withoutinterwiki';
 34+ function __construct( $name = 'Withoutinterwiki' ) {
 35+ parent::__construct( $name );
3636 }
 37+
 38+ function execute( $par ) {
 39+ global $wgRequest;
 40+ $this->prefix = Title::capitalize( $wgRequest->getVal( 'prefix', $par ), NS_MAIN );
 41+ parent::execute( $par );
 42+ }
3743
3844 function getPageHeader() {
3945 global $wgScript, $wgMiserMode;
@@ -58,6 +64,10 @@
5965 function sortDescending() {
6066 return false;
6167 }
 68+
 69+ function getOrderFields() {
 70+ return array( 'page_namespace', 'page_title' );
 71+ }
6272
6373 function isExpensive() {
6474 return true;
@@ -67,36 +77,22 @@
6878 return false;
6979 }
7080
71 - function getSQL() {
72 - $dbr = wfGetDB( DB_SLAVE );
73 - list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' );
74 - $prefix = $this->prefix ? 'AND page_title' . $dbr->buildLike( $this->prefix , $dbr->anyString() ) : '';
75 - return
76 - "SELECT 'Withoutinterwiki' AS type,
77 - page_namespace AS namespace,
78 - page_title AS title,
79 - page_title AS value
80 - FROM $page
81 - LEFT JOIN $langlinks
82 - ON ll_from = page_id
83 - WHERE ll_title IS NULL
84 - AND page_namespace=" . NS_MAIN . "
85 - AND page_is_redirect = 0
86 - {$prefix}";
 81+ function getQueryInfo() {
 82+ $query = array (
 83+ 'tables' => array ( 'page', 'langlinks' ),
 84+ 'fields' => array ( 'page_namespace AS namespace',
 85+ 'page_title AS title',
 86+ 'page_title AS value' ),
 87+ 'conds' => array ( 'll_title IS NULL',
 88+ 'page_namespace' => NS_MAIN,
 89+ 'page_is_redirect' => 0 ),
 90+ 'join_conds' => array ( 'langlinks' => array (
 91+ 'LEFT JOIN', 'll_from = page_id' ) )
 92+ );
 93+ if ( $this->prefix ) {
 94+ $dbr = wfGetDb( DB_SLAVE );
 95+ $query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() );
 96+ }
 97+ return $query;
8798 }
88 -
89 - function setPrefix( $prefix = '' ) {
90 - $this->prefix = $prefix;
91 - }
92 -
9399 }
94 -
95 -function wfSpecialWithoutinterwiki() {
96 - global $wgRequest;
97 - list( $limit, $offset ) = wfCheckLimits();
98 - // Only searching the mainspace anyway
99 - $prefix = Title::capitalize( $wgRequest->getVal( 'prefix' ), NS_MAIN );
100 - $wip = new WithoutInterwikiPage();
101 - $wip->setPrefix( $prefix );
102 - $wip->doQuery( $offset, $limit );
103 -}
Index: trunk/phase3/includes/specials/SpecialUnusedimages.php
@@ -27,55 +27,57 @@
2828 * @ingroup SpecialPage
2929 */
3030 class UnusedimagesPage extends ImageQueryPage {
31 -
32 - function isExpensive() { return true; }
33 -
34 - function getName() {
35 - return 'Unusedimages';
 31+ function __construct( $name = 'Unusedimages' ) {
 32+ parent::__construct( $name );
3633 }
37 -
 34+
 35+ function isExpensive() {
 36+ return true;
 37+ }
 38+
3839 function sortDescending() {
3940 return false;
4041 }
41 - function isSyndicated() { return false; }
 42+
 43+ function isSyndicated() {
 44+ return false;
 45+ }
4246
43 - function getSQL() {
 47+ function getQueryInfo() {
4448 global $wgCountCategorizedImagesAsUsed;
 49+ $retval = array (
 50+ 'tables' => array ( 'image', 'imagelinks' ),
 51+ 'fields' => array ( "'" . NS_FILE . "' AS namespace",
 52+ 'img_name AS title',
 53+ 'img_timestamp AS value',
 54+ 'img_user', 'img_user_text',
 55+ 'img_description' ),
 56+ 'conds' => array ( 'il_to IS NULL' ),
 57+ 'join_conds' => array ( 'imagelinks' => array (
 58+ 'LEFT JOIN', 'il_to = img_name' ) )
 59+ );
4560
46 - $dbr = wfGetDB( DB_SLAVE );
47 -
48 - $epoch = $dbr->unixTimestamp( 'img_timestamp' );
49 -
5061 if ( $wgCountCategorizedImagesAsUsed ) {
51 - list( $page, $image, $imagelinks, $categorylinks ) = $dbr->tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' );
52 -
53 - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, $epoch as value,
54 - img_user, img_user_text, img_description
55 - FROM ((($page AS I LEFT JOIN $categorylinks AS L ON I.page_id = L.cl_from)
56 - LEFT JOIN $imagelinks AS P ON I.page_title = P.il_to)
57 - INNER JOIN $image AS G ON I.page_title = G.img_name)
58 - WHERE I.page_namespace = ".NS_FILE." AND L.cl_from IS NULL AND P.il_to IS NULL";
59 - } else {
60 - list( $image, $imagelinks ) = $dbr->tableNamesN( 'image','imagelinks' );
61 -
62 - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, $epoch as value,
63 - img_user, img_user_text, img_description
64 - FROM $image LEFT JOIN $imagelinks ON img_name=il_to WHERE il_to IS NULL ";
 62+ // Order is significant
 63+ $retval['tables'] = array ( 'image', 'page', 'categorylinks',
 64+ 'imagelinks' );
 65+ $retval['conds']['page_namespace'] = NS_FILE;
 66+ $retval['conds'][] = 'cl_from IS NULL';
 67+ $retval['conds'][] = 'img_name = page_title';
 68+ $retval['join_conds']['categorylinks'] = array (
 69+ 'LEFT JOIN', 'cl_from = page_id' );
 70+ $retval['join_conds']['imagelinks'] = array (
 71+ 'LEFT JOIN', 'il_to = page_title' );
6572 }
 73+ return $retval;
6674 }
6775
 76+ function usesTimestamps() {
 77+ return true;
 78+ }
 79+
6880 function getPageHeader() {
6981 return wfMsgExt( 'unusedimagestext', array( 'parse' ) );
7082 }
7183
7284 }
73 -
74 -/**
75 - * Entry point
76 - */
77 -function wfSpecialUnusedimages() {
78 - list( $limit, $offset ) = wfCheckLimits();
79 - $uip = new UnusedimagesPage();
80 -
81 - return $uip->doQuery( $offset, $limit );
82 -}
Index: trunk/phase3/includes/specials/SpecialUncategorizedimages.php
@@ -27,10 +27,11 @@
2828 *
2929 * @ingroup SpecialPage
3030 */
 31+// FIXME: Use an instance of UncategorizedPagesPage or something
3132 class UncategorizedImagesPage extends ImageQueryPage {
3233
33 - function getName() {
34 - return 'Uncategorizedimages';
 34+ function __construct( $name = 'Uncategorizedimages' ) {
 35+ parent::__construct( $name );
3536 }
3637
3738 function sortDescending() {
@@ -44,22 +45,19 @@
4546 function isSyndicated() {
4647 return false;
4748 }
48 -
49 - function getSQL() {
50 - $dbr = wfGetDB( DB_SLAVE );
51 - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' );
52 - $ns = NS_FILE;
53 -
54 - return "SELECT 'Uncategorizedimages' AS type, page_namespace AS namespace,
55 - page_title AS title, page_title AS value
56 - FROM {$page} LEFT JOIN {$categorylinks} ON page_id = cl_from
57 - WHERE cl_from IS NULL AND page_namespace = {$ns} AND page_is_redirect = 0";
 49+
 50+ function getQueryInfo() {
 51+ return array (
 52+ 'tables' => array( 'page', 'categorylinks' ),
 53+ 'fields' => array( 'page_namespace AS namespace',
 54+ 'page_title AS title',
 55+ 'page_title AS value' ),
 56+ 'conds' => array( 'cl_from IS NULL',
 57+ 'page_namespace' => NS_FILE,
 58+ 'page_is_redirect' => 0 ),
 59+ 'join_conds' => array( 'categorylinks' => array(
 60+ 'LEFT JOIN', 'cl_from=page_id' ) )
 61+ );
5862 }
5963
6064 }
61 -
62 -function wfSpecialUncategorizedimages() {
63 - $uip = new UncategorizedImagesPage();
64 - list( $limit, $offset ) = wfCheckLimits();
65 - return $uip->doQuery( $offset, $limit );
66 -}
Index: trunk/phase3/includes/specials/SpecialTags.php
@@ -48,7 +48,8 @@
4949 Xml::tags( 'th', null, wfMsgExt( 'tags-hitcount-header', 'parseinline' ) )
5050 );
5151 $dbr = wfGetDB( DB_SLAVE );
52 - $res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) as hitcount' ), array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) );
 52+ $res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) AS hitcount' ),
 53+ array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) );
5354
5455 foreach ( $res as $row ) {
5556 $html .= $this->doTagRow( $row->ct_tag, $row->hitcount );
Index: trunk/phase3/includes/specials/SpecialAncientpages.php
@@ -28,8 +28,8 @@
2929 */
3030 class AncientPagesPage extends QueryPage {
3131
32 - function getName() {
33 - return "Ancientpages";
 32+ function __construct( $name = 'Ancientpages' ) {
 33+ parent::__construct( $name );
3434 }
3535
3636 function isExpensive() {
@@ -38,20 +38,20 @@
3939
4040 function isSyndicated() { return false; }
4141
42 - function getSQL() {
43 - $db = wfGetDB( DB_SLAVE );
44 - $page = $db->tableName( 'page' );
45 - $revision = $db->tableName( 'revision' );
46 - $epoch = $db->unixTimestamp( 'rev_timestamp' );
 42+ function getQueryInfo() {
 43+ return array(
 44+ 'tables' => array( 'page', 'revision' ),
 45+ 'fields' => array( 'page_namespace AS namespace',
 46+ 'page_title AS title',
 47+ 'rev_timestamp AS value' ),
 48+ 'conds' => array( 'page_namespace' => MWNamespace::getContentNamespaces(),
 49+ 'page_is_redirect' => 0,
 50+ 'page_latest=rev_id' )
 51+ );
 52+ }
4753
48 - return
49 - "SELECT 'Ancientpages' as type,
50 - page_namespace as namespace,
51 - page_title as title,
52 - $epoch as value
53 - FROM $page, $revision
54 - WHERE page_namespace=".NS_MAIN." AND page_is_redirect=0
55 - AND page_latest=rev_id";
 54+ function usesTimestamps() {
 55+ return true;
5656 }
5757
5858 function sortDescending() {
@@ -67,14 +67,6 @@
6868 $title,
6969 htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
7070 );
71 - return wfSpecialList($link, htmlspecialchars($d) );
 71+ return wfSpecialList( $link, htmlspecialchars($d) );
7272 }
7373 }
74 -
75 -function wfSpecialAncientpages() {
76 - list( $limit, $offset ) = wfCheckLimits();
77 -
78 - $app = new AncientPagesPage();
79 -
80 - $app->doQuery( $offset, $limit );
81 -}
Index: trunk/phase3/includes/specials/SpecialWantedpages.php
@@ -27,60 +27,53 @@
2828 * @ingroup SpecialPage
2929 */
3030 class WantedPagesPage extends WantedQueryPage {
31 - var $nlinks;
32 -
33 - function __construct( $inc = false, $nlinks = true ) {
34 - $this->setListoutput( $inc );
35 - $this->nlinks = $nlinks;
 31+ function __construct( $name = 'Wantedpages' ) {
 32+ parent::__construct( $name );
3633 }
 34+
 35+ function execute( $par ) {
 36+ $inc = $this->including();
3737
38 - function getName() {
39 - return 'Wantedpages';
 38+ if ( $inc ) {
 39+ @list( $limit, $nlinks ) = explode( '/', $par, 2 );
 40+ $this->limit = (int)$limit;
 41+ // FIXME: nlinks is ignored
 42+ $nlinks = $nlinks === 'nlinks';
 43+ $this->offset = 0;
 44+ } else {
 45+ $nlinks = true;
 46+ }
 47+ $this->setListOutput( $inc );
 48+ $this->shownavigation = !$inc;
 49+ parent::execute( $par );
4050 }
4151
42 - function getSQL() {
 52+ function getQueryInfo() {
4353 global $wgWantedPagesThreshold;
4454 $count = $wgWantedPagesThreshold - 1;
45 - $dbr = wfGetDB( DB_SLAVE );
46 - $pagelinks = $dbr->tableName( 'pagelinks' );
47 - $page = $dbr->tableName( 'page' );
48 - $sql = "SELECT 'Wantedpages' AS type,
49 - pl_namespace AS namespace,
50 - pl_title AS title,
51 - COUNT(*) AS value
52 - FROM $pagelinks
53 - LEFT JOIN $page AS pg1
54 - ON pl_namespace = pg1.page_namespace AND pl_title = pg1.page_title
55 - LEFT JOIN $page AS pg2
56 - ON pl_from = pg2.page_id
57 - WHERE pg1.page_namespace IS NULL
58 - AND pl_namespace NOT IN ( " . NS_USER . ", ". NS_USER_TALK . ")
59 - AND pg2.page_namespace != " . NS_MEDIAWIKI . "
60 - GROUP BY pl_namespace, pl_title
61 - HAVING COUNT(*) > $count";
62 -
63 - wfRunHooks( 'WantedPages::getSQL', array( &$this, &$sql ) );
64 - return $sql;
65 - }
 55+ $query = array (
 56+ 'tables' => array ( 'pagelinks', 'pg1' => 'page',
 57+ 'pg2' => 'page' ),
 58+ 'fields' => array ( 'pl_namespace AS namespace',
 59+ 'pl_title AS title',
 60+ 'COUNT(*) AS value' ),
 61+ 'conds' => array ( 'pg1.page_namespace IS NULL',
 62+ "pl_namespace NOT IN ( '" . NS_USER .
 63+ "', '" . NS_USER_TALK . "' )",
 64+ "pg2.page_namespace != '" .
 65+ NS_MEDIAWIKI . "'" ),
 66+ 'options' => array ( 'HAVING' => "COUNT(*) > $count",
 67+ 'GROUP BY' => 'pl_namespace, pl_title' ),
 68+ 'join_conds' => array ( 'page AS pg1' => array (
 69+ 'LEFT JOIN', array (
 70+ 'pg1.page_namespace = pl_namespace',
 71+ 'pg1.page_title = pl_title' ) ),
 72+ 'page AS pg2' => array ( 'LEFT JOIN',
 73+ 'pg2.page_id = pl_from' ) )
 74+ );
 75+ // Replacement WantedPages::getSQL
 76+ wfRunHooks( 'WantedPages::getQueryInfo',
 77+ array( &$this, &$query ) );
 78+ return $query;
 79+ }
6680 }
67 -
68 -/**
69 - * constructor
70 - */
71 -function wfSpecialWantedpages( $par = null, $specialPage ) {
72 - $inc = $specialPage->including();
73 -
74 - if ( $inc ) {
75 - @list( $limit, $nlinks ) = explode( '/', $par, 2 );
76 - $limit = (int)$limit;
77 - $nlinks = $nlinks === 'nlinks';
78 - $offset = 0;
79 - } else {
80 - list( $limit, $offset ) = wfCheckLimits();
81 - $nlinks = true;
82 - }
83 -
84 - $wpp = new WantedPagesPage( $inc, $nlinks );
85 -
86 - $wpp->doQuery( $offset, $limit, !$inc );
87 -}
Index: trunk/phase3/includes/specials/SpecialDeadendpages.php
@@ -28,8 +28,8 @@
2929 */
3030 class DeadendPagesPage extends PageQueryPage {
3131
32 - function getName( ) {
33 - return "Deadendpages";
 32+ function __construct( $name = 'Deadendpages' ) {
 33+ parent::__construct( $name );
3434 }
3535
3636 function getPageHeader() {
@@ -41,11 +41,13 @@
4242 *
4343 * @return true
4444 */
45 - function isExpensive( ) {
46 - return 1;
 45+ function isExpensive() {
 46+ return true;
4747 }
4848
49 - function isSyndicated() { return false; }
 49+ function isSyndicated() {
 50+ return false;
 51+ }
5052
5153 /**
5254 * @return false
@@ -54,28 +56,30 @@
5557 return false;
5658 }
5759
58 - /**
59 - * @return string an sqlquery
60 - */
61 - function getSQL() {
62 - $dbr = wfGetDB( DB_SLAVE );
63 - list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' );
64 - return "SELECT 'Deadendpages' as type, page_namespace AS namespace, page_title as title, page_title AS value " .
65 - "FROM $page LEFT JOIN $pagelinks ON page_id = pl_from " .
66 - "WHERE pl_from IS NULL " .
67 - "AND page_namespace = 0 " .
68 - "AND page_is_redirect = 0";
 60+ function getQueryInfo() {
 61+ return array(
 62+ 'tables' => array( 'page', 'pagelinks' ),
 63+ 'fields' => array( 'page_namespace AS namespace',
 64+ 'page_title AS title',
 65+ 'page_title AS value'
 66+ ),
 67+ 'conds' => array( 'pl_from IS NULL',
 68+ 'page_namespace' => MWNamespace::getContentNamespaces(),
 69+ 'page_is_redirect' => 0
 70+ ),
 71+ 'join_conds' => array( 'pagelinks' => array( 'LEFT JOIN', array(
 72+ 'page_id=pl_from'
 73+ ) ) )
 74+ );
6975 }
 76+
 77+ function getOrderFields() {
 78+ // For some crazy reason ordering by a constant
 79+ // causes a filesort
 80+ if( count( MWNamespace::getContentNamespaces() ) > 1 ) {
 81+ return array( 'page_namespace', 'page_title' );
 82+ } else {
 83+ return array( 'page_title' );
 84+ }
 85+ }
7086 }
71 -
72 -/**
73 - * Constructor
74 - */
75 -function wfSpecialDeadendpages() {
76 -
77 - list( $limit, $offset ) = wfCheckLimits();
78 -
79 - $depp = new DeadendPagesPage();
80 -
81 - return $depp->doQuery( $offset, $limit );
82 -}
Index: trunk/phase3/includes/specials/SpecialMostlinked.php
@@ -32,39 +32,27 @@
3333 */
3434 class MostlinkedPage extends QueryPage {
3535
36 - function getName() { return 'Mostlinked'; }
 36+ function __construct( $name = 'Mostlinked' ) {
 37+ parent::__construct( $name );
 38+ }
 39+
3740 function isExpensive() { return true; }
3841 function isSyndicated() { return false; }
3942
40 - function getSQL() {
41 - global $wgMiserMode;
42 -
43 - $dbr = wfGetDB( DB_SLAVE );
44 -
45 - # In miser mode, reduce the query cost by adding a threshold for large wikis
46 - if ( $wgMiserMode ) {
47 - $numPages = SiteStats::pages();
48 - if ( $numPages > 10000 ) {
49 - $cutoff = 100;
50 - } elseif ( $numPages > 100 ) {
51 - $cutoff = intval( sqrt( $numPages ) );
52 - } else {
53 - $cutoff = 1;
54 - }
55 - } else {
56 - $cutoff = 1;
57 - }
58 -
59 - list( $pagelinks, $page ) = $dbr->tableNamesN( 'pagelinks', 'page' );
60 - return
61 - "SELECT 'Mostlinked' AS type,
62 - pl_namespace AS namespace,
63 - pl_title AS title,
64 - COUNT(*) AS value
65 - FROM $pagelinks
66 - LEFT JOIN $page ON pl_namespace=page_namespace AND pl_title=page_title
67 - GROUP BY pl_namespace, pl_title
68 - HAVING COUNT(*) > $cutoff";
 43+ function getQueryInfo() {
 44+ return array (
 45+ 'tables' => array ( 'pagelinks', 'page' ),
 46+ 'fields' => array ( 'pl_namespace AS namespace',
 47+ 'pl_title AS title',
 48+ 'COUNT(*) AS value',
 49+ 'page_namespace' ),
 50+ 'options' => array ( 'HAVING' => 'COUNT(*) > 1',
 51+ 'GROUP BY' => 'pl_namespace, pl_title, '.
 52+ 'page_namespace' ),
 53+ 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
 54+ array ( 'page_namespace = pl_namespace',
 55+ 'page_title = pl_title' ) ) )
 56+ );
6957 }
7058
7159 /**
@@ -114,14 +102,3 @@
115103 return wfSpecialList( $link, $wlh );
116104 }
117105 }
118 -
119 -/**
120 - * constructor
121 - */
122 -function wfSpecialMostlinked() {
123 - list( $limit, $offset ) = wfCheckLimits();
124 -
125 - $wpp = new MostlinkedPage();
126 -
127 - $wpp->doQuery( $offset, $limit );
128 -}
Index: trunk/phase3/includes/specials/SpecialMIMEsearch.php
@@ -28,49 +28,60 @@
2929 * @ingroup SpecialPage
3030 */
3131 class MIMEsearchPage extends QueryPage {
32 - var $major, $minor;
 32+ protected $major, $minor;
3333
34 - function __construct( $major, $minor ) {
35 - $this->major = $major;
36 - $this->minor = $minor;
 34+ function __construct( $name = 'MIMEsearch' ) {
 35+ parent::__construct( $name );
3736 }
3837
39 - function getName() { return 'MIMEsearch'; }
40 -
41 - /**
42 - * Due to this page relying upon extra fields being passed in the SELECT it
43 - * will fail if it's set as expensive and misermode is on
44 - */
4538 function isExpensive() { return true; }
4639 function isSyndicated() { return false; }
 40+ function isCacheable() { return false; }
4741
4842 function linkParameters() {
49 - $arr = array( $this->major, $this->minor );
50 - $mime = implode( '/', $arr );
51 - return array( 'mime' => $mime );
 43+ return array( 'mime' => "{$this->major}/{$this->minor}" );
5244 }
 45+
 46+ public function getQueryInfo() {
 47+ return array(
 48+ 'tables' => array( 'image' ),
 49+ 'fields' => array( "'" . NS_FILE . "' AS namespace",
 50+ 'img_name AS title',
 51+ 'img_major_mime AS value',
 52+ 'img_size',
 53+ 'img_width',
 54+ 'img_height',
 55+ 'img_user_text',
 56+ 'img_timestamp' ),
 57+ 'conds' => array( 'img_major_mime' => $this->major,
 58+ 'img_minor_mime' => $this->minor )
 59+ );
 60+ }
 61+
 62+ function execute( $par ) {
 63+ global $wgRequest, $wgOut;
 64+ $mime = $par ? $par : $wgRequest->getText( 'mime' );
 65+
 66+ $this->setHeaders();
 67+ $this->outputHeader();
 68+ $wgOut->addHTML(
 69+ Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) .
 70+ Xml::openElement( 'fieldset' ) .
 71+ Html::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) .
 72+ Xml::element( 'legend', null, wfMsg( 'mimesearch' ) ) .
 73+ Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . ' ' .
 74+ Xml::submitButton( wfMsg( 'ilsubmit' ) ) .
 75+ Xml::closeElement( 'fieldset' ) .
 76+ Xml::closeElement( 'form' )
 77+ );
5378
54 - function getSQL() {
55 - $dbr = wfGetDB( DB_SLAVE );
56 - $image = $dbr->tableName( 'image' );
57 - $major = $dbr->addQuotes( $this->major );
58 - $minor = $dbr->addQuotes( $this->minor );
59 -
60 - return
61 - "SELECT 'MIMEsearch' AS type,
62 - " . NS_FILE . " AS namespace,
63 - img_name AS title,
64 - img_major_mime AS value,
65 -
66 - img_size,
67 - img_width,
68 - img_height,
69 - img_user_text,
70 - img_timestamp
71 - FROM $image
72 - WHERE img_major_mime = $major AND img_minor_mime = $minor
73 - ";
 79+ list( $this->major, $this->minor ) = self::parseMIME( $mime );
 80+ if ( $this->major == '' || $this->minor == '' || !self::isValidType( $this->major ) ) {
 81+ return;
 82+ }
 83+ parent::execute( $par );
7484 }
 85+
7586
7687 function formatResult( $skin, $result ) {
7788 global $wgContLang, $wgLang;
@@ -83,7 +94,7 @@
8495 );
8596
8697 $download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) );
87 - $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
 98+ $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
8899 $wgLang->formatNum( $result->img_size ) );
89100 $dimensions = htmlspecialchars( wfMsg( 'widthheight',
90101 $wgLang->formatNum( $result->img_width ),
@@ -94,63 +105,34 @@
95106
96107 return "($download) $plink . . $dimensions . . $bytes . . $user . . $time";
97108 }
98 -}
 109+
 110+ protected static function parseMIME( $str ) {
 111+ // searched for an invalid MIME type.
 112+ if( strpos( $str, '/' ) === false ) {
 113+ return array( '', '' );
 114+ }
99115
100 -/**
101 - * Output the HTML search form, and constructs the MIMEsearchPage object.
102 - */
103 -function wfSpecialMIMEsearch( $par = null ) {
104 - global $wgRequest, $wgOut;
 116+ list( $major, $minor ) = explode( '/', $str, 2 );
105117
106 - $mime = isset( $par ) ? $par : $wgRequest->getText( 'mime' );
107 -
108 - $wgOut->addHTML(
109 - Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) .
110 - Xml::openElement( 'fieldset' ) .
111 - Html::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) .
112 - Xml::element( 'legend', null, wfMsg( 'mimesearch' ) ) .
113 - Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . ' ' .
114 - Xml::submitButton( wfMsg( 'ilsubmit' ) ) .
115 - Xml::closeElement( 'fieldset' ) .
116 - Xml::closeElement( 'form' )
117 - );
118 -
119 - list( $major, $minor ) = wfSpecialMIMEsearchParse( $mime );
120 - if ( $major == '' or $minor == '' or !wfSpecialMIMEsearchValidType( $major ) )
121 - return;
122 - $wpp = new MIMEsearchPage( $major, $minor );
123 -
124 - list( $limit, $offset ) = wfCheckLimits();
125 - $wpp->doQuery( $offset, $limit );
126 -}
127 -
128 -function wfSpecialMIMEsearchParse( $str ) {
129 - // searched for an invalid MIME type.
130 - if( strpos( $str, '/' ) === false) {
131 - return array ('', '');
 118+ return array(
 119+ ltrim( $major, ' ' ),
 120+ rtrim( $minor, ' ' )
 121+ );
132122 }
133 -
134 - list( $major, $minor ) = explode( '/', $str, 2 );
135 -
136 - return array(
137 - ltrim( $major, ' ' ),
138 - rtrim( $minor, ' ' )
139 - );
 123+
 124+ protected static function isValidType( $type ) {
 125+ // From maintenance/tables.sql => img_major_mime
 126+ $types = array(
 127+ 'unknown',
 128+ 'application',
 129+ 'audio',
 130+ 'image',
 131+ 'text',
 132+ 'video',
 133+ 'message',
 134+ 'model',
 135+ 'multipart'
 136+ );
 137+ return in_array( $type, $types );
 138+ }
140139 }
141 -
142 -function wfSpecialMIMEsearchValidType( $type ) {
143 - // From maintenance/tables.sql => img_major_mime
144 - $types = array(
145 - 'unknown',
146 - 'application',
147 - 'audio',
148 - 'image',
149 - 'text',
150 - 'video',
151 - 'message',
152 - 'model',
153 - 'multipart'
154 - );
155 -
156 - return in_array( $type, $types );
157 -}
Index: trunk/phase3/includes/specials/SpecialMostlinkedcategories.php
@@ -31,24 +31,20 @@
3232 */
3333 class MostlinkedCategoriesPage extends QueryPage {
3434
35 - function getName() { return 'Mostlinkedcategories'; }
 35+ function __construct( $name = 'Mostlinkedcategories' ) {
 36+ parent::__construct( $name );
 37+ }
 38+
3639 function isExpensive() { return true; }
3740 function isSyndicated() { return false; }
3841
39 - function getSQL() {
40 - $dbr = wfGetDB( DB_SLAVE );
41 - $categorylinks = $dbr->tableName( 'categorylinks' );
42 - $name = $dbr->addQuotes( $this->getName() );
43 - return
44 - "
45 - SELECT
46 - $name as type,
47 - " . NS_CATEGORY . " as namespace,
48 - cl_to as title,
49 - COUNT(*) as value
50 - FROM $categorylinks
51 - GROUP BY cl_to
52 - ";
 42+ function getQueryInfo() {
 43+ return array (
 44+ 'tables' => array ( 'categorylinks' ),
 45+ 'fields' => array ( 'cl_to AS title',
 46+ 'COUNT(*) AS value' ),
 47+ 'options' => array ( 'GROUP BY' => 'cl_to' )
 48+ );
5349 }
5450
5551 function sortDescending() { return true; }
@@ -59,37 +55,27 @@
6056 function preprocessResults( $db, $res ) {
6157 $batch = new LinkBatch;
6258 foreach ( $res as $row ) {
63 - $batch->add( $row->namespace, $row->title );
 59+ $batch->add( NS_CATEGORY, $row->title );
6460 }
6561 $batch->execute();
6662
6763 // Back to start for display
68 - if ( $db->numRows( $res ) > 0 )
 64+ if ( $db->numRows( $res ) > 0 ) {
6965 // If there are no rows we get an error seeking.
7066 $db->dataSeek( $res, 0 );
 67+ }
7168 }
7269
7370 function formatResult( $skin, $result ) {
7471 global $wgLang, $wgContLang;
7572
76 - $nt = Title::makeTitle( $result->namespace, $result->title );
 73+ $nt = Title::makeTitle( NS_CATEGORY, $result->title );
7774 $text = $wgContLang->convert( $nt->getText() );
7875
7976 $plink = $skin->link( $nt, htmlspecialchars( $text ) );
8077
81 - $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'),
 78+ $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ),
8279 $wgLang->formatNum( $result->value ) );
83 - return wfSpecialList($plink, $nlinks);
 80+ return wfSpecialList( $plink, $nlinks );
8481 }
8582 }
86 -
87 -/**
88 - * constructor
89 - */
90 -function wfSpecialMostlinkedCategories() {
91 - list( $limit, $offset ) = wfCheckLimits();
92 -
93 - $wpp = new MostlinkedCategoriesPage();
94 -
95 - $wpp->doQuery( $offset, $limit );
96 -}
Index: trunk/phase3/includes/specials/SpecialDoubleRedirects.php
@@ -29,63 +29,63 @@
3030 */
3131 class DoubleRedirectsPage extends PageQueryPage {
3232
33 - function getName() {
34 - return 'DoubleRedirects';
 33+ function __construct( $name = 'DoubleRedirects' ) {
 34+ parent::__construct( $name );
3535 }
36 -
37 - function isExpensive( ) { return true; }
 36+
 37+ function isExpensive() { return true; }
3838 function isSyndicated() { return false; }
 39+ function sortDescending() { return false; }
3940
40 - function getPageHeader( ) {
 41+ function getPageHeader() {
4142 return wfMsgExt( 'doubleredirectstext', array( 'parse' ) );
4243 }
4344
44 - function getSQLText( &$dbr, $namespace = null, $title = null ) {
45 -
46 - list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' );
47 -
 45+ function reallyGetQueryInfo( $namespace = null, $title = null ) {
4846 $limitToTitle = !( $namespace === null && $title === null );
49 - $sql = $limitToTitle ? "SELECT" : "SELECT 'DoubleRedirects' as type," ;
50 - $sql .=
51 - " pa.page_namespace as namespace, pa.page_title as title," .
52 - " pb.page_namespace as nsb, pb.page_title as tb," .
53 - " pc.page_namespace as nsc, pc.page_title as tc" .
54 - " FROM $redirect AS ra, $redirect AS rb, $page AS pa, $page AS pb, $page AS pc" .
55 - " WHERE ra.rd_from=pa.page_id" .
56 - " AND ra.rd_namespace=pb.page_namespace" .
57 - " AND ra.rd_title=pb.page_title" .
58 - " AND rb.rd_from=pb.page_id" .
59 - " AND rb.rd_namespace=pc.page_namespace" .
60 - " AND rb.rd_title=pc.page_title";
61 -
62 - if( $limitToTitle ) {
63 - $encTitle = $dbr->addQuotes( $title );
64 - $sql .= " AND pa.page_namespace=$namespace" .
65 - " AND pa.page_title=$encTitle";
 47+ $retval = array (
 48+ 'tables' => array ( 'ra' => 'redirect',
 49+ 'rb' => 'redirect', 'pa' => 'page',
 50+ 'pb' => 'page', 'pc' => 'page' ),
 51+ 'fields' => array ( 'pa.page_namespace AS namespace',
 52+ 'pa.page_title AS title',
 53+ 'pb.page_namespace AS nsb',
 54+ 'pb.page_title AS tb',
 55+ 'pc.page_namespace AS nsc',
 56+ 'pc.page_title AS tc' ),
 57+ 'conds' => array ( 'ra.rd_from = pa.page_id',
 58+ 'pb.page_namespace = ra.rd_namespace',
 59+ 'pb.page_title = ra.rd_title',
 60+ 'rb.rd_from = pb.page_id',
 61+ 'pc.page_namespace = rb.rd_namespace',
 62+ 'pc.page_title = rb.rd_title' )
 63+ );
 64+ if ( $limitToTitle ) {
 65+ $retval['conds']['pa.page_namespace'] = $namespace;
 66+ $retval['conds']['pa.page_title'] = $title;
6667 }
67 -
68 - return $sql;
 68+ return $retval;
6969 }
7070
71 - function getSQL() {
72 - $dbr = wfGetDB( DB_SLAVE );
73 - return $this->getSQLText( $dbr );
 71+ function getQueryInfo() {
 72+ return $this->reallyGetQueryInfo();
7473 }
7574
76 - function getOrder() {
77 - return '';
 75+ function getOrderFields() {
 76+ return array ( 'ra.rd_namespace', 'ra.rd_title' );
7877 }
7978
8079 function formatResult( $skin, $result ) {
8180 global $wgContLang;
8281
83 - $fname = 'DoubleRedirectsPage::formatResult';
8482 $titleA = Title::makeTitle( $result->namespace, $result->title );
8583
8684 if ( $result && !isset( $result->nsb ) ) {
8785 $dbr = wfGetDB( DB_SLAVE );
88 - $sql = $this->getSQLText( $dbr, $result->namespace, $result->title );
89 - $res = $dbr->query( $sql, $fname );
 86+ $qi = $this->reallyGetQueryInfo( $result->namespace,
 87+ $result->title );
 88+ $res = $dbr->select($qi['tables'], $qi['fields'],
 89+ $qi['conds'], __METHOD__ );
9090 if ( $res ) {
9191 $result = $dbr->fetchObject( $res );
9292 }
@@ -124,15 +124,3 @@
125125 return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
126126 }
127127 }
128 -
129 -/**
130 - * constructor
131 - */
132 -function wfSpecialDoubleRedirects() {
133 - list( $limit, $offset ) = wfCheckLimits();
134 -
135 - $sdr = new DoubleRedirectsPage();
136 -
137 - return $sdr->doQuery( $offset, $limit );
138 -
139 -}
Index: trunk/phase3/includes/specials/SpecialListredirects.php
@@ -30,19 +30,35 @@
3131 */
3232 class ListredirectsPage extends QueryPage {
3333
34 - function getName() { return( 'Listredirects' ); }
35 - function isExpensive() { return( true ); }
36 - function isSyndicated() { return( false ); }
37 - function sortDescending() { return( false ); }
 34+ function __construct( $name = 'Listredirects' ) {
 35+ parent::__construct( $name );
 36+ }
 37+
 38+ function isExpensive() { return true; }
 39+ function isSyndicated() { return false; }
 40+ function sortDescending() { return false; }
3841
39 - function getSQL() {
40 - $dbr = wfGetDB( DB_SLAVE );
41 - $page = $dbr->tableName( 'page' );
42 - $sql = "SELECT 'Listredirects' AS type, page_title AS title, page_namespace AS namespace,
43 - 0 AS value FROM $page WHERE page_is_redirect = 1";
44 - return( $sql );
 42+ function getQueryInfo() {
 43+ return array(
 44+ 'tables' => array( 'p1' => 'page', 'redirect', 'p2' => 'page' ),
 45+ 'fields' => array( 'p1.page_namespace AS namespace',
 46+ 'p1.page_title AS title',
 47+ 'rd_namespace',
 48+ 'rd_title',
 49+ 'p2.page_id AS redirid' ),
 50+ 'conds' => array( 'p1.page_is_redirect' => 1 ),
 51+ 'join_conds' => array( 'redirect' => array(
 52+ 'LEFT JOIN', 'rd_from=p1.page_id' ),
 53+ 'p2' => array( 'LEFT JOIN', array(
 54+ 'p2.page_namespace=rd_namespace',
 55+ 'p2.page_title=rd_title' ) ) )
 56+ );
4557 }
4658
 59+ function getOrderFields() {
 60+ return array ( 'p1.page_namespace', 'p1.page_title' );
 61+ }
 62+
4763 function formatResult( $skin, $result ) {
4864 global $wgContLang;
4965
@@ -72,9 +88,3 @@
7389 }
7490 }
7591 }
76 -
77 -function wfSpecialListredirects() {
78 - list( $limit, $offset ) = wfCheckLimits();
79 - $lrp = new ListredirectsPage();
80 - $lrp->doQuery( $offset, $limit );
81 -}
Index: trunk/phase3/includes/specials/SpecialLinkSearch.php
@@ -25,69 +25,6 @@
2626
2727 /**
2828 * Special:LinkSearch to search the external-links table.
29 - */
30 -function wfSpecialLinkSearch( $par ) {
31 -
32 - list( $limit, $offset ) = wfCheckLimits();
33 - global $wgOut, $wgUrlProtocols, $wgMiserMode, $wgLang;
34 - $target = $GLOBALS['wgRequest']->getVal( 'target', $par );
35 - $namespace = $GLOBALS['wgRequest']->getIntorNull( 'namespace', null );
36 -
37 - $protocols_list[] = '';
38 - foreach( $wgUrlProtocols as $prot ) {
39 - $protocols_list[] = $prot;
40 - }
41 -
42 - $target2 = $target;
43 - $protocol = '';
44 - $pr_sl = strpos($target2, '//' );
45 - $pr_cl = strpos($target2, ':' );
46 - if ( $pr_sl ) {
47 - // For protocols with '//'
48 - $protocol = substr( $target2, 0 , $pr_sl+2 );
49 - $target2 = substr( $target2, $pr_sl+2 );
50 - } elseif ( !$pr_sl && $pr_cl ) {
51 - // For protocols without '//' like 'mailto:'
52 - $protocol = substr( $target2, 0 , $pr_cl+1 );
53 - $target2 = substr( $target2, $pr_cl+1 );
54 - } elseif ( $protocol == '' && $target2 != '' ) {
55 - // default
56 - $protocol = 'http://';
57 - }
58 - if ( !in_array( $protocol, $protocols_list ) ) {
59 - // unsupported protocol, show original search request
60 - $target2 = $target;
61 - $protocol = '';
62 - }
63 -
64 - $self = Title::makeTitle( NS_SPECIAL, 'Linksearch' );
65 -
66 - $wgOut->addWikiMsg( 'linksearch-text', '<nowiki>' . $wgLang->commaList( $wgUrlProtocols ) . '</nowiki>' );
67 - $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) .
68 - Html::hidden( 'title', $self->getPrefixedDbKey() ) .
69 - '<fieldset>' .
70 - Xml::element( 'legend', array(), wfMsg( 'linksearch' ) ) .
71 - Xml::inputLabel( wfMsg( 'linksearch-pat' ), 'target', 'target', 50, $target ) . ' ';
72 - if ( !$wgMiserMode ) {
73 - $s .= Xml::label( wfMsg( 'linksearch-ns' ), 'namespace' ) . ' ' .
74 - Xml::namespaceSelector( $namespace, '' );
75 - }
76 - $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) .
77 - '</fieldset>' .
78 - Xml::closeElement( 'form' );
79 - $wgOut->addHTML( $s );
80 -
81 - if( $target != '' ) {
82 - $searcher = new LinkSearchPage;
83 - $searcher->setParams( array(
84 - 'query' => $target2,
85 - 'namespace' => $namespace,
86 - 'protocol' => $protocol ) );
87 - $searcher->doQuery( $offset, $limit );
88 - }
89 -}
90 -
91 -/**
9229 * @ingroup SpecialPage
9330 */
9431 class LinkSearchPage extends QueryPage {
@@ -97,10 +34,70 @@
9835 $this->mProt = $params['protocol'];
9936 }
10037
101 - function getName() {
102 - return 'LinkSearch';
 38+ function __construct( $name = 'LinkSearch' ) {
 39+ parent::__construct( $name );
10340 }
 41+
 42+ function execute( $par ) {
 43+ global $wgOut, $wgRequest, $wgUrlProtocols, $wgMiserMode, $wgLang, $wgScript;
 44+ $target = $wgRequest->getVal( 'target', $par );
 45+ $namespace = $wgRequest->getIntorNull( 'namespace', null );
10446
 47+ $protocols_list[] = '';
 48+ foreach( $wgUrlProtocols as $prot ) {
 49+ $protocols_list[] = $prot;
 50+ }
 51+
 52+ $target2 = $target;
 53+ $protocol = '';
 54+ $pr_sl = strpos($target2, '//' );
 55+ $pr_cl = strpos($target2, ':' );
 56+ if ( $pr_sl ) {
 57+ // For protocols with '//'
 58+ $protocol = substr( $target2, 0 , $pr_sl+2 );
 59+ $target2 = substr( $target2, $pr_sl+2 );
 60+ } elseif ( !$pr_sl && $pr_cl ) {
 61+ // For protocols without '//' like 'mailto:'
 62+ $protocol = substr( $target2, 0 , $pr_cl+1 );
 63+ $target2 = substr( $target2, $pr_cl+1 );
 64+ } elseif ( $protocol == '' && $target2 != '' ) {
 65+ // default
 66+ $protocol = 'http://';
 67+ }
 68+ if ( !in_array( $protocol, $protocols_list ) ) {
 69+ // unsupported protocol, show original search request
 70+ $target2 = $target;
 71+ $protocol = '';
 72+ }
 73+
 74+ $self = $this->getTitle();
 75+
 76+ $wgOut->addWikiMsg( 'linksearch-text', '<nowiki>' . $wgLang->commaList( $wgUrlProtocols ) . '</nowiki>' );
 77+ $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) .
 78+ Html::hidden( 'title', $self->getPrefixedDbKey() ) .
 79+ '<fieldset>' .
 80+ Xml::element( 'legend', array(), wfMsg( 'linksearch' ) ) .
 81+ Xml::inputLabel( wfMsg( 'linksearch-pat' ), 'target', 'target', 50, $target ) . ' ';
 82+ if ( !$wgMiserMode ) {
 83+ $s .= Xml::label( wfMsg( 'linksearch-ns' ), 'namespace' ) . ' ' .
 84+ Xml::namespaceSelector( $namespace, '' );
 85+ }
 86+ $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) .
 87+ '</fieldset>' .
 88+ Xml::closeElement( 'form' );
 89+ $wgOut->addHTML( $s );
 90+
 91+ if( $target != '' ) {
 92+ $this->setParams( array(
 93+ 'query' => $target2,
 94+ 'namespace' => $namespace,
 95+ 'protocol' => $protocol ) );
 96+ parent::execute( $par );
 97+ if( $this->mMungedQuery === false )
 98+ $wgOut->addWikiText( wfMsg( 'linksearch-error' ) );
 99+ }
 100+ }
 101+
105102 /**
106103 * Disable RSS/Atom feeds
107104 */
@@ -113,11 +110,11 @@
114111 */
115112 static function mungeQuery( $query , $prot ) {
116113 $field = 'el_index';
117 - $rv = LinkFilter::makeLikeArray( $query , $prot );
118 - if ($rv === false) {
 114+ $rv = LinkFilter::makeLike( $query , $prot );
 115+ if ( $rv === false ) {
119116 // LinkFilter doesn't handle wildcard in IP, so we'll have to munge here.
120117 if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) {
121 - $rv = array( $prot . rtrim($query, " \t*"), $dbr->anyString() );
 118+ $rv = $prot . rtrim($query, " \t*") . '%';
122119 $field = 'el_to';
123120 }
124121 }
@@ -134,35 +131,32 @@
135132 return $params;
136133 }
137134
138 - function getSQL() {
 135+ function getQueryInfo() {
139136 global $wgMiserMode;
140137 $dbr = wfGetDB( DB_SLAVE );
141 - $page = $dbr->tableName( 'page' );
142 - $externallinks = $dbr->tableName( 'externallinks' );
143 -
144 - /* strip everything past first wildcard, so that index-based-only lookup would be done */
145 - list( $munged, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
146 - $stripped = LinkFilter::keepOneWildcard( $munged );
147 - $like = $dbr->buildLike( $stripped );
148 -
149 - $encSQL = '';
150 - if ( isset ($this->mNs) && !$wgMiserMode )
151 - $encSQL = 'AND page_namespace=' . $dbr->addQuotes( $this->mNs );
152 -
153 - $use_index = $dbr->useIndexClause( $clause );
154 - return
155 - "SELECT
156 - page_namespace AS namespace,
157 - page_title AS title,
158 - el_index AS value,
159 - el_to AS url
160 - FROM
161 - $page,
162 - $externallinks $use_index
163 - WHERE
164 - page_id=el_from
165 - AND $clause $like
166 - $encSQL";
 138+ // strip everything past first wildcard, so that
 139+ // index-based-only lookup would be done
 140+ list( $this->mMungedQuery, $clause ) = self::mungeQuery(
 141+ $this->mQuery, $this->mProt );
 142+ if( $this->mMungedQuery === false )
 143+ // Invalid query; return no results
 144+ return array( 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' );
 145+
 146+ $stripped = substr( $this->mMungedQuery, 0, strpos( $this->mMungedQuery, '%' ) + 1 );
 147+ $encSearch = $dbr->addQuotes( $stripped );
 148+ $retval = array (
 149+ 'tables' => array ( 'page', 'externallinks' ),
 150+ 'fields' => array ( 'page_namespace AS namespace',
 151+ 'page_title AS title',
 152+ 'el_index AS value', 'el_to AS url' ),
 153+ 'conds' => array ( 'page_id = el_from',
 154+ "$clause LIKE $encSearch" ),
 155+ 'options' => array( 'USE INDEX' => $clause )
 156+ );
 157+ if ( isset( $this->mNs ) && !$wgMiserMode ) {
 158+ $retval['conds']['page_namespace'] = $this->mNs;
 159+ }
 160+ return $retval;
167161 }
168162
169163 function formatResult( $skin, $result ) {
@@ -196,7 +190,7 @@
197191 * it as good enough for optimizing sort. The implicit ordering
198192 * from the scan will usually do well enough for our needs.
199193 */
200 - function getOrder() {
201 - return '';
 194+ function getOrderFields() {
 195+ return array();
202196 }
203197 }
Index: trunk/phase3/includes/QueryPage.php
@@ -15,20 +15,22 @@
1616 global $wgQueryPages; // not redundant
1717 $wgQueryPages = array(
1818 // QueryPage subclass Special page name Limit (false for none, none for the default)
19 -//----------------------------------------------------------------------------
 19+// ----------------------------------------------------------------------------
2020 array( 'AncientPagesPage', 'Ancientpages' ),
2121 array( 'BrokenRedirectsPage', 'BrokenRedirects' ),
2222 array( 'DeadendPagesPage', 'Deadendpages' ),
2323 array( 'DisambiguationsPage', 'Disambiguations' ),
2424 array( 'DoubleRedirectsPage', 'DoubleRedirects' ),
 25+ array( 'FileDuplicateSearchPage', 'FileDuplicateSearch' ),
2526 array( 'LinkSearchPage', 'LinkSearch' ),
2627 array( 'ListredirectsPage', 'Listredirects' ),
2728 array( 'LonelyPagesPage', 'Lonelypages' ),
2829 array( 'LongPagesPage', 'Longpages' ),
 30+ array( 'MIMEsearchPage', 'MIMEsearch' ),
2931 array( 'MostcategoriesPage', 'Mostcategories' ),
3032 array( 'MostimagesPage', 'Mostimages' ),
3133 array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ),
32 - array( 'SpecialMostlinkedtemplates', 'Mostlinkedtemplates' ),
 34+ array( 'MostlinkedtemplatesPage', 'Mostlinkedtemplates' ),
3335 array( 'MostlinkedPage', 'Mostlinked' ),
3436 array( 'MostrevisionsPage', 'Mostrevisions' ),
3537 array( 'FewestrevisionsPage', 'Fewestrevisions' ),
@@ -51,7 +53,7 @@
5254
5355 global $wgDisableCounters;
5456 if ( !$wgDisableCounters )
55 - $wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' );
 57+ $wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' );
5658
5759
5860 /**
@@ -60,7 +62,7 @@
6163 * subclasses derive from it.
6264 * @ingroup SpecialPage
6365 */
64 -class QueryPage {
 66+abstract class QueryPage extends SpecialPage {
6567 /**
6668 * Whether or not we want plain listoutput rather than an ordered list
6769 *
@@ -77,6 +79,18 @@
7880 var $limit = 0;
7981
8082 /**
 83+ * The number of rows returned by the query. Reading this variable
 84+ * only makes sense in functions that are run after the query has been
 85+ * done, such as preprocessResults() and formatRow().
 86+ */
 87+ protected $numRows;
 88+
 89+ /**
 90+ * Wheter to show prev/next links
 91+ */
 92+ var $shownavigation = true;
 93+
 94+ /**
8195 * A mutator for $this->listoutput;
8296 *
8397 * @param $bool Boolean
@@ -86,17 +100,6 @@
87101 }
88102
89103 /**
90 - * Subclasses return their name here. Make sure the name is also
91 - * specified in SpecialPage.php and in Language.php as a language message
92 - * param.
93 - *
94 - * @return String
95 - */
96 - function getName() {
97 - return '';
98 - }
99 -
100 - /**
101104 * Return title object representing this page
102105 *
103106 * @return Title
@@ -106,25 +109,71 @@
107110 }
108111
109112 /**
110 - * Subclasses return an SQL query here.
 113+ * Subclasses return an SQL query here, formatted as an array with the
 114+ * following keys:
 115+ * tables => Table(s) for passing to Database::select()
 116+ * fields => Field(s) for passing to Database::select(), may be *
 117+ * conds => WHERE conditions
 118+ * options => options
 119+ * join_conds => JOIN conditions
111120 *
112 - * Note that the query itself should return the following four columns:
113 - * 'type' (your special page's name), 'namespace', 'title', and 'value'
 121+ * Note that the query itself should return the following three columns:
 122+ * 'namespace', 'title', and 'value'
114123 * *in that order*. 'value' is used for sorting.
115124 *
116125 * These may be stored in the querycache table for expensive queries,
117126 * and that cached data will be returned sometimes, so the presence of
118127 * extra fields can't be relied upon. The cached 'value' column will be
119 - * an integer; non-numeric values are useful only for sorting the initial
120 - * query.
 128+ * an integer; non-numeric values are useful only for sorting the
 129+ * initial query (except if they're timestamps, see usesTimestamps()).
121130 *
122 - * Don't include an ORDER or LIMIT clause, this will be added.
 131+ * Don't include an ORDER or LIMIT clause, they will be added.
 132+ *
 133+ * If this function is not overridden or returns something other than
 134+ * an array, getSQL() will be used instead. This is for backwards
 135+ * compatibility only and is strongly deprecated.
 136+ * @return array
 137+ * @since 1.18
123138 */
 139+ function getQueryInfo() {
 140+ return null;
 141+ }
 142+
 143+ /**
 144+ * For back-compat, subclasses may return a raw SQL query here, as a string.
 145+ * This is stronly deprecated; getQueryInfo() should be overridden instead.
 146+ * @return string
 147+ * @deprecated since 1.18
 148+ */
124149 function getSQL() {
125 - return "SELECT 'sample' as type, 0 as namespace, 'Sample result' as title, 42 as value";
 150+ throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor getQuery() properly" );
126151 }
127152
128153 /**
 154+ * Subclasses return an array of fields to order by here. Don't append
 155+ * DESC to the field names, that'll be done automatically if
 156+ * sortDescending() returns true.
 157+ * @return array
 158+ * @since 1.18
 159+ */
 160+ function getOrderFields() {
 161+ return array( 'value' );
 162+ }
 163+
 164+ /**
 165+ * Does this query return timestamps rather than integers in its
 166+ * 'value' field? If true, this class will convert 'value' to a
 167+ * UNIX timestamp for caching.
 168+ * NOTE: formatRow() may get timestamps in TS_MW (mysql), TS_DB (pgsql)
 169+ * or TS_UNIX (querycache) format, so be sure to always run them
 170+ * through wfTimestamp()
 171+ * @return bool
 172+ */
 173+ function usesTimestamps() {
 174+ return false;
 175+ }
 176+
 177+ /**
129178 * Override to sort by increasing values
130179 *
131180 * @return Boolean
@@ -133,11 +182,6 @@
134183 return true;
135184 }
136185
137 - function getOrder() {
138 - return ' ORDER BY value ' .
139 - ($this->sortDescending() ? 'DESC' : '');
140 - }
141 -
142186 /**
143187 * Is this query expensive (for some definition of expensive)? Then we
144188 * don't let it run in miser mode. $wgDisableQueryPages causes all query
@@ -151,7 +195,17 @@
152196 }
153197
154198 /**
155 - * Whether or not the output of the page in question is retrived from
 199+ * Is the output of this query cacheable? Non-cacheable expensive pages
 200+ * will be disabled in miser mode and will not have their results written
 201+ * to the querycache table.
 202+ * @return Boolean
 203+ */
 204+ public function isCacheable() {
 205+ return true;
 206+ }
 207+
 208+ /**
 209+ * Whether or not the output of the page in question is retrieved from
156210 * the database cache.
157211 *
158212 * @return Boolean
@@ -175,14 +229,15 @@
176230 * Formats the results of the query for display. The skin is the current
177231 * skin; you can use it for making links. The result is a single row of
178232 * result data. You should be able to grab SQL results off of it.
179 - * If the function return "false", the line output will be skipped.
 233+ * If the function returns false, the line output will be skipped.
 234+ * @param $skin Skin
 235+ * @param $result object Result row
 236+ * @return mixed String or false to skip
180237 *
181238 * @param $skin Skin object
182239 * @param $result Object: database row
183240 */
184 - function formatResult( $skin, $result ) {
185 - return '';
186 - }
 241+ abstract function formatResult( $skin, $result );
187242
188243 /**
189244 * The content returned by this function will be output before any result
@@ -207,8 +262,9 @@
208263 /**
209264 * Some special pages (for example SpecialListusers) might not return the
210265 * current object formatted, but return the previous one instead.
211 - * Setting this to return true, will call one more time wfFormatResult to
212 - * be sure that the very last result is formatted and shown.
 266+ * Setting this to return true will ensure formatResult() is called
 267+ * one more time to make sure that the very last result is formatted
 268+ * as well.
213269 */
214270 function tryLastResult() {
215271 return false;
@@ -224,7 +280,7 @@
225281 $fname = get_class( $this ) . '::recache';
226282 $dbw = wfGetDB( DB_MASTER );
227283 $dbr = wfGetDB( DB_SLAVE, array( $this->getName(), __METHOD__, 'vslow' ) );
228 - if ( !$dbw || !$dbr ) {
 284+ if ( !$dbw || !$dbr || !$this->isCacheable() ) {
229285 return false;
230286 }
231287
@@ -236,10 +292,7 @@
237293 # Clear out any old cached data
238294 $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname );
239295 # Do query
240 - $sql = $this->getSQL() . $this->getOrder();
241 - if ( $limit !== false )
242 - $sql = $dbr->limitResult( $sql, $limit, 0 );
243 - $res = $dbr->query( $sql, $fname );
 296+ $res = $this->reallyDoQuery( $limit, false );
244297 $num = false;
245298 if ( $res ) {
246299 $num = $dbr->numRows( $res );
@@ -247,22 +300,27 @@
248301 $vals = array();
249302 while ( $res && $row = $dbr->fetchObject( $res ) ) {
250303 if ( isset( $row->value ) ) {
251 - $value = intval( $row->value ); // @bug 14414
 304+ if ( $this->usesTimestamps() ) {
 305+ $value = wfTimestamp( TS_UNIX,
 306+ $row->value );
 307+ } else {
 308+ $value = intval( $row->value ); // @bug 14414
 309+ }
252310 } else {
253311 $value = 0;
254312 }
255 -
256 - $vals[] = array('qc_type' => $row->type,
 313+
 314+ $vals[] = array( 'qc_type' => $this->getName(),
257315 'qc_namespace' => $row->namespace,
258316 'qc_title' => $row->title,
259 - 'qc_value' => $value);
 317+ 'qc_value' => $value );
260318 }
261319
262320 # Save results into the querycache table on the master
263321 if ( count( $vals ) ) {
264322 if ( !$dbw->insert( 'querycache', $vals, __METHOD__ ) ) {
265323 // Set result to false to indicate error
266 - $res = false;
 324+ $num = false;
267325 }
268326 }
269327 if ( $ignoreErrors ) {
@@ -279,42 +337,124 @@
280338 }
281339
282340 /**
 341+ * Run the query and return the result
 342+ * @param $limit mixed Numerical limit or false for no limit
 343+ * @param $offset mixed Numerical offset or false for no offset
 344+ * @return ResultWrapper
 345+ */
 346+ function reallyDoQuery( $limit, $offset = false ) {
 347+ $fname = get_class( $this ) . "::reallyDoQuery";
 348+ $query = $this->getQueryInfo();
 349+ $order = $this->getOrderFields();
 350+ if ( $this->sortDescending() ) {
 351+ foreach ( $order as &$field ) {
 352+ $field .= ' DESC';
 353+ }
 354+ }
 355+ if ( is_array( $query ) ) {
 356+ $tables = isset( $query['tables'] ) ? (array)$query['tables'] : array();
 357+ $fields = isset( $query['fields'] ) ? (array)$query['fields'] : array();
 358+ $conds = isset( $query['conds'] ) ? (array)$query['conds'] : array();
 359+ $options = isset( $query['options'] ) ? (array)$query['options'] : array();
 360+ $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : array();
 361+ if ( count( $order ) ) {
 362+ $options['ORDER BY'] = implode( ', ', $order );
 363+ }
 364+ if ( $limit !== false ) {
 365+ $options['LIMIT'] = intval( $limit );
 366+ }
 367+ if ( $offset !== false ) {
 368+ $options['OFFSET'] = intval( $offset );
 369+ }
 370+
 371+ $dbr = wfGetDB( DB_SLAVE );
 372+ $res = $dbr->select( $tables, $fields, $conds, $fname,
 373+ $options, $join_conds
 374+ );
 375+ } else {
 376+ // Old-fashioned raw SQL style, deprecated
 377+ $sql = $this->getSQL();
 378+ $sql .= ' ORDER BY ' . implode( ', ', $order );
 379+ $sql = $dbr->limitResult( $sql, $limit, $offset );
 380+ $res = $dbr->query( $sql );
 381+ }
 382+ return $dbr->resultObject( $res );
 383+ }
 384+
 385+ function doQuery( $limit, $offset = false ) {
 386+ if ( $this->isCached() && $this->isCacheable() ) {
 387+ return $this->fetchFromCache( $limit, $offset );
 388+ } else {
 389+ return $this->reallyDoQuery( $limit, $offset );
 390+ }
 391+ }
 392+
 393+ /**
 394+ * Fetch the query results from the query cache
 395+ * @param $limit mixed Numerical limit or false for no limit
 396+ * @param $offset mixed Numerical offset or false for no offset
 397+ * @return ResultWrapper
 398+ */
 399+ function fetchFromCache( $limit, $offset = false ) {
 400+ $dbr = wfGetDB( DB_SLAVE );
 401+ $options = array ();
 402+ if ( $limit !== false ) {
 403+ $options['LIMIT'] = intval( $limit );
 404+ }
 405+ if ( $offset !== false ) {
 406+ $options['OFFSET'] = intval( $offset );
 407+ }
 408+ $res = $dbr->select( 'querycache', array( 'qc_type',
 409+ 'qc_namespace AS namespace',
 410+ 'qc_title AS title',
 411+ 'qc_value AS value' ),
 412+ array( 'qc_type' => $this->getName() ),
 413+ __METHOD__, $options
 414+ );
 415+ return $dbr->resultObject( $res );
 416+ }
 417+
 418+ /**
283419 * This is the actual workhorse. It does everything needed to make a
284420 * real, honest-to-gosh query page.
285 - *
286 - * @param $offset database query offset
287 - * @param $limit database query limit
288 - * @param $shownavigation show navigation like "next 200"?
289421 */
290 - function doQuery( $offset, $limit, $shownavigation=true ) {
291 - global $wgUser, $wgOut, $wgLang, $wgContLang;
 422+ function execute( $par ) {
 423+ global $wgUser, $wgOut, $wgLang;
292424
293 - $this->offset = $offset;
294 - $this->limit = $limit;
 425+ if ( !$this->userCanExecute( $wgUser ) ) {
 426+ $this->displayRestrictionError();
 427+ return;
 428+ }
295429
 430+ if ( $this->limit == 0 && $this->offset == 0 )
 431+ list( $this->limit, $this->offset ) = wfCheckLimits();
296432 $sname = $this->getName();
297 - $fname = get_class($this) . '::doQuery';
 433+ $fname = get_class( $this ) . '::doQuery';
298434 $dbr = wfGetDB( DB_SLAVE );
299435
 436+ $this->setHeaders();
300437 $wgOut->setSyndicated( $this->isSyndicated() );
301438
 439+ if ( $this->isCached() && !$this->isCacheable() ) {
 440+ $wgOut->setSyndicated( false );
 441+ $wgOut->addWikiMsg( 'querypage-disabled' );
 442+ return 0;
 443+ }
 444+
 445+ // TODO: Use doQuery()
 446+ // $res = null;
302447 if ( !$this->isCached() ) {
303 - $sql = $this->getSQL();
 448+ $res = $this->reallyDoQuery( $this->limit, $this->offset );
304449 } else {
305450 # Get the cached result
306 - $querycache = $dbr->tableName( 'querycache' );
307 - $type = $dbr->strencode( $sname );
308 - $sql =
309 - "SELECT qc_type as type, qc_namespace as namespace,qc_title as title, qc_value as value
310 - FROM $querycache WHERE qc_type='$type'";
 451+ $res = $this->fetchFromCache( $this->limit, $this->offset );
 452+ if ( !$this->listoutput ) {
311453
312 - if( !$this->listoutput ) {
313 -
314454 # Fetch the timestamp of this update
315 - $tRes = $dbr->select( 'querycache_info', array( 'qci_timestamp' ), array( 'qci_type' => $type ), $fname );
 455+ $tRes = $dbr->select( 'querycache_info', array( 'qci_timestamp' ), array( 'qci_type' => $sname ), $fname );
316456 $tRow = $dbr->fetchObject( $tRes );
317457
318 - if( $tRow ) {
 458+ if ( $tRow ) {
319459 $updated = $wgLang->timeanddate( $tRow->qci_timestamp, true, true );
320460 $updateddate = $wgLang->date( $tRow->qci_timestamp, true, true );
321461 $updatedtime = $wgLang->time( $tRow->qci_timestamp, true, true );
@@ -328,7 +468,7 @@
329469 # If updates on this page have been disabled, let the user know
330470 # that the data set won't be refreshed for now
331471 global $wgDisableQueryPageUpdate;
332 - if( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) {
 472+ if ( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) {
333473 $wgOut->addWikiMsg( 'querypage-no-updates' );
334474 }
335475
@@ -336,23 +476,21 @@
337477
338478 }
339479
340 - $sql .= $this->getOrder();
341 - $sql = $dbr->limitResult($sql, $limit, $offset);
342 - $res = $dbr->query( $sql );
343 - $num = $dbr->numRows($res);
 480+ $this->numRows = $dbr->numRows( $res );
344481
345482 $this->preprocessResults( $dbr, $res );
346483
347 - $wgOut->addHTML( Xml::openElement( 'div', array('class' => 'mw-spcontent') ) );
 484+ $wgOut->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) );
348485
349486 # Top header and navigation
350 - if( $shownavigation ) {
 487+ if ( $this->shownavigation ) {
351488 $wgOut->addHTML( $this->getPageHeader() );
352 - if( $num > 0 ) {
353 - $wgOut->addHTML( '<p>' . wfShowingResults( $offset, $num ) . '</p>' );
 489+ if ( $this->numRows > 0 ) {
 490+ $wgOut->addHTML( '<p>' . wfShowingResults( $this->offset, $this->numRows ) . '</p>' );
354491 # Disable the "next" link when we reach the end
355 - $paging = wfViewPrevNext( $offset, $limit, $wgContLang->specialPage( $sname ),
356 - wfArrayToCGI( $this->linkParameters() ), ( $num < $limit ) );
 492+ $paging = wfViewPrevNext( $this->offset, $this->limit,
 493+ $this->getTitle( $par ),
 494+ wfArrayToCGI( $this->linkParameters() ), ( $this->numRows < $this->limit ) );
357495 $wgOut->addHTML( '<p>' . $paging . '</p>' );
358496 } else {
359497 # No results to show, so don't bother with "showing X of Y" etc.
@@ -370,17 +508,17 @@
371509 $wgUser->getSkin(),
372510 $dbr, # Should use a ResultWrapper for this
373511 $res,
374 - $dbr->numRows( $res ),
375 - $offset );
 512+ $this->numRows,
 513+ $this->offset );
376514
377515 # Repeat the paging links at the bottom
378 - if( $shownavigation ) {
 516+ if ( $this->shownavigation ) {
379517 $wgOut->addHTML( '<p>' . $paging . '</p>' );
380518 }
381519
382520 $wgOut->addHTML( Xml::closeElement( 'div' ) );
383521
384 - return $num;
 522+ return $this->numRows;
385523 }
386524
387525 /**
@@ -397,16 +535,16 @@
398536 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
399537 global $wgContLang;
400538
401 - if( $num > 0 ) {
 539+ if ( $num > 0 ) {
402540 $html = array();
403 - if( !$this->listoutput )
 541+ if ( !$this->listoutput )
404542 $html[] = $this->openList( $offset );
405543
406544 # $res might contain the whole 1,000 rows, so we read up to
407545 # $num [should update this to use a Pager]
408 - for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
 546+ for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
409547 $line = $this->formatResult( $skin, $row );
410 - if( $line ) {
 548+ if ( $line ) {
411549 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
412550 ? ' class="not-patrolled"'
413551 : '';
@@ -417,10 +555,10 @@
418556 }
419557
420558 # Flush the final result
421 - if( $this->tryLastResult() ) {
 559+ if ( $this->tryLastResult() ) {
422560 $row = null;
423561 $line = $this->formatResult( $skin, $row );
424 - if( $line ) {
 562+ if ( $line ) {
425563 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
426564 ? ' class="not-patrolled"'
427565 : '';
@@ -430,7 +568,7 @@
431569 }
432570 }
433571
434 - if( !$this->listoutput )
 572+ if ( !$this->listoutput )
435573 $html[] = $this->closeList();
436574
437575 $html = $this->listoutput
@@ -465,13 +603,13 @@
466604 $wgOut->addWikiMsg( 'feed-unavailable' );
467605 return;
468606 }
469 -
 607+
470608 global $wgFeedLimit;
471 - if( $limit > $wgFeedLimit ) {
 609+ if ( $limit > $wgFeedLimit ) {
472610 $limit = $wgFeedLimit;
473611 }
474612
475 - if( isset($wgFeedClasses[$class]) ) {
 613+ if ( isset( $wgFeedClasses[$class] ) ) {
476614 $feed = new $wgFeedClasses[$class](
477615 $this->feedTitle(),
478616 $this->feedDesc(),
@@ -479,12 +617,10 @@
480618 $feed->outHeader();
481619
482620 $dbr = wfGetDB( DB_SLAVE );
483 - $sql = $this->getSQL() . $this->getOrder();
484 - $sql = $dbr->limitResult( $sql, $limit, 0 );
485 - $res = $dbr->query( $sql, 'QueryPage::doFeed' );
 621+ $res = $this->reallyDoQuery( $limit, 0 );
486622 foreach ( $res as $obj ) {
487623 $item = $this->feedResult( $obj );
488 - if( $item ) {
 624+ if ( $item ) {
489625 $feed->outItem( $item );
490626 }
491627 }
@@ -501,14 +637,14 @@
502638 * feedItemDesc()
503639 */
504640 function feedResult( $row ) {
505 - if( !isset( $row->title ) ) {
 641+ if ( !isset( $row->title ) ) {
506642 return null;
507643 }
508644 $title = Title::MakeTitle( intval( $row->namespace ), $row->title );
509 - if( $title ) {
 645+ if ( $title ) {
510646 $date = isset( $row->timestamp ) ? $row->timestamp : '';
511647 $comments = '';
512 - if( $title ) {
 648+ if ( $title ) {
513649 $talkpage = $title->getTalkPage();
514650 $comments = $talkpage->getFullURL();
515651 }
@@ -519,7 +655,7 @@
520656 $title->getFullURL(),
521657 $date,
522658 $this->feedItemAuthor( $row ),
523 - $comments);
 659+ $comments );
524660 } else {
525661 return null;
526662 }
@@ -579,7 +715,7 @@
580716 // If there are no rows we get an error seeking.
581717 $db->dataSeek( $res, 0 );
582718 }
583 -
 719+
584720 /**
585721 * Should formatResult() always check page existence, even if
586722 * the results are fresh? This is a (hopefully temporary)
@@ -600,8 +736,8 @@
601737 */
602738 public function formatResult( $skin, $result ) {
603739 $title = Title::makeTitleSafe( $result->namespace, $result->title );
604 - if( $title instanceof Title ) {
605 - if( $this->isCached() || $this->forceExistenceCheck() ) {
 740+ if ( $title instanceof Title ) {
 741+ if ( $this->isCached() || $this->forceExistenceCheck() ) {
606742 $pageLink = $title->isKnown()
607743 ? '<del>' . $skin->link( $title ) . '</del>'
608744 : $skin->link(
@@ -626,7 +762,7 @@
627763 return wfMsgHtml( 'wantedpages-badtitle', $tsafe );
628764 }
629765 }
630 -
 766+
631767 /**
632768 * Make a "what links here" link for a given title
633769 *
Index: trunk/phase3/includes/SpecialPage.php
@@ -84,36 +84,36 @@
8585 */
8686 static public $mList = array(
8787 # Maintenance Reports
88 - 'BrokenRedirects' => array( 'SpecialPage', 'BrokenRedirects' ),
89 - 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ),
90 - 'DoubleRedirects' => array( 'SpecialPage', 'DoubleRedirects' ),
91 - 'Longpages' => array( 'SpecialPage', 'Longpages' ),
92 - 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ),
93 - 'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ),
94 - 'Fewestrevisions' => array( 'SpecialPage', 'Fewestrevisions' ),
95 - 'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ),
 88+ 'BrokenRedirects' => array( 'BrokenRedirectsPage' ),
 89+ 'Deadendpages' => array( 'DeadendpagesPage' ),
 90+ 'DoubleRedirects' => array( 'DoubleRedirectsPage' ),
 91+ 'Longpages' => array( 'LongpagesPage' ),
 92+ 'Ancientpages' => array( 'AncientpagesPage' ),
 93+ 'Lonelypages' => array( 'LonelypagesPage' ),
 94+ 'Fewestrevisions' => array( 'FewestrevisionsPage' ),
 95+ 'Withoutinterwiki' => array( 'WithoutinterwikiPage' ),
9696 'Protectedpages' => 'SpecialProtectedpages',
9797 'Protectedtitles' => 'SpecialProtectedtitles',
98 - 'Shortpages' => array( 'SpecialPage', 'Shortpages' ),
99 - 'Uncategorizedcategories' => array( 'SpecialPage', 'Uncategorizedcategories' ),
100 - 'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ),
101 - 'Uncategorizedpages' => array( 'SpecialPage', 'Uncategorizedpages' ),
102 - 'Uncategorizedtemplates' => array( 'SpecialPage', 'Uncategorizedtemplates' ),
103 - 'Unusedcategories' => array( 'SpecialPage', 'Unusedcategories' ),
104 - 'Unusedimages' => array( 'SpecialPage', 'Unusedimages' ),
105 - 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ),
106 - 'Unwatchedpages' => array( 'SpecialPage', 'Unwatchedpages', 'unwatchedpages' ),
107 - 'Wantedcategories' => array( 'SpecialPage', 'Wantedcategories' ),
108 - 'Wantedfiles' => array( 'SpecialPage', 'Wantedfiles' ),
109 - 'Wantedpages' => array( 'IncludableSpecialPage', 'Wantedpages' ),
110 - 'Wantedtemplates' => array( 'SpecialPage', 'Wantedtemplates' ),
 98+ 'Shortpages' => array( 'ShortpagesPage' ),
 99+ 'Uncategorizedcategories' => array( 'UncategorizedcategoriesPage' ),
 100+ 'Uncategorizedimages' => array( 'UncategorizedimagesPage' ),
 101+ 'Uncategorizedpages' => array( 'UncategorizedpagesPage' ),
 102+ 'Uncategorizedtemplates' => array( 'UncategorizedtemplatesPage' ),
 103+ 'Unusedcategories' => array( 'UnusedcategoriesPage' ),
 104+ 'Unusedimages' => array( 'UnusedimagesPage' ),
 105+ 'Unusedtemplates' => array( 'UnusedtemplatesPage' ),
 106+ 'Unwatchedpages' => array( 'UnwatchedpagesPage' ),
 107+ 'Wantedcategories' => array( 'WantedcategoriesPage' ),
 108+ 'Wantedfiles' => array( 'WantedfilesPage' ),
 109+ 'Wantedpages' => array( 'WantedpagesPage' ),
 110+ 'Wantedtemplates' => array( 'WantedtemplatesPage' ),
111111
112112 # List of pages
113113 'Allpages' => 'SpecialAllpages',
114114 'Prefixindex' => 'SpecialPrefixindex',
115115 'Categories' => 'SpecialCategories',
116 - 'Disambiguations' => array( 'SpecialPage', 'Disambiguations' ),
117 - 'Listredirects' => array( 'SpecialPage', 'Listredirects' ),
 116+ 'Disambiguations' => array( 'DisambiguationsPage' ),
 117+ 'Listredirects' => array( 'ListredirectsPage' ),
118118
119119 # Login/create account
120120 'Userlogin' => 'LoginForm',
@@ -147,8 +147,8 @@
148148 # Media reports and uploads
149149 'Listfiles' => array( 'SpecialPage', 'Listfiles' ),
150150 'Filepath' => 'SpecialFilepath',
151 - 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ),
152 - 'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ),
 151+ 'MIMEsearch' => 'MIMEsearchPage',
 152+ 'FileDuplicateSearch' => 'FileDuplicateSearchPage',
153153 'Upload' => 'SpecialUpload',
154154 'UploadStash' => 'SpecialUploadStash',
155155
@@ -160,17 +160,17 @@
161161 'Unlockdb' => 'SpecialUnlockdb',
162162
163163 # Redirecting special pages
164 - 'LinkSearch' => array( 'SpecialPage', 'LinkSearch' ),
 164+ 'LinkSearch' => array( 'LinkSearchPage' ),
165165 'Randompage' => 'Randompage',
166166 'Randomredirect' => 'SpecialRandomredirect',
167167
168168 # High use pages
169 - 'Mostlinkedcategories' => array( 'SpecialPage', 'Mostlinkedcategories' ),
170 - 'Mostimages' => array( 'SpecialPage', 'Mostimages' ),
171 - 'Mostlinked' => array( 'SpecialPage', 'Mostlinked' ),
172 - 'Mostlinkedtemplates' => array( 'SpecialPage', 'Mostlinkedtemplates' ),
173 - 'Mostcategories' => array( 'SpecialPage', 'Mostcategories' ),
174 - 'Mostrevisions' => array( 'SpecialPage', 'Mostrevisions' ),
 169+ 'Mostlinkedcategories' => array( 'MostlinkedCategoriesPage' ),
 170+ 'Mostimages' => array( 'MostimagesPage' ),
 171+ 'Mostlinked' => array( 'MostlinkedPage' ),
 172+ 'Mostlinkedtemplates' => array( 'MostlinkedTemplatesPage' ),
 173+ 'Mostcategories' => array( 'MostcategoriesPage' ),
 174+ 'Mostrevisions' => array( 'MostrevisionsPage' ),
175175
176176 # Page tools
177177 'ComparePages' => 'SpecialComparePages',
@@ -220,7 +220,7 @@
221221 self::$mListInitialised = true;
222222
223223 if( !$wgDisableCounters ) {
224 - self::$mList['Popularpages'] = array( 'SpecialPage', 'Popularpages' );
 224+ self::$mList['Popularpages'] = array( 'PopularpagesPage' );
225225 }
226226
227227 if( !$wgDisableInternalSearch ) {
Index: trunk/phase3/languages/messages/MessagesQqq.php
@@ -2120,6 +2120,7 @@
21212121 'pager-newer-n' => "This is part of the navigation message on the top and bottom of Special pages which are lists of things in date order, e.g. the User's contributions page. It is passed as the second argument of {{msg-mw|Viewprevnext}}. $1 is the number of items shown per page.",
21222122 'pager-older-n' => "This is part of the navigation message on the top and bottom of Special pages which are lists of things in date order, e.g. the User's contributions page. It is passed as the first argument of {{msg-mw|Viewprevnext}}. $1 is the number of items shown per page.",
21232123 'suppress' => '{{Identical|Oversight}}',
 2124+'querypage-disabled' => "On special pages that use expensive database queries but are not cacheable, this message is displayed when 'miser mode' is on (i.e. no expensive queries allowed).",
21242125
21252126 # Book sources
21262127 'booksources' => 'Name of special page displayed in [[Special:SpecialPages]]',
Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -2520,6 +2520,7 @@
25212521 'pager-newer-n' => '{{PLURAL:$1|newer 1|newer $1}}',
25222522 'pager-older-n' => '{{PLURAL:$1|older 1|older $1}}',
25232523 'suppress' => 'Oversight',
 2524+'querypage-disabled' => 'This special page is disabled for performance reasons.',
25242525
25252526 # Book sources
25262527 'booksources' => 'Book sources',
Index: trunk/phase3/RELEASE-NOTES
@@ -19,6 +19,8 @@
2020 it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
2121
2222 === Configuration changes in 1.18 ===
 23+* The WantedPages::getSQL hook has been removed and replaced with
 24+ WantedPages::getQueryInfo . This may break older extensions.
2325
2426 === New features in 1.18 ===
2527 * Added a special page, disabled by default, that allows users with the

Follow-up revisions

RevisionCommit summaryAuthorDate
r78788Followup r78786: merge querypage-work2 for extensionscatrope14:33, 22 December 2010
r78789Followup r78786: remove useless getTitle() override, was causing E_STRICTcatrope14:35, 22 December 2010
r78792Really do what r78790 claimed to do (fix E_STRICT in r78786)catrope15:00, 22 December 2010
r78793Follow-up r78786: for consistency, removed array() when there's only one itemialex15:06, 22 December 2010
r78795Followup r78786, fix special wanted pages. Aliasing pages in left join doesn'...reedy15:20, 22 December 2010
r78804Mark LinkSearch as uncacheable and fix its headers display. Followup r78786catrope16:19, 22 December 2010
r78809Followup r78786: var->protectedcatrope17:29, 22 December 2010
r78811Followup r78786: fix $wgQueryPages registration in ProofreadPagecatrope17:55, 22 December 2010
r78814Followup r78786: make back compat mode actually work by moving up $dbr assign...catrope18:08, 22 December 2010
r78824(bug 14869) Add API module for accessing QueryPage-based special pages. Took ...catrope20:35, 22 December 2010
r78825RELEASE-NOTES for r78824, r78786reedy20:38, 22 December 2010
r78837Per bug 14869 being fixed, bug 8130 and bug 14020 have been fixed. Marking th...reedy21:30, 22 December 2010
r79345Remove now-unused SQL timestamp conversion functions added in r77231. They we...catrope16:29, 31 December 2010
r79347Follow-up r78786: the querypage branch merge threw SpecialLinkSearch back to ...catrope16:44, 31 December 2010
r79349Followup r78786: pass $fname in the raw SQL case toocatrope16:47, 31 December 2010
r79744Follow up r78786. Do not mark as @deprecated yet, since it is called by getQu...platonides19:15, 6 January 2011
r81198Per fixme on r78786, obey sortDescending in Cached mode (ie if sortDescending...reedy22:07, 29 January 2011
r81392* (bug 27102) Backport part of r78786: don't require the "File:" on input fie...ialex14:54, 2 February 2011
r82641Followup r78786, per complaints on r82636, revert parameter ordering...reedy23:22, 22 February 2011
r91744Fix for r78786: make Special:Wantedpages includable againialex18:20, 8 July 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r53149(bug 14869) Allow access to QueryPage-based special pages via API...btongminh21:51, 12 July 2009

Comments

#Comment by Raymond (talk | contribs)   14:31, 22 December 2010

On my local testwiki calling Special:UnusedFiles

Strict Standards: Declaration of QueryPage::getTitle() should be compatible with that of SpecialPage::getTitle() in \includes\QueryPage.php on line 687

#Comment by Catrope (talk | contribs)   14:36, 22 December 2010

Removed in r78789

#Comment by Reedy (talk | contribs)   14:35, 22 December 2010

Visually looks ok. Few minor code style errors, and out of place comments. Will look at tidying up braces etc in near future, comments will get sorted out too

#Comment by Nikerabbit (talk | contribs)   15:23, 22 December 2010

+ var $shownavigation = true; Var??

+       function reallyDoQuery( $limit, $offset = false ) {

Why only offset is optional?

#Comment by Catrope (talk | contribs)   17:30, 22 December 2010

var fixed in r78809.

I don't remember why I made $limit required but $offset optional. The old code had doQuery( $offset, $limit ) , and I think I changed it in the branch about two years ago.

#Comment by Platonides (talk | contribs)   22:03, 22 December 2010

includes/specials/SpecialLinkSearch.php:

- $rv = LinkFilter::makeLikeArray( $query , $prot );
+ $rv = LinkFilter::makeLike( $query , $prot );

LinkFilter::makeLike is deprecated: "Use makeLikeArray() and pass result to Database::buildLike() instead"

#Comment by Catrope (talk | contribs)   17:20, 31 December 2010

Fixed in r79347.

#Comment by Platonides (talk | contribs)   18:56, 6 January 2011

Should we have getSQL() marked as @deprecated, given that it is called by a non-deprecated function? (yes, it's just as a fallback, but calling getSQL isn't bad. Implementing it is.)

#Comment by Catrope (talk | contribs)   18:59, 6 January 2011

As long as it's clear that getQueryInfo() should be implemented instead of getSQL(), I'm fine with it.

#Comment by Happy-melon (talk | contribs)   19:00, 6 January 2011

@deprecated is a marker for us as much as for extension writers: it reminds us that we should be planning to remove it at a later date, and so should be phasing it out if it's not already, and preventing new usage. Otherwise we'd need a "@to-be-deprecated-later" tag, which is silly... :D

#Comment by Aaron Schulz (talk | contribs)   19:32, 29 January 2011

LongPages seems to be listing backwards now.

#Comment by Reedy (talk | contribs)   20:04, 29 January 2011

That would presumably mean that ShortPages would also be backwards?

#Comment by Reedy (talk | contribs)   20:09, 29 January 2011
(hist) ‎Main Page ‎[606 bytes]
(hist) ‎Sandbox ‎[137 bytes]
(hist) ‎FullTwoLow ‎[108 bytes]
(hist) ‎Test ‎[59 bytes]

That looks fine to me?

#Comment by Aaron Schulz (talk | contribs)   20:16, 29 January 2011

ShortPages is in the right order. LongPages gives:

  1. (hist) ‎HalfTwoLow ‎[2 bytes]
  2. (hist) ‎HalfOneUp ‎[2 bytes]
  3. (hist) ‎Alan Smithee ‎[3 bytes]
  4. (hist) ‎Redir ‎[3 bytes]
  5. (hist) ‎Autoreviewedpage ‎[4 bytes]

...

I don't see any sort specified in fetchFromCache(). MySQL probably just reading off the type,val INDEX in ascending order.

#Comment by Reedy (talk | contribs)   20:26, 29 January 2011

SpecialShortpages has

	function getOrderFields() {
		return array( 'page_len' );
	}

	function sortDescending() {
		return false;
	}

SpecialLongPages has (note it inherits from SpecialShortpages and overrides the property)

	function sortDescending() {
		return true;
	}

And it's doing

Query 9 (slave): SELECT /* LongPagesPage::reallyDoQuery 192.168.0.25 */  page_namespace AS namespace,page_title AS title,page_len AS value  FROM `mw_page` FORCE INDEX (page_len) WHERE page_namespace = '0' AND page_is_redirect = '0'  ORDER BY page_len DESC LIMIT 50 

For longpages...

#Comment by Aaron Schulz (talk | contribs)   20:33, 29 January 2011
  • That* SELECT is OK, but not the one from querycache. See my above comment :)
#Comment by Reedy (talk | contribs)   12:14, 23 February 2011
	/**#@+
	  * Accessor
	  *
	  * @deprecated
	  */
	function getName() { return $this->mName; }

Why's that marked as deprecated?

Seems to be the inverse behaviour...

#Comment by 😂 (talk | contribs)   17:09, 18 April 2011

Pretty sure this is all cleaned up now, marking resolved.

If you've got bugs, put them in BZ ;-)

Status & tagging log