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 24use MediaWiki\MediaWikiServices; 25use MediaWiki\Revision\RevisionRecord; 26 27/** 28 * General controller for RevDel, used by both SpecialRevisiondelete and 29 * ApiRevisionDelete. 30 * @ingroup RevisionDelete 31 */ 32class RevisionDeleter { 33 /** 34 * List of known revdel types, with their corresponding ObjectFactory spec to 35 * create the relevant class. All specs need to include DBLoadBalancerFactory, 36 * which is used in the base RevDelList class 37 */ 38 private const ALLOWED_TYPES = [ 39 'revision' => [ 40 'class' => RevDelRevisionList::class, 41 'services' => [ 42 'DBLoadBalancerFactory', 43 'HookContainer', 44 'HtmlCacheUpdater', 45 'RevisionStore', 46 'MainWANObjectCache', 47 ], 48 ], 49 'archive' => [ 50 'class' => RevDelArchiveList::class, 51 'services' => [ 52 'DBLoadBalancerFactory', 53 'HookContainer', 54 'HtmlCacheUpdater', 55 'RevisionStore', 56 'MainWANObjectCache', 57 ], 58 ], 59 'oldimage' => [ 60 'class' => RevDelFileList::class, 61 'services' => [ 62 'DBLoadBalancerFactory', 63 'HtmlCacheUpdater', 64 'RepoGroup', 65 ], 66 ], 67 'filearchive' => [ 68 'class' => RevDelArchivedFileList::class, 69 'services' => [ 70 'DBLoadBalancerFactory', 71 'HtmlCacheUpdater', 72 'RepoGroup', 73 ], 74 ], 75 'logging' => [ 76 'class' => RevDelLogList::class, 77 'services' => [ 78 'DBLoadBalancerFactory', 79 'ActorMigration', 80 'CommentStore', 81 ], 82 ], 83 ]; 84 85 /** Type map to support old log entries */ 86 private const DEPRECATED_TYPE_MAP = [ 87 'oldid' => 'revision', 88 'artimestamp' => 'archive', 89 'oldimage' => 'oldimage', 90 'fileid' => 'filearchive', 91 'logid' => 'logging', 92 ]; 93 94 /** 95 * Lists the valid possible types for revision deletion. 96 * 97 * @since 1.22 98 * @return array 99 */ 100 public static function getTypes() { 101 return array_keys( self::ALLOWED_TYPES ); 102 } 103 104 /** 105 * Gets the canonical type name, if any. 106 * 107 * @since 1.22 108 * @param string $typeName 109 * @return string|null 110 */ 111 public static function getCanonicalTypeName( $typeName ) { 112 if ( isset( self::DEPRECATED_TYPE_MAP[$typeName] ) ) { 113 $typeName = self::DEPRECATED_TYPE_MAP[$typeName]; 114 } 115 return isset( self::ALLOWED_TYPES[$typeName] ) ? $typeName : null; 116 } 117 118 /** 119 * Instantiate the appropriate list class for a given list of IDs. 120 * 121 * @since 1.22 122 * @param string $typeName RevDel type, see RevisionDeleter::getTypes() 123 * @param IContextSource $context 124 * @param Title $title 125 * @param array $ids 126 * @return RevDelList 127 * @throws MWException 128 */ 129 public static function createList( $typeName, IContextSource $context, Title $title, array $ids ) { 130 $typeName = self::getCanonicalTypeName( $typeName ); 131 if ( !$typeName ) { 132 throw new MWException( __METHOD__ . ": Unknown RevDel type '$typeName'" ); 133 } 134 $spec = self::ALLOWED_TYPES[$typeName]; 135 $objectFactory = MediaWikiServices::getInstance()->getObjectFactory(); 136 137 // ObjectFactory::createObject accepts an array, not just a callable (phan bug) 138 // @phan-suppress-next-line PhanTypeInvalidCallableArrayKey 139 return $objectFactory->createObject( 140 $spec, 141 [ 142 'extraArgs' => [ $context, $title, $ids ], 143 'assertClass' => RevDelList::class, 144 ] 145 ); 146 } 147 148 /** 149 * Checks for a change in the bitfield for a certain option and updates the 150 * provided array accordingly. 151 * 152 * @param string $desc Description to add to the array if the option was 153 * enabled / disabled. 154 * @param int $field The bitmask describing the single option. 155 * @param int $diff The xor of the old and new bitfields. 156 * @param int $new The new bitfield 157 * @param array &$arr The array to update. 158 */ 159 protected static function checkItem( $desc, $field, $diff, $new, &$arr ) { 160 if ( $diff & $field ) { 161 $arr[( $new & $field ) ? 0 : 1][] = $desc; 162 } 163 } 164 165 /** 166 * Gets an array of message keys describing the changes made to the 167 * visibility of the revision. 168 * 169 * If the resulting array is $arr, then $arr[0] will contain an array of 170 * keys describing the items that were hidden, $arr[1] will contain 171 * an array of keys describing the items that were unhidden, and $arr[2] 172 * will contain an array with a single message key, which can be one of 173 * "revdelete-restricted", "revdelete-unrestricted" indicating (un)suppression 174 * or null to indicate nothing in particular. 175 * You can turn the keys in $arr[0] and $arr[1] into message keys by 176 * appending -hid and -unhid to the keys respectively. 177 * 178 * @param int $n The new bitfield. 179 * @param int $o The old bitfield. 180 * @return array An array as described above. 181 * @since 1.19 public 182 */ 183 public static function getChanges( $n, $o ) { 184 $diff = $n ^ $o; 185 $ret = [ 0 => [], 1 => [], 2 => [] ]; 186 // Build bitfield changes in language 187 self::checkItem( 'revdelete-content', 188 RevisionRecord::DELETED_TEXT, $diff, $n, $ret ); 189 self::checkItem( 'revdelete-summary', 190 RevisionRecord::DELETED_COMMENT, $diff, $n, $ret ); 191 self::checkItem( 'revdelete-uname', 192 RevisionRecord::DELETED_USER, $diff, $n, $ret ); 193 // Restriction application to sysops 194 if ( $diff & RevisionRecord::DELETED_RESTRICTED ) { 195 if ( $n & RevisionRecord::DELETED_RESTRICTED ) { 196 $ret[2][] = 'revdelete-restricted'; 197 } else { 198 $ret[2][] = 'revdelete-unrestricted'; 199 } 200 } 201 return $ret; 202 } 203 204 /** Get DB field name for URL param... 205 * Future code for other things may also track 206 * other types of revision-specific changes. 207 * @param string $typeName 208 * @return string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name 209 */ 210 public static function getRelationType( $typeName ) { 211 $typeName = self::getCanonicalTypeName( $typeName ); 212 if ( !$typeName ) { 213 return null; 214 } 215 return call_user_func( [ self::ALLOWED_TYPES[$typeName]['class'], 'getRelationType' ] ); 216 } 217 218 /** 219 * Get the user right required for the RevDel type 220 * @since 1.22 221 * @param string $typeName 222 * @return string User right 223 */ 224 public static function getRestriction( $typeName ) { 225 $typeName = self::getCanonicalTypeName( $typeName ); 226 if ( !$typeName ) { 227 return null; 228 } 229 return call_user_func( [ self::ALLOWED_TYPES[$typeName]['class'], 'getRestriction' ] ); 230 } 231 232 /** 233 * Get the revision deletion constant for the RevDel type 234 * @since 1.22 235 * @param string $typeName 236 * @return int RevDel constant 237 */ 238 public static function getRevdelConstant( $typeName ) { 239 $typeName = self::getCanonicalTypeName( $typeName ); 240 if ( !$typeName ) { 241 return null; 242 } 243 return call_user_func( [ self::ALLOWED_TYPES[$typeName]['class'], 'getRevdelConstant' ] ); 244 } 245 246 /** 247 * Suggest a target for the revision deletion 248 * @since 1.22 249 * @param string $typeName 250 * @param Title|null $target User-supplied target 251 * @param array $ids 252 * @return Title|null 253 */ 254 public static function suggestTarget( $typeName, $target, array $ids ) { 255 $typeName = self::getCanonicalTypeName( $typeName ); 256 if ( !$typeName ) { 257 return $target; 258 } 259 return call_user_func( 260 [ self::ALLOWED_TYPES[$typeName]['class'], 'suggestTarget' ], 261 $target, 262 $ids 263 ); 264 } 265 266 /** 267 * Put together a rev_deleted bitfield 268 * @since 1.22 269 * @param array $bitPars ExtractBitParams() params 270 * @param int $oldfield Current bitfield 271 * @return int 272 */ 273 public static function extractBitfield( array $bitPars, $oldfield ) { 274 // Build the actual new rev_deleted bitfield 275 $newBits = 0; 276 foreach ( $bitPars as $const => $val ) { 277 if ( $val == 1 ) { 278 $newBits |= $const; // $const is the *_deleted const 279 } elseif ( $val == -1 ) { 280 $newBits |= ( $oldfield & $const ); // use existing 281 } 282 } 283 return $newBits; 284 } 285} 286