Autopromote.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. /**
  3. * Automatic user rights promotion based on conditions specified
  4. * in $wgAutopromote.
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program; if not, write to the Free Software Foundation, Inc.,
  18. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. * http://www.gnu.org/copyleft/gpl.html
  20. *
  21. * @file
  22. */
  23. use MediaWiki\MediaWikiServices;
  24. /**
  25. * This class checks if user can get extra rights
  26. * because of conditions specified in $wgAutopromote
  27. */
  28. class Autopromote {
  29. /**
  30. * Get the groups for the given user based on $wgAutopromote.
  31. *
  32. * @param User $user The user to get the groups for
  33. * @return array Array of groups to promote to.
  34. */
  35. public static function getAutopromoteGroups( User $user ) {
  36. global $wgAutopromote;
  37. $promote = [];
  38. foreach ( $wgAutopromote as $group => $cond ) {
  39. if ( self::recCheckCondition( $cond, $user ) ) {
  40. $promote[] = $group;
  41. }
  42. }
  43. Hooks::run( 'GetAutoPromoteGroups', [ $user, &$promote ] );
  44. return $promote;
  45. }
  46. /**
  47. * Get the groups for the given user based on the given criteria.
  48. *
  49. * Does not return groups the user already belongs to or has once belonged.
  50. *
  51. * @param User $user The user to get the groups for
  52. * @param string $event Key in $wgAutopromoteOnce (each one has groups/criteria)
  53. *
  54. * @return array Groups the user should be promoted to.
  55. *
  56. * @see $wgAutopromoteOnce
  57. */
  58. public static function getAutopromoteOnceGroups( User $user, $event ) {
  59. global $wgAutopromoteOnce;
  60. $promote = [];
  61. if ( isset( $wgAutopromoteOnce[$event] ) && count( $wgAutopromoteOnce[$event] ) ) {
  62. $currentGroups = $user->getGroups();
  63. $formerGroups = $user->getFormerGroups();
  64. foreach ( $wgAutopromoteOnce[$event] as $group => $cond ) {
  65. // Do not check if the user's already a member
  66. if ( in_array( $group, $currentGroups ) ) {
  67. continue;
  68. }
  69. // Do not autopromote if the user has belonged to the group
  70. if ( in_array( $group, $formerGroups ) ) {
  71. continue;
  72. }
  73. // Finally - check the conditions
  74. if ( self::recCheckCondition( $cond, $user ) ) {
  75. $promote[] = $group;
  76. }
  77. }
  78. }
  79. return $promote;
  80. }
  81. /**
  82. * Recursively check a condition. Conditions are in the form
  83. * [ '&' or '|' or '^' or '!', cond1, cond2, ... ]
  84. * where cond1, cond2, ... are themselves conditions; *OR*
  85. * APCOND_EMAILCONFIRMED, *OR*
  86. * [ APCOND_EMAILCONFIRMED ], *OR*
  87. * [ APCOND_EDITCOUNT, number of edits ], *OR*
  88. * [ APCOND_AGE, seconds since registration ], *OR*
  89. * similar constructs defined by extensions.
  90. * This function evaluates the former type recursively, and passes off to
  91. * self::checkCondition for evaluation of the latter type.
  92. *
  93. * @param mixed $cond A condition, possibly containing other conditions
  94. * @param User $user The user to check the conditions against
  95. * @return bool Whether the condition is true
  96. */
  97. private static function recCheckCondition( $cond, User $user ) {
  98. $validOps = [ '&', '|', '^', '!' ];
  99. if ( is_array( $cond ) && count( $cond ) >= 2 && in_array( $cond[0], $validOps ) ) {
  100. # Recursive condition
  101. if ( $cond[0] == '&' ) { // AND (all conds pass)
  102. foreach ( array_slice( $cond, 1 ) as $subcond ) {
  103. if ( !self::recCheckCondition( $subcond, $user ) ) {
  104. return false;
  105. }
  106. }
  107. return true;
  108. } elseif ( $cond[0] == '|' ) { // OR (at least one cond passes)
  109. foreach ( array_slice( $cond, 1 ) as $subcond ) {
  110. if ( self::recCheckCondition( $subcond, $user ) ) {
  111. return true;
  112. }
  113. }
  114. return false;
  115. } elseif ( $cond[0] == '^' ) { // XOR (exactly one cond passes)
  116. if ( count( $cond ) > 3 ) {
  117. wfWarn( 'recCheckCondition() given XOR ("^") condition on three or more conditions.' .
  118. ' Check your $wgAutopromote and $wgAutopromoteOnce settings.' );
  119. }
  120. return self::recCheckCondition( $cond[1], $user )
  121. xor self::recCheckCondition( $cond[2], $user );
  122. } elseif ( $cond[0] == '!' ) { // NOT (no conds pass)
  123. foreach ( array_slice( $cond, 1 ) as $subcond ) {
  124. if ( self::recCheckCondition( $subcond, $user ) ) {
  125. return false;
  126. }
  127. }
  128. return true;
  129. }
  130. }
  131. // If we got here, the array presumably does not contain other conditions;
  132. // it's not recursive. Pass it off to self::checkCondition.
  133. if ( !is_array( $cond ) ) {
  134. $cond = [ $cond ];
  135. }
  136. return self::checkCondition( $cond, $user );
  137. }
  138. /**
  139. * As recCheckCondition, but *not* recursive. The only valid conditions
  140. * are those whose first element is APCOND_EMAILCONFIRMED/APCOND_EDITCOUNT/
  141. * APCOND_AGE. Other types will throw an exception if no extension evaluates them.
  142. *
  143. * @param array $cond A condition, which must not contain other conditions
  144. * @param User $user The user to check the condition against
  145. * @throws MWException
  146. * @return bool Whether the condition is true for the user
  147. */
  148. private static function checkCondition( $cond, User $user ) {
  149. global $wgEmailAuthentication;
  150. if ( count( $cond ) < 1 ) {
  151. return false;
  152. }
  153. switch ( $cond[0] ) {
  154. case APCOND_EMAILCONFIRMED:
  155. if ( Sanitizer::validateEmail( $user->getEmail() ) ) {
  156. if ( $wgEmailAuthentication ) {
  157. return (bool)$user->getEmailAuthenticationTimestamp();
  158. } else {
  159. return true;
  160. }
  161. }
  162. return false;
  163. case APCOND_EDITCOUNT:
  164. $reqEditCount = $cond[1];
  165. // T157718: Avoid edit count lookup if specified edit count is 0 or invalid
  166. if ( $reqEditCount <= 0 ) {
  167. return true;
  168. }
  169. return $user->getEditCount() >= $reqEditCount;
  170. case APCOND_AGE:
  171. $age = time() - (int)wfTimestampOrNull( TS_UNIX, $user->getRegistration() );
  172. return $age >= $cond[1];
  173. case APCOND_AGE_FROM_EDIT:
  174. $age = time() - (int)wfTimestampOrNull( TS_UNIX, $user->getFirstEditTimestamp() );
  175. return $age >= $cond[1];
  176. case APCOND_INGROUPS:
  177. $groups = array_slice( $cond, 1 );
  178. return count( array_intersect( $groups, $user->getGroups() ) ) == count( $groups );
  179. case APCOND_ISIP:
  180. return $cond[1] == $user->getRequest()->getIP();
  181. case APCOND_IPINRANGE:
  182. return IP::isInRange( $user->getRequest()->getIP(), $cond[1] );
  183. case APCOND_BLOCKED:
  184. return $user->getBlock() && $user->getBlock()->isSitewide();
  185. case APCOND_ISBOT:
  186. return in_array( 'bot', MediaWikiServices::getInstance()
  187. ->getPermissionManager()
  188. ->getGroupPermissions( $user->getGroups() ) );
  189. default:
  190. $result = null;
  191. Hooks::run( 'AutopromoteCondition', [ $cond[0],
  192. array_slice( $cond, 1 ), $user, &$result ] );
  193. if ( $result === null ) {
  194. throw new MWException( "Unrecognized condition {$cond[0]} for autopromotion!" );
  195. }
  196. return (bool)$result;
  197. }
  198. }
  199. }