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