2012-05-25 19:50:48 +00:00
|
|
|
<?php
|
2012-07-19 00:11:26 +00:00
|
|
|
/**
|
|
|
|
* Parsoid API wrapper.
|
2012-07-19 21:25:16 +00:00
|
|
|
*
|
2012-07-19 00:11:26 +00:00
|
|
|
* @file
|
|
|
|
* @ingroup Extensions
|
2013-02-19 23:37:34 +00:00
|
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
2012-07-19 00:11:26 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
2012-05-25 19:50:48 +00:00
|
|
|
class ApiVisualEditor extends ApiBase {
|
2012-11-14 18:33:57 +00:00
|
|
|
protected function getHTML( $title, $parserParams ) {
|
2012-11-15 01:16:13 +00:00
|
|
|
global $wgVisualEditorParsoidURL, $wgVisualEditorParsoidPrefix,
|
|
|
|
$wgVisualEditorParsoidTimeout;
|
2013-05-24 14:48:27 +00:00
|
|
|
|
|
|
|
$restoring = false;
|
|
|
|
|
2012-11-28 23:57:00 +00:00
|
|
|
if ( $title->exists() ) {
|
2013-04-15 23:22:51 +00:00
|
|
|
if ( $parserParams['oldid'] === 0 ) {
|
|
|
|
$parserParams['oldid'] = ''; // Parsoid wants empty string rather than zero
|
2012-12-04 21:04:19 +00:00
|
|
|
}
|
2012-11-28 23:57:00 +00:00
|
|
|
$revision = Revision::newFromId( $parserParams['oldid'] );
|
2012-12-04 21:04:19 +00:00
|
|
|
if ( $revision === null ) {
|
2012-11-28 23:57:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
2013-05-24 14:48:27 +00:00
|
|
|
$restoring = !$revision->isCurrent();
|
|
|
|
|
2012-12-11 18:32:14 +00:00
|
|
|
$parserParams['touched'] = $title->getTouched();
|
|
|
|
$parserParams['cache'] = 1;
|
2013-03-05 17:24:51 +00:00
|
|
|
|
|
|
|
$req = MWHttpRequest::factory( wfAppendQuery(
|
2012-11-28 23:57:00 +00:00
|
|
|
$wgVisualEditorParsoidURL . '/' . $wgVisualEditorParsoidPrefix .
|
|
|
|
'/' . urlencode( $title->getPrefixedDBkey() ),
|
|
|
|
$parserParams
|
|
|
|
),
|
2013-03-05 17:24:51 +00:00
|
|
|
array(
|
|
|
|
'method' => 'GET',
|
|
|
|
'timeout' => $wgVisualEditorParsoidTimeout
|
|
|
|
)
|
2012-11-28 23:57:00 +00:00
|
|
|
);
|
2013-03-05 17:24:51 +00:00
|
|
|
$status = $req->execute();
|
|
|
|
|
|
|
|
if ( $status->isOK() ) {
|
|
|
|
$content = $req->getContent();
|
2013-05-29 14:49:16 +00:00
|
|
|
} elseif ( $status->isGood() ) {
|
2013-03-05 17:24:51 +00:00
|
|
|
$this->dieUsage( $req->getContent(), 'parsoidserver-http-'.$req->getStatus() );
|
2013-05-29 14:49:16 +00:00
|
|
|
} elseif ( $errors = $status->getErrorsByType( 'error' ) ) {
|
2013-03-09 18:28:23 +00:00
|
|
|
$error = $errors[0];
|
|
|
|
$code = $error['message'];
|
2013-05-29 14:49:16 +00:00
|
|
|
if ( count( $error['params'] ) ) {
|
2013-03-09 18:28:23 +00:00
|
|
|
$message = $error['params'][0];
|
|
|
|
} else {
|
|
|
|
$message = 'MWHttpRequest error';
|
|
|
|
}
|
|
|
|
$this->dieUsage( $message, 'parsoidserver-'.$code );
|
2013-03-05 17:24:51 +00:00
|
|
|
}
|
|
|
|
|
2012-12-04 21:04:19 +00:00
|
|
|
if ( $content === false ) {
|
|
|
|
return false;
|
|
|
|
}
|
2012-11-28 23:57:00 +00:00
|
|
|
$timestamp = $revision->getTimestamp();
|
|
|
|
} else {
|
|
|
|
$content = '';
|
|
|
|
$timestamp = wfTimestampNow();
|
2012-11-14 18:33:57 +00:00
|
|
|
}
|
2012-11-28 23:57:00 +00:00
|
|
|
return array(
|
2013-05-24 14:48:27 +00:00
|
|
|
'result' => array(
|
|
|
|
'content' => $content,
|
|
|
|
'basetimestamp' => $timestamp,
|
|
|
|
'starttimestamp' => wfTimestampNow(),
|
|
|
|
),
|
|
|
|
'restoring' => $restoring,
|
2012-11-14 18:33:57 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-04-15 23:22:51 +00:00
|
|
|
protected function postHTML( $title, $html, $parserParams ) {
|
2012-11-27 01:39:44 +00:00
|
|
|
global $wgVisualEditorParsoidURL, $wgVisualEditorParsoidPrefix,
|
|
|
|
$wgVisualEditorParsoidTimeout;
|
2013-04-15 23:22:51 +00:00
|
|
|
if ( $parserParams['oldid'] === 0 ) {
|
|
|
|
$parserParams['oldid'] = '';
|
|
|
|
}
|
2012-11-14 18:33:57 +00:00
|
|
|
return Http::post(
|
2012-11-27 01:39:44 +00:00
|
|
|
$wgVisualEditorParsoidURL . '/' . $wgVisualEditorParsoidPrefix .
|
|
|
|
'/' . urlencode( $title->getPrefixedDBkey() ),
|
2012-11-15 01:16:13 +00:00
|
|
|
array(
|
2013-05-25 10:00:06 +00:00
|
|
|
'postData' => array(
|
2013-04-19 22:13:57 +00:00
|
|
|
'content' => $html,
|
2013-05-25 10:00:06 +00:00
|
|
|
'oldid' => $parserParams['oldid']
|
2013-04-19 22:13:57 +00:00
|
|
|
),
|
|
|
|
'timeout' => $wgVisualEditorParsoidTimeout
|
2012-11-15 01:16:13 +00:00
|
|
|
)
|
2012-11-14 18:33:57 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function saveWikitext( $title, $wikitext, $params ) {
|
|
|
|
$apiParams = array(
|
|
|
|
'action' => 'edit',
|
|
|
|
'title' => $title->getPrefixedDBkey(),
|
|
|
|
'text' => $wikitext,
|
|
|
|
'summary' => $params['summary'],
|
|
|
|
'basetimestamp' => $params['basetimestamp'],
|
2012-11-28 23:57:00 +00:00
|
|
|
'starttimestamp' => $params['starttimestamp'],
|
2012-11-14 18:33:57 +00:00
|
|
|
'token' => $params['token'],
|
|
|
|
);
|
|
|
|
if ( $params['minor'] ) {
|
|
|
|
$apiParams['minor'] = true;
|
|
|
|
}
|
|
|
|
// FIXME add some way that the user's preferences can be respected
|
|
|
|
$apiParams['watchlist'] = $params['watch'] ? 'watch' : 'unwatch';
|
|
|
|
$api = new ApiMain(
|
|
|
|
new DerivativeRequest(
|
|
|
|
$this->getRequest(),
|
|
|
|
$apiParams,
|
|
|
|
true // was posted
|
|
|
|
),
|
|
|
|
true // enable write
|
|
|
|
);
|
|
|
|
$api->execute();
|
|
|
|
return $api->getResultData();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function parseWikitext( $title ) {
|
|
|
|
$apiParams = array(
|
|
|
|
'action' => 'parse',
|
|
|
|
'page' => $title->getPrefixedDBkey()
|
|
|
|
);
|
|
|
|
$api = new ApiMain(
|
|
|
|
new DerivativeRequest(
|
|
|
|
$this->getRequest(),
|
|
|
|
$apiParams,
|
|
|
|
false // was posted?
|
|
|
|
),
|
|
|
|
true // enable write?
|
|
|
|
);
|
|
|
|
|
|
|
|
$api->execute();
|
|
|
|
$result = $api->getResultData();
|
2012-11-28 23:57:00 +00:00
|
|
|
$content = isset( $result['parse']['text']['*'] ) ? $result['parse']['text']['*'] : false;
|
|
|
|
$revision = Revision::newFromId( $result['parse']['revid'] );
|
|
|
|
$timestamp = $revision ? $revision->getTimestamp() : wfTimestampNow();
|
|
|
|
|
|
|
|
if ( $content === false || ( strlen( $content ) && $revision === null ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return array(
|
|
|
|
'content' => $content,
|
|
|
|
'basetimestamp' => $timestamp,
|
|
|
|
'starttimestamp' => wfTimestampNow()
|
|
|
|
);
|
2012-11-14 18:33:57 +00:00
|
|
|
}
|
|
|
|
|
2013-05-15 23:51:11 +00:00
|
|
|
protected function parseWikitextFragment( $wikitext, $title = null ) {
|
|
|
|
$apiParams = array(
|
|
|
|
'action' => 'parse',
|
|
|
|
'title' => $title,
|
|
|
|
'prop' => 'text',
|
|
|
|
'disablepp' => true,
|
|
|
|
'text' => $wikitext
|
|
|
|
);
|
|
|
|
$api = new ApiMain(
|
|
|
|
new DerivativeRequest(
|
|
|
|
$this->getRequest(),
|
|
|
|
$apiParams,
|
|
|
|
false // was posted?
|
|
|
|
),
|
|
|
|
true // enable write?
|
|
|
|
);
|
|
|
|
|
|
|
|
$api->execute();
|
|
|
|
$result = $api->getResultData();
|
|
|
|
return isset( $result['parse']['text']['*'] ) ? $result['parse']['text']['*'] : false;
|
|
|
|
}
|
|
|
|
|
2012-11-14 18:33:57 +00:00
|
|
|
protected function diffWikitext( $title, $wikitext ) {
|
|
|
|
$apiParams = array(
|
|
|
|
'action' => 'query',
|
|
|
|
'prop' => 'revisions',
|
|
|
|
'titles' => $title->getPrefixedDBkey(),
|
|
|
|
'rvdifftotext' => $wikitext
|
|
|
|
);
|
|
|
|
$api = new ApiMain(
|
|
|
|
new DerivativeRequest(
|
|
|
|
$this->getRequest(),
|
|
|
|
$apiParams,
|
|
|
|
false // was posted?
|
|
|
|
),
|
|
|
|
false // enable write?
|
|
|
|
);
|
|
|
|
$api->execute();
|
|
|
|
$result = $api->getResultData();
|
2012-12-07 16:23:23 +00:00
|
|
|
if ( !isset( $result['query']['pages'][$title->getArticleID()]['revisions'][0]['diff']['*'] ) ) {
|
2013-05-14 17:40:00 +00:00
|
|
|
return array( 'result' => 'fail' );
|
2012-12-07 16:23:23 +00:00
|
|
|
}
|
|
|
|
$diffRows = $result['query']['pages'][$title->getArticleID()]['revisions'][0]['diff']['*'];
|
|
|
|
|
2013-05-10 22:32:23 +00:00
|
|
|
if ( $diffRows !== '' ) {
|
|
|
|
$context = new DerivativeContext( $this->getContext() );
|
|
|
|
$context->setTitle( $title );
|
|
|
|
$engine = new DifferenceEngine( $context );
|
2013-05-14 17:40:00 +00:00
|
|
|
return array(
|
|
|
|
'result' => 'success',
|
|
|
|
'diff' => $engine->addHeader(
|
|
|
|
$diffRows,
|
|
|
|
wfMessage( 'currentrev' )->parse(),
|
|
|
|
wfMessage( 'yourtext' )->parse()
|
|
|
|
)
|
2013-05-10 22:32:23 +00:00
|
|
|
);
|
|
|
|
} else {
|
2013-05-14 17:40:00 +00:00
|
|
|
return array( 'result' => 'nochanges' );
|
2013-05-10 22:32:23 +00:00
|
|
|
}
|
2012-11-14 18:33:57 +00:00
|
|
|
}
|
2012-05-25 22:23:40 +00:00
|
|
|
|
2012-05-25 19:50:48 +00:00
|
|
|
public function execute() {
|
2012-12-11 03:40:09 +00:00
|
|
|
global $wgVisualEditorNamespaces, $wgVisualEditorUseChangeTagging,
|
|
|
|
$wgVisualEditorEditNotices;
|
2012-07-21 17:37:20 +00:00
|
|
|
$user = $this->getUser();
|
2012-05-25 19:50:48 +00:00
|
|
|
$params = $this->extractRequestParams();
|
|
|
|
$page = Title::newFromText( $params['page'] );
|
2012-11-14 18:33:57 +00:00
|
|
|
if ( !$page ) {
|
|
|
|
$this->dieUsageMsg( 'invalidtitle', $params['page'] );
|
|
|
|
}
|
|
|
|
if ( !in_array( $page->getNamespace(), $wgVisualEditorNamespaces ) ) {
|
|
|
|
$this->dieUsage( "VisualEditor is not enabled in namespace " .
|
|
|
|
$page->getNamespace(), 'novenamespace' );
|
|
|
|
}
|
2012-05-25 19:50:48 +00:00
|
|
|
|
2013-05-15 23:51:11 +00:00
|
|
|
$parserParams = array();
|
|
|
|
if ( isset( $params['oldid'] ) ) {
|
|
|
|
$parserParams['oldid'] = $params['oldid'];
|
|
|
|
}
|
2012-08-23 19:01:07 +00:00
|
|
|
|
2013-05-15 21:17:06 +00:00
|
|
|
switch ( $params['paction'] ) {
|
|
|
|
case 'parse':
|
|
|
|
$parsed = $this->getHTML( $page, $parserParams );
|
|
|
|
// Dirty hack to provide the correct context for edit notices
|
|
|
|
global $wgTitle; // FIXME NOOOOOOOOES
|
|
|
|
$wgTitle = $page;
|
|
|
|
$notices = $page->getEditNotices();
|
|
|
|
if ( $user->isAnon() ) {
|
|
|
|
$wgVisualEditorEditNotices[] = 'anoneditwarning';
|
2012-12-11 03:40:09 +00:00
|
|
|
}
|
2013-05-24 14:48:27 +00:00
|
|
|
if ( $parsed && $parsed['restoring'] ) {
|
|
|
|
$wgVisualEditorEditNotices[] = 'editingold';
|
|
|
|
}
|
2013-05-15 21:17:06 +00:00
|
|
|
if ( count( $wgVisualEditorEditNotices ) ) {
|
|
|
|
foreach ( $wgVisualEditorEditNotices as $key ) {
|
|
|
|
$notices[] = wfMessage( $key )->parseAsBlock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( $parsed === false ) {
|
|
|
|
$this->dieUsage( 'Error contacting the Parsoid server', 'parsoidserver' );
|
|
|
|
} else {
|
|
|
|
$result = array_merge(
|
2013-05-24 14:48:27 +00:00
|
|
|
array( 'result' => 'success', 'notices' => $notices ), $parsed['result']
|
2012-11-14 18:33:57 +00:00
|
|
|
);
|
2013-05-15 21:17:06 +00:00
|
|
|
}
|
|
|
|
break;
|
2013-05-15 23:51:11 +00:00
|
|
|
case 'parsefragment':
|
|
|
|
$content = $this->parseWikitextFragment( $params['wikitext'], $page->getText() );
|
|
|
|
if ( $content === false ) {
|
|
|
|
$this->dieUsage( 'Error contacting the Parsoid server', 'parsoidserver' );
|
|
|
|
} else {
|
|
|
|
$result = array(
|
|
|
|
'result' => 'success',
|
|
|
|
'content' => $content
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
2013-05-15 21:17:06 +00:00
|
|
|
case 'serialize':
|
|
|
|
if ( $params['html'] === null ) {
|
|
|
|
$this->dieUsageMsg( 'missingparam', 'html' );
|
|
|
|
}
|
|
|
|
$content = $this->postHTML( $page, $params['html'], $parserParams );
|
|
|
|
if ( $content === false ) {
|
|
|
|
$this->dieUsage( 'Error contacting the Parsoid server', 'parsoidserver' );
|
2012-06-01 23:26:03 +00:00
|
|
|
} else {
|
2013-05-15 21:17:06 +00:00
|
|
|
$result = array( 'result' => 'success', 'content' => $content );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'save':
|
|
|
|
case 'diff':
|
|
|
|
$wikitext = $this->postHTML( $page, $params['html'], $parserParams );
|
|
|
|
|
|
|
|
if ( $wikitext === false ) {
|
|
|
|
$this->dieUsage( 'Error contacting the Parsoid server', 'parsoidserver' );
|
2013-05-29 14:49:16 +00:00
|
|
|
} elseif ( $params['paction'] === 'save' ) {
|
2013-05-15 21:17:06 +00:00
|
|
|
// Save page
|
|
|
|
$editResult = $this->saveWikitext( $page, $wikitext, $params );
|
|
|
|
if (
|
|
|
|
!isset( $editResult['edit']['result'] ) ||
|
|
|
|
$editResult['edit']['result'] !== 'Success'
|
|
|
|
) {
|
|
|
|
$result = array(
|
|
|
|
'result' => 'error',
|
|
|
|
'edit' => $editResult['edit']
|
2012-11-22 02:26:29 +00:00
|
|
|
);
|
2013-05-15 21:17:06 +00:00
|
|
|
} else {
|
|
|
|
if ( isset( $editResult['edit']['newrevid'] ) && $wgVisualEditorUseChangeTagging ) {
|
|
|
|
ChangeTags::addTags( 'visualeditor', null,
|
|
|
|
intval( $editResult['edit']['newrevid'] ),
|
|
|
|
null
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$result = $this->parseWikitext( $page );
|
|
|
|
if ( $result === false ) {
|
|
|
|
$this->dieUsage( 'Error contacting the Parsoid server', 'parsoidserver' );
|
|
|
|
}
|
|
|
|
$result['result'] = 'success';
|
|
|
|
if ( isset( $editResult['edit']['newrevid'] ) ) {
|
|
|
|
$result['newrevid'] = intval( $editResult['edit']['newrevid'] );
|
|
|
|
}
|
2012-11-22 02:26:29 +00:00
|
|
|
}
|
2013-05-29 14:49:16 +00:00
|
|
|
} elseif ( $params['paction'] === 'diff' ) {
|
2013-05-15 21:17:06 +00:00
|
|
|
$diff = $this->diffWikitext( $page, $wikitext );
|
|
|
|
if ( $diff['result'] === 'fail' ) {
|
|
|
|
$this->dieUsage( 'Diff failed', 'difffailed' );
|
2013-05-03 12:15:54 +00:00
|
|
|
}
|
2013-05-15 21:17:06 +00:00
|
|
|
$result = $diff;
|
2012-06-01 23:26:03 +00:00
|
|
|
}
|
2013-05-15 21:17:06 +00:00
|
|
|
break;
|
2012-05-25 19:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->getResult()->addValue( null, $this->getModuleName(), $result );
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getAllowedParams() {
|
|
|
|
return array(
|
|
|
|
'page' => array(
|
|
|
|
ApiBase::PARAM_REQUIRED => true,
|
|
|
|
),
|
|
|
|
'paction' => array(
|
|
|
|
ApiBase::PARAM_REQUIRED => true,
|
2013-05-15 23:51:11 +00:00
|
|
|
ApiBase::PARAM_TYPE => array( 'parse', 'parsefragment', 'serialize', 'save', 'diff' ),
|
2012-05-31 00:09:06 +00:00
|
|
|
),
|
2012-11-28 23:57:00 +00:00
|
|
|
'token' => array(
|
|
|
|
ApiBase::PARAM_REQUIRED => true,
|
2012-05-25 22:23:40 +00:00
|
|
|
),
|
2013-05-15 23:51:11 +00:00
|
|
|
'wikitext' => null,
|
2012-11-14 18:33:57 +00:00
|
|
|
'basetimestamp' => null,
|
2012-11-28 23:57:00 +00:00
|
|
|
'starttimestamp' => null,
|
2013-05-15 23:51:11 +00:00
|
|
|
'oldid' => null,
|
2012-11-28 23:57:00 +00:00
|
|
|
'minor' => null,
|
|
|
|
'watch' => null,
|
|
|
|
'html' => null,
|
|
|
|
'summary' => null
|
2012-05-25 19:50:48 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function needsToken() {
|
2012-11-14 18:33:57 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getTokenSalt() {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
public function mustBePosted() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isWriteMode() {
|
|
|
|
return true;
|
2012-05-25 19:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public function getVersion() {
|
|
|
|
return __CLASS__ . ': $Id$';
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getParamDescription() {
|
|
|
|
return array(
|
2012-05-31 00:09:06 +00:00
|
|
|
'page' => 'The page to perform actions on.',
|
2012-11-14 18:33:57 +00:00
|
|
|
'paction' => 'Action to perform',
|
2013-05-29 14:49:16 +00:00
|
|
|
'oldid' => 'The revision number to use. If zero, the empty string is passed to Parsoid'
|
|
|
|
.' to indicate new page creation.',
|
2012-05-31 00:09:06 +00:00
|
|
|
'minor' => 'Flag for minor edit.',
|
2012-11-14 18:33:57 +00:00
|
|
|
'html' => 'HTML to send to parsoid in exchange for wikitext',
|
|
|
|
'summary' => 'Edit summary',
|
2013-05-29 14:49:16 +00:00
|
|
|
'basetimestamp' => 'When saving, set this to the timestamp of the revision that was'
|
|
|
|
.' edited. Used to detect edit conflicts.',
|
|
|
|
'starttimestamp' => 'When saving, set this to the timestamp of when the page was loaded.'
|
|
|
|
.' Used to detect edit conflicts.',
|
2012-11-14 18:33:57 +00:00
|
|
|
'token' => 'Edit token',
|
2012-05-25 19:50:48 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDescription() {
|
|
|
|
return 'Returns HTML5 for a page from the parsoid service.';
|
|
|
|
}
|
|
|
|
}
|