From f6d65e533c62f6deb21342d4901ece24497b433e Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Thu, 4 Jun 2015 07:31:04 +0200 Subject: Update to MediaWiki 1.25.1 --- includes/media/BMP.php | 2 +- includes/media/Bitmap.php | 14 +--- includes/media/BitmapMetadataHandler.php | 2 +- includes/media/DjVu.php | 5 +- includes/media/DjVuImage.php | 16 ++-- includes/media/Exif.php | 2 +- includes/media/ExifBitmap.php | 5 +- includes/media/FormatMetadata.php | 98 ++++++++++++++-------- includes/media/GIF.php | 5 +- includes/media/GIFMetadataExtractor.php | 2 +- includes/media/IPTC.php | 2 +- includes/media/ImageHandler.php | 6 +- includes/media/Jpeg.php | 4 +- includes/media/JpegMetadataExtractor.php | 4 +- includes/media/MediaHandler.php | 34 ++++++-- .../MediaTransformInvalidParametersException.php | 26 ++++++ includes/media/MediaTransformOutput.php | 40 +++++++-- includes/media/PNG.php | 10 ++- includes/media/SVG.php | 9 +- includes/media/SVGMetadataExtractor.php | 20 ++--- includes/media/Tiff.php | 2 +- includes/media/TransformationalImageHandler.php | 75 +++++++++++------ includes/media/XCF.php | 2 +- includes/media/XMP.php | 13 +-- includes/media/XMPInfo.php | 2 +- 25 files changed, 262 insertions(+), 138 deletions(-) create mode 100644 includes/media/MediaTransformInvalidParametersException.php (limited to 'includes/media') diff --git a/includes/media/BMP.php b/includes/media/BMP.php index d8b0ba64..52f9518f 100644 --- a/includes/media/BMP.php +++ b/includes/media/BMP.php @@ -71,7 +71,7 @@ class BmpHandler extends BitmapHandler { try { $w = wfUnpack( 'V', $w, 4 ); $h = wfUnpack( 'V', $h, 4 ); - } catch ( MWException $e ) { + } catch ( Exception $e ) { return false; } diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php index e81b37de..eadcf94b 100644 --- a/includes/media/Bitmap.php +++ b/includes/media/Bitmap.php @@ -142,7 +142,7 @@ class BitmapHandler extends TransformationalImageHandler { $env['MAGICK_TMPDIR'] = $wgImageMagickTempDir; } - $rotation = $this->getRotation( $image ); + $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image ); list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation ); $cmd = call_user_func_array( 'wfEscapeShellArg', array_merge( @@ -169,10 +169,8 @@ class BitmapHandler extends TransformationalImageHandler { array( $this->escapeMagickOutput( $params['dstPath'] ) ) ) ); wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" ); - wfProfileIn( 'convert' ); $retval = 0; $err = wfShellExecWithStderr( $cmd, $retval, $env ); - wfProfileOut( 'convert' ); if ( $retval !== 0 ) { $this->logErrorForExternalProcess( $retval, $err, $cmd ); @@ -204,7 +202,7 @@ class BitmapHandler extends TransformationalImageHandler { / ( $params['srcWidth'] + $params['srcHeight'] ) < $wgSharpenReductionThreshold ) { - // Hack, since $wgSharpenParamater is written specifically for the command line convert + // Hack, since $wgSharpenParameter is written specifically for the command line convert list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter ); $im->sharpenImage( $radius, $sigma ); } @@ -223,7 +221,7 @@ class BitmapHandler extends TransformationalImageHandler { } } - $rotation = $this->getRotation( $image ); + $rotation = isset( $params['disableRotation'] ) ? 0 : $this->getRotation( $image ); list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation ); $im->setImageBackgroundColor( new ImagickPixel( 'white' ) ); @@ -280,10 +278,8 @@ class BitmapHandler extends TransformationalImageHandler { $cmd = str_replace( '%h', wfEscapeShellArg( $params['physicalHeight'] ), str_replace( '%w', wfEscapeShellArg( $params['physicalWidth'] ), $cmd ) ); # Size wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" ); - wfProfileIn( 'convert' ); $retval = 0; $err = wfShellExecWithStderr( $cmd, $retval ); - wfProfileOut( 'convert' ); if ( $retval !== 0 ) { $this->logErrorForExternalProcess( $retval, $err, $cmd ); @@ -344,7 +340,7 @@ class BitmapHandler extends TransformationalImageHandler { $src_image = call_user_func( $loader, $params['srcPath'] ); - $rotation = function_exists( 'imagerotate' ) ? $this->getRotation( $image ) : 0; + $rotation = function_exists( 'imagerotate' ) && !isset( $params['disableRotation'] ) ? $this->getRotation( $image ) : 0; list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation ); $dst_image = imagecreatetruecolor( $width, $height ); @@ -457,10 +453,8 @@ class BitmapHandler extends TransformationalImageHandler { " -rotate " . wfEscapeShellArg( "-$rotation" ) . " " . wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ); wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" ); - wfProfileIn( 'convert' ); $retval = 0; $err = wfShellExecWithStderr( $cmd, $retval ); - wfProfileOut( 'convert' ); if ( $retval !== 0 ) { $this->logErrorForExternalProcess( $retval, $err, $cmd ); diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php index 1d790155..c8d37bbb 100644 --- a/includes/media/BitmapMetadataHandler.php +++ b/includes/media/BitmapMetadataHandler.php @@ -61,7 +61,7 @@ class BitmapMetadataHandler { private function doApp13( $app13 ) { try { $this->iptcType = JpegMetadataExtractor::doPSIR( $app13 ); - } catch ( MWException $e ) { + } catch ( Exception $e ) { // Error reading the iptc hash information. // This probably means the App13 segment is something other than what we expect. // However, still try to read it, and treat it as if the hash didn't exist. diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php index daeb475f..1b0eb492 100644 --- a/includes/media/DjVu.php +++ b/includes/media/DjVu.php @@ -221,11 +221,9 @@ class DjVuHandler extends ImageHandler { $cmd .= " | {$wgDjvuPostProcessor}"; } $cmd .= ' > ' . wfEscapeShellArg( $dstPath ) . ') 2>&1'; - wfProfileIn( 'ddjvu' ); wfDebug( __METHOD__ . ": $cmd\n" ); $retval = ''; $err = wfShellExec( $cmd, $retval ); - wfProfileOut( 'ddjvu' ); $removed = $this->removeBadFile( $dstPath, $retval ); if ( $retval != 0 || $removed ) { @@ -266,6 +264,7 @@ class DjVuHandler extends ImageHandler { * * @param File $file The DjVu file in question * @return string XML metadata as a string. + * @throws MWException */ private function getUnserializedMetadata( File $file ) { $metadata = $file->getMetadata(); @@ -312,7 +311,6 @@ class DjVuHandler extends ImageHandler { return false; } - wfProfileIn( __METHOD__ ); wfSuppressWarnings(); try { @@ -338,7 +336,6 @@ class DjVuHandler extends ImageHandler { wfDebug( "Bogus multipage XML metadata on '{$image->getName()}'\n" ); } wfRestoreWarnings(); - wfProfileOut( __METHOD__ ); if ( $gettext ) { return $image->djvuTextTree; } else { diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php index 6ff19c90..e8faa70a 100644 --- a/includes/media/DjVuImage.php +++ b/includes/media/DjVuImage.php @@ -265,37 +265,34 @@ class DjVuImage { /** * Return an XML string describing the DjVu image - * @return string + * @return string|bool */ function retrieveMetaData() { global $wgDjvuToXML, $wgDjvuDump, $wgDjvuTxt; - wfProfileIn( __METHOD__ ); + + if ( !$this->isValid() ) { + return false; + } if ( isset( $wgDjvuDump ) ) { # djvudump is faster as of version 3.5 # http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583 - wfProfileIn( 'djvudump' ); $cmd = wfEscapeShellArg( $wgDjvuDump ) . ' ' . wfEscapeShellArg( $this->mFilename ); $dump = wfShellExec( $cmd ); $xml = $this->convertDumpToXML( $dump ); - wfProfileOut( 'djvudump' ); } elseif ( isset( $wgDjvuToXML ) ) { - wfProfileIn( 'djvutoxml' ); $cmd = wfEscapeShellArg( $wgDjvuToXML ) . ' --without-anno --without-text ' . wfEscapeShellArg( $this->mFilename ); $xml = wfShellExec( $cmd ); - wfProfileOut( 'djvutoxml' ); } else { $xml = null; } # Text layer if ( isset( $wgDjvuTxt ) ) { - wfProfileIn( 'djvutxt' ); $cmd = wfEscapeShellArg( $wgDjvuTxt ) . ' --detail=page ' . wfEscapeShellArg( $this->mFilename ); wfDebug( __METHOD__ . ": $cmd\n" ); $retval = ''; $txt = wfShellExec( $cmd, $retval, array(), array( 'memory' => self::DJVUTXT_MEMORY_LIMIT ) ); - wfProfileOut( 'djvutxt' ); if ( $retval == 0 ) { # Strip some control characters $txt = preg_replace( "/[\013\035\037]/", "", $txt ); @@ -316,14 +313,13 @@ EOR; $xml = $xml . $txt . ''; } } - wfProfileOut( __METHOD__ ); return $xml; } function pageTextCallback( $matches ) { # Get rid of invalid UTF-8, strip control characters - $val = htmlspecialchars( UtfNormal::cleanUp( stripcslashes( $matches[1] ) ) ); + $val = htmlspecialchars( UtfNormal\Validator::cleanUp( stripcslashes( $matches[1] ) ) ); $val = str_replace( array( "\n", '�' ), array( ' ', '' ), $val ); return ''; } diff --git a/includes/media/Exif.php b/includes/media/Exif.php index 018b58c5..33868689 100644 --- a/includes/media/Exif.php +++ b/includes/media/Exif.php @@ -477,7 +477,7 @@ class Exif { } else { // if valid utf-8, assume that, otherwise assume windows-1252 $valCopy = $val; - UtfNormal::quickIsNFCVerify( $valCopy ); //validates $valCopy. + UtfNormal\Validator::quickIsNFCVerify( $valCopy ); //validates $valCopy. if ( $valCopy !== $val ) { wfSuppressWarnings(); $val = iconv( 'Windows-1252', 'UTF-8//IGNORE', $val ); diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php index b7657cb3..f56a947f 100644 --- a/includes/media/ExifBitmap.php +++ b/includes/media/ExifBitmap.php @@ -125,15 +125,16 @@ class ExifBitmapHandler extends BitmapHandler { /** * @param File $image + * @param bool|IContextSource $context Context to use (optional) * @return array|bool */ - function formatMetadata( $image ) { + function formatMetadata( $image, $context = false ) { $meta = $this->getCommonMetaArray( $image ); if ( count( $meta ) === 0 ) { return false; } - return $this->formatMetadataHelper( $meta ); + return $this->formatMetadataHelper( $meta, $context ); } public function getCommonMetaArray( File $file ) { diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php index 43569539..501bb9c2 100644 --- a/includes/media/FormatMetadata.php +++ b/includes/media/FormatMetadata.php @@ -1595,11 +1595,8 @@ class FormatMetadata extends ContextSource { public function fetchExtendedMetadata( File $file ) { global $wgMemc; - wfProfileIn( __METHOD__ ); - // If revision deleted, exit immediately if ( $file->isDeleted( File::DELETED_FILE ) ) { - wfProfileOut( __METHOD__ ); return array(); } @@ -1614,7 +1611,7 @@ class FormatMetadata extends ContextSource { $cachedValue = $wgMemc->get( $cacheKey ); if ( $cachedValue - && wfRunHooks( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) ) + && Hooks::run( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) ) ) { $extendedMetadata = $cachedValue['data']; } else { @@ -1624,17 +1621,16 @@ class FormatMetadata extends ContextSource { if ( $this->singleLang ) { $this->resolveMultilangMetadata( $extendedMetadata ); } + $this->discardMultipleValues( $extendedMetadata ); // Make sure the metadata won't break the API when an XML format is used. // This is an API-specific function so it would be cleaner to call it from // outside fetchExtendedMetadata, but this way we don't need to redo the // computation on a cache hit. - $this->sanitizeArrayForXml( $extendedMetadata ); + $this->sanitizeArrayForAPI( $extendedMetadata ); $valueToCache = array( 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() ); $wgMemc->set( $cacheKey, $valueToCache, $maxCacheTime ); } - wfProfileOut( __METHOD__ ); - return $extendedMetadata; } @@ -1656,8 +1652,6 @@ class FormatMetadata extends ContextSource { return $file->getExtendedMetadata() ?: array(); } - wfProfileIn( __METHOD__ ); - $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() ); $fileMetadata = array( @@ -1685,19 +1679,6 @@ class FormatMetadata extends ContextSource { ); } - $common = $file->getCommonMetaArray(); - - if ( $common !== false ) { - foreach ( $common as $key => $value ) { - $fileMetadata[$key] = array( - 'value' => $value, - 'source' => 'file-metadata', - ); - } - } - - wfProfileOut( __METHOD__ ); - return $fileMetadata; } @@ -1714,9 +1695,8 @@ class FormatMetadata extends ContextSource { protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata, &$maxCacheTime ) { - wfProfileIn( __METHOD__ ); - wfRunHooks( 'GetExtendedMetadata', array( + Hooks::run( 'GetExtendedMetadata', array( &$extendedMetadata, $file, $this->getContext(), @@ -1731,8 +1711,6 @@ class FormatMetadata extends ContextSource { } } - wfProfileOut( __METHOD__ ); - return $extendedMetadata; } @@ -1776,6 +1754,32 @@ class FormatMetadata extends ContextSource { return null; } + /** + * Turns an XMP-style multivalue array into a single value by dropping all but the first value. + * If the value is not a multivalue array (or a multivalue array inside a multilang array), it is returned unchanged. + * See mediawiki.org/wiki/Manual:File_metadata_handling#Multi-language_array_format + * @param mixed $value + * @return mixed The value, or the first value if there were multiple ones + * @since 1.25 + */ + protected function resolveMultivalueValue( $value ) { + if ( !is_array( $value ) ) { + return $value; + } elseif ( isset( $value['_type'] ) && $value['_type'] === 'lang' ) { // if this is a multilang array, process fields separately + $newValue = array(); + foreach ( $value as $k => $v ) { + $newValue[$k] = $this->resolveMultivalueValue( $v ); + } + return $newValue; + } else { // _type is 'ul' or 'ol' or missing in which case it defaults to 'ul' + list( $k, $v ) = each( $value ); + if ( $k === '_type' ) { + $v = current( $value ); + } + return $v; + } + } + /** * Takes an array returned by the getExtendedMetadata* functions, * and resolves multi-language values in it. @@ -1793,19 +1797,41 @@ class FormatMetadata extends ContextSource { } } + /** + * Takes an array returned by the getExtendedMetadata* functions, + * and turns all fields into single-valued ones by dropping extra values. + * @param array $metadata + * @since 1.25 + */ + protected function discardMultipleValues( &$metadata ) { + if ( !is_array( $metadata ) ) { + return; + } + foreach ( $metadata as $key => &$field ) { + if ( $key === 'Software' || $key === 'Contact' ) { + // we skip some fields which have composite values. They are not particularly interesting + // and you can get them via the metadata / commonmetadata APIs anyway. + continue; + } + if ( isset( $field['value'] ) ) { + $field['value'] = $this->resolveMultivalueValue( $field['value'] ); + } + } + + } + /** * Makes sure the given array is a valid API response fragment - * (can be transformed into XML) * @param array $arr */ - protected function sanitizeArrayForXml( &$arr ) { + protected function sanitizeArrayForAPI( &$arr ) { if ( !is_array( $arr ) ) { return; } $counter = 1; foreach ( $arr as $key => &$value ) { - $sanitizedKey = $this->sanitizeKeyForXml( $key ); + $sanitizedKey = $this->sanitizeKeyForAPI( $key ); if ( $sanitizedKey !== $key ) { if ( isset( $arr[$sanitizedKey] ) ) { // Make the sanitized keys hopefully unique. @@ -1819,20 +1845,24 @@ class FormatMetadata extends ContextSource { unset( $arr[$key] ); } if ( is_array( $value ) ) { - $this->sanitizeArrayForXml( $value ); + $this->sanitizeArrayForAPI( $value ); } } + + // Handle API metadata keys (particularly "_type") + $keys = array_filter( array_keys( $arr ), 'ApiResult::isMetadataKey' ); + if ( $keys ) { + ApiResult::setPreserveKeysList( $arr, $keys ); + } } /** - * Turns a string into a valid XML identifier. - * Used to ensure that keys of an associative array in the - * API response do not break the XML formatter. + * Turns a string into a valid API identifier. * @param string $key * @return string * @since 1.23 */ - protected function sanitizeKeyForXml( $key ) { + protected function sanitizeKeyForAPI( $key ) { // drop all characters which are not valid in an XML tag name // a bunch of non-ASCII letters would be valid but probably won't // be used so we take the easy way diff --git a/includes/media/GIF.php b/includes/media/GIF.php index 5992be11..e3621fbd 100644 --- a/includes/media/GIF.php +++ b/includes/media/GIF.php @@ -44,15 +44,16 @@ class GIFHandler extends BitmapHandler { /** * @param File $image + * @param bool|IContextSource $context Context to use (optional) * @return array|bool */ - function formatMetadata( $image ) { + function formatMetadata( $image, $context = false ) { $meta = $this->getCommonMetaArray( $image ); if ( count( $meta ) === 0 ) { return false; } - return $this->formatMetadataHelper( $meta ); + return $this->formatMetadataHelper( $meta, $context ); } /** diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php index 178b0bf7..5c370465 100644 --- a/includes/media/GIFMetadataExtractor.php +++ b/includes/media/GIFMetadataExtractor.php @@ -158,7 +158,7 @@ class GIFMetadataExtractor { // assume its that, otherwise assume its windows-1252 (iso-8859-1) $dataCopy = $data; // quickIsNFCVerify has the side effect of replacing any invalid characters - UtfNormal::quickIsNFCVerify( $dataCopy ); + UtfNormal\Validator::quickIsNFCVerify( $dataCopy ); if ( $dataCopy !== $data ) { wfSuppressWarnings(); diff --git a/includes/media/IPTC.php b/includes/media/IPTC.php index 478249fe..0eb27cc8 100644 --- a/includes/media/IPTC.php +++ b/includes/media/IPTC.php @@ -456,7 +456,7 @@ class IPTC { //treat as utf-8 if is valid utf-8. otherwise pretend its windows-1252 // most of the time if there is no 1:90 tag, it is either ascii, latin1, or utf-8 $oldData = $data; - UtfNormal::quickIsNFCVerify( $data ); //make $data valid utf-8 + UtfNormal\Validator::quickIsNFCVerify( $data ); //make $data valid utf-8 if ( $data === $oldData ) { return $data; //if validation didn't change $data } else { diff --git a/includes/media/ImageHandler.php b/includes/media/ImageHandler.php index 6dd0453e..968e4243 100644 --- a/includes/media/ImageHandler.php +++ b/includes/media/ImageHandler.php @@ -57,7 +57,7 @@ abstract class ImageHandler extends MediaHandler { } elseif ( isset( $params['width'] ) ) { $width = $params['width']; } else { - throw new MWException( 'No width specified to ' . __METHOD__ ); + throw new MediaTransformInvalidParametersException( 'No width specified to ' . __METHOD__ ); } # Removed for ProofreadPage @@ -245,11 +245,11 @@ abstract class ImageHandler extends MediaHandler { if ( $pages === false || $pages <= 1 ) { $msg = wfMessage( 'file-info-size' )->numParams( $file->getWidth(), $file->getHeight() )->params( $size, - $file->getMimeType() )->parse(); + '' . $file->getMimeType() . '' )->parse(); } else { $msg = wfMessage( 'file-info-size-pages' )->numParams( $file->getWidth(), $file->getHeight() )->params( $size, - $file->getMimeType() )->numParams( $pages )->parse(); + '' . $file->getMimeType() . '' )->numParams( $pages )->parse(); } return $msg; diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php index fbdbdfe3..5463922b 100644 --- a/includes/media/Jpeg.php +++ b/includes/media/Jpeg.php @@ -106,7 +106,7 @@ class JpegHandler extends ExifBitmapHandler { $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version(); return serialize( $meta ); - } catch ( MWException $e ) { + } catch ( Exception $e ) { // BitmapMetadataHandler throws an exception in certain exceptional // cases like if file does not exist. wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" ); @@ -143,10 +143,8 @@ class JpegHandler extends ExifBitmapHandler { " -outfile " . wfEscapeShellArg( $params['dstPath'] ) . " " . wfEscapeShellArg( $params['srcPath'] ); wfDebug( __METHOD__ . ": running jpgtran: $cmd\n" ); - wfProfileIn( 'jpegtran' ); $retval = 0; $err = wfShellExecWithStderr( $cmd, $retval ); - wfProfileOut( 'jpegtran' ); if ( $retval !== 0 ) { $this->logErrorForExternalProcess( $retval, $err, $cmd ); diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php index aaa9930a..ae4af8d1 100644 --- a/includes/media/JpegMetadataExtractor.php +++ b/includes/media/JpegMetadataExtractor.php @@ -98,7 +98,7 @@ class JpegMetadataExtractor { // First see if valid utf-8, // if not try to convert it to windows-1252. $com = $oldCom = trim( self::jpegExtractMarker( $fh ) ); - UtfNormal::quickIsNFCVerify( $com ); + UtfNormal\Validator::quickIsNFCVerify( $com ); // turns $com to valid utf-8. // thus if no change, its utf-8, otherwise its something else. if ( $com !== $oldCom ) { @@ -108,7 +108,7 @@ class JpegMetadataExtractor { } // Try it again, if its still not a valid string, then probably // binary junk or some really weird encoding, so don't extract. - UtfNormal::quickIsNFCVerify( $com ); + UtfNormal\Validator::quickIsNFCVerify( $com ); if ( $com === $oldCom ) { $segments["COM"][] = $oldCom; } else { diff --git a/includes/media/MediaHandler.php b/includes/media/MediaHandler.php index 64ca0115..33aed34f 100644 --- a/includes/media/MediaHandler.php +++ b/includes/media/MediaHandler.php @@ -162,7 +162,7 @@ abstract class MediaHandler { */ static function getMetadataVersion() { $version = array( '2' ); // core metadata version - wfRunHooks( 'GetMetadataVersion', array( &$version ) ); + Hooks::run( 'GetMetadataVersion', array( &$version ) ); return implode( ';', $version ); } @@ -507,9 +507,10 @@ abstract class MediaHandler { * to some standard. That makes it possible to do things like visual * indication of grouped and chained streams in ogg container files. * @param File $image + * @param bool|IContextSource $context Context to use (optional) * @return array|bool */ - function formatMetadata( $image ) { + function formatMetadata( $image, $context = false ) { return false; } @@ -520,15 +521,16 @@ abstract class MediaHandler { * This is used by the media handlers that use the FormatMetadata class * * @param array $metadataArray Metadata array + * @param bool|IContextSource $context Context to use (optional) * @return array Array for use displaying metadata. */ - function formatMetadataHelper( $metadataArray ) { + function formatMetadataHelper( $metadataArray, $context = false ) { $result = array( 'visible' => array(), 'collapsed' => array() ); - $formatted = FormatMetadata::getFormattedData( $metadataArray ); + $formatted = FormatMetadata::getFormattedData( $metadataArray, $context ); // Sort fields into visible and collapsed $visibleFields = $this->visibleMetadataFields(); foreach ( $formatted as $name => $value ) { @@ -637,7 +639,7 @@ abstract class MediaHandler { */ static function getGeneralLongDesc( $file ) { return wfMessage( 'file-info' )->sizeParams( $file->getSize() ) - ->params( $file->getMimeType() )->parse(); + ->params( '' . $file->getMimeType() . '' )->parse(); } /** @@ -859,4 +861,26 @@ abstract class MediaHandler { public function sanitizeParamsForBucketing( $params ) { return $params; } + + /** + * Gets configuration for the file warning message. Return value of + * the following structure: + * array( + * 'module' => 'example.filewarning.messages', // Required, module with messages loaded for the client + * 'messages' => array( // Required, array of names of messages + * 'main' => 'example-filewarning-main', // Required, main warning message + * 'header' => 'example-filewarning-header', // Optional, header for warning dialog + * 'footer' => 'example-filewarning-footer', // Optional, footer for warning dialog + * 'info' => 'example-filewarning-info', // Optional, text for more-information link (see below) + * ), + * 'link' => 'http://example.com', // Optional, link for more information + * ) + * + * Returns null if no warning is necessary. + * @param File $file + * @return array|null + */ + public function getWarningConfig( $file ) { + return null; + } } diff --git a/includes/media/MediaTransformInvalidParametersException.php b/includes/media/MediaTransformInvalidParametersException.php new file mode 100644 index 00000000..15a2ca55 --- /dev/null +++ b/includes/media/MediaTransformInvalidParametersException.php @@ -0,0 +1,26 @@ + $alt, + 'src' => $this->url, + ); if ( !empty( $options['custom-url-link'] ) ) { $linkAttribs = array( 'href' => $options['custom-url-link'] ); @@ -381,13 +386,11 @@ class ThumbnailImage extends MediaTransformOutput { $linkAttribs = array( 'href' => $this->file->getURL() ); } else { $linkAttribs = false; + if ( !empty( $options['title'] ) ) { + $attribs['title'] = $options['title']; + } } - $attribs = array( - 'alt' => $alt, - 'src' => $this->url, - ); - if ( empty( $options['no-dimensions'] ) ) { $attribs['width'] = $this->width; $attribs['height'] = $this->height; @@ -410,7 +413,7 @@ class ThumbnailImage extends MediaTransformOutput { $attribs['srcset'] = Html::srcSet( $this->responsiveUrls ); } - wfRunHooks( 'ThumbnailBeforeProduceHTML', array( $this, &$attribs, &$linkAttribs ) ); + Hooks::run( 'ThumbnailBeforeProduceHTML', array( $this, &$attribs, &$linkAttribs ) ); return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) ); } @@ -474,3 +477,24 @@ class TransformParameterError extends MediaTransformError { wfMessage( 'thumbnail_invalid_params' )->text() ); } } + +/** + * Shortcut class for parameter file size errors + * + * @ingroup Media + * @since 1.25 + */ +class TransformTooBigImageAreaError extends MediaTransformError { + function __construct( $params, $maxImageArea ) { + $msg = wfMessage( 'thumbnail_toobigimagearea' ); + + parent::__construct( 'thumbnail_error', + max( isset( $params['width'] ) ? $params['width'] : 0, 120 ), + max( isset( $params['height'] ) ? $params['height'] : 0, 120 ), + $msg->rawParams( + $msg->getLanguage()->formatComputingNumbers( + $maxImageArea, 1000, "size-$1pixel" ) + )->text() + ); + } +} diff --git a/includes/media/PNG.php b/includes/media/PNG.php index 7b3ddb51..5f1aca57 100644 --- a/includes/media/PNG.php +++ b/includes/media/PNG.php @@ -49,15 +49,16 @@ class PNGHandler extends BitmapHandler { /** * @param File $image + * @param bool|IContextSource $context Context to use (optional) * @return array|bool */ - function formatMetadata( $image ) { + function formatMetadata( $image, $context = false ) { $meta = $this->getCommonMetaArray( $image ); if ( count( $meta ) === 0 ) { return false; } - return $this->formatMetadataHelper( $meta ); + return $this->formatMetadataHelper( $meta, $context ); } /** @@ -174,7 +175,10 @@ class PNGHandler extends BitmapHandler { return $wgLang->commaList( $info ); } + // PNGs should be easy to support, but it will need some sharpening applied + // and another user test to check if the perceived quality change is noticeable + public function supportsBucketing() { - return true; + return false; } } diff --git a/includes/media/SVG.php b/includes/media/SVG.php index 74e5e048..8fdfa474 100644 --- a/includes/media/SVG.php +++ b/includes/media/SVG.php @@ -272,10 +272,8 @@ class SvgHandler extends ImageHandler { $env['LANG'] = $lang; } - wfProfileIn( 'rsvg' ); wfDebug( __METHOD__ . ": $cmd\n" ); $err = wfShellExecWithStderr( $cmd, $retval, $env ); - wfProfileOut( 'rsvg' ); } } $removed = $this->removeBadFile( $dstPath, $retval ); @@ -364,7 +362,7 @@ class SvgHandler extends ImageHandler { $metadata = array( 'version' => self::SVG_METADATA_VERSION ); try { $metadata += SVGMetadataExtractor::getMetadata( $filename ); - } catch ( MWException $e ) { // @todo SVG specific exceptions + } catch ( Exception $e ) { // @todo SVG specific exceptions // File not found, broken, etc. $metadata['error'] = array( 'message' => $e->getMessage(), @@ -412,9 +410,10 @@ class SvgHandler extends ImageHandler { /** * @param File $file + * @param bool|IContextSource $context Context to use (optional) * @return array|bool */ - function formatMetadata( $file ) { + function formatMetadata( $file, $context = false ) { $result = array( 'visible' => array(), 'collapsed' => array() @@ -488,7 +487,7 @@ class SvgHandler extends ImageHandler { function makeParamString( $params ) { $lang = ''; if ( isset( $params['lang'] ) && $params['lang'] !== 'en' ) { - $params['lang'] = mb_strtolower( $params['lang'] ); + $params['lang'] = strtolower( $params['lang'] ); $lang = "lang{$params['lang']}-"; } if ( !isset( $params['width'] ) ) { diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php index 2a1091d8..2037c331 100644 --- a/includes/media/SVGMetadataExtractor.php +++ b/includes/media/SVGMetadataExtractor.php @@ -138,7 +138,7 @@ class SVGReader { $keepReading = $this->reader->read(); /* Skip until first element */ - while ( $keepReading && $this->reader->nodeType != XmlReader::ELEMENT ) { + while ( $keepReading && $this->reader->nodeType != XMLReader::ELEMENT ) { $keepReading = $this->reader->read(); } @@ -158,7 +158,7 @@ class SVGReader { $this->debug( "$tag" ); - if ( $isSVG && $tag == 'svg' && $type == XmlReader::END_ELEMENT + if ( $isSVG && $tag == 'svg' && $type == XMLReader::END_ELEMENT && $this->reader->depth <= $exitDepth ) { break; @@ -166,7 +166,7 @@ class SVGReader { $this->readField( $tag, 'title' ); } elseif ( $isSVG && $tag == 'desc' ) { $this->readField( $tag, 'description' ); - } elseif ( $isSVG && $tag == 'metadata' && $type == XmlReader::ELEMENT ) { + } elseif ( $isSVG && $tag == 'metadata' && $type == XMLReader::ELEMENT ) { $this->readXml( $tag, 'metadata' ); } elseif ( $isSVG && $tag == 'script' ) { // We normally do not allow scripted svgs. @@ -199,17 +199,17 @@ class SVGReader { */ private function readField( $name, $metafield = null ) { $this->debug( "Read field $metafield" ); - if ( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) { + if ( !$metafield || $this->reader->nodeType != XMLReader::ELEMENT ) { return; } $keepReading = $this->reader->read(); while ( $keepReading ) { if ( $this->reader->localName == $name && $this->reader->namespaceURI == self::NS_SVG - && $this->reader->nodeType == XmlReader::END_ELEMENT + && $this->reader->nodeType == XMLReader::END_ELEMENT ) { break; - } elseif ( $this->reader->nodeType == XmlReader::TEXT ) { + } elseif ( $this->reader->nodeType == XMLReader::TEXT ) { $this->metadata[$metafield] = trim( $this->reader->value ); } $keepReading = $this->reader->read(); @@ -224,7 +224,7 @@ class SVGReader { */ private function readXml( $metafield = null ) { $this->debug( "Read top level metadata" ); - if ( !$metafield || $this->reader->nodeType != XmlReader::ELEMENT ) { + if ( !$metafield || $this->reader->nodeType != XMLReader::ELEMENT ) { return; } // @todo Find and store type of xml snippet. metadata['metadataType'] = "rdf" @@ -246,7 +246,7 @@ class SVGReader { */ private function animateFilterAndLang( $name ) { $this->debug( "animate filter for tag $name" ); - if ( $this->reader->nodeType != XmlReader::ELEMENT ) { + if ( $this->reader->nodeType != XMLReader::ELEMENT ) { return; } if ( $this->reader->isEmptyElement ) { @@ -256,11 +256,11 @@ class SVGReader { $keepReading = $this->reader->read(); while ( $keepReading ) { if ( $this->reader->localName == $name && $this->reader->depth <= $exitDepth - && $this->reader->nodeType == XmlReader::END_ELEMENT + && $this->reader->nodeType == XMLReader::END_ELEMENT ) { break; } elseif ( $this->reader->namespaceURI == self::NS_SVG - && $this->reader->nodeType == XmlReader::ELEMENT + && $this->reader->nodeType == XMLReader::ELEMENT ) { $sysLang = $this->reader->getAttribute( 'systemLanguage' ); diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php index bea6cab3..750528f0 100644 --- a/includes/media/Tiff.php +++ b/includes/media/Tiff.php @@ -88,7 +88,7 @@ class TiffHandler extends ExifBitmapHandler { $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version(); return serialize( $meta ); - } catch ( MWException $e ) { + } catch ( Exception $e ) { // BitmapMetadataHandler throws an exception in certain exceptional // cases like if file does not exist. wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" ); diff --git a/includes/media/TransformationalImageHandler.php b/includes/media/TransformationalImageHandler.php index 3e3be3d1..fd8d81d2 100644 --- a/includes/media/TransformationalImageHandler.php +++ b/includes/media/TransformationalImageHandler.php @@ -61,29 +61,6 @@ abstract class TransformationalImageHandler extends ImageHandler { } } - # Check if the file is smaller than the maximum image area for thumbnailing - # For historical reasons, hook starts with BitmapHandler - $checkImageAreaHookResult = null; - wfRunHooks( - 'BitmapHandlerCheckImageArea', - array( $image, &$params, &$checkImageAreaHookResult ) - ); - - if ( is_null( $checkImageAreaHookResult ) ) { - global $wgMaxImageArea; - - if ( $srcWidth * $srcHeight > $wgMaxImageArea - && !( $image->getMimeType() == 'image/jpeg' - && $this->getScalerType( false, false ) == 'im' ) - ) { - # Only ImageMagick can efficiently downsize jpg images without loading - # the entire file in memory - return false; - } - } else { - return $checkImageAreaHookResult; - } - return true; } @@ -190,6 +167,11 @@ abstract class TransformationalImageHandler extends ImageHandler { return $this->getClientScalingThumbnailImage( $image, $scalerParams ); } + if ( !$this->isImageAreaOkForThumbnaling( $image, $params ) ) { + global $wgMaxImageArea; + return new TransformTooBigImageAreaError( $params, $wgMaxImageArea ); + } + if ( $flags & self::TRANSFORM_LATER ) { wfDebug( __METHOD__ . ": Transforming later per flags.\n" ); $newParams = array( @@ -216,6 +198,12 @@ abstract class TransformationalImageHandler extends ImageHandler { # Transform functions and binaries need a FS source file $thumbnailSource = $this->getThumbnailSource( $image, $params ); + // If the source isn't the original, disable EXIF rotation because it's already been applied + if ( $scalerParams['srcWidth'] != $thumbnailSource['width'] + || $scalerParams['srcHeight'] != $thumbnailSource['height'] ) { + $scalerParams['disableRotation'] = true; + } + $scalerParams['srcPath'] = $thumbnailSource['path']; $scalerParams['srcWidth'] = $thumbnailSource['width']; $scalerParams['srcHeight'] = $thumbnailSource['height']; @@ -234,7 +222,7 @@ abstract class TransformationalImageHandler extends ImageHandler { # Try a hook. Called "Bitmap" for historical reasons. /** @var $mto MediaTransformOutput */ $mto = null; - wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) ); + Hooks::run( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) ); if ( !is_null( $mto ) ) { wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" ); $scaler = 'hookaborted'; @@ -590,4 +578,43 @@ abstract class TransformationalImageHandler extends ImageHandler { public function mustRender( $file ) { return $this->canRotate() && $this->getRotation( $file ) != 0; } + + /** + * Check if the file is smaller than the maximum image area for thumbnailing. + * + * Runs the 'BitmapHandlerCheckImageArea' hook. + * + * @param File $file + * @param array $params + * @return bool + * @since 1.25 + */ + public function isImageAreaOkForThumbnaling( $file, &$params ) { + global $wgMaxImageArea; + + # For historical reasons, hook starts with BitmapHandler + $checkImageAreaHookResult = null; + Hooks::run( + 'BitmapHandlerCheckImageArea', + array( $file, &$params, &$checkImageAreaHookResult ) + ); + + if ( !is_null( $checkImageAreaHookResult ) ) { + // was set by hook, so return that value + return (bool)$checkImageAreaHookResult; + } + + $srcWidth = $file->getWidth( $params['page'] ); + $srcHeight = $file->getHeight( $params['page'] ); + + if ( $srcWidth * $srcHeight > $wgMaxImageArea + && !( $file->getMimeType() == 'image/jpeg' + && $this->getScalerType( false, false ) == 'im' ) + ) { + # Only ImageMagick can efficiently downsize jpg images without loading + # the entire file in memory + return false; + } + return true; + } } diff --git a/includes/media/XCF.php b/includes/media/XCF.php index 48b7a47c..6544d5cf 100644 --- a/includes/media/XCF.php +++ b/includes/media/XCF.php @@ -130,7 +130,7 @@ class XCFHandler extends BitmapHandler { "/Nbase_type", # / $binaryHeader ); - } catch ( MWException $mwe ) { + } catch ( Exception $mwe ) { return false; } diff --git a/includes/media/XMP.php b/includes/media/XMP.php index a3f45e6c..50f04ae9 100644 --- a/includes/media/XMP.php +++ b/includes/media/XMP.php @@ -195,7 +195,7 @@ class XMPReader { $data = $this->results; - wfRunHooks( 'XMPGetResults', array( &$data ) ); + Hooks::run( 'XMPGetResults', array( &$data ) ); if ( isset( $data['xmp-special']['AuthorsPosition'] ) && is_string( $data['xmp-special']['AuthorsPosition'] ) @@ -331,7 +331,7 @@ class XMPReader { // could declare entities unsafe to parse with xml_parse (T85848/T71210). if ( $this->parsable !== self::PARSABLE_OK ) { if ( $this->parsable === self::PARSABLE_NO ) { - throw new MWException( 'Unsafe doctype declaration in XML.' ); + throw new Exception( 'Unsafe doctype declaration in XML.' ); } $content = $this->xmlParsableBuffer . $content; @@ -344,7 +344,7 @@ class XMPReader { $msg = ( $this->parsable === self::PARSABLE_NO ) ? 'Unsafe doctype declaration in XML.' : 'No root element found in XML.'; - throw new MWException( $msg ); + throw new Exception( $msg ); } } @@ -359,7 +359,7 @@ class XMPReader { $this->results = array(); // blank if error. return false; } - } catch ( MWException $e ) { + } catch ( Exception $e ) { wfDebugLog( 'XMP', 'XMP parse error: ' . $e ); $this->results = array(); @@ -501,6 +501,10 @@ class XMPReader { ); $oldDisable = libxml_disable_entity_loader( true ); + $reset = new ScopedCallback( + 'libxml_disable_entity_loader', + array( $oldDisable ) + ); $reader->setParserProperty( XMLReader::SUBST_ENTITIES, false ); // Even with LIBXML_NOWARNING set, XMLReader::read gives a warning @@ -520,7 +524,6 @@ class XMPReader { } } wfRestoreWarnings(); - libxml_disable_entity_loader( $oldDisable ); if ( !is_null( $result ) ) { return $result; diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php index 7e47ec14..e0a491cb 100644 --- a/includes/media/XMPInfo.php +++ b/includes/media/XMPInfo.php @@ -34,7 +34,7 @@ class XMPInfo { if ( !self::$ranHooks ) { // This is for if someone makes a custom metadata extension. // For example, a medical wiki might want to decode DICOM xmp properties. - wfRunHooks( 'XMPGetInfo', array( &self::$items ) ); + Hooks::run( 'XMPGetInfo', array( &self::$items ) ); self::$ranHooks = true; // Only want to do this once. } -- cgit v1.2.3-54-g00ecf