Introduce VisualEditorApiVisualEditorPostSaveHook

What:

Add hook that runs after a save attempt is made in ApiVisualEditorEdit.
The hook receives the same data available in ApiVisualEditorEdit, and
implementations of the hook can modify the API response.

Also introduce templated
parameters (https://www.mediawiki.org/wiki/API:Templated_parameters) in
the API parameters; this allows plugins to pass arbitrary data along
with their request using e.g. plugins=linkrecommendation&data-linkrecommendation=foo

Add ServiceWiring files, a PHP namespace, and a HookRunner class to
support the above changes.

Why:

VE plugins may wish to send additional data when saving an edit and take
action based on that data on the server-side. See for example the
AddLink plugin in I7a052f8e which sends annotation data, and then uses
the new hook to perform a database operation.

Change-Id: I392691475fbdcec766acbd832600e82efcb5bfe8
This commit is contained in:
Kosta Harlan 2021-04-22 21:27:47 +02:00
parent 1486e5f038
commit f035ce51f1
7 changed files with 175 additions and 5 deletions

View file

@ -27,6 +27,9 @@
"MediaWiki": ">= 1.36.0"
},
"callback": "VisualEditorHooks::onRegistration",
"ServiceWiringFiles": [
"includes/ServiceWiring.php"
],
"config": {
"VisualEditorAllowExternalLinkPaste": {
"value": false
@ -187,7 +190,12 @@
"UserNameUtils"
]
},
"visualeditoredit": "ApiVisualEditorEdit"
"visualeditoredit": {
"class": "ApiVisualEditorEdit",
"services": [
"VisualEditorHookRunner"
]
}
},
"MessagesDirs": {
"VisualEditor": [
@ -2827,6 +2835,9 @@
"SpecialPages": {
"CollabPad": "SpecialCollabPad"
},
"AutoloadNamespaces": {
"MediaWiki\\Extension\\VisualEditor\\": "includes/"
},
"AutoloadClasses": {
"ApiParsoidTrait": "includes/ApiParsoidTrait.php",
"ApiVisualEditor": "includes/ApiVisualEditor.php",

View file

@ -52,6 +52,8 @@
"apihelp-visualeditoredit-param-cachekey": "Use the result of a previous serializeforcache request with this key. Overrides $1html.",
"apihelp-visualeditoredit-param-captchaid": "Captcha ID (when saving with a captcha response).",
"apihelp-visualeditoredit-param-captchaword": "Answer to the captcha (when saving with a captcha response).",
"apihelp-visualeditoredit-param-plugins": "Plugins associated with the API request.",
"apihelp-visualeditoredit-param-data-{plugin}": "Arbitrary data sent by a plugin with the API request.",
"apihelp-visualeditoredit-param-etag": "ETag to send.",
"apihelp-visualeditoredit-param-html": "HTML to send to Parsoid in exchange for wikitext.",
"apihelp-visualeditoredit-param-minor": "Flag for minor edit.",

View file

@ -61,6 +61,8 @@
"apihelp-visualeditoredit-param-cachekey": "{{doc-apihelp-param|visualeditoredit|cachekey}}",
"apihelp-visualeditoredit-param-captchaid": "{{doc-apihelp-param|visualeditoredit|captchaid}}",
"apihelp-visualeditoredit-param-captchaword": "{{doc-apihelp-param|visualeditoredit|captchaword}}",
"apihelp-visualeditoredit-param-plugins": "{{doc-apihelp-param|visualeditoredit|plugins}}",
"apihelp-visualeditoredit-param-data-{plugin}": "{{doc-apihelp-param|visualeditoredit|data-{plugin} }}",
"apihelp-visualeditoredit-param-etag": "{{doc-apihelp-param|visualeditoredit|etag}}",
"apihelp-visualeditoredit-param-html": "{{doc-apihelp-param|visualeditoredit|html}}",
"apihelp-visualeditoredit-param-minor": "{{doc-apihelp-param|visualeditoredit|minor}}",

View file

@ -4,12 +4,14 @@
*
* @file
* @ingroup Extensions
* @copyright 2011-2020 VisualEditor Team and others; see AUTHORS.txt
* @copyright 2011-2021 VisualEditor Team and others; see AUTHORS.txt
* @license MIT
*/
use MediaWiki\Extension\VisualEditor\VisualEditorHookRunner;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\PageIdentityValue;
use MediaWiki\User\UserIdentity;
use Wikimedia\ParamValidator\ParamValidator;
@ -19,12 +21,18 @@ class ApiVisualEditorEdit extends ApiBase {
private const MAX_CACHE_RECENT = 2;
private const MAX_CACHE_TTL = 900;
/** @var VisualEditorHookRunner */
private $hookRunner;
/**
* @inheritDoc
* @param ApiMain $main
* @param string $name Name of this module
* @param VisualEditorHookRunner $hookRunner
*/
public function __construct( ApiMain $main, $name ) {
public function __construct( ApiMain $main, string $name, VisualEditorHookRunner $hookRunner ) {
parent::__construct( $main, $name );
$this->setLogger( LoggerFactory::getInstance( 'VisualEditor' ) );
$this->hookRunner = $hookRunner;
}
/**
@ -446,8 +454,26 @@ class ApiVisualEditorEdit extends ApiBase {
$result['watchlistexpiry'] = $saveresult['edit']['watchlistexpiry'] ?? null;
$result['result'] = 'success';
}
$plugins = explode( '|', $this->getRequest()->getVal( 'plugins', '' ) );
$pluginData = [];
foreach ( $plugins as $plugin ) {
$pluginData[$plugin] = $this->getRequest()->getVal( 'data-' . $plugin );
}
$this->hookRunner->onVisualEditorApiVisualEditorEditPostSave(
new PageIdentityValue(
$title->getId(),
$title->getNamespace(),
$title->getDBkey(),
PageIdentityValue::LOCAL
),
$user,
$wikitext,
$params,
$pluginData,
$saveresult,
$result
);
}
// @phan-suppress-next-line PhanPossiblyUndeclaredVariable False positive
$this->getResult()->addValue( null, $this->getModuleName(), $result );
}
@ -502,6 +528,16 @@ class ApiVisualEditorEdit extends ApiBase {
'tags' => [
ParamValidator::PARAM_ISMULTI => true,
],
'plugins' => [
ParamValidator::PARAM_ISMULTI => true,
ParamValidator::PARAM_TYPE => 'string',
],
// Additional data sent by the client. Not used directly in the ApiVisualEditorEdit workflows, but
// is passed alongside the other parameters to implementations of onApiVisualEditorEditPostSave
'data-{plugin}' => [
ParamValidator::PARAM_ISMULTI => true,
ApiBase::PARAM_TEMPLATE_VARS => [ 'plugin' => 'plugins' ]
]
];
}

View file

@ -0,0 +1,20 @@
<?php
/**
* ServiceWiring files for VisualEditor.
*
* @file
* @ingroup Extensions
* @copyright 2011-2021 VisualEditor Team and others; see AUTHORS.txt
* @license MIT
*/
namespace MediaWiki\Extension\VisualEditor;
use MediaWiki\MediaWikiServices;
return [
VisualEditorHookRunner::SERVICE_NAME => static function ( MediaWikiServices $services ): VisualEditorHookRunner {
return new VisualEditorHookRunner( $services->getHookContainer() );
},
];

View file

@ -0,0 +1,47 @@
<?php
namespace MediaWiki\Extension\VisualEditor;
use MediaWiki\Page\ProperPageIdentity;
use MediaWiki\User\UserIdentity;
/**
* VisualEditorApiVisualEditorEditPostSaveHook
*
* @file
* @ingroup Extensions
* @copyright 2011-2021 VisualEditor Team and others; see AUTHORS.txt
* @license MIT
*/
interface VisualEditorApiVisualEditorEditPostSaveHook {
/**
* This hook is executed in the ApiVisualEditorEdit after a action=save attempt.
*
* ApiVisualEditorEdit will wait for implementations of this hook to complete before returning a response, so
* if the implementation needs to do something time-consuming that does not need to be sent back with the response,
* consider using a DeferredUpdate or Job.
*
* @param ProperPageIdentity $page The page identity of the title used in the save attempt.
* @param UserIdentity $user User associated with the save attempt.
* @param string $wikitext The wikitext used in the save attempt.
* @param array $params The params passed by the client in the API request. See
* ApiVisualEditorEdit::getAllowedParams()
* @param array $pluginData Associative array containing additional data specified by plugins, where the keys of
* the array are plugin names and the value are arbitrary data.
* @param array $saveResult The result from ApiVisualEditorEdit::saveWikitext()
* @param array &$apiResponse The modifiable API response that VisualEditor will return to the client.
* @return void
*/
public function onVisualEditorApiVisualEditorEditPostSave(
ProperPageIdentity $page,
UserIdentity $user,
string $wikitext,
array $params,
array $pluginData,
array $saveResult,
array &$apiResponse
): void;
}

View file

@ -0,0 +1,52 @@
<?php
namespace MediaWiki\Extension\VisualEditor;
/**
* VisualEditorHookRunner
*
* @file
* @ingroup Extensions
* @copyright 2011-2021 VisualEditor Team and others; see AUTHORS.txt
* @license MIT
*/
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\Page\ProperPageIdentity;
use MediaWiki\User\UserIdentity;
class VisualEditorHookRunner implements VisualEditorApiVisualEditorEditPostSaveHook {
public const SERVICE_NAME = 'VisualEditorHookRunner';
/** @var HookContainer */
private $hookContainer;
/**
* @param HookContainer $hookContainer
*/
public function __construct( HookContainer $hookContainer ) {
$this->hookContainer = $hookContainer;
}
/** @inheritDoc */
public function onVisualEditorApiVisualEditorEditPostSave(
ProperPageIdentity $page,
UserIdentity $user,
string $wikitext,
array $params,
array $pluginData,
array $saveResult,
array &$apiResponse
): void {
$this->hookContainer->run( 'VisualEditorApiVisualEditorEditPostSave', [
$page,
$user,
$wikitext,
$params,
$pluginData,
$saveResult,
&$apiResponse
], [ 'abortable' => false ] );
}
}