MediaWiki  1.29.1
WikiEditor.hooks.php
Go to the documentation of this file.
1 <?php
10  // ID used for grouping entries all of a session's entries together in
11  // EventLogging.
12  private static $statsId = false;
13 
14  /* Protected Static Members */
15 
16  protected static $features = [
17 
18  /* Toolbar Features */
19 
20  'toolbar' => [
21  'preferences' => [
22  // Ideally this key would be 'wikieditor-toolbar'
23  'usebetatoolbar' => [
24  'type' => 'toggle',
25  'label-message' => 'wikieditor-toolbar-preference',
26  'section' => 'editing/editor',
27  ],
28  ],
29  'requirements' => [
30  'usebetatoolbar' => true,
31  ],
32  'modules' => [
33  'ext.wikiEditor.toolbar',
34  ],
35  'stylemodules' => [
36  'ext.wikiEditor.toolbar.styles',
37  ],
38  ],
39  'dialogs' => [
40  'preferences' => [
41  // Ideally this key would be 'wikieditor-toolbar-dialogs'
42  'usebetatoolbar-cgd' => [
43  'type' => 'toggle',
44  'label-message' => 'wikieditor-toolbar-dialogs-preference',
45  'section' => 'editing/editor',
46  ],
47  ],
48  'requirements' => [
49  'usebetatoolbar-cgd' => true,
50  'usebetatoolbar' => true,
51  ],
52  'modules' => [
53  'ext.wikiEditor.dialogs',
54  ],
55  ],
56 
57  /* Labs Features */
58 
59  'preview' => [
60  'preferences' => [
61  'wikieditor-preview' => [
62  'type' => 'toggle',
63  'label-message' => 'wikieditor-preview-preference',
64  'section' => 'editing/labs',
65  ],
66  ],
67  'requirements' => [
68  'wikieditor-preview' => true,
69  ],
70  'modules' => [
71  'ext.wikiEditor.preview',
72  ],
73  ],
74  'publish' => [
75  'preferences' => [
76  'wikieditor-publish' => [
77  'type' => 'toggle',
78  'label-message' => 'wikieditor-publish-preference',
79  'section' => 'editing/labs',
80  ],
81  ],
82  'requirements' => [
83  'wikieditor-publish' => true,
84  ],
85  'modules' => [
86  'ext.wikiEditor.publish',
87  ],
88  ]
89  ];
90 
91  /* Static Methods */
92 
102  public static function isEnabled( $name ) {
103  global $wgWikiEditorFeatures, $wgUser;
104 
105  // Features with global set to true are always enabled
106  if ( !isset( $wgWikiEditorFeatures[$name] ) || $wgWikiEditorFeatures[$name]['global'] ) {
107  return true;
108  }
109  // Features with user preference control can have any number of preferences
110  // to be specific values to be enabled
111  if ( $wgWikiEditorFeatures[$name]['user'] ) {
112  if ( isset( self::$features[$name]['requirements'] ) ) {
113  foreach ( self::$features[$name]['requirements'] as $requirement => $value ) {
114  // Important! We really do want fuzzy evaluation here
115  if ( $wgUser->getOption( $requirement ) != $value ) {
116  return false;
117  }
118  }
119  }
120  return true;
121  }
122  // Features controlled by $wgWikiEditorFeatures with both global and user
123  // set to false are always disabled
124  return false;
125  }
126 
136  public static function doEventLogging( $action, $article, $data = [] ) {
138  if ( !class_exists( 'EventLogging' ) ) {
139  return false;
140  }
141  // Sample 6.25% (via hex digit)
142  if ( $data['editingSessionId'][0] > '0' ) {
143  return false;
144  }
145 
146  $user = $article->getContext()->getUser();
147  $page = $article->getPage();
148  $title = $article->getTitle();
149 
150  $data = [
151  'action' => $action,
152  'version' => 1,
153  'editor' => 'wikitext',
154  'platform' => 'desktop', // FIXME
155  'integration' => 'page',
156  'page.id' => $page->getId(),
157  'page.title' => $title->getPrefixedText(),
158  'page.ns' => $title->getNamespace(),
159  'page.revid' => $page->getRevision() ? $page->getRevision()->getId() : 0,
160  'user.id' => $user->getId(),
161  'user.editCount' => $user->getEditCount() ?: 0,
162  'mediawiki.version' => $wgVersion
163  ] + $data;
164 
165  if ( $user->isAnon() ) {
166  $data['user.class'] = 'IP';
167  }
168 
169  return EventLogging::logEvent( 'Edit', 13457736, $data );
170  }
171 
181  public static function editPageShowEditFormInitial( $editPage, $outputPage ) {
182  if ( $editPage->contentModel !== CONTENT_MODEL_WIKITEXT ) {
183  return true;
184  }
185 
186  $outputPage->addModuleStyles( 'ext.wikiEditor.styles' );
187 
188  // Add modules for enabled features
189  foreach ( self::$features as $name => $feature ) {
190  if ( !self::isEnabled( $name ) ) {
191  continue;
192  }
193  if ( isset( $feature['stylemodules'] ) ) {
194  $outputPage->addModuleStyles( $feature['stylemodules'] );
195  }
196  if ( isset( $feature['modules'] ) ) {
197  $outputPage->addModules( $feature['modules'] );
198  }
199  }
200 
201  $article = $editPage->getArticle();
202  $request = $article->getContext()->getRequest();
203  // Don't run this if the request was posted - we don't want to log 'init' when the
204  // user just pressed 'Show preview' or 'Show changes', or switched from VE keeping
205  // changes.
206  if ( class_exists( 'EventLogging' ) && !$request->wasPosted() ) {
207  $data = [];
208  $data['editingSessionId'] = self::getEditingStatsId();
209  if ( $request->getVal( 'section' ) ) {
210  $data['action.init.type'] = 'section';
211  } else {
212  $data['action.init.type'] = 'page';
213  }
214  if ( $request->getHeader( 'Referer' ) ) {
215  if ( $request->getVal( 'section' ) === 'new' || !$article->exists() ) {
216  $data['action.init.mechanism'] = 'new';
217  } else {
218  $data['action.init.mechanism'] = 'click';
219  }
220  } else {
221  $data['action.init.mechanism'] = 'url';
222  }
223 
224  self::doEventLogging( 'init', $article, $data );
225  }
226 
227  return true;
228  }
229 
239  public static function editPageShowEditFormFields( $editPage, $outputPage ) {
240  if ( $editPage->contentModel !== CONTENT_MODEL_WIKITEXT ) {
241  return true;
242  }
243 
244  $req = $outputPage->getContext()->getRequest();
245  $editingStatsId = $req->getVal( 'editingStatsId' );
246  if ( !$editingStatsId || !$req->wasPosted() ) {
247  $editingStatsId = self::getEditingStatsId();
248  }
249 
250  $outputPage->addHTML(
251  Xml::element(
252  'input',
253  [
254  'type' => 'hidden',
255  'name' => 'editingStatsId',
256  'id' => 'editingStatsId',
257  'value' => $editingStatsId
258  ]
259  )
260  );
261  return true;
262  }
263 
272  public static function EditPageBeforeEditToolbar( &$toolbar ) {
273  if ( self::isEnabled( 'toolbar' ) ) {
274  $toolbar = Html::rawElement(
275  'div', [
276  'class' => 'wikiEditor-oldToolbar'
277  ],
278  $toolbar
279  );
280  // Return false to signify that the toolbar has been over-written, so
281  // the old toolbar code shouldn't be added to the page.
282  return false;
283  }
284  return true;
285  }
286 
296  public static function getPreferences( $user, &$defaultPreferences ) {
297  global $wgWikiEditorFeatures;
298 
299  foreach ( self::$features as $name => $feature ) {
300  if (
301  isset( $feature['preferences'] ) &&
302  ( !isset( $wgWikiEditorFeatures[$name] ) || $wgWikiEditorFeatures[$name]['user'] )
303  ) {
304  foreach ( $feature['preferences'] as $key => $options ) {
305  $defaultPreferences[$key] = $options;
306  }
307  }
308  }
309  return true;
310  }
311 
316  public static function resourceLoaderGetConfigVars( &$vars ) {
317  // expose magic words for use by the wikieditor toolbar
319 
320  $vars['mw.msg.wikieditor'] = wfMessage( 'sig-text', '~~~~' )->inContentLanguage()->text();
321 
322  return true;
323  }
324 
335  public static function resourceLoaderTestModules( &$testModules, &$resourceLoader ) {
336  $testModules['qunit']['ext.wikiEditor.toolbar.test'] = [
337  'scripts' => [ 'tests/qunit/ext.wikiEditor.toolbar.test.js' ],
338  'dependencies' => [ 'ext.wikiEditor.toolbar' ],
339  'localBasePath' => __DIR__,
340  'remoteExtPath' => 'WikiEditor',
341  ];
342  return true;
343  }
344 
352  public static function makeGlobalVariablesScript( &$vars ) {
353  // Build and export old-style wgWikiEditorEnabledModules object for back compat
354  $enabledModules = [];
355  foreach ( self::$features as $name => $feature ) {
356  $enabledModules[$name] = self::isEnabled( $name );
357  }
358 
359  $vars['wgWikiEditorEnabledModules'] = $enabledModules;
360  return true;
361  }
362 
368  private static function getMagicWords( &$vars ) {
369  $requiredMagicWords = [
370  'redirect',
371  'img_right',
372  'img_left',
373  'img_none',
374  'img_center',
375  'img_thumbnail',
376  'img_framed',
377  'img_frameless',
378  ];
379  $magicWords = [];
380  foreach ( $requiredMagicWords as $name ) {
381  $magicWords[$name] = MagicWord::get( $name )->getSynonym( 0 );
382  }
383  $vars['wgWikiEditorMagicWords'] = $magicWords;
384  }
385 
390  private static function getEditingStatsId() {
391  if ( self::$statsId ) {
392  return self::$statsId;
393  }
394  return self::$statsId = MWCryptRand::generateHex( 32 );
395  }
396 
404  public static function editPageAttemptSave( EditPage $editPage ) {
405  $article = $editPage->getArticle();
406  $request = $article->getContext()->getRequest();
407  if ( $request->getVal( 'editingStatsId' ) ) {
408  self::doEventLogging(
409  'saveAttempt',
410  $article,
411  [ 'editingSessionId' => $request->getVal( 'editingStatsId' ) ]
412  );
413  }
414 
415  return true;
416  }
417 
425  public static function editPageAttemptSaveAfter( EditPage $editPage, Status $status ) {
426  $article = $editPage->getArticle();
427  $request = $article->getContext()->getRequest();
428  if ( $request->getVal( 'editingStatsId' ) ) {
429  $data = [];
430  $data['editingSessionId'] = $request->getVal( 'editingStatsId' );
431 
432  if ( $status->isOK() ) {
433  $action = 'saveSuccess';
434  } else {
435  $action = 'saveFailure';
436  $errors = $status->getErrorsArray();
437 
438  if ( isset( $errors[0][0] ) ) {
439  $data['action.saveFailure.message'] = $errors[0][0];
440  }
441 
442  if ( $status->value === EditPage::AS_CONFLICT_DETECTED ) {
443  $data['action.saveFailure.type'] = 'editConflict';
444  } elseif ( $status->value === EditPage::AS_ARTICLE_WAS_DELETED ) {
445  $data['action.saveFailure.type'] = 'editPageDeleted';
446  } elseif ( isset( $errors[0][0] ) && $errors[0][0] === 'abusefilter-disallowed' ) {
447  $data['action.saveFailure.type'] = 'extensionAbuseFilter';
448  } elseif ( isset( $editPage->getArticle()->getPage()->ConfirmEdit_ActivateCaptcha ) ) {
449  // TODO: :(
450  $data['action.saveFailure.type'] = 'extensionCaptcha';
451  } elseif ( isset( $errors[0][0] ) && $errors[0][0] === 'spamprotectiontext' ) {
452  $data['action.saveFailure.type'] = 'extensionSpamBlacklist';
453  } else {
454  // Catch everything else... We don't seem to get userBadToken or
455  // userNewUser through this hook.
456  $data['action.saveFailure.type'] = 'responseUnknown';
457  }
458  }
459  self::doEventLogging( $action, $article, $data );
460  }
461 
462  return true;
463  }
464 }
WikiEditorHooks::resourceLoaderGetConfigVars
static resourceLoaderGetConfigVars(&$vars)
Definition: WikiEditor.hooks.php:316
$wgUser
$wgUser
Definition: Setup.php:781
WikiEditorHooks::editPageAttemptSave
static editPageAttemptSave(EditPage $editPage)
This is attached to the MediaWiki 'EditPage::attemptSave' hook.
Definition: WikiEditor.hooks.php:404
$request
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2612
WikiEditorHooks
Definition: WikiEditor.hooks.php:9
WikiEditorHooks::$features
static $features
Definition: WikiEditor.hooks.php:16
$wgVersion
$wgVersion
MediaWiki version number.
Definition: DefaultSettings.php:78
WikiEditorHooks::editPageShowEditFormInitial
static editPageShowEditFormInitial( $editPage, $outputPage)
EditPage::showEditForm:initial hook.
Definition: WikiEditor.hooks.php:181
$status
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1049
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:246
$req
this hook is for auditing only $req
Definition: hooks.txt:990
MagicWord\get
static & get( $id)
Factory: creates an object representing an ID.
Definition: MagicWord.php:258
WikiEditorHooks::doEventLogging
static doEventLogging( $action, $article, $data=[])
Log stuff to EventLogging's Schema:Edit - see https://meta.wikimedia.org/wiki/Schema:Edit If you don'...
Definition: WikiEditor.hooks.php:136
MWCryptRand\generateHex
static generateHex( $chars, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:76
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
CONTENT_MODEL_WIKITEXT
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:233
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
WikiEditorHooks::editPageAttemptSaveAfter
static editPageAttemptSaveAfter(EditPage $editPage, Status $status)
This is attached to the MediaWiki 'EditPage::attemptSave:after' hook.
Definition: WikiEditor.hooks.php:425
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
$page
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2536
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
WikiEditorHooks::EditPageBeforeEditToolbar
static EditPageBeforeEditToolbar(&$toolbar)
EditPageBeforeEditToolbar hook.
Definition: WikiEditor.hooks.php:272
EditPage\getArticle
getArticle()
Definition: EditPage.php:443
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
$vars
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2179
$magicWords
magicword txt Magic Words are some phrases used in the wikitext They are used for two that looks like templates but that don t accept any parameter *Parser functions(like {{fullurl:...}}, {{#special:...}}) $magicWords['en']
Definition: magicword.txt:33
WikiEditorHooks::isEnabled
static isEnabled( $name)
Checks if a certain option is enabled.
Definition: WikiEditor.hooks.php:102
WikiEditorHooks::getMagicWords
static getMagicWords(&$vars)
Expose useful magic words which are used by the wikieditor toolbar.
Definition: WikiEditor.hooks.php:368
WikiEditorHooks::$statsId
static $statsId
Definition: WikiEditor.hooks.php:12
WikiEditorHooks::resourceLoaderTestModules
static resourceLoaderTestModules(&$testModules, &$resourceLoader)
ResourceLoaderTestModules hook.
Definition: WikiEditor.hooks.php:335
$value
$value
Definition: styleTest.css.php:45
EditPage
The edit page/HTML interface (split from Article) The actual database and text munging is still in Ar...
Definition: EditPage.php:42
$resourceLoader
error also a ContextSource you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext such as when responding to a resource loader request or generating HTML output & $resourceLoader
Definition: hooks.txt:2612
WikiEditorHooks::getEditingStatsId
static getEditingStatsId()
Gets a 32 character alphanumeric random string to be used for stats.
Definition: WikiEditor.hooks.php:390
WikiEditorHooks::makeGlobalVariablesScript
static makeGlobalVariablesScript(&$vars)
MakeGlobalVariablesScript hook.
Definition: WikiEditor.hooks.php:352
WikiEditorHooks::editPageShowEditFormFields
static editPageShowEditFormFields( $editPage, $outputPage)
EditPage::showEditForm:fields hook.
Definition: WikiEditor.hooks.php:239
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
$article
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function array $article
Definition: hooks.txt:78
EditPage\AS_CONFLICT_DETECTED
const AS_CONFLICT_DETECTED
Status: (non-resolvable) edit conflict.
Definition: EditPage.php:113
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
WikiEditorHooks::getPreferences
static getPreferences( $user, &$defaultPreferences)
GetPreferences hook.
Definition: WikiEditor.hooks.php:296
EditPage\AS_ARTICLE_WAS_DELETED
const AS_ARTICLE_WAS_DELETED
Status: article was deleted while editing and param wpRecreate == false or form was not posted.
Definition: EditPage.php:97
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1049