production.
=== Configuration changes in 1.30 ===
-* …
+* The C.UTF-8 locale should be used for $wgShellLocale, if available, to avoid
+ unexpected behavior when things use local-sensitive string comparisons. For
+ example, Scribunto considers "bar" < "Foo" in most locales since it ignores
+ case.
+* $wgShellLocale now affects LC_ALL rather than only LC_CTYPE. See
+ documentation of $wgShellLocale for details.
=== New features in 1.30 ===
* …
'MWCryptRand' => __DIR__ . '/includes/utils/MWCryptRand.php',
'MWDebug' => __DIR__ . '/includes/debug/MWDebug.php',
'MWDocGen' => __DIR__ . '/maintenance/mwdocgen.php',
- 'MWDocGenFilter' => __DIR__ . '/maintenance/mwdoc-filter.php',
'MWException' => __DIR__ . '/includes/exception/MWException.php',
'MWExceptionHandler' => __DIR__ . '/includes/exception/MWExceptionHandler.php',
'MWExceptionRenderer' => __DIR__ . '/includes/exception/MWExceptionRenderer.php',
$wgPhpCli = '/usr/bin/php';
/**
- * Locale for LC_CTYPE, to work around https://bugs.php.net/bug.php?id=45132
- * For Unix-like operating systems, set this to to a locale that has a UTF-8
- * character set. Only the character set is relevant.
- */
-$wgShellLocale = 'en_US.utf8';
+ * Locale for LC_ALL, to provide a known environment for locale-sensitive operations
+ *
+ * For Unix-like operating systems, this should be set to C.UTF-8 or an
+ * equivalent to provide the most consistent behavior for locale-sensitive
+ * C library operations across different-language wikis. If that locale is not
+ * available, use another locale that has a UTF-8 character set.
+ *
+ * This setting mainly affects the behavior of C library functions, including:
+ * - String collation (order when sorting using locale-sensitive comparison)
+ * - For example, whether "Å" and "A" are considered to be the same letter or
+ * different letters and if different whether it comes after "A" or after
+ * "Z", and whether sorting is case sensitive.
+ * - String character set (how characters beyond basic ASCII are represented)
+ * - We need this to be a UTF-8 character set to work around
+ * https://bugs.php.net/bug.php?id=45132
+ * - Language used for low-level error messages.
+ * - Formatting of date/time and numeric values (e.g. '.' versus ',' as the
+ * decimal separator)
+ *
+ * MediaWiki provides its own methods and classes to perform many
+ * locale-sensitive operations, which are designed to be able to vary locale
+ * based on wiki language or user preference:
+ * - MediaWiki's Collation class should generally be used instead of the C
+ * library collation functions when locale-sensitive sorting is needed.
+ * - MediaWiki's Message class should be used for localization of messages
+ * displayed to the user.
+ * - MediaWiki's Language class should be used for formatting numeric and
+ * date/time values.
+ *
+ * @note If multiple wikis are being served from the same process (e.g. the
+ * same fastCGI or Apache server), this setting must be the same on all those
+ * wikis.
+ * @see wfInitShellLocale()
+ */
+$wgShellLocale = 'C.UTF-8';
/** @} */ # End shell }
}
/**
- * Workaround for https://bugs.php.net/bug.php?id=45132
- * escapeshellarg() destroys non-ASCII characters if LANG is not a UTF-8 locale
+ * Set the locale for locale-sensitive operations
+ *
+ * Sets LC_ALL to a known value to work around issues like the following:
+ * - https://bugs.php.net/bug.php?id=45132 escapeshellarg() destroys non-ASCII
+ * characters if LANG is not a UTF-8 locale
+ * - T107128 Scribunto string comparison works case insensitive while the
+ * standard Lua case sensitive
+ *
+ * @see $wgShellLocale
*/
function wfInitShellLocale() {
static $done = false;
}
$done = true;
global $wgShellLocale;
- putenv( "LC_CTYPE=$wgShellLocale" );
- setlocale( LC_CTYPE, $wgShellLocale );
+ putenv( "LC_ALL=$wgShellLocale" );
+ setlocale( LC_ALL, $wgShellLocale );
}
/**
}
# Try the most common ones.
- $commonLocales = [ 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ];
+ $commonLocales = [ 'C.UTF-8', 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ];
foreach ( $commonLocales as $commonLocale ) {
if ( isset( $candidatesByLocale[$commonLocale] ) ) {
$this->setVar( 'wgShellLocale', $commonLocale );
}
if ( !$this->values['wgShellLocale'] ) {
- $this->values['wgShellLocale'] = 'en_US.UTF-8';
+ $this->values['wgShellLocale'] = 'C.UTF-8';
$locale = '#';
} else {
$locale = '';
class ExifBitmapHandler extends BitmapHandler {
const BROKEN_FILE = '-1'; // error extracting metadata
const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
- const SRGB_ICC_PROFILE_NAME = 'IEC 61966-2.1 Default RGB colour space - sRGB';
function convertMetadataVersion( $metadata, $version = 1 ) {
// basically flattens arrays.
return 0;
}
-
- protected function transformImageMagick( $image, $params ) {
- global $wgUseTinyRGBForJPGThumbnails;
-
- $ret = parent::transformImageMagick( $image, $params );
-
- if ( $ret ) {
- return $ret;
- }
-
- if ( $params['mimeType'] === 'image/jpeg' && $wgUseTinyRGBForJPGThumbnails ) {
- // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller
- // (and free) TinyRGB
-
- $this->swapICCProfile(
- $params['dstPath'],
- self::SRGB_ICC_PROFILE_NAME,
- realpath( __DIR__ ) . '/tinyrgb.icc'
- );
- }
-
- return false;
- }
-
- /**
- * Swaps an embedded ICC profile for another, if found.
- * Depends on exiftool, no-op if not installed.
- * @param string $filepath File to be manipulated (will be overwritten)
- * @param string $oldProfileString Exact name of color profile to look for
- * (the one that will be replaced)
- * @param string $profileFilepath ICC profile file to apply to the file
- * @since 1.26
- * @return bool
- */
- public function swapICCProfile( $filepath, $oldProfileString, $profileFilepath ) {
- global $wgExiftool;
-
- if ( !$wgExiftool || !is_executable( $wgExiftool ) ) {
- return false;
- }
-
- $cmd = wfEscapeShellArg( $wgExiftool,
- '-DeviceModelDesc',
- '-S',
- '-T',
- $filepath
- );
-
- $output = wfShellExecWithStderr( $cmd, $retval );
-
- if ( $retval !== 0 || strcasecmp( trim( $output ), $oldProfileString ) !== 0 ) {
- // We can't establish that this file has the expected ICC profile, don't process it
- return false;
- }
-
- $cmd = wfEscapeShellArg( $wgExiftool,
- '-overwrite_original',
- '-icc_profile<=' . $profileFilepath,
- $filepath
- );
-
- $output = wfShellExecWithStderr( $cmd, $retval );
-
- if ( $retval !== 0 ) {
- $this->logErrorForExternalProcess( $retval, $output, $cmd );
-
- return false;
- }
-
- return true;
- }
}
* @ingroup Media
*/
class JpegHandler extends ExifBitmapHandler {
+ const SRGB_EXIF_COLOR_SPACE = 'sRGB';
+ const SRGB_ICC_PROFILE_DESCRIPTION = 'sRGB IEC61966-2.1';
function normaliseParams( $image, &$params ) {
if ( !parent::normaliseParams( $image, $params ) ) {
return $params;
}
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function transformImageMagick( $image, $params ) {
+ global $wgUseTinyRGBForJPGThumbnails;
+
+ $ret = parent::transformImageMagick( $image, $params );
+
+ if ( $ret ) {
+ return $ret;
+ }
+
+ if ( $wgUseTinyRGBForJPGThumbnails ) {
+ // T100976 If the profile embedded in the JPG is sRGB, swap it for the smaller
+ // (and free) TinyRGB
+
+ /**
+ * We'll want to replace the color profile for JPGs:
+ * * in the sRGB color space, or with the sRGB profile
+ * (other profiles will be left untouched)
+ * * without color space or profile, in which case browsers
+ * should assume sRGB, but don't always do (e.g. on wide-gamut
+ * monitors (unless it's meant for low bandwith)
+ * @see https://phabricator.wikimedia.org/T134498
+ */
+ $colorSpaces = [ self::SRGB_EXIF_COLOR_SPACE, '-' ];
+ $profiles = [ self::SRGB_ICC_PROFILE_DESCRIPTION ];
+
+ // we'll also add TinyRGB profile to images lacking a profile, but
+ // only if they're not low quality (which are meant to save bandwith
+ // and we don't want to increase the filesize by adding a profile)
+ if ( $params['quality'] > 30 ) {
+ $profiles[] = '-';
+ }
+
+ $this->swapICCProfile(
+ $params['dstPath'],
+ $colorSpaces,
+ $profiles,
+ realpath( __DIR__ ) . '/tinyrgb.icc'
+ );
+ }
+
+ return false;
+ }
+
+ /**
+ * Swaps an embedded ICC profile for another, if found.
+ * Depends on exiftool, no-op if not installed.
+ * @param string $filepath File to be manipulated (will be overwritten)
+ * @param array $colorSpaces Only process files with this/these Color Space(s)
+ * @param array $oldProfileStrings Exact name(s) of color profile to look for
+ * (the one that will be replaced)
+ * @param string $profileFilepath ICC profile file to apply to the file
+ * @since 1.26
+ * @return bool
+ */
+ public function swapICCProfile( $filepath, array $colorSpaces,
+ array $oldProfileStrings, $profileFilepath
+ ) {
+ global $wgExiftool;
+
+ if ( !$wgExiftool || !is_executable( $wgExiftool ) ) {
+ return false;
+ }
+
+ $cmd = wfEscapeShellArg( $wgExiftool,
+ '-EXIF:ColorSpace',
+ '-ICC_Profile:ProfileDescription',
+ '-S',
+ '-T',
+ $filepath
+ );
+
+ $output = wfShellExecWithStderr( $cmd, $retval );
+
+ // Explode EXIF data into an array with [0 => Color Space, 1 => Device Model Desc]
+ $data = explode( "\t", trim( $output ) );
+
+ if ( $retval !== 0 ) {
+ return false;
+ }
+
+ // Make a regex out of the source data to match it to an array of color
+ // spaces in a case-insensitive way
+ $colorSpaceRegex = '/'.preg_quote( $data[0], '/' ).'/i';
+ if ( empty( preg_grep( $colorSpaceRegex, $colorSpaces ) ) ) {
+ // We can't establish that this file matches the color space, don't process it
+ return false;
+ }
+
+ $profileRegex = '/'.preg_quote( $data[1], '/' ).'/i';
+ if ( empty( preg_grep( $profileRegex, $oldProfileStrings ) ) ) {
+ // We can't establish that this file has the expected ICC profile, don't process it
+ return false;
+ }
+
+ $cmd = wfEscapeShellArg( $wgExiftool,
+ '-overwrite_original',
+ '-icc_profile<=' . $profileFilepath,
+ $filepath
+ );
+
+ $output = wfShellExecWithStderr( $cmd, $retval );
+
+ if ( $retval !== 0 ) {
+ $this->logErrorForExternalProcess( $retval, $output, $cmd );
+
+ return false;
+ }
+
+ return true;
+ }
}
'name' => 'changeType',
'title' => 'rcfilters-filtergroup-changetype',
'class' => ChangesListBooleanFilterGroup::class,
+ 'priority' => -8,
'filters' => [
[
'name' => 'hidepageedits',
],
],
- [
- 'name' => 'watchlist',
- 'title' => 'rcfilters-filtergroup-watchlist',
- 'class' => ChangesListStringOptionsFilterGroup::class,
- 'isFullCoverage' => true,
- 'filters' => [
- [
- 'name' => 'watched',
- 'label' => 'rcfilters-filter-watchlist-watched-label',
- 'description' => 'rcfilters-filter-watchlist-watched-description',
- 'cssClassSuffix' => 'watched',
- 'isRowApplicableCallable' => function ( $ctx, $rc ) {
- return $rc->getAttribute( 'wl_user' );
- }
- ],
- [
- 'name' => 'watchednew',
- 'label' => 'rcfilters-filter-watchlist-watchednew-label',
- 'description' => 'rcfilters-filter-watchlist-watchednew-description',
- 'cssClassSuffix' => 'watchednew',
- 'isRowApplicableCallable' => function ( $ctx, $rc ) {
- return $rc->getAttribute( 'wl_user' ) &&
- $rc->getAttribute( 'rc_timestamp' ) &&
- $rc->getAttribute( 'wl_notificationtimestamp' ) &&
- $rc->getAttribute( 'rc_timestamp' ) >= $rc->getAttribute( 'wl_notificationtimestamp' );
- },
- ],
- [
- 'name' => 'notwatched',
- 'label' => 'rcfilters-filter-watchlist-notwatched-label',
- 'description' => 'rcfilters-filter-watchlist-notwatched-description',
- 'cssClassSuffix' => 'notwatched',
- 'isRowApplicableCallable' => function ( $ctx, $rc ) {
- return $rc->getAttribute( 'wl_user' ) === null;
- },
- ]
- ],
- 'default' => ChangesListStringOptionsFilterGroup::NONE,
- 'queryCallable' => function ( $specialPageClassName, $context, $dbr,
- &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedValues ) {
- sort( $selectedValues );
- $notwatchedCond = 'wl_user IS NULL';
- $watchedCond = 'wl_user IS NOT NULL';
- $newCond = 'rc_timestamp >= wl_notificationtimestamp';
-
- if ( $selectedValues === [ 'notwatched' ] ) {
- $conds[] = $notwatchedCond;
- return;
- }
-
- if ( $selectedValues === [ 'watched' ] ) {
- $conds[] = $watchedCond;
- return;
- }
-
- if ( $selectedValues === [ 'watchednew' ] ) {
- $conds[] = $dbr->makeList( [
- $watchedCond,
- $newCond
- ], LIST_AND );
- return;
- }
-
- if ( $selectedValues === [ 'notwatched', 'watched' ] ) {
- // no filters
- return;
- }
-
- if ( $selectedValues === [ 'notwatched', 'watchednew' ] ) {
- $conds[] = $dbr->makeList( [
- $notwatchedCond,
- $dbr->makeList( [
- $watchedCond,
- $newCond
- ], LIST_AND )
- ], LIST_OR );
- return;
- }
-
- if ( $selectedValues === [ 'watched', 'watchednew' ] ) {
- $conds[] = $watchedCond;
- return;
- }
-
- if ( $selectedValues === [ 'notwatched', 'watched', 'watchednew' ] ) {
- // no filters
- return;
- }
- },
- ],
];
$this->reviewStatusFilterGroupDefinition = [
// Make sure this is not being transcluded (we don't want to show this
// information to all users just because the user that saves the edit can
- // patrol)
+ // patrol or is logged in)
if ( !$this->including() && $this->getUser()->useRCPatrol() ) {
$this->registerFiltersFromDefinitions( $this->reviewStatusFilterGroupDefinition );
}
'rcfilters-hideminor-conflicts-typeofchange',
'rcfilters-typeofchange-conflicts-hideminor'
);
-
- $watchlistGroup = $this->getFilterGroup( 'watchlist' );
- $watchlistGroup->getFilter( 'watched' )->setAsSupersetOf(
- $watchlistGroup->getFilter( 'watchednew' )
- );
}
/**
* @ingroup SpecialPage
*/
class SpecialRecentChanges extends ChangesListSpecialPage {
+
+ private $watchlistFilterGroupDefinition;
+
// @codingStandardsIgnoreStart Needed "useless" override to change parameters.
public function __construct( $name = 'Recentchanges', $restriction = '' ) {
parent::__construct( $name, $restriction );
+
+ $this->watchlistFilterGroupDefinition = [
+ 'name' => 'watchlist',
+ 'title' => 'rcfilters-filtergroup-watchlist',
+ 'class' => ChangesListStringOptionsFilterGroup::class,
+ 'priority' => -9,
+ 'isFullCoverage' => true,
+ 'filters' => [
+ [
+ 'name' => 'watched',
+ 'label' => 'rcfilters-filter-watchlist-watched-label',
+ 'description' => 'rcfilters-filter-watchlist-watched-description',
+ 'cssClassSuffix' => 'watched',
+ 'isRowApplicableCallable' => function ( $ctx, $rc ) {
+ return $rc->getAttribute( 'wl_user' );
+ }
+ ],
+ [
+ 'name' => 'watchednew',
+ 'label' => 'rcfilters-filter-watchlist-watchednew-label',
+ 'description' => 'rcfilters-filter-watchlist-watchednew-description',
+ 'cssClassSuffix' => 'watchednew',
+ 'isRowApplicableCallable' => function ( $ctx, $rc ) {
+ return $rc->getAttribute( 'wl_user' ) &&
+ $rc->getAttribute( 'rc_timestamp' ) &&
+ $rc->getAttribute( 'wl_notificationtimestamp' ) &&
+ $rc->getAttribute( 'rc_timestamp' ) >= $rc->getAttribute( 'wl_notificationtimestamp' );
+ },
+ ],
+ [
+ 'name' => 'notwatched',
+ 'label' => 'rcfilters-filter-watchlist-notwatched-label',
+ 'description' => 'rcfilters-filter-watchlist-notwatched-description',
+ 'cssClassSuffix' => 'notwatched',
+ 'isRowApplicableCallable' => function ( $ctx, $rc ) {
+ return $rc->getAttribute( 'wl_user' ) === null;
+ },
+ ]
+ ],
+ 'default' => ChangesListStringOptionsFilterGroup::NONE,
+ 'queryCallable' => function ( $specialPageClassName, $context, $dbr,
+ &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedValues ) {
+ sort( $selectedValues );
+ $notwatchedCond = 'wl_user IS NULL';
+ $watchedCond = 'wl_user IS NOT NULL';
+ $newCond = 'rc_timestamp >= wl_notificationtimestamp';
+
+ if ( $selectedValues === [ 'notwatched' ] ) {
+ $conds[] = $notwatchedCond;
+ return;
+ }
+
+ if ( $selectedValues === [ 'watched' ] ) {
+ $conds[] = $watchedCond;
+ return;
+ }
+
+ if ( $selectedValues === [ 'watchednew' ] ) {
+ $conds[] = $dbr->makeList( [
+ $watchedCond,
+ $newCond
+ ], LIST_AND );
+ return;
+ }
+
+ if ( $selectedValues === [ 'notwatched', 'watched' ] ) {
+ // no filters
+ return;
+ }
+
+ if ( $selectedValues === [ 'notwatched', 'watchednew' ] ) {
+ $conds[] = $dbr->makeList( [
+ $notwatchedCond,
+ $dbr->makeList( [
+ $watchedCond,
+ $newCond
+ ], LIST_AND )
+ ], LIST_OR );
+ return;
+ }
+
+ if ( $selectedValues === [ 'watched', 'watchednew' ] ) {
+ $conds[] = $watchedCond;
+ return;
+ }
+
+ if ( $selectedValues === [ 'notwatched', 'watched', 'watchednew' ] ) {
+ // no filters
+ return;
+ }
+ }
+ ];
}
// @codingStandardsIgnoreEnd
protected function registerFilters() {
parent::registerFilters();
+ if (
+ !$this->including() &&
+ $this->getUser()->isLoggedIn() &&
+ $this->getUser()->isAllowed( 'viewmywatchlist' )
+ ) {
+ $this->registerFiltersFromDefinitions( [ $this->watchlistFilterGroupDefinition ] );
+ $watchlistGroup = $this->getFilterGroup( 'watchlist' );
+ $watchlistGroup->getFilter( 'watched' )->setAsSupersetOf(
+ $watchlistGroup->getFilter( 'watchednew' )
+ );
+ }
+
$user = $this->getUser();
$significance = $this->getFilterGroup( 'significance' );
$fields = array_merge( RecentChange::selectFields(), $fields );
// JOIN on watchlist for users
- if ( $user->getId() && $user->isAllowed( 'viewmywatchlist' ) ) {
+ if ( $user->isLoggedIn() && $user->isAllowed( 'viewmywatchlist' ) ) {
$tables[] = 'watchlist';
$fields[] = 'wl_user';
$fields[] = 'wl_notificationtimestamp';
] ];
}
- if ( $user->isAllowed( 'rollback' ) ) {
- $tables[] = 'page';
- $fields[] = 'page_latest';
- $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
- }
+ // JOIN on page, used for 'last revision' filter highlight
+ $tables[] = 'page';
+ $fields[] = 'page_latest';
+ $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
ChangeTags::modifyDisplayQuery(
$tables,
* DEALINGS IN THE SOFTWARE.
*/
-require_once __DIR__ . '/Maintenance.php';
-
-/**
- * Maintenance script that builds doxygen documentation.
- * @ingroup Maintenance
- */
-class MWDocGenFilter extends Maintenance {
- public function __construct() {
- parent::__construct();
- $this->addDescription( 'Doxygen filter to fix member variable types in documentation. '
- . 'Used by mwdocgen.php'
- );
- $this->addArg( 'filename', 'PHP file to filter', true );
- }
+// Warning: Converting this to a Maintenance script may reduce performance.
+if ( PHP_SAPI != 'cli' ) {
+ die( "This filter can only be run from the command line.\n" );
+}
- public function execute() {
- $source = file_get_contents( $this->getArg( 0 ) );
- $tokens = token_get_all( $source );
+$source = file_get_contents( $argv[1] );
+$tokens = token_get_all( $source );
- $buffer = $bufferType = null;
- foreach ( $tokens as $token ) {
- if ( is_string( $token ) ) {
- if ( $buffer !== null && $token === ';' ) {
- // If we still have a buffer and the statement has ended,
- // flush it and move on.
- echo $buffer;
- $buffer = $bufferType = null;
- }
- echo $token;
- continue;
+$buffer = $bufferType = null;
+foreach ( $tokens as $token ) {
+ if ( is_string( $token ) ) {
+ if ( $buffer !== null && $token === ';' ) {
+ // If we still have a buffer and the statement has ended,
+ // flush it and move on.
+ echo $buffer;
+ $buffer = $bufferType = null;
+ }
+ echo $token;
+ continue;
+ }
+ list( $id, $content ) = $token;
+ switch ( $id ) {
+ case T_DOC_COMMENT:
+ // Escape slashes so that references to namespaces are not
+ // wrongly interpreted as a Doxygen "\command".
+ $content = addcslashes( $content, '\\' );
+ // Look for instances of "@var Type" not followed by $name.
+ if ( preg_match( '#@var\s+([^\s]+)\s+([^\$]+)#s', $content ) ) {
+ $buffer = preg_replace_callback(
+ // Strip the "@var Type" part and remember the type
+ '#(@var\s+)([^\s]+)#s',
+ function ( $matches ) use ( &$bufferType ) {
+ $bufferType = $matches[2];
+ return '';
+ },
+ $content
+ );
+ } else {
+ echo $content;
}
- list( $id, $content ) = $token;
- switch ( $id ) {
- case T_DOC_COMMENT:
- // Escape slashes so that references to namespaces are not
- // wrongly interpreted as a Doxygen "\command".
- $content = addcslashes( $content, '\\' );
- // Look for instances of "@var Type" not followed by $name.
- if ( preg_match( '#@var\s+([^\s]+)\s+([^\$]+)#s', $content ) ) {
- $buffer = preg_replace_callback(
- // Strip the "@var Type" part and remember the type
- '#(@var\s+)([^\s]+)#s',
- function ( $matches ) use ( &$bufferType ) {
- $bufferType = $matches[2];
- return '';
- },
- $content
- );
- } else {
- echo $content;
- }
- break;
+ break;
- case T_VARIABLE:
- if ( $buffer !== null ) {
- echo $buffer;
- echo "$bufferType $content";
- $buffer = $bufferType = null;
- } else {
- echo $content;
- }
- break;
+ case T_VARIABLE:
+ if ( $buffer !== null ) {
+ echo $buffer;
+ echo "$bufferType $content";
+ $buffer = $bufferType = null;
+ } else {
+ echo $content;
+ }
+ break;
- default:
- if ( $buffer !== null ) {
- $buffer .= $content;
- } else {
- echo $content;
- }
- break;
+ default:
+ if ( $buffer !== null ) {
+ $buffer .= $content;
+ } else {
+ echo $content;
}
- }
+ break;
}
}
-
-$maintClass = 'MWDocGenFilter';
-require_once RUN_MAINTENANCE_IF_MAIN;
}
protected function init() {
- global $IP;
+ global $wgPhpCli, $IP;
$this->doxygen = $this->getOption( 'doxygen', 'doxygen' );
$this->mwVersion = $this->getOption( 'version', 'master' );
$this->output = $this->getOption( 'output', "$IP/docs" );
- $this->inputFilter = wfShellWikiCmd( $IP . '/maintenance/mwdoc-filter.php' );
+ // Do not use wfShellWikiCmd, because mwdoc-filter.php is not
+ // a Maintenance script.
+ $this->inputFilter = wfEscapeShellArg( [
+ $wgPhpCli,
+ $IP . '/maintenance/mwdoc-filter.php'
+ ] );
+
$this->template = $IP . '/maintenance/Doxyfile';
$this->excludes = [
'vendor',
$res = $this->handler->convertMetadataVersion( $metadata, 1 );
$this->assertEquals( $expected, $res );
}
-
- /**
- * @dataProvider provideSwappingICCProfile
- * @covers ExifBitmapHandler::swapICCProfile
- */
- public function testSwappingICCProfile(
- $sourceFilename, $controlFilename, $newProfileFilename, $oldProfileName
- ) {
- global $wgExiftool;
-
- if ( !$wgExiftool || !is_file( $wgExiftool ) ) {
- $this->markTestSkipped( "Exiftool not installed, cannot test ICC profile swapping" );
- }
-
- $this->setMwGlobals( 'wgUseTinyRGBForJPGThumbnails', true );
-
- $sourceFilepath = $this->filePath . $sourceFilename;
- $controlFilepath = $this->filePath . $controlFilename;
- $profileFilepath = $this->filePath . $newProfileFilename;
- $filepath = $this->getNewTempFile();
-
- copy( $sourceFilepath, $filepath );
-
- $file = $this->dataFile( $sourceFilename, 'image/jpeg' );
- $this->handler->swapICCProfile( $filepath, $oldProfileName, $profileFilepath );
-
- $this->assertEquals(
- sha1( file_get_contents( $filepath ) ),
- sha1( file_get_contents( $controlFilepath ) )
- );
- }
-
- public function provideSwappingICCProfile() {
- return [
- // File with sRGB should end up with TinyRGB
- [
- 'srgb.jpg',
- 'tinyrgb.jpg',
- 'tinyrgb.icc',
- 'IEC 61966-2.1 Default RGB colour space - sRGB'
- ],
- // File with TinyRGB should be left unchanged
- [
- 'tinyrgb.jpg',
- 'tinyrgb.jpg',
- 'tinyrgb.icc',
- 'IEC 61966-2.1 Default RGB colour space - sRGB'
- ],
- // File with no profile should be left unchanged
- [
- 'test.jpg',
- 'test.jpg',
- 'tinyrgb.icc',
- 'IEC 61966-2.1 Default RGB colour space - sRGB'
- ]
- ];
- }
}
$this->assertEquals( $res, $expected );
}
+
+ /**
+ * @dataProvider provideSwappingICCProfile
+ * @covers ExifBitmapHandler::swapICCProfile
+ */
+ public function testSwappingICCProfile(
+ $sourceFilename, $controlFilename, $newProfileFilename, $oldProfileName
+ ) {
+ global $wgExiftool;
+
+ if ( !$wgExiftool || !is_file( $wgExiftool ) ) {
+ $this->markTestSkipped( "Exiftool not installed, cannot test ICC profile swapping" );
+ }
+
+ $this->setMwGlobals( 'wgUseTinyRGBForJPGThumbnails', true );
+
+ $sourceFilepath = $this->filePath . $sourceFilename;
+ $controlFilepath = $this->filePath . $controlFilename;
+ $profileFilepath = $this->filePath . $newProfileFilename;
+ $filepath = $this->getNewTempFile();
+
+ copy( $sourceFilepath, $filepath );
+
+ $file = $this->dataFile( $sourceFilename, 'image/jpeg' );
+ $this->handler->swapICCProfile(
+ $filepath,
+ [ 'sRGB', '-' ],
+ [ $oldProfileName ],
+ $profileFilepath
+ );
+
+ $this->assertEquals(
+ sha1( file_get_contents( $filepath ) ),
+ sha1( file_get_contents( $controlFilepath ) )
+ );
+ }
+
+ public function provideSwappingICCProfile() {
+ return [
+ // File with sRGB should end up with TinyRGB
+ [
+ 'srgb.jpg',
+ 'tinyrgb.jpg',
+ 'tinyrgb.icc',
+ 'sRGB IEC61966-2.1'
+ ],
+ // File with TinyRGB should be left unchanged
+ [
+ 'tinyrgb.jpg',
+ 'tinyrgb.jpg',
+ 'tinyrgb.icc',
+ 'sRGB IEC61966-2.1'
+ ],
+ // File without profile should end up with TinyRGB
+ [
+ 'missingprofile.jpg',
+ 'tinyrgb.jpg',
+ 'tinyrgb.icc',
+ 'sRGB IEC61966-2.1'
+ ],
+ // Non-sRGB file should be left untouched
+ [
+ 'adobergb.jpg',
+ 'adobergb.jpg',
+ 'tinyrgb.icc',
+ 'sRGB IEC61966-2.1'
+ ]
+ ];
+ }
}