Merge "Migrate SpecialUndelete and Diff from tag_summary to change_tag"
[lhc/web/wiklou.git] / includes / Revision / SlotRoleRegistry.php
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\Linker\LinkTarget;
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 /**
51 * @var NameTableStore
52 */
53 private $roleNamesStore;
54
55 /**
56 * @var callable[]
57 */
58 private $instantiators = [];
59
60 /**
61 * @var SlotRoleHandler[]
62 */
63 private $handlers;
64
65 /**
66 * SlotRoleRegistry constructor.
67 *
68 * @param NameTableStore $roleNamesStore
69 */
70 public function __construct( NameTableStore $roleNamesStore ) {
71 $this->roleNamesStore = $roleNamesStore;
72 }
73
74 /**
75 * Defines a slot role.
76 *
77 * For use by extensions that wish to define roles beyond the main slot role.
78 *
79 * @see defineRoleWithModel()
80 *
81 * @param string $role The role name of the slot to define. This should follow the
82 * same convention as message keys:
83 * @param callable $instantiator called with $role as a parameter;
84 * Signature: function ( string $role ): SlotRoleHandler
85 */
86 public function defineRole( $role, callable $instantiator ) {
87 if ( $this->isDefinedRole( $role ) ) {
88 throw new LogicException( "Role $role is already defined" );
89 }
90
91 $this->instantiators[$role] = $instantiator;
92 }
93
94 /**
95 * Defines a slot role that allows only the given content model, and has no special
96 * behavior.
97 *
98 * For use by extensions that wish to define roles beyond the main slot role, but have
99 * no need to implement any special behavior for that slot.
100 *
101 * @see defineRole()
102 *
103 * @param string $role The role name of the slot to define, see defineRole()
104 * for more information.
105 * @param string $model A content model name, see ContentHandler
106 * @param array $layout See SlotRoleHandler getOutputLayoutHints
107 */
108 public function defineRoleWithModel( $role, $model, $layout = [] ) {
109 $this->defineRole(
110 $role,
111 function ( $role ) use ( $model, $layout ) {
112 return new SlotRoleHandler( $role, $model, $layout );
113 }
114 );
115 }
116
117 /**
118 * Gets the SlotRoleHandler that should be used when processing content of the given role.
119 *
120 * @param string $role
121 *
122 * @throws InvalidArgumentException If $role is not a known slot role.
123 * @return SlotRoleHandler The handler to be used for $role. This may be a
124 * FallbackSlotRoleHandler if the slot is "known" but not "defined".
125 */
126 public function getRoleHandler( $role ) {
127 if ( !isset( $this->handlers[$role] ) ) {
128 if ( !$this->isDefinedRole( $role ) ) {
129 if ( $this->isKnownRole( $role ) ) {
130 // The role has no handler defined, but is represented in the database.
131 // This may happen e.g. when the extension that defined the role was uninstalled.
132 wfWarn( __METHOD__ . ": known but undefined slot role $role" );
133 $this->handlers[$role] = new FallbackSlotRoleHandler( $role );
134 } else {
135 // The role doesn't have a handler defined, and is not represented in
136 // the database. Something must be quite wrong.
137 throw new InvalidArgumentException( "Unknown role $role" );
138 }
139 } else {
140 $handler = call_user_func( $this->instantiators[$role], $role );
141
142 Assert::postcondition(
143 $handler instanceof SlotRoleHandler,
144 "Instantiator for $role role must return a SlotRoleHandler"
145 );
146
147 $this->handlers[$role] = $handler;
148 }
149 }
150
151 return $this->handlers[$role];
152 }
153
154 /**
155 * Returns the list of roles allowed when creating a new revision on the given page.
156 * The choice should not depend on external state, such as the page content.
157 * Note that existing revisions of that page are not guaranteed to comply with this list.
158 *
159 * All implementations of this method are required to return at least all "required" roles.
160 *
161 * @param LinkTarget $title
162 *
163 * @return string[]
164 */
165 public function getAllowedRoles( LinkTarget $title ) {
166 // TODO: allow this to be overwritten per namespace (or page type)
167 // TODO: decide how to control which slots are offered for editing per default (T209927)
168 return $this->getDefinedRoles();
169 }
170
171 /**
172 * Returns the list of roles required when creating a new revision on the given page.
173 * The should not depend on external state, such as the page content.
174 * Note that existing revisions of that page are not guaranteed to comply with this list.
175 *
176 * All required roles are implicitly considered "allowed", so any roles
177 * returned by this method will also be returned by getAllowedRoles().
178 *
179 * @param LinkTarget $title
180 *
181 * @return string[]
182 */
183 public function getRequiredRoles( LinkTarget $title ) {
184 // TODO: allow this to be overwritten per namespace (or page type)
185 return [ 'main' ];
186 }
187
188 /**
189 * Returns the list of roles defined by calling defineRole().
190 *
191 * This list should be used when enumerating slot roles that can be used for editing.
192 *
193 * @return string[]
194 */
195 public function getDefinedRoles() {
196 return array_keys( $this->instantiators );
197 }
198
199 /**
200 * Returns the list of known roles, including the ones returned by getDefinedRoles(),
201 * and roles that exist according to the NameTableStore provided to the constructor.
202 *
203 * This list should be used when enumerating slot roles that can be used in queries or
204 * for display.
205 *
206 * @return string[]
207 */
208 public function getKnownRoles() {
209 return array_unique( array_merge(
210 $this->getDefinedRoles(),
211 $this->roleNamesStore->getMap()
212 ) );
213 }
214
215 /**
216 * Whether the given role is defined, that is, it was defined by calling defineRole().
217 *
218 * @param string $role
219 * @return bool
220 */
221 public function isDefinedRole( $role ) {
222 return in_array( $role, $this->getDefinedRoles(), true );
223 }
224
225 /**
226 * Whether the given role is known, that is, it's either defined or exist according to
227 * the NameTableStore provided to the constructor.
228 *
229 * @param string $role
230 * @return bool
231 */
232 public function isKnownRole( $role ) {
233 return in_array( $role, $this->getKnownRoles(), true );
234 }
235
236 }