Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 61 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
RevisionDeleter | |
0.00% |
0 / 61 |
|
0.00% |
0 / 10 |
600 | |
0.00% |
0 / 1 |
getTypes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCanonicalTypeName | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
createList | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
checkItem | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
12 | |||
getChanges | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
getRelationType | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getRestriction | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getRevdelConstant | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
suggestTarget | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
extractBitfield | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | /** |
3 | * Revision/log/file deletion backend |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup RevisionDelete |
22 | */ |
23 | |
24 | use MediaWiki\Context\IContextSource; |
25 | use MediaWiki\MediaWikiServices; |
26 | use MediaWiki\Page\PageIdentity; |
27 | use MediaWiki\Revision\RevisionRecord; |
28 | use MediaWiki\Title\Title; |
29 | |
30 | /** |
31 | * General controller for RevDel, used by both SpecialRevisiondelete and |
32 | * ApiRevisionDelete. |
33 | * @ingroup RevisionDelete |
34 | */ |
35 | class RevisionDeleter { |
36 | /** |
37 | * List of known revdel types, with their corresponding ObjectFactory spec to |
38 | * create the relevant class. All specs need to include DBLoadBalancerFactory, |
39 | * which is used in the base RevDelList class |
40 | */ |
41 | private const ALLOWED_TYPES = [ |
42 | 'revision' => [ |
43 | 'class' => RevDelRevisionList::class, |
44 | 'services' => [ |
45 | 'DBLoadBalancerFactory', |
46 | 'HookContainer', |
47 | 'HtmlCacheUpdater', |
48 | 'RevisionStore', |
49 | 'DomainEventDispatcher' |
50 | ], |
51 | ], |
52 | 'archive' => [ |
53 | 'class' => RevDelArchiveList::class, |
54 | 'services' => [ |
55 | 'DBLoadBalancerFactory', |
56 | 'HookContainer', |
57 | 'HtmlCacheUpdater', |
58 | 'RevisionStore', |
59 | 'DomainEventDispatcher' |
60 | ], |
61 | ], |
62 | 'oldimage' => [ |
63 | 'class' => RevDelFileList::class, |
64 | 'services' => [ |
65 | 'DBLoadBalancerFactory', |
66 | 'HtmlCacheUpdater', |
67 | 'RepoGroup', |
68 | ], |
69 | ], |
70 | 'filearchive' => [ |
71 | 'class' => RevDelArchivedFileList::class, |
72 | 'services' => [ |
73 | 'DBLoadBalancerFactory', |
74 | 'HtmlCacheUpdater', |
75 | 'RepoGroup', |
76 | ], |
77 | ], |
78 | 'logging' => [ |
79 | 'class' => RevDelLogList::class, |
80 | 'services' => [ |
81 | 'DBLoadBalancerFactory', |
82 | 'CommentStore', |
83 | 'LogFormatterFactory', |
84 | ], |
85 | ], |
86 | ]; |
87 | |
88 | /** Type map to support old log entries */ |
89 | private const DEPRECATED_TYPE_MAP = [ |
90 | 'oldid' => 'revision', |
91 | 'artimestamp' => 'archive', |
92 | 'oldimage' => 'oldimage', |
93 | 'fileid' => 'filearchive', |
94 | 'logid' => 'logging', |
95 | ]; |
96 | |
97 | /** |
98 | * Lists the valid possible types for revision deletion. |
99 | * |
100 | * @since 1.22 |
101 | * @return array |
102 | */ |
103 | public static function getTypes() { |
104 | return array_keys( self::ALLOWED_TYPES ); |
105 | } |
106 | |
107 | /** |
108 | * Gets the canonical type name, if any. |
109 | * |
110 | * @since 1.22 |
111 | * @param string $typeName |
112 | * @return string|null |
113 | */ |
114 | public static function getCanonicalTypeName( $typeName ) { |
115 | if ( isset( self::DEPRECATED_TYPE_MAP[$typeName] ) ) { |
116 | $typeName = self::DEPRECATED_TYPE_MAP[$typeName]; |
117 | } |
118 | return isset( self::ALLOWED_TYPES[$typeName] ) ? $typeName : null; |
119 | } |
120 | |
121 | /** |
122 | * Instantiate the appropriate list class for a given list of IDs. |
123 | * |
124 | * @since 1.22 |
125 | * @param string $typeName RevDel type, see RevisionDeleter::getTypes() |
126 | * @param IContextSource $context |
127 | * @param PageIdentity $page |
128 | * @param array $ids |
129 | * @return RevDelList |
130 | */ |
131 | public static function createList( $typeName, IContextSource $context, PageIdentity $page, array $ids ) { |
132 | $typeName = self::getCanonicalTypeName( $typeName ); |
133 | if ( !$typeName ) { |
134 | throw new InvalidArgumentException( __METHOD__ . ": Unknown RevDel type '$typeName'" ); |
135 | } |
136 | $spec = self::ALLOWED_TYPES[$typeName]; |
137 | $objectFactory = MediaWikiServices::getInstance()->getObjectFactory(); |
138 | |
139 | // ObjectFactory::createObject accepts an array, not just a callable (phan bug) |
140 | // @phan-suppress-next-line PhanTypeInvalidCallableArrayKey |
141 | return $objectFactory->createObject( |
142 | $spec, |
143 | [ |
144 | 'extraArgs' => [ $context, $page, $ids ], |
145 | 'assertClass' => RevDelList::class, |
146 | ] |
147 | ); |
148 | } |
149 | |
150 | /** |
151 | * Checks for a change in the bitfield for a certain option and updates the |
152 | * provided array accordingly. |
153 | * |
154 | * @param string $desc Description to add to the array if the option was |
155 | * enabled / disabled. |
156 | * @param int $field The bitmask describing the single option. |
157 | * @param int $diff The xor of the old and new bitfields. |
158 | * @param int $new The new bitfield |
159 | * @param array &$arr The array to update. |
160 | */ |
161 | protected static function checkItem( $desc, $field, $diff, $new, &$arr ) { |
162 | if ( $diff & $field ) { |
163 | $arr[( $new & $field ) ? 0 : 1][] = $desc; |
164 | } |
165 | } |
166 | |
167 | /** |
168 | * Gets an array of message keys describing the changes made to the |
169 | * visibility of the revision. |
170 | * |
171 | * If the resulting array is $arr, then $arr[0] will contain an array of |
172 | * keys describing the items that were hidden, $arr[1] will contain |
173 | * an array of keys describing the items that were unhidden, and $arr[2] |
174 | * will contain an array with a single message key, which can be one of |
175 | * "revdelete-restricted", "revdelete-unrestricted" indicating (un)suppression |
176 | * or null to indicate nothing in particular. |
177 | * You can turn the keys in $arr[0] and $arr[1] into message keys by |
178 | * appending -hid and -unhid to the keys respectively. |
179 | * |
180 | * @param int $n The new bitfield. |
181 | * @param int $o The old bitfield. |
182 | * @return array An array as described above. |
183 | * @since 1.19 public |
184 | */ |
185 | public static function getChanges( $n, $o ) { |
186 | $diff = $n ^ $o; |
187 | $ret = [ 0 => [], 1 => [], 2 => [] ]; |
188 | // Build bitfield changes in language |
189 | self::checkItem( 'revdelete-content', |
190 | RevisionRecord::DELETED_TEXT, $diff, $n, $ret ); |
191 | self::checkItem( 'revdelete-summary', |
192 | RevisionRecord::DELETED_COMMENT, $diff, $n, $ret ); |
193 | self::checkItem( 'revdelete-uname', |
194 | RevisionRecord::DELETED_USER, $diff, $n, $ret ); |
195 | // Restriction application to sysops |
196 | if ( $diff & RevisionRecord::DELETED_RESTRICTED ) { |
197 | if ( $n & RevisionRecord::DELETED_RESTRICTED ) { |
198 | $ret[2][] = 'revdelete-restricted'; |
199 | } else { |
200 | $ret[2][] = 'revdelete-unrestricted'; |
201 | } |
202 | } |
203 | return $ret; |
204 | } |
205 | |
206 | /** Get DB field name for URL param... |
207 | * Future code for other things may also track |
208 | * other types of revision-specific changes. |
209 | * @param string $typeName |
210 | * @return string|null One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name |
211 | */ |
212 | public static function getRelationType( $typeName ) { |
213 | $typeName = self::getCanonicalTypeName( $typeName ); |
214 | if ( !$typeName ) { |
215 | return null; |
216 | } |
217 | $class = self::ALLOWED_TYPES[$typeName]['class']; |
218 | return $class::getRelationType(); |
219 | } |
220 | |
221 | /** |
222 | * Get the user right required for the RevDel type |
223 | * @since 1.22 |
224 | * @param string $typeName |
225 | * @return string|null User right |
226 | */ |
227 | public static function getRestriction( $typeName ) { |
228 | $typeName = self::getCanonicalTypeName( $typeName ); |
229 | if ( !$typeName ) { |
230 | return null; |
231 | } |
232 | $class = self::ALLOWED_TYPES[$typeName]['class']; |
233 | return $class::getRestriction(); |
234 | } |
235 | |
236 | /** |
237 | * Get the revision deletion constant for the RevDel type |
238 | * @since 1.22 |
239 | * @param string $typeName |
240 | * @return int|null RevDel constant |
241 | */ |
242 | public static function getRevdelConstant( $typeName ) { |
243 | $typeName = self::getCanonicalTypeName( $typeName ); |
244 | if ( !$typeName ) { |
245 | return null; |
246 | } |
247 | $class = self::ALLOWED_TYPES[$typeName]['class']; |
248 | return $class::getRevdelConstant(); |
249 | } |
250 | |
251 | /** |
252 | * Suggest a target for the revision deletion |
253 | * @since 1.22 |
254 | * @param string $typeName |
255 | * @param Title|null $target User-supplied target |
256 | * @param array $ids |
257 | * @return Title|null |
258 | */ |
259 | public static function suggestTarget( $typeName, $target, array $ids ) { |
260 | $typeName = self::getCanonicalTypeName( $typeName ); |
261 | if ( !$typeName ) { |
262 | return $target; |
263 | } |
264 | $class = self::ALLOWED_TYPES[$typeName]['class']; |
265 | return $class::suggestTarget( |
266 | $target, |
267 | $ids |
268 | ); |
269 | } |
270 | |
271 | /** |
272 | * Put together a rev_deleted bitfield |
273 | * @since 1.22 |
274 | * @param array $bitPars associative array mapping bit masks to 0, 1 or -1. |
275 | * A value of 0 unsets the bits in the mask, 1 will set the bits in |
276 | * the mask, and any other value will retain the bits already present |
277 | * in $oldfield. |
278 | * @param int $oldfield Current bitfield |
279 | * |
280 | * @internal |
281 | * @return int |
282 | */ |
283 | public static function extractBitfield( array $bitPars, $oldfield ) { |
284 | // Build the actual new rev_deleted bitfield |
285 | $newBits = $oldfield; |
286 | foreach ( $bitPars as $const => $val ) { |
287 | // $const is the XXX_DELETED const |
288 | |
289 | if ( $val == 1 ) { |
290 | $newBits |= $const; // set the bit |
291 | } elseif ( $val == 0 ) { |
292 | $newBits &= ~$const; // unset the bit |
293 | } |
294 | } |
295 | return $newBits; |
296 | } |
297 | } |