From a1789ddde42033f1b05cc4929491214ee6e79383 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Thu, 17 Dec 2015 09:15:42 +0100 Subject: Update to MediaWiki 1.26.0 --- includes/media/Bitmap.php | 7 +- includes/media/BitmapMetadataHandler.php | 8 +- includes/media/DjVu.php | 96 ++++--- includes/media/DjVuImage.php | 10 +- includes/media/Exif.php | 12 +- includes/media/ExifBitmap.php | 78 +++++- includes/media/FormatMetadata.php | 35 +-- includes/media/GIF.php | 8 +- includes/media/GIFMetadataExtractor.php | 4 +- includes/media/IPTC.php | 4 +- includes/media/ImageHandler.php | 4 +- includes/media/Jpeg.php | 2 +- includes/media/JpegMetadataExtractor.php | 4 +- includes/media/MediaHandler.php | 4 +- .../MediaTransformInvalidParametersException.php | 3 +- includes/media/PNG.php | 8 +- includes/media/PNGMetadataExtractor.php | 16 +- includes/media/SVG.php | 15 +- includes/media/SVGMetadataExtractor.php | 17 +- includes/media/TransformationalImageHandler.php | 2 +- includes/media/WebP.php | 306 +++++++++++++++++++++ includes/media/XCF.php | 6 +- includes/media/XMP.php | 245 ++++++++++------- includes/media/XMPInfo.php | 11 +- includes/media/XMPValidate.php | 60 ++-- includes/media/tinyrgb.icc | Bin 0 -> 524 bytes 26 files changed, 692 insertions(+), 273 deletions(-) create mode 100644 includes/media/WebP.php create mode 100644 includes/media/tinyrgb.icc (limited to 'includes/media') diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php index 3b1d978e..0cc093bf 100644 --- a/includes/media/Bitmap.php +++ b/includes/media/Bitmap.php @@ -92,9 +92,8 @@ class BitmapHandler extends TransformationalImageHandler { // JPEG decoder hint to reduce memory, available since IM 6.5.6-2 $decoderHint = array( '-define', "jpeg:size={$params['physicalDimensions']}" ); } - } elseif ( $params['mimeType'] == 'image/png' ) { + } elseif ( $params['mimeType'] == 'image/png' || $params['mimeType'] == 'image/webp' ) { $quality = array( '-quality', '95' ); // zlib 9, adaptive filtering - } elseif ( $params['mimeType'] == 'image/gif' ) { if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) { // Extract initial frame only; we're so big it'll @@ -121,9 +120,9 @@ class BitmapHandler extends TransformationalImageHandler { '-layers', 'merge', '-background', 'white', ); - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $xcfMeta = unserialize( $image->getMetadata() ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $xcfMeta && isset( $xcfMeta['colorType'] ) && $xcfMeta['colorType'] === 'greyscale-alpha' diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php index c8d37bbb..a5cddac9 100644 --- a/includes/media/BitmapMetadataHandler.php +++ b/includes/media/BitmapMetadataHandler.php @@ -21,6 +21,8 @@ * @ingroup Media */ +use MediaWiki\Logger\LoggerFactory; + /** * Class to deal with reconciling and extracting metadata from bitmap images. * This is meant to comply with http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf @@ -167,7 +169,7 @@ class BitmapMetadataHandler { } } if ( isset( $seg['XMP'] ) && $showXMP ) { - $xmp = new XMPReader(); + $xmp = new XMPReader( LoggerFactory::getInstance( 'XMP' ) ); $xmp->parse( $seg['XMP'] ); foreach ( $seg['XMP_ext'] as $xmpExt ) { /* Support for extended xmp in jpeg files @@ -203,7 +205,7 @@ class BitmapMetadataHandler { if ( isset( $array['text']['xmp']['x-default'] ) && $array['text']['xmp']['x-default'] !== '' && $showXMP ) { - $xmp = new XMPReader(); + $xmp = new XMPReader( LoggerFactory::getInstance( 'XMP' ) ); $xmp->parse( $array['text']['xmp']['x-default'] ); $xmpRes = $xmp->getResults(); foreach ( $xmpRes as $type => $xmpSection ) { @@ -237,7 +239,7 @@ class BitmapMetadataHandler { } if ( $baseArray['xmp'] !== '' && XMPReader::isSupported() ) { - $xmp = new XMPReader(); + $xmp = new XMPReader( LoggerFactory::getInstance( 'XMP' ) ); $xmp->parse( $baseArray['xmp'] ); $xmpRes = $xmp->getResults(); foreach ( $xmpRes as $type => $xmpSection ) { diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php index 1b0eb492..b422bfa2 100644 --- a/includes/media/DjVu.php +++ b/includes/media/DjVu.php @@ -27,6 +27,8 @@ * @ingroup Media */ class DjVuHandler extends ImageHandler { + const EXPENSIVE_SIZE_LIMIT = 10485760; // 10MiB + /** * @return bool */ @@ -49,6 +51,15 @@ class DjVuHandler extends ImageHandler { return true; } + /** + * True if creating thumbnails from the file is large or otherwise resource-intensive. + * @param File $file + * @return bool + */ + public function isExpensiveToThumbnail( $file ) { + return $file->getSize() > static::EXPENSIVE_SIZE_LIMIT; + } + /** * @param File $file * @return bool @@ -137,31 +148,12 @@ class DjVuHandler extends ImageHandler { function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { global $wgDjvuRenderer, $wgDjvuPostProcessor; - // Fetch XML and check it, to give a more informative error message than the one which - // normaliseParams will inevitably give. - $xml = $image->getMetadata(); - if ( !$xml ) { - $width = isset( $params['width'] ) ? $params['width'] : 0; - $height = isset( $params['height'] ) ? $params['height'] : 0; - - return new MediaTransformError( 'thumbnail_error', $width, $height, - wfMessage( 'djvu_no_xml' )->text() ); - } - if ( !$this->normaliseParams( $image, $params ) ) { return new TransformParameterError( $params ); } $width = $params['width']; $height = $params['height']; $page = $params['page']; - if ( $page > $this->pageCount( $image ) ) { - return new MediaTransformError( - 'thumbnail_error', - $width, - $height, - wfMessage( 'djvu_page_error' )->text() - ); - } if ( $flags & self::TRANSFORM_LATER ) { $params = array( @@ -273,9 +265,9 @@ class DjVuHandler extends ImageHandler { return $metadata; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $unser = unserialize( $metadata ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( is_array( $unser ) ) { if ( isset( $unser['error'] ) ) { return false; @@ -312,7 +304,7 @@ class DjVuHandler extends ImageHandler { return false; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); try { // Set to false rather than null to avoid further attempts $image->dejaMetaTree = false; @@ -335,7 +327,7 @@ class DjVuHandler extends ImageHandler { } catch ( Exception $e ) { wfDebug( "Bogus multipage XML metadata on '{$image->getName()}'\n" ); } - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $gettext ) { return $image->djvuTextTree; } else { @@ -384,29 +376,55 @@ class DjVuHandler extends ImageHandler { } function pageCount( $image ) { - $tree = $this->getMetaTree( $image ); - if ( !$tree ) { - return false; + global $wgMemc; + + $key = wfMemcKey( 'file-djvu', 'pageCount', $image->getSha1() ); + + $count = $wgMemc->get( $key ); + if ( $count === false ) { + $tree = $this->getMetaTree( $image ); + if ( !$tree ) { + return false; + } + $count = count( $tree->xpath( '//OBJECT' ) ); + $wgMemc->set( $key, $count ); } - return count( $tree->xpath( '//OBJECT' ) ); + return $count; } function getPageDimensions( $image, $page ) { - $tree = $this->getMetaTree( $image ); - if ( !$tree ) { - return false; - } + global $wgMemc; - $o = $tree->BODY[0]->OBJECT[$page - 1]; - if ( $o ) { - return array( - 'width' => intval( $o['width'] ), - 'height' => intval( $o['height'] ) - ); - } else { - return false; + $key = wfMemcKey( 'file-djvu', 'dimensions', $image->getSha1() ); + + $dimsByPage = $wgMemc->get( $key ); + if ( !is_array( $dimsByPage ) ) { + $tree = $this->getMetaTree( $image ); + if ( !$tree ) { + return false; + } + + $dimsByPage = array(); + $count = count( $tree->xpath( '//OBJECT' ) ); + for ( $i = 0; $i < $count; ++$i ) { + $o = $tree->BODY[0]->OBJECT[$i]; + if ( $o ) { + $dimsByPage[$i] = array( + 'width' => (int)$o['width'], + 'height' => (int)$o['height'] + ); + } else { + $dimsByPage[$i] = false; + } + } + + $wgMemc->set( $key, $dimsByPage ); } + + $index = $page - 1; // MW starts pages at 1 + + return isset( $dimsByPage[$index] ) ? $dimsByPage[$index] : false; } /** diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php index e8faa70a..dac76fad 100644 --- a/includes/media/DjVuImage.php +++ b/includes/media/DjVuImage.php @@ -123,9 +123,9 @@ class DjVuImage { } function getInfo() { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $file = fopen( $this->mFilename, 'rb' ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $file === false ) { wfDebug( __METHOD__ . ": missing or failed file read\n" ); @@ -150,7 +150,7 @@ class DjVuImage { wfDebug( __METHOD__ . ": not a DjVu file\n" ); } elseif ( $subtype == 'DJVU' ) { // Single-page document - $info = $this->getPageInfo( $file, $formLength ); + $info = $this->getPageInfo( $file ); } elseif ( $subtype == 'DJVM' ) { // Multi-page document $info = $this->getMultiPageInfo( $file, $formLength ); @@ -202,7 +202,7 @@ class DjVuImage { if ( $subtype == 'DJVU' ) { wfDebug( __METHOD__ . ": found first subpage\n" ); - return $this->getPageInfo( $file, $length ); + return $this->getPageInfo( $file ); } $this->skipChunk( $file, $length - 4 ); } else { @@ -216,7 +216,7 @@ class DjVuImage { return false; } - private function getPageInfo( $file, $formLength ) { + private function getPageInfo( $file ) { list( $chunk, $length ) = $this->readChunk( $file ); if ( $chunk != 'INFO' ) { wfDebug( __METHOD__ . ": expected INFO chunk, got '$chunk'\n" ); diff --git a/includes/media/Exif.php b/includes/media/Exif.php index 33868689..b4cc43e5 100644 --- a/includes/media/Exif.php +++ b/includes/media/Exif.php @@ -294,9 +294,9 @@ class Exif { $this->debugFile( $this->basename, __FUNCTION__, true ); if ( function_exists( 'exif_read_data' ) ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $data = exif_read_data( $this->file, 0, true ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } else { throw new MWException( "Internal error: exif_read_data not present. " . "\$wgShowEXIF may be incorrectly set or not checked by an extension." ); @@ -471,17 +471,17 @@ class Exif { break; } if ( $charset ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $val = iconv( $charset, 'UTF-8//IGNORE', $val ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } else { // if valid utf-8, assume that, otherwise assume windows-1252 $valCopy = $val; UtfNormal\Validator::quickIsNFCVerify( $valCopy ); //validates $valCopy. if ( $valCopy !== $val ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $val = iconv( 'Windows-1252', 'UTF-8//IGNORE', $val ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } } diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php index f56a947f..5ba5c68c 100644 --- a/includes/media/ExifBitmap.php +++ b/includes/media/ExifBitmap.php @@ -30,6 +30,7 @@ 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. @@ -100,9 +101,9 @@ class ExifBitmapHandler extends BitmapHandler { if ( $metadata === self::BROKEN_FILE ) { return self::METADATA_GOOD; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $exif = unserialize( $metadata ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) || $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() ) { @@ -224,9 +225,9 @@ class ExifBitmapHandler extends BitmapHandler { if ( !$data ) { return 0; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $data = unserialize( $data ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( isset( $data['Orientation'] ) ) { # See http://sylvana.net/jpegcrop/exif_orientation.html switch ( $data['Orientation'] ) { @@ -243,4 +244,73 @@ class ExifBitmapHandler extends BitmapHandler { 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; + } } diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php index 501bb9c2..0fee8cc0 100644 --- a/includes/media/FormatMetadata.php +++ b/includes/media/FormatMetadata.php @@ -706,7 +706,7 @@ class FormatMetadata extends ContextSource { break; case 'GPSDOP': - // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS) + // See https://en.wikipedia.org/wiki/Dilution_of_precision_(GPS) if ( $val <= 2 ) { $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) ); } elseif ( $val <= 5 ) { @@ -1003,28 +1003,6 @@ class FormatMetadata extends ContextSource { return $obj->flattenArrayReal( $vals, $type, $noHtml ); } - /** - * Flatten an array, using the user language for any messages. - * - * @param array $vals Array of values - * @param string $type Type of array (either lang, ul, ol). - * lang = language assoc array with keys being the lang code - * ul = unordered list, ol = ordered list - * type can also come from the '_type' member of $vals. - * @param bool $noHtml If to avoid returning anything resembling HTML. - * (Ugly hack for backwards compatibility with old MediaWiki). - * @param bool|IContextSource $context - * @return string Single value (in wiki-syntax). - */ - public static function flattenArray( $vals, $type = 'ul', $noHtml = false, $context = false ) { - $obj = new FormatMetadata; - if ( $context ) { - $obj->setContext( $context ); - } - - return $obj->flattenArrayReal( $vals, $type, $noHtml ); - } - /** * A function to collapse multivalued tags into a single value. * This turns an array of (for example) authors into a bulleted list. @@ -1305,7 +1283,7 @@ class FormatMetadata extends ContextSource { */ private function gcd( $a, $b ) { /* - // http://en.wikipedia.org/wiki/Euclidean_algorithm + // https://en.wikipedia.org/wiki/Euclidean_algorithm // Recursive form would be: if( $b == 0 ) return $a; @@ -1597,7 +1575,6 @@ class FormatMetadata extends ContextSource { // If revision deleted, exit immediately if ( $file->isDeleted( File::DELETED_FILE ) ) { - return array(); } @@ -1755,8 +1732,9 @@ class FormatMetadata extends ContextSource { } /** - * 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. + * 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 @@ -1765,7 +1743,8 @@ class FormatMetadata extends ContextSource { 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 + } 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 ); diff --git a/includes/media/GIF.php b/includes/media/GIF.php index e3621fbd..94aca615 100644 --- a/includes/media/GIF.php +++ b/includes/media/GIF.php @@ -131,9 +131,9 @@ class GIFHandler extends BitmapHandler { return self::METADATA_GOOD; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $data = unserialize( $metadata ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !$data || !is_array( $data ) ) { wfDebug( __METHOD__ . " invalid GIF metadata\n" ); @@ -161,9 +161,9 @@ class GIFHandler extends BitmapHandler { $original = parent::getLongDesc( $image ); - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $metadata = unserialize( $image->getMetadata() ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !$metadata || $metadata['frameCount'] <= 1 ) { return $original; diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php index 5c370465..6ee23cda 100644 --- a/includes/media/GIFMetadataExtractor.php +++ b/includes/media/GIFMetadataExtractor.php @@ -161,9 +161,9 @@ class GIFMetadataExtractor { UtfNormal\Validator::quickIsNFCVerify( $dataCopy ); if ( $dataCopy !== $data ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $data = iconv( 'windows-1252', 'UTF-8', $data ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } $commentCount = count( $comment ); diff --git a/includes/media/IPTC.php b/includes/media/IPTC.php index 0eb27cc8..c3d58b8b 100644 --- a/includes/media/IPTC.php +++ b/includes/media/IPTC.php @@ -445,9 +445,9 @@ class IPTC { */ private static function convIPTCHelper( $data, $charset ) { if ( $charset ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $data = iconv( $charset, "UTF-8//IGNORE", $data ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $data === false ) { $data = ""; wfDebugLog( 'iptc', __METHOD__ . " Error converting iptc data charset $charset to utf-8" ); diff --git a/includes/media/ImageHandler.php b/includes/media/ImageHandler.php index 968e4243..db74bb35 100644 --- a/includes/media/ImageHandler.php +++ b/includes/media/ImageHandler.php @@ -201,9 +201,9 @@ abstract class ImageHandler extends MediaHandler { } function getImageSize( $image, $path ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $gis = getimagesize( $path ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); return $gis; } diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php index 5463922b..040ff96e 100644 --- a/includes/media/Jpeg.php +++ b/includes/media/Jpeg.php @@ -137,7 +137,7 @@ class JpegHandler extends ExifBitmapHandler { $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360; - if ( $wgJpegTran && is_file( $wgJpegTran ) ) { + if ( $wgJpegTran && is_executable( $wgJpegTran ) ) { $cmd = wfEscapeShellArg( $wgJpegTran ) . " -rotate " . wfEscapeShellArg( $rotation ) . " -outfile " . wfEscapeShellArg( $params['dstPath'] ) . diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php index ae4af8d1..5069f180 100644 --- a/includes/media/JpegMetadataExtractor.php +++ b/includes/media/JpegMetadataExtractor.php @@ -102,9 +102,9 @@ class JpegMetadataExtractor { // turns $com to valid utf-8. // thus if no change, its utf-8, otherwise its something else. if ( $com !== $oldCom ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $com = $oldCom = iconv( 'windows-1252', 'UTF-8//IGNORE', $oldCom ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } // Try it again, if its still not a valid string, then probably // binary junk or some really weird encoding, so don't extract. diff --git a/includes/media/MediaHandler.php b/includes/media/MediaHandler.php index 33aed34f..3a59996c 100644 --- a/includes/media/MediaHandler.php +++ b/includes/media/MediaHandler.php @@ -181,9 +181,9 @@ abstract class MediaHandler { if ( !is_array( $metadata ) ) { //unserialize to keep return parameter consistent. - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $ret = unserialize( $metadata ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); return $ret; } diff --git a/includes/media/MediaTransformInvalidParametersException.php b/includes/media/MediaTransformInvalidParametersException.php index 15a2ca55..6f9c2916 100644 --- a/includes/media/MediaTransformInvalidParametersException.php +++ b/includes/media/MediaTransformInvalidParametersException.php @@ -23,4 +23,5 @@ * * @ingroup Exception */ -class MediaTransformInvalidParametersException extends MWException {} +class MediaTransformInvalidParametersException extends MWException { +} diff --git a/includes/media/PNG.php b/includes/media/PNG.php index 5f1aca57..c3f08325 100644 --- a/includes/media/PNG.php +++ b/includes/media/PNG.php @@ -118,9 +118,9 @@ class PNGHandler extends BitmapHandler { return self::METADATA_GOOD; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $data = unserialize( $metadata ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !$data || !is_array( $data ) ) { wfDebug( __METHOD__ . " invalid png metadata\n" ); @@ -147,9 +147,9 @@ class PNGHandler extends BitmapHandler { global $wgLang; $original = parent::getLongDesc( $image ); - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $metadata = unserialize( $image->getMetadata() ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !$metadata || $metadata['frameCount'] <= 0 ) { return $original; diff --git a/includes/media/PNGMetadataExtractor.php b/includes/media/PNGMetadataExtractor.php index bccd36c1..ac92460e 100644 --- a/includes/media/PNGMetadataExtractor.php +++ b/includes/media/PNGMetadataExtractor.php @@ -201,9 +201,9 @@ class PNGMetadataExtractor { // if compressed if ( $items[2] == "\x01" ) { if ( function_exists( 'gzuncompress' ) && $items[4] === "\x00" ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $items[5] = gzuncompress( $items[5] ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $items[5] === false ) { // decompression failed @@ -245,9 +245,9 @@ class PNGMetadataExtractor { fseek( $fh, self::$crcSize, SEEK_CUR ); continue; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $content = iconv( 'ISO-8859-1', 'UTF-8', $content ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $content === false ) { throw new Exception( __METHOD__ . ": Read error (error with iconv)" ); @@ -285,9 +285,9 @@ class PNGMetadataExtractor { continue; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $content = gzuncompress( $content ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $content === false ) { // decompression failed @@ -296,9 +296,9 @@ class PNGMetadataExtractor { continue; } - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $content = iconv( 'ISO-8859-1', 'UTF-8', $content ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( $content === false ) { throw new Exception( __METHOD__ . ": Read error (error with iconv)" ); diff --git a/includes/media/SVG.php b/includes/media/SVG.php index 8fdfa474..1118598f 100644 --- a/includes/media/SVG.php +++ b/includes/media/SVG.php @@ -95,7 +95,7 @@ class SvgHandler extends ImageHandler { $metadata = $this->unpackMetadata( $metadata ); if ( isset( $metadata['translations'] ) ) { foreach ( $metadata['translations'] as $lang => $langType ) { - if ( $langType === SvgReader::LANG_FULL_MATCH ) { + if ( $langType === SVGReader::LANG_FULL_MATCH ) { $langList[] = $lang; } } @@ -205,11 +205,12 @@ class SvgHandler extends ImageHandler { $tmpDir = wfTempDir() . '/svg_' . wfRandomString( 24 ); $lnPath = "$tmpDir/" . basename( $srcPath ); $ok = mkdir( $tmpDir, 0771 ) && symlink( $srcPath, $lnPath ); + /** @noinspection PhpUnusedLocalVariableInspection */ $cleaner = new ScopedCallback( function () use ( $tmpDir, $lnPath ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); unlink( $lnPath ); rmdir( $tmpDir ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } ); if ( !$ok ) { wfDebugLog( 'thumbnail', @@ -307,9 +308,9 @@ class SvgHandler extends ImageHandler { */ function getImageSize( $file, $path, $metadata = false ) { if ( $metadata === false ) { - $metadata = $file->getMetaData(); + $metadata = $file->getMetadata(); } - $metadata = $this->unpackMetaData( $metadata ); + $metadata = $this->unpackMetadata( $metadata ); if ( isset( $metadata['width'] ) && isset( $metadata['height'] ) ) { return array( $metadata['width'], $metadata['height'], 'SVG', @@ -375,9 +376,9 @@ class SvgHandler extends ImageHandler { } function unpackMetadata( $metadata ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $unser = unserialize( $metadata ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( isset( $unser['version'] ) && $unser['version'] == self::SVG_METADATA_VERSION ) { return $unser; } else { diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php index 2037c331..1ec2f814 100644 --- a/includes/media/SVGMetadataExtractor.php +++ b/includes/media/SVGMetadataExtractor.php @@ -108,17 +108,17 @@ class SVGReader { // Because we cut off the end of the svg making an invalid one. Complicated // try catch thing to make sure warnings get restored. Seems like there should // be a better way. - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); try { $this->read(); } catch ( Exception $e ) { // Note, if this happens, the width/height will be taken to be 0x0. // Should we consider it the default 512x512 instead? - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); libxml_disable_entity_loader( $oldDisable ); throw $e; } - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); libxml_disable_entity_loader( $oldDisable ); } @@ -262,7 +262,6 @@ class SVGReader { } elseif ( $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XMLReader::ELEMENT ) { - $sysLang = $this->reader->getAttribute( 'systemLanguage' ); if ( !is_null( $sysLang ) && $sysLang !== '' ) { // See http://www.w3.org/TR/SVG/struct.html#SystemLanguageAttribute @@ -320,16 +319,6 @@ class SVGReader { } } - // @todo FIXME: Unused, remove? - private function warn( $data ) { - wfDebug( "SVGReader: $data\n" ); - } - - // @todo FIXME: Unused, remove? - private function notice( $data ) { - wfDebug( "SVGReader WARN: $data\n" ); - } - /** * Parse the attributes of an SVG element * diff --git a/includes/media/TransformationalImageHandler.php b/includes/media/TransformationalImageHandler.php index fd8d81d2..15753a96 100644 --- a/includes/media/TransformationalImageHandler.php +++ b/includes/media/TransformationalImageHandler.php @@ -167,7 +167,7 @@ abstract class TransformationalImageHandler extends ImageHandler { return $this->getClientScalingThumbnailImage( $image, $scalerParams ); } - if ( !$this->isImageAreaOkForThumbnaling( $image, $params ) ) { + if ( $image->isTransformedLocally() && !$this->isImageAreaOkForThumbnaling( $image, $params ) ) { global $wgMaxImageArea; return new TransformTooBigImageAreaError( $params, $wgMaxImageArea ); } diff --git a/includes/media/WebP.php b/includes/media/WebP.php new file mode 100644 index 00000000..ff4dcee2 --- /dev/null +++ b/includes/media/WebP.php @@ -0,0 +1,306 @@ + + * + * 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 + * @ingroup Media + */ + +/** + * Handler for Google's WebP format + * + * @ingroup Media + */ +class WebPHandler extends BitmapHandler { + const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata. + /** + * @var int Minimum chunk header size to be able to read all header types + */ + const MINIMUM_CHUNK_HEADER_LENGTH = 18; + /** + * @var int version of the metadata stored in db records + */ + const _MW_WEBP_VERSION = 1; + + const VP8X_ICC = 32; + const VP8X_ALPHA = 16; + const VP8X_EXIF = 8; + const VP8X_XMP = 4; + const VP8X_ANIM = 2; + + public function getMetadata( $image, $filename ) { + $parsedWebPData = self::extractMetadata( $filename ); + if ( !$parsedWebPData ) { + return self::BROKEN_FILE; + } + + $parsedWebPData['metadata']['_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION; + return serialize( $parsedWebPData ); + } + + public function getMetadataType( $image ) { + return 'parsed-webp'; + } + + public function isMetadataValid( $image, $metadata ) { + if ( $metadata === self::BROKEN_FILE ) { + // Do not repetitivly regenerate metadata on broken file. + return self::METADATA_GOOD; + } + + wfSuppressWarnings(); + $data = unserialize( $metadata ); + wfRestoreWarnings(); + + if ( !$data || !is_array( $data ) ) { + wfDebug( __METHOD__ . " invalid WebP metadata\n" ); + + return self::METADATA_BAD; + } + + if ( !isset( $data['metadata']['_MW_WEBP_VERSION'] ) + || $data['metadata']['_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION + ) { + wfDebug( __METHOD__ . " old but compatible WebP metadata\n" ); + + return self::METADATA_COMPATIBLE; + } + return self::METADATA_GOOD; + } + + /** + * Extracts the image size and WebP type from a file + * + * @param string $chunks Chunks as extracted by RiffExtractor + * @return array|bool Header data array with entries 'compression', 'width' and 'height', + * where 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown'. False if + * file is not a valid WebP file. + */ + public static function extractMetadata( $filename ) { + wfDebugLog( 'WebP', __METHOD__ . ": Extracting metadata from $filename\n" ); + + $info = RiffExtractor::findChunksFromFile( $filename, 100 ); + if ( $info === false ) { + wfDebugLog( 'WebP', __METHOD__ . ": Not a valid RIFF file\n" ); + return false; + } + + if ( $info['fourCC'] != 'WEBP' ) { + wfDebugLog( 'WebP', __METHOD__ . ': FourCC was not WEBP: ' . + bin2hex( $info['fourCC'] ) . " \n" ); + return false; + } + + $metadata = self::extractMetadataFromChunks( $info['chunks'], $filename ); + if ( !$metadata ) { + wfDebugLog( 'WebP', __METHOD__ . ": No VP8 chunks found\n" ); + return false; + } + + return $metadata; + } + + /** + * Extracts the image size and WebP type from a file based on the chunk list + * @param array $chunks Chunks as extracted by RiffExtractor + * @return array Header data array with entries 'compression', 'width' and 'height', where + * 'compression' can be 'lossy', 'lossless', 'animated' or 'unknown' + */ + public static function extractMetadataFromChunks( $chunks, $filename ) { + $vp8Info = array(); + + foreach ( $chunks as $chunk ) { + if ( !in_array( $chunk['fourCC'], array( 'VP8 ', 'VP8L', 'VP8X' ) ) ) { + // Not a chunk containing interesting metadata + continue; + } + + $chunkHeader = file_get_contents( $filename, false, null, + $chunk['start'], self::MINIMUM_CHUNK_HEADER_LENGTH ); + wfDebugLog( 'WebP', __METHOD__ . ": {$chunk['fourCC']}\n" ); + + switch ( $chunk['fourCC'] ) { + case 'VP8 ': + return array_merge( $vp8Info, + self::decodeLossyChunkHeader( $chunkHeader ) ); + case 'VP8L': + return array_merge( $vp8Info, + self::decodeLosslessChunkHeader( $chunkHeader ) ); + case 'VP8X': + $vp8Info = array_merge( $vp8Info, + self::decodeExtendedChunkHeader( $chunkHeader ) ); + // Continue looking for other chunks to improve the metadata + break; + } + } + return $vp8Info; + } + + /** + * Decodes a lossy chunk header + * @param string $header Header string + * @return boolean|array See WebPHandler::decodeHeader + */ + protected static function decodeLossyChunkHeader( $header ) { + // Bytes 0-3 are 'VP8 ' + // Bytes 4-7 are the VP8 stream size + // Bytes 8-10 are the frame tag + // Bytes 11-13 are 0x9D 0x01 0x2A called the sync code + $syncCode = substr( $header, 11, 3 ); + if ( $syncCode != "\x9D\x01\x2A" ) { + wfDebugLog( 'WebP', __METHOD__ . ': Invalid sync code: ' . + bin2hex( $syncCode ) . "\n" ); + return array(); + } + // Bytes 14-17 are image size + $imageSize = unpack( 'v2', substr( $header, 14, 4 ) ); + // Image sizes are 14 bit, 2 MSB are scaling parameters which are ignored here + return array( + 'compression' => 'lossy', + 'width' => $imageSize[1] & 0x3FFF, + 'height' => $imageSize[2] & 0x3FFF + ); + } + + /** + * Decodes a lossless chunk header + * @param string $header Header string + * @return boolean|array See WebPHandler::decodeHeader + */ + public static function decodeLosslessChunkHeader( $header ) { + // Bytes 0-3 are 'VP8L' + // Bytes 4-7 are chunk stream size + // Byte 8 is 0x2F called the signature + if ( $header{8} != "\x2F" ) { + wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' . + bin2hex( $header{8} ) . "\n" ); + return array(); + } + // Bytes 9-12 contain the image size + // Bits 0-13 are width-1; bits 15-27 are height-1 + $imageSize = unpack( 'C4', substr( $header, 9, 4 ) ); + return array( + 'compression' => 'lossless', + 'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1, + 'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) | + ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1 + ); + } + + /** + * Decodes an extended chunk header + * @param string $header Header string + * @return boolean|array See WebPHandler::decodeHeader + */ + public static function decodeExtendedChunkHeader( $header ) { + // Bytes 0-3 are 'VP8X' + // Byte 4-7 are chunk length + // Byte 8-11 are a flag bytes + $flags = unpack( 'c', substr( $header, 8, 1 ) ); + + // Byte 12-17 are image size (24 bits) + $width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" ); + $height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" ); + + return array( + 'compression' => 'unknown', + 'animated' => ( $flags[1] & self::VP8X_ANIM ) == self::VP8X_ANIM, + 'transparency' => ( $flags[1] & self::VP8X_ALPHA ) == self::VP8X_ALPHA, + 'width' => ( $width[1] & 0xFFFFFF ) + 1, + 'height' => ( $height[1] & 0xFFFFFF ) + 1 + ); + } + + public function getImageSize( $file, $path, $metadata = false ) { + if ( $file === null ) { + $metadata = self::getMetadata( $file, $path ); + } + if ( $metadata === false ) { + $metadata = $file->getMetadata(); + } + + wfSuppressWarnings(); + $metadata = unserialize( $metadata ); + wfRestoreWarnings(); + + if ( $metadata == false ) { + return false; + } + return array( $metadata['width'], $metadata['height'] ); + } + + /** + * @param $file + * @return bool True, not all browsers support WebP + */ + public function mustRender( $file ) { + return true; + } + + /** + * @param $file + * @return bool False if we are unable to render this image + */ + public function canRender( $file ) { + if ( self::isAnimatedImage( $file ) ) { + return false; + } + return true; + } + + /** + * @param File $image + * @return bool + */ + public function isAnimatedImage( $image ) { + $ser = $image->getMetadata(); + if ( $ser ) { + $metadata = unserialize( $ser ); + if ( isset( $metadata['animated'] ) && $metadata['animated'] === true ) { + return true; + } + } + + return false; + } + + public function canAnimateThumbnail( $file ) { + return false; + } + + /** + * Render files as PNG + * + * @param $ext + * @param $mime + * @param $params + * @return array + */ + public function getThumbType( $ext, $mime, $params = null ) { + return array( 'png', 'image/png' ); + } + + /** + * Must use "im" for XCF + * + * @return string + */ + protected function getScalerType( $dstPath, $checkDstPath = true ) { + return 'im'; + } +} diff --git a/includes/media/XCF.php b/includes/media/XCF.php index 6544d5cf..16e11dc4 100644 --- a/includes/media/XCF.php +++ b/includes/media/XCF.php @@ -3,7 +3,7 @@ * Handler for the Gimp's native file format (XCF) * * Overview: - * http://en.wikipedia.org/wiki/XCF_(file_format) + * https://en.wikipedia.org/wiki/XCF_(file_format) * Specification in Gnome repository: * http://svn.gnome.org/viewvc/gimp/trunk/devel-docs/xcf.txt?view=markup * @@ -222,9 +222,9 @@ class XCFHandler extends BitmapHandler { * @return bool */ public function canRender( $file ) { - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $xcfMeta = unserialize( $file->getMetadata() ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( isset( $xcfMeta['colorType'] ) && $xcfMeta['colorType'] === 'index-coloured' ) { return false; } diff --git a/includes/media/XMP.php b/includes/media/XMP.php index 50f04ae9..64a7e8a8 100644 --- a/includes/media/XMP.php +++ b/includes/media/XMP.php @@ -21,6 +21,10 @@ * @ingroup Media */ +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; + /** * Class for reading xmp data containing properties relevant to * images, and spitting out an array that FormatMetadata accepts. @@ -46,7 +50,7 @@ * read rdf. * */ -class XMPReader { +class XMPReader implements LoggerAwareInterface { /** @var array XMP item configuration array */ protected $items; @@ -120,16 +124,26 @@ class XMPReader { const PARSABLE_BUFFERING = 2; const PARSABLE_NO = 3; + /** + * @var LoggerInterface + */ + private $logger; + /** * Constructor. * * Primary job is to initialize the XMLParser */ - function __construct() { + function __construct( LoggerInterface $logger = null ) { if ( !function_exists( 'xml_parser_create_ns' ) ) { // this should already be checked by this point - throw new MWException( 'XMP support requires XML Parser' ); + throw new RuntimeException( 'XMP support requires XML Parser' ); + } + if ( $logger ) { + $this->setLogger( $logger ); + } else { + $this->setLogger( new NullLogger() ); } $this->items = XMPInfo::getItems(); @@ -137,16 +151,31 @@ class XMPReader { $this->resetXMLParser(); } + public function setLogger( LoggerInterface $logger ) { + $this->logger = $logger; + } + + /** + * free the XML parser. + * + * @note It is unclear to me if we really need to do this ourselves + * or if php garbage collection will automatically free the xmlParser + * when it is no longer needed. + */ + private function destroyXMLParser() { + if ( $this->xmlParser ) { + xml_parser_free( $this->xmlParser ); + $this->xmlParser = null; + } + } + /** * Main use is if a single item has multiple xmp documents describing it. * For example in jpeg's with extendedXMP */ private function resetXMLParser() { - if ( $this->xmlParser ) { - //is this needed? - xml_parser_free( $this->xmlParser ); - } + $this->destroyXMLParser(); $this->xmlParser = xml_parser_create_ns( 'UTF-8', ' ' ); xml_parser_set_option( $this->xmlParser, XML_OPTION_CASE_FOLDING, 0 ); @@ -162,15 +191,6 @@ class XMPReader { $this->xmlParsableBuffer = ''; } - /** Destroy the xml parser - * - * Not sure if this is actually needed. - */ - function __destruct() { - // not sure if this is needed. - xml_parser_free( $this->xmlParser ); - } - /** * Check if this instance supports using this class */ @@ -195,8 +215,6 @@ class XMPReader { $data = $this->results; - Hooks::run( 'XMPGetResults', array( &$data ) ); - if ( isset( $data['xmp-special']['AuthorsPosition'] ) && is_string( $data['xmp-special']['AuthorsPosition'] ) && isset( $data['xmp-general']['Artist'][0] ) @@ -278,12 +296,11 @@ class XMPReader { * * @param string $content XMP data * @param bool $allOfIt If this is all the data (true) or if its split up (false). Default true - * @param bool $reset Does xml parser need to be reset. Default false - * @throws MWException + * @throws RuntimeException * @return bool Success. */ - public function parse( $content, $allOfIt = true, $reset = false ) { - if ( $reset ) { + public function parse( $content, $allOfIt = true ) { + if ( !$this->xmlParser ) { $this->resetXMLParser(); } try { @@ -313,7 +330,7 @@ class XMPReader { break; default: //this should be impossible to get to - throw new MWException( "Invalid BOM" ); + throw new RuntimeException( "Invalid BOM" ); } } else { // standard specifically says, if no bom assume utf-8 @@ -322,16 +339,16 @@ class XMPReader { } if ( $this->charset !== 'UTF-8' ) { //don't convert if already utf-8 - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); $content = iconv( $this->charset, 'UTF-8//IGNORE', $content ); - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); } // Ensure the XMP block does not have an xml doctype declaration, which // 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 Exception( 'Unsafe doctype declaration in XML.' ); + throw new RuntimeException( 'Unsafe doctype declaration in XML.' ); } $content = $this->xmlParsableBuffer . $content; @@ -344,27 +361,49 @@ class XMPReader { $msg = ( $this->parsable === self::PARSABLE_NO ) ? 'Unsafe doctype declaration in XML.' : 'No root element found in XML.'; - throw new Exception( $msg ); + throw new RuntimeException( $msg ); } } $ok = xml_parse( $this->xmlParser, $content, $allOfIt ); if ( !$ok ) { - $error = xml_error_string( xml_get_error_code( $this->xmlParser ) ); - $where = 'line: ' . xml_get_current_line_number( $this->xmlParser ) - . ' column: ' . xml_get_current_column_number( $this->xmlParser ) - . ' byte offset: ' . xml_get_current_byte_index( $this->xmlParser ); - - wfDebugLog( 'XMP', "XMPReader::parse : Error reading XMP content: $error ($where)" ); + $code = xml_get_error_code( $this->xmlParser ); + $error = xml_error_string( $code ); + $line = xml_get_current_line_number( $this->xmlParser ); + $col = xml_get_current_column_number( $this->xmlParser ); + $offset = xml_get_current_byte_index( $this->xmlParser ); + + $this->logger->warning( + '{method} : Error reading XMP content: {error} ' . + '(line: {line} column: {column} byte offset: {offset})', + array( + 'method' => __METHOD__, + 'error_code' => $code, + 'error' => $error, + 'line' => $line, + 'column' => $col, + 'offset' => $offset, + 'content' => $content, + ) ); $this->results = array(); // blank if error. + $this->destroyXMLParser(); return false; } } catch ( Exception $e ) { - wfDebugLog( 'XMP', 'XMP parse error: ' . $e ); + $this->logger->warning( + '{method} Exception caught while parsing: ' . $e->getMessage(), + array( + 'method' => __METHOD__, + 'exception' => $e, + 'content' => $content, + ) + ); $this->results = array(); - return false; } + if ( $allOfIt ) { + $this->destroyXMLParser(); + } return true; } @@ -383,7 +422,7 @@ class XMPReader { if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] ) || $this->results['xmp-special']['HasExtendedXMP'] !== $guid ) { - wfDebugLog( 'XMP', __METHOD__ . + $this->logger->info( __METHOD__ . " Ignoring XMPExtended block due to wrong guid (guid= '$guid')" ); return false; @@ -391,7 +430,7 @@ class XMPReader { $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) ); if ( !$len || $len['length'] < 4 || $len['offset'] < 0 || $len['offset'] > $len['length'] ) { - wfDebugLog( 'XMP', __METHOD__ . 'Error reading extended XMP block, invalid length or offset.' ); + $this->logger->info( __METHOD__ . 'Error reading extended XMP block, invalid length or offset.' ); return false; } @@ -408,7 +447,7 @@ class XMPReader { // > 128k, and be in the wrong order is very low... if ( $len['offset'] !== $this->extendedXMPOffset ) { - wfDebugLog( 'XMP', __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was ' + $this->logger->info( __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was ' . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')' ); return false; @@ -430,7 +469,7 @@ class XMPReader { $atEnd = false; } - wfDebugLog( 'XMP', __METHOD__ . 'Parsing a XMPExtended block' ); + $this->logger->debug( __METHOD__ . 'Parsing a XMPExtended block' ); return $this->parse( $actualContent, $atEnd ); } @@ -449,7 +488,7 @@ class XMPReader { * * @param XMLParser $parser XMLParser reference to the xml parser * @param string $data Character data - * @throws MWException On invalid data + * @throws RuntimeException On invalid data */ function char( $parser, $data ) { @@ -459,7 +498,7 @@ class XMPReader { } if ( !isset( $this->mode[0] ) ) { - throw new MWException( 'Unexpected character data before first rdf:Description element' ); + throw new RuntimeException( 'Unexpected character data before first rdf:Description element' ); } if ( $this->mode[0] === self::MODE_IGNORE ) { @@ -469,7 +508,7 @@ class XMPReader { if ( $this->mode[0] !== self::MODE_SIMPLE && $this->mode[0] !== self::MODE_QDESC ) { - throw new MWException( 'character data where not expected. (mode ' . $this->mode[0] . ')' ); + throw new RuntimeException( 'character data where not expected. (mode ' . $this->mode[0] . ')' ); } // to check, how does this handle w.s. @@ -501,6 +540,7 @@ class XMPReader { ); $oldDisable = libxml_disable_entity_loader( true ); + /** @noinspection PhpUnusedLocalVariableInspection */ $reset = new ScopedCallback( 'libxml_disable_entity_loader', array( $oldDisable ) @@ -509,7 +549,7 @@ class XMPReader { // Even with LIBXML_NOWARNING set, XMLReader::read gives a warning // when parsing truncated XML, which causes unit tests to fail. - wfSuppressWarnings(); + MediaWiki\suppressWarnings(); while ( $reader->read() ) { if ( $reader->nodeType === XMLReader::ELEMENT ) { // Reached the first element without hitting a doctype declaration @@ -523,7 +563,7 @@ class XMPReader { break; } } - wfRestoreWarnings(); + MediaWiki\restoreWarnings(); if ( !is_null( $result ) ) { return $result; @@ -597,7 +637,7 @@ class XMPReader { * This method is called when we hit the "" tag. * * @param string $elm Namespace . space . tag name. - * @throws MWException + * @throws RuntimeException */ private function endElementNested( $elm ) { @@ -607,35 +647,38 @@ class XMPReader { && !( $elm === self::NS_RDF . ' Description' && $this->mode[0] === self::MODE_STRUCT ) ) { - throw new MWException( "nesting mismatch. got a but expected a but expected a curItem[0] . '>' ); } // Validate structures. list( $ns, $tag ) = explode( ' ', $elm, 2 ); if ( isset( $this->items[$ns][$tag]['validate'] ) ) { - $info =& $this->items[$ns][$tag]; $finalName = isset( $info['map_name'] ) ? $info['map_name'] : $tag; - $validate = is_array( $info['validate'] ) ? $info['validate'] - : array( 'XMPValidate', $info['validate'] ); + if ( is_array( $info['validate'] ) ) { + $validate = $info['validate']; + } else { + $validator = new XMPValidate( $this->logger ); + $validate = array( $validator, $info['validate'] ); + } if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) { // This can happen if all the members of the struct failed validation. - wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> has no valid members." ); + $this->logger->debug( __METHOD__ . " <$ns:$tag> has no valid members." ); } elseif ( is_callable( $validate ) ) { $val =& $this->results['xmp-' . $info['map_group']][$finalName]; call_user_func_array( $validate, array( $info, &$val, false ) ); if ( is_null( $val ) ) { // the idea being the validation function will unset the variable if // its invalid. - wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> failed validation." ); + $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." ); unset( $this->results['xmp-' . $info['map_group']][$finalName] ); } } else { - wfDebugLog( 'XMP', __METHOD__ . " Validation function for $finalName (" + $this->logger->warning( __METHOD__ . " Validation function for $finalName (" . $validate[0] . '::' . $validate[1] . '()) is not callable.' ); } } @@ -664,7 +707,7 @@ class XMPReader { * hit the "") * * @param string $elm Namespace . ' ' . element name - * @throws MWException + * @throws RuntimeException */ private function endElementModeLi( $elm ) { @@ -676,7 +719,7 @@ class XMPReader { array_shift( $this->mode ); if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) { - wfDebugLog( 'XMP', __METHOD__ . " Empty compund element $finalName." ); + $this->logger->debug( __METHOD__ . " Empty compund element $finalName." ); return; } @@ -691,7 +734,7 @@ class XMPReader { $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang'; } } else { - throw new MWException( __METHOD__ . " expected or but instead got $elm." ); + throw new RuntimeException( __METHOD__ . " expected or but instead got $elm." ); } } @@ -729,7 +772,7 @@ class XMPReader { * * @param XMLParser $parser * @param string $elm Namespace . ' ' . element name - * @throws MWException + * @throws RuntimeException */ function endElement( $parser, $elm ) { if ( $elm === ( self::NS_RDF . ' RDF' ) @@ -743,7 +786,7 @@ class XMPReader { if ( $elm === self::NS_RDF . ' type' ) { // these aren't really supported properly yet. // However, it appears they almost never used. - wfDebugLog( 'XMP', __METHOD__ . ' encountered ' ); + $this->logger->info( __METHOD__ . ' encountered ' ); } if ( strpos( $elm, ' ' ) === false ) { @@ -751,7 +794,7 @@ class XMPReader { // However, there is a bug in an adobe product // that forgets the namespace on some things. // (Luckily they are unimportant things). - wfDebugLog( 'XMP', __METHOD__ . " Encountered which has no namespace. Skipping." ); + $this->logger->info( __METHOD__ . " Encountered which has no namespace. Skipping." ); return; } @@ -759,13 +802,13 @@ class XMPReader { if ( count( $this->mode[0] ) === 0 ) { // This should never ever happen and means // there is a pretty major bug in this class. - throw new MWException( 'Encountered end element with no mode' ); + throw new RuntimeException( 'Encountered end element with no mode' ); } if ( count( $this->curItem ) == 0 && $this->mode[0] !== self::MODE_INITIAL ) { // just to be paranoid. Should always have a curItem, except for initially // (aka during MODE_INITAL). - throw new MWException( "Hit end element but no curItem" ); + throw new RuntimeException( "Hit end element but no curItem" ); } switch ( $this->mode[0] ) { @@ -786,7 +829,7 @@ class XMPReader { if ( $elm === self::NS_RDF . ' Description' ) { array_shift( $this->mode ); } else { - throw new MWException( 'Element ended unexpectedly while in MODE_INITIAL' ); + throw new RuntimeException( 'Element ended unexpectedly while in MODE_INITIAL' ); } break; case self::MODE_LI: @@ -797,7 +840,7 @@ class XMPReader { $this->endElementModeQDesc( $elm ); break; default: - wfDebugLog( 'XMP', __METHOD__ . " no mode (elm = $elm)" ); + $this->logger->warning( __METHOD__ . " no mode (elm = $elm)" ); break; } } @@ -825,13 +868,13 @@ class XMPReader { * this should always be * * @param string $elm Namespace . ' ' . tag - * @throws MWException If we have an element that's not + * @throws RuntimeException If we have an element that's not */ private function startElementModeBag( $elm ) { if ( $elm === self::NS_RDF . ' Bag' ) { array_unshift( $this->mode, self::MODE_LI ); } else { - throw new MWException( "Expected but got $elm." ); + throw new RuntimeException( "Expected but got $elm." ); } } @@ -840,18 +883,18 @@ class XMPReader { * this should always be * * @param string $elm Namespace . ' ' . tag - * @throws MWException If we have an element that's not + * @throws RuntimeException If we have an element that's not */ private function startElementModeSeq( $elm ) { if ( $elm === self::NS_RDF . ' Seq' ) { array_unshift( $this->mode, self::MODE_LI ); } elseif ( $elm === self::NS_RDF . ' Bag' ) { # bug 27105 - wfDebugLog( 'XMP', __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending' + $this->logger->info( __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending' . ' it is a Seq, since some buggy software is known to screw this up.' ); array_unshift( $this->mode, self::MODE_LI ); } else { - throw new MWException( "Expected but got $elm." ); + throw new RuntimeException( "Expected but got $elm." ); } } @@ -867,13 +910,13 @@ class XMPReader { * we don't care about. * * @param string $elm Namespace . ' ' . tag - * @throws MWException If we have an element that's not + * @throws RuntimeException If we have an element that's not */ private function startElementModeLang( $elm ) { if ( $elm === self::NS_RDF . ' Alt' ) { array_unshift( $this->mode, self::MODE_LI_LANG ); } else { - throw new MWException( "Expected but got $elm." ); + throw new RuntimeException( "Expected but got $elm." ); } } @@ -893,7 +936,7 @@ class XMPReader { * * @param string $elm Namespace and tag names separated by space. * @param array $attribs Attributes of the element. - * @throws MWException + * @throws RuntimeException */ private function startElementModeSimple( $elm, $attribs ) { if ( $elm === self::NS_RDF . ' Description' ) { @@ -907,10 +950,10 @@ class XMPReader { } } elseif ( $elm === self::NS_RDF . ' value' ) { // This should not be here. - throw new MWException( __METHOD__ . ' Encountered where it was unexpected.' ); + throw new RuntimeException( __METHOD__ . ' Encountered where it was unexpected.' ); } else { // something else we don't recognize, like a qualifier maybe. - wfDebugLog( 'XMP', __METHOD__ . + $this->logger->info( __METHOD__ . " Encountered element <$elm> where only expecting character data as value of " . $this->curItem[0] ); array_unshift( $this->mode, self::MODE_IGNORE ); @@ -952,7 +995,7 @@ class XMPReader { * @param string $ns Namespace * @param string $tag Tag name (without namespace prefix) * @param array $attribs Array of attributes - * @throws MWException + * @throws RuntimeException */ private function startElementModeInitial( $ns, $tag, $attribs ) { if ( $ns !== self::NS_RDF ) { @@ -964,7 +1007,7 @@ class XMPReader { // a child of a struct), then something weird is // happening, so ignore this element and its children. - wfDebugLog( 'XMP', "Encountered <$ns:$tag> outside" + $this->logger->warning( "Encountered <$ns:$tag> outside" . " of its expected parent. Ignoring." ); array_unshift( $this->mode, self::MODE_IGNORE ); @@ -982,11 +1025,11 @@ class XMPReader { if ( $this->charContent !== false ) { // Something weird. // Should not happen in valid XMP. - throw new MWException( 'tag nested in non-whitespace characters.' ); + throw new RuntimeException( 'tag nested in non-whitespace characters.' ); } } else { // This element is not on our list of allowed elements so ignore. - wfDebugLog( 'XMP', __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." ); + $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." ); array_unshift( $this->mode, self::MODE_IGNORE ); array_unshift( $this->curItem, $ns . ' ' . $tag ); @@ -1014,7 +1057,7 @@ class XMPReader { * @param string $ns Namespace * @param string $tag Tag name (no ns) * @param array $attribs Array of attribs w/ values. - * @throws MWException + * @throws RuntimeException */ private function startElementModeStruct( $ns, $tag, $attribs ) { if ( $ns !== self::NS_RDF ) { @@ -1025,7 +1068,7 @@ class XMPReader { ) { // This assumes that we don't have inter-namespace nesting // which we don't in all the properties we're interested in. - throw new MWException( " <$tag> appeared nested in <" . $this->ancestorStruct + throw new RuntimeException( " <$tag> appeared nested in <" . $this->ancestorStruct . "> where it is not allowed." ); } array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] ); @@ -1033,7 +1076,7 @@ class XMPReader { if ( $this->charContent !== false ) { // Something weird. // Should not happen in valid XMP. - throw new MWException( "tag <$tag> nested in non-whitespace characters (" . + throw new RuntimeException( "tag <$tag> nested in non-whitespace characters (" . $this->charContent . ")." ); } } else { @@ -1062,17 +1105,17 @@ class XMPReader { * * @param string $elm Namespace . ' ' . tagname * @param array $attribs Attributes. (needed for BAGSTRUCTS) - * @throws MWException If gets a tag other than + * @throws RuntimeException If gets a tag other than */ private function startElementModeLi( $elm, $attribs ) { if ( ( $elm ) !== self::NS_RDF . ' li' ) { - throw new MWException( " expected but got $elm." ); + throw new RuntimeException( " expected but got $elm." ); } if ( !isset( $this->mode[1] ) ) { // This should never ever ever happen. Checking for it // to be paranoid. - throw new MWException( 'In mode Li, but no 2xPrevious mode!' ); + throw new RuntimeException( 'In mode Li, but no 2xPrevious mode!' ); } if ( $this->mode[1] === self::MODE_BAGSTRUCT ) { @@ -1083,7 +1126,7 @@ class XMPReader { if ( !isset( $this->curItem[1] ) ) { // be paranoid. - throw new MWException( 'Can not find parent of BAGSTRUCT.' ); + throw new RuntimeException( 'Can not find parent of BAGSTRUCT.' ); } list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] ); $this->ancestorStruct = isset( $this->items[$curNS][$curTag]['map_name'] ) @@ -1112,16 +1155,16 @@ class XMPReader { * * @param string $elm Namespace . ' ' . tag * @param array $attribs Array of elements (most importantly xml:lang) - * @throws MWException If gets a tag other than or if no xml:lang + * @throws RuntimeException If gets a tag other than or if no xml:lang */ private function startElementModeLiLang( $elm, $attribs ) { if ( $elm !== self::NS_RDF . ' li' ) { - throw new MWException( __METHOD__ . " expected but got $elm." ); + throw new RuntimeException( __METHOD__ . " expected but got $elm." ); } if ( !isset( $attribs[self::NS_XML . ' lang'] ) || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[self::NS_XML . ' lang'] ) ) { - throw new MWException( __METHOD__ + throw new RuntimeException( __METHOD__ . " did not contain, or has invalid xml:lang attribute in lang alternative" ); } @@ -1143,7 +1186,7 @@ class XMPReader { * @param XMLParser $parser * @param string $elm Namespace "" element * @param array $attribs Attribute name => value - * @throws MWException + * @throws RuntimeException */ function startElement( $parser, $elm, $attribs ) { @@ -1166,12 +1209,12 @@ class XMPReader { // // also it seems as if exiv2 and exiftool do not support // this either (That or I misunderstand the standard) - wfDebugLog( 'XMP', __METHOD__ . ' Encountered which isn\'t currently supported' ); + $this->logger->info( __METHOD__ . ' Encountered which isn\'t currently supported' ); } if ( strpos( $elm, ' ' ) === false ) { // This probably shouldn't happen. - wfDebugLog( 'XMP', __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." ); + $this->logger->info( __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." ); return; } @@ -1180,7 +1223,7 @@ class XMPReader { if ( count( $this->mode ) === 0 ) { // This should not happen. - throw new MWException( 'Error extracting XMP, ' + throw new RuntimeException( 'Error extracting XMP, ' . "encountered <$elm> with no mode" ); } @@ -1217,7 +1260,7 @@ class XMPReader { $this->startElementModeQDesc( $elm ); break; default: - throw new MWException( 'StartElement in unknown mode: ' . $this->mode[0] ); + throw new RuntimeException( 'StartElement in unknown mode: ' . $this->mode[0] ); } } @@ -1236,7 +1279,7 @@ class XMPReader { * @codingStandardsIgnoreEnd * * @param array $attribs Array attribute=>value - * @throws MWException + * @throws RuntimeException */ private function doAttribs( $attribs ) { // first check for rdf:parseType attribute, as that can change @@ -1253,7 +1296,7 @@ class XMPReader { if ( strpos( $name, ' ' ) === false ) { // This shouldn't happen, but so far some old software forgets namespace // on rdf:about. - wfDebugLog( 'XMP', __METHOD__ . ' Encountered non-namespaced attribute: ' + $this->logger->info( __METHOD__ . ' Encountered non-namespaced attribute: ' . " $name=\"$val\". Skipping. " ); continue; } @@ -1266,12 +1309,12 @@ class XMPReader { } } elseif ( isset( $this->items[$ns][$tag] ) ) { if ( $this->mode[0] === self::MODE_SIMPLE ) { - throw new MWException( __METHOD__ + throw new RuntimeException( __METHOD__ . " $ns:$tag found as attribute where not allowed" ); } $this->saveValue( $ns, $tag, $val ); } else { - wfDebugLog( 'XMP', __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." ); + $this->logger->debug( __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." ); } } } @@ -1293,20 +1336,24 @@ class XMPReader { $finalName = isset( $info['map_name'] ) ? $info['map_name'] : $tag; if ( isset( $info['validate'] ) ) { - $validate = is_array( $info['validate'] ) ? $info['validate'] - : array( 'XMPValidate', $info['validate'] ); + if ( is_array( $info['validate'] ) ) { + $validate = $info['validate']; + } else { + $validator = new XMPValidate( $this->logger ); + $validate = array( $validator, $info['validate'] ); + } if ( is_callable( $validate ) ) { call_user_func_array( $validate, array( $info, &$val, true ) ); // the reasoning behind using &$val instead of using the return value // is to be consistent between here and validating structures. if ( is_null( $val ) ) { - wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> failed validation." ); + $this->logger->info( __METHOD__ . " <$ns:$tag> failed validation." ); return; } } else { - wfDebugLog( 'XMP', __METHOD__ . " Validation function for $finalName (" + $this->logger->warning( __METHOD__ . " Validation function for $finalName (" . $validate[0] . '::' . $validate[1] . '()) is not callable.' ); } } diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php index e0a491cb..1d8d7771 100644 --- a/includes/media/XMPInfo.php +++ b/includes/media/XMPInfo.php @@ -31,18 +31,9 @@ class XMPInfo { * @return array XMP item configuration array. */ public static function getItems() { - 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. - Hooks::run( 'XMPGetInfo', array( &self::$items ) ); - self::$ranHooks = true; // Only want to do this once. - } - return self::$items; } - static private $ranHooks = false; - /** * XMPInfo::$items keeps a list of all the items * we are interested to extract, as well as @@ -57,7 +48,7 @@ class XMPInfo { * * mode - What type of item (self::MODE_SIMPLE usually, see above for * all values). * * validate - Method to validate input. Could also post-process the - * input. A string value is assumed to be a static method of + * input. A string value is assumed to be a method of * XMPValidate. Can also take a array( 'className', 'methodName' ). * * choices - Array of potential values (format of 'value' => true ). * Only used with validateClosed. diff --git a/includes/media/XMPValidate.php b/includes/media/XMPValidate.php index 0fa60117..55e8ce79 100644 --- a/includes/media/XMPValidate.php +++ b/includes/media/XMPValidate.php @@ -21,6 +21,9 @@ * @ingroup Media */ +use Psr\Log\LoggerInterface; +use Psr\Log\LoggerAwareInterface; + /** * This contains some static methods for * validating XMP properties. See XMPInfo and XMPReader classes. @@ -40,7 +43,20 @@ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf starting at pg 28 * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf starting at pg 11 */ -class XMPValidate { +class XMPValidate implements LoggerAwareInterface { + + /** + * @var LoggerInterface + */ + private $logger; + + public function __construct( LoggerInterface $logger ) { + $this->setLogger( $logger ); + } + + public function setLogger( LoggerInterface $logger ) { + $this->logger = $logger; + } /** * Function to validate boolean properties ( True or False ) * @@ -48,13 +64,13 @@ class XMPValidate { * @param mixed &$val Current value to validate * @param bool $standalone If this is a simple property or array */ - public static function validateBoolean( $info, &$val, $standalone ) { + public function validateBoolean( $info, &$val, $standalone ) { if ( !$standalone ) { // this only validates standalone properties, not arrays, etc return; } if ( $val !== 'True' && $val !== 'False' ) { - wfDebugLog( 'XMP', __METHOD__ . " Expected True or False but got $val" ); + $this->debug->info( __METHOD__ . " Expected True or False but got $val" ); $val = null; } } @@ -66,13 +82,13 @@ class XMPValidate { * @param mixed &$val Current value to validate * @param bool $standalone If this is a simple property or array */ - public static function validateRational( $info, &$val, $standalone ) { + public function validateRational( $info, &$val, $standalone ) { if ( !$standalone ) { // this only validates standalone properties, not arrays, etc return; } if ( !preg_match( '/^(?:-?\d+)\/(?:\d+[1-9]|[1-9]\d*)$/D', $val ) ) { - wfDebugLog( 'XMP', __METHOD__ . " Expected rational but got $val" ); + $this->logger->info( __METHOD__ . " Expected rational but got $val" ); $val = null; } } @@ -87,7 +103,7 @@ class XMPValidate { * @param mixed &$val Current value to validate * @param bool $standalone If this is a simple property or array */ - public static function validateRating( $info, &$val, $standalone ) { + public function validateRating( $info, &$val, $standalone ) { if ( !$standalone ) { // this only validates standalone properties, not arrays, etc return; @@ -95,7 +111,7 @@ class XMPValidate { if ( !preg_match( '/^[-+]?\d*(?:\.?\d*)$/D', $val ) || !is_numeric( $val ) ) { - wfDebugLog( 'XMP', __METHOD__ . " Expected rating but got $val" ); + $this->logger->info( __METHOD__ . " Expected rating but got $val" ); $val = null; return; @@ -105,13 +121,13 @@ class XMPValidate { // We do < 0 here instead of < -1 here, since // the values between 0 and -1 are also illegal // as -1 is meant as a special reject rating. - wfDebugLog( 'XMP', __METHOD__ . " Rating too low, setting to -1 (Rejected)" ); + $this->logger->info( __METHOD__ . " Rating too low, setting to -1 (Rejected)" ); $val = '-1'; return; } if ( $nVal > 5 ) { - wfDebugLog( 'XMP', __METHOD__ . " Rating too high, setting to 5" ); + $this->logger->info( __METHOD__ . " Rating too high, setting to 5" ); $val = '5'; return; @@ -126,13 +142,13 @@ class XMPValidate { * @param mixed &$val Current value to validate * @param bool $standalone If this is a simple property or array */ - public static function validateInteger( $info, &$val, $standalone ) { + public function validateInteger( $info, &$val, $standalone ) { if ( !$standalone ) { // this only validates standalone properties, not arrays, etc return; } if ( !preg_match( '/^[-+]?\d+$/D', $val ) ) { - wfDebugLog( 'XMP', __METHOD__ . " Expected integer but got $val" ); + $this->logger->info( __METHOD__ . " Expected integer but got $val" ); $val = null; } } @@ -145,7 +161,7 @@ class XMPValidate { * @param mixed &$val Current value to validate * @param bool $standalone If this is a simple property or array */ - public static function validateClosed( $info, &$val, $standalone ) { + public function validateClosed( $info, &$val, $standalone ) { if ( !$standalone ) { // this only validates standalone properties, not arrays, etc return; @@ -163,7 +179,7 @@ class XMPValidate { } if ( !isset( $info['choices'][$val] ) && !$inRange ) { - wfDebugLog( 'XMP', __METHOD__ . " Expected closed choice, but got $val" ); + $this->logger->info( __METHOD__ . " Expected closed choice, but got $val" ); $val = null; } } @@ -175,7 +191,7 @@ class XMPValidate { * @param mixed &$val Current value to validate * @param bool $standalone If this is a simple property or array */ - public static function validateFlash( $info, &$val, $standalone ) { + public function validateFlash( $info, &$val, $standalone ) { if ( $standalone ) { // this only validates flash structs, not individual properties return; @@ -186,7 +202,7 @@ class XMPValidate { && isset( $val['RedEyeMode'] ) && isset( $val['Return'] ) ) ) { - wfDebugLog( 'XMP', __METHOD__ . " Flash structure did not have all the required components" ); + $this->logger->info( __METHOD__ . " Flash structure did not have all the required components" ); $val = null; } else { $val = ( "\0" | ( $val['Fired'] === 'True' ) @@ -209,14 +225,14 @@ class XMPValidate { * @param mixed &$val Current value to validate * @param bool $standalone If this is a simple property or array */ - public static function validateLangCode( $info, &$val, $standalone ) { + public function validateLangCode( $info, &$val, $standalone ) { if ( !$standalone ) { // this only validates standalone properties, not arrays, etc return; } if ( !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $val ) ) { //this is a rather naive check. - wfDebugLog( 'XMP', __METHOD__ . " Expected Lang code but got $val" ); + $this->logger->info( __METHOD__ . " Expected Lang code but got $val" ); $val = null; } } @@ -238,7 +254,7 @@ class XMPValidate { * 2011:04. * @param bool $standalone If this is a simple property or array */ - public static function validateDate( $info, &$val, $standalone ) { + public function validateDate( $info, &$val, $standalone ) { if ( !$standalone ) { // this only validates standalone properties, not arrays, etc return; @@ -252,7 +268,7 @@ class XMPValidate { ) { // @codingStandardsIgnoreEnd - wfDebugLog( 'XMP', __METHOD__ . " Expected date but got $val" ); + $this->logger->info( __METHOD__ . " Expected date but got $val" ); $val = null; } else { /* @@ -270,7 +286,7 @@ class XMPValidate { * some programs convert between metadata formats. */ if ( $res[1] === '0000' ) { - wfDebugLog( 'XMP', __METHOD__ . " Invalid date (year 0): $val" ); + $this->logger->info( __METHOD__ . " Invalid date (year 0): $val" ); $val = null; return; @@ -339,7 +355,7 @@ class XMPValidate { * or DDD,MM.mmk form * @param bool $standalone If its a simple prop (should always be true) */ - public static function validateGPS( $info, &$val, $standalone ) { + public function validateGPS( $info, &$val, $standalone ) { if ( !$standalone ) { return; } @@ -371,7 +387,7 @@ class XMPValidate { return; } else { - wfDebugLog( 'XMP', __METHOD__ + $this->logger->info( __METHOD__ . " Expected GPSCoordinate, but got $val." ); $val = null; diff --git a/includes/media/tinyrgb.icc b/includes/media/tinyrgb.icc new file mode 100644 index 00000000..eab973f5 Binary files /dev/null and b/includes/media/tinyrgb.icc differ -- cgit v1.2.3-54-g00ecf