SpecialDoubleRedirects.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <?php
  2. /**
  3. * Implements Special:DoubleRedirects
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup SpecialPage
  22. */
  23. use MediaWiki\MediaWikiServices;
  24. use Wikimedia\Rdbms\IResultWrapper;
  25. use Wikimedia\Rdbms\IDatabase;
  26. /**
  27. * A special page listing redirects to redirecting page.
  28. * The software will automatically not follow double redirects, to prevent loops.
  29. *
  30. * @ingroup SpecialPage
  31. */
  32. class SpecialDoubleRedirects extends QueryPage {
  33. function __construct( $name = 'DoubleRedirects' ) {
  34. parent::__construct( $name );
  35. }
  36. public function isExpensive() {
  37. return true;
  38. }
  39. function isSyndicated() {
  40. return false;
  41. }
  42. function sortDescending() {
  43. return false;
  44. }
  45. function getPageHeader() {
  46. return $this->msg( 'doubleredirectstext' )->parseAsBlock();
  47. }
  48. function reallyGetQueryInfo( $namespace = null, $title = null ) {
  49. $limitToTitle = !( $namespace === null && $title === null );
  50. $dbr = wfGetDB( DB_REPLICA );
  51. $retval = [
  52. 'tables' => [
  53. 'ra' => 'redirect',
  54. 'rb' => 'redirect',
  55. 'pa' => 'page',
  56. 'pb' => 'page'
  57. ],
  58. 'fields' => [
  59. 'namespace' => 'pa.page_namespace',
  60. 'title' => 'pa.page_title',
  61. 'b_namespace' => 'pb.page_namespace',
  62. 'b_title' => 'pb.page_title',
  63. // Select fields from redirect instead of page. Because there may
  64. // not actually be a page table row for this target (e.g. for interwiki redirects)
  65. 'c_namespace' => 'rb.rd_namespace',
  66. 'c_title' => 'rb.rd_title',
  67. 'c_fragment' => 'rb.rd_fragment',
  68. 'c_interwiki' => 'rb.rd_interwiki',
  69. ],
  70. 'conds' => [
  71. 'ra.rd_from = pa.page_id',
  72. // Filter out redirects where the target goes interwiki (T42353).
  73. // This isn't an optimization, it is required for correct results,
  74. // otherwise a non-double redirect like Bar -> w:Foo will show up
  75. // like "Bar -> Foo -> w:Foo".
  76. // Need to check both NULL and "" for some reason,
  77. // apparently either can be stored for non-iw entries.
  78. 'ra.rd_interwiki IS NULL OR ra.rd_interwiki = ' . $dbr->addQuotes( '' ),
  79. 'pb.page_namespace = ra.rd_namespace',
  80. 'pb.page_title = ra.rd_title',
  81. 'rb.rd_from = pb.page_id',
  82. ]
  83. ];
  84. if ( $limitToTitle ) {
  85. $retval['conds']['pa.page_namespace'] = $namespace;
  86. $retval['conds']['pa.page_title'] = $title;
  87. }
  88. return $retval;
  89. }
  90. public function getQueryInfo() {
  91. return $this->reallyGetQueryInfo();
  92. }
  93. function getOrderFields() {
  94. return [ 'ra.rd_namespace', 'ra.rd_title' ];
  95. }
  96. /**
  97. * @param Skin $skin
  98. * @param object $result Result row
  99. * @return string
  100. */
  101. function formatResult( $skin, $result ) {
  102. // If no Title B or C is in the query, it means this came from
  103. // querycache (which only saves the 3 columns for title A).
  104. // That does save the bulk of the query cost, but now we need to
  105. // get a little more detail about each individual entry quickly
  106. // using the filter of reallyGetQueryInfo.
  107. $deep = false;
  108. if ( $result ) {
  109. if ( isset( $result->b_namespace ) ) {
  110. $deep = $result;
  111. } else {
  112. $dbr = wfGetDB( DB_REPLICA );
  113. $qi = $this->reallyGetQueryInfo(
  114. $result->namespace,
  115. $result->title
  116. );
  117. $res = $dbr->select(
  118. $qi['tables'],
  119. $qi['fields'],
  120. $qi['conds'],
  121. __METHOD__
  122. );
  123. if ( $res ) {
  124. $deep = $dbr->fetchObject( $res ) ?: false;
  125. }
  126. }
  127. }
  128. $titleA = Title::makeTitle( $result->namespace, $result->title );
  129. $linkRenderer = $this->getLinkRenderer();
  130. if ( !$deep ) {
  131. return '<del>' . $linkRenderer->makeLink( $titleA, null, [], [ 'redirect' => 'no' ] ) . '</del>';
  132. }
  133. // if the page is editable, add an edit link
  134. if (
  135. // check user permissions
  136. MediaWikiServices::getInstance()
  137. ->getPermissionManager()
  138. ->userHasRight( $this->getUser(), 'edit' ) &&
  139. // check, if the content model is editable through action=edit
  140. ContentHandler::getForTitle( $titleA )->supportsDirectEditing()
  141. ) {
  142. $edit = $linkRenderer->makeKnownLink(
  143. $titleA,
  144. $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->text(),
  145. [],
  146. [ 'action' => 'edit' ]
  147. );
  148. } else {
  149. $edit = '';
  150. }
  151. $linkA = $linkRenderer->makeKnownLink(
  152. $titleA,
  153. null,
  154. [],
  155. [ 'redirect' => 'no' ]
  156. );
  157. $titleB = Title::makeTitle( $deep->b_namespace, $deep->b_title );
  158. $linkB = $linkRenderer->makeKnownLink(
  159. $titleB,
  160. null,
  161. [],
  162. [ 'redirect' => 'no' ]
  163. );
  164. $titleC = Title::makeTitle(
  165. $deep->c_namespace,
  166. $deep->c_title,
  167. $deep->c_fragment,
  168. $deep->c_interwiki
  169. );
  170. $linkC = $linkRenderer->makeKnownLink( $titleC, $titleC->getFullText() );
  171. $lang = $this->getLanguage();
  172. $arr = $lang->getArrow() . $lang->getDirMark();
  173. return ( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
  174. }
  175. public function execute( $par ) {
  176. $this->addHelpLink( 'Help:Redirects' );
  177. parent::execute( $par );
  178. }
  179. /**
  180. * Cache page content model and gender distinction for performance
  181. *
  182. * @param IDatabase $db
  183. * @param IResultWrapper $res
  184. */
  185. function preprocessResults( $db, $res ) {
  186. if ( !$res->numRows() ) {
  187. return;
  188. }
  189. $batch = new LinkBatch;
  190. foreach ( $res as $row ) {
  191. $batch->add( $row->namespace, $row->title );
  192. if ( isset( $row->b_namespace ) ) {
  193. // lazy loaded when using cached results
  194. $batch->add( $row->b_namespace, $row->b_title );
  195. }
  196. if ( isset( $row->c_interwiki ) && !$row->c_interwiki ) {
  197. // lazy loaded when using cached result, not added when interwiki link
  198. $batch->add( $row->c_namespace, $row->c_title );
  199. }
  200. }
  201. $batch->execute();
  202. // Back to start for display
  203. $res->seek( 0 );
  204. }
  205. protected function getGroupName() {
  206. return 'maintenance';
  207. }
  208. }