summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'CommentStreams/includes/CommentStreamsStore.php')
-rw-r--r--CommentStreams/includes/CommentStreamsStore.php601
1 files changed, 601 insertions, 0 deletions
diff --git a/CommentStreams/includes/CommentStreamsStore.php b/CommentStreams/includes/CommentStreamsStore.php
new file mode 100644
index 00000000..b3202924
--- /dev/null
+++ b/CommentStreams/includes/CommentStreamsStore.php
@@ -0,0 +1,601 @@
+<?php
+
+/*
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+namespace MediaWiki\Extension\CommentStreams;
+
+use ContentHandler;
+use FatalError;
+use MWException;
+use Title;
+use User;
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\ILoadBalancer;
+use Wikimedia\Rdbms\IResultWrapper;
+use WikiPage;
+use WikitextContent;
+
+class CommentStreamsStore {
+ /**
+ * @var ILoadBalancer
+ */
+ private $loadBalancer;
+
+ /**
+ * @param ILoadBalancer $loadBalancer
+ */
+ public function __construct( ILoadBalancer $loadBalancer ) {
+ $this->loadBalancer = $loadBalancer;
+ }
+
+ /**
+ * @return ILoadBalancer
+ */
+ private function getDBLoadBalancer(): ILoadBalancer {
+ return $this->loadBalancer;
+ }
+
+ /**
+ * @param int $mode DB_MASTER or DB_REPLICA
+ * @return IDatabase
+ */
+ private function getDBConnection( int $mode ): IDatabase {
+ $lb = $this->getDBLoadBalancer();
+
+ return $lb->getConnection( $mode );
+ }
+
+ /**
+ * @param User $user
+ * @param string $wikitext
+ * @param ?string $comment_block_id
+ * @param int $assoc_page_id
+ * @param ?int $parent_page_id
+ * @param ?string $comment_title
+ * @return ?WikiPage
+ * @throws MWException
+ */
+ public function insertComment(
+ User $user,
+ string $wikitext,
+ ?string $comment_block_id,
+ int $assoc_page_id,
+ ?int $parent_page_id,
+ ?string $comment_title
+ ): ?WikiPage {
+ $annotated_wikitext = $this->addAnnotations( $wikitext, $comment_title );
+ $content = new WikitextContent( $annotated_wikitext );
+
+ $success = false;
+ $index = wfRandomString();
+ do {
+ $title = Title::newFromText( (string)$index, NS_COMMENTSTREAMS );
+ $wikipage = new WikiPage( $title );
+ $deleted = CommentStreamsUtils::hasDeletedEdits( $title );
+ if ( !$deleted && !$title->exists() ) {
+ if ( !CommentStreamsUtils::userCan( 'cs-comment', $user, $title ) ) {
+ return null;
+ }
+ $status = CommentStreamsUtils::doEditContent(
+ $wikipage,
+ $content,
+ $user,
+ EDIT_NEW | EDIT_SUPPRESS_RC
+ );
+ if ( !$status->isOK() && !$status->isGood() ) {
+ if ( $status->getMessage()->getKey() == 'edit-already-exists' ) {
+ $index = wfRandomString();
+ } else {
+ return null;
+ }
+ } else {
+ $success = true;
+ }
+ } else {
+ $index = wfRandomString();
+ }
+ } while ( !$success );
+
+ $dbw = $this->getDBConnection( DB_MASTER );
+ $result = $dbw->insert(
+ 'cs_comment_data',
+ [
+ 'cst_page_id' => $wikipage->getId(),
+ 'cst_id' => $comment_block_id,
+ 'cst_assoc_page_id' => $assoc_page_id,
+ 'cst_parent_page_id' => $parent_page_id,
+ 'cst_comment_title' => $comment_title
+ ],
+ __METHOD__
+ );
+
+ if ( !$result ) {
+ return null;
+ }
+
+ return $wikipage;
+ }
+
+ /**
+ * @param int $id
+ * @return ?array
+ */
+ public function getComment( int $id ): ?array {
+ $dbr = $this->getDBConnection( DB_REPLICA );
+ $result = $dbr->selectRow(
+ 'cs_comment_data',
+ [
+ 'cst_id',
+ 'cst_assoc_page_id',
+ 'cst_parent_page_id',
+ 'cst_comment_title'
+ ],
+ [
+ 'cst_page_id' => $id
+ ],
+ __METHOD__
+ );
+ if ( $result ) {
+ return [
+ 'comment_block_id' => $result->cst_id,
+ 'assoc_page_id' => (int)$result->cst_assoc_page_id,
+ 'parent_page_id' => $result->cst_parent_page_id ? (int)$result->cst_parent_page_id
+ : null,
+ 'comment_title' => $result->cst_comment_title
+ ];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param int $limit
+ * @param int $offset
+ * @return IResultWrapper
+ */
+ public function getCommentPages( int $limit, int $offset ) : IResultWrapper {
+ $dbr = $this->getDBConnection( DB_REPLICA );
+ return $dbr->select(
+ [
+ 'cs_comment_data',
+ 'page',
+ 'revision'
+ ],
+ [
+ 'page_id'
+ ],
+ [
+ 'cst_page_id = page_id',
+ 'page_latest = rev_id'
+ ],
+ __METHOD__,
+ [
+ 'ORDER BY' => 'rev_timestamp DESC' ,
+ 'LIMIT' => $limit,
+ 'OFFSET' => $offset
+ ]
+ );
+ }
+
+ /**
+ * @param WikiPage $wikipage
+ * @param ?string $comment_title
+ * @param string $wikitext
+ * @param User $user
+ * @return bool
+ * @throws MWException
+ */
+ public function updateComment(
+ WikiPage $wikipage,
+ ?string $comment_title,
+ string $wikitext,
+ User $user
+ ) : bool {
+ $annotated_wikitext = $this->addAnnotations( $wikitext, $comment_title );
+ $content = new WikitextContent( $annotated_wikitext );
+
+ $status = CommentStreamsUtils::doEditContent(
+ $wikipage,
+ $content,
+ $user,
+ EDIT_UPDATE | EDIT_SUPPRESS_RC
+ );
+ if ( !$status->isOK() && !$status->isGood() ) {
+ return false;
+ }
+
+ $dbw = $this->getDBConnection( DB_MASTER );
+ return $dbw->update(
+ 'cs_comment_data',
+ [
+ 'cst_comment_title' => $comment_title
+ ],
+ [
+ 'cst_page_id' => $wikipage->getId()
+ ],
+ __METHOD__
+ );
+ }
+
+ /**
+ * @param WikiPage $wikipage
+ * @param User $deleter
+ * @return bool
+ * @throws FatalError
+ * @throws MWException
+ */
+ public function deleteComment( WikiPage $wikipage, User $deleter ) : bool {
+ // must save page ID before deleting page
+ $pageid = $wikipage->getId();
+
+ $status = CommentStreamsUtils::deDeleteArticle(
+ $wikipage,
+ 'comment deleted',
+ $deleter
+ );
+
+ if ( !$status->isOK() && !$status->isGood() ) {
+ return false;
+ }
+
+ $dbw = $this->getDBConnection( DB_MASTER );
+ return $dbw->delete(
+ 'cs_comment_data',
+ [
+ 'cst_page_id' => $pageid
+ ],
+ __METHOD__
+ );
+ }
+
+ /**
+ * @param int $id
+ * @return array
+ */
+ public function getAssociatedComments( int $id ) : array {
+ $dbr = $this->getDBConnection( DB_REPLICA );
+ $result = $dbr->select(
+ 'cs_comment_data',
+ [
+ 'cst_page_id'
+ ],
+ [
+ 'cst_assoc_page_id' => $id
+ ],
+ __METHOD__
+ );
+ $comment_page_ids = [];
+ foreach ( $result as $row ) {
+ $comment_page_ids[] = $row->cst_page_id;
+ }
+ return $comment_page_ids;
+ }
+
+ /**
+ * @param int $id
+ * @return array
+ */
+ public function getReplies( int $id ): array {
+ $dbr = $this->getDBConnection( DB_REPLICA );
+ $result = $dbr->select(
+ 'cs_comment_data',
+ [
+ 'cst_page_id'
+ ],
+ [
+ 'cst_parent_page_id' => $id
+ ],
+ __METHOD__
+ );
+ $reply_ids = [];
+ foreach ( $result as $row ) {
+ $reply_ids[] = $row->cst_page_id;
+ }
+ return $reply_ids;
+ }
+
+ /**
+ * @param int $id
+ * @return int
+ */
+ public function getNumReplies( int $id ): int {
+ $dbr = $this->getDBConnection( DB_REPLICA );
+
+ return $dbr->selectRowCount(
+ 'cs_comment_data',
+ '*',
+ [
+ 'cst_parent_page_id' => $id
+ ],
+ __METHOD__
+ );
+ }
+
+ /**
+ * @param int $page_id
+ * @param int $user_id
+ * @return int -1, 0, or 1
+ */
+ public function getVote( int $page_id, int $user_id ) : int {
+ $dbr = $this->getDBConnection( DB_REPLICA );
+ $result = $dbr->selectRow(
+ 'cs_votes',
+ [
+ 'cst_v_vote'
+ ],
+ [
+ 'cst_v_page_id' => $page_id,
+ 'cst_v_user_id' => $user_id
+ ],
+ __METHOD__
+ );
+ if ( $result ) {
+ $vote = (int)$result->cst_v_vote;
+ if ( $vote > 0 ) {
+ return 1;
+ }
+ if ( $vote < 0 ) {
+ return -1;
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * @param int $id
+ * @return int
+ */
+ public function getNumUpVotes( int $id ): int {
+ return $this->getNumVotes( $id, true );
+ }
+
+ /**
+ * @param int $id
+ * @return int
+ */
+ public function getNumDownVotes( int $id ): int {
+ return $this->getNumVotes( $id, false );
+ }
+
+ /**
+ * @param int $id
+ * @param bool $up
+ * @return int
+ */
+ private function getNumVotes( int $id, bool $up ): int {
+ $dbr = $this->getDBConnection( DB_REPLICA );
+
+ return $dbr->selectRowCount(
+ 'cs_votes',
+ '*',
+ [
+ 'cst_v_page_id' => $id,
+ 'cst_v_vote' => $up ? 1 : -1
+ ],
+ __METHOD__
+ );
+ }
+
+ /**
+ * @param int $vote
+ * @param int $page_id
+ * @param int $user_id
+ * @return bool true for OK, false for error
+ */
+ public function vote( int $vote, int $page_id, int $user_id ) : bool {
+ $dbw = $this->getDBConnection( DB_MASTER );
+ $result = $dbw->selectRow(
+ 'cs_votes',
+ [
+ 'cst_v_vote'
+ ],
+ [
+ 'cst_v_page_id' => $page_id,
+ 'cst_v_user_id' => $user_id
+ ],
+ __METHOD__
+ );
+ if ( $result ) {
+ if ( $vote === (int)$result->cst_v_vote ) {
+ return true;
+ }
+ if ( $vote === 1 || $vote === -1 ) {
+ return $dbw->update(
+ 'cs_votes',
+ [
+ 'cst_v_vote' => $vote
+ ],
+ [
+ 'cst_v_page_id' => $page_id,
+ 'cst_v_user_id' => $user_id
+ ],
+ __METHOD__
+ );
+ } else {
+ return $dbw->delete(
+ 'cs_votes',
+ [
+ 'cst_v_page_id' => $page_id,
+ 'cst_v_user_id' => $user_id
+ ],
+ __METHOD__
+ );
+ }
+ }
+ if ( $vote === 0 ) {
+ return true;
+ }
+
+ return $dbw->insert(
+ 'cs_votes',
+ [
+ 'cst_v_page_id' => $page_id,
+ 'cst_v_user_id' => $user_id,
+ 'cst_v_vote' => $vote
+ ],
+ __METHOD__
+ );
+ }
+
+ /**
+ * @param int $page_id the page ID of the comment to watch
+ * @param int $user_id the user ID of the user watching the comment
+ * @return bool true for OK, false for error
+ */
+ public function watch( int $page_id, int $user_id ) : bool {
+ if ( $this->isWatching( $page_id, $user_id, DB_MASTER ) ) {
+ return true;
+ }
+ $dbw = $this->getDBConnection( DB_MASTER );
+
+ return $dbw->insert(
+ 'cs_watchlist',
+ [
+ 'cst_wl_page_id' => $page_id,
+ 'cst_wl_user_id' => $user_id
+ ],
+ __METHOD__
+ );
+ }
+
+ /**
+ * @param int $page_id the page ID of the comment to watch
+ * @param int $user_id the user ID of the user watching the comment
+ * @return bool true for OK, false for error
+ */
+ public function unwatch( int $page_id, int $user_id ): bool {
+ if ( !$this->isWatching( $page_id, $user_id, DB_MASTER ) ) {
+ return true;
+ }
+ $dbw = $this->getDBConnection( DB_MASTER );
+
+ return $dbw->delete(
+ 'cs_watchlist',
+ [
+ 'cst_wl_page_id' => $page_id,
+ 'cst_wl_user_id' => $user_id
+ ],
+ __METHOD__
+ );
+ }
+
+ /**
+ * @param int $page_id the page ID of the comment to check
+ * @param int $user_id the user ID of the user watching the comment
+ * @param int $fromdb DB_MASTER or DB_REPLICA
+ * @return bool database true for OK, false for error
+ */
+ public function isWatching( int $page_id, int $user_id, int $fromdb = DB_REPLICA ): bool {
+ $db = $this->getDBConnection( $fromdb );
+ $result = $db->selectRow(
+ 'cs_watchlist',
+ [
+ 'cst_wl_page_id'
+ ],
+ [
+ 'cst_wl_page_id' => $page_id,
+ 'cst_wl_user_id' => $user_id
+ ],
+ __METHOD__
+ );
+
+ if ( $result ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param int $id
+ * @return array of user IDs
+ */
+ public function getWatchers( int $id ): array {
+ $dbr = $this->getDBConnection( DB_REPLICA );
+ $result = $dbr->select(
+ 'cs_watchlist',
+ [
+ 'cst_wl_user_id'
+ ],
+ [
+ 'cst_wl_page_id' => $id
+ ],
+ __METHOD__
+ );
+ $users = [];
+ foreach ( $result as $row ) {
+ $user_id = $row->cst_wl_user_id;
+ $user = User::newFromId( $user_id );
+ $users[$user_id] = $user;
+ }
+
+ return $users;
+ }
+
+ /**
+ * @param WikiPage $wikipage
+ * @param ?string $comment_title
+ * @return string
+ * @throws MWException
+ */
+ public function getWikiText( WikiPage $wikipage, ?string $comment_title ) : string {
+ $wikitext = ContentHandler::getContentText( CommentStreamsUtils::getContent( $wikipage ) );
+ return $this->removeAnnotations( $wikitext, $comment_title );
+ }
+
+ /**
+ * add extra information to wikitext before storage
+ *
+ * @param string $wikitext the wikitext to which to add
+ * @param ?string $comment_title string title of comment
+ * @return string annotated wikitext
+ */
+ private function addAnnotations( string $wikitext, ?string $comment_title ) : string {
+ if ( $comment_title !== null ) {
+ $wikitext .= <<<EOT
+{{DISPLAYTITLE:
+$comment_title
+}}
+EOT;
+ }
+ return $wikitext;
+ }
+
+ /**
+ * add extra information to wikitext before storage
+ *
+ * @param string $wikitext the wikitext to which to add
+ * @param ?string $comment_title
+ * @return string wikitext without annotations
+ */
+ private function removeAnnotations( string $wikitext, ?string $comment_title ) : string {
+ if ( $comment_title !== null ) {
+ $strip = <<<EOT
+{{DISPLAYTITLE:
+$comment_title
+}}
+EOT;
+ $wikitext = str_replace( $strip, '', $wikitext );
+ }
+ return $wikitext;
+ }
+}