SpecialRedirect.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. <?php
  2. /**
  3. * Implements Special:Redirect
  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. /**
  25. * A special page that redirects to: the user for a numeric user id,
  26. * the file for a given filename, or the page for a given revision id.
  27. *
  28. * @ingroup SpecialPage
  29. * @since 1.22
  30. */
  31. class SpecialRedirect extends FormSpecialPage {
  32. /**
  33. * The type of the redirect (user/file/revision)
  34. *
  35. * Example value: `'user'`
  36. *
  37. * @var string $mType
  38. */
  39. protected $mType;
  40. /**
  41. * The identifier/value for the redirect (which id, which file)
  42. *
  43. * Example value: `'42'`
  44. *
  45. * @var string $mValue
  46. */
  47. protected $mValue;
  48. function __construct() {
  49. parent::__construct( 'Redirect' );
  50. $this->mType = null;
  51. $this->mValue = null;
  52. }
  53. /**
  54. * Set $mType and $mValue based on parsed value of $subpage.
  55. * @param string $subpage
  56. */
  57. function setParameter( $subpage ) {
  58. // parse $subpage to pull out the parts
  59. $parts = explode( '/', $subpage, 2 );
  60. $this->mType = $parts[0];
  61. $this->mValue = $parts[1] ?? null;
  62. }
  63. /**
  64. * Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY)
  65. *
  66. * @return Status A good status contains the url to redirect to
  67. */
  68. function dispatchUser() {
  69. if ( !ctype_digit( $this->mValue ) ) {
  70. // Message: redirect-not-numeric
  71. return Status::newFatal( $this->getMessagePrefix() . '-not-numeric' );
  72. }
  73. $user = User::newFromId( (int)$this->mValue );
  74. $username = $user->getName(); // load User as side-effect
  75. if ( $user->isAnon() ) {
  76. // Message: redirect-not-exists
  77. return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
  78. }
  79. if ( $user->isHidden() && !MediaWikiServices::getInstance()->getPermissionManager()
  80. ->userHasRight( $this->getUser(), 'hideuser' )
  81. ) {
  82. throw new PermissionsError( null, [ 'badaccess-group0' ] );
  83. }
  84. $userpage = Title::makeTitle( NS_USER, $username );
  85. return Status::newGood( [
  86. $userpage->getFullURL( '', false, PROTO_CURRENT ), 302
  87. ] );
  88. }
  89. /**
  90. * Handle Special:Redirect/file/xxxx
  91. *
  92. * @return Status A good status contains the url to redirect to
  93. */
  94. function dispatchFile() {
  95. try {
  96. $title = Title::newFromTextThrow( $this->mValue, NS_FILE );
  97. if ( $title && !$title->inNamespace( NS_FILE ) ) {
  98. // If the given value contains a namespace enforce file namespace
  99. $title = Title::newFromTextThrow( Title::makeName( NS_FILE, $this->mValue ) );
  100. }
  101. } catch ( MalformedTitleException $e ) {
  102. return Status::newFatal( $e->getMessageObject() );
  103. }
  104. $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $title );
  105. if ( !$file || !$file->exists() ) {
  106. // Message: redirect-not-exists
  107. return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
  108. }
  109. // Default behavior: Use the direct link to the file.
  110. $url = $file->getUrl();
  111. $request = $this->getRequest();
  112. $width = $request->getInt( 'width', -1 );
  113. $height = $request->getInt( 'height', -1 );
  114. // If a width is requested...
  115. if ( $width != -1 ) {
  116. $mto = $file->transform( [ 'width' => $width, 'height' => $height ] );
  117. // ... and we can
  118. if ( $mto && !$mto->isError() ) {
  119. // ... change the URL to point to a thumbnail.
  120. // Note: This url is more temporary as can change
  121. // if file is reuploaded and has different aspect ratio.
  122. $url = [ $mto->getUrl(), $height === -1 ? 301 : 302 ];
  123. }
  124. }
  125. return Status::newGood( $url );
  126. }
  127. /**
  128. * Handle Special:Redirect/revision/xxx
  129. * (by redirecting to index.php?oldid=xxx)
  130. *
  131. * @return Status A good status contains the url to redirect to
  132. */
  133. function dispatchRevision() {
  134. $oldid = $this->mValue;
  135. if ( !ctype_digit( $oldid ) ) {
  136. // Message: redirect-not-numeric
  137. return Status::newFatal( $this->getMessagePrefix() . '-not-numeric' );
  138. }
  139. $oldid = (int)$oldid;
  140. if ( $oldid === 0 ) {
  141. // Message: redirect-not-exists
  142. return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
  143. }
  144. return Status::newGood( wfAppendQuery( wfScript( 'index' ), [
  145. 'oldid' => $oldid
  146. ] ) );
  147. }
  148. /**
  149. * Handle Special:Redirect/page/xxx (by redirecting to index.php?curid=xxx)
  150. *
  151. * @return Status A good status contains the url to redirect to
  152. */
  153. function dispatchPage() {
  154. $curid = $this->mValue;
  155. if ( !ctype_digit( $curid ) ) {
  156. // Message: redirect-not-numeric
  157. return Status::newFatal( $this->getMessagePrefix() . '-not-numeric' );
  158. }
  159. $curid = (int)$curid;
  160. if ( $curid === 0 ) {
  161. // Message: redirect-not-exists
  162. return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
  163. }
  164. return Status::newGood( wfAppendQuery( wfScript( 'index' ), [
  165. 'curid' => $curid
  166. ] ) );
  167. }
  168. /**
  169. * Handle Special:Redirect/logid/xxx
  170. * (by redirecting to index.php?title=Special:Log&logid=xxx)
  171. *
  172. * @since 1.27
  173. * @return Status A good status contains the url to redirect to
  174. */
  175. function dispatchLog() {
  176. $logid = $this->mValue;
  177. if ( !ctype_digit( $logid ) ) {
  178. // Message: redirect-not-numeric
  179. return Status::newFatal( $this->getMessagePrefix() . '-not-numeric' );
  180. }
  181. $logid = (int)$logid;
  182. if ( $logid === 0 ) {
  183. // Message: redirect-not-exists
  184. return Status::newFatal( $this->getMessagePrefix() . '-not-exists' );
  185. }
  186. $query = [ 'title' => 'Special:Log', 'logid' => $logid ];
  187. return Status::newGood( wfAppendQuery( wfScript( 'index' ), $query ) );
  188. }
  189. /**
  190. * Use appropriate dispatch* method to obtain a redirection URL,
  191. * and either: redirect, set a 404 error code and error message,
  192. * or do nothing (if $mValue wasn't set) allowing the form to be
  193. * displayed.
  194. *
  195. * @return Status|bool True if a redirect was successfully handled.
  196. */
  197. function dispatch() {
  198. // the various namespaces supported by Special:Redirect
  199. switch ( $this->mType ) {
  200. case 'user':
  201. $status = $this->dispatchUser();
  202. break;
  203. case 'file':
  204. $status = $this->dispatchFile();
  205. break;
  206. case 'revision':
  207. $status = $this->dispatchRevision();
  208. break;
  209. case 'page':
  210. $status = $this->dispatchPage();
  211. break;
  212. case 'logid':
  213. $status = $this->dispatchLog();
  214. break;
  215. default:
  216. $status = null;
  217. break;
  218. }
  219. if ( $status && $status->isGood() ) {
  220. // These urls can sometimes be linked from prominent places,
  221. // so varnish cache.
  222. $value = $status->getValue();
  223. if ( is_array( $value ) ) {
  224. list( $url, $code ) = $value;
  225. } else {
  226. $url = $value;
  227. $code = 301;
  228. }
  229. if ( $code === 301 ) {
  230. $this->getOutput()->setCdnMaxage( 60 * 60 );
  231. } else {
  232. $this->getOutput()->setCdnMaxage( 10 );
  233. }
  234. $this->getOutput()->redirect( $url, $code );
  235. return true;
  236. }
  237. if ( !is_null( $this->mValue ) ) {
  238. $this->getOutput()->setStatusCode( 404 );
  239. return $status;
  240. }
  241. return false;
  242. }
  243. protected function getFormFields() {
  244. $mp = $this->getMessagePrefix();
  245. $ns = [
  246. // subpage => message
  247. // Messages: redirect-user, redirect-page, redirect-revision,
  248. // redirect-file, redirect-logid
  249. 'user' => $mp . '-user',
  250. 'page' => $mp . '-page',
  251. 'revision' => $mp . '-revision',
  252. 'file' => $mp . '-file',
  253. 'logid' => $mp . '-logid',
  254. ];
  255. $a = [];
  256. $a['type'] = [
  257. 'type' => 'select',
  258. 'label-message' => $mp . '-lookup', // Message: redirect-lookup
  259. 'options' => [],
  260. 'default' => current( array_keys( $ns ) ),
  261. ];
  262. foreach ( $ns as $n => $m ) {
  263. $m = $this->msg( $m )->text();
  264. $a['type']['options'][$m] = $n;
  265. }
  266. $a['value'] = [
  267. 'type' => 'text',
  268. 'label-message' => $mp . '-value' // Message: redirect-value
  269. ];
  270. // set the defaults according to the parsed subpage path
  271. if ( !empty( $this->mType ) ) {
  272. $a['type']['default'] = $this->mType;
  273. }
  274. if ( !empty( $this->mValue ) ) {
  275. $a['value']['default'] = $this->mValue;
  276. }
  277. return $a;
  278. }
  279. public function onSubmit( array $data ) {
  280. if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) {
  281. $this->setParameter( $data['type'] . '/' . $data['value'] );
  282. }
  283. /* if this returns false, will show the form */
  284. return $this->dispatch();
  285. }
  286. public function onSuccess() {
  287. /* do nothing, we redirect in $this->dispatch if successful. */
  288. }
  289. protected function alterForm( HTMLForm $form ) {
  290. /* display summary at top of page */
  291. $this->outputHeader();
  292. // tweak label on submit button
  293. // Message: redirect-submit
  294. $form->setSubmitTextMsg( $this->getMessagePrefix() . '-submit' );
  295. /* submit form every time */
  296. $form->setMethod( 'get' );
  297. }
  298. protected function getDisplayFormat() {
  299. return 'ooui';
  300. }
  301. /**
  302. * Return an array of subpages that this special page will accept.
  303. *
  304. * @return string[] subpages
  305. */
  306. protected function getSubpagesForPrefixSearch() {
  307. return [
  308. 'file',
  309. 'page',
  310. 'revision',
  311. 'user',
  312. 'logid',
  313. ];
  314. }
  315. /**
  316. * @return bool
  317. */
  318. public function requiresWrite() {
  319. return false;
  320. }
  321. /**
  322. * @return bool
  323. */
  324. public function requiresUnblock() {
  325. return false;
  326. }
  327. protected function getGroupName() {
  328. return 'redirects';
  329. }
  330. }