Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
68.42% |
52 / 76 |
|
33.33% |
2 / 6 |
CRAP | |
0.00% |
0 / 1 |
PageArchive | |
68.42% |
52 / 76 |
|
33.33% |
2 / 6 |
28.20 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
listPagesBySearch | |
50.00% |
13 / 26 |
|
0.00% |
0 / 1 |
6.00 | |||
listPagesByPrefix | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
2.00 | |||
listPages | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
listFiles | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
undeleteAsUser | |
86.96% |
20 / 23 |
|
0.00% |
0 / 1 |
8.14 |
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 | */ |
20 | |
21 | use MediaWiki\FileRepo\File\FileSelectQueryBuilder; |
22 | use MediaWiki\MediaWikiServices; |
23 | use MediaWiki\Page\UndeletePage; |
24 | use MediaWiki\Title\Title; |
25 | use MediaWiki\User\UserIdentity; |
26 | use Wikimedia\Rdbms\IExpression; |
27 | use Wikimedia\Rdbms\IReadableDatabase; |
28 | use Wikimedia\Rdbms\IResultWrapper; |
29 | use Wikimedia\Rdbms\LikeValue; |
30 | use Wikimedia\Rdbms\SelectQueryBuilder; |
31 | |
32 | /** |
33 | * Used to show archived pages and eventually restore them. |
34 | */ |
35 | class PageArchive { |
36 | |
37 | protected Title $title; |
38 | |
39 | /** |
40 | * @param Title $title |
41 | */ |
42 | public function __construct( Title $title ) { |
43 | $this->title = $title; |
44 | } |
45 | |
46 | /** |
47 | * List deleted pages recorded in the archive matching the |
48 | * given term, using search engine archive. |
49 | * Returns result wrapper with (ar_namespace, ar_title, count) fields. |
50 | * |
51 | * @param string $term Search term |
52 | * @return IResultWrapper|bool |
53 | */ |
54 | public static function listPagesBySearch( $term ) { |
55 | $title = Title::newFromText( $term ); |
56 | if ( $title ) { |
57 | $ns = $title->getNamespace(); |
58 | $termMain = $title->getText(); |
59 | $termDb = $title->getDBkey(); |
60 | } else { |
61 | // Prolly won't work too good |
62 | // @todo handle bare namespace names cleanly? |
63 | $ns = 0; |
64 | $termMain = $termDb = $term; |
65 | } |
66 | |
67 | // Try search engine first |
68 | $engine = MediaWikiServices::getInstance()->newSearchEngine(); |
69 | $engine->setLimitOffset( 100 ); |
70 | $engine->setNamespaces( [ $ns ] ); |
71 | $results = $engine->searchArchiveTitle( $termMain ); |
72 | if ( !$results->isOK() ) { |
73 | $results = []; |
74 | } else { |
75 | $results = $results->getValue(); |
76 | } |
77 | |
78 | if ( !$results ) { |
79 | // Fall back to regular prefix search |
80 | return self::listPagesByPrefix( $term ); |
81 | } |
82 | |
83 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
84 | $condTitles = array_values( array_unique( array_map( static function ( Title $t ) { |
85 | return $t->getDBkey(); |
86 | }, $results ) ) ); |
87 | $conds = [ |
88 | 'ar_namespace' => $ns, |
89 | $dbr->expr( 'ar_title', '=', $condTitles ) |
90 | ->or( 'ar_title', IExpression::LIKE, new LikeValue( $termDb, $dbr->anyString() ) ), |
91 | ]; |
92 | |
93 | return self::listPages( $dbr, $conds ); |
94 | } |
95 | |
96 | /** |
97 | * List deleted pages recorded in the archive table matching the |
98 | * given title prefix. |
99 | * Returns result wrapper with (ar_namespace, ar_title, count) fields. |
100 | * |
101 | * @param string $prefix Title prefix |
102 | * @return IResultWrapper|bool |
103 | */ |
104 | public static function listPagesByPrefix( $prefix ) { |
105 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
106 | |
107 | $title = Title::newFromText( $prefix ); |
108 | if ( $title ) { |
109 | $ns = $title->getNamespace(); |
110 | $prefix = $title->getDBkey(); |
111 | } else { |
112 | // Prolly won't work too good |
113 | // @todo handle bare namespace names cleanly? |
114 | $ns = 0; |
115 | } |
116 | |
117 | $conds = [ |
118 | 'ar_namespace' => $ns, |
119 | $dbr->expr( 'ar_title', IExpression::LIKE, new LikeValue( $prefix, $dbr->anyString() ) ), |
120 | ]; |
121 | |
122 | return self::listPages( $dbr, $conds ); |
123 | } |
124 | |
125 | /** |
126 | * @param IReadableDatabase $dbr |
127 | * @param string|array $condition |
128 | * @return IResultWrapper |
129 | */ |
130 | protected static function listPages( IReadableDatabase $dbr, $condition ) { |
131 | return $dbr->newSelectQueryBuilder() |
132 | ->select( [ 'ar_namespace', 'ar_title', 'count' => 'COUNT(*)' ] ) |
133 | ->from( 'archive' ) |
134 | ->where( $condition ) |
135 | ->groupBy( [ 'ar_namespace', 'ar_title' ] ) |
136 | ->orderBy( [ 'ar_namespace', 'ar_title' ] ) |
137 | ->limit( 100 ) |
138 | ->caller( __METHOD__ )->fetchResultSet(); |
139 | } |
140 | |
141 | /** |
142 | * List the deleted file revisions for this page, if it's a file page. |
143 | * Returns a result wrapper with various filearchive fields, or null |
144 | * if not a file page. |
145 | * |
146 | * @return IResultWrapper|null |
147 | * @todo Does this belong in Image for fuller encapsulation? |
148 | */ |
149 | public function listFiles() { |
150 | if ( $this->title->getNamespace() !== NS_FILE ) { |
151 | return null; |
152 | } |
153 | |
154 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
155 | $queryBuilder = FileSelectQueryBuilder::newForArchivedFile( $dbr ); |
156 | $queryBuilder->where( [ 'fa_name' => $this->title->getDBkey() ] ) |
157 | ->orderBy( 'fa_timestamp', SelectQueryBuilder::SORT_DESC ); |
158 | return $queryBuilder->caller( __METHOD__ )->fetchResultSet(); |
159 | } |
160 | |
161 | /** |
162 | * Restore the given (or all) text and file revisions for the page. |
163 | * Once restored, the items will be removed from the archive tables. |
164 | * The deletion log will be updated with an undeletion notice. |
165 | * |
166 | * @since 1.35 |
167 | * @deprecated since 1.38, use UndeletePage instead, hard-deprecated since 1.43 |
168 | * |
169 | * @param array $timestamps Pass an empty array to restore all revisions, |
170 | * otherwise list the ones to undelete. |
171 | * @param UserIdentity $user |
172 | * @param string $comment |
173 | * @param array $fileVersions |
174 | * @param bool $unsuppress |
175 | * @param string|string[]|null $tags Change tags to add to log entry |
176 | * ($user should be able to add the specified tags before this is called) |
177 | * @return array|false [ number of file revisions restored, number of image revisions |
178 | * restored, log message ] on success, false on failure. |
179 | */ |
180 | public function undeleteAsUser( |
181 | $timestamps, |
182 | UserIdentity $user, |
183 | $comment = '', |
184 | $fileVersions = [], |
185 | $unsuppress = false, |
186 | $tags = null |
187 | ) { |
188 | wfDeprecated( __METHOD__, '1.43' ); |
189 | $services = MediaWikiServices::getInstance(); |
190 | $page = $services->getWikiPageFactory()->newFromTitle( $this->title ); |
191 | $user = $services->getUserFactory()->newFromUserIdentity( $user ); |
192 | $up = $services->getUndeletePageFactory()->newUndeletePage( $page, $user ); |
193 | if ( is_string( $tags ) ) { |
194 | $tags = [ $tags ]; |
195 | } elseif ( $tags === null ) { |
196 | $tags = []; |
197 | } |
198 | $status = $up |
199 | ->setUndeleteOnlyTimestamps( $timestamps ) |
200 | ->setUndeleteOnlyFileVersions( $fileVersions ?: [] ) |
201 | ->setUnsuppress( $unsuppress ) |
202 | ->setTags( $tags ?: [] ) |
203 | ->undeleteUnsafe( $comment ); |
204 | // BC with old return format |
205 | if ( $status->isGood() ) { |
206 | $restoredRevs = $status->getValue()[UndeletePage::REVISIONS_RESTORED]; |
207 | $restoredFiles = $status->getValue()[UndeletePage::FILES_RESTORED]; |
208 | if ( $restoredRevs === 0 && $restoredFiles === 0 ) { |
209 | $ret = false; |
210 | } else { |
211 | $ret = [ $restoredRevs, $restoredFiles, $comment ]; |
212 | } |
213 | } else { |
214 | $ret = false; |
215 | } |
216 | return $ret; |
217 | } |
218 | |
219 | } |