SpecialNewpages.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. <?php
  2. /**
  3. * implements Special:Newpages
  4. * @ingroup SpecialPage
  5. */
  6. class SpecialNewpages extends SpecialPage {
  7. // Stored objects
  8. protected $opts, $skin;
  9. // Some internal settings
  10. protected $showNavigation = false;
  11. public function __construct() {
  12. parent::__construct( 'Newpages' );
  13. $this->includable( true );
  14. }
  15. protected function setup( $par ) {
  16. global $wgRequest, $wgUser, $wgEnableNewpagesUserFilter;
  17. // Options
  18. $opts = new FormOptions();
  19. $this->opts = $opts; // bind
  20. $opts->add( 'hideliu', false );
  21. $opts->add( 'hidepatrolled', $wgUser->getBoolOption( 'newpageshidepatrolled' ) );
  22. $opts->add( 'hidebots', false );
  23. $opts->add( 'hideredirs', true );
  24. $opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) );
  25. $opts->add( 'offset', '' );
  26. $opts->add( 'namespace', '0' );
  27. $opts->add( 'username', '' );
  28. $opts->add( 'feed', '' );
  29. $opts->add( 'tagfilter', '' );
  30. // Set values
  31. $opts->fetchValuesFromRequest( $wgRequest );
  32. if ( $par ) $this->parseParams( $par );
  33. // Validate
  34. $opts->validateIntBounds( 'limit', 0, 5000 );
  35. if( !$wgEnableNewpagesUserFilter ) {
  36. $opts->setValue( 'username', '' );
  37. }
  38. // Store some objects
  39. $this->skin = $wgUser->getSkin();
  40. }
  41. protected function parseParams( $par ) {
  42. global $wgLang;
  43. $bits = preg_split( '/\s*,\s*/', trim( $par ) );
  44. foreach ( $bits as $bit ) {
  45. if ( 'shownav' == $bit )
  46. $this->showNavigation = true;
  47. if ( 'hideliu' === $bit )
  48. $this->opts->setValue( 'hideliu', true );
  49. if ( 'hidepatrolled' == $bit )
  50. $this->opts->setValue( 'hidepatrolled', true );
  51. if ( 'hidebots' == $bit )
  52. $this->opts->setValue( 'hidebots', true );
  53. if ( 'showredirs' == $bit )
  54. $this->opts->setValue( 'hideredirs', false );
  55. if ( is_numeric( $bit ) )
  56. $this->opts->setValue( 'limit', intval( $bit ) );
  57. $m = array();
  58. if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) )
  59. $this->opts->setValue( 'limit', intval($m[1]) );
  60. // PG offsets not just digits!
  61. if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) )
  62. $this->opts->setValue( 'offset', intval($m[1]) );
  63. if ( preg_match( '/^username=(.*)$/', $bit, $m ) )
  64. $this->opts->setValue( 'username', $m[1] );
  65. if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
  66. $ns = $wgLang->getNsIndex( $m[1] );
  67. if( $ns !== false ) {
  68. $this->opts->setValue( 'namespace', $ns );
  69. }
  70. }
  71. }
  72. }
  73. /**
  74. * Show a form for filtering namespace and username
  75. *
  76. * @param string $par
  77. * @return string
  78. */
  79. public function execute( $par ) {
  80. global $wgLang, $wgUser, $wgOut;
  81. $this->setHeaders();
  82. $this->outputHeader();
  83. $this->showNavigation = !$this->including(); // Maybe changed in setup
  84. $this->setup( $par );
  85. if( !$this->including() ) {
  86. // Settings
  87. $this->form();
  88. $this->setSyndicated();
  89. $feedType = $this->opts->getValue( 'feed' );
  90. if( $feedType ) {
  91. return $this->feed( $feedType );
  92. }
  93. }
  94. $pager = new NewPagesPager( $this, $this->opts );
  95. $pager->mLimit = $this->opts->getValue( 'limit' );
  96. $pager->mOffset = $this->opts->getValue( 'offset' );
  97. if( $pager->getNumRows() ) {
  98. $navigation = '';
  99. if ( $this->showNavigation ) $navigation = $pager->getNavigationBar();
  100. $wgOut->addHTML( $navigation . $pager->getBody() . $navigation );
  101. } else {
  102. $wgOut->addWikiMsg( 'specialpage-empty' );
  103. }
  104. }
  105. protected function filterLinks() {
  106. global $wgGroupPermissions, $wgUser, $wgLang;
  107. // show/hide links
  108. $showhide = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) );
  109. // Option value -> message mapping
  110. $filters = array(
  111. 'hideliu' => 'rcshowhideliu',
  112. 'hidepatrolled' => 'rcshowhidepatr',
  113. 'hidebots' => 'rcshowhidebots',
  114. 'hideredirs' => 'whatlinkshere-hideredirs'
  115. );
  116. // Disable some if needed
  117. if ( $wgGroupPermissions['*']['createpage'] !== true )
  118. unset($filters['hideliu']);
  119. if ( !$wgUser->useNPPatrol() )
  120. unset($filters['hidepatrolled']);
  121. $links = array();
  122. $changed = $this->opts->getChangedValues();
  123. unset($changed['offset']); // Reset offset if query type changes
  124. $self = $this->getTitle();
  125. foreach ( $filters as $key => $msg ) {
  126. $onoff = 1 - $this->opts->getValue($key);
  127. $link = $this->skin->link( $self, $showhide[$onoff], array(),
  128. array( $key => $onoff ) + $changed
  129. );
  130. $links[$key] = wfMsgHtml( $msg, $link );
  131. }
  132. return $wgLang->pipeList( $links );
  133. }
  134. protected function form() {
  135. global $wgOut, $wgEnableNewpagesUserFilter, $wgScript;
  136. // Consume values
  137. $this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW
  138. $namespace = $this->opts->consumeValue( 'namespace' );
  139. $username = $this->opts->consumeValue( 'username' );
  140. // Check username input validity
  141. $ut = Title::makeTitleSafe( NS_USER, $username );
  142. $userText = $ut ? $ut->getText() : '';
  143. // Store query values in hidden fields so that form submission doesn't lose them
  144. $hidden = array();
  145. foreach ( $this->opts->getUnconsumedValues() as $key => $value ) {
  146. $hidden[] = Xml::hidden( $key, $value );
  147. }
  148. $hidden = implode( "\n", $hidden );
  149. $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] );
  150. if ($tagFilter)
  151. list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter;
  152. $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) .
  153. Xml::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
  154. Xml::fieldset( wfMsg( 'newpages' ) ) .
  155. Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) .
  156. "<tr>
  157. <td class='mw-label'>" .
  158. Xml::label( wfMsg( 'namespace' ), 'namespace' ) .
  159. "</td>
  160. <td class='mw-input'>" .
  161. Xml::namespaceSelector( $namespace, 'all' ) .
  162. "</td>
  163. </tr>" . ( $tagFilter ? (
  164. "<tr>
  165. <td class='mw-label'>" .
  166. $tagFilterLabel .
  167. "</td>
  168. <td class='mw-input'>" .
  169. $tagFilterSelector .
  170. "</td>
  171. </tr>" ) : '' ) .
  172. ($wgEnableNewpagesUserFilter ?
  173. "<tr>
  174. <td class='mw-label'>" .
  175. Xml::label( wfMsg( 'newpages-username' ), 'mw-np-username' ) .
  176. "</td>
  177. <td class='mw-input'>" .
  178. Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) .
  179. "</td>
  180. </tr>" : "" ) .
  181. "<tr> <td></td>
  182. <td class='mw-submit'>" .
  183. Xml::submitButton( wfMsg( 'allpagessubmit' ) ) .
  184. "</td>
  185. </tr>" .
  186. "<tr>
  187. <td></td>
  188. <td class='mw-input'>" .
  189. $this->filterLinks() .
  190. "</td>
  191. </tr>" .
  192. Xml::closeElement( 'table' ) .
  193. Xml::closeElement( 'fieldset' ) .
  194. $hidden .
  195. Xml::closeElement( 'form' );
  196. $wgOut->addHTML( $form );
  197. }
  198. protected function setSyndicated() {
  199. global $wgOut;
  200. $queryParams = array(
  201. 'namespace' => $this->opts->getValue( 'namespace' ),
  202. 'username' => $this->opts->getValue( 'username' )
  203. );
  204. $wgOut->setSyndicated( true );
  205. $wgOut->setFeedAppendQuery( wfArrayToCGI( $queryParams ) );
  206. }
  207. /**
  208. * Format a row, providing the timestamp, links to the page/history, size, user links, and a comment
  209. *
  210. * @param $skin Skin to use
  211. * @param $result Result row
  212. * @return string
  213. */
  214. public function formatRow( $result ) {
  215. global $wgLang, $wgContLang, $wgUser;
  216. $classes = array();
  217. $dm = $wgContLang->getDirMark();
  218. $title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title );
  219. $time = $wgLang->timeAndDate( $result->rc_timestamp, true );
  220. $query = $this->patrollable( $result ) ? "rcid={$result->rc_id}&redirect=no" : 'redirect=no';
  221. $plink = $this->skin->makeKnownLinkObj( $title, '', $query );
  222. $hist = $this->skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' );
  223. $length = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
  224. $wgLang->formatNum( $result->length ) );
  225. $ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' .
  226. $this->skin->userToolLinks( $result->rc_user, $result->rc_user_text );
  227. $comment = $this->skin->commentBlock( $result->rc_comment );
  228. if ( $this->patrollable( $result ) )
  229. $classes[] = 'not-patrolled';
  230. # Tags, if any.
  231. list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' );
  232. $classes = array_merge( $classes, $newClasses );
  233. $css = count($classes) ? ' class="'.implode( " ", $classes).'"' : '';
  234. return "<li{$css}>{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n";
  235. }
  236. /**
  237. * Should a specific result row provide "patrollable" links?
  238. *
  239. * @param $result Result row
  240. * @return bool
  241. */
  242. protected function patrollable( $result ) {
  243. global $wgUser;
  244. return ( $wgUser->useNPPatrol() && !$result->rc_patrolled );
  245. }
  246. /**
  247. * Output a subscription feed listing recent edits to this page.
  248. * @param string $type
  249. */
  250. protected function feed( $type ) {
  251. global $wgFeed, $wgFeedClasses, $wgFeedLimit;
  252. if ( !$wgFeed ) {
  253. global $wgOut;
  254. $wgOut->addWikiMsg( 'feed-unavailable' );
  255. return;
  256. }
  257. if( !isset( $wgFeedClasses[$type] ) ) {
  258. global $wgOut;
  259. $wgOut->addWikiMsg( 'feed-invalid' );
  260. return;
  261. }
  262. $feed = new $wgFeedClasses[$type](
  263. $this->feedTitle(),
  264. wfMsgExt( 'tagline', 'parsemag' ),
  265. $this->getTitle()->getFullUrl() );
  266. $pager = new NewPagesPager( $this, $this->opts );
  267. $limit = $this->opts->getValue( 'limit' );
  268. $pager->mLimit = min( $limit, $wgFeedLimit );
  269. $feed->outHeader();
  270. if( $pager->getNumRows() > 0 ) {
  271. while( $row = $pager->mResult->fetchObject() ) {
  272. $feed->outItem( $this->feedItem( $row ) );
  273. }
  274. }
  275. $feed->outFooter();
  276. }
  277. protected function feedTitle() {
  278. global $wgContLanguageCode, $wgSitename;
  279. $page = SpecialPage::getPage( 'Newpages' );
  280. $desc = $page->getDescription();
  281. return "$wgSitename - $desc [$wgContLanguageCode]";
  282. }
  283. protected function feedItem( $row ) {
  284. $title = Title::MakeTitle( intval( $row->rc_namespace ), $row->rc_title );
  285. if( $title ) {
  286. $date = $row->rc_timestamp;
  287. $comments = $title->getTalkPage()->getFullURL();
  288. return new FeedItem(
  289. $title->getPrefixedText(),
  290. $this->feedItemDesc( $row ),
  291. $title->getFullURL(),
  292. $date,
  293. $this->feedItemAuthor( $row ),
  294. $comments);
  295. } else {
  296. return NULL;
  297. }
  298. }
  299. protected function feedItemAuthor( $row ) {
  300. return isset( $row->rc_user_text ) ? $row->rc_user_text : '';
  301. }
  302. protected function feedItemDesc( $row ) {
  303. $revision = Revision::newFromId( $row->rev_id );
  304. if( $revision ) {
  305. return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) .
  306. htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
  307. "</p>\n<hr />\n<div>" .
  308. nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
  309. }
  310. return '';
  311. }
  312. }
  313. /**
  314. * @ingroup SpecialPage Pager
  315. */
  316. class NewPagesPager extends ReverseChronologicalPager {
  317. // Stored opts
  318. protected $opts, $mForm;
  319. function __construct( $form, FormOptions $opts ) {
  320. parent::__construct();
  321. $this->mForm = $form;
  322. $this->opts = $opts;
  323. }
  324. function getTitle() {
  325. static $title = null;
  326. if ( $title === null )
  327. $title = $this->mForm->getTitle();
  328. return $title;
  329. }
  330. function getQueryInfo() {
  331. global $wgEnableNewpagesUserFilter, $wgGroupPermissions, $wgUser;
  332. $conds = array();
  333. $conds['rc_new'] = 1;
  334. $namespace = $this->opts->getValue( 'namespace' );
  335. $namespace = ( $namespace === 'all' ) ? false : intval( $namespace );
  336. $username = $this->opts->getValue( 'username' );
  337. $user = Title::makeTitleSafe( NS_USER, $username );
  338. if( $namespace !== false ) {
  339. $conds['rc_namespace'] = $namespace;
  340. $rcIndexes = array( 'new_name_timestamp' );
  341. } else {
  342. $rcIndexes = array( 'rc_timestamp' );
  343. }
  344. # $wgEnableNewpagesUserFilter - temp WMF hack
  345. if( $wgEnableNewpagesUserFilter && $user ) {
  346. $conds['rc_user_text'] = $user->getText();
  347. $rcIndexes = 'rc_user_text';
  348. # If anons cannot make new pages, don't "exclude logged in users"!
  349. } elseif( $wgGroupPermissions['*']['createpage'] && $this->opts->getValue( 'hideliu' ) ) {
  350. $conds['rc_user'] = 0;
  351. }
  352. # If this user cannot see patrolled edits or they are off, don't do dumb queries!
  353. if( $this->opts->getValue( 'hidepatrolled' ) && $wgUser->useNPPatrol() ) {
  354. $conds['rc_patrolled'] = 0;
  355. }
  356. if( $this->opts->getValue( 'hidebots' ) ) {
  357. $conds['rc_bot'] = 0;
  358. }
  359. if ( $this->opts->getValue( 'hideredirs' ) ) {
  360. $conds['page_is_redirect'] = 0;
  361. }
  362. $info = array(
  363. 'tables' => array( 'recentchanges', 'page' ),
  364. 'fields' => 'rc_namespace,rc_title, rc_cur_id, rc_user,rc_user_text,rc_comment,
  365. rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id, ts_tags',
  366. 'conds' => $conds,
  367. 'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ),
  368. 'join_conds' => array(
  369. 'page' => array('INNER JOIN', 'page_id=rc_cur_id'),
  370. ),
  371. );
  372. ## Empty array for fields, it'll be set by us anyway.
  373. $fields = array();
  374. ## Modify query for tags
  375. ChangeTags::modifyDisplayQuery( $info['tables'],
  376. $fields,
  377. $info['conds'],
  378. $info['join_conds'],
  379. $info['options'],
  380. $this->opts['tagfilter'] );
  381. return $info;
  382. }
  383. function getIndexField() {
  384. return 'rc_timestamp';
  385. }
  386. function formatRow( $row ) {
  387. return $this->mForm->formatRow( $row );
  388. }
  389. function getStartBody() {
  390. # Do a batch existence check on pages
  391. $linkBatch = new LinkBatch();
  392. while( $row = $this->mResult->fetchObject() ) {
  393. $linkBatch->add( NS_USER, $row->rc_user_text );
  394. $linkBatch->add( NS_USER_TALK, $row->rc_user_text );
  395. $linkBatch->add( $row->rc_namespace, $row->rc_title );
  396. }
  397. $linkBatch->execute();
  398. return "<ul>";
  399. }
  400. function getEndBody() {
  401. return "</ul>";
  402. }
  403. }