SlotRoleRegistry.php 7.5 KB

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