Index: branches/iwtransclusion/phase3/includes/SpecialPage.php |
— | — | @@ -173,6 +173,7 @@ |
174 | 174 | # Page tools |
175 | 175 | 'ComparePages' => 'SpecialComparePages', |
176 | 176 | 'Export' => 'SpecialExport', |
| 177 | + 'GlobalTemplateUsage' => 'SpecialGlobalTemplateUsage', |
177 | 178 | 'Import' => 'SpecialImport', |
178 | 179 | 'Undelete' => 'UndeleteForm', |
179 | 180 | 'Whatlinkshere' => 'SpecialWhatlinkshere', |
Index: branches/iwtransclusion/phase3/includes/specials/SpecialGlobalTemplateUsage.php |
— | — | @@ -0,0 +1,442 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * This file has been copied from Extension:GlobalUsage and adapted |
| 5 | + * to show the usage of a template instead of a file. |
| 6 | + * Special page to show global template usage. Also contains hook functions for |
| 7 | + * showing usage on an template page. |
| 8 | + */ |
| 9 | + |
| 10 | +class SpecialGlobalTemplateUsage extends SpecialPage { |
| 11 | + public function __construct() { |
| 12 | + parent::__construct( 'GlobalTemplateUsage', 'globaltemplateusage' ); |
| 13 | + } |
| 14 | + |
| 15 | + /** |
| 16 | + * Entry point |
| 17 | + */ |
| 18 | + public function execute( $par ) { |
| 19 | + global $wgOut, $wgRequest; |
| 20 | + |
| 21 | + $target = $par ? $par : $wgRequest->getVal( 'target' ); |
| 22 | + $this->target = Title::newFromText( $target ); |
| 23 | + |
| 24 | + $this->setHeaders(); |
| 25 | + |
| 26 | + $this->showForm(); |
| 27 | + |
| 28 | + if ( is_null( $this->target ) ) |
| 29 | + { |
| 30 | + $wgOut->setPageTitle( wfMsg( 'globaltemplateusage' ) ); |
| 31 | + return; |
| 32 | + } |
| 33 | + |
| 34 | + $wgOut->setPageTitle( wfMsg( 'globaltemplateusage-for', $this->target->getPrefixedText() ) ); |
| 35 | + |
| 36 | + $this->showResult(); |
| 37 | + } |
| 38 | + |
| 39 | + /** |
| 40 | + * Shows the search form |
| 41 | + */ |
| 42 | + private function showForm() { |
| 43 | + global $wgScript, $wgOut, $wgRequest; |
| 44 | + |
| 45 | + /* Build form */ |
| 46 | + $html = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . "\n"; |
| 47 | + // Name of SpecialPage |
| 48 | + $html .= Xml::hidden( 'title', $this->getTitle( )->getPrefixedText( ) ) . "\n"; |
| 49 | + // Limit |
| 50 | + $html .= Xml::hidden( 'limit', $wgRequest->getInt( 'limit', 50 ) ); |
| 51 | + // Input box with target prefilled if available |
| 52 | + $formContent = "\t" . Xml::input( 'target', 40, is_null( $this->target ) ? '' |
| 53 | + : $this->target->getPrefixedText( ) ) |
| 54 | + // Submit button |
| 55 | + . "\n\t" . Xml::element( 'input', array( |
| 56 | + 'type' => 'submit', |
| 57 | + 'value' => wfMsg( 'globaltemplateusage-ok' ) |
| 58 | + ) ); |
| 59 | + |
| 60 | + // Wrap the entire form in a nice fieldset |
| 61 | + $html .= Xml::fieldSet( wfMsg( 'globaltemplateusage-text' ), $formContent ) . "\n</form>"; |
| 62 | + |
| 63 | + $wgOut->addHtml( $html ); |
| 64 | + } |
| 65 | + |
| 66 | + /** |
| 67 | + * Creates as queryer and executes it based on $wgRequest |
| 68 | + */ |
| 69 | + private function showResult() { |
| 70 | + global $wgRequest; |
| 71 | + |
| 72 | + $query = new GlobalTemplateUsageQuery( $this->target ); |
| 73 | + |
| 74 | + // Extract params from $wgRequest |
| 75 | + if ( $wgRequest->getText( 'from' ) ) { |
| 76 | + $query->setOffset( $wgRequest->getText( 'from' ) ); |
| 77 | + } elseif ( $wgRequest->getText( 'to' ) ) { |
| 78 | + $query->setOffset( $wgRequest->getText( 'to' ), true ); |
| 79 | + } |
| 80 | + $query->setLimit( $wgRequest->getInt( 'limit', 50 ) ); |
| 81 | + |
| 82 | + // Perform query |
| 83 | + $query->execute(); |
| 84 | + |
| 85 | + // Show result |
| 86 | + global $wgOut; |
| 87 | + |
| 88 | + // Don't show form element if there is no data |
| 89 | + if ( $query->count() == 0 ) { |
| 90 | + $wgOut->addWikiMsg( 'globaltemplateusage-no-results', $this->target->getPrefixedText( ) ); |
| 91 | + return; |
| 92 | + } |
| 93 | + |
| 94 | + $offset = $query->getOffsetString( ); |
| 95 | + $navbar = $this->getNavBar( $query ); |
| 96 | + $targetName = $this->target->getPrefixedText( ); |
| 97 | + |
| 98 | + // Top navbar |
| 99 | + $wgOut->addHtml( $navbar ); |
| 100 | + |
| 101 | + $wgOut->addHtml( '<div id="mw-globaltemplateusage-result">' ); |
| 102 | + foreach ( $query->getSingleTemplateResult() as $wiki => $result ) { |
| 103 | + $wgOut->addHtml( |
| 104 | + '<h2>' . wfMsgExt( |
| 105 | + 'globaltemplateusage-on-wiki', 'parseinline', |
| 106 | + $targetName, WikiMap::getWikiName( $wiki ) ) |
| 107 | + . "</h2><ul>\n" ); |
| 108 | + foreach ( $result as $item ) { |
| 109 | + $wgOut->addHtml( "\t<li>" . self::formatItem( $item ) . "</li>\n" ); |
| 110 | + } |
| 111 | + $wgOut->addHtml( "</ul>\n" ); |
| 112 | + } |
| 113 | + $wgOut->addHtml( '</div>' ); |
| 114 | + |
| 115 | + // Bottom navbar |
| 116 | + $wgOut->addHtml( $navbar ); |
| 117 | + } |
| 118 | + |
| 119 | + /** |
| 120 | + * Helper to format a specific item |
| 121 | + */ |
| 122 | + public static function formatItem( $item ) { |
| 123 | + if ( !$item['namespace'] ) { |
| 124 | + $page = $item['title']; |
| 125 | + } else { |
| 126 | + $page = "{$item['namespace']}:{$item['title']}"; |
| 127 | + } |
| 128 | + |
| 129 | + $link = WikiMap::makeForeignLink( $item['wiki'], $page, |
| 130 | + str_replace( '_', ' ', $page ) ); |
| 131 | + // Return only the title if no link can be constructed |
| 132 | + return $link === false ? $page : $link; |
| 133 | + } |
| 134 | + |
| 135 | + /** |
| 136 | + * Helper function to create the navbar, stolen from wfViewPrevNext |
| 137 | + * |
| 138 | + * @param $query GlobalTemplateUsageQuery An executed GlobalTemplateUsageQuery object |
| 139 | + * @return string Navbar HTML |
| 140 | + */ |
| 141 | + protected function getNavBar( $query ) { |
| 142 | + global $wgLang, $wgUser; |
| 143 | + |
| 144 | + $skin = $wgUser->getSkin(); |
| 145 | + |
| 146 | + $target = $this->target->getPrefixedText(); |
| 147 | + $limit = $query->getLimit(); |
| 148 | + $fmtLimit = $wgLang->formatNum( $limit ); |
| 149 | + |
| 150 | + # Find out which strings are for the prev and which for the next links |
| 151 | + $offset = $query->getOffsetString(); |
| 152 | + $continue = $query->getContinueString(); |
| 153 | + if ( $query->isReversed() ) { |
| 154 | + $from = $offset; |
| 155 | + $to = $continue; |
| 156 | + } else { |
| 157 | + $from = $continue; |
| 158 | + $to = $offset; |
| 159 | + } |
| 160 | + |
| 161 | + # Get prev/next link display text |
| 162 | + $prev = wfMsgExt( 'prevn', array( 'parsemag', 'escape' ), $fmtLimit ); |
| 163 | + $next = wfMsgExt( 'nextn', array( 'parsemag', 'escape' ), $fmtLimit ); |
| 164 | + # Get prev/next link title text |
| 165 | + $pTitle = wfMsgExt( 'prevn-title', array( 'parsemag', 'escape' ), $fmtLimit ); |
| 166 | + $nTitle = wfMsgExt( 'nextn-title', array( 'parsemag', 'escape' ), $fmtLimit ); |
| 167 | + |
| 168 | + # Fetch the title object |
| 169 | + $title = $this->getTitle(); |
| 170 | + |
| 171 | + # Make 'previous' link |
| 172 | + if ( $to ) { |
| 173 | + $attr = array( 'title' => $pTitle, 'class' => 'mw-prevlink' ); |
| 174 | + $q = array( 'limit' => $limit, 'to' => $to, 'target' => $target ); |
| 175 | + $plink = $skin->link( $title, $prev, $attr, $q ); |
| 176 | + } else { |
| 177 | + $plink = $prev; |
| 178 | + } |
| 179 | + |
| 180 | + # Make 'next' link |
| 181 | + if ( $from ) { |
| 182 | + $attr = array( 'title' => $nTitle, 'class' => 'mw-nextlink' ); |
| 183 | + $q = array( 'limit' => $limit, 'from' => $from, 'target' => $target ); |
| 184 | + $nlink = $skin->link( $title, $next, $attr, $q ); |
| 185 | + } else { |
| 186 | + $nlink = $next; |
| 187 | + } |
| 188 | + |
| 189 | + # Make links to set number of items per page |
| 190 | + $numLinks = array(); |
| 191 | + foreach ( array( 20, 50, 100, 250, 500 ) as $num ) { |
| 192 | + $fmtLimit = $wgLang->formatNum( $num ); |
| 193 | + |
| 194 | + $q = array( 'offset' => $offset, 'limit' => $num, 'target' => $target ); |
| 195 | + $lTitle = wfMsgExt( 'shown-title', array( 'parsemag', 'escape' ), $num ); |
| 196 | + $attr = array( 'title' => $lTitle, 'class' => 'mw-numlink' ); |
| 197 | + |
| 198 | + $numLinks[] = $skin->link( $title, $fmtLimit, $attr, $q ); |
| 199 | + } |
| 200 | + $nums = $wgLang->pipeList( $numLinks ); |
| 201 | + |
| 202 | + return wfMsgHtml( 'viewprevnext', $plink, $nlink, $nums ); |
| 203 | + } |
| 204 | +} |
| 205 | + |
| 206 | +/** |
| 207 | + * This class has been copied from Extension:GlobalUsage / GlobalUsageQuery.php |
| 208 | + * Extension:GlobalUsage should be built-in and the GlobalUsageQuery adapted |
| 209 | + * to be able to fetch the global usage of templates as well as files. |
| 210 | + */ |
| 211 | +class GlobalTemplateUsageQuery { |
| 212 | + private $limit = 50; |
| 213 | + private $offset; |
| 214 | + private $hasMore = false; |
| 215 | + private $result; |
| 216 | + private $continue; |
| 217 | + private $reversed = false; |
| 218 | + private $target = null; |
| 219 | + |
| 220 | + /** |
| 221 | + * @param $target mixed Title or db key, or array of db keys of target(s) |
| 222 | + */ |
| 223 | + public function __construct( $target ) { |
| 224 | + global $wgGlobalDatabase; |
| 225 | + $this->db = wfGetDB( DB_SLAVE, array(), $wgGlobalDatabase ); |
| 226 | + $this->target = $target; |
| 227 | + $this->offset = array(); |
| 228 | + } |
| 229 | + |
| 230 | + /** |
| 231 | + * Set the offset parameter |
| 232 | + * |
| 233 | + * @param $offset string offset |
| 234 | + * @param $reversed bool True if this is the upper offset |
| 235 | + */ |
| 236 | + public function setOffset( $offset, $reversed = null ) { |
| 237 | + if ( !is_null( $reversed ) ) { |
| 238 | + $this->reversed = $reversed; |
| 239 | + } |
| 240 | + |
| 241 | + if ( !is_array( $offset ) ) { |
| 242 | + $offset = explode( '|', $offset ); |
| 243 | + } |
| 244 | + |
| 245 | + if ( count( $offset ) == 3 ) { |
| 246 | + $this->offset = $offset; |
| 247 | + return true; |
| 248 | + } else { |
| 249 | + return false; |
| 250 | + } |
| 251 | + } |
| 252 | + /** |
| 253 | + * Return the offset set by the user |
| 254 | + * |
| 255 | + * @return array offset |
| 256 | + */ |
| 257 | + public function getOffsetString() { |
| 258 | + return implode( '|', $this->offset ); |
| 259 | + } |
| 260 | + |
| 261 | + /** |
| 262 | + * Is the result reversed |
| 263 | + * |
| 264 | + * @return bool |
| 265 | + */ |
| 266 | + public function isReversed() { |
| 267 | + return $this->reversed; |
| 268 | + } |
| 269 | + |
| 270 | + /** |
| 271 | + * Returns the string used for continuation |
| 272 | + * |
| 273 | + * @return string |
| 274 | + * |
| 275 | + */ |
| 276 | + public function getContinueString() { |
| 277 | + if ( $this->hasMore() ) |
| 278 | + return "{$this->lastRow->gtl_to_title}|{$this->lastRow->gtl_from_wiki}|{$this->lastRow->gtl_from_page}"; |
| 279 | + else |
| 280 | + return ''; |
| 281 | + } |
| 282 | + |
| 283 | + /** |
| 284 | + * Set the maximum amount of items to return. Capped at 500. |
| 285 | + * |
| 286 | + * @param $limit int The limit |
| 287 | + */ |
| 288 | + public function setLimit( $limit ) { |
| 289 | + $this->limit = min( $limit, 500 ); |
| 290 | + } |
| 291 | + |
| 292 | + /** |
| 293 | + * Returns the user set limit |
| 294 | + */ |
| 295 | + public function getLimit() { |
| 296 | + return $this->limit; |
| 297 | + } |
| 298 | + |
| 299 | + /** |
| 300 | + * Executes the query |
| 301 | + */ |
| 302 | + public function execute() { |
| 303 | + global $wgLocalInterwiki; |
| 304 | + |
| 305 | + /* Construct a where clause */ |
| 306 | + // Add target template(s) |
| 307 | + $where = array( 'gtl_to_prefix' => $wgLocalInterwiki, |
| 308 | + 'gtl_to_namespace' => $this->target->getNamespace( ), |
| 309 | + 'gtl_to_title' => $this->target->getDBkey( ) |
| 310 | + ); |
| 311 | + |
| 312 | + // Set the continuation condition |
| 313 | + $order = 'ASC'; |
| 314 | + if ( $this->offset ) { |
| 315 | + $qTo = $this->db->addQuotes( $this->offset[0] ); |
| 316 | + $qWiki = $this->db->addQuotes( $this->offset[1] ); |
| 317 | + $qPage = intval( $this->offset[2] ); |
| 318 | + |
| 319 | + // Check which limit we got in order to determine which way to traverse rows |
| 320 | + if ( $this->reversed ) { |
| 321 | + // Reversed traversal; do not include offset row |
| 322 | + $op1 = '<'; |
| 323 | + $op2 = '<'; |
| 324 | + $order = 'DESC'; |
| 325 | + } else { |
| 326 | + // Normal traversal; include offset row |
| 327 | + $op1 = '>'; |
| 328 | + $op2 = '>='; |
| 329 | + $order = 'ASC'; |
| 330 | + } |
| 331 | + |
| 332 | + $where[] = "(gtl_to_title $op1 $qTo) OR " . |
| 333 | + "(gtl_to_title = $qTo AND gtl_from_wiki $op1 $qWiki) OR " . |
| 334 | + "(gtl_to_title = $qTo AND gtl_from_wiki = $qWiki AND gtl_from_page $op2 $qPage)"; |
| 335 | + } |
| 336 | + |
| 337 | + /* Perform select (Duh.) */ |
| 338 | + $res = $this->db->select( 'globaltemplatelinks', |
| 339 | + array( |
| 340 | + 'gtl_to_title', |
| 341 | + 'gtl_from_wiki', |
| 342 | + 'gtl_from_page', |
| 343 | + 'gtl_from_namespace', |
| 344 | + 'gtl_from_title' |
| 345 | + ), |
| 346 | + $where, |
| 347 | + __METHOD__, |
| 348 | + array( |
| 349 | + 'ORDER BY' => "gtl_to_title $order, gtl_from_wiki $order, gtl_from_page $order", |
| 350 | + // Select an extra row to check whether we have more rows available |
| 351 | + 'LIMIT' => $this->limit + 1, |
| 352 | + ) |
| 353 | + ); |
| 354 | + |
| 355 | + /* Process result */ |
| 356 | + // Always return the result in the same order; regardless whether reversed was specified |
| 357 | + // reversed is really only used to determine from which direction the offset is |
| 358 | + $rows = array(); |
| 359 | + foreach ( $res as $row ) { |
| 360 | + $rows[] = $row; |
| 361 | + } |
| 362 | + if ( $this->reversed ) { |
| 363 | + $rows = array_reverse( $rows ); |
| 364 | + } |
| 365 | + |
| 366 | + // Build the result array |
| 367 | + $count = 0; |
| 368 | + $this->hasMore = false; |
| 369 | + $this->result = array(); |
| 370 | + foreach ( $rows as $row ) { |
| 371 | + $count++; |
| 372 | + if ( $count > $this->limit ) { |
| 373 | + // We've reached the extra row that indicates that there are more rows |
| 374 | + $this->hasMore = true; |
| 375 | + $this->lastRow = $row; |
| 376 | + break; |
| 377 | + } |
| 378 | + |
| 379 | + if ( !isset( $this->result[$row->gtl_to_title] ) ) { |
| 380 | + $this->result[$row->gtl_to_title] = array(); |
| 381 | + } |
| 382 | + if ( !isset( $this->result[$row->gtl_to_title][$row->gtl_from_wiki] ) ) { |
| 383 | + $this->result[$row->gtl_to_title][$row->gtl_from_wiki] = array(); |
| 384 | + } |
| 385 | + |
| 386 | + $this->result[$row->gtl_to_title][$row->gtl_from_wiki][] = array( |
| 387 | + 'template' => $row->gtl_to_title, |
| 388 | + 'id' => $row->gtl_from_page, |
| 389 | + 'namespace' => $row->gtl_from_namespace, |
| 390 | + 'title' => $row->gtl_from_title, |
| 391 | + 'wiki' => $row->gtl_from_wiki, |
| 392 | + ); |
| 393 | + } |
| 394 | + } |
| 395 | + /** |
| 396 | + * Returns the result set. The result is a 4 dimensional array |
| 397 | + * (file, wiki, page), whose items are arrays with keys: |
| 398 | + * - template: File name |
| 399 | + * - id: Page id |
| 400 | + * - namespace: Page namespace text |
| 401 | + * - title: Unprefixed page title |
| 402 | + * - wiki: Wiki id |
| 403 | + * |
| 404 | + * @return array Result set |
| 405 | + */ |
| 406 | + public function getResult() { |
| 407 | + return $this->result; |
| 408 | + } |
| 409 | + |
| 410 | + /** |
| 411 | + * Returns a 3 dimensional array with the result of the first file. Useful |
| 412 | + * if only one template was queried. |
| 413 | + * |
| 414 | + * For further information see documentation of getResult() |
| 415 | + * |
| 416 | + * @return array Result set |
| 417 | + */ |
| 418 | + public function getSingleTemplateResult() { |
| 419 | + if ( $this->result ) { |
| 420 | + return current( $this->result ); |
| 421 | + } else { |
| 422 | + return array(); |
| 423 | + } |
| 424 | + } |
| 425 | + |
| 426 | + /** |
| 427 | + * Returns whether there are more results |
| 428 | + * |
| 429 | + * @return bool |
| 430 | + */ |
| 431 | + public function hasMore() { |
| 432 | + return $this->hasMore; |
| 433 | + } |
| 434 | + |
| 435 | + /** |
| 436 | + * Returns the result length |
| 437 | + * |
| 438 | + * @return int |
| 439 | + */ |
| 440 | + public function count() { |
| 441 | + return count( $this->result ); |
| 442 | + } |
| 443 | +} |
Property changes on: branches/iwtransclusion/phase3/includes/specials/SpecialGlobalTemplateUsage.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 444 | + native |
Index: branches/iwtransclusion/phase3/includes/AutoLoader.php |
— | — | @@ -591,6 +591,7 @@ |
592 | 592 | 'SpecialComparePages' => 'includes/specials/SpecialComparePages.php', |
593 | 593 | 'SpecialExport' => 'includes/specials/SpecialExport.php', |
594 | 594 | 'SpecialFilepath' => 'includes/specials/SpecialFilepath.php', |
| 595 | + 'SpecialGlobalTemplateUsage' => 'includes/specials/SpecialGlobalTemplateUsage.php', |
595 | 596 | 'SpecialImport' => 'includes/specials/SpecialImport.php', |
596 | 597 | 'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php', |
597 | 598 | 'SpecialLockdb' => 'includes/specials/SpecialLockdb.php', |
Index: branches/iwtransclusion/phase3/languages/messages/MessagesEn.php |
— | — | @@ -4315,6 +4315,18 @@ |
4316 | 4316 | 'compare-rev2' => 'Revision 2', |
4317 | 4317 | 'compare-submit' => 'Compare', |
4318 | 4318 | |
| 4319 | +# Special:GlobalTemplateUsage |
| 4320 | +'globaltemplateusage' => 'Global template usage', |
| 4321 | +'globaltemplateusage-for' => 'Global template usage for "$1"', |
| 4322 | +'globaltemplateusage-desc' => '[[Special:GlobalTemplateUsage|Special page]] to view global template usage', |
| 4323 | +'globaltemplateusage-ok' => 'Search', |
| 4324 | +'globaltemplateusage-text' => 'Search global template usage', |
| 4325 | +'globaltemplateusage-no-results' => '[[$1]] is not used on other wikis.', |
| 4326 | +'globaltemplateusage-on-wiki' => 'Usage on $2', |
| 4327 | +'globaltemplateusage-of-file' => 'The following other wikis use this template:', |
| 4328 | +'globaltemplateusage-more' => 'View [[{{#Special:GlobalUsage}}/$1|more global usage]] of this template.', |
| 4329 | +'globaltemplateusage-filterlocal' => 'Do not show local usage', |
| 4330 | + |
4319 | 4331 | # Database error messages |
4320 | 4332 | 'dberr-header' => 'This wiki has a problem', |
4321 | 4333 | 'dberr-problems' => 'Sorry! |