Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
36 / 36 |
|
100.00% |
10 / 10 |
CRAP | |
100.00% |
1 / 1 |
SlotRoleRegistry | |
100.00% |
36 / 36 |
|
100.00% |
10 / 10 |
14 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
defineRole | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
defineRoleWithModel | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getRoleHandler | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
4 | |||
getAllowedRoles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRequiredRoles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDefinedRoles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getKnownRoles | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
isDefinedRole | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
isKnownRole | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * This file is part of MediaWiki. |
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 | */ |
22 | |
23 | namespace MediaWiki\Revision; |
24 | |
25 | use InvalidArgumentException; |
26 | use LogicException; |
27 | use MediaWiki\Page\PageIdentity; |
28 | use MediaWiki\Storage\NameTableStore; |
29 | use Wikimedia\Assert\Assert; |
30 | |
31 | /** |
32 | * A registry service for SlotRoleHandlers, used to define which slot roles are available on |
33 | * which page. |
34 | * |
35 | * Extensions may use the SlotRoleRegistry to register the slots they define. |
36 | * |
37 | * In the context of the SlotRoleRegistry, it is useful to distinguish between "defined" and "known" |
38 | * slot roles: A slot role is "defined" if defineRole() or defineRoleWithModel() was called for |
39 | * that role. A slot role is "known" if the NameTableStore provided to the constructor as the |
40 | * $roleNamesStore parameter has an ID associated with that role, which essentially means that |
41 | * the role at some point has been used on the wiki. Roles that are not "defined" but are |
42 | * "known" typically belong to extensions that used to be installed on the wiki, but no longer are. |
43 | * Such slots should be considered ok for display and administrative operations, but only "defined" |
44 | * slots should be supported for editing. |
45 | * |
46 | * @since 1.33 |
47 | */ |
48 | class SlotRoleRegistry { |
49 | |
50 | private NameTableStore $roleNamesStore; |
51 | /** @var array<string,callable> */ |
52 | private array $instantiators = []; |
53 | /** @var array<string,SlotRoleHandler> */ |
54 | private array $handlers = []; |
55 | |
56 | public function __construct( NameTableStore $roleNamesStore ) { |
57 | $this->roleNamesStore = $roleNamesStore; |
58 | } |
59 | |
60 | /** |
61 | * Defines a slot role. |
62 | * |
63 | * For use by extensions that wish to define roles beyond the main slot role. |
64 | * |
65 | * @see defineRoleWithModel() |
66 | * |
67 | * @param string $role The role name of the slot to define. This should follow the |
68 | * same convention as message keys: |
69 | * @param callable $instantiator called with $role as a parameter; |
70 | * Signature: function ( string $role ): SlotRoleHandler |
71 | */ |
72 | public function defineRole( string $role, callable $instantiator ): void { |
73 | $role = strtolower( $role ); |
74 | |
75 | if ( isset( $this->instantiators[$role] ) ) { |
76 | throw new LogicException( "Role $role is already defined" ); |
77 | } |
78 | |
79 | $this->instantiators[$role] = $instantiator; |
80 | } |
81 | |
82 | /** |
83 | * Defines a slot role that allows only the given content model, and has no special |
84 | * behavior. |
85 | * |
86 | * For use by extensions that wish to define roles beyond the main slot role, but have |
87 | * no need to implement any special behavior for that slot. |
88 | * |
89 | * @see defineRole() |
90 | * |
91 | * @param string $role The role name of the slot to define, see defineRole() |
92 | * for more information. |
93 | * @param string $model A content model name, see ContentHandler |
94 | * @param array $layout See SlotRoleHandler getOutputLayoutHints |
95 | * @param bool $derived see SlotRoleHandler constructor |
96 | * @since 1.36 optional $derived parameter added |
97 | */ |
98 | public function defineRoleWithModel( |
99 | string $role, |
100 | string $model, |
101 | array $layout = [], |
102 | bool $derived = false |
103 | ): void { |
104 | $this->defineRole( |
105 | $role, |
106 | static function ( $role ) use ( $model, $layout, $derived ) { |
107 | return new SlotRoleHandler( $role, $model, $layout, $derived ); |
108 | } |
109 | ); |
110 | } |
111 | |
112 | /** |
113 | * Gets the SlotRoleHandler that should be used when processing content of the given role. |
114 | * |
115 | * @param string $role |
116 | * |
117 | * @throws InvalidArgumentException If $role is not a known slot role. |
118 | * @return SlotRoleHandler The handler to be used for $role. This may be a |
119 | * FallbackSlotRoleHandler if the slot is "known" but not "defined". |
120 | */ |
121 | public function getRoleHandler( string $role ): SlotRoleHandler { |
122 | $role = strtolower( $role ); |
123 | |
124 | if ( !isset( $this->handlers[$role] ) ) { |
125 | if ( !isset( $this->instantiators[$role] ) ) { |
126 | if ( $this->isKnownRole( $role ) ) { |
127 | // The role has no handler defined, but is represented in the database. |
128 | // This may happen e.g. when the extension that defined the role was uninstalled. |
129 | wfWarn( __METHOD__ . ": known but undefined slot role $role" ); |
130 | $this->handlers[$role] = new FallbackSlotRoleHandler( $role ); |
131 | } else { |
132 | // The role doesn't have a handler defined, and is not represented in |
133 | // the database. Something must be quite wrong. |
134 | throw new InvalidArgumentException( "Unknown role $role" ); |
135 | } |
136 | } else { |
137 | $handler = call_user_func( $this->instantiators[$role], $role ); |
138 | |
139 | Assert::postcondition( |
140 | $handler instanceof SlotRoleHandler, |
141 | "Instantiator for $role role must return a SlotRoleHandler" |
142 | ); |
143 | |
144 | $this->handlers[$role] = $handler; |
145 | } |
146 | } |
147 | |
148 | return $this->handlers[$role]; |
149 | } |
150 | |
151 | /** |
152 | * Returns the list of roles allowed when creating a new revision on the given page. |
153 | * The choice should not depend on external state, such as the page content. |
154 | * Note that existing revisions of that page are not guaranteed to comply with this list. |
155 | * |
156 | * All implementations of this method are required to return at least all "required" roles. |
157 | * |
158 | * @param PageIdentity $page |
159 | * |
160 | * @return string[] |
161 | */ |
162 | public function getAllowedRoles( PageIdentity $page ): array { |
163 | // TODO: allow this to be overwritten per namespace (or page type) |
164 | // TODO: decide how to control which slots are offered for editing per default (T209927) |
165 | return $this->getDefinedRoles(); |
166 | } |
167 | |
168 | /** |
169 | * Returns the list of roles required when creating a new revision on the given page. |
170 | * The should not depend on external state, such as the page content. |
171 | * Note that existing revisions of that page are not guaranteed to comply with this list. |
172 | * |
173 | * All required roles are implicitly considered "allowed", so any roles |
174 | * returned by this method will also be returned by getAllowedRoles(). |
175 | * |
176 | * @param PageIdentity $page |
177 | * |
178 | * @return string[] |
179 | */ |
180 | public function getRequiredRoles( PageIdentity $page ): array { |
181 | // TODO: allow this to be overwritten per namespace (or page type) |
182 | return [ SlotRecord::MAIN ]; |
183 | } |
184 | |
185 | /** |
186 | * Returns the list of roles defined by calling defineRole(). |
187 | * |
188 | * This list should be used when enumerating slot roles that can be used for editing. |
189 | * |
190 | * @return string[] |
191 | */ |
192 | public function getDefinedRoles(): array { |
193 | return array_keys( $this->instantiators ); |
194 | } |
195 | |
196 | /** |
197 | * Returns the list of known roles, including the ones returned by getDefinedRoles(), |
198 | * and roles that exist according to the NameTableStore provided to the constructor. |
199 | * |
200 | * This list should be used when enumerating slot roles that can be used in queries or |
201 | * for display. |
202 | * |
203 | * @return string[] |
204 | */ |
205 | public function getKnownRoles(): array { |
206 | return array_unique( array_merge( |
207 | $this->getDefinedRoles(), |
208 | $this->roleNamesStore->getMap() |
209 | ) ); |
210 | } |
211 | |
212 | /** |
213 | * Whether the given role is defined, that is, it was defined by calling defineRole(). |
214 | */ |
215 | public function isDefinedRole( string $role ): bool { |
216 | $role = strtolower( $role ); |
217 | return isset( $this->instantiators[$role] ); |
218 | } |
219 | |
220 | /** |
221 | * Whether the given role is known, that is, it's either defined or exist according to |
222 | * the NameTableStore provided to the constructor. |
223 | */ |
224 | public function isKnownRole( string $role ): bool { |
225 | $role = strtolower( $role ); |
226 | return in_array( $role, $this->getKnownRoles(), true ); |
227 | } |
228 | |
229 | } |