RevisionDeleter.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. * General controller for RevDel, used by both SpecialRevisiondelete and
  25. * ApiRevisionDelete.
  26. * @ingroup RevisionDelete
  27. */
  28. class RevisionDeleter {
  29. /** List of known revdel types, with their corresponding list classes */
  30. private static $allowedTypes = [
  31. 'revision' => RevDelRevisionList::class,
  32. 'archive' => RevDelArchiveList::class,
  33. 'oldimage' => RevDelFileList::class,
  34. 'filearchive' => RevDelArchivedFileList::class,
  35. 'logging' => RevDelLogList::class,
  36. ];
  37. /** Type map to support old log entries */
  38. private static $deprecatedTypeMap = [
  39. 'oldid' => 'revision',
  40. 'artimestamp' => 'archive',
  41. 'oldimage' => 'oldimage',
  42. 'fileid' => 'filearchive',
  43. 'logid' => 'logging',
  44. ];
  45. /**
  46. * Lists the valid possible types for revision deletion.
  47. *
  48. * @since 1.22
  49. * @return array
  50. */
  51. public static function getTypes() {
  52. return array_keys( self::$allowedTypes );
  53. }
  54. /**
  55. * Gets the canonical type name, if any.
  56. *
  57. * @since 1.22
  58. * @param string $typeName
  59. * @return string|null
  60. */
  61. public static function getCanonicalTypeName( $typeName ) {
  62. if ( isset( self::$deprecatedTypeMap[$typeName] ) ) {
  63. $typeName = self::$deprecatedTypeMap[$typeName];
  64. }
  65. return isset( self::$allowedTypes[$typeName] ) ? $typeName : null;
  66. }
  67. /**
  68. * Instantiate the appropriate list class for a given list of IDs.
  69. *
  70. * @since 1.22
  71. * @param string $typeName RevDel type, see RevisionDeleter::getTypes()
  72. * @param IContextSource $context
  73. * @param Title $title
  74. * @param array $ids
  75. * @return RevDelList
  76. * @throws MWException
  77. */
  78. public static function createList( $typeName, IContextSource $context, Title $title, array $ids ) {
  79. $typeName = self::getCanonicalTypeName( $typeName );
  80. if ( !$typeName ) {
  81. throw new MWException( __METHOD__ . ": Unknown RevDel type '$typeName'" );
  82. }
  83. $class = self::$allowedTypes[$typeName];
  84. return new $class( $context, $title, $ids );
  85. }
  86. /**
  87. * Checks for a change in the bitfield for a certain option and updates the
  88. * provided array accordingly.
  89. *
  90. * @param string $desc Description to add to the array if the option was
  91. * enabled / disabled.
  92. * @param int $field The bitmask describing the single option.
  93. * @param int $diff The xor of the old and new bitfields.
  94. * @param int $new The new bitfield
  95. * @param array &$arr The array to update.
  96. */
  97. protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
  98. if ( $diff & $field ) {
  99. $arr[( $new & $field ) ? 0 : 1][] = $desc;
  100. }
  101. }
  102. /**
  103. * Gets an array of message keys describing the changes made to the
  104. * visibility of the revision.
  105. *
  106. * If the resulting array is $arr, then $arr[0] will contain an array of
  107. * keys describing the items that were hidden, $arr[1] will contain
  108. * an array of keys describing the items that were unhidden, and $arr[2]
  109. * will contain an array with a single message key, which can be one of
  110. * "revdelete-restricted", "revdelete-unrestricted" indicating (un)suppression
  111. * or null to indicate nothing in particular.
  112. * You can turn the keys in $arr[0] and $arr[1] into message keys by
  113. * appending -hid and -unhid to the keys respectively.
  114. *
  115. * @param int $n The new bitfield.
  116. * @param int $o The old bitfield.
  117. * @return array An array as described above.
  118. * @since 1.19 public
  119. */
  120. public static function getChanges( $n, $o ) {
  121. $diff = $n ^ $o;
  122. $ret = [ 0 => [], 1 => [], 2 => [] ];
  123. // Build bitfield changes in language
  124. self::checkItem( 'revdelete-content',
  125. Revision::DELETED_TEXT, $diff, $n, $ret );
  126. self::checkItem( 'revdelete-summary',
  127. Revision::DELETED_COMMENT, $diff, $n, $ret );
  128. self::checkItem( 'revdelete-uname',
  129. Revision::DELETED_USER, $diff, $n, $ret );
  130. // Restriction application to sysops
  131. if ( $diff & Revision::DELETED_RESTRICTED ) {
  132. if ( $n & Revision::DELETED_RESTRICTED ) {
  133. $ret[2][] = 'revdelete-restricted';
  134. } else {
  135. $ret[2][] = 'revdelete-unrestricted';
  136. }
  137. }
  138. return $ret;
  139. }
  140. /** Get DB field name for URL param...
  141. * Future code for other things may also track
  142. * other types of revision-specific changes.
  143. * @param string $typeName
  144. * @return string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
  145. */
  146. public static function getRelationType( $typeName ) {
  147. $typeName = self::getCanonicalTypeName( $typeName );
  148. if ( !$typeName ) {
  149. return null;
  150. }
  151. return call_user_func( [ self::$allowedTypes[$typeName], 'getRelationType' ] );
  152. }
  153. /**
  154. * Get the user right required for the RevDel type
  155. * @since 1.22
  156. * @param string $typeName
  157. * @return string User right
  158. */
  159. public static function getRestriction( $typeName ) {
  160. $typeName = self::getCanonicalTypeName( $typeName );
  161. if ( !$typeName ) {
  162. return null;
  163. }
  164. return call_user_func( [ self::$allowedTypes[$typeName], 'getRestriction' ] );
  165. }
  166. /**
  167. * Get the revision deletion constant for the RevDel type
  168. * @since 1.22
  169. * @param string $typeName
  170. * @return int RevDel constant
  171. */
  172. public static function getRevdelConstant( $typeName ) {
  173. $typeName = self::getCanonicalTypeName( $typeName );
  174. if ( !$typeName ) {
  175. return null;
  176. }
  177. return call_user_func( [ self::$allowedTypes[$typeName], 'getRevdelConstant' ] );
  178. }
  179. /**
  180. * Suggest a target for the revision deletion
  181. * @since 1.22
  182. * @param string $typeName
  183. * @param Title|null $target User-supplied target
  184. * @param array $ids
  185. * @return Title|null
  186. */
  187. public static function suggestTarget( $typeName, $target, array $ids ) {
  188. $typeName = self::getCanonicalTypeName( $typeName );
  189. if ( !$typeName ) {
  190. return $target;
  191. }
  192. return call_user_func( [ self::$allowedTypes[$typeName], 'suggestTarget' ], $target, $ids );
  193. }
  194. /**
  195. * Checks if a revision still exists in the revision table.
  196. * If it doesn't, returns the corresponding ar_timestamp field
  197. * so that this key can be used instead.
  198. *
  199. * @param Title $title
  200. * @param int $revid
  201. * @return bool|mixed
  202. */
  203. public static function checkRevisionExistence( $title, $revid ) {
  204. $dbr = wfGetDB( DB_REPLICA );
  205. $exists = $dbr->selectField( 'revision', '1',
  206. [ 'rev_id' => $revid ], __METHOD__ );
  207. if ( $exists ) {
  208. return true;
  209. }
  210. $timestamp = $dbr->selectField( 'archive', 'ar_timestamp',
  211. [ 'ar_namespace' => $title->getNamespace(),
  212. 'ar_title' => $title->getDBkey(),
  213. 'ar_rev_id' => $revid ], __METHOD__ );
  214. return $timestamp;
  215. }
  216. /**
  217. * Put together a rev_deleted bitfield
  218. * @since 1.22
  219. * @param array $bitPars ExtractBitParams() params
  220. * @param int $oldfield Current bitfield
  221. * @return int
  222. */
  223. public static function extractBitfield( array $bitPars, $oldfield ) {
  224. // Build the actual new rev_deleted bitfield
  225. $newBits = 0;
  226. foreach ( $bitPars as $const => $val ) {
  227. if ( $val == 1 ) {
  228. $newBits |= $const; // $const is the *_deleted const
  229. } elseif ( $val == -1 ) {
  230. $newBits |= ( $oldfield & $const ); // use existing
  231. }
  232. }
  233. return $newBits;
  234. }
  235. }