Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
75.00% |
36 / 48 |
|
50.00% |
3 / 6 |
CRAP | |
0.00% |
0 / 1 |
ConditionalDefaultsLookup | |
75.00% |
36 / 48 |
|
50.00% |
3 / 6 |
26.25 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
hasConditionalDefault | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getConditionallyDefaultOptions | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getOptionDefaultForUser | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
checkConditionsForUser | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
checkConditionForUser | |
87.50% |
21 / 24 |
|
0.00% |
0 / 1 |
11.24 |
1 | <?php |
2 | |
3 | namespace MediaWiki\User\Options; |
4 | |
5 | use InvalidArgumentException; |
6 | use MediaWiki\Config\ServiceOptions; |
7 | use MediaWiki\MainConfigNames; |
8 | use MediaWiki\User\Registration\UserRegistrationLookup; |
9 | use MediaWiki\User\UserGroupManager; |
10 | use MediaWiki\User\UserIdentity; |
11 | use MediaWiki\User\UserIdentityUtils; |
12 | use Wikimedia\Timestamp\ConvertibleTimestamp; |
13 | |
14 | class ConditionalDefaultsLookup { |
15 | |
16 | /** |
17 | * @internal Exposed for ServiceWiring only |
18 | */ |
19 | public const CONSTRUCTOR_OPTIONS = [ |
20 | MainConfigNames::ConditionalUserOptions, |
21 | ]; |
22 | |
23 | private ServiceOptions $options; |
24 | private UserRegistrationLookup $userRegistrationLookup; |
25 | private UserIdentityUtils $userIdentityUtils; |
26 | /** |
27 | * UserGroupManager must be provided as a callback function to avoid circular dependency |
28 | * @var callable |
29 | */ |
30 | private $userGroupManagerCallback; |
31 | private array $extraConditions; |
32 | |
33 | public function __construct( |
34 | ServiceOptions $options, |
35 | UserRegistrationLookup $userRegistrationLookup, |
36 | UserIdentityUtils $userIdentityUtils, |
37 | callable $userGroupManagerCallback, |
38 | array $extraConditions = [] |
39 | ) { |
40 | $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); |
41 | |
42 | $this->options = $options; |
43 | $this->userRegistrationLookup = $userRegistrationLookup; |
44 | $this->userIdentityUtils = $userIdentityUtils; |
45 | $this->userGroupManagerCallback = $userGroupManagerCallback; |
46 | $this->extraConditions = $extraConditions; |
47 | } |
48 | |
49 | /** |
50 | * Does the option support conditional defaults? |
51 | * |
52 | * @param string $option |
53 | * @return bool |
54 | */ |
55 | public function hasConditionalDefault( string $option ): bool { |
56 | return array_key_exists( |
57 | $option, |
58 | $this->options->get( MainConfigNames::ConditionalUserOptions ) |
59 | ); |
60 | } |
61 | |
62 | /** |
63 | * Get all conditionally default user options |
64 | * |
65 | * @return string[] |
66 | */ |
67 | public function getConditionallyDefaultOptions(): array { |
68 | return array_keys( |
69 | $this->options->get( MainConfigNames::ConditionalUserOptions ) |
70 | ); |
71 | } |
72 | |
73 | /** |
74 | * Get the conditional default for user and option |
75 | * |
76 | * @param string $optionName |
77 | * @param UserIdentity $userIdentity |
78 | * @return mixed|null The default value if set, or null if it cannot be determined |
79 | * conditionally (default from DefaultOptionsLookup should be used in that case). |
80 | */ |
81 | public function getOptionDefaultForUser( |
82 | string $optionName, |
83 | UserIdentity $userIdentity |
84 | ) { |
85 | $conditionalDefaults = $this->options |
86 | ->get( MainConfigNames::ConditionalUserOptions )[$optionName] ?? []; |
87 | foreach ( $conditionalDefaults as $conditionalDefault ) { |
88 | // At the zeroth index of the conditional case, the intended value is found; the rest |
89 | // of the array are conditions, which are evaluated in checkConditionsForUser(). |
90 | $value = array_shift( $conditionalDefault ); |
91 | if ( $this->checkConditionsForUser( $userIdentity, $conditionalDefault ) ) { |
92 | return $value; |
93 | } |
94 | } |
95 | |
96 | return null; |
97 | } |
98 | |
99 | /** |
100 | * Are ALL conditions satisfied for the given user? |
101 | * |
102 | * @param UserIdentity $userIdentity |
103 | * @param array $conditions |
104 | * @return bool |
105 | */ |
106 | private function checkConditionsForUser( UserIdentity $userIdentity, array $conditions ): bool { |
107 | foreach ( $conditions as $condition ) { |
108 | if ( !$this->checkConditionForUser( $userIdentity, $condition ) ) { |
109 | return false; |
110 | } |
111 | } |
112 | return true; |
113 | } |
114 | |
115 | /** |
116 | * Is ONE condition satisfied for the given user? |
117 | * |
118 | * @param UserIdentity $userIdentity |
119 | * @param array|int $cond Either [ CUDCOND_*, args ] or CUDCOND_*, depending on whether the |
120 | * condition has any arguments. |
121 | * @return bool |
122 | */ |
123 | private function checkConditionForUser( |
124 | UserIdentity $userIdentity, |
125 | $cond |
126 | ): bool { |
127 | if ( !is_array( $cond ) ) { |
128 | $cond = [ $cond ]; |
129 | } |
130 | if ( $cond === [] ) { |
131 | throw new InvalidArgumentException( 'Empty condition' ); |
132 | } |
133 | $condName = array_shift( $cond ); |
134 | switch ( $condName ) { |
135 | case CUDCOND_AFTER: |
136 | $registration = $this->userRegistrationLookup->getRegistration( $userIdentity ); |
137 | if ( $registration === null || $registration === false ) { |
138 | return false; |
139 | } |
140 | |
141 | return ( |
142 | (int)ConvertibleTimestamp::convert( TS_UNIX, $registration ) - |
143 | (int)ConvertibleTimestamp::convert( TS_UNIX, $cond[0] ) |
144 | ) > 0; |
145 | case CUDCOND_ANON: |
146 | return !$userIdentity->isRegistered(); |
147 | case CUDCOND_NAMED: |
148 | return $this->userIdentityUtils->isNamed( $userIdentity ); |
149 | case CUDCOND_USERGROUP: |
150 | $userGroupManagerCallback = $this->userGroupManagerCallback; |
151 | /** @var UserGroupManager */ |
152 | $userGroupManager = $userGroupManagerCallback(); |
153 | return in_array( $cond[0], $userGroupManager->getUserEffectiveGroups( $userIdentity ) ); |
154 | default: |
155 | if ( array_key_exists( $condName, $this->extraConditions ) ) { |
156 | return call_user_func( $this->extraConditions[$condName], $userIdentity, $cond ); |
157 | } |
158 | throw new InvalidArgumentException( 'Unsupported condition ' . $condName ); |
159 | } |
160 | } |
161 | } |