for more details regarding custom functions.
** $wgResourceLoaderLESSImportPaths is an array of file system paths. Files
referenced in LESS '@import' statements are looked up here first.
+* Added meta=filerepoinfo API module for getting information about foreign
+ image repositories, and related ForeignAPIRepo methods getInfo and getApiUrl.
=== Bug fixes in 1.22 ===
* Disable Special:PasswordReset when $wgEnableEmail is false. Previously one
'ApiQueryQueryPage' => 'includes/api/ApiQueryQueryPage.php',
'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php',
'ApiQueryRecentChanges' => 'includes/api/ApiQueryRecentChanges.php',
+ 'ApiQueryFileRepoInfo' => 'includes/api/ApiQueryFileRepoInfo.php',
'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php',
'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
* added to the executed command environment.
* @param array $limits optional array with limits(filesize, memory, time, walltime)
* this overwrites the global wgShellMax* limits.
- * @return string collected stdout as a string (trailing newlines stripped)
+ * @param array $options Array of options. Only one is "duplicateStderr" => true, which
+ * Which duplicates stderr to stdout, including errors from limit.sh
+ * @return string collected stdout as a string
*/
-function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
+function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array(), $options = array() ) {
global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
$wgMaxShellWallClockTime, $wgShellCgroup;
'Unable to run external programs, passthru() is disabled.';
}
+ $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
+
wfInitShellLocale();
$envcmd = '';
$cmd = $envcmd . $cmd;
if ( php_uname( 's' ) == 'Linux' ) {
+ $stderrDuplication = '';
+ if ( $includeStderr ) {
+ $stderrDuplication = 'exec 2>&1; ';
+ }
$time = intval ( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
if ( isset( $limits['walltime'] ) ) {
$wallTime = intval( $limits['walltime'] );
$cmd = '/bin/bash ' . escapeshellarg( "$IP/includes/limit.sh" ) . ' ' .
escapeshellarg( $cmd ) . ' ' .
escapeshellarg(
+ $stderrDuplication .
"MW_CPU_LIMIT=$time; " .
'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
"MW_MEM_LIMIT=$mem; " .
"MW_FILE_SIZE_LIMIT=$filesize; " .
"MW_WALL_CLOCK_LIMIT=$wallTime"
);
+ } else {
+ $cmd .= ' 2>&1';
}
+ } elseif ( $includeStderr ) {
+ $cmd .= ' 2>&1';
}
wfDebug( "wfShellExec: $cmd\n" );
- $retval = 1; // error by default?
+ // Default to an unusual value that shouldn't happen naturally,
+ // so in the unlikely event of a weird php bug, it would be
+ // more obvious what happened.
+ $retval = 200;
ob_start();
passthru( $cmd, $retval );
$output = ob_get_contents();
return $output;
}
+/**
+ * Execute a shell command, returning both stdout and stderr. Convenience
+ * function, as all the arguments to wfShellExec can become unwieldy.
+ *
+ * @note This also includes errors from limit.sh, e.g. if $wgMaxShellFileSize is exceeded.
+ * @param string $cmd Command line, properly escaped for shell.
+ * @param &$retval null|Mixed optional, will receive the program's exit code.
+ * (non-zero is usually failure)
+ * @param array $environ optional environment variables which should be
+ * added to the executed command environment.
+ * @param array $limits optional array with limits(filesize, memory, time, walltime)
+ * this overwrites the global wgShellMax* limits.
+ * @return string collected stdout and stderr as a string
+ */
+function wfShellExecWithStderr( $cmd, &$retval = null, $environ = array(), $limits = array() ) {
+ return wfShellExec( $cmd, $retval, $environ, $limits, array( 'duplicateStderr' => true ) );
+}
+
/**
* Workaround for http://bugs.php.net/bug.php?id=45132
* escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
return false;
}
+ // Reduce effects of race conditions for slow parses (bug 46014)
+ $cacheTime = wfTimestampNow();
+
$time = - microtime( true );
$this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
$time += microtime( true );
}
if ( $this->cacheable && $this->parserOutput->isCacheable() ) {
- ParserCache::singleton()->save( $this->parserOutput, $this->page, $this->parserOptions );
+ ParserCache::singleton()->save(
+ $this->parserOutput, $this->page, $this->parserOptions, $cacheTime );
}
// Make sure file cache is not used on uncacheable content.
'allmessages' => 'ApiQueryAllMessages',
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
+ 'filerepoinfo' => 'ApiQueryFileRepoInfo',
);
/**
--- /dev/null
+<?php
+/**
+ * Copyright © 2013 Mark Holmquist <mtraceur@member.fsf.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @since 1.22
+ */
+
+/**
+ * A query action to return meta information about the foreign file repos
+ * configured on the wiki.
+ *
+ * @ingroup API
+ */
+class ApiQueryFileRepoInfo extends ApiQueryBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'fri' );
+ }
+
+ protected function getInitialisedRepoGroup() {
+ $repoGroup = RepoGroup::singleton();
+
+ if ( !$repoGroup->reposInitialised ) {
+ $repoGroup->initialiseRepos();
+ }
+
+ return $repoGroup;
+ }
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $props = array_flip( $params['prop'] );
+
+ $repos = array();
+
+ $repoGroup = $this->getInitialisedRepoGroup();
+
+ $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$repos, $props ) {
+ $repos[] = array_intersect_key( $repo->getInfo(), $props );
+ } );
+
+ $repos[] = array_intersect_key( $repoGroup->localRepo->getInfo(), $props );
+
+ $result = $this->getResult();
+ $result->setIndexedTagName( $repos, 'repo' );
+ $result->addValue( array( 'query' ), 'repos', $repos );
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ $props = $this->getProps();
+
+ return array(
+ 'prop' => array(
+ ApiBase::PARAM_DFLT => join( '|', $props ),
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => $props,
+ ),
+ );
+ }
+
+ public function getProps() {
+ $props = array();
+ $repoGroup = $this->getInitialisedRepoGroup();
+
+ $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$props ) {
+ $props = array_merge( $props, array_keys( $repo->getInfo() ) );
+ } );
+
+ return array_values( array_unique( array_merge( $props, array_keys( $repoGroup->localRepo->getInfo() ) ) ) );
+ }
+
+ public function getParamDescription() {
+ $p = $this->getModulePrefix();
+ return array(
+ 'prop' => array(
+ 'Which repository properties to get (there may be more available on some wikis):',
+ ' apiurl - URL to the repository API - helpful for getting image info from the host.',
+ ' name - The key of the repository - used in e.g. $wgForeignFileRepos and imageinfo return values.',
+ ' displayname - The human-readable name of the repository wiki.',
+ ' rooturl - Root URL for image paths.',
+ ' local - Whether that repository is the local one or not.',
+ ),
+ );
+ }
+
+ public function getDescription() {
+ return 'Return meta information about image repositories configured on the wiki.';
+ }
+
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&meta=filerepoinfo&friprop=apiurl|name|displayname',
+ );
+ }
+}
abstract class FileBackendStore extends FileBackend {
/** @var BagOStuff */
protected $memCache;
- /** @var ProcessCacheLRU */
- protected $cheapCache; // Map of paths to small (RAM/disk) cache items
- /** @var ProcessCacheLRU */
- protected $expensiveCache; // Map of paths to large (RAM/disk) cache items
+ /** @var ProcessCacheLRU Map of paths to small (RAM/disk) cache items */
+ protected $cheapCache;
+ /** @var ProcessCacheLRU Map of paths to large (RAM/disk) cache items */
+ protected $expensiveCache;
- /** @var Array Map of container names to sharding settings */
- protected $shardViaHashLevels = array(); // (container name => config array)
+ /** @var Array Map of container names to sharding config */
+ protected $shardViaHashLevels = array();
+
+ /** @var callback Method to get the MIME type of files */
+ protected $mimeCallback;
protected $maxFileSize = 4294967296; // integer bytes (4GiB)
/**
* @see FileBackend::__construct()
+ * Additional $config params include:
+ * - mimeCallback : Callback that takes (storage path, content, file system path) and
+ * returns the MIME type of the file or 'unknown/unknown'. The file
+ * system path parameter should be used if the content one is null.
*
* @param array $config
*/
public function __construct( array $config ) {
parent::__construct( $config );
+ $this->mimeCallback = isset( $config['mimeCallback'] )
+ ? $config['mimeCallback']
+ : function( $storagePath, $content, $fsPath ) {
+ // @TODO: handle the case of extension-less files using the contents
+ return StreamFile::contentTypeFromPath( $storagePath ) ?: 'unknown/unknown';
+ };
$this->memCache = new EmptyBagOStuff(); // disabled by default
$this->cheapCache = new ProcessCacheLRU( self::CACHE_CHEAP_SIZE );
$this->expensiveCache = new ProcessCacheLRU( self::CACHE_EXPENSIVE_SIZE );
}
return $opts;
}
+
+ /**
+ * Get the content type to use in HEAD/GET requests for a file
+ *
+ * @param string $storagePath
+ * @param string|null $content File data
+ * @param string|null $fsPath File system path
+ * @return MIME type
+ */
+ protected function getContentType( $storagePath, $content, $fsPath ) {
+ return call_user_func_array( $this->mimeCallback, func_get_args() );
+ }
}
/**
// The MD5 here will be checked within Swift against its own MD5.
$obj->set_etag( md5( $params['content'] ) );
// Use the same content type as StreamFile for security
- $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
- if ( !strlen( $obj->content_type ) ) { // special case
- $obj->content_type = 'unknown/unknown';
- }
+ $obj->content_type = $this->getContentType( $params['dst'], $params['content'], null );
// Set any other custom headers if requested
if ( isset( $params['headers'] ) ) {
$obj->headers += $this->sanitizeHdrs( $params['headers'] );
// The MD5 here will be checked within Swift against its own MD5.
$obj->set_etag( md5_file( $params['src'] ) );
// Use the same content type as StreamFile for security
- $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
- if ( !strlen( $obj->content_type ) ) { // special case
- $obj->content_type = 'unknown/unknown';
- }
+ $obj->content_type = $this->getContentType( $params['dst'], null, $params['src'] );
// Set any other custom headers if requested
if ( isset( $params['headers'] ) ) {
$obj->headers += $this->sanitizeHdrs( $params['headers'] );
* @throws MWException
*/
protected function assertWritableRepo() {}
+
+
+ /**
+ * Return information about the repository.
+ *
+ * @return array
+ * @since 1.22
+ */
+ public function getInfo() {
+ return array(
+ 'name' => $this->getName(),
+ 'displayname' => $this->getDisplayName(),
+ 'rootUrl' => $this->getRootUrl(),
+ 'local' => $this->isLocal(),
+ );
+ }
}
/**
}
}
+ /**
+ * @return string
+ * @since 1.22
+ */
+ function getApiUrl() {
+ return $this->mApiBase;
+ }
+
/**
* Per docs in FileRepo, this needs to return false if we don't support versioned
* files. Well, we don't.
return Http::userAgent() . " ForeignAPIRepo/" . self::VERSION;
}
+ /**
+ * Get information about the repo - overrides/extends the parent
+ * class's information.
+ * @return array
+ * @since 1.22
+ */
+ function getInfo() {
+ $info = parent::getInfo();
+ $info['apiurl'] = $this->getApiUrl();
+ return $info;
+ }
+
/**
* Like a Http:get request, but with custom User-Agent.
* @see Http:get
return array( $width, $height );
}
- /**
- * Function that returns the number of pixels to be thumbnailed.
- * Intended for animated GIFs to multiply by the number of frames.
- *
- * @param File $image
- * @return int
- */
- function getImageArea( $image ) {
- return $image->getWidth() * $image->getHeight();
- }
-
/**
* @param $image File
* @param $dstPath
" -depth 8 $sharpen " .
" -rotate -$rotation " .
" {$animation_post} " .
- wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ) . " 2>&1";
+ wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) );
wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
wfProfileIn( 'convert' );
$retval = 0;
- $err = wfShellExec( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval, $env );
wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" );
wfProfileIn( 'convert' );
$retval = 0;
- $err = wfShellExec( $cmd, $retval );
+ $err = wfShellExecWithStderr( $cmd, $retval );
wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
imagejpeg( $dst_image, $thumbPath, 95 );
}
- /**
- * On supporting image formats, try to read out the low-level orientation
- * of the file and return the angle that the file needs to be rotated to
- * be viewed.
- *
- * This information is only useful when manipulating the original file;
- * the width and height we normally work with is logical, and will match
- * any produced output views.
- *
- * The base BitmapHandler doesn't understand any metadata formats, so this
- * is left up to child classes to implement.
- *
- * @param $file File
- * @return int 0, 90, 180 or 270
- */
- public function getRotation( $file ) {
- return 0;
- }
/**
* Returns whether the current scaler supports rotation (im and gd do)
$cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " .
wfEscapeShellArg( $this->escapeMagickInput( $params['srcPath'], $scene ) ) .
" -rotate -$rotation " .
- wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ) . " 2>&1";
+ wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) );
wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
wfProfileIn( 'convert' );
$retval = 0;
- $err = wfShellExec( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval, $env );
wfProfileOut( 'convert' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
wfRestoreWarnings();
return $gis;
}
+ /**
+ * Function that returns the number of pixels to be thumbnailed.
+ * Intended for animated GIFs to multiply by the number of frames.
+ *
+ * If the file doesn't support a notion of "area" return 0.
+ *
+ * @param File $image
+ * @return int
+ */
+ function getImageArea( $image ) {
+ return $image->getWidth() * $image->getHeight();
+ }
+
/**
* @param $file File
$cmd = wfEscapeShellArg( $wgJpegTran ) .
" -rotate " . wfEscapeShellArg( $rotation ) .
" -outfile " . wfEscapeShellArg( $params['dstPath'] ) .
- " " . wfEscapeShellArg( $params['srcPath'] ) . " 2>&1";
+ " " . wfEscapeShellArg( $params['srcPath'] );
wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" );
wfProfileIn( 'jpegtran' );
$retval = 0;
- $err = wfShellExec( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval, $env );
wfProfileOut( 'jpegtran' );
if ( $retval !== 0 ) {
$this->logErrorForExternalProcess( $retval, $err, $cmd );
public static function canRotate() {
return false;
}
+
+ /**
+ * On supporting image formats, try to read out the low-level orientation
+ * of the file and return the angle that the file needs to be rotated to
+ * be viewed.
+ *
+ * This information is only useful when manipulating the original file;
+ * the width and height we normally work with is logical, and will match
+ * any produced output views.
+ *
+ * For files we don't know, we return 0.
+ *
+ * @param $file File
+ * @return int 0, 90, 180 or 270
+ */
+ public function getRotation( $file ) {
+ return 0;
+ }
+
}
wfEscapeShellArg( $srcPath ),
wfEscapeShellArg( $dstPath ) ),
$wgSVGConverters[$wgSVGConverter]
- ) . " 2>&1";
+ );
$env = array();
if ( $lang !== false ) {
wfProfileIn( 'rsvg' );
wfDebug( __METHOD__ . ": $cmd\n" );
- $err = wfShellExec( $cmd, $retval, $env );
+ $err = wfShellExecWithStderr( $cmd, $retval, $env );
wfProfileOut( 'rsvg' );
}
}
* @param $parserOutput ParserOutput
* @param $article Article
* @param $popts ParserOptions
+ * @param $cacheTime Time when the cache was generated
*/
- public function save( $parserOutput, $article, $popts ) {
+ public function save( $parserOutput, $article, $popts, $cacheTime = null ) {
$expire = $parserOutput->getCacheExpiry();
-
if ( $expire > 0 ) {
- $now = wfTimestampNow();
+ $cacheTime = $cacheTime ?: wfTimestampNow();
$optionsKey = new CacheTime;
$optionsKey->mUsedOptions = $parserOutput->getUsedOptions();
$optionsKey->updateCacheExpiry( $expire );
- $optionsKey->setCacheTime( $now );
- $parserOutput->setCacheTime( $now );
+ $optionsKey->setCacheTime( $cacheTime );
+ $parserOutput->setCacheTime( $cacheTime );
$optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
// Save the timestamp so that we don't have to load the revision row on view
$parserOutput->setTimestamp( $article->getTimestamp() );
- $parserOutput->mText .= "\n<!-- Saved in parser cache with key $parserOutputKey and timestamp $now -->\n";
- wfDebug( "Saved in parser cache with key $parserOutputKey and timestamp $now\n" );
+ $parserOutput->mText .= "\n<!-- Saved in parser cache with key $parserOutputKey and timestamp $cacheTime\n -->\n";
+ wfDebug( "Saved in parser cache with key $parserOutputKey and timestamp $cacheTime\n" );
// Save the parser output
$this->mMemc->set( $parserOutputKey, $parserOutput, $expire );
// If logging in and not on HTTPS, either redirect to it or offer a link.
global $wgSecureLogin;
- if (
- $this->mType !== 'signup' &&
- WebRequest::detectProtocol() !== 'https'
- ) {
+ if ( WebRequest::detectProtocol() !== 'https' ) {
$title = $this->getFullTitle();
$query = array(
'returnto' => $this->mReturnTo,
# NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
# that does not seem to be worth the pain.
# Ask me (Duesentrieb) about it if it's ever needed.
- $output = wfShellExec( "$command 2>&1", $exitCode );
+ $output = wfShellExecWithStderr( $command, $exitCode );
# map exit code to AV_xxx constants.
$mappedCode = $exitCode;
postFinished = false;
periRange = document.selection.createRange().duplicate();
- preRange = rangeForElementIE( e ),
+ preRange = rangeForElementIE( e );
// Move the end where we need it
preRange.setEndPoint( 'EndToStart', periRange );
'</div>' +
'<ul><li></li></ul>' +
'</div>';
- $( tocHtml ).appendTo( '#qunit-fixture' ),
- $toggleLink = $( '#togglelink' );
+ $( tocHtml ).appendTo( '#qunit-fixture' );
+ $toggleLink = $( '#togglelink' );
assert.strictEqual( $toggleLink.length, 1, 'Toggle link is appended to the page.' );
header( 'HTTP/1.1 500 Internal server error' );
}
if ( $wgShowHostnames ) {
+ header( 'X-MW-Thumbnail-Renderer: ' . wfHostname() );
$url = htmlspecialchars( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' );
$hostname = htmlspecialchars( wfHostname() );
$debug = "<!-- $url -->\n<!-- $hostname -->\n";