summaryrefslogtreecommitdiff
path: root/www/wiki/includes/revisiondelete
diff options
context:
space:
mode:
authorYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
committerYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
commitfc7369835258467bf97eb64f184b93691f9a9fd5 (patch)
treedaabd60089d2dd76d9f5fb416b005fbe159c799d /www/wiki/includes/revisiondelete
first commit
Diffstat (limited to 'www/wiki/includes/revisiondelete')
-rw-r--r--www/wiki/includes/revisiondelete/RevDelArchiveItem.php109
-rw-r--r--www/wiki/includes/revisiondelete/RevDelArchiveList.php86
-rw-r--r--www/wiki/includes/revisiondelete/RevDelArchivedFileItem.php146
-rw-r--r--www/wiki/includes/revisiondelete/RevDelArchivedFileList.php60
-rw-r--r--www/wiki/includes/revisiondelete/RevDelArchivedRevisionItem.php53
-rw-r--r--www/wiki/includes/revisiondelete/RevDelFileItem.php250
-rw-r--r--www/wiki/includes/revisiondelete/RevDelFileList.php134
-rw-r--r--www/wiki/includes/revisiondelete/RevDelItem.php82
-rw-r--r--www/wiki/includes/revisiondelete/RevDelList.php440
-rw-r--r--www/wiki/includes/revisiondelete/RevDelLogItem.php150
-rw-r--r--www/wiki/includes/revisiondelete/RevDelLogList.php108
-rw-r--r--www/wiki/includes/revisiondelete/RevDelRevisionItem.php213
-rw-r--r--www/wiki/includes/revisiondelete/RevDelRevisionList.php184
-rw-r--r--www/wiki/includes/revisiondelete/RevisionDeleteUser.php208
-rw-r--r--www/wiki/includes/revisiondelete/RevisionDeleter.php251
15 files changed, 2474 insertions, 0 deletions
diff --git a/www/wiki/includes/revisiondelete/RevDelArchiveItem.php b/www/wiki/includes/revisiondelete/RevDelArchiveItem.php
new file mode 100644
index 00000000..679acc64
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelArchiveItem.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+/**
+ * Item class for a archive table row
+ */
+class RevDelArchiveItem extends RevDelRevisionItem {
+ public function __construct( $list, $row ) {
+ RevDelItem::__construct( $list, $row );
+ $this->revision = Revision::newFromArchiveRow( $row,
+ [ 'page' => $this->list->title->getArticleID() ] );
+ }
+
+ public function getIdField() {
+ return 'ar_timestamp';
+ }
+
+ public function getTimestampField() {
+ return 'ar_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'ar_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'ar_user_text';
+ }
+
+ public function getAuthorActorField() {
+ return 'ar_actor';
+ }
+
+ public function getId() {
+ # Convert DB timestamp to MW timestamp
+ return $this->revision->getTimestamp();
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'archive',
+ [ 'ar_deleted' => $bits ],
+ [
+ 'ar_namespace' => $this->list->title->getNamespace(),
+ 'ar_title' => $this->list->title->getDBkey(),
+ // use timestamp for index
+ 'ar_timestamp' => $this->row->ar_timestamp,
+ 'ar_rev_id' => $this->row->ar_rev_id,
+ 'ar_deleted' => $this->getBits()
+ ],
+ __METHOD__ );
+
+ return (bool)$dbw->affectedRows();
+ }
+
+ protected function getRevisionLink() {
+ $date = $this->list->getLanguage()->userTimeAndDate(
+ $this->revision->getTimestamp(), $this->list->getUser() );
+
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return htmlspecialchars( $date );
+ }
+
+ return $this->getLinkRenderer()->makeLink(
+ SpecialPage::getTitleFor( 'Undelete' ),
+ $date,
+ [],
+ [
+ 'target' => $this->list->title->getPrefixedText(),
+ 'timestamp' => $this->revision->getTimestamp()
+ ]
+ );
+ }
+
+ protected function getDiffLink() {
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $this->list->msg( 'diff' )->escaped();
+ }
+
+ return $this->getLinkRenderer()->makeLink(
+ SpecialPage::getTitleFor( 'Undelete' ),
+ $this->list->msg( 'diff' )->text(),
+ [],
+ [
+ 'target' => $this->list->title->getPrefixedText(),
+ 'diff' => 'prev',
+ 'timestamp' => $this->revision->getTimestamp()
+ ]
+ );
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelArchiveList.php b/www/wiki/includes/revisiondelete/RevDelArchiveList.php
new file mode 100644
index 00000000..4f66cdae
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelArchiveList.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * List for archive table items, i.e. revisions deleted via action=delete
+ */
+class RevDelArchiveList extends RevDelRevisionList {
+ public function getType() {
+ return 'archive';
+ }
+
+ public static function getRelationType() {
+ return 'ar_timestamp';
+ }
+
+ /**
+ * @param IDatabase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $timestamps = [];
+ foreach ( $this->ids as $id ) {
+ $timestamps[] = $db->timestamp( $id );
+ }
+
+ $arQuery = Revision::getArchiveQueryInfo();
+ $tables = $arQuery['tables'];
+ $fields = $arQuery['fields'];
+ $conds = [
+ 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey(),
+ 'ar_timestamp' => $timestamps,
+ ];
+ $join_conds = $arQuery['joins'];
+ $options = [ 'ORDER BY' => 'ar_timestamp DESC' ];
+
+ ChangeTags::modifyDisplayQuery(
+ $tables,
+ $fields,
+ $conds,
+ $join_conds,
+ $options,
+ ''
+ );
+
+ return $db->select( $tables,
+ $fields,
+ $conds,
+ __METHOD__,
+ $options,
+ $join_conds
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDelArchiveItem( $this, $row );
+ }
+
+ public function doPreCommitUpdates() {
+ return Status::newGood();
+ }
+
+ public function doPostCommitUpdates( array $visibilityChangeMap ) {
+ return Status::newGood();
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelArchivedFileItem.php b/www/wiki/includes/revisiondelete/RevDelArchivedFileItem.php
new file mode 100644
index 00000000..d36fac92
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelArchivedFileItem.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+/**
+ * Item class for a filearchive table row
+ */
+class RevDelArchivedFileItem extends RevDelFileItem {
+ /** @var RevDelArchivedFileList $list */
+ /** @var ArchivedFile $file */
+ /** @var LocalFile */
+ protected $lockFile;
+
+ public function __construct( $list, $row ) {
+ RevDelItem::__construct( $list, $row );
+ $this->file = ArchivedFile::newFromRow( $row );
+ $this->lockFile = RepoGroup::singleton()->getLocalRepo()->newFile( $row->fa_name );
+ }
+
+ public function getIdField() {
+ return 'fa_id';
+ }
+
+ public function getTimestampField() {
+ return 'fa_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'fa_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'fa_user_text';
+ }
+
+ public function getAuthorActorField() {
+ return 'fa_actor';
+ }
+
+ public function getId() {
+ return $this->row->fa_id;
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'filearchive',
+ [ 'fa_deleted' => $bits ],
+ [
+ 'fa_id' => $this->row->fa_id,
+ 'fa_deleted' => $this->getBits(),
+ ],
+ __METHOD__
+ );
+
+ return (bool)$dbw->affectedRows();
+ }
+
+ protected function getLink() {
+ $date = $this->list->getLanguage()->userTimeAndDate(
+ $this->file->getTimestamp(), $this->list->getUser() );
+
+ # Hidden files...
+ if ( !$this->canViewContent() ) {
+ $link = htmlspecialchars( $date );
+ } else {
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $key = $this->file->getKey();
+ $link = $this->getLinkRenderer()->makeLink( $undelete, $date, [],
+ [
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $key,
+ 'token' => $this->list->getUser()->getEditToken( $key )
+ ]
+ );
+ }
+ if ( $this->isDeleted() ) {
+ $link = '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ return $link;
+ }
+
+ public function getApiData( ApiResult $result ) {
+ $file = $this->file;
+ $user = $this->list->getUser();
+ $ret = [
+ 'title' => $this->list->title->getPrefixedText(),
+ 'timestamp' => wfTimestamp( TS_ISO_8601, $file->getTimestamp() ),
+ 'width' => $file->getWidth(),
+ 'height' => $file->getHeight(),
+ 'size' => $file->getSize(),
+ 'userhidden' => (bool)$file->isDeleted( Revision::DELETED_USER ),
+ 'commenthidden' => (bool)$file->isDeleted( Revision::DELETED_COMMENT ),
+ 'contenthidden' => (bool)$this->isDeleted(),
+ ];
+ if ( $this->canViewContent() ) {
+ $ret += [
+ 'url' => SpecialPage::getTitleFor( 'Revisiondelete' )->getLinkURL(
+ [
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $file->getKey(),
+ 'token' => $user->getEditToken( $file->getKey() )
+ ]
+ ),
+ ];
+ }
+ if ( $file->userCan( Revision::DELETED_USER, $user ) ) {
+ $ret += [
+ 'userid' => $file->getUser( 'id' ),
+ 'user' => $file->getUser( 'text' ),
+ ];
+ }
+ if ( $file->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $ret += [
+ 'comment' => $file->getRawDescription(),
+ ];
+ }
+
+ return $ret;
+ }
+
+ public function lock() {
+ return $this->lockFile->acquireFileLock();
+ }
+
+ public function unlock() {
+ return $this->lockFile->releaseFileLock();
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelArchivedFileList.php b/www/wiki/includes/revisiondelete/RevDelArchivedFileList.php
new file mode 100644
index 00000000..1ed87263
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelArchivedFileList.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * List for filearchive table items
+ */
+class RevDelArchivedFileList extends RevDelFileList {
+ public function getType() {
+ return 'filearchive';
+ }
+
+ public static function getRelationType() {
+ return 'fa_id';
+ }
+
+ /**
+ * @param IDatabase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+
+ $fileQuery = ArchivedFile::getQueryInfo();
+ return $db->select(
+ $fileQuery['tables'],
+ $fileQuery['fields'],
+ [
+ 'fa_name' => $this->title->getDBkey(),
+ 'fa_id' => $ids
+ ],
+ __METHOD__,
+ [ 'ORDER BY' => 'fa_id DESC' ],
+ $fileQuery['joins']
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDelArchivedFileItem( $this, $row );
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelArchivedRevisionItem.php b/www/wiki/includes/revisiondelete/RevDelArchivedRevisionItem.php
new file mode 100644
index 00000000..d839fcfc
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelArchivedRevisionItem.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+/**
+ * Item class for a archive table row by ar_rev_id -- actually
+ * used via RevDelRevisionList.
+ */
+class RevDelArchivedRevisionItem extends RevDelArchiveItem {
+ public function __construct( $list, $row ) {
+ RevDelItem::__construct( $list, $row );
+
+ $this->revision = Revision::newFromArchiveRow( $row,
+ [ 'page' => $this->list->title->getArticleID() ] );
+ }
+
+ public function getIdField() {
+ return 'ar_rev_id';
+ }
+
+ public function getId() {
+ return $this->revision->getId();
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'archive',
+ [ 'ar_deleted' => $bits ],
+ [ 'ar_rev_id' => $this->row->ar_rev_id,
+ 'ar_deleted' => $this->getBits()
+ ],
+ __METHOD__ );
+
+ return (bool)$dbw->affectedRows();
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelFileItem.php b/www/wiki/includes/revisiondelete/RevDelFileItem.php
new file mode 100644
index 00000000..0ca84d7f
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelFileItem.php
@@ -0,0 +1,250 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+/**
+ * Item class for an oldimage table row
+ */
+class RevDelFileItem extends RevDelItem {
+ /** @var RevDelFileList */
+ protected $list;
+ /** @var OldLocalFile */
+ protected $file;
+
+ public function __construct( $list, $row ) {
+ parent::__construct( $list, $row );
+ $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
+ }
+
+ public function getIdField() {
+ return 'oi_archive_name';
+ }
+
+ public function getTimestampField() {
+ return 'oi_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'oi_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'oi_user_text';
+ }
+
+ public function getAuthorActorField() {
+ return 'oi_actor';
+ }
+
+ public function getId() {
+ $parts = explode( '!', $this->row->oi_archive_name );
+
+ return $parts[0];
+ }
+
+ public function canView() {
+ return $this->file->userCan( File::DELETED_RESTRICTED, $this->list->getUser() );
+ }
+
+ public function canViewContent() {
+ return $this->file->userCan( File::DELETED_FILE, $this->list->getUser() );
+ }
+
+ public function getBits() {
+ return $this->file->getVisibility();
+ }
+
+ public function setBits( $bits ) {
+ # Queue the file op
+ # @todo FIXME: Move to LocalFile.php
+ if ( $this->isDeleted() ) {
+ if ( $bits & File::DELETED_FILE ) {
+ # Still deleted
+ } else {
+ # Newly undeleted
+ $key = $this->file->getStorageKey();
+ $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
+ $this->list->storeBatch[] = [
+ $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
+ 'public',
+ $this->file->getRel()
+ ];
+ $this->list->cleanupBatch[] = $key;
+ }
+ } elseif ( $bits & File::DELETED_FILE ) {
+ # Newly deleted
+ $key = $this->file->getStorageKey();
+ $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
+ $this->list->deleteBatch[] = [ $this->file->getRel(), $dstRel ];
+ }
+
+ # Do the database operations
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'oldimage',
+ [ 'oi_deleted' => $bits ],
+ [
+ 'oi_name' => $this->row->oi_name,
+ 'oi_timestamp' => $this->row->oi_timestamp,
+ 'oi_deleted' => $this->getBits()
+ ],
+ __METHOD__
+ );
+
+ return (bool)$dbw->affectedRows();
+ }
+
+ public function isDeleted() {
+ return $this->file->isDeleted( File::DELETED_FILE );
+ }
+
+ /**
+ * Get the link to the file.
+ * Overridden by RevDelArchivedFileItem.
+ * @return string
+ */
+ protected function getLink() {
+ $date = $this->list->getLanguage()->userTimeAndDate(
+ $this->file->getTimestamp(), $this->list->getUser() );
+
+ if ( !$this->isDeleted() ) {
+ # Regular files...
+ return Html::element( 'a', [ 'href' => $this->file->getUrl() ], $date );
+ }
+
+ # Hidden files...
+ if ( !$this->canViewContent() ) {
+ $link = htmlspecialchars( $date );
+ } else {
+ $link = $this->getLinkRenderer()->makeLink(
+ SpecialPage::getTitleFor( 'Revisiondelete' ),
+ $date,
+ [],
+ [
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $this->file->getArchiveName(),
+ 'token' => $this->list->getUser()->getEditToken(
+ $this->file->getArchiveName() )
+ ]
+ );
+ }
+
+ return '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ /**
+ * Generate a user tool link cluster if the current user is allowed to view it
+ * @return string HTML
+ */
+ protected function getUserTools() {
+ if ( $this->file->userCan( Revision::DELETED_USER, $this->list->getUser() ) ) {
+ $uid = $this->file->getUser( 'id' );
+ $name = $this->file->getUser( 'text' );
+ $link = Linker::userLink( $uid, $name ) . Linker::userToolLinks( $uid, $name );
+ } else {
+ $link = $this->list->msg( 'rev-deleted-user' )->escaped();
+ }
+ if ( $this->file->isDeleted( Revision::DELETED_USER ) ) {
+ return '<span class="history-deleted">' . $link . '</span>';
+ }
+
+ return $link;
+ }
+
+ /**
+ * Wrap and format the file's comment block, if the current
+ * user is allowed to view it.
+ *
+ * @return string HTML
+ */
+ protected function getComment() {
+ if ( $this->file->userCan( File::DELETED_COMMENT, $this->list->getUser() ) ) {
+ $block = Linker::commentBlock( $this->file->getDescription() );
+ } else {
+ $block = ' ' . $this->list->msg( 'rev-deleted-comment' )->escaped();
+ }
+ if ( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
+ return "<span class=\"history-deleted\">$block</span>";
+ }
+
+ return $block;
+ }
+
+ public function getHTML() {
+ $data =
+ $this->list->msg( 'widthheight' )->numParams(
+ $this->file->getWidth(), $this->file->getHeight() )->text() .
+ ' (' . $this->list->msg( 'nbytes' )->numParams( $this->file->getSize() )->text() . ')';
+
+ return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
+ $data . ' ' . $this->getComment() . '</li>';
+ }
+
+ public function getApiData( ApiResult $result ) {
+ $file = $this->file;
+ $user = $this->list->getUser();
+ $ret = [
+ 'title' => $this->list->title->getPrefixedText(),
+ 'archivename' => $file->getArchiveName(),
+ 'timestamp' => wfTimestamp( TS_ISO_8601, $file->getTimestamp() ),
+ 'width' => $file->getWidth(),
+ 'height' => $file->getHeight(),
+ 'size' => $file->getSize(),
+ 'userhidden' => (bool)$file->isDeleted( Revision::DELETED_USER ),
+ 'commenthidden' => (bool)$file->isDeleted( Revision::DELETED_COMMENT ),
+ 'contenthidden' => (bool)$this->isDeleted(),
+ ];
+ if ( !$this->isDeleted() ) {
+ $ret += [
+ 'url' => $file->getUrl(),
+ ];
+ } elseif ( $this->canViewContent() ) {
+ $ret += [
+ 'url' => SpecialPage::getTitleFor( 'Revisiondelete' )->getLinkURL(
+ [
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $file->getArchiveName(),
+ 'token' => $user->getEditToken( $file->getArchiveName() )
+ ]
+ ),
+ ];
+ }
+ if ( $file->userCan( Revision::DELETED_USER, $user ) ) {
+ $ret += [
+ 'userid' => $file->user,
+ 'user' => $file->user_text,
+ ];
+ }
+ if ( $file->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $ret += [
+ 'comment' => $file->description,
+ ];
+ }
+
+ return $ret;
+ }
+
+ public function lock() {
+ return $this->file->acquireFileLock();
+ }
+
+ public function unlock() {
+ return $this->file->releaseFileLock();
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelFileList.php b/www/wiki/includes/revisiondelete/RevDelFileList.php
new file mode 100644
index 00000000..6a6b86c0
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelFileList.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * List for oldimage table items
+ */
+class RevDelFileList extends RevDelList {
+ /** @var array */
+ public $storeBatch;
+
+ /** @var array */
+ public $deleteBatch;
+
+ /** @var array */
+ public $cleanupBatch;
+
+ public function getType() {
+ return 'oldimage';
+ }
+
+ public static function getRelationType() {
+ return 'oi_archive_name';
+ }
+
+ public static function getRestriction() {
+ return 'deleterevision';
+ }
+
+ public static function getRevdelConstant() {
+ return File::DELETED_FILE;
+ }
+
+ /**
+ * @param IDatabase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $archiveNames = [];
+ foreach ( $this->ids as $timestamp ) {
+ $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
+ }
+
+ $oiQuery = OldLocalFile::getQueryInfo();
+ return $db->select(
+ $oiQuery['tables'],
+ $oiQuery['fields'],
+ [
+ 'oi_name' => $this->title->getDBkey(),
+ 'oi_archive_name' => $archiveNames
+ ],
+ __METHOD__,
+ [ 'ORDER BY' => 'oi_timestamp DESC' ],
+ $oiQuery['joins']
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDelFileItem( $this, $row );
+ }
+
+ public function clearFileOps() {
+ $this->deleteBatch = [];
+ $this->storeBatch = [];
+ $this->cleanupBatch = [];
+ }
+
+ public function doPreCommitUpdates() {
+ $status = Status::newGood();
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ if ( $this->storeBatch ) {
+ $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ if ( $this->deleteBatch ) {
+ $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
+ }
+ if ( !$status->isOK() ) {
+ // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
+ // modified (but destined for rollback) causes data loss
+ return $status;
+ }
+ if ( $this->cleanupBatch ) {
+ $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
+ }
+
+ return $status;
+ }
+
+ public function doPostCommitUpdates( array $visibilityChangeMap ) {
+ $file = wfLocalFile( $this->title );
+ $file->purgeCache();
+ $file->purgeDescription();
+
+ // Purge full images from cache
+ $purgeUrls = [];
+ foreach ( $this->ids as $timestamp ) {
+ $archiveName = $timestamp . '!' . $this->title->getDBkey();
+ $file->purgeOldThumbnails( $archiveName );
+ $purgeUrls[] = $file->getArchiveUrl( $archiveName );
+ }
+ DeferredUpdates::addUpdate(
+ new CdnCacheUpdate( $purgeUrls ),
+ DeferredUpdates::PRESEND
+ );
+
+ return Status::newGood();
+ }
+
+ public function getSuppressBit() {
+ return File::DELETED_RESTRICTED;
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelItem.php b/www/wiki/includes/revisiondelete/RevDelItem.php
new file mode 100644
index 00000000..47620852
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelItem.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+/**
+ * Abstract base class for deletable items
+ */
+abstract class RevDelItem extends RevisionItemBase {
+ /**
+ * Returns true if the item is "current", and the operation to set the given
+ * bits can't be executed for that reason
+ * STUB
+ * @param int $newBits
+ * @return bool
+ */
+ public function isHideCurrentOp( $newBits ) {
+ return false;
+ }
+
+ /**
+ * Get the current deletion bitfield value
+ *
+ * @return int
+ */
+ abstract public function getBits();
+
+ /**
+ * Set the visibility of the item. This should do any necessary DB queries.
+ *
+ * The DB update query should have a condition which forces it to only update
+ * if the value in the DB matches the value fetched earlier with the SELECT.
+ * If the update fails because it did not match, the function should return
+ * false. This prevents concurrency problems.
+ *
+ * @param int $newBits
+ * @return bool Success
+ */
+ abstract public function setBits( $newBits );
+
+ /**
+ * Get the return information about the revision for the API
+ * @since 1.23
+ * @param ApiResult $result
+ * @return array Data for the API result
+ */
+ abstract public function getApiData( ApiResult $result );
+
+ /**
+ * Lock the item against changes outside of the DB
+ * @return Status
+ * @since 1.28
+ */
+ public function lock() {
+ return Status::newGood();
+ }
+
+ /**
+ * Unlock the item against changes outside of the DB
+ * @return Status
+ * @since 1.28
+ */
+ public function unlock() {
+ return Status::newGood();
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelList.php b/www/wiki/includes/revisiondelete/RevDelList.php
new file mode 100644
index 00000000..89025bc6
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelList.php
@@ -0,0 +1,440 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Abstract base class for a list of deletable items. The list class
+ * needs to be able to make a query from a set of identifiers to pull
+ * relevant rows, to return RevDelItem subclasses wrapping them, and
+ * to wrap bulk update operations.
+ */
+abstract class RevDelList extends RevisionListBase {
+ function __construct( IContextSource $context, Title $title, array $ids ) {
+ parent::__construct( $context, $title );
+ $this->ids = $ids;
+ }
+
+ /**
+ * Get the DB field name associated with the ID list.
+ * This used to populate the log_search table for finding log entries.
+ * Override this function.
+ * @return string|null
+ */
+ public static function getRelationType() {
+ return null;
+ }
+
+ /**
+ * Get the user right required for this list type
+ * Override this function.
+ * @since 1.22
+ * @return string|null
+ */
+ public static function getRestriction() {
+ return null;
+ }
+
+ /**
+ * Get the revision deletion constant for this list type
+ * Override this function.
+ * @since 1.22
+ * @return int|null
+ */
+ public static function getRevdelConstant() {
+ return null;
+ }
+
+ /**
+ * Suggest a target for the revision deletion
+ * Optionally override this function.
+ * @since 1.22
+ * @param Title|null $target User-supplied target
+ * @param array $ids
+ * @return Title|null
+ */
+ public static function suggestTarget( $target, array $ids ) {
+ return $target;
+ }
+
+ /**
+ * Indicate whether any item in this list is suppressed
+ * @since 1.25
+ * @return bool
+ */
+ public function areAnySuppressed() {
+ $bit = $this->getSuppressBit();
+
+ /** @var RevDelItem $item */
+ foreach ( $this as $item ) {
+ if ( $item->getBits() & $bit ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Set the visibility for the revisions in this list. Logging and
+ * transactions are done here.
+ *
+ * @param array $params Associative array of parameters. Members are:
+ * value: ExtractBitParams() bitfield array
+ * comment: The log comment
+ * perItemStatus: Set if you want per-item status reports
+ * tags: The array of change tags to apply to the log entry
+ * @return Status
+ * @since 1.23 Added 'perItemStatus' param
+ */
+ public function setVisibility( array $params ) {
+ global $wgActorTableSchemaMigrationStage;
+
+ $status = Status::newGood();
+
+ $bitPars = $params['value'];
+ $comment = $params['comment'];
+ $perItemStatus = isset( $params['perItemStatus'] ) ? $params['perItemStatus'] : false;
+
+ // CAS-style checks are done on the _deleted fields so the select
+ // does not need to use FOR UPDATE nor be in the atomic section
+ $dbw = wfGetDB( DB_MASTER );
+ $this->res = $this->doQuery( $dbw );
+
+ $status->merge( $this->acquireItemLocks() );
+ if ( !$status->isGood() ) {
+ return $status;
+ }
+
+ $dbw->startAtomic( __METHOD__ );
+ $dbw->onTransactionResolution(
+ function () {
+ // Release locks on commit or error
+ $this->releaseItemLocks();
+ },
+ __METHOD__
+ );
+
+ $missing = array_flip( $this->ids );
+ $this->clearFileOps();
+ $idsForLog = [];
+ $authorIds = $authorIPs = $authorActors = [];
+
+ if ( $perItemStatus ) {
+ $status->itemStatuses = [];
+ }
+
+ // For multi-item deletions, set the old/new bitfields in log_params such that "hid X"
+ // shows in logs if field X was hidden from ANY item and likewise for "unhid Y". Note the
+ // form does not let the same field get hidden and unhidden in different items at once.
+ $virtualOldBits = 0;
+ $virtualNewBits = 0;
+ $logType = 'delete';
+
+ // Will be filled with id => [old, new bits] information and
+ // passed to doPostCommitUpdates().
+ $visibilityChangeMap = [];
+
+ /** @var RevDelItem $item */
+ foreach ( $this as $item ) {
+ unset( $missing[$item->getId()] );
+
+ if ( $perItemStatus ) {
+ $itemStatus = Status::newGood();
+ $status->itemStatuses[$item->getId()] = $itemStatus;
+ } else {
+ $itemStatus = $status;
+ }
+
+ $oldBits = $item->getBits();
+ // Build the actual new rev_deleted bitfield
+ $newBits = RevisionDeleter::extractBitfield( $bitPars, $oldBits );
+
+ if ( $oldBits == $newBits ) {
+ $itemStatus->warning(
+ 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ } elseif ( $oldBits == 0 && $newBits != 0 ) {
+ $opType = 'hide';
+ } elseif ( $oldBits != 0 && $newBits == 0 ) {
+ $opType = 'show';
+ } else {
+ $opType = 'modify';
+ }
+
+ if ( $item->isHideCurrentOp( $newBits ) ) {
+ // Cannot hide current version text
+ $itemStatus->error(
+ 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ } elseif ( !$item->canView() ) {
+ // Cannot access this revision
+ $msg = ( $opType == 'show' ) ?
+ 'revdelete-show-no-access' : 'revdelete-modify-no-access';
+ $itemStatus->error( $msg, $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ // Cannot just "hide from Sysops" without hiding any fields
+ } elseif ( $newBits == Revision::DELETED_RESTRICTED ) {
+ $itemStatus->warning(
+ 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ }
+
+ // Update the revision
+ $ok = $item->setBits( $newBits );
+
+ if ( $ok ) {
+ $idsForLog[] = $item->getId();
+ // If any item field was suppressed or unsupressed
+ if ( ( $oldBits | $newBits ) & $this->getSuppressBit() ) {
+ $logType = 'suppress';
+ }
+ // Track which fields where (un)hidden for each item
+ $addedBits = ( $oldBits ^ $newBits ) & $newBits;
+ $removedBits = ( $oldBits ^ $newBits ) & $oldBits;
+ $virtualNewBits |= $addedBits;
+ $virtualOldBits |= $removedBits;
+
+ $status->successCount++;
+ if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ if ( $item->getAuthorId() > 0 ) {
+ $authorIds[] = $item->getAuthorId();
+ } elseif ( IP::isIPAddress( $item->getAuthorName() ) ) {
+ $authorIPs[] = $item->getAuthorName();
+ }
+ }
+ if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ $authorActors[] = $item->getAuthorActor();
+ }
+
+ // Save the old and new bits in $visibilityChangeMap for
+ // later use.
+ $visibilityChangeMap[$item->getId()] = [
+ 'oldBits' => $oldBits,
+ 'newBits' => $newBits,
+ ];
+ } else {
+ $itemStatus->error(
+ 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ }
+ }
+
+ // Handle missing revisions
+ foreach ( $missing as $id => $unused ) {
+ if ( $perItemStatus ) {
+ $status->itemStatuses[$id] = Status::newFatal( 'revdelete-modify-missing', $id );
+ } else {
+ $status->error( 'revdelete-modify-missing', $id );
+ }
+ $status->failCount++;
+ }
+
+ if ( $status->successCount == 0 ) {
+ $dbw->endAtomic( __METHOD__ );
+ return $status;
+ }
+
+ // Save success count
+ $successCount = $status->successCount;
+
+ // Move files, if there are any
+ $status->merge( $this->doPreCommitUpdates() );
+ if ( !$status->isOK() ) {
+ // Fatal error, such as no configured archive directory or I/O failures
+ $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $lbFactory->rollbackMasterChanges( __METHOD__ );
+ return $status;
+ }
+
+ // Log it
+ $authorFields = [];
+ if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+ $authorFields['authorIds'] = $authorIds;
+ $authorFields['authorIPs'] = $authorIPs;
+ }
+ if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+ $authorFields['authorActors'] = $authorActors;
+ }
+ $this->updateLog(
+ $logType,
+ [
+ 'title' => $this->title,
+ 'count' => $successCount,
+ 'newBits' => $virtualNewBits,
+ 'oldBits' => $virtualOldBits,
+ 'comment' => $comment,
+ 'ids' => $idsForLog,
+ 'tags' => isset( $params['tags'] ) ? $params['tags'] : [],
+ ] + $authorFields
+ );
+
+ // Clear caches after commit
+ DeferredUpdates::addCallableUpdate(
+ function () use ( $visibilityChangeMap ) {
+ $this->doPostCommitUpdates( $visibilityChangeMap );
+ },
+ DeferredUpdates::PRESEND,
+ $dbw
+ );
+
+ $dbw->endAtomic( __METHOD__ );
+
+ return $status;
+ }
+
+ final protected function acquireItemLocks() {
+ $status = Status::newGood();
+ /** @var RevDelItem $item */
+ foreach ( $this as $item ) {
+ $status->merge( $item->lock() );
+ }
+
+ return $status;
+ }
+
+ final protected function releaseItemLocks() {
+ $status = Status::newGood();
+ /** @var RevDelItem $item */
+ foreach ( $this as $item ) {
+ $status->merge( $item->unlock() );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Reload the list data from the master DB. This can be done after setVisibility()
+ * to allow $item->getHTML() to show the new data.
+ */
+ function reloadFromMaster() {
+ $dbw = wfGetDB( DB_MASTER );
+ $this->res = $this->doQuery( $dbw );
+ }
+
+ /**
+ * Record a log entry on the action
+ * @param string $logType One of (delete,suppress)
+ * @param array $params Associative array of parameters:
+ * newBits: The new value of the *_deleted bitfield
+ * oldBits: The old value of the *_deleted bitfield.
+ * title: The target title
+ * ids: The ID list
+ * comment: The log comment
+ * authorIds: The array of the user IDs of the offenders
+ * authorIPs: The array of the IP/anon user offenders
+ * authorActors: The array of the actor IDs of the offenders
+ * tags: The array of change tags to apply to the log entry
+ * @throws MWException
+ */
+ private function updateLog( $logType, $params ) {
+ // Get the URL param's corresponding DB field
+ $field = RevisionDeleter::getRelationType( $this->getType() );
+ if ( !$field ) {
+ throw new MWException( "Bad log URL param type!" );
+ }
+ // Add params for affected page and ids
+ $logParams = $this->getLogParams( $params );
+ // Actually add the deletion log entry
+ $logEntry = new ManualLogEntry( $logType, $this->getLogAction() );
+ $logEntry->setTarget( $params['title'] );
+ $logEntry->setComment( $params['comment'] );
+ $logEntry->setParameters( $logParams );
+ $logEntry->setPerformer( $this->getUser() );
+ // Allow for easy searching of deletion log items for revision/log items
+ $relations = [
+ $field => $params['ids'],
+ ];
+ if ( isset( $params['authorIds'] ) ) {
+ $relations += [
+ 'target_author_id' => $params['authorIds'],
+ 'target_author_ip' => $params['authorIPs'],
+ ];
+ }
+ if ( isset( $params['authorActors'] ) ) {
+ $relations += [
+ 'target_author_actor' => $params['authorActors'],
+ ];
+ }
+ $logEntry->setRelations( $relations );
+ // Apply change tags to the log entry
+ $logEntry->setTags( $params['tags'] );
+ $logId = $logEntry->insert();
+ $logEntry->publish( $logId );
+ }
+
+ /**
+ * Get the log action for this list type
+ * @return string
+ */
+ public function getLogAction() {
+ return 'revision';
+ }
+
+ /**
+ * Get log parameter array.
+ * @param array $params Associative array of log parameters, same as updateLog()
+ * @return array
+ */
+ public function getLogParams( $params ) {
+ return [
+ '4::type' => $this->getType(),
+ '5::ids' => $params['ids'],
+ '6::ofield' => $params['oldBits'],
+ '7::nfield' => $params['newBits'],
+ ];
+ }
+
+ /**
+ * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
+ * STUB
+ */
+ public function clearFileOps() {
+ }
+
+ /**
+ * A hook for setVisibility(): do batch updates pre-commit.
+ * STUB
+ * @return Status
+ */
+ public function doPreCommitUpdates() {
+ return Status::newGood();
+ }
+
+ /**
+ * A hook for setVisibility(): do any necessary updates post-commit.
+ * STUB
+ * @param array $visibilityChangeMap [id => ['oldBits' => $oldBits, 'newBits' => $newBits], ... ]
+ * @return Status
+ */
+ public function doPostCommitUpdates( array $visibilityChangeMap ) {
+ return Status::newGood();
+ }
+
+ /**
+ * Get the integer value of the flag used for suppression
+ */
+ abstract public function getSuppressBit();
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelLogItem.php b/www/wiki/includes/revisiondelete/RevDelLogItem.php
new file mode 100644
index 00000000..36198cd2
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelLogItem.php
@@ -0,0 +1,150 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+/**
+ * Item class for a logging table row
+ */
+class RevDelLogItem extends RevDelItem {
+ public function getIdField() {
+ return 'log_id';
+ }
+
+ public function getTimestampField() {
+ return 'log_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'log_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'log_user_text';
+ }
+
+ public function getAuthorActorField() {
+ return 'log_actor';
+ }
+
+ public function canView() {
+ return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED, $this->list->getUser() );
+ }
+
+ public function canViewContent() {
+ return true; // none
+ }
+
+ public function getBits() {
+ return (int)$this->row->log_deleted;
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $dbw->update( 'logging',
+ [ 'log_deleted' => $bits ],
+ [
+ 'log_id' => $this->row->log_id,
+ 'log_deleted' => $this->getBits() // cas
+ ],
+ __METHOD__
+ );
+
+ if ( !$dbw->affectedRows() ) {
+ // Concurrent fail!
+ return false;
+ }
+
+ $dbw->update( 'recentchanges',
+ [
+ 'rc_deleted' => $bits,
+ 'rc_patrolled' => RecentChange::PRC_PATROLLED
+ ],
+ [
+ 'rc_logid' => $this->row->log_id,
+ 'rc_timestamp' => $this->row->log_timestamp // index
+ ],
+ __METHOD__
+ );
+
+ return true;
+ }
+
+ public function getHTML() {
+ $date = htmlspecialchars( $this->list->getLanguage()->userTimeAndDate(
+ $this->row->log_timestamp, $this->list->getUser() ) );
+ $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
+ $formatter = LogFormatter::newFromRow( $this->row );
+ $formatter->setContext( $this->list->getContext() );
+ $formatter->setAudience( LogFormatter::FOR_THIS_USER );
+
+ // Log link for this page
+ $loglink = $this->getLinkRenderer()->makeLink(
+ SpecialPage::getTitleFor( 'Log' ),
+ $this->list->msg( 'log' )->text(),
+ [],
+ [ 'page' => $title->getPrefixedText() ]
+ );
+ $loglink = $this->list->msg( 'parentheses' )->rawParams( $loglink )->escaped();
+ // User links and action text
+ $action = $formatter->getActionText();
+ // Comment
+ $comment = CommentStore::getStore()->getComment( 'log_comment', $this->row )->text;
+ $comment = $this->list->getLanguage()->getDirMark()
+ . Linker::commentBlock( $comment );
+
+ if ( LogEventsList::isDeleted( $this->row, LogPage::DELETED_COMMENT ) ) {
+ $comment = '<span class="history-deleted">' . $comment . '</span>';
+ }
+
+ return "<li>$loglink $date $action $comment</li>";
+ }
+
+ public function getApiData( ApiResult $result ) {
+ $logEntry = DatabaseLogEntry::newFromRow( $this->row );
+ $user = $this->list->getUser();
+ $ret = [
+ 'id' => $logEntry->getId(),
+ 'type' => $logEntry->getType(),
+ 'action' => $logEntry->getSubtype(),
+ 'userhidden' => (bool)$logEntry->isDeleted( LogPage::DELETED_USER ),
+ 'commenthidden' => (bool)$logEntry->isDeleted( LogPage::DELETED_COMMENT ),
+ 'actionhidden' => (bool)$logEntry->isDeleted( LogPage::DELETED_ACTION ),
+ ];
+
+ if ( LogEventsList::userCan( $this->row, LogPage::DELETED_ACTION, $user ) ) {
+ $ret['params'] = LogFormatter::newFromEntry( $logEntry )->formatParametersForApi();
+ }
+ if ( LogEventsList::userCan( $this->row, LogPage::DELETED_USER, $user ) ) {
+ $ret += [
+ 'userid' => $this->row->log_user,
+ 'user' => $this->row->log_user_text,
+ ];
+ }
+ if ( LogEventsList::userCan( $this->row, LogPage::DELETED_COMMENT, $user ) ) {
+ $ret += [
+ 'comment' => CommentStore::getStore()->getComment( 'log_comment', $this->row )
+ ->text,
+ ];
+ }
+
+ return $ret;
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelLogList.php b/www/wiki/includes/revisiondelete/RevDelLogList.php
new file mode 100644
index 00000000..b26fffd1
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelLogList.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * List for logging table items
+ */
+class RevDelLogList extends RevDelList {
+ public function getType() {
+ return 'logging';
+ }
+
+ public static function getRelationType() {
+ return 'log_id';
+ }
+
+ public static function getRestriction() {
+ return 'deletelogentry';
+ }
+
+ public static function getRevdelConstant() {
+ return LogPage::DELETED_ACTION;
+ }
+
+ public static function suggestTarget( $target, array $ids ) {
+ $result = wfGetDB( DB_REPLICA )->select( 'logging',
+ 'log_type',
+ [ 'log_id' => $ids ],
+ __METHOD__,
+ [ 'DISTINCT' ]
+ );
+ if ( $result->numRows() == 1 ) {
+ // If there's only one type, the target can be set to include it.
+ return SpecialPage::getTitleFor( 'Log', $result->current()->log_type );
+ }
+
+ return SpecialPage::getTitleFor( 'Log' );
+ }
+
+ /**
+ * @param IDatabase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+
+ $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
+ $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
+
+ return $db->select(
+ [ 'logging' ] + $commentQuery['tables'] + $actorQuery['tables'],
+ [
+ 'log_id',
+ 'log_type',
+ 'log_action',
+ 'log_timestamp',
+ 'log_namespace',
+ 'log_title',
+ 'log_page',
+ 'log_params',
+ 'log_deleted'
+ ] + $commentQuery['fields'] + $actorQuery['fields'],
+ [ 'log_id' => $ids ],
+ __METHOD__,
+ [ 'ORDER BY' => 'log_id DESC' ],
+ $commentQuery['joins'] + $actorQuery['joins']
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevDelLogItem( $this, $row );
+ }
+
+ public function getSuppressBit() {
+ return Revision::DELETED_RESTRICTED;
+ }
+
+ public function getLogAction() {
+ return 'event';
+ }
+
+ public function getLogParams( $params ) {
+ return [
+ '4::ids' => $params['ids'],
+ '5::ofield' => $params['oldBits'],
+ '6::nfield' => $params['newBits'],
+ ];
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelRevisionItem.php b/www/wiki/includes/revisiondelete/RevDelRevisionItem.php
new file mode 100644
index 00000000..7b5d130b
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelRevisionItem.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+/**
+ * Item class for a live revision table row
+ */
+class RevDelRevisionItem extends RevDelItem {
+ /** @var Revision */
+ public $revision;
+
+ public function __construct( $list, $row ) {
+ parent::__construct( $list, $row );
+ $this->revision = new Revision( $row );
+ }
+
+ public function getIdField() {
+ return 'rev_id';
+ }
+
+ public function getTimestampField() {
+ return 'rev_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'rev_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'rev_user_text';
+ }
+
+ public function getAuthorActorField() {
+ return 'rev_actor';
+ }
+
+ public function canView() {
+ return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->list->getUser() );
+ }
+
+ public function canViewContent() {
+ return $this->revision->userCan( Revision::DELETED_TEXT, $this->list->getUser() );
+ }
+
+ public function getBits() {
+ return $this->revision->getVisibility();
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ // Update revision table
+ $dbw->update( 'revision',
+ [ 'rev_deleted' => $bits ],
+ [
+ 'rev_id' => $this->revision->getId(),
+ 'rev_page' => $this->revision->getPage(),
+ 'rev_deleted' => $this->getBits() // cas
+ ],
+ __METHOD__
+ );
+ if ( !$dbw->affectedRows() ) {
+ // Concurrent fail!
+ return false;
+ }
+ // Update recentchanges table
+ $dbw->update( 'recentchanges',
+ [
+ 'rc_deleted' => $bits,
+ 'rc_patrolled' => RecentChange::PRC_PATROLLED
+ ],
+ [
+ 'rc_this_oldid' => $this->revision->getId(), // condition
+ // non-unique timestamp index
+ 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
+ ],
+ __METHOD__
+ );
+
+ return true;
+ }
+
+ public function isDeleted() {
+ return $this->revision->isDeleted( Revision::DELETED_TEXT );
+ }
+
+ public function isHideCurrentOp( $newBits ) {
+ return ( $newBits & Revision::DELETED_TEXT )
+ && $this->list->getCurrent() == $this->getId();
+ }
+
+ /**
+ * Get the HTML link to the revision text.
+ * Overridden by RevDelArchiveItem.
+ * @return string
+ */
+ protected function getRevisionLink() {
+ $date = $this->list->getLanguage()->userTimeAndDate(
+ $this->revision->getTimestamp(), $this->list->getUser() );
+
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return htmlspecialchars( $date );
+ }
+
+ return $this->getLinkRenderer()->makeKnownLink(
+ $this->list->title,
+ $date,
+ [],
+ [
+ 'oldid' => $this->revision->getId(),
+ 'unhide' => 1
+ ]
+ );
+ }
+
+ /**
+ * Get the HTML link to the diff.
+ * Overridden by RevDelArchiveItem
+ * @return string
+ */
+ protected function getDiffLink() {
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $this->list->msg( 'diff' )->escaped();
+ } else {
+ return $this->getLinkRenderer()->makeKnownLink(
+ $this->list->title,
+ $this->list->msg( 'diff' )->text(),
+ [],
+ [
+ 'diff' => $this->revision->getId(),
+ 'oldid' => 'prev',
+ 'unhide' => 1
+ ]
+ );
+ }
+ }
+
+ /**
+ * @return string A HTML <li> element representing this revision, showing
+ * change tags and everything
+ */
+ public function getHTML() {
+ $difflink = $this->list->msg( 'parentheses' )
+ ->rawParams( $this->getDiffLink() )->escaped();
+ $revlink = $this->getRevisionLink();
+ $userlink = Linker::revUserLink( $this->revision );
+ $comment = Linker::revComment( $this->revision );
+ if ( $this->isDeleted() ) {
+ $revlink = "<span class=\"history-deleted\">$revlink</span>";
+ }
+ $content = "$difflink $revlink $userlink $comment";
+ $attribs = [];
+ $tags = $this->getTags();
+ if ( $tags ) {
+ list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow(
+ $tags,
+ 'revisiondelete',
+ $this->list->getContext()
+ );
+ $content .= " $tagSummary";
+ $attribs['class'] = implode( ' ', $classes );
+ }
+ return Xml::tags( 'li', $attribs, $content );
+ }
+
+ /**
+ * @return string Comma-separated list of tags
+ */
+ public function getTags() {
+ return $this->row->ts_tags;
+ }
+
+ public function getApiData( ApiResult $result ) {
+ $rev = $this->revision;
+ $user = $this->list->getUser();
+ $ret = [
+ 'id' => $rev->getId(),
+ 'timestamp' => wfTimestamp( TS_ISO_8601, $rev->getTimestamp() ),
+ 'userhidden' => (bool)$rev->isDeleted( Revision::DELETED_USER ),
+ 'commenthidden' => (bool)$rev->isDeleted( Revision::DELETED_COMMENT ),
+ 'texthidden' => (bool)$rev->isDeleted( Revision::DELETED_TEXT ),
+ ];
+ if ( $rev->userCan( Revision::DELETED_USER, $user ) ) {
+ $ret += [
+ 'userid' => $rev->getUser( Revision::FOR_THIS_USER ),
+ 'user' => $rev->getUserText( Revision::FOR_THIS_USER ),
+ ];
+ }
+ if ( $rev->userCan( Revision::DELETED_COMMENT, $user ) ) {
+ $ret += [
+ 'comment' => $rev->getComment( Revision::FOR_THIS_USER ),
+ ];
+ }
+
+ return $ret;
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevDelRevisionList.php b/www/wiki/includes/revisiondelete/RevDelRevisionList.php
new file mode 100644
index 00000000..07362c42
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevDelRevisionList.php
@@ -0,0 +1,184 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+use Wikimedia\Rdbms\FakeResultWrapper;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * List for revision table items
+ *
+ * This will check both the 'revision' table for live revisions and the
+ * 'archive' table for traditionally-deleted revisions that have an
+ * ar_rev_id saved.
+ *
+ * See RevDelRevisionItem and RevDelArchivedRevisionItem for items.
+ */
+class RevDelRevisionList extends RevDelList {
+ /** @var int */
+ public $currentRevId;
+
+ public function getType() {
+ return 'revision';
+ }
+
+ public static function getRelationType() {
+ return 'rev_id';
+ }
+
+ public static function getRestriction() {
+ return 'deleterevision';
+ }
+
+ public static function getRevdelConstant() {
+ return Revision::DELETED_TEXT;
+ }
+
+ public static function suggestTarget( $target, array $ids ) {
+ $rev = Revision::newFromId( $ids[0] );
+ return $rev ? $rev->getTitle() : $target;
+ }
+
+ /**
+ * @param IDatabase $db
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+ $revQuery = Revision::getQueryInfo( [ 'page', 'user' ] );
+ $queryInfo = [
+ 'tables' => $revQuery['tables'],
+ 'fields' => $revQuery['fields'],
+ 'conds' => [
+ 'rev_page' => $this->title->getArticleID(),
+ 'rev_id' => $ids,
+ ],
+ 'options' => [
+ 'ORDER BY' => 'rev_id DESC',
+ 'USE INDEX' => [ 'revision' => 'PRIMARY' ] // workaround for MySQL bug (T104313)
+ ],
+ 'join_conds' => $revQuery['joins'],
+ ];
+ ChangeTags::modifyDisplayQuery(
+ $queryInfo['tables'],
+ $queryInfo['fields'],
+ $queryInfo['conds'],
+ $queryInfo['join_conds'],
+ $queryInfo['options'],
+ ''
+ );
+
+ $live = $db->select(
+ $queryInfo['tables'],
+ $queryInfo['fields'],
+ $queryInfo['conds'],
+ __METHOD__,
+ $queryInfo['options'],
+ $queryInfo['join_conds']
+ );
+ if ( $live->numRows() >= count( $ids ) ) {
+ // All requested revisions are live, keeps things simple!
+ return $live;
+ }
+
+ $arQuery = Revision::getArchiveQueryInfo();
+ $archiveQueryInfo = [
+ 'tables' => $arQuery['tables'],
+ 'fields' => $arQuery['fields'],
+ 'conds' => [
+ 'ar_rev_id' => $ids,
+ ],
+ 'options' => [ 'ORDER BY' => 'ar_rev_id DESC' ],
+ 'join_conds' => $arQuery['joins'],
+ ];
+
+ ChangeTags::modifyDisplayQuery(
+ $archiveQueryInfo['tables'],
+ $archiveQueryInfo['fields'],
+ $archiveQueryInfo['conds'],
+ $archiveQueryInfo['join_conds'],
+ $archiveQueryInfo['options'],
+ ''
+ );
+
+ // Check if any requested revisions are available fully deleted.
+ $archived = $db->select(
+ $archiveQueryInfo['tables'],
+ $archiveQueryInfo['fields'],
+ $archiveQueryInfo['conds'],
+ __METHOD__,
+ $archiveQueryInfo['options'],
+ $archiveQueryInfo['join_conds']
+ );
+
+ if ( $archived->numRows() == 0 ) {
+ return $live;
+ } elseif ( $live->numRows() == 0 ) {
+ return $archived;
+ } else {
+ // Combine the two! Whee
+ $rows = [];
+ foreach ( $live as $row ) {
+ $rows[$row->rev_id] = $row;
+ }
+ foreach ( $archived as $row ) {
+ $rows[$row->ar_rev_id] = $row;
+ }
+ krsort( $rows );
+ return new FakeResultWrapper( array_values( $rows ) );
+ }
+ }
+
+ public function newItem( $row ) {
+ if ( isset( $row->rev_id ) ) {
+ return new RevDelRevisionItem( $this, $row );
+ } elseif ( isset( $row->ar_rev_id ) ) {
+ return new RevDelArchivedRevisionItem( $this, $row );
+ } else {
+ // This shouldn't happen. :)
+ throw new MWException( 'Invalid row type in RevDelRevisionList' );
+ }
+ }
+
+ public function getCurrent() {
+ if ( is_null( $this->currentRevId ) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $this->currentRevId = $dbw->selectField(
+ 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
+ }
+ return $this->currentRevId;
+ }
+
+ public function getSuppressBit() {
+ return Revision::DELETED_RESTRICTED;
+ }
+
+ public function doPreCommitUpdates() {
+ $this->title->invalidateCache();
+ return Status::newGood();
+ }
+
+ public function doPostCommitUpdates( array $visibilityChangeMap ) {
+ $this->title->purgeSquid();
+ // Extensions that require referencing previous revisions may need this
+ Hooks::run( 'ArticleRevisionVisibilitySet', [ $this->title, $this->ids, $visibilityChangeMap ] );
+ return Status::newGood();
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevisionDeleteUser.php b/www/wiki/includes/revisiondelete/RevisionDeleteUser.php
new file mode 100644
index 00000000..6291e8d9
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevisionDeleteUser.php
@@ -0,0 +1,208 @@
+<?php
+/**
+ * Backend functions for suppressing and unsuppressing all references to a given user.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * Backend functions for suppressing and unsuppressing all references to a given user,
+ * used when blocking with HideUser enabled. This was spun out of SpecialBlockip.php
+ * in 1.18; at some point it needs to be rewritten to either use RevisionDelete abstraction,
+ * or at least schema abstraction.
+ *
+ * @ingroup RevisionDelete
+ */
+class RevisionDeleteUser {
+
+ /**
+ * Update *_deleted bitfields in various tables to hide or unhide usernames
+ * @param string $name Username
+ * @param int $userId User id
+ * @param string $op Operator '|' or '&'
+ * @param null|IDatabase $dbw If you happen to have one lying around
+ * @return bool
+ */
+ private static function setUsernameBitfields( $name, $userId, $op, $dbw ) {
+ global $wgActorTableSchemaMigrationStage;
+
+ if ( !$userId || ( $op !== '|' && $op !== '&' ) ) {
+ return false; // sanity check
+ }
+ if ( !$dbw instanceof IDatabase ) {
+ $dbw = wfGetDB( DB_MASTER );
+ }
+
+ # To suppress, we OR the current bitfields with Revision::DELETED_USER
+ # to put a 1 in the username *_deleted bit. To unsuppress we AND the
+ # current bitfields with the inverse of Revision::DELETED_USER. The
+ # username bit is made to 0 (x & 0 = 0), while others are unchanged (x & 1 = x).
+ # The same goes for the sysop-restricted *_deleted bit.
+ $delUser = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ $delAction = LogPage::DELETED_ACTION | Revision::DELETED_RESTRICTED;
+ if ( $op == '&' ) {
+ $delUser = $dbw->bitNot( $delUser );
+ $delAction = $dbw->bitNot( $delAction );
+ }
+
+ # Normalize user name
+ $userTitle = Title::makeTitleSafe( NS_USER, $name );
+ $userDbKey = $userTitle->getDBkey();
+
+ if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+ # Hide name from live edits
+ $dbw->update(
+ 'revision',
+ [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
+ [ 'rev_user' => $userId ],
+ __METHOD__ );
+
+ # Hide name from deleted edits
+ $dbw->update(
+ 'archive',
+ [ self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ],
+ [ 'ar_user_text' => $name ],
+ __METHOD__
+ );
+
+ # Hide name from logs
+ $dbw->update(
+ 'logging',
+ [ self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ],
+ [ 'log_user' => $userId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
+ __METHOD__
+ );
+
+ # Hide name from RC
+ $dbw->update(
+ 'recentchanges',
+ [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ],
+ [ 'rc_user_text' => $name ],
+ __METHOD__
+ );
+
+ # Hide name from live images
+ $dbw->update(
+ 'oldimage',
+ [ self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ],
+ [ 'oi_user_text' => $name ],
+ __METHOD__
+ );
+
+ # Hide name from deleted images
+ $dbw->update(
+ 'filearchive',
+ [ self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ],
+ [ 'fa_user_text' => $name ],
+ __METHOD__
+ );
+ }
+
+ if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+ $actorId = $dbw->selectField( 'actor', 'actor_id', [ 'actor_name' => $name ], __METHOD__ );
+ if ( $actorId ) {
+ # Hide name from live edits
+ $subquery = $dbw->selectSQLText(
+ 'revision_actor_temp', 'revactor_rev', [ 'revactor_actor' => $actorId ], __METHOD__
+ );
+ $dbw->update(
+ 'revision',
+ [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
+ [ "rev_id IN ($subquery)" ],
+ __METHOD__ );
+
+ # Hide name from deleted edits
+ $dbw->update(
+ 'archive',
+ [ self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ],
+ [ 'ar_actor' => $actorId ],
+ __METHOD__
+ );
+
+ # Hide name from logs
+ $dbw->update(
+ 'logging',
+ [ self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ],
+ [ 'log_actor' => $actorId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
+ __METHOD__
+ );
+
+ # Hide name from RC
+ $dbw->update(
+ 'recentchanges',
+ [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ],
+ [ 'rc_actor' => $actorId ],
+ __METHOD__
+ );
+
+ # Hide name from live images
+ $dbw->update(
+ 'oldimage',
+ [ self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ],
+ [ 'oi_actor' => $actorId ],
+ __METHOD__
+ );
+
+ # Hide name from deleted images
+ $dbw->update(
+ 'filearchive',
+ [ self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ],
+ [ 'fa_actor' => $actorId ],
+ __METHOD__
+ );
+ }
+ }
+
+ # Hide log entries pointing to the user page
+ $dbw->update(
+ 'logging',
+ [ self::buildSetBitDeletedField( 'log_deleted', $op, $delAction, $dbw ) ],
+ [ 'log_namespace' => NS_USER, 'log_title' => $userDbKey,
+ 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
+ __METHOD__
+ );
+
+ # Hide RC entries pointing to the user page
+ $dbw->update(
+ 'recentchanges',
+ [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delAction, $dbw ) ],
+ [ 'rc_namespace' => NS_USER, 'rc_title' => $userDbKey, 'rc_logid > 0' ],
+ __METHOD__
+ );
+
+ # Done!
+ return true;
+ }
+
+ private static function buildSetBitDeletedField( $field, $op, $value, $dbw ) {
+ return $field . ' = ' . ( $op === '&'
+ ? $dbw->bitAnd( $field, $value )
+ : $dbw->bitOr( $field, $value ) );
+ }
+
+ public static function suppressUserName( $name, $userId, $dbw = null ) {
+ return self::setUsernameBitfields( $name, $userId, '|', $dbw );
+ }
+
+ public static function unsuppressUserName( $name, $userId, $dbw = null ) {
+ return self::setUsernameBitfields( $name, $userId, '&', $dbw );
+ }
+}
diff --git a/www/wiki/includes/revisiondelete/RevisionDeleter.php b/www/wiki/includes/revisiondelete/RevisionDeleter.php
new file mode 100644
index 00000000..7b2147a5
--- /dev/null
+++ b/www/wiki/includes/revisiondelete/RevisionDeleter.php
@@ -0,0 +1,251 @@
+<?php
+/**
+ * Revision/log/file deletion backend
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+
+/**
+ * General controller for RevDel, used by both SpecialRevisiondelete and
+ * ApiRevisionDelete.
+ * @ingroup RevisionDelete
+ */
+class RevisionDeleter {
+ /** List of known revdel types, with their corresponding list classes */
+ private static $allowedTypes = [
+ 'revision' => RevDelRevisionList::class,
+ 'archive' => RevDelArchiveList::class,
+ 'oldimage' => RevDelFileList::class,
+ 'filearchive' => RevDelArchivedFileList::class,
+ 'logging' => RevDelLogList::class,
+ ];
+
+ /** Type map to support old log entries */
+ private static $deprecatedTypeMap = [
+ 'oldid' => 'revision',
+ 'artimestamp' => 'archive',
+ 'oldimage' => 'oldimage',
+ 'fileid' => 'filearchive',
+ 'logid' => 'logging',
+ ];
+
+ /**
+ * Lists the valid possible types for revision deletion.
+ *
+ * @since 1.22
+ * @return array
+ */
+ public static function getTypes() {
+ return array_keys( self::$allowedTypes );
+ }
+
+ /**
+ * Gets the canonical type name, if any.
+ *
+ * @since 1.22
+ * @param string $typeName
+ * @return string|null
+ */
+ public static function getCanonicalTypeName( $typeName ) {
+ if ( isset( self::$deprecatedTypeMap[$typeName] ) ) {
+ $typeName = self::$deprecatedTypeMap[$typeName];
+ }
+ return isset( self::$allowedTypes[$typeName] ) ? $typeName : null;
+ }
+
+ /**
+ * Instantiate the appropriate list class for a given list of IDs.
+ *
+ * @since 1.22
+ * @param string $typeName RevDel type, see RevisionDeleter::getTypes()
+ * @param IContextSource $context
+ * @param Title $title
+ * @param array $ids
+ * @return RevDelList
+ * @throws MWException
+ */
+ public static function createList( $typeName, IContextSource $context, Title $title, array $ids ) {
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
+ throw new MWException( __METHOD__ . ": Unknown RevDel type '$typeName'" );
+ }
+ $class = self::$allowedTypes[$typeName];
+ return new $class( $context, $title, $ids );
+ }
+
+ /**
+ * Checks for a change in the bitfield for a certain option and updates the
+ * provided array accordingly.
+ *
+ * @param string $desc Description to add to the array if the option was
+ * enabled / disabled.
+ * @param int $field The bitmask describing the single option.
+ * @param int $diff The xor of the old and new bitfields.
+ * @param int $new The new bitfield
+ * @param array &$arr The array to update.
+ */
+ protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
+ if ( $diff & $field ) {
+ $arr[( $new & $field ) ? 0 : 1][] = $desc;
+ }
+ }
+
+ /**
+ * Gets an array of message keys describing the changes made to the
+ * visibility of the revision.
+ *
+ * If the resulting array is $arr, then $arr[0] will contain an array of
+ * keys describing the items that were hidden, $arr[1] will contain
+ * an array of keys describing the items that were unhidden, and $arr[2]
+ * will contain an array with a single message key, which can be one of
+ * "revdelete-restricted", "revdelete-unrestricted" indicating (un)suppression
+ * or null to indicate nothing in particular.
+ * You can turn the keys in $arr[0] and $arr[1] into message keys by
+ * appending -hid and -unhid to the keys respectively.
+ *
+ * @param int $n The new bitfield.
+ * @param int $o The old bitfield.
+ * @return array An array as described above.
+ * @since 1.19 public
+ */
+ public static function getChanges( $n, $o ) {
+ $diff = $n ^ $o;
+ $ret = [ 0 => [], 1 => [], 2 => [] ];
+ // Build bitfield changes in language
+ self::checkItem( 'revdelete-content',
+ Revision::DELETED_TEXT, $diff, $n, $ret );
+ self::checkItem( 'revdelete-summary',
+ Revision::DELETED_COMMENT, $diff, $n, $ret );
+ self::checkItem( 'revdelete-uname',
+ Revision::DELETED_USER, $diff, $n, $ret );
+ // Restriction application to sysops
+ if ( $diff & Revision::DELETED_RESTRICTED ) {
+ if ( $n & Revision::DELETED_RESTRICTED ) {
+ $ret[2][] = 'revdelete-restricted';
+ } else {
+ $ret[2][] = 'revdelete-unrestricted';
+ }
+ }
+ return $ret;
+ }
+
+ /** Get DB field name for URL param...
+ * Future code for other things may also track
+ * other types of revision-specific changes.
+ * @param string $typeName
+ * @return string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
+ */
+ public static function getRelationType( $typeName ) {
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
+ return null;
+ }
+ return call_user_func( [ self::$allowedTypes[$typeName], 'getRelationType' ] );
+ }
+
+ /**
+ * Get the user right required for the RevDel type
+ * @since 1.22
+ * @param string $typeName
+ * @return string User right
+ */
+ public static function getRestriction( $typeName ) {
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
+ return null;
+ }
+ return call_user_func( [ self::$allowedTypes[$typeName], 'getRestriction' ] );
+ }
+
+ /**
+ * Get the revision deletion constant for the RevDel type
+ * @since 1.22
+ * @param string $typeName
+ * @return int RevDel constant
+ */
+ public static function getRevdelConstant( $typeName ) {
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
+ return null;
+ }
+ return call_user_func( [ self::$allowedTypes[$typeName], 'getRevdelConstant' ] );
+ }
+
+ /**
+ * Suggest a target for the revision deletion
+ * @since 1.22
+ * @param string $typeName
+ * @param Title|null $target User-supplied target
+ * @param array $ids
+ * @return Title|null
+ */
+ public static function suggestTarget( $typeName, $target, array $ids ) {
+ $typeName = self::getCanonicalTypeName( $typeName );
+ if ( !$typeName ) {
+ return $target;
+ }
+ return call_user_func( [ self::$allowedTypes[$typeName], 'suggestTarget' ], $target, $ids );
+ }
+
+ /**
+ * Checks if a revision still exists in the revision table.
+ * If it doesn't, returns the corresponding ar_timestamp field
+ * so that this key can be used instead.
+ *
+ * @param Title $title
+ * @param int $revid
+ * @return bool|mixed
+ */
+ public static function checkRevisionExistence( $title, $revid ) {
+ $dbr = wfGetDB( DB_REPLICA );
+ $exists = $dbr->selectField( 'revision', '1',
+ [ 'rev_id' => $revid ], __METHOD__ );
+
+ if ( $exists ) {
+ return true;
+ }
+
+ $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
+ [ 'ar_namespace' => $title->getNamespace(),
+ 'ar_title' => $title->getDBkey(),
+ 'ar_rev_id' => $revid ], __METHOD__ );
+
+ return $timestamp;
+ }
+
+ /**
+ * Put together a rev_deleted bitfield
+ * @since 1.22
+ * @param array $bitPars ExtractBitParams() params
+ * @param int $oldfield Current bitfield
+ * @return int
+ */
+ public static function extractBitfield( array $bitPars, $oldfield ) {
+ // Build the actual new rev_deleted bitfield
+ $newBits = 0;
+ foreach ( $bitPars as $const => $val ) {
+ if ( $val == 1 ) {
+ $newBits |= $const; // $const is the *_deleted const
+ } elseif ( $val == -1 ) {
+ $newBits |= ( $oldfield & $const ); // use existing
+ }
+ }
+ return $newBits;
+ }
+}