Original: https://www.mediawiki.org/wiki/Extension:Comments Das hier ist eine an https://znil.net angepasste Version.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CommentsPage.php 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. <?php
  2. /**
  3. * Class for Comments methods that are not specific to one comments,
  4. * but specific to one comment-using page
  5. */
  6. class CommentsPage extends ContextSource {
  7. /**
  8. * @var Integer: page ID (page.page_id) of this page.
  9. */
  10. public $id = 0;
  11. /**
  12. * @var Integer: if this is _not_ 0, then the comments are ordered by their
  13. * Comment_Score in descending order
  14. */
  15. public $orderBy = 0;
  16. /**
  17. * @var Integer: maximum amount of threads of comments shown per page before pagination is enabled;
  18. */
  19. public $limit = 50;
  20. /**
  21. * @TODO document
  22. *
  23. * @var int
  24. */
  25. public $pagerLimit = 9;
  26. /**
  27. * The current page of comments we are paged to
  28. *
  29. * @var int
  30. */
  31. public $currentPagerPage = 0;
  32. /**
  33. * List of users allowed to comment. Empty string - any user can comment
  34. *
  35. * @var string
  36. */
  37. public $allow = '';
  38. /**
  39. * What voting to disallow - disallow PLUS, MINUS, or BOTH
  40. *
  41. * @var string
  42. */
  43. public $voting = '';
  44. /**
  45. * @var Boolean: allow positive (plus) votes?
  46. */
  47. public $allowPlus = true;
  48. /**
  49. * @var Boolean: allow negative (minus) votes?
  50. */
  51. public $allowMinus = true;
  52. /**
  53. * @TODO document
  54. *
  55. * @var string
  56. */
  57. public $pageQuery = 'cpage';
  58. /**
  59. * @var Title: title object for this page
  60. */
  61. public $title = null;
  62. /**
  63. * List of lists of comments on this page.
  64. * Each list is a separate 'thread' of comments, with the parent comment first, and any replies following
  65. * Not populated until display() is called
  66. *
  67. * @var array
  68. */
  69. public $comments = [];
  70. /**
  71. * Constructor
  72. *
  73. * @param $pageID: current page ID
  74. */
  75. function __construct( $pageID, $context ) {
  76. $this->id = $pageID;
  77. $this->setContext( $context );
  78. $this->title = Title::newFromID( $pageID );
  79. }
  80. /**
  81. * Gets the total amount of comments on this page
  82. *
  83. * @return int
  84. */
  85. function countTotal() {
  86. $dbr = wfGetDB( DB_REPLICA );
  87. $count = 0;
  88. $s = $dbr->selectRow(
  89. 'Comments',
  90. [ 'COUNT(*) AS CommentCount' ],
  91. [ 'Comment_Page_ID' => $this->id ],
  92. __METHOD__
  93. );
  94. if ( $s !== false ) {
  95. $count = $s->CommentCount;
  96. }
  97. return $count;
  98. }
  99. /**
  100. * Gets the ID number of the latest comment for the current page.
  101. *
  102. * @return int
  103. */
  104. function getLatestCommentID() {
  105. $latestCommentID = 0;
  106. $dbr = wfGetDB( DB_REPLICA );
  107. $s = $dbr->selectRow(
  108. 'Comments',
  109. [ 'CommentID' ],
  110. [ 'Comment_Page_ID' => $this->id ],
  111. __METHOD__,
  112. [ 'ORDER BY' => 'Comment_Date DESC', 'LIMIT' => 1 ]
  113. );
  114. if ( $s !== false ) {
  115. $latestCommentID = $s->CommentID;
  116. }
  117. return $latestCommentID;
  118. }
  119. /**
  120. * Set voting either totally off, or disallow "thumbs down" or disallow
  121. * "thumbs up".
  122. *
  123. * @param string $voting 'OFF', 'PLUS' or 'MINUS' (will be strtoupper()ed)
  124. */
  125. function setVoting( $voting ) {
  126. $this->voting = $voting;
  127. $voting = strtoupper( $voting );
  128. if ( $voting == 'OFF' ) {
  129. $this->allowMinus = false;
  130. $this->allowPlus = false;
  131. }
  132. if ( $voting == 'PLUS' ) {
  133. $this->allowMinus = false;
  134. }
  135. if ( $voting == 'MINUS' ) {
  136. $this->allowPlus = false;
  137. }
  138. }
  139. /**
  140. * Fetches all comments, called by display().
  141. *
  142. * @return array Array containing every possible bit of information about
  143. * a comment, including score, timestamp and more
  144. */
  145. public function getComments() {
  146. $dbr = wfGetDB( DB_REPLICA );
  147. // Defaults (for non-social wikis)
  148. $tables = [
  149. 'Comments',
  150. 'vote1' => 'Comments_Vote',
  151. 'vote2' => 'Comments_Vote',
  152. ];
  153. $fields = [
  154. 'Comment_Username', 'Comment_IP', 'Comment_Text',
  155. 'Comment_Date', 'Comment_Date AS timestamp',
  156. 'Comment_user_id', 'CommentID', 'Comment_Parent_ID',
  157. 'vote1.Comment_Vote_Score AS current_vote',
  158. 'SUM(vote2.Comment_Vote_Score) AS comment_score'
  159. ];
  160. $joinConds = [
  161. // For current user's vote
  162. 'vote1' => [
  163. 'LEFT JOIN',
  164. [
  165. 'vote1.Comment_Vote_ID = CommentID',
  166. 'vote1.Comment_Vote_Username' => $this->getUser()->getName()
  167. ]
  168. ],
  169. // For total vote count
  170. 'vote2' => [ 'LEFT JOIN', 'vote2.Comment_Vote_ID = CommentID' ]
  171. ];
  172. $params = [ 'GROUP BY' => 'CommentID' ];
  173. // If SocialProfile is installed, query the user_stats table too.
  174. if (
  175. class_exists( 'UserProfile' ) &&
  176. $dbr->tableExists( 'user_stats' )
  177. ) {
  178. $tables[] = 'user_stats';
  179. $fields[] = 'stats_total_points';
  180. $joinConds['Comments'] = [
  181. 'LEFT JOIN', 'Comment_user_id = stats_user_id'
  182. ];
  183. }
  184. // Perform the query
  185. $res = $dbr->select(
  186. $tables,
  187. $fields,
  188. [ 'Comment_Page_ID' => $this->id ],
  189. __METHOD__,
  190. $params,
  191. $joinConds
  192. );
  193. $comments = [];
  194. foreach ( $res as $row ) {
  195. if ( $row->Comment_Parent_ID == 0 ) {
  196. $thread = $row->CommentID;
  197. } else {
  198. $thread = $row->Comment_Parent_ID;
  199. }
  200. $data = [
  201. 'Comment_Username' => $row->Comment_Username,
  202. 'Comment_IP' => $row->Comment_IP,
  203. 'Comment_Text' => $row->Comment_Text,
  204. 'Comment_Date' => $row->Comment_Date,
  205. 'Comment_user_id' => $row->Comment_user_id,
  206. 'Comment_user_points' => ( isset( $row->stats_total_points ) ? number_format( $row->stats_total_points ) : 0 ),
  207. 'CommentID' => $row->CommentID,
  208. 'Comment_Parent_ID' => $row->Comment_Parent_ID,
  209. 'thread' => $thread,
  210. 'timestamp' => wfTimestamp( TS_UNIX, $row->timestamp ),
  211. 'current_vote' => ( isset( $row->current_vote ) ? $row->current_vote : false ),
  212. 'total_vote' => ( isset( $row->comment_score ) ? $row->comment_score : 0 ),
  213. ];
  214. $comments[] = new Comment( $this, $this->getContext(), $data );
  215. }
  216. $commentThreads = [];
  217. foreach ( $comments as $comment ) {
  218. if ( $comment->parentID == 0 ) {
  219. $commentThreads[$comment->id] = [ $comment ];
  220. } else {
  221. $commentThreads[$comment->parentID][] = $comment;
  222. }
  223. }
  224. return $commentThreads;
  225. }
  226. /**
  227. * @return int The page we are currently paged to
  228. * not used for any API calls
  229. */
  230. function getCurrentPagerPage() {
  231. if ( $this->currentPagerPage == 0 ) {
  232. $this->currentPagerPage = $this->getRequest()->getInt( $this->pageQuery, 1 );
  233. if ( $this->currentPagerPage < 1 ) {
  234. $this->currentPagerPage = 1;
  235. }
  236. }
  237. return $this->currentPagerPage;
  238. }
  239. /**
  240. * Display pager for the current page.
  241. *
  242. * @param int $pagerCurrent Page we are currently paged to
  243. * @param int $pagesCount The maximum page number
  244. *
  245. * @return string the links for paging through pages of comments
  246. */
  247. function displayPager( $pagerCurrent, $pagesCount ) {
  248. // Middle is used to "center" pages around the current page.
  249. $pager_middle = ceil( $this->pagerLimit / 2 );
  250. // first is the first page listed by this pager piece (re quantity)
  251. $pagerFirst = $pagerCurrent - $pager_middle + 1;
  252. // last is the last page listed by this pager piece (re quantity)
  253. $pagerLast = $pagerCurrent + $this->pagerLimit - $pager_middle;
  254. // Prepare for generation loop.
  255. $i = $pagerFirst;
  256. if ( $pagerLast > $pagesCount ) {
  257. // Adjust "center" if at end of query.
  258. $i = $i + ( $pagesCount - $pagerLast );
  259. $pagerLast = $pagesCount;
  260. }
  261. if ( $i <= 0 ) {
  262. // Adjust "center" if at start of query.
  263. $pagerLast = $pagerLast + ( 1 - $i );
  264. $i = 1;
  265. }
  266. $output = '';
  267. if ( $pagesCount > 1 ) {
  268. $output .= '<ul class="c-pager">';
  269. $pagerEllipsis = '<li class="c-pager-item c-pager-ellipsis"><span>...</span></li>';
  270. // Whether to display the "Previous page" link
  271. if ( $pagerCurrent > 1 ) {
  272. $output .= '<li class="c-pager-item c-pager-previous">' .
  273. Html::rawElement(
  274. 'a',
  275. [
  276. 'rel' => 'nofollow',
  277. 'class' => 'c-pager-link',
  278. 'href' => '#cfirst',
  279. 'data-' . $this->pageQuery => ( $pagerCurrent - 1 ),
  280. ],
  281. '&lt;'
  282. ) .
  283. '</li>';
  284. }
  285. // Whether to display the "First page" link
  286. if ( $i > 1 ) {
  287. $output .= '<li class="c-pager-item c-pager-first">' .
  288. Html::rawElement(
  289. 'a',
  290. [
  291. 'rel' => 'nofollow',
  292. 'class' => 'c-pager-link',
  293. 'href' => '#cfirst',
  294. 'data-' . $this->pageQuery => 1,
  295. ],
  296. 1
  297. ) .
  298. '</li>';
  299. }
  300. // When there is more than one page, create the pager list.
  301. if ( $i != $pagesCount ) {
  302. if ( $i > 2 ) {
  303. $output .= $pagerEllipsis;
  304. }
  305. // Now generate the actual pager piece.
  306. for ( ; $i <= $pagerLast && $i <= $pagesCount; $i++ ) {
  307. if ( $i == $pagerCurrent ) {
  308. $output .= '<li class="c-pager-item c-pager-current"><span>' .
  309. $i . '</span></li>';
  310. } else {
  311. $output .= '<li class="c-pager-item">' .
  312. Html::rawElement(
  313. 'a',
  314. [
  315. 'rel' => 'nofollow',
  316. 'class' => 'c-pager-link',
  317. 'href' => '#cfirst',
  318. 'data-' . $this->pageQuery => $i,
  319. ],
  320. $i
  321. ) .
  322. '</li>';
  323. }
  324. }
  325. if ( $i < $pagesCount ) {
  326. $output .= $pagerEllipsis;
  327. }
  328. }
  329. // Whether to display the "Last page" link
  330. if ( $pagesCount > ( $i - 1 ) ) {
  331. $output .= '<li class="c-pager-item c-pager-last">' .
  332. Html::rawElement(
  333. 'a',
  334. [
  335. 'rel' => 'nofollow',
  336. 'class' => 'c-pager-link',
  337. 'href' => '#cfirst',
  338. 'data-' . $this->pageQuery => $pagesCount,
  339. ],
  340. $pagesCount
  341. ) .
  342. '</li>';
  343. }
  344. // Whether to display the "Next page" link
  345. if ( $pagerCurrent < $pagesCount ) {
  346. $output .= '<li class="c-pager-item c-pager-next">' .
  347. Html::rawElement(
  348. 'a',
  349. [
  350. 'rel' => 'nofollow',
  351. 'class' => 'c-pager-link',
  352. 'href' => '#cfirst',
  353. 'data-' . $this->pageQuery => ( $pagerCurrent + 1 ),
  354. ],
  355. '&gt;'
  356. ) .
  357. '</li>';
  358. }
  359. $output .= '</ul>';
  360. }
  361. return $output;
  362. }
  363. /**
  364. * Get this list of anon commenters in the given list of comments,
  365. * and return a mapped array of IP adressess to the number anon poster
  366. * (so anon posters can be called Anon#1, Anon#2, etc
  367. *
  368. * @return array
  369. */
  370. function getAnonList() {
  371. $counter = 1;
  372. $bucket = [];
  373. $commentThreads = $this->comments;
  374. $comments = []; // convert 2nd threads array to a simple list of comments
  375. foreach ( $commentThreads as $thread ) {
  376. $comments = array_merge( $comments, $thread );
  377. }
  378. usort( $comments, [ 'CommentFunctions', 'sortTime' ] );
  379. foreach ( $comments as $comment ) {
  380. if (
  381. !array_key_exists( $comment->username, $bucket ) &&
  382. $comment->userID == 0
  383. ) {
  384. $bucket[$comment->username] = $counter;
  385. $counter++;
  386. }
  387. }
  388. return $bucket;
  389. }
  390. /**
  391. * Sort an array of comment threads
  392. * @param $threads
  393. * @return mixed
  394. */
  395. function sort( $threads ) {
  396. global $wgCommentsSortDescending;
  397. if ( $this->orderBy ) {
  398. usort( $threads, [ 'CommentFunctions', 'sortScore' ] );
  399. } elseif ( $wgCommentsSortDescending ) {
  400. usort( $threads, [ 'CommentFunctions', 'sortDesc' ] );
  401. } else {
  402. usort( $threads, [ 'CommentFunctions', 'sortAsc' ] );
  403. }
  404. return $threads;
  405. }
  406. /**
  407. * Convert an array of comment threads into an array of pages (arrays) of comment threads
  408. * @param $comments
  409. * @return array
  410. */
  411. function page( $comments ) {
  412. return array_chunk( $comments, $this->limit );
  413. }
  414. /**
  415. * Display all the comments for the current page.
  416. * CSS and JS is loaded in CommentsHooks.php
  417. */
  418. function display() {
  419. $output = '';
  420. $commentThreads = $this->getComments();
  421. $commentThreads = $this->sort( $commentThreads );
  422. $this->comments = $commentThreads;
  423. $commentPages = $this->page( $commentThreads );
  424. $currentPageNum = $this->getCurrentPagerPage();
  425. $numPages = count( $commentPages );
  426. // Suppress random E_NOTICE about "Undefined offset: 0", which seems to
  427. // be breaking ProblemReports (at least on my local devbox, not sure
  428. // about prod). --Jack Phoenix, 13 July 2015
  429. Wikimedia\suppressWarnings();
  430. $currentPage = $commentPages[$currentPageNum - 1];
  431. Wikimedia\restoreWarnings();
  432. // Load complete blocked list for logged in user so they don't see their comments
  433. $blockList = [];
  434. if ( $this->getUser()->getId() != 0 ) {
  435. $blockList = CommentFunctions::getBlockList( $this->getUser()->getId() );
  436. }
  437. if ( $currentPage ) {
  438. $pager = $this->displayPager( $currentPageNum, $numPages );
  439. $output .= $pager;
  440. $output .= '<a id="cfirst" name="cfirst" rel="nofollow"></a>';
  441. $anonList = $this->getAnonList();
  442. foreach ( $currentPage as $thread ) {
  443. foreach ( $thread as $comment ) {
  444. $output .= $comment->display( $blockList, $anonList );
  445. }
  446. }
  447. $output .= $pager;
  448. }
  449. return $output;
  450. }
  451. /**
  452. * Displays the "Sort by X" form and a link to auto-refresh comments
  453. *
  454. * @return string HTML
  455. */
  456. // ## START ## 25.05.2019 von Bernhard Linz ########################################################################################
  457. function displayOrderForm() {
  458. $output = '<div id="spy" class="c-spy">
  459. <a href="javascript:void(0)">' .
  460. wfMessage( 'comments-auto-refresher-enable' )->plain() .
  461. '</a>
  462. </div>
  463. <div class="visualClear"></div>
  464. <br />' . "\n";
  465. /* $output = '<div class="c-order">
  466. <div class="c-order-select">
  467. <form name="ChangeOrder" action="">
  468. <select name="TheOrder">
  469. <option value="0">' .
  470. wfMessage( 'comments-sort-by-date' )->plain() .
  471. '</option>
  472. <option value="1">' .
  473. wfMessage( 'comments-sort-by-score' )->plain() .
  474. '</option>
  475. </select>
  476. </form>
  477. </div>
  478. <div id="spy" class="c-spy">
  479. <a href="javascript:void(0)">' .
  480. wfMessage( 'comments-auto-refresher-enable' )->plain() .
  481. '</a>
  482. </div>
  483. <div class="visualClear"></div>
  484. </div>
  485. <br />' . "\n";
  486. */
  487. // ## ENDE ## 25.05.2019 von Bernhard Linz ########################################################################################
  488. return $output;
  489. }
  490. /**
  491. * Displays the form for adding new comments
  492. *
  493. * @return string HTML output
  494. */
  495. function displayForm() {
  496. $output = '<form action="" method="post" name="commentForm">' . "\n";
  497. if ( $this->allow ) {
  498. $pos = strpos(
  499. strtoupper( addslashes( $this->allow ) ),
  500. strtoupper( addslashes( $this->getUser()->getName() ) )
  501. );
  502. }
  503. // 'comment' user right is required to add new comments
  504. if ( !$this->getUser()->isAllowed( 'comment' ) ) {
  505. $output .= wfMessage( 'comments-not-allowed' )->parse();
  506. } else {
  507. // Blocked users can't add new comments under any conditions...
  508. // and maybe there's a list of users who should be allowed to post
  509. // comments
  510. if ( $this->getUser()->isBlocked() == false && ( $this->allow == '' || $pos !== false ) ) {
  511. $output .= '<div class="c-form-title">' . wfMessage( 'comments-submit' )->plain() . '</div>' . "\n";
  512. $output .= '<div id="replyto" class="c-form-reply-to"></div>' . "\n";
  513. // Show a message to anons, prompting them to register or log in
  514. if ( !$this->getUser()->isLoggedIn() ) {
  515. $login_title = SpecialPage::getTitleFor( 'Userlogin' );
  516. $register_title = SpecialPage::getTitleFor( 'Userlogin', 'signup' );
  517. $output .= '<div class="c-form-message">' . wfMessage(
  518. 'comments-anon-message',
  519. htmlspecialchars( $register_title->getFullURL() ),
  520. htmlspecialchars( $login_title->getFullURL() )
  521. )->text() . '</div>' . "\n";
  522. }
  523. // ## START ## 25.05.2019 von Bernhard Linz ########################################################################################
  524. if ( !$this->getUser()->isLoggedIn() ) {
  525. $output .= '<p><label for="txt_username">Name oder Emailadresse oder leer lassen:</label><br /><input style="margin: 0px; width: 530px;" type="text" name="txt_username" id="txt_username" />' . "</p>";
  526. } else {
  527. $output .= '<p>Benutzer:<b>' . $this->getUser()->getName() . '</b></p>';
  528. }
  529. // ## ENDE ## 25.05.2019 von Bernhard Linz ########################################################################################
  530. $output .= '<textarea name="commentText" id="comment" rows="5" cols="64"></textarea>' . "\n";
  531. $output .= '<div class="c-form-button"><input type="button" value="' .
  532. wfMessage( 'comments-post' )->plain() . '" class="site-button" /></div>' . "\n";
  533. }
  534. $output .= '<input type="hidden" name="action" value="purge" />' . "\n";
  535. $output .= '<input type="hidden" name="pageId" value="' . $this->id . '" />' . "\n";
  536. $output .= '<input type="hidden" name="commentid" />' . "\n";
  537. $output .= '<input type="hidden" name="lastCommentId" value="' . $this->getLatestCommentID() . '" />' . "\n";
  538. $output .= '<input type="hidden" name="commentParentId" />' . "\n";
  539. $output .= '<input type="hidden" name="' . $this->pageQuery . '" value="' . $this->getCurrentPagerPage() . '" />' . "\n";
  540. $output .= Html::hidden( 'token', $this->getUser()->getEditToken() );
  541. }
  542. $output .= '</form>' . "\n";
  543. return $output;
  544. }
  545. /**
  546. * Purge caches (parser cache and Squid cache)
  547. */
  548. function clearCommentListCache() {
  549. wfDebug( "Clearing comments for page {$this->id} from cache\n" );
  550. if ( is_object( $this->title ) ) {
  551. $this->title->invalidateCache();
  552. $this->title->purgeSquid();
  553. }
  554. }
  555. }