Autopromote.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. /**
  24. * This class checks if user can get extra rights
  25. * because of conditions specified in $wgAutopromote
  26. */
  27. class Autopromote {
  28. /**
  29. * Get the groups for the given user based on $wgAutopromote.
  30. *
  31. * @param User $user The user to get the groups for
  32. * @return array Array of groups to promote to.
  33. */
  34. public static function getAutopromoteGroups( User $user ) {
  35. global $wgAutopromote;
  36. $promote = [];
  37. foreach ( $wgAutopromote as $group => $cond ) {
  38. if ( self::recCheckCondition( $cond, $user ) ) {
  39. $promote[] = $group;
  40. }
  41. }
  42. Hooks::run( 'GetAutoPromoteGroups', [ $user, &$promote ] );
  43. return $promote;
  44. }
  45. /**
  46. * Get the groups for the given user based on the given criteria.
  47. *
  48. * Does not return groups the user already belongs to or has once belonged.
  49. *
  50. * @param User $user The user to get the groups for
  51. * @param string $event Key in $wgAutopromoteOnce (each one has groups/criteria)
  52. *
  53. * @return array Groups the user should be promoted to.
  54. *
  55. * @see $wgAutopromoteOnce
  56. */
  57. public static function getAutopromoteOnceGroups( User $user, $event ) {
  58. global $wgAutopromoteOnce;
  59. $promote = [];
  60. if ( isset( $wgAutopromoteOnce[$event] ) && count( $wgAutopromoteOnce[$event] ) ) {
  61. $currentGroups = $user->getGroups();
  62. $formerGroups = $user->getFormerGroups();
  63. foreach ( $wgAutopromoteOnce[$event] as $group => $cond ) {
  64. // Do not check if the user's already a member
  65. if ( in_array( $group, $currentGroups ) ) {
  66. continue;
  67. }
  68. // Do not autopromote if the user has belonged to the group
  69. if ( in_array( $group, $formerGroups ) ) {
  70. continue;
  71. }
  72. // Finally - check the conditions
  73. if ( self::recCheckCondition( $cond, $user ) ) {
  74. $promote[] = $group;
  75. }
  76. }
  77. }
  78. return $promote;
  79. }
  80. /**
  81. * Recursively check a condition. Conditions are in the form
  82. * array( '&' or '|' or '^' or '!', cond1, cond2, ... )
  83. * where cond1, cond2, ... are themselves conditions; *OR*
  84. * APCOND_EMAILCONFIRMED, *OR*
  85. * array( APCOND_EMAILCONFIRMED ), *OR*
  86. * array( APCOND_EDITCOUNT, number of edits ), *OR*
  87. * array( APCOND_AGE, seconds since registration ), *OR*
  88. * similar constructs defined by extensions.
  89. * This function evaluates the former type recursively, and passes off to
  90. * self::checkCondition for evaluation of the latter type.
  91. *
  92. * @param mixed $cond A condition, possibly containing other conditions
  93. * @param User $user The user to check the conditions against
  94. * @return bool Whether the condition is true
  95. */
  96. private static function recCheckCondition( $cond, User $user ) {
  97. $validOps = [ '&', '|', '^', '!' ];
  98. if ( is_array( $cond ) && count( $cond ) >= 2 && in_array( $cond[0], $validOps ) ) {
  99. # Recursive condition
  100. if ( $cond[0] == '&' ) { // AND (all conds pass)
  101. foreach ( array_slice( $cond, 1 ) as $subcond ) {
  102. if ( !self::recCheckCondition( $subcond, $user ) ) {
  103. return false;
  104. }
  105. }
  106. return true;
  107. } elseif ( $cond[0] == '|' ) { // OR (at least one cond passes)
  108. foreach ( array_slice( $cond, 1 ) as $subcond ) {
  109. if ( self::recCheckCondition( $subcond, $user ) ) {
  110. return true;
  111. }
  112. }
  113. return false;
  114. } elseif ( $cond[0] == '^' ) { // XOR (exactly one cond passes)
  115. if ( count( $cond ) > 3 ) {
  116. wfWarn( 'recCheckCondition() given XOR ("^") condition on three or more conditions.' .
  117. ' Check your $wgAutopromote and $wgAutopromoteOnce settings.' );
  118. }
  119. return self::recCheckCondition( $cond[1], $user )
  120. xor self::recCheckCondition( $cond[2], $user );
  121. } elseif ( $cond[0] == '!' ) { // NOT (no conds pass)
  122. foreach ( array_slice( $cond, 1 ) as $subcond ) {
  123. if ( self::recCheckCondition( $subcond, $user ) ) {
  124. return false;
  125. }
  126. }
  127. return true;
  128. }
  129. }
  130. // If we got here, the array presumably does not contain other conditions;
  131. // it's not recursive. Pass it off to self::checkCondition.
  132. if ( !is_array( $cond ) ) {
  133. $cond = [ $cond ];
  134. }
  135. return self::checkCondition( $cond, $user );
  136. }
  137. /**
  138. * As recCheckCondition, but *not* recursive. The only valid conditions
  139. * are those whose first element is APCOND_EMAILCONFIRMED/APCOND_EDITCOUNT/
  140. * APCOND_AGE. Other types will throw an exception if no extension evaluates them.
  141. *
  142. * @param array $cond A condition, which must not contain other conditions
  143. * @param User $user The user to check the condition against
  144. * @throws MWException
  145. * @return bool Whether the condition is true for the user
  146. */
  147. private static function checkCondition( $cond, User $user ) {
  148. global $wgEmailAuthentication;
  149. if ( count( $cond ) < 1 ) {
  150. return false;
  151. }
  152. switch ( $cond[0] ) {
  153. case APCOND_EMAILCONFIRMED:
  154. if ( Sanitizer::validateEmail( $user->getEmail() ) ) {
  155. if ( $wgEmailAuthentication ) {
  156. return (bool)$user->getEmailAuthenticationTimestamp();
  157. } else {
  158. return true;
  159. }
  160. }
  161. return false;
  162. case APCOND_EDITCOUNT:
  163. $reqEditCount = $cond[1];
  164. // T157718: Avoid edit count lookup if specified edit count is 0 or invalid
  165. if ( $reqEditCount <= 0 ) {
  166. return true;
  167. }
  168. return $user->getEditCount() >= $reqEditCount;
  169. case APCOND_AGE:
  170. $age = time() - wfTimestampOrNull( TS_UNIX, $user->getRegistration() );
  171. return $age >= $cond[1];
  172. case APCOND_AGE_FROM_EDIT:
  173. $age = time() - wfTimestampOrNull( TS_UNIX, $user->getFirstEditTimestamp() );
  174. return $age >= $cond[1];
  175. case APCOND_INGROUPS:
  176. $groups = array_slice( $cond, 1 );
  177. return count( array_intersect( $groups, $user->getGroups() ) ) == count( $groups );
  178. case APCOND_ISIP:
  179. return $cond[1] == $user->getRequest()->getIP();
  180. case APCOND_IPINRANGE:
  181. return IP::isInRange( $user->getRequest()->getIP(), $cond[1] );
  182. case APCOND_BLOCKED:
  183. return $user->isBlocked();
  184. case APCOND_ISBOT:
  185. return in_array( 'bot', User::getGroupPermissions( $user->getGroups() ) );
  186. default:
  187. $result = null;
  188. Hooks::run( 'AutopromoteCondition', [ $cond[0],
  189. array_slice( $cond, 1 ), $user, &$result ] );
  190. if ( $result === null ) {
  191. throw new MWException( "Unrecognized condition {$cond[0]} for autopromotion!" );
  192. }
  193. return (bool)$result;
  194. }
  195. }
  196. }