first commit

This commit is contained in:
root
2017-09-27 17:53:26 +02:00
commit a01e7fe597
139 changed files with 7890 additions and 0 deletions

858
includes/Comment.class.php Normal file
View File

@@ -0,0 +1,858 @@
<?php
/**
* Comment class
* Functions for managing comments and everything related to them, including:
* -blocking comments from a given user
* -counting the total amount of comments in the database
* -displaying the form for adding a new comment
* -getting all comments for a given page
*
* @file
* @ingroup Extensions
*/
class Comment extends ContextSource {
/**
* @var CommentsPage: page of the page the <comments /> tag is in
*/
public $page = null;
/**
* @var Integer: total amount of comments by distinct commenters that the
* current page has
*/
public $commentTotal = 0;
/**
* @var String: text of the current comment
*/
public $text = null;
/* START Anpassung znilwiki */
public $CommentUsernameKOK = null; /* 25.10.2013 von Kai-Ole Kirsten */
/* ENDE Anpassung znilwiki */
/**
* Date when the comment was posted
*
* @var null
*/
public $date = null;
/**
* @var Integer: internal ID number (Comments.CommentID DB field) of the
* current comment that we're dealing with
*/
public $id = 0;
/**
* @var Integer: ID of the parent comment, if this is a child comment
*/
public $parentID = 0;
/**
* The current vote from this user on this comment
*
* @var int|boolean: false if no vote, otherwise -1, 0, or 1
*/
public $currentVote = false;
/**
* @var string: comment score (SUM() of all votes) of the current comment
*/
public $currentScore = '0';
/**
* Username of the user who posted the comment
*
* @var string
*/
public $username = '';
/**
* IP of the comment poster
*
* @var string
*/
public $ip = '';
/**
* ID of the user who posted the comment
*
* @var int
*/
public $userID = 0;
/**
* @TODO document
*
* @var int
*/
public $userPoints = 0;
/**
* Comment ID of the thread this comment is in
* this is the ID of the parent comment if there is one,
* or this comment if there is not
* Used for sorting
*
* @var null
*/
public $thread = null;
/**
* Unix timestamp when the comment was posted
* Used for sorting
* Processed from $date
*
* @var null
*/
public $timestamp = null;
/**
* Constructor - set the page ID
*
* @param $page CommentsPage: ID number of the current page
* @param IContextSource $context
* @param $data: straight from the DB about the comment
*/
public function __construct( CommentsPage $page, $context = null, $data ) {
$this->page = $page;
$this->setContext( $context );
$this->username = $data['Comment_Username'];
$this->ip = $data['Comment_IP'];
$this->text = $data['Comment_Text'];
$this->date = $data['Comment_Date'];
$this->userID = $data['Comment_user_id'];
$this->userPoints = $data['Comment_user_points'];
$this->id = $data['CommentID'];
$this->parentID = $data['Comment_Parent_ID'];
$this->thread = $data['thread'];
$this->timestamp = $data['timestamp'];
if ( isset( $data['current_vote'] ) ) {
$vote = $data['current_vote'];
} else {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
'Comments_Vote',
array( 'Comment_Vote_Score' ),
array(
'Comment_Vote_ID' => $this->id,
'Comment_Vote_Username' => $this->getUser()->getName()
),
__METHOD__
);
if ( $row !== false ) {
$vote = $row->Comment_Vote_Score;
} else {
$vote = false;
}
}
$this->currentVote = $vote;
$this->currentScore = isset( $data['total_vote'] )
? $data['total_vote'] : $this->getScore();
}
public static function newFromID( $id ) {
$context = RequestContext::getMain();
$dbr = wfGetDB( DB_SLAVE );
if ( !is_numeric( $id ) || $id == 0 ) {
return null;
}
$tables = array();
$params = array();
$joinConds = array();
// Defaults (for non-social wikis)
$tables[] = 'Comments';
$fields = array(
'Comment_Username', 'Comment_IP', 'Comment_Text',
'Comment_Date', 'Comment_Date AS timestamp',
'Comment_user_id', 'CommentID', 'Comment_Parent_ID',
'CommentID', 'Comment_Page_ID'
);
// If SocialProfile is installed, query the user_stats table too.
if (
class_exists( 'UserProfile' ) &&
$dbr->tableExists( 'user_stats' )
) {
$tables[] = 'user_stats';
$fields[] = 'stats_total_points';
$joinConds = array(
'Comments' => array(
'LEFT JOIN', 'Comment_user_id = stats_user_id'
)
);
}
// Perform the query
$res = $dbr->select(
$tables,
$fields,
array( 'CommentID' => $id ),
__METHOD__,
$params,
$joinConds
);
$row = $res->fetchObject();
if ( $row->Comment_Parent_ID == 0 ) {
$thread = $row->CommentID;
} else {
$thread = $row->Comment_Parent_ID;
}
$data = array(
'Comment_Username' => $row->Comment_Username,
'Comment_IP' => $row->Comment_IP,
'Comment_Text' => $row->Comment_Text,
'Comment_Date' => $row->Comment_Date,
'Comment_user_id' => $row->Comment_user_id,
'Comment_user_points' => ( isset( $row->stats_total_points ) ? number_format( $row->stats_total_points ) : 0 ),
'CommentID' => $row->CommentID,
'Comment_Parent_ID' => $row->Comment_Parent_ID,
'thread' => $thread,
'timestamp' => wfTimestamp( TS_UNIX, $row->timestamp )
);
$page = new CommentsPage( $row->Comment_Page_ID, $context );
return new Comment( $page, $context, $data );
}
/**
* Parse and return the text for this comment
*
* @return mixed|string
* @throws MWException
*/
function getText() {
global $wgParser;
$commentText = trim( str_replace( '&quot;', "'", $this->text ) );
$comment_text_parts = explode( "\n", $commentText );
$comment_text_fix = '';
foreach ( $comment_text_parts as $part ) {
$comment_text_fix .= ( ( $comment_text_fix ) ? "\n" : '' ) . trim( $part );
}
if ( $this->getTitle()->getArticleID() > 0 ) {
$commentText = $wgParser->recursiveTagParse( $comment_text_fix );
} else {
$commentText = $this->getOutput()->parse( $comment_text_fix );
}
// really bad hack because we want to parse=firstline, but don't want wrapping <p> tags
if ( substr( $commentText, 0 , 3 ) == '<p>' ) {
$commentText = substr( $commentText, 3 );
}
if ( substr( $commentText, strlen( $commentText ) -4 , 4 ) == '</p>' ) {
$commentText = substr( $commentText, 0, strlen( $commentText ) -4 );
}
// make sure link text is not too long (will overflow)
// this function changes too long links to <a href=#>http://www.abc....xyz.html</a>
$commentText = preg_replace_callback(
"/(<a[^>]*>)(.*?)(<\/a>)/i",
array( 'CommentFunctions', 'cutCommentLinkText' ),
$commentText
);
return $commentText;
}
/**
* Adds the comment and all necessary info into the Comments table in the
* database.
*
* @param string $text: text of the comment
* @param CommentsPage $page: container page
* @param User $user: user commenting
* @param int $parentID: ID of parent comment, if this is a reply
*
* @return Comment: the added comment
*/
static function add( $text, CommentsPage $page, User $user, $parentID ) {
global $wgCommentsInRecentChanges;
$dbw = wfGetDB( DB_MASTER );
$context = RequestContext::getMain();
wfSuppressWarnings();
$commentDate = date( 'Y-m-d H:i:s' );
wfRestoreWarnings();
/*if ( $this->getUser()->isLoggedIn() ) {
$kok_username = $user->getName();
} else {
$kok_username = $this->CommentUsernameKOK;
$kok_username = preg_replace('/<.*>/i', '', $kok_username);
$kok_username = preg_replace('/[^A-Za-z0-9. \-\@]/i', '', $kok_username);
$kok_username = str_replace("1'1", '', $kok_username);
$kok_username = str_replace('USER_NAME', '', $kok_username);
$kok_username = str_replace('DESC', '', $kok_username);
$kok_username = str_replace('(*)', '', $kok_username);
$kok_username = str_replace('EXEC', '', $kok_username);
}*/
$dbw->insert(
'Comments',
array(
'Comment_Page_ID' => $page->id,
'Comment_Username' => $user->getName(),
'Comment_user_id' => $user->getId(),
'Comment_Text' => $text,
'Comment_Date' => $commentDate,
'Comment_Parent_ID' => $parentID,
'Comment_IP' => $_SERVER['REMOTE_ADDR']
),
__METHOD__
);
$commentId = $dbw->insertId();
$dbw->commit( __METHOD__ ); // misza: added this
$id = $commentId;
$page->clearCommentListCache();
// Add a log entry.
self::log( 'add', $user, $page->id, $commentId, $text );
$dbr = wfGetDB( DB_SLAVE );
if (
class_exists( 'UserProfile' ) &&
$dbr->tableExists( 'user_stats' )
) {
$res = $dbr->select( // need this data for seeding a Comment object
'user_stats',
'stats_total_points',
array( 'stats_user_id' => $user->getId() ),
__METHOD__
);
$row = $res->fetchObject();
$userPoints = number_format( $row->stats_total_points );
} else {
$userPoints = 0;
}
if ( $parentID == 0 ) {
$thread = $id;
} else {
$thread = $parentID;
}
$data = array(
'Comment_Username' => $user->getName(),
'Comment_IP' => $context->getRequest()->getIP(),
'Comment_Text' => $text,
'Comment_Date' => $commentDate,
'Comment_user_id' => $user->getID(),
'Comment_user_points' => $userPoints,
'CommentID' => $id,
'Comment_Parent_ID' => $parentID,
'thread' => $thread,
'timestamp' => strtotime( $commentDate )
);
$page = new CommentsPage( $page->id, $context );
$comment = new Comment( $page, $context, $data );
Hooks::run( 'Comment::add', array( $comment, $commentId, $comment->page->id ) );
/* ## START Kommentar auch per Email versenden ## 11/2014 Bernhard Linz */
$znilpageTitle = "";
$comment_mailto = "root@linz.email";
$comment_mailsubject = "Neuer Kommentar von: " . $kok_username . " - IP: " . $_SERVER['REMOTE_ADDR'] . " - DNS: " . gethostbyaddr($_SERVER['REMOTE_ADDR']) ;
$comment_mailfrom = "MIME-Version: 1.0\r\n";
$comment_mailfrom .= "Content-type: text/html; charset=utf-8\r\n";
$comment_mailfrom .= "From: znil.net Kommentare <root@linz.email>\r\n";
$comment_url = "<a href=\"http://znil.net/index.php?title={$znilpageTitle}#comment-{$commentId}\">http://znil.net/index.php?title={$znilpageTitle}#comment-{$commentId}</a>";
$comment_mailtext = $commentDate . "<br><br>" . $comment_url . "<br><br><hr>" . "IP: " . $_SERVER['REMOTE_ADDR'] . "<br>" . "DNS: " . gethostbyaddr($_SERVER['REMOTE_ADDR']) ."<br><br>" . $kok_username . "<br><br>" . $text;
$comment_mailtext = nl2br($comment_mailtext);
mail($comment_mailto, $comment_mailsubject, $comment_mailtext, $comment_mailfrom);
/* ## ENDE Bernhard Linz */
return $comment;
}
/**
* Gets the score for this comment from the database table Comments_Vote
*
* @return string
*/
function getScore() {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
'Comments_Vote',
array( 'SUM(Comment_Vote_Score) AS CommentScore' ),
array( 'Comment_Vote_ID' => $this->id ),
__METHOD__
);
$score = '0';
if ( $row !== false && $row->CommentScore ) {
$score = $row->CommentScore;
}
return $score;
}
/* START Anpassungen znilwiki */
/* ## START ## 25.10.2013 Hinzugefügt von Kai-Ole */
function setCommentUsernameKOK( $UsernameKOK ) {
$this->CommentUsernameKOK = $UsernameKOK;
}
/* ## ENDE ## 25.10.2013 Kai-Ole */
/* ENDE Anpassungen znilwiki */
/**
* Adds a vote for a comment if the user hasn't voted for said comment yet.
*
* @param $value int: upvote or downvote (1 or -1)
*/
function vote( $value ) {
global $wgMemc;
$dbw = wfGetDB( DB_MASTER );
if ( $value < -1 ) { // limit to range -1 -> 0 -> 1
$value = -1;
} elseif ( $value > 1 ) {
$value = 1;
}
if ( $value == $this->currentVote ) { // user toggling off a preexisting vote
$value = 0;
}
wfSuppressWarnings();
$commentDate = date( 'Y-m-d H:i:s' );
wfRestoreWarnings();
if ( $this->currentVote === false ) { // no vote, insert
$dbw->insert(
'Comments_Vote',
array(
'Comment_Vote_id' => $this->id,
'Comment_Vote_Username' => $this->getUser()->getName(),
'Comment_Vote_user_id' => $this->getUser()->getId(),
'Comment_Vote_Score' => $value,
'Comment_Vote_Date' => $commentDate,
'Comment_Vote_IP' => $_SERVER['REMOTE_ADDR']
),
__METHOD__
);
} else { // already a vote, update
$dbw->update(
'Comments_Vote',
array(
'Comment_Vote_Score' => $value,
'Comment_Vote_Date' => $commentDate,
'Comment_Vote_IP' => $_SERVER['REMOTE_ADDR']
),
array(
'Comment_Vote_id' => $this->id,
'Comment_Vote_Username' => $this->getUser()->getName(),
'Comment_Vote_user_id' => $this->getUser()->getId(),
),
__METHOD__
);
}
$dbw->commit( __METHOD__ );
// update cache for comment list
// should perform better than deleting cache completely since Votes happen more frequently
$key = wfMemcKey( 'comment', 'pagethreadlist', $this->page->id );
$comments = $wgMemc->get( $key );
if ( $comments ) {
foreach ( $comments as &$comment ) {
if ( $comment->id == $this->id ) {
$comment->currentScore = $this->currentScore;
}
}
$wgMemc->set( $key, $comments );
}
$score = $this->getScore();
$this->currentVote = $value;
$this->currentScore = $score;
}
/**
* Deletes entries from Comments and Comments_Vote tables and clears caches
*/
function delete() {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete(
'Comments',
array( 'CommentID' => $this->id ),
__METHOD__
);
$dbw->delete(
'Comments_Vote',
array( 'Comment_Vote_ID' => $this->id ),
__METHOD__
);
$dbw->commit( __METHOD__ );
// Log the deletion to Special:Log/comments.
self::log( 'delete', $this->getUser(), $this->page->id, $this->id );
// Clear memcache & Squid cache
$this->page->clearCommentListCache();
// Ping other extensions that may have hooked into this point (i.e. LinkFilter)
Hooks::run( 'Comment::delete', array( $this, $this->id, $this->page->id ) );
}
/**
* Log an action in the comment log.
*
* @param string $action Action to log, can be either 'add' or 'delete'
* @param User $user User who performed the action
* @param int $pageId Page ID of the page that contains the comment thread
* @param int $commentId Comment ID of the affected comment
* @param string $commentText Supplementary log comment, if any
*/
static function log( $action, $user, $pageId, $commentId, $commentText = null ) {
global $wgCommentsInRecentChanges;
$logEntry = new ManualLogEntry( 'comments', $action );
$logEntry->setPerformer( $user );
$logEntry->setTarget( Title::newFromId( $pageId ) );
if ( $commentText !== null ) {
$logEntry->setComment( $commentText );
}
$logEntry->setParameters( array(
'4::commentid' => $commentId
) );
$logId = $logEntry->insert();
$logEntry->publish( $logId, ( $wgCommentsInRecentChanges ? 'rcandudp' : 'udp' ) );
}
/**
* Return the HTML for the comment vote links
*
* @param int $voteType up (+1) vote or down (-1) vote
* @return string
*/
function getVoteLink( $voteType ) {
global $wgExtensionAssetsPath;
// Blocked users cannot vote, obviously
if ( $this->getUser()->isBlocked() ) {
return '';
}
if ( !$this->getUser()->isAllowed( 'comment' ) ) {
return '';
}
$voteLink = '';
if ( $this->getUser()->isLoggedIn() ) {
$voteLink .= '<a id="comment-vote-link" data-comment-id="' .
$this->id . '" data-vote-type="' . $voteType .
'" data-voting="' . $this->page->voting . '" href="javascript:void(0);">';
} else {
$login = SpecialPage::getTitleFor( 'Userlogin' ); // Anonymous users need to log in before they can vote
$returnTo = $this->page->title->getPrefixedDBkey(); // Determine a sane returnto URL parameter
$voteLink .=
"<a href=\"" .
htmlspecialchars( $login->getLocalURL( array( 'returnto' => $returnTo ) ) ) .
"\" rel=\"nofollow\">";
}
$imagePath = $wgExtensionAssetsPath . '/Comments/resources/images';
if ( $voteType == 1 ) {
if ( $this->currentVote == 1 ) {
$voteLink .= "<img src=\"{$imagePath}/up-voted.png\" border=\"0\" alt=\"+\" /></a>";
} else {
$voteLink .= "<img src=\"{$imagePath}/up-unvoted.png\" border=\"0\" alt=\"+\" /></a>";
}
} else {
if ( $this->currentVote == -1 ) {
$voteLink .= "<img src=\"{$imagePath}/down-voted.png\" border=\"0\" alt=\"+\" /></a>";
} else {
$voteLink .= "<img src=\"{$imagePath}/down-unvoted.png\" border=\"0\" alt=\"+\" /></a>";
}
}
return $voteLink;
}
/**
* Show the HTML for this comment and ignore section
*
* @param array $blockList list of users the current user has blocked
* @param array $anonList map of ip addresses to names like anon#1, anon#2
* @return string html
*/
function display( $blockList, $anonList ) {
if ( $this->parentID == 0 ) {
$container_class = 'full';
} else {
$container_class = 'reply';
}
$output = '';
if ( in_array( $this->username, $blockList ) ) {
$output .= $this->showIgnore( false, $container_class );
$output .= $this->showComment( true, $container_class, $blockList, $anonList );
} else {
$output .= $this->showIgnore( true, $container_class );
$output .= $this->showComment( false, $container_class, $blockList, $anonList );
}
return $output;
}
function displayForCommentOfTheDay() {
$output = '';
$title2 = $this->page->getTitle();
if ( $this->userID != 0 ) {
$title = Title::makeTitle( NS_USER, $this->username );
$commentPoster_Display = $this->username;
$commentPoster = '<a href="' . $title->getFullURL() . '" title="' . $title->getText() . '" rel="nofollow">' . $this->username . '</a>';
if ( class_exists( 'wAvatar' ) ) {
$avatar = new wAvatar( $this->userID, 's' );
$commentIcon = $avatar->getAvatarImage();
} else {
$commentIcon = '';
}
} else {
$commentPoster_Display = wfMessage( 'comments-anon-name' )->plain();
$commentPoster = wfMessage( 'comments-anon-name' )->plain();
$commentIcon = 'default_s.gif';
}
$avatarHTML = '';
if ( class_exists( 'wAvatar' ) ) {
global $wgUploadPath;
$avatarHTML = '<img src="' . $wgUploadPath . '/avatars/' . $commentIcon .
'" alt="" align="middle" border="0"/>';
}
$comment_text = substr( $this->text, 0, 50 - strlen( $commentPoster_Display ) );
if ( $comment_text != $this->text ) {
$comment_text .= wfMessage( 'ellipsis' )->plain();
}
$output .= '<div class="cod">';
$sign = '';
if ( $this->currentScore > 0 ) {
$sign = '+';
} elseif ( $this->currentScore < 0 ) {
$sign = '-'; // this *really* shouldn't be happening...
}
$output .= '<span class="cod-score">' . $sign . $this->currentScore .
'</span> ' . $avatarHTML .
'<span class="cod-poster">' . $commentPoster . '</span>';
$output .= '<span class="cod-comment"><a href="' .
$title2->getFullURL() . '#comment-' . $this->id .
'" title="' . $title2->getText() . '">' . $comment_text .
'</a></span>';
$output .= '</div>';
return $output;
}
/**
* Show the box for if this comment has been ignored
*
* @param bool $hide
* @param $containerClass
* @return string
*/
function showIgnore( $hide = false, $containerClass ) {
$blockListTitle = SpecialPage::getTitleFor( 'CommentIgnoreList' );
$style = '';
if ( $hide ) {
$style = " style='display:none;'";
}
$output = "<div id='ignore-{$this->id}' class='c-ignored {$containerClass}'{$style}>\n";
$output .= wfMessage( 'comments-ignore-message' )->parse();
$output .= '<div class="c-ignored-links">' . "\n";
$output .= "<a href=\"javascript:void(0);\" data-comment-id=\"{$this->id}\">" .
$this->msg( 'comments-show-comment-link' )->plain() . '</a> | ';
$output .= '<a href="' . htmlspecialchars( $blockListTitle->getFullURL() ) . '">' .
$this->msg( 'comments-manage-blocklist-link' )->plain() . '</a>';
$output .= '</div>' . "\n";
$output .= '</div>' . "\n";
return $output;
}
/**
* Show the comment
*
* @param bool $hide: if true, comment is returned but hidden (display:none)
* @param $containerClass
* @param $blockList
* @param $anonList
* @return string
*/
function showComment( $hide = false, $containerClass, $blockList, $anonList ) {
global $wgUserLevels, $wgExtensionAssetsPath;
$style = '';
if ( $hide ) {
$style = " style='display:none;'";
}
$commentPosterLevel = '';
if ( $this->userID != 0 ) {
$title = Title::makeTitle( NS_USER, $this->username );
$commentPoster = '<a href="' . htmlspecialchars( $title->getFullURL() ) .
'" rel="nofollow">' . $this->username . '</a>';
$CommentReplyTo = $this->username;
if ( $wgUserLevels && class_exists( 'UserLevel' ) ) {
$user_level = new UserLevel( $this->userPoints );
$commentPosterLevel = "{$user_level->getLevelName()}";
}
$user = User::newFromId( $this->userID );
$CommentReplyToGender = $user->getOption( 'gender', 'unknown' );
} else {
$anonMsg = $this->msg( 'comments-anon-name' )->inContentLanguage()->plain();
$commentPoster = $anonMsg . ' #' . $anonList[$this->username];
$CommentReplyTo = $anonMsg;
$CommentReplyToGender = 'unknown'; // Undisclosed gender as anon user
}
// Comment delete button for privileged users
$dlt = '';
if ( $this->getUser()->isAllowed( 'commentadmin' ) ) {
$dlt = ' | <span class="c-delete">' .
'<a href="javascript:void(0);" rel="nofollow" class="comment-delete-link" data-comment-id="' .
$this->id . '">' .
$this->msg( 'comments-delete-link' )->plain() . '</a></span>';
}
// Reply Link (does not appear on child comments)
$replyRow = '';
if ( $this->getUser()->isAllowed( 'comment' ) ) {
if ( $this->parentID == 0 ) {
if ( $replyRow ) {
$replyRow .= wfMessage( 'pipe-separator' )->plain();
}
$replyRow .= " | <a href=\"#end\" rel=\"nofollow\" class=\"comments-reply-to\" data-comment-id=\"{$this->id}\" data-comments-safe-username=\"" .
htmlspecialchars( $CommentReplyTo, ENT_QUOTES ) . "\" data-comments-user-gender=\"" .
htmlspecialchars( $CommentReplyToGender ) . '">' .
wfMessage( 'comments-reply' )->plain() . '</a>';
}
}
if ( $this->parentID == 0 ) {
$comment_class = 'f-message';
} else {
$comment_class = 'r-message';
}
// Display Block icon for logged in users for comments of users
// that are already not in your block list
$blockLink = '';
if (
$this->getUser()->getID() != 0 && $this->getUser()->getID() != $this->userID &&
!( in_array( $this->userID, $blockList ) )
) {
$blockLink = '<a href="javascript:void(0);" rel="nofollow" class="comments-block-user" data-comments-safe-username="' .
htmlspecialchars( $this->username, ENT_QUOTES ) .
'" data-comments-comment-id="' . $this->id . '" data-comments-user-id="' .
$this->userID . "\">
<img src=\"{$wgExtensionAssetsPath}/Comments/resources/images/block.svg\" border=\"0\" alt=\"\"/>
</a>";
}
// Default avatar image, if SocialProfile extension isn't enabled
global $wgCommentsDefaultAvatar;
$avatarImg = '<img src="' . $wgCommentsDefaultAvatar . '" alt="" border="0" />';
// If SocialProfile *is* enabled, then use its wAvatar class to get the avatars for each commenter
if ( class_exists( 'wAvatar' ) ) {
$avatar = new wAvatar( $this->userID, 'ml' );
$avatarImg = $avatar->getAvatarURL() . "\n";
}
$output = "<div id='comment-{$this->id}' class='c-item {$containerClass}'{$style}>" . "\n";
$output .= "<div class=\"c-avatar\">{$avatarImg}</div>" . "\n";
$output .= '<div class="c-container">' . "\n";
$output .= '<div class="c-user">' . "\n";
$output .= "{$commentPoster}";
$output .= "<span class=\"c-user-level\">{$commentPosterLevel}</span> {$blockLink}" . "\n";
wfSuppressWarnings(); // E_STRICT bitches about strtotime()
$output .= '<div class="c-time">' .
wfMessage(
'comments-time-ago',
CommentFunctions::getTimeAgo( strtotime( $this->date ) )
)->parse() . '</div>' . "\n";
wfRestoreWarnings();
$output .= '<div class="c-score">' . "\n";
$output .= $this->getScoreHTML();
$output .= '</div>' . "\n";
$output .= '</div>' . "\n";
$output .= "<div class=\"c-comment {$comment_class}\">" . "\n";
$output .= $this->getText();
$output .= '</div>' . "\n";
$output .= '<div class="c-actions">' . "\n";
if ( $this->page->title ) { // for some reason doesn't always exist
$output .= '<a href="' . htmlspecialchars( $this->page->title->getFullURL() ) . "#comment-{$this->id}\" rel=\"nofollow\">" .
$this->msg( 'comments-permalink' )->plain() . '</a> ';
}
if ( $replyRow || $dlt ) {
$output .= "{$replyRow} {$dlt}" . "\n";
}
$output .= '</div>' . "\n";
$output .= '</div>' . "\n";
$output .= '<div class="visualClear"></div>' . "\n";
$output .= '</div>' . "\n";
return $output;
}
/**
* Get the HTML for the comment score section of the comment
*
* @return string
*/
function getScoreHTML() {
$output = '';
if ( $this->page->allowMinus == true || $this->page->allowPlus == true ) {
$output .= '<span class="c-score-title">' .
wfMessage( 'comments-score-text' )->plain() .
" <span id=\"Comment{$this->id}\">{$this->currentScore}</span></span>";
// Voting is possible only when database is unlocked
if ( !wfReadOnly() ) {
// You can only vote for other people's comments, not for your own
if ( $this->getUser()->getName() != $this->username ) {
$output .= "<span id=\"CommentBtn{$this->id}\">";
if ( $this->page->allowPlus == true ) {
$output .= $this->getVoteLink( 1 );
}
if ( $this->page->allowMinus == true ) {
$output .= $this->getVoteLink( -1 );
}
$output .= '</span>';
} else {
$output .= wfMessage( 'word-separator' )->plain() . wfMessage( 'comments-you' )->plain();
}
}
}
return $output;
}
}

View File

@@ -0,0 +1,319 @@
<?php
class CommentFunctions {
/**
* The following four functions are borrowed
* from includes/wikia/GlobalFunctionsNY.php
*/
static function dateDiff( $date1, $date2 ) {
$dtDiff = $date1 - $date2;
$totalDays = intval( $dtDiff / ( 24 * 60 * 60 ) );
$totalSecs = $dtDiff - ( $totalDays * 24 * 60 * 60 );
$dif['mo'] = intval( $totalDays / 30 );
$dif['d'] = $totalDays;
$dif['h'] = $h = intval( $totalSecs / ( 60 * 60 ) );
$dif['m'] = $m = intval( ( $totalSecs - ( $h * 60 * 60 ) ) / 60 );
$dif['s'] = $totalSecs - ( $h * 60 * 60 ) - ( $m * 60 );
return $dif;
}
static function getTimeOffset( $time, $timeabrv, $timename ) {
$timeStr = ''; // misza: initialize variables, DUMB FUCKS!
if( $time[$timeabrv] > 0 ) {
// Give grep a chance to find the usages:
// comments-time-days, comments-time-hours, comments-time-minutes, comments-time-seconds, comments-time-months
$timeStr = wfMessage( "comments-time-{$timename}", $time[$timeabrv] )->parse();
}
if( $timeStr ) {
$timeStr .= ' ';
}
return $timeStr;
}
static function getTimeAgo( $time ) {
$timeArray = self::dateDiff( time(), $time );
$timeStr = '';
$timeStrMo = self::getTimeOffset( $timeArray, 'mo', 'months' );
$timeStrD = self::getTimeOffset( $timeArray, 'd', 'days' );
$timeStrH = self::getTimeOffset( $timeArray, 'h', 'hours' );
$timeStrM = self::getTimeOffset( $timeArray, 'm', 'minutes' );
$timeStrS = self::getTimeOffset( $timeArray, 's', 'seconds' );
if ( $timeStrMo ) {
$timeStr = $timeStrMo;
} else {
$timeStr = $timeStrD;
if( $timeStr < 2 ) {
$timeStr .= $timeStrH;
$timeStr .= $timeStrM;
if( !$timeStr ) {
$timeStr .= $timeStrS;
}
}
}
if( !$timeStr ) {
$timeStr = wfMessage( 'comments-time-seconds', 1 )->parse();
}
return $timeStr;
}
/**
* Makes sure that link text is not too long by changing too long links to
* <a href=#>http://www.abc....xyz.html</a>
*
* @param $matches Array
* @return String: shortened URL
*/
public static function cutCommentLinkText( $matches ) {
$tagOpen = $matches[1];
$linkText = $matches[2];
$tagClose = $matches[3];
$image = preg_match( "/<img src=/i", $linkText );
$isURL = ( preg_match( '%^(?:http|https|ftp)://(?:www\.)?.*$%i', $linkText ) ? true : false );
if( $isURL && !$image && strlen( $linkText ) > 30 ) {
$start = substr( $linkText, 0, ( 30 / 2 ) - 3 );
$end = substr( $linkText, strlen( $linkText ) - ( 30 / 2 ) + 3, ( 30 / 2 ) - 3 );
$linkText = trim( $start ) . wfMessage( 'ellipsis' )->escaped() . trim( $end );
}
return $tagOpen . $linkText . $tagClose;
}
/**
* Simple spam check -- checks the supplied text against MediaWiki's
* built-in regex-based spam filters
*
* @param $text String: text to check for spam patterns
* @return Boolean: true if it contains spam, otherwise false
*/
public static function isSpam( $text ) {
global $wgSpamRegex, $wgSummarySpamRegex;
$retVal = false;
// Allow to hook other anti-spam extensions so that sites that use,
// for example, AbuseFilter, Phalanx or SpamBlacklist can add additional
// checks
Hooks::run( 'Comments::isSpam', array( &$text, &$retVal ) );
if ( $retVal ) {
// Should only be true here...
return $retVal;
}
// Run text through $wgSpamRegex (and $wgSummarySpamRegex if it has been specified)
if ( $wgSpamRegex && preg_match( $wgSpamRegex, $text ) ) {
return true;
}
if ( $wgSummarySpamRegex && is_array( $wgSummarySpamRegex ) ) {
foreach ( $wgSummarySpamRegex as $spamRegex ) {
if ( preg_match( $spamRegex, $text ) ) {
return true;
}
}
}
return $retVal;
}
/**
* Checks the supplied text for links
*
* @param $text String: text to check
* @return Boolean: true if it contains links, otherwise false
*/
public static function haveLinks( $text ) {
$linkPatterns = array(
'/(https?)|(ftp):\/\//',
'/=\\s*[\'"]?\\s*mailto:/',
);
foreach ( $linkPatterns as $linkPattern ) {
if ( preg_match( $linkPattern, $text ) ) {
return true;
}
}
return false;
}
/**
* Blocks comments from a user
*
* @param User $blocker The user who is blocking someone else's comments
* @param int $userId User ID of the guy whose comments we want to block
* @param mixed $userName User name of the same guy
*/
public static function blockUser( $blocker, $userId, $userName ) {
$dbw = wfGetDB( DB_MASTER );
wfSuppressWarnings(); // E_STRICT bitching
$date = date( 'Y-m-d H:i:s' );
wfRestoreWarnings();
$dbw->insert(
'Comments_block',
array(
'cb_user_id' => $blocker->getId(),
'cb_user_name' => $blocker->getName(),
'cb_user_id_blocked' => $userId,
'cb_user_name_blocked' => $userName,
'cb_date' => $date
),
__METHOD__
);
$dbw->commit( __METHOD__ );
}
/**
* Fetches the list of blocked users from the database
*
* @param int $userId User ID for whom we're getting the blocks(?)
* @return array List of comment-blocked users
*/
static function getBlockList( $userId ) {
$blockList = array();
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
'Comments_block',
'cb_user_name_blocked',
array( 'cb_user_id' => $userId ),
__METHOD__
);
foreach ( $res as $row ) {
$blockList[] = $row->cb_user_name_blocked;
}
return $blockList;
}
static function isUserCommentBlocked( $userId, $userIdBlocked ) {
$dbr = wfGetDB( DB_SLAVE );
$s = $dbr->selectRow(
'Comments_block',
array( 'cb_id' ),
array(
'cb_user_id' => $userId,
'cb_user_id_blocked' => $userIdBlocked
),
__METHOD__
);
if ( $s !== false ) {
return true;
} else {
return false;
}
}
/**
* Deletes a user from your personal comment-block list.
*
* @param int $userId Your user ID
* @param int $userIdBlocked User ID of the blocked user
*/
public static function deleteBlock( $userId, $userIdBlocked ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete(
'Comments_block',
array(
'cb_user_id' => $userId,
'cb_user_id_blocked' => $userIdBlocked
),
__METHOD__
);
$dbw->commit( __METHOD__ );
}
/**
* Sort threads ascending
*
* @param $x
* @param $y
* @return int
*/
public static function sortAsc( $x, $y ) {
// return -1 - x goes above y
// return 1 - x goes below y
// return 0 - order irrelevant (only when x == y)
if ( $x[0]->timestamp < $y[0]->timestamp ) {
return -1;
} else {
return 1;
}
}
/**
* Sort threads descending
*
* @param $x
* @param $y
* @return int
*/
public static function sortDesc( $x, $y ) {
// return -1 - x goes above y
// return 1 - x goes below y
// return 0 - order irrelevant (only when x == y)
if ( $x[0]->timestamp > $y[0]->timestamp ) {
return -1;
} else {
return 1;
}
}
/**
* Sort threads by score
*
* @param $x
* @param $y
*/
public static function sortScore( $x, $y ) {
// return -1 - x goes above y
// return 1 - x goes below y
// return 0 - order irrelevant (only when x == y)
if ( $x[0]->currentScore > $y[0]->currentScore ) {
return -1;
} else {
return 1;
}
}
/**
* Sort COMMENTS (not threads) by score
*
* @param $x
* @param $y
*/
public static function sortCommentScore( $x, $y ) {
// return -1 - x goes above y
// return 1 - x goes below y
// return 0 - order irrelevant (only when x == y)
if ( $x->currentScore > $y->currentScore ) {
return -1;
} else {
return 1;
}
}
/**
* Sort the comments purely by the time, from earliest to latest
*
* @param $x
* @param $y
* @return int
*/
public static function sortTime( $x, $y ) {
// return -1 - x goes above y
// return 1 - x goes below y
// return 0 - order irrelevant (only when x == y)
if ( $x->timestamp == $y->timestamp ) {
return 0;
} elseif ( $x->timestamp < $y->timestamp ) {
return -1;
} else {
return 1;
}
}
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* Aliases for special pages
*
* @file
* @ingroup Extensions
*/
// @codingStandardsIgnoreFile
$specialPageAliases = array();
/** English */
$specialPageAliases['en'] = array(
'CommentIgnoreList' => array( 'CommentIgnoreList' ),
);

173
includes/Comments.hooks.php Normal file
View File

@@ -0,0 +1,173 @@
<?php
/**
* Hooked functions used by the Comments extension.
* All class methods are public and static.
*
* @file
* @ingroup Extensions
* @author Jack Phoenix <jack@countervandalism.net>
* @author Alexia E. Smith
* @copyright (c) 2013 Curse Inc.
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
* @link https://www.mediawiki.org/wiki/Extension:Comments Documentation
*/
class CommentsHooks {
/**
* Registers the <comments> tag with the Parser.
*
* @param Parser $parser
* @return bool
*/
public static function onParserFirstCallInit( Parser &$parser ) {
$parser->setHook( 'comments', array( 'CommentsHooks', 'displayComments' ) );
return true;
}
/**
* Callback function for onParserFirstCallInit().
*
* @param $input
* @param array $args
* @param Parser $parser
* @return string HTML
*/
public static function displayComments( $input, $args, $parser ) {
global $wgOut, $wgCommentsSortDescending;
$parser->disableCache();
// If an unclosed <comments> tag is added to a page, the extension will
// go to an infinite loop...this protects against that condition.
$parser->setHook( 'comments', array( 'CommentsHooks', 'nonDisplayComments' ) );
$title = $parser->getTitle();
if ( $title->getArticleID() == 0 && $title->getDBkey() == 'CommentListGet' ) {
return self::nonDisplayComments( $input, $args, $parser );
}
// Add required CSS & JS via ResourceLoader
$wgOut->addModuleStyles( 'ext.comments.css' );
$wgOut->addModules( 'ext.comments.js' );
$wgOut->addJsConfigVars( array( 'wgCommentsSortDescending' => $wgCommentsSortDescending ) );
// Parse arguments
// The preg_match() lines here are to support the old-style way of
// adding arguments:
// <comments>
// Allow=Foo,Bar
// Voting=Plus
// </comments>
// whereas the normal, standard MediaWiki style, which this extension
// also supports is: <comments allow="Foo,Bar" voting="Plus" />
$allow = '';
if ( preg_match( '/^\s*Allow\s*=\s*(.*)/mi', $input, $matches ) ) {
$allow = htmlspecialchars( $matches[1] );
} elseif ( !empty( $args['allow'] ) ) {
$allow = $args['allow'];
}
$voting = '';
if ( preg_match( '/^\s*Voting\s*=\s*(.*)/mi', $input, $matches ) ) {
$voting = htmlspecialchars( $matches[1] );
} elseif (
!empty( $args['voting'] ) &&
in_array( strtoupper( $args['voting'] ), array( 'OFF', 'PLUS', 'MINUS' ) )
) {
$voting = $args['voting'];
}
$commentsPage = new CommentsPage( $title->getArticleID(), $wgOut->getContext() );
$commentsPage->allow = $allow;
$commentsPage->setVoting( $voting );
$output = '<div class="comments-body">';
if ( $wgCommentsSortDescending ) { // form before comments
$output .= '<a id="end" rel="nofollow"></a>';
if ( !wfReadOnly() ) {
$output .= $commentsPage->displayForm();
} else {
$output .= wfMessage( 'comments-db-locked' )->parse();
}
}
$output .= $commentsPage->displayOrderForm();
$output .= '<div id="allcomments">' . $commentsPage->display() . '</div>';
// If the database is in read-only mode, display a message informing the
// user about that, otherwise allow them to comment
if ( !$wgCommentsSortDescending ) { // form after comments
if ( !wfReadOnly() ) {
$output .= $commentsPage->displayForm();
} else {
$output .= wfMessage( 'comments-db-locked' )->parse();
}
$output .= '<a id="end" rel="nofollow"></a>';
}
$output .= '</div>'; // div.comments-body
return $output;
}
public static function nonDisplayComments( $input, $args, $parser ) {
$attr = array();
foreach ( $args as $name => $value ) {
$attr[] = htmlspecialchars( $name ) . '="' . htmlspecialchars( $value ) . '"';
}
$output = '&lt;comments';
if ( count( $attr ) > 0 ) {
$output .= ' ' . implode( ' ', $attr );
}
if ( !is_null( $input ) ) {
$output .= '&gt;' . htmlspecialchars( $input ) . '&lt;/comments&gt;';
} else {
$output .= ' /&gt;';
}
return $output;
}
/**
* Adds the three new required database tables into the database when the
* user runs /maintenance/update.php (the core database updater script).
*
* @param DatabaseUpdater $updater
* @return bool
*/
public static function onLoadExtensionSchemaUpdates( $updater ) {
$dir = __DIR__ . '/../sql';
$dbType = $updater->getDB()->getType();
// For non-MySQL/MariaDB/SQLite DBMSes, use the appropriately named file
if ( !in_array( $dbType, array( 'mysql', 'sqlite' ) ) ) {
$filename = "comments.{$dbType}.sql";
} else {
$filename = 'comments.sql';
}
$updater->addExtensionUpdate( array( 'addTable', 'Comments', "{$dir}/{$filename}", true ) );
$updater->addExtensionUpdate( array( 'addTable', 'Comments_Vote', "{$dir}/{$filename}", true ) );
$updater->addExtensionUpdate( array( 'addTable', 'Comments_block', "{$dir}/{$filename}", true ) );
return true;
}
/**
* For integration with the Renameuser extension.
*
* @param RenameuserSQL $renameUserSQL
* @return bool
*/
public static function onRenameUserSQL( $renameUserSQL ) {
$renameUserSQL->tables['Comments'] = array( 'Comment_Username', 'Comment_user_id' );
$renameUserSQL->tables['Comments_Vote'] = array( 'Comment_Vote_Username', 'Comment_Vote_user_id' );
$renameUserSQL->tables['Comments_block'] = array( 'cb_user_name', 'cb_user_id' );
$renameUserSQL->tables['Comments_block'] = array( 'cb_user_name_blocked', 'cb_user_id_blocked' );
return true;
}
}

View File

@@ -0,0 +1,12 @@
<?php
/**
* Magic words for extension.
*/
$magicWords = array();
/** English (English) */
$magicWords['en'] = array(
'NUMBEROFCOMMENTS' => array( 0, 'NUMBEROFCOMMENTS' ),
'NUMBEROFCOMMENTSPAGE' => array( 0, 'NUMBEROFCOMMENTSPAGE' ),
);

View File

@@ -0,0 +1,65 @@
<?php
/**
* Logging formatter for Comments' log entries.
*
* @file
* @date 28 July 2013
*/
class CommentsLogFormatter extends LogFormatter {
/**
* Gets the log action, including username.
*
* This is a copy of LogFormatter::getActionText() with one "escaped"
* swapped to parse; no other changes here!
*
* @return string HTML
*/
public function getActionText() {
if ( $this->canView( LogPage::DELETED_ACTION ) ) {
$element = $this->getActionMessage();
if ( $element instanceof Message ) {
$element = $this->plaintext ? $element->text() : $element->parse(); // <-- here's the change!
}
if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) {
$element = $this->styleRestricedElement( $element );
}
} else {
$performer = $this->getPerformerElement() . $this->msg( 'word-separator' )->text();
$element = $performer . $this->getRestrictedElement( 'rev-deleted-event' );
}
return $element;
}
/**
* Formats parameters intented for action message from
* array of all parameters. There are three hardcoded
* parameters (array is zero-indexed, this list not):
* - 1: user name with premade link
* - 2: usable for gender magic function
* - 3: target page with premade link
* @return array
*/
protected function getMessageParameters() {
if ( isset( $this->parsedParameters ) ) {
return $this->parsedParameters;
}
$entry = $this->entry;
$params = $this->extractParameters();
$commentId = $params[3]; // = $4, because array numbering starts from 0
$params[0] = Message::rawParam( $this->getPerformerElement() );
$params[1] = $this->canView( LogPage::DELETED_USER ) ? $entry->getPerformer()->getName() : '';
$title = $entry->getTarget();
if ( $title instanceof Title ) { // healthy paranoia
$title->setFragment( '#comment-' . $commentId );
}
$params[2] = Message::rawParam( $this->makePageLink( $title ) );
// Bad things happens if the numbers are not in correct order
ksort( $params );
return $this->parsedParameters = $params;
}
}

View File

@@ -0,0 +1,120 @@
<?php
/**
* Comments of the Day parser hook -- shows the five newest comments that have
* been sent within the last 24 hours.
*
* @file
* @ingroup Extensions
* @date 27 November 2015
*/
class CommentsOfTheDay {
/**
* Register the new <commentsoftheday /> parser hook with the Parser.
*
* @param Parser $parser Instance of Parser
* @return bool
*/
public static function registerTag( &$parser ) {
$parser->setHook( 'commentsoftheday', array( __CLASS__, 'getHTML' ) );
return true;
}
/**
* Get comments of the day -- five newest comments within the last 24 hours
*
* @return string HTML
*/
public static function getHTML( $input, $args, $parser ) {
$comments = self::get( (bool)$args['nocache'] );
$commentOutput = '';
foreach ( $comments as $comment ) {
$commentOutput .= $comment->displayForCommentOfTheDay();
}
$output = '';
if ( !empty( $commentOutput ) ) {
$output .= $commentOutput;
} else {
$output .= wfMessage( 'comments-no-comments-of-day' )->plain();
}
return $output;
}
/**
* Get comments of the day, either from cache or the DB.
*
* @param bool $skipCache Skip using memcached and fetch data directly from the DB?
* @param int $cacheTime How long to cache the results in memcached? Default is one day (60 * 60 * 24).
* @param array $whereConds WHERE conditions for the SQL clause (if not using the defaults)
* @return array
*/
public static function get( $skipCache = false, $cacheTime = 86400, $whereConds = array() ) {
global $wgMemc;
// Try memcached first
$key = wfMemcKey( 'comments-of-the-day', 'standalone-hook-new' );
$data = $wgMemc->get( $key );
if ( $data ) { // success, got it from memcached!
$comments = $data;
} elseif ( !$data || $skipCache ) { // just query the DB
$dbr = wfGetDB( DB_SLAVE );
if ( empty( $whereConds ) ) {
$whereConds = array(
'Comment_Page_ID = page_id',
'UNIX_TIMESTAMP(Comment_Date) > ' . ( time() - ( $cacheTime ) )
);
}
$res = $dbr->select(
array( 'Comments', 'page' ),
array(
'Comment_Username', 'Comment_IP', 'Comment_Text',
'Comment_Date', 'Comment_User_Id', 'CommentID',
'Comment_Parent_ID', 'Comment_Page_ID'
),
$whereConds,
__METHOD__
);
$comments = array();
foreach ( $res as $row ) {
if ( $row->Comment_Parent_ID == 0 ) {
$thread = $row->CommentID;
} else {
$thread = $row->Comment_Parent_ID;
}
$data = array(
'Comment_Username' => $row->Comment_Username,
'Comment_IP' => $row->Comment_IP,
'Comment_Text' => $row->Comment_Text,
'Comment_Date' => $row->Comment_Date,
'Comment_user_id' => $row->Comment_User_Id,
// @todo FIXME: absolutely disgusting -- should use Language's formatNum() for better i18n
'Comment_user_points' => ( isset( $row->stats_total_points ) ? number_format( $row->stats_total_points ) : 0 ),
'CommentID' => $row->CommentID,
'Comment_Parent_ID' => $row->Comment_Parent_ID,
'thread' => $thread,
'timestamp' => wfTimestamp( TS_UNIX, $row->Comment_Date )
);
$page = new CommentsPage( $row->Comment_Page_ID, new RequestContext() );
$comments[] = new Comment( $page, new RequestContext(), $data );
}
usort( $comments, array( 'CommentFunctions', 'sortCommentScore' ) );
$comments = array_slice( $comments, 0, 5 );
$wgMemc->set( $key, $comments, $cacheTime );
}
return $comments;
}
}

View File

@@ -0,0 +1,608 @@
<?php
/**
* Class for Comments methods that are not specific to one comments,
* but specific to one comment-using page
*/
class CommentsPage extends ContextSource {
/**
* @var Integer: page ID (page.page_id) of this page.
*/
public $id = 0;
/**
* @var Integer: if this is _not_ 0, then the comments are ordered by their
* Comment_Score in descending order
*/
public $orderBy = 0;
/**
* @var Integer: maximum amount of threads of comments shown per page before pagination is enabled;
*/
public $limit = 50;
/**
* @TODO document
*
* @var int
*/
public $pagerLimit = 9;
/**
* The current page of comments we are paged to
*
* @var int
*/
public $currentPagerPage = 0;
/**
* List of users allowed to comment. Empty string - any user can comment
*
* @var string
*/
public $allow = '';
/**
* What voting to disallow - disallow PLUS, MINUS, or BOTH
*
* @var string
*/
public $voting = '';
/**
* @var Boolean: allow positive (plus) votes?
*/
public $allowPlus = true;
/**
* @var Boolean: allow negative (minus) votes?
*/
public $allowMinus = true;
/**
* @TODO document
*
* @var string
*/
public $pageQuery = 'cpage';
/**
* @var Title: title object for this page
*/
public $title = null;
/**
* List of lists of comments on this page.
* Each list is a separate 'thread' of comments, with the parent comment first, and any replies following
* Not populated until display() is called
*
* @var array
*/
public $comments = array();
/**
* Constructor
*
* @param $pageID: current page ID
*/
function __construct ( $pageID, $context ) {
$this->id = $pageID;
$this->setContext( $context );
$this->title = Title::newFromID( $pageID );
}
/**
* Gets the total amount of comments on this page
*
* @return int
*/
function countTotal() {
$dbr = wfGetDB( DB_SLAVE );
$count = 0;
$s = $dbr->selectRow(
'Comments',
array( 'COUNT(*) AS CommentCount' ),
array( 'Comment_Page_ID' => $this->id ),
__METHOD__
);
if ( $s !== false ) {
$count = $s->CommentCount;
}
return $count;
}
/**
* Gets the ID number of the latest comment for the current page.
*
* @return int
*/
function getLatestCommentID() {
$latestCommentID = 0;
$dbr = wfGetDB( DB_SLAVE );
$s = $dbr->selectRow(
'Comments',
array( 'CommentID' ),
array( 'Comment_Page_ID' => $this->id ),
__METHOD__,
array( 'ORDER BY' => 'Comment_Date DESC', 'LIMIT' => 1 )
);
if ( $s !== false ) {
$latestCommentID = $s->CommentID;
}
return $latestCommentID;
}
/**
* Set voting either totally off, or disallow "thumbs down" or disallow
* "thumbs up".
*
* @param string $voting 'OFF', 'PLUS' or 'MINUS' (will be strtoupper()ed)
*/
function setVoting( $voting ) {
$this->voting = $voting;
$voting = strtoupper( $voting );
if ( $voting == 'OFF' ) {
$this->allowMinus = false;
$this->allowPlus = false;
}
if ( $voting == 'PLUS' ) {
$this->allowMinus = false;
}
if ( $voting == 'MINUS' ) {
$this->allowPlus = false;
}
}
/**
* Fetches all comments, called by display().
*
* @return array Array containing every possible bit of information about
* a comment, including score, timestamp and more
*/
public function getComments() {
$dbr = wfGetDB( DB_SLAVE );
// Defaults (for non-social wikis)
$tables = array(
'Comments',
'vote1' => 'Comments_Vote',
'vote2' => 'Comments_Vote',
);
$fields = array(
'Comment_Username', 'Comment_IP', 'Comment_Text',
'Comment_Date', 'Comment_Date AS timestamp',
'Comment_user_id', 'CommentID', 'Comment_Parent_ID',
'vote1.Comment_Vote_Score AS current_vote',
'SUM(vote2.Comment_Vote_Score) AS comment_score'
);
$joinConds = array(
// For current user's vote
'vote1' => array(
'LEFT JOIN',
array(
'vote1.Comment_Vote_ID = CommentID',
'vote1.Comment_Vote_Username' => $this->getUser()->getName()
)
),
// For total vote count
'vote2' => array( 'LEFT JOIN', 'vote2.Comment_Vote_ID = CommentID' )
);
$params = array( 'GROUP BY' => 'CommentID' );
// If SocialProfile is installed, query the user_stats table too.
if (
class_exists( 'UserProfile' ) &&
$dbr->tableExists( 'user_stats' )
) {
$tables[] = 'user_stats';
$fields[] = 'stats_total_points';
$joinConds['Comments'] = array(
'LEFT JOIN', 'Comment_user_id = stats_user_id'
);
}
// Perform the query
$res = $dbr->select(
$tables,
$fields,
array( 'Comment_Page_ID' => $this->id ),
__METHOD__,
$params,
$joinConds
);
$comments = array();
foreach ( $res as $row ) {
if ( $row->Comment_Parent_ID == 0 ) {
$thread = $row->CommentID;
} else {
$thread = $row->Comment_Parent_ID;
}
$data = array(
'Comment_Username' => $row->Comment_Username,
'Comment_IP' => $row->Comment_IP,
'Comment_Text' => $row->Comment_Text,
'Comment_Date' => $row->Comment_Date,
'Comment_user_id' => $row->Comment_user_id,
'Comment_user_points' => ( isset( $row->stats_total_points ) ? number_format( $row->stats_total_points ) : 0 ),
'CommentID' => $row->CommentID,
'Comment_Parent_ID' => $row->Comment_Parent_ID,
'thread' => $thread,
'timestamp' => wfTimestamp( TS_UNIX, $row->timestamp ),
'current_vote' => ( isset( $row->current_vote ) ? $row->current_vote : false ),
'total_vote' => ( isset( $row->comment_score ) ? $row->comment_score : 0 ),
);
$comments[] = new Comment( $this, $this->getContext(), $data );
}
$commentThreads = array();
foreach ( $comments as $comment ) {
if ( $comment->parentID == 0 ) {
$commentThreads[$comment->id] = array( $comment );
} else {
$commentThreads[$comment->parentID][] = $comment;
}
}
return $commentThreads;
}
/**
* @return int The page we are currently paged to
* not used for any API calls
*/
function getCurrentPagerPage() {
if ( $this->currentPagerPage == 0 ) {
$this->currentPagerPage = $this->getRequest()->getInt( $this->pageQuery, 1 );
if ( $this->currentPagerPage < 1 ) {
$this->currentPagerPage = 1;
}
}
return $this->currentPagerPage;
}
/**
* Display pager for the current page.
*
* @param int $pagerCurrent Page we are currently paged to
* @param int $pagesCount The maximum page number
*
* @return string: the links for paging through pages of comments
*/
function displayPager( $pagerCurrent, $pagesCount ) {
// Middle is used to "center" pages around the current page.
$pager_middle = ceil( $this->pagerLimit / 2 );
// first is the first page listed by this pager piece (re quantity)
$pagerFirst = $pagerCurrent - $pager_middle + 1;
// last is the last page listed by this pager piece (re quantity)
$pagerLast = $pagerCurrent + $this->pagerLimit - $pager_middle;
// Prepare for generation loop.
$i = $pagerFirst;
if ( $pagerLast > $pagesCount ) {
// Adjust "center" if at end of query.
$i = $i + ( $pagesCount - $pagerLast );
$pagerLast = $pagesCount;
}
if ( $i <= 0 ) {
// Adjust "center" if at start of query.
$pagerLast = $pagerLast + ( 1 - $i );
$i = 1;
}
$output = '';
if ( $pagesCount > 1 ) {
$output .= '<ul class="c-pager">';
$pagerEllipsis = '<li class="c-pager-item c-pager-ellipsis"><span>...</span></li>';
// Whether to display the "Previous page" link
if ( $pagerCurrent > 1 ) {
$output .= '<li class="c-pager-item c-pager-previous">' .
Html::rawElement(
'a',
array(
'rel' => 'nofollow',
'class' => 'c-pager-link',
'href' => '#cfirst',
'data-' . $this->pageQuery => ( $pagerCurrent - 1 ),
),
'&lt;'
) .
'</li>';
}
// Whether to display the "First page" link
if ( $i > 1 ) {
$output .= '<li class="c-pager-item c-pager-first">' .
Html::rawElement(
'a',
array(
'rel' => 'nofollow',
'class' => 'c-pager-link',
'href' => '#cfirst',
'data-' . $this->pageQuery => 1,
),
1
) .
'</li>';
}
// When there is more than one page, create the pager list.
if ( $i != $pagesCount ) {
if ( $i > 2 ) {
$output .= $pagerEllipsis;
}
// Now generate the actual pager piece.
for ( ; $i <= $pagerLast && $i <= $pagesCount; $i++ ) {
if ( $i == $pagerCurrent ) {
$output .= '<li class="c-pager-item c-pager-current"><span>' .
$i . '</span></li>';
} else {
$output .= '<li class="c-pager-item">' .
Html::rawElement(
'a',
array(
'rel' => 'nofollow',
'class' => 'c-pager-link',
'href' => '#cfirst',
'data-' . $this->pageQuery => $i,
),
$i
) .
'</li>';
}
}
if ( $i < $pagesCount ) {
$output .= $pagerEllipsis;
}
}
// Whether to display the "Last page" link
if ( $pagesCount > ( $i - 1 ) ) {
$output .= '<li class="c-pager-item c-pager-last">' .
Html::rawElement(
'a',
array(
'rel' => 'nofollow',
'class' => 'c-pager-link',
'href' => '#cfirst',
'data-' . $this->pageQuery => $pagesCount,
),
$pagesCount
) .
'</li>';
}
// Whether to display the "Next page" link
if ( $pagerCurrent < $pagesCount ) {
$output .= '<li class="c-pager-item c-pager-next">' .
Html::rawElement(
'a',
array(
'rel' => 'nofollow',
'class' => 'c-pager-link',
'href' => '#cfirst',
'data-' . $this->pageQuery => ( $pagerCurrent + 1 ),
),
'&gt;'
) .
'</li>';
}
$output .= '</ul>';
}
return $output;
}
/**
* Get this list of anon commenters in the given list of comments,
* and return a mapped array of IP adressess to the the number anon poster
* (so anon posters can be called Anon#1, Anon#2, etc
*
* @return array
*/
function getAnonList() {
$counter = 1;
$bucket = array();
$commentThreads = $this->comments;
$comments = array(); // convert 2nd threads array to a simple list of comments
foreach ( $commentThreads as $thread ) {
$comments = array_merge( $comments, $thread );
}
usort( $comments, array( 'CommentFunctions', 'sortTime' ) );
foreach ( $comments as $comment ) {
if (
!array_key_exists( $comment->username, $bucket ) &&
$comment->userID == 0
) {
$bucket[$comment->username] = $counter;
$counter++;
}
}
return $bucket;
}
/**
* Sort an array of comment threads
* @param $threads
* @return mixed
*/
function sort( $threads ) {
global $wgCommentsSortDescending;
if ( $this->orderBy ) {
usort( $threads, array( 'CommentFunctions', 'sortScore' ) );
} elseif ( $wgCommentsSortDescending ) {
usort( $threads, array( 'CommentFunctions', 'sortDesc' ) );
} else {
usort( $threads, array( 'CommentFunctions', 'sortAsc' ) );
}
return $threads;
}
/**
* Convert an array of comment threads into an array of pages (arrays) of comment threads
* @param $comments
* @return array
*/
function page( $comments ) {
return array_chunk( $comments, $this->limit );
}
/**
* Display all the comments for the current page.
* CSS and JS is loaded in CommentsHooks.php
*/
function display() {
$output = '';
$commentThreads = $this->getComments();
$commentThreads = $this->sort( $commentThreads );
$this->comments = $commentThreads;
$commentPages = $this->page( $commentThreads );
$currentPageNum = $this->getCurrentPagerPage();
$numPages = count( $commentPages );
// Suppress random E_NOTICE about "Undefined offset: 0", which seems to
// be breaking ProblemReports (at least on my local devbox, not sure
// about prod). --Jack Phoenix, 13 July 2015
wfSuppressWarnings();
$currentPage = $commentPages[$currentPageNum - 1];
wfRestoreWarnings();
// Load complete blocked list for logged in user so they don't see their comments
$blockList = array();
if ( $this->getUser()->getID() != 0 ) {
$blockList = CommentFunctions::getBlockList( $this->getUser()->getId() );
}
if ( $currentPage ) {
$pager = $this->displayPager( $currentPageNum, $numPages );
$output .= $pager;
$output .= '<a id="cfirst" name="cfirst" rel="nofollow"></a>';
$anonList = $this->getAnonList();
foreach ( $currentPage as $thread ) {
foreach ( $thread as $comment ) {
$output .= $comment->display( $blockList, $anonList );
}
}
$output .= $pager;
}
return $output;
}
/**
* Displays the "Sort by X" form and a link to auto-refresh comments
*
* @return string HTML
*/
function displayOrderForm() {
$output = '<div class="c-order">
<div class="c-order-select">
<form name="ChangeOrder" action="">
<select name="TheOrder">
<option value="0">' .
wfMessage( 'comments-sort-by-date' )->plain() .
'</option>
<option value="1">' .
wfMessage( 'comments-sort-by-score' )->plain() .
'</option>
</select>
</form>
</div>
<div id="spy" class="c-spy">
<a href="javascript:void(0)">' .
wfMessage( 'comments-auto-refresher-enable' )->plain() .
'</a>
</div>
<div class="visualClear"></div>
</div>
<br />' . "\n";
return $output;
}
/**
* Displays the form for adding new comments
*
* @return string HTML output
*/
function displayForm() {
$output = '<form action="" method="post" name="commentForm">' . "\n";
if ( $this->allow ) {
$pos = strpos(
strtoupper( addslashes( $this->allow ) ),
strtoupper( addslashes( $this->getUser()->getName() ) )
);
}
// 'comment' user right is required to add new comments
if ( !$this->getUser()->isAllowed( 'comment' ) ) {
$output .= wfMessage( 'comments-not-allowed' )->parse();
} else {
// Blocked users can't add new comments under any conditions...
// and maybe there's a list of users who should be allowed to post
// comments
if ( $this->getUser()->isBlocked() == false && ( $this->allow == '' || $pos !== false ) ) {
$output .= '<div class="c-form-title">' . wfMessage( 'comments-submit' )->plain() . '</div>' . "\n";
$output .= '<div id="replyto" class="c-form-reply-to"></div>' . "\n";
// Show a message to anons, prompting them to register or log in
if ( !$this->getUser()->isLoggedIn() ) {
$login_title = SpecialPage::getTitleFor( 'Userlogin' );
$register_title = SpecialPage::getTitleFor( 'Userlogin', 'signup' );
$output .= '<div class="c-form-message">' . wfMessage(
'comments-anon-message',
htmlspecialchars( $register_title->getFullURL() ),
htmlspecialchars( $login_title->getFullURL() )
)->text() . '</div>' . "\n";
}
$output .= '<textarea name="commentText" id="comment" rows="5" cols="64"></textarea>' . "\n";
$output .= '<div class="c-form-button"><input type="button" value="' .
wfMessage( 'comments-post' )->plain() . '" class="site-button" /></div>' . "\n";
}
$output .= '<input type="hidden" name="action" value="purge" />' . "\n";
$output .= '<input type="hidden" name="pageId" value="' . $this->id . '" />' . "\n";
$output .= '<input type="hidden" name="commentid" />' . "\n";
$output .= '<input type="hidden" name="lastCommentId" value="' . $this->getLatestCommentID() . '" />' . "\n";
$output .= '<input type="hidden" name="commentParentId" />' . "\n";
$output .= '<input type="hidden" name="' . $this->pageQuery . '" value="' . $this->getCurrentPagerPage() . '" />' . "\n";
$output .= Html::hidden( 'token', $this->getUser()->getEditToken() );
}
$output .= '</form>' . "\n";
return $output;
}
/**
* Purge caches (parser cache and Squid cache)
*/
function clearCommentListCache() {
wfDebug( "Clearing comments for page {$this->id} from cache\n" );
if ( is_object( $this->title ) ) {
$this->title->invalidateCache();
$this->title->purgeSquid();
}
}
}

View File

@@ -0,0 +1,135 @@
<?php
class NumberOfComments {
/**
* Registers NUMBEROFCOMMENTS and NUMPBEROFCOMMENTSPAGE as a valid magic word identifier.
*
* @param array $variableIds Array of valid magic word identifiers
* @return bool
*/
public static function registerNumberOfCommentsMagicWord( &$variableIds ) {
$variableIds[] = 'NUMBEROFCOMMENTS';
$variableIds[] = 'NUMBEROFCOMMENTSPAGE';
return true;
}
/**
* Hook to setup parser function
*
* @param Parser $parser
* @return bool
*/
static function setupNumberOfCommentsPageParser( &$parser ) {
$parser->setFunctionHook( 'NUMBEROFCOMMENTSPAGE', 'NumberOfComments::getNumberOfCommentsPageParser', Parser::SFH_NO_HASH );
return true;
}
/**
* Main backend logic for the {{NUMBEROFCOMMENTS}} and {{NUMBEROFCOMMENTSPAGE}}
* magic word.
* If the {{NUMBEROFCOMMENTS}} magic word is found, first checks memcached to
* see if we can get the value from cache, but if that fails for some reason,
* then a COUNT(*) SQL query is done to fetch the amount from the database.
* If the {{NUMBEROFCOMMENTSPAGE}} magic word is found, uses
* NumberOfComments::getNumberOfCommentsPage to get the number of comments
* for this article.
*
* @param $parser Parser
* @param $cache
* @param string $magicWordId Magic word identifier
* @param int $ret What to return to the user (in our case, the number of comments)
* @return bool
*/
public static function getNumberOfCommentsMagic( &$parser, &$cache, &$magicWordId, &$ret ) {
global $wgMemc;
if ( $magicWordId == 'NUMBEROFCOMMENTS' ) {
$key = wfMemcKey( 'comments', 'magic-word' );
$data = $wgMemc->get( $key );
if ( $data != '' ) {
// We have it in cache? Oh goody, let's just use the cached value!
wfDebugLog(
'Comments',
'Got the amount of comments from memcached'
);
// return value
$ret = $data;
} else {
// Not cached → have to fetch it from the database
$dbr = wfGetDB( DB_SLAVE );
$commentCount = (int)$dbr->selectField(
'Comments',
'COUNT(*) AS count',
array(),
__METHOD__
);
wfDebugLog( 'Comments', 'Got the amount of comments from DB' );
// Store the count in cache...
// (86400 = seconds in a day)
$wgMemc->set( $key, $commentCount, 86400 );
// ...and return the value to the user
$ret = $commentCount;
}
} elseif ( $magicWordId == 'NUMBEROFCOMMENTSPAGE' ) {
$id = $parser->getTitle()->getArticleID();
$ret = NumberOfComments::getNumberOfCommentsPage( $id );
}
return true;
}
/**
* Hook for parser function {{NUMBEROFCOMMENTSPAGE:<page>}}
*
* @param Parser $parser
* @param string $pagename Page name
* @return int Amount of comments on the given page
*/
static function getNumberOfCommentsPageParser( $parser, $pagename ) {
$page = Title::newFromText( $pagename );
if ( $page instanceof Title ) {
$id = $page->getArticleID();
} else {
$id = $parser->getTitle()->getArticleID();
}
return NumberOfComments::getNumberOfCommentsPage( $id );
}
/**
* Get the actual number of comments
*
* @param int $pageId ID of page to get number of comments for
* @return int
*/
static function getNumberOfCommentsPage( $pageId ) {
global $wgMemc;
$key = wfMemcKey( 'comments', 'numberofcommentspage', $pageId );
$cache = $wgMemc->get( $key );
if ( $cache ) {
$val = intval( $cache );
} else {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->selectField(
'Comments',
'COUNT(*)',
array( 'Comment_Page_ID' => $pageId ),
__METHOD__
);
if ( !$res ) {
$val = 0;
} else {
$val = intval( $res );
}
$wgMemc->set( $key, $val, 60 * 60 ); // cache for an hour
}
return $val;
}
}

View File

@@ -0,0 +1,52 @@
<?php
class CommentBlockAPI extends ApiBase {
public function execute() {
// Do nothing when the database is in read-only mode
if ( wfReadOnly() ) {
return true;
}
// Load user_name and user_id for person we want to block from the comment it originated from
$dbr = wfGetDB( DB_SLAVE );
$s = $dbr->selectRow(
'Comments',
array( 'comment_username', 'comment_user_id' ),
array( 'CommentID' => $this->getMain()->getVal( 'commentID' ) ),
__METHOD__
);
if ( $s !== false ) {
$userID = $s->comment_user_id;
$username = $s->comment_username;
}
CommentFunctions::blockUser( $this->getUser(), $userID, $username );
if ( class_exists( 'UserStatsTrack' ) ) {
$stats = new UserStatsTrack( $userID, $username );
$stats->incStatField( 'comment_ignored' );
}
$result = $this->getResult();
$result->addValue( $this->getModuleName(), 'ok', 'ok' );
return true;
}
public function needsToken() {
return 'csrf';
}
public function isWriteMode() {
return true;
}
public function getAllowedParams() {
return array(
'commentID' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'integer'
)
);
}
}

View File

@@ -0,0 +1,41 @@
<?php
class CommentDeleteAPI extends ApiBase {
public function execute() {
$user = $this->getUser();
// Blocked users cannot delete comments, and neither can unprivileged ones.
// Also check for database read-only status
if (
$user->isBlocked() ||
!$user->isAllowed( 'commentadmin' ) ||
wfReadOnly()
) {
return true;
}
$comment = Comment::newFromID( $this->getMain()->getVal( 'commentID' ) );
$comment->delete();
$result = $this->getResult();
$result->addValue( $this->getModuleName(), 'ok', 'ok' );
return true;
}
public function needsToken() {
return 'csrf';
}
public function isWriteMode() {
return true;
}
public function getAllowedParams() {
return array(
'commentID' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'integer'
)
);
}
}

View File

@@ -0,0 +1,22 @@
<?php
class CommentLatestIdAPI extends ApiBase {
public function execute() {
$pageID = $this->getMain()->getVal( 'pageID' );
$commentsPage = new CommentsPage( $pageID, RequestContext::getMain() );
$result = $this->getResult();
$result->addValue( $this->getModuleName(), 'id', $commentsPage->getLatestCommentID() );
}
public function getAllowedParams() {
return array(
'pageID' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'int'
)
);
}
}

View File

@@ -0,0 +1,44 @@
<?php
class CommentListAPI extends ApiBase {
public function execute() {
$commentsPage = new CommentsPage( $this->getMain()->getVal( 'pageID' ), RequestContext::getMain() );
$commentsPage->orderBy = $this->getMain()->getVal( 'order' );
$commentsPage->currentPagerPage = $this->getMain()->getVal( 'pagerPage' );
$output = '';
if ( $this->getMain()->getVal( 'showForm' ) ) {
$output .= $commentsPage->displayOrderForm();
}
$output .= $commentsPage->display();
if ( $this->getMain()->getVal( 'showForm' ) ) {
$output .= $commentsPage->displayForm();
}
$result = $this->getResult();
$result->addValue( $this->getModuleName(), 'html', $output );
return true;
}
public function getAllowedParams() {
return array(
'pageID' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'integer'
),
'order' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'boolean'
),
'pagerPage' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'integer'
),
'showForm' => array(
ApiBase::PARAM_REQUIRED => false,
ApiBase::PARAM_TYPE => 'integer'
)
);
}
}

View File

@@ -0,0 +1,73 @@
<?php
class CommentSubmitAPI extends ApiBase {
public function execute() {
$user = $this->getUser();
// Blocked users cannot submit new comments, and neither can those users
// without the necessary privileges. Also prevent obvious cross-site request
// forgeries (CSRF)
if (
$user->isBlocked() ||
!$user->isAllowed( 'comment' ) ||
wfReadOnly()
) {
return true;
}
$commentText = $this->getMain()->getVal( 'commentText' );
if ( $commentText != '' ) {
// To protect against spam, it's necessary to check the supplied text
// against spam filters (but comment admins are allowed to bypass the
// spam filters)
if ( !$user->isAllowed( 'commentadmin' ) && CommentFunctions::isSpam( $commentText ) ) {
$this->dieUsage( wfMessage( 'comments-is-spam' )->plain(), 'comments-is-spam' );
}
// If the comment contains links but the user isn't allowed to post
// links, reject the submission
if ( !$user->isAllowed( 'commentlinks' ) && CommentFunctions::haveLinks( $commentText ) ) {
$this->dieUsage( wfMessage( 'comments-links-are-forbidden' )->plain(), 'comments-links-are-forbidden' );
}
$page = new CommentsPage( $this->getMain()->getVal( 'pageID' ), $this->getContext() );
Comment::add( $commentText, $page, $user, $this->getMain()->getVal( 'parentID' ) );
if ( class_exists( 'UserStatsTrack' ) ) {
$stats = new UserStatsTrack( $user->getID(), $user->getName() );
$stats->incStatField( 'comment' );
}
}
$result = $this->getResult();
$result->addValue( $this->getModuleName(), 'ok', 'ok' );
return true;
}
public function needsToken() {
return 'csrf';
}
public function isWriteMode() {
return true;
}
public function getAllowedParams() {
return array(
'pageID' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'integer'
),
'parentID' => array(
ApiBase::PARAM_REQUIRED => false,
ApiBase::PARAM_TYPE => 'integer'
),
'commentText' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'string'
)
);
}
}

View File

@@ -0,0 +1,72 @@
<?php
class CommentVoteAPI extends ApiBase {
public function execute() {
// Blocked users cannot vote, obviously, and neither can those users without the necessary privileges
if (
$this->getUser()->isBlocked() ||
!$this->getUser()->isAllowed( 'comment' ) ||
wfReadOnly()
) {
return '';
}
$comment = Comment::newFromID( $this->getMain()->getVal( 'commentID' ) );
$voteValue = $this->getMain()->getVal( 'voteValue' );
if ( $comment && is_numeric( $voteValue ) ) {
$comment->vote( $voteValue );
$html = $comment->getScoreHTML();
$html = htmlspecialchars( $html );
if ( class_exists( 'UserStatsTrack' ) ) {
$stats = new UserStatsTrack( $this->getUser()->getID(), $this->getUser()->getName() );
// Must update stats for user doing the voting
if ( $voteValue == 1 ) {
$stats->incStatField( 'comment_give_plus' );
}
if ( $voteValue == -1 ) {
$stats->incStatField( 'comment_give_neg' );
}
// Also must update the stats for user receiving the vote
$stats_comment_owner = new UserStatsTrack( $comment->userID, $comment->username );
$stats_comment_owner->updateCommentScoreRec( $voteValue );
$stats_comment_owner->updateTotalPoints();
if ( $voteValue === 1 ) {
$stats_comment_owner->updateWeeklyPoints( $stats_comment_owner->point_values['comment_plus'] );
$stats_comment_owner->updateMonthlyPoints( $stats_comment_owner->point_values['comment_plus'] );
}
}
$result = $this->getResult();
$result->addValue( $this->getModuleName(), 'html', $html );
return true;
}
}
public function needsToken() {
return 'csrf';
}
public function isWriteMode() {
return true;
}
public function getAllowedParams() {
return array(
'commentID' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'integer'
),
'voteValue' => array(
ApiBase::PARAM_REQUIRED => true,
ApiBase::PARAM_TYPE => 'integer'
),
);
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* A special page for displaying the list of users whose comments you're
* ignoring.
* @file
* @ingroup Extensions
*/
class CommentIgnoreList extends SpecialPage {
/**
* Constructor -- set up the new special page
*/
public function __construct() {
parent::__construct( 'CommentIgnoreList' );
}
public function doesWrites() {
return true;
}
/**
* Group this special page under the correct header in Special:SpecialPages.
*
* @return string
*/
function getGroupName() {
return 'users';
}
/**
* Show the special page
*
* @param mixed|null $par Parameter passed to the page or null
*/
public function execute( $par ) {
$out = $this->getOutput();
$request = $this->getRequest();
$user = $this->getUser();
$user_name = $request->getVal( 'user' );
/**
* Redirect anonymous users to Login Page
* It will automatically return them to the CommentIgnoreList page
*/
if ( $user->getID() == 0 && $user_name == '' ) {
$loginPage = SpecialPage::getTitleFor( 'Userlogin' );
$out->redirect( $loginPage->getLocalURL( 'returnto=Special:CommentIgnoreList' ) );
return;
}
$out->setPageTitle( $this->msg( 'comments-ignore-title' )->text() );
$output = ''; // Prevent E_NOTICE
if ( $user_name == '' ) {
$output .= $this->displayCommentBlockList();
} else {
if ( $request->wasPosted() ) {
// Check for cross-site request forgeries (CSRF)
if ( !$user->matchEditToken( $request->getVal( 'token' ) ) ) {
$out->addWikiMsg( 'sessionfailure' );
return;
}
$user_name = htmlspecialchars_decode( $user_name );
$user_id = User::idFromName( $user_name );
// Anons can be comment-blocked, but idFromName returns nothing
// for an anon, so...
if ( !$user_id ) {
$user_id = 0;
}
CommentFunctions::deleteBlock( $user->getID(), $user_id );
if ( $user_id && class_exists( 'UserStatsTrack' ) ) {
$stats = new UserStatsTrack( $user_id, $user_name );
$stats->decStatField( 'comment_ignored' );
}
$output .= $this->displayCommentBlockList();
} else {
$output .= $this->confirmCommentBlockDelete();
}
}
$out->addHTML( $output );
}
/**
* Displays the list of users whose comments you're ignoring.
*
* @return string HTML
*/
function displayCommentBlockList() {
$lang = $this->getLanguage();
$title = $this->getPageTitle();
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
'Comments_block',
array( 'cb_user_name_blocked', 'cb_date' ),
array( 'cb_user_id' => $this->getUser()->getID() ),
__METHOD__,
array( 'ORDER BY' => 'cb_user_name' )
);
if ( $dbr->numRows( $res ) > 0 ) {
$out = '<ul>';
foreach ( $res as $row ) {
$user_title = Title::makeTitle( NS_USER, $row->cb_user_name_blocked );
$out .= '<li>' . $this->msg(
'comments-ignore-item',
htmlspecialchars( $user_title->getFullURL() ),
$user_title->getText(),
$lang->timeanddate( $row->cb_date ),
htmlspecialchars( $title->getFullURL( 'user=' . $user_title->getText() ) )
)->text() . '</li>';
}
$out .= '</ul>';
} else {
$out = '<div class="comment_blocked_user">' .
$this->msg( 'comments-ignore-no-users' )->text() . '</div>';
}
return $out;
}
/**
* Asks for a confirmation when you're about to unblock someone's comments.
*
* @return string HTML
*/
function confirmCommentBlockDelete() {
$user_name = $this->getRequest()->getVal( 'user' );
$out = '<div class="comment_blocked_user">' .
$this->msg( 'comments-ignore-remove-message', $user_name )->parse() .
'</div>
<div>
<form action="" method="post" name="comment_block">' .
Html::hidden( 'user', $user_name ) . "\n" .
Html::hidden( 'token', $this->getUser()->getEditToken() ) . "\n" .
'<input type="button" class="site-button" value="' . $this->msg( 'comments-ignore-unblock' )->text() . '" onclick="document.comment_block.submit()" />
<input type="button" class="site-button" value="' . $this->msg( 'comments-ignore-cancel' )->text() . '" onclick="history.go(-1)" />
</form>
</div>';
return $out;
}
}