mediawiki-extensions-Scribunto/common/ApiScribuntoConsole.php
Brad Jorsch e878314048 Handle session loss in the console
If the session data gets lost, the console forgets the content and
previous commands. Detect this situation and handle it.

Change-Id: I82fb5e111c09091d4f9a87d2e1b1c245eced1420
2013-02-11 13:23:10 -05:00

164 lines
4.4 KiB
PHP

<?php
/**
* API module for serving debug console requests on the edit page
*/
class ApiScribuntoConsole extends ApiBase {
const SC_MAX_SIZE = 500000;
const SC_SESSION_EXPIRY = 3600;
public function execute() {
$params = $this->extractRequestParams();
$title = Title::newFromText( $params['title'] );
if ( !$title ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
if ( $params['session'] ) {
$sessionId = $params['session'];
} else {
$sessionId = mt_rand( 0, 0x7fffffff );
}
global $wgUser;
$sessionKey = wfMemcKey( 'scribunto-console', $wgUser->getId(), $sessionId );
$cache = ObjectCache::getInstance( CACHE_ANYTHING );
$session = null;
$sessionIsNew = false;
if ( $params['session'] ) {
$session = $cache->get( $sessionKey );
}
if ( !isset( $session['version'] ) ) {
$session = $this->newSession();
$sessionIsNew = true;
}
// Create a variable holding the session which will be stored if there
// are no errors. If there are errors, we don't want to store the current
// question to the state builder array, since that will cause subsequent
// requests to fail.
$newSession = $session;
if ( !empty( $params['clear'] ) ) {
$newSession['size'] -= strlen( implode( '', $newSession['questions'] ) );
$newSession['questions'] = array();
$session['questions'] = array();
}
if ( strlen( $params['question'] ) ) {
$newSession['size'] += strlen( $params['question'] );
$newSession['questions'][] = $params['question'];
}
if ( $params['content'] ) {
$newSession['size'] += strlen( $params['content'] ) - strlen( $newSession['content'] );
$newSession['content'] = $params['content'];
}
if ( $newSession['size'] > self::SC_MAX_SIZE ) {
$this->dieUsage(
$this->msg( 'scribunto-console-too-large' )->text(),
'scribunto-console-too-large'
);
}
$result = $this->runConsole( array(
'title' => $title,
'content' => $newSession['content'],
'prevQuestions' => $session['questions'],
'question' => $params['question'] ) );
if ( $result['type'] === 'error' ) {
// Restore the questions array
$newSession['questions'] = $session['questions'];
}
$cache->set( $sessionKey, $newSession, self::SC_SESSION_EXPIRY );
$result['session'] = $sessionId;
$result['sessionSize'] = $newSession['size'];
$result['sessionMaxSize'] = self::SC_MAX_SIZE;
if ( $sessionIsNew ) {
$result['sessionIsNew'] = '';
}
foreach ( $result as $key => $value ) {
$this->getResult()->addValue( null, $key, $value );
}
}
protected function runConsole( $params ) {
global $wgParser;
$options = new ParserOptions;
$wgParser->startExternalParse( $params['title'], $options, Parser::OT_HTML, true );
$engine = Scribunto::getParserEngine( $wgParser );
try {
$result = $engine->runConsole( $params );
} catch ( ScribuntoException $e ) {
$trace = $e->getScriptTraceHtml();
$message = $e->getMessage();
$html = Html::element( 'p', array(), $message );
if ( $trace !== false ) {
$html .= Html::element( 'p',
array(),
$this->msg( 'scribunto-common-backtrace' )->inContentLanguage()->text()
) . $trace;
}
return array(
'type' => 'error',
'html' => $html,
'message' => $message,
'messagename' => $e->getMessageName() );
}
return array(
'type' => 'normal',
'print' => strval( $result['print'] ),
'return' => strval( $result['return'] )
);
}
protected function newSession() {
return array(
'content' => '',
'questions' => array(),
'size' => 0,
'version' => 1,
);
}
public function getAllowedParams() {
return array(
'title' => array(
ApiBase::PARAM_TYPE => 'string',
),
'content' => array(
ApiBase::PARAM_TYPE => 'string'
),
'session' => array(
ApiBase::PARAM_TYPE => 'integer',
),
'question' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true,
),
'clear' => array(
ApiBase::PARAM_TYPE => 'boolean',
),
);
}
public function getParamDescription() {
return array(
'title' => 'The module title to test',
'content' => 'The new content of the module',
'question' => 'The next line to evaluate as a script',
'clear' => 'Set this to true to clear the current session state',
);
}
public function getDescription() {
return 'Internal module for servicing XHR requests from the Scribunto console';
}
public function getVersion() {
return __CLASS__.': 1';
}
}