Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
RevDelRevisionList
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 11
420
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRelationType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRestriction
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRevdelConstant
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 suggestTarget
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 doQuery
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
42
 newItem
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getCurrent
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 doPreCommitUpdates
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 doPostCommitUpdates
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 * @ingroup RevisionDelete
20 */
21
22use MediaWiki\Cache\HTMLCacheUpdater;
23use MediaWiki\Context\IContextSource;
24use MediaWiki\HookContainer\HookContainer;
25use MediaWiki\HookContainer\HookRunner;
26use MediaWiki\MediaWikiServices;
27use MediaWiki\Page\PageIdentity;
28use MediaWiki\Revision\RevisionRecord;
29use MediaWiki\Revision\RevisionStore;
30use MediaWiki\Status\Status;
31use MediaWiki\Title\Title;
32use Wikimedia\Rdbms\FakeResultWrapper;
33use Wikimedia\Rdbms\IResultWrapper;
34use Wikimedia\Rdbms\LBFactory;
35
36/**
37 * List for revision table items
38 *
39 * This will check both the 'revision' table for live revisions and the
40 * 'archive' table for traditionally-deleted revisions that have an
41 * ar_rev_id saved.
42 *
43 * See RevDelRevisionItem and RevDelArchivedRevisionItem for items.
44 */
45class RevDelRevisionList extends RevDelList {
46
47    /** @var LBFactory */
48    private $lbFactory;
49
50    /** @var HookRunner */
51    private $hookRunner;
52
53    /** @var HTMLCacheUpdater */
54    private $htmlCacheUpdater;
55
56    /** @var RevisionStore */
57    private $revisionStore;
58
59    /** @var int */
60    public $currentRevId;
61
62    /**
63     * @param IContextSource $context
64     * @param PageIdentity $page
65     * @param array $ids
66     * @param LBFactory $lbFactory
67     * @param HookContainer $hookContainer
68     * @param HTMLCacheUpdater $htmlCacheUpdater
69     * @param RevisionStore $revisionStore
70     */
71    public function __construct(
72        IContextSource $context,
73        PageIdentity $page,
74        array $ids,
75        LBFactory $lbFactory,
76        HookContainer $hookContainer,
77        HTMLCacheUpdater $htmlCacheUpdater,
78        RevisionStore $revisionStore
79    ) {
80        parent::__construct( $context, $page, $ids, $lbFactory );
81        $this->lbFactory = $lbFactory;
82        $this->hookRunner = new HookRunner( $hookContainer );
83        $this->htmlCacheUpdater = $htmlCacheUpdater;
84        $this->revisionStore = $revisionStore;
85    }
86
87    public function getType() {
88        return 'revision';
89    }
90
91    public static function getRelationType() {
92        return 'rev_id';
93    }
94
95    public static function getRestriction() {
96        return 'deleterevision';
97    }
98
99    public static function getRevdelConstant() {
100        return RevisionRecord::DELETED_TEXT;
101    }
102
103    public static function suggestTarget( $target, array $ids ) {
104        $revisionRecord = MediaWikiServices::getInstance()
105            ->getRevisionLookup()
106            ->getRevisionById( $ids[0] );
107
108        if ( $revisionRecord ) {
109            return Title::newFromLinkTarget( $revisionRecord->getPageAsLinkTarget() );
110        }
111        return $target;
112    }
113
114    /**
115     * @param \Wikimedia\Rdbms\IReadableDatabase $db
116     * @return IResultWrapper
117     */
118    public function doQuery( $db ) {
119        $ids = array_map( 'intval', $this->ids );
120        $queryBuilder = $this->revisionStore->newSelectQueryBuilder( $db )
121            ->joinComment()
122            ->joinUser()
123            ->joinPage()
124            ->where( [ 'rev_page' => $this->page->getId(), 'rev_id' => $ids ] )
125            ->orderBy( 'rev_id', \Wikimedia\Rdbms\SelectQueryBuilder::SORT_DESC )
126            // workaround for MySQL bug (T104313)
127            ->useIndex( [ 'revision' => 'PRIMARY' ] );
128
129        MediaWikiServices::getInstance()->getChangeTagsStore()->modifyDisplayQueryBuilder( $queryBuilder, 'revision' );
130
131        $live = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
132        if ( $live->numRows() >= count( $ids ) ) {
133            // All requested revisions are live, keeps things simple!
134            return $live;
135        }
136
137        $queryBuilder = $this->revisionStore->newArchiveSelectQueryBuilder( $db )
138            ->joinComment()
139            ->where( [ 'ar_rev_id' => $ids ] )
140            ->orderBy( 'ar_rev_id', \Wikimedia\Rdbms\SelectQueryBuilder::SORT_DESC );
141
142        MediaWikiServices::getInstance()->getChangeTagsStore()->modifyDisplayQueryBuilder( $queryBuilder, 'archive' );
143
144        // Check if any requested revisions are available fully deleted.
145        $archived = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
146
147        if ( $archived->numRows() == 0 ) {
148            return $live;
149        } elseif ( $live->numRows() == 0 ) {
150            return $archived;
151        } else {
152            // Combine the two! Whee
153            $rows = [];
154            foreach ( $live as $row ) {
155                $rows[$row->rev_id] = $row;
156            }
157            foreach ( $archived as $row ) {
158                $rows[$row->ar_rev_id] = $row;
159            }
160            krsort( $rows );
161            return new FakeResultWrapper( array_values( $rows ) );
162        }
163    }
164
165    public function newItem( $row ) {
166        if ( isset( $row->rev_id ) ) {
167            return new RevDelRevisionItem( $this, $row );
168        } elseif ( isset( $row->ar_rev_id ) ) {
169            return new RevDelArchivedRevisionItem( $this, $row, $this->lbFactory );
170        } else {
171            // This shouldn't happen. :)
172            throw new InvalidArgumentException( 'Invalid row type in RevDelRevisionList' );
173        }
174    }
175
176    public function getCurrent() {
177        if ( $this->currentRevId === null ) {
178            $dbw = $this->lbFactory->getPrimaryDatabase();
179            $this->currentRevId = $dbw->newSelectQueryBuilder()
180                ->select( 'page_latest' )
181                ->from( 'page' )
182                ->where( [ 'page_namespace' => $this->page->getNamespace(), 'page_title' => $this->page->getDBkey() ] )
183                ->caller( __METHOD__ )->fetchField();
184        }
185        return $this->currentRevId;
186    }
187
188    public function doPreCommitUpdates() {
189        Title::newFromPageIdentity( $this->page )->invalidateCache();
190        return Status::newGood();
191    }
192
193    public function doPostCommitUpdates( array $visibilityChangeMap ) {
194        $this->htmlCacheUpdater->purgeTitleUrls(
195            $this->page,
196            HTMLCacheUpdater::PURGE_INTENT_TXROUND_REFLECTED
197        );
198        // Extensions that require referencing previous revisions may need this
199        $this->hookRunner->onArticleRevisionVisibilitySet(
200            Title::newFromPageIdentity( $this->page ),
201            $this->ids,
202            $visibilityChangeMap
203        );
204
205        return Status::newGood();
206    }
207}