X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FImage.php;h=9145a0dcf66df4edebac9fd6db3bade89cfcc8c5;hb=a2b6b9ab699cfe0614051a73ea446ca5ff88e290;hp=e5b12166ce9a7090926ea1957b4dedb2b998767c;hpb=643396ce0a02f6e59ba76cf6b8e38d51a680624e;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Image.php b/includes/Image.php index e5b12166ce..9145a0dcf6 100644 --- a/includes/Image.php +++ b/includes/Image.php @@ -1,6 +1,5 @@ extension = Image::normalizeExtension( $n ? substr( $this->name, $n + 1 ) : '' ); $this->historyLine = 0; + $this->page = 1; $this->dataLoaded = false; } - + /** * Normalize a file extension to the common form, and ensure it's clean. * Extensions with non-alphanumeric characters will be discarded. @@ -113,18 +113,18 @@ class Image return ''; } } - + /** * Get the memcached keys * Returns an array, first element is the local cache key, second is the shared cache key, if there is one */ function getCacheKeys( ) { - global $wgDBname, $wgUseSharedUploads, $wgSharedUploadDBname, $wgCacheSharedUploads; + global $wgUseSharedUploads, $wgSharedUploadDBname, $wgCacheSharedUploads; $hashedName = md5($this->name); - $keys = array( "$wgDBname:Image:$hashedName" ); + $keys = array( wfMemcKey( 'Image', $hashedName ) ); if ( $wgUseSharedUploads && $wgSharedUploadDBname && $wgCacheSharedUploads ) { - $keys[] = "$wgSharedUploadDBname:Image:$hashedName"; + $keys[] = wfForeignMemcKey( $wgSharedUploadDBname, false, 'Image', $hashedName ); } return $keys; } @@ -142,7 +142,7 @@ class Image // Check if the key existed and belongs to this version of MediaWiki if (!empty($cachedValues) && is_array($cachedValues) && isset($cachedValues['version']) && ( $cachedValues['version'] == MW_IMAGE_VERSION ) - && $cachedValues['fileExists'] && isset( $cachedValues['mime'] ) && isset( $cachedValues['metadata'] ) ) + && isset( $cachedValues['mime'] ) && isset( $cachedValues['metadata'] ) ) { if ( $wgUseSharedUploads && $cachedValues['fromShared']) { # if this is shared file, we need to check if image @@ -200,13 +200,13 @@ class Image * Save the image metadata to memcached */ function saveToCache() { - global $wgMemc; + global $wgMemc, $wgUseSharedUploads; $this->load(); $keys = $this->getCacheKeys(); - if ( $this->fileExists ) { - // We can't cache negative metadata for non-existent files, - // because if the file later appears in commons, the local - // keys won't be purged. + // We can't cache negative metadata for non-existent files, + // because if the file later appears in commons, the local + // keys won't be purged. + if ( $this->fileExists || !$wgUseSharedUploads ) { $cachedValues = array( 'version' => MW_IMAGE_VERSION, 'name' => $this->name, @@ -233,7 +233,7 @@ class Image * Load metadata from the file itself */ function loadFromFile() { - global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang, $wgShowEXIF; + global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang; wfProfileIn( __METHOD__ ); $this->imagePath = $this->getFullPath(); $this->fileExists = file_exists( $this->imagePath ); @@ -258,7 +258,7 @@ class Image if ( $this->fileExists ) { - $magic=& wfGetMimeMagic(); + $magic=& MimeMagic::singleton(); $this->mime = $magic->guessMimeType($this->imagePath,true); $this->type = $magic->getMediaType($this->imagePath,$this->mime); @@ -266,7 +266,7 @@ class Image # Get size in bytes $this->size = filesize( $this->imagePath ); - $magic=& wfGetMimeMagic(); + $magic=& MimeMagic::singleton(); # Height and width wfSuppressWarnings(); @@ -307,7 +307,11 @@ class Image $this->dataLoaded = true; - $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) ); + if ( $this->mime == 'image/vnd.djvu' ) { + $this->metadata = $deja->retrieveMetaData(); + } else { + $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) ); + } if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits']; else $this->bits = 0; @@ -322,8 +326,7 @@ class Image global $wgUseSharedUploads, $wgSharedUploadDBname, $wgSharedUploadDBprefix, $wgContLang; wfProfileIn( __METHOD__ ); - $dbr =& wfGetDB( DB_SLAVE ); - + $dbr = wfGetDB( DB_SLAVE ); $this->checkDBSchema($dbr); $row = $dbr->selectRow( 'image', @@ -344,7 +347,7 @@ class Image # capitalize the first letter of the filename before # looking it up in the shared repository. $name = $wgContLang->ucfirst($this->name); - $dbc =& wfGetDB( DB_SLAVE, 'commons' ); + $dbc = wfGetDB( DB_SLAVE, 'commons' ); $row = $dbc->selectRow( "`$wgSharedUploadDBname`.{$wgSharedUploadDBprefix}image", array( @@ -374,6 +377,7 @@ class Image $this->fileExists = false; $this->fromSharedDirectory = false; $this->metadata = serialize ( array() ) ; + $this->mime = false; } # Unconditionally set loaded=true, we don't want the accessors constantly rechecking @@ -416,9 +420,12 @@ class Image $this->loadFromDB(); if ( !$wgSharedUploadDBname && $wgUseSharedUploads ) { $this->loadFromFile(); - } elseif ( $this->fileExists ) { + } elseif ( $this->fileExists || !$wgUseSharedUploads ) { + // We can do negative caching for local images, because the cache + // will be purged on upload. But we can't do it when shared images + // are enabled, since updates to that won't purge foreign caches. $this->saveToCache(); - } + } } $this->dataLoaded = true; } @@ -442,10 +449,10 @@ class Image // Write to the other DB using selectDB, not database selectors // This avoids breaking replication in MySQL - $dbw =& wfGetDB( DB_MASTER, 'commons' ); + $dbw = wfGetDB( DB_MASTER, 'commons' ); $dbw->selectDB( $wgSharedUploadDBname ); } else { - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); } $this->checkDBSchema($dbw); @@ -470,7 +477,7 @@ class Image } wfProfileOut( __METHOD__ ); } - + /** * Split an internet media type into its two components; if not * a two-part name, set the minor type to 'unknown'. @@ -601,7 +608,7 @@ class Image * @todo remember the result of this check. */ function canRender() { - global $wgUseImageMagick; + global $wgUseImageMagick, $wgDjvuRenderer; if( $this->getWidth()<=0 || $this->getHeight()<=0 ) return false; @@ -647,6 +654,7 @@ class Image if ( $mime === 'image/vnd.wap.wbmp' || $mime === 'image/x-xbitmap' ) return true; } + if ( $mime === 'image/vnd.djvu' && isset( $wgDjvuRenderer ) && $wgDjvuRenderer ) return true; return false; } @@ -734,9 +742,16 @@ class Image * Return the escapeLocalURL of this image * @public */ - function getEscapeLocalURL() { + function getEscapeLocalURL( $query=false) { $this->getTitle(); - return $this->title->escapeLocalURL(); + if ( $query === false ) { + if ( $this->page != 1 ) { + $query = 'page=' . $this->page; + } else { + $query = ''; + } + } + return $this->title->escapeLocalURL( $query ); } /** @@ -836,6 +851,9 @@ class Image */ function thumbName( $width ) { $thumb = $width."px-".$this->name; + if ( $this->page != 1 ) { + $thumb = "page{$this->page}-$thumb"; + } if( $this->mustRender() ) { if( $this->canRender() ) { @@ -902,10 +920,10 @@ class Image } else { // Don't render, just return the URL if ( $this->validateThumbParams( $width, $height ) ) { - if ( $width == $this->width && $height == $this->height ) { + if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) { $url = $this->getURL(); } else { - list( $isScriptUrl, $url ) = $this->thumbUrl( $width ); + list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $width ); } $thumb = new ThumbnailImage( $url, $width, $height ); } else { @@ -946,7 +964,7 @@ class Image */ function validateThumbParams( &$width, &$height ) { global $wgSVGMaxSize, $wgMaxImageArea; - + $this->load(); if ( ! $this->exists() ) @@ -954,9 +972,9 @@ class Image # If there is no image, there will be no thumbnail return false; } - + $width = intval( $width ); - + # Sanity check $width if( $width <= 0 || $this->width <= 0) { # BZZZT @@ -985,7 +1003,7 @@ class Image $height = round( $this->height * $width / $this->width ); return true; } - + /** * Create a thumbnail of the image having the specified width. * The thumbnail will not be created if the width is larger than the @@ -1011,13 +1029,13 @@ class Image return null; } - if ( $width == $this->width && $height == $this->height ) { + if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) { # validateThumbParams (or the user) wants us to return the unscaled image $thumb = new ThumbnailImage( $this->getURL(), $width, $height ); wfProfileOut( __METHOD__ ); return $thumb; } - + list( $isScriptUrl, $url ) = $this->thumbUrl( $width ); if ( $isScriptUrl && $useScript ) { // Use thumb.php to render the image @@ -1056,7 +1074,7 @@ class Image @unlink( $thumbDir ); } wfMkdirParents( $thumbDir ); - + $oldThumbPath = wfDeprecatedThumbDir( $thumbName, 'thumb', $this->fromSharedDirectory ). '/'.$thumbName; $done = false; @@ -1123,13 +1141,14 @@ class Image global $wgSVGConverters, $wgSVGConverter; global $wgUseImageMagick, $wgImageMagickConvertCommand; global $wgCustomConvertCommand; + global $wgDjvuRenderer, $wgDjvuPostProcessor; $this->load(); $err = false; $cmd = ""; $retval = 0; - + if( $this->mime === "image/svg" ) { #Right now we have only SVG @@ -1149,96 +1168,112 @@ class Image $err = wfShellExec( $cmd, $retval ); wfProfileOut( 'rsvg' ); } - } elseif ( $wgUseImageMagick ) { - # use ImageMagick - - if ( $this->mime == 'image/jpeg' ) { - $quality = "-quality 80"; // 80% - } elseif ( $this->mime == 'image/png' ) { - $quality = "-quality 95"; // zlib 9, adaptive filtering - } else { - $quality = ''; // default - } - - # Specify white background color, will be used for transparent images - # in Internet Explorer/Windows instead of default black. - - # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}". - # It seems that ImageMagick has a bug wherein it produces thumbnails of - # the wrong size in the second case. - - $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) . - " {$quality} -background white -size {$width} ". - wfEscapeShellArg($this->imagePath) . - // Coalesce is needed to scale animated GIFs properly (bug 1017). - ' -coalesce ' . - // For the -resize option a "!" is needed to force exact size, - // or ImageMagick may decide your ratio is wrong and slice off - // a pixel. - " -resize " . wfEscapeShellArg( "{$width}x{$height}!" ) . - " -depth 8 " . - wfEscapeShellArg($thumbPath) . " 2>&1"; - wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n"); - wfProfileIn( 'convert' ); - $err = wfShellExec( $cmd, $retval ); - wfProfileOut( 'convert' ); - } elseif( $wgCustomConvertCommand ) { - # Use a custom convert command - # Variables: %s %d %w %h - $src = wfEscapeShellArg( $this->imagePath ); - $dst = wfEscapeShellArg( $thumbPath ); - $cmd = $wgCustomConvertCommand; - $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames - $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size - wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" ); - wfProfileIn( 'convert' ); - $err = wfShellExec( $cmd, $retval ); - wfProfileOut( 'convert' ); } else { - # Use PHP's builtin GD library functions. - # - # First find out what kind of file this is, and select the correct - # input routine for this. - - $typemap = array( - 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ), - 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( &$this, 'imageJpegWrapper' ) ), - 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ), - 'image/vnd.wap.wmbp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ), - 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ), - ); - if( !isset( $typemap[$this->mime] ) ) { - $err = 'Image type not supported'; - wfDebug( "$err\n" ); - return $err; - } - list( $loader, $colorStyle, $saveType ) = $typemap[$this->mime]; + if ( $this->mime === "image/vnd.djvu" && $wgDjvuRenderer ) { + // DJVU image + // The file contains several images. First, extract the + // page in hi-res, if it doesn't yet exist. Then, thumbnail + // it. + + $cmd = "{$wgDjvuRenderer} -page={$this->page} -size=${width}x${height} " . + wfEscapeShellArg( $this->imagePath ) . + " | {$wgDjvuPostProcessor} > " . wfEscapeShellArg($thumbPath); + wfProfileIn( 'ddjvu' ); + wfDebug( "reallyRenderThumb DJVU: $cmd\n" ); + $err = wfShellExec( $cmd, $retval ); + wfProfileOut( 'ddjvu' ); - if( !function_exists( $loader ) ) { - $err = "Incomplete GD library configuration: missing function $loader"; - wfDebug( "$err\n" ); - return $err; - } - if( $colorStyle == 'palette' ) { - $truecolor = false; - } elseif( $colorStyle == 'truecolor' ) { - $truecolor = true; - } elseif( $colorStyle == 'bits' ) { - $truecolor = ( $this->bits > 8 ); - } + } elseif ( $wgUseImageMagick ) { + # use ImageMagick - $src_image = call_user_func( $loader, $this->imagePath ); - if ( $truecolor ) { - $dst_image = imagecreatetruecolor( $width, $height ); + if ( $this->mime == 'image/jpeg' ) { + $quality = "-quality 80"; // 80% + } elseif ( $this->mime == 'image/png' ) { + $quality = "-quality 95"; // zlib 9, adaptive filtering + } else { + $quality = ''; // default + } + + # Specify white background color, will be used for transparent images + # in Internet Explorer/Windows instead of default black. + + # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}". + # It seems that ImageMagick has a bug wherein it produces thumbnails of + # the wrong size in the second case. + + $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) . + " {$quality} -background white -size {$width} ". + wfEscapeShellArg($this->imagePath) . + // Coalesce is needed to scale animated GIFs properly (bug 1017). + ' -coalesce ' . + // For the -resize option a "!" is needed to force exact size, + // or ImageMagick may decide your ratio is wrong and slice off + // a pixel. + " -thumbnail " . wfEscapeShellArg( "{$width}x{$height}!" ) . + " -depth 8 " . + wfEscapeShellArg($thumbPath) . " 2>&1"; + wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n"); + wfProfileIn( 'convert' ); + $err = wfShellExec( $cmd, $retval ); + wfProfileOut( 'convert' ); + } elseif( $wgCustomConvertCommand ) { + # Use a custom convert command + # Variables: %s %d %w %h + $src = wfEscapeShellArg( $this->imagePath ); + $dst = wfEscapeShellArg( $thumbPath ); + $cmd = $wgCustomConvertCommand; + $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames + $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size + wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" ); + wfProfileIn( 'convert' ); + $err = wfShellExec( $cmd, $retval ); + wfProfileOut( 'convert' ); } else { - $dst_image = imagecreate( $width, $height ); + # Use PHP's builtin GD library functions. + # + # First find out what kind of file this is, and select the correct + # input routine for this. + + $typemap = array( + 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ), + 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( &$this, 'imageJpegWrapper' ) ), + 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ), + 'image/vnd.wap.wmbp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ), + 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ), + ); + if( !isset( $typemap[$this->mime] ) ) { + $err = 'Image type not supported'; + wfDebug( "$err\n" ); + return $err; + } + list( $loader, $colorStyle, $saveType ) = $typemap[$this->mime]; + + if( !function_exists( $loader ) ) { + $err = "Incomplete GD library configuration: missing function $loader"; + wfDebug( "$err\n" ); + return $err; + } + if( $colorStyle == 'palette' ) { + $truecolor = false; + } elseif( $colorStyle == 'truecolor' ) { + $truecolor = true; + } elseif( $colorStyle == 'bits' ) { + $truecolor = ( $this->bits > 8 ); + } + + $src_image = call_user_func( $loader, $this->imagePath ); + if ( $truecolor ) { + $dst_image = imagecreatetruecolor( $width, $height ); + } else { + $dst_image = imagecreate( $width, $height ); + } + imagecopyresampled( $dst_image, $src_image, + 0,0,0,0, + $width, $height, $this->width, $this->height ); + call_user_func( $saveType, $dst_image, $thumbPath ); + imagedestroy( $dst_image ); + imagedestroy( $src_image ); } - imagecopyresampled( $dst_image, $src_image, - 0,0,0,0, - $width, $height, $this->width, $this->height ); - call_user_func( $saveType, $dst_image, $thumbPath ); - imagedestroy( $dst_image ); - imagedestroy( $src_image ); } # @@ -1282,16 +1317,17 @@ class Image $files = array(); $dir = wfImageThumbDir( $this->name, $shared ); - // This generates an error on failure, hence the @ - $handle = @opendir( $dir ); + if ( is_dir( $dir ) ) { + $handle = opendir( $dir ); - if ( $handle ) { - while ( false !== ( $file = readdir($handle) ) ) { - if ( $file{0} != '.' ) { - $files[] = $file; + if ( $handle ) { + while ( false !== ( $file = readdir($handle) ) ) { + if ( $file{0} != '.' ) { + $files[] = $file; + } } + closedir( $handle ); } - closedir( $handle ); } } else { $files = array(); @@ -1323,22 +1359,24 @@ class Image $dir = wfImageThumbDir( $this->name, $shared ); $urls = array(); foreach ( $files as $file ) { + $m = array(); if ( preg_match( '/^(\d+)px/', $file, $m ) ) { - $urls[] = $this->thumbUrl( $m[1], $this->fromSharedDirectory ); + list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] ); + $urls[] = $url; @unlink( "$dir/$file" ); } } // Purge the squid if ( $wgUseSquid ) { - $urls[] = $this->getViewURL(); + $urls[] = $this->getURL(); foreach ( $archiveFiles as $file ) { $urls[] = wfImageArchiveUrl( $file ); } wfPurgeSquidServers( $urls ); } } - + /** * Purge the image description page, but don't go after * pages using the image. Use when modifying file history @@ -1349,7 +1387,7 @@ class Image $page->invalidateCache(); $page->purgeSquid(); } - + /** * Purge metadata and all affected pages when the image is created, * deleted, or majorly updated. A set of additional URLs may be @@ -1360,21 +1398,23 @@ class Image // Delete thumbnails and refresh image metadata cache $this->purgeCache(); $this->purgeDescription(); - + // Purge cache of all pages using this image $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ); $update->doUpdate(); } function checkDBSchema(&$db) { + static $checkDone = false; global $wgCheckDBSchema; - if (!$wgCheckDBSchema) { + if (!$wgCheckDBSchema || $checkDone) { return; } # img_name must be unique if ( !$db->indexUnique( 'image', 'img_name' ) && !$db->indexExists('image','PRIMARY') ) { throw new MWException( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' ); } + $checkDone = true; # new fields must exist # @@ -1404,7 +1444,7 @@ class Image * @public */ function nextHistoryLine() { - $dbr =& wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE ); $this->checkDBSchema($dbr); @@ -1422,7 +1462,7 @@ class Image array( 'img_name' => $this->title->getDBkey() ), __METHOD__ ); - if ( 0 == wfNumRows( $this->historyRes ) ) { + if ( 0 == $dbr->numRows( $this->historyRes ) ) { return FALSE; } } else if ( $this->historyLine == 1 ) { @@ -1489,7 +1529,7 @@ class Image * @return bool * @static */ - function isHashed( $shared ) { + public static function isHashed( $shared ) { global $wgHashedUploadDirectory, $wgHashedSharedUploadDirectory; return $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory; } @@ -1500,7 +1540,7 @@ class Image function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) { global $wgUser, $wgUseCopyrightUpload; - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $this->checkDBSchema($dbw); @@ -1656,13 +1696,13 @@ class Image wfProfileIn( __METHOD__ ); if ( $options ) { - $db =& wfGetDB( DB_MASTER ); + $db = wfGetDB( DB_MASTER ); } else { - $db =& wfGetDB( DB_SLAVE ); + $db = wfGetDB( DB_SLAVE ); } $linkCache =& LinkCache::singleton(); - extract( $db->tableNames( 'page', 'imagelinks' ) ); + list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' ); $encName = $db->addQuotes( $this->name ); $sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options"; $res = $db->query( $sql, __METHOD__ ); @@ -1680,7 +1720,7 @@ class Image wfProfileOut( __METHOD__ ); return $retVal; } - + /** * Retrive Exif data from the file and prune unrecognized tags * and/or tags with invalid contents @@ -1690,7 +1730,7 @@ class Image */ private function retrieveExifData( $filename ) { global $wgShowEXIF; - + /* if ( $this->getMimeType() !== "image/jpeg" ) return array(); @@ -1700,13 +1740,13 @@ class Image $exif = new Exif( $filename ); return $exif->getFilteredData(); } - + return array(); } function getExifData() { global $wgRequest; - if ( $this->metadata === '0' ) + if ( $this->metadata === '0' || $this->mime == 'image/vnd.djvu' ) return array(); $purge = $wgRequest->getVal( 'action' ) == 'purge'; @@ -1740,7 +1780,7 @@ class Image } # Update EXIF data in database - $dbw =& wfGetDB( DB_MASTER ); + $dbw = wfGetDB( DB_MASTER ); $this->checkDBSchema($dbw); @@ -1760,7 +1800,7 @@ class Image function isLocal() { return !$this->fromSharedDirectory; } - + /** * Was this image ever deleted from the wiki? * @@ -1770,7 +1810,7 @@ class Image $title = Title::makeTitle( NS_IMAGE, $this->name ); return ( $title->isDeleted() > 0 ); } - + /** * Delete all versions of the image. * @@ -1785,34 +1825,34 @@ class Image function delete( $reason ) { $transaction = new FSTransaction(); $urlArr = array( $this->getURL() ); - + if( !FileStore::lock() ) { wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" ); return false; } - + try { $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); - + // Delete old versions $result = $dbw->select( 'oldimage', array( 'oi_archive_name' ), array( 'oi_name' => $this->name ) ); - + while( $row = $dbw->fetchObject( $result ) ) { $oldName = $row->oi_archive_name; - + $transaction->add( $this->prepareDeleteOld( $oldName, $reason ) ); - + // We'll need to purge this URL from caches... $urlArr[] = wfImageArchiveUrl( $oldName ); } $dbw->freeResult( $result ); - + // And the current version... $transaction->add( $this->prepareDeleteCurrent( $reason ) ); - + $dbw->immediateCommit(); } catch( MWException $e ) { wfDebug( __METHOD__.": db error, rolling back file transactions\n" ); @@ -1820,22 +1860,22 @@ class Image FileStore::unlock(); throw $e; } - + wfDebug( __METHOD__.": deleted db items, applying file transactions\n" ); $transaction->commit(); FileStore::unlock(); - + // Update site_stats $site_stats = $dbw->tableName( 'site_stats' ); $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ ); - + $this->purgeEverything( $urlArr ); - + return true; } - - + + /** * Delete an old version of the image. * @@ -1851,12 +1891,12 @@ class Image function deleteOld( $archiveName, $reason ) { $transaction = new FSTransaction(); $urlArr = array(); - + if( !FileStore::lock() ) { wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" ); return false; } - + $transaction = new FSTransaction(); try { $dbw = wfGetDB( DB_MASTER ); @@ -1869,11 +1909,11 @@ class Image FileStore::unlock(); throw $e; } - + wfDebug( __METHOD__.": deleted db items, applying file transaction\n" ); $transaction->commit(); FileStore::unlock(); - + $this->purgeDescription(); // Squid purging @@ -1886,7 +1926,7 @@ class Image } return true; } - + /** * Delete the current version of a file. * May throw a database error. @@ -1960,12 +2000,12 @@ class Image */ private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $fname ) { global $wgUser, $wgSaveDeletedFiles; - + // Dupe the file into the file store if( file_exists( $path ) ) { if( $wgSaveDeletedFiles ) { $group = 'deleted'; - + $store = FileStore::get( $group ); $key = FileStore::calculateKey( $path, $this->extension ); $transaction = $store->insert( $key, $path, @@ -1981,24 +2021,24 @@ class Image $key = null; $transaction = new FSTransaction(); // empty } - + if( $transaction === false ) { // Fail to restore? wfDebug( __METHOD__.": import to file store failed, aborting\n" ); throw new MWException( "Could not archive and delete file $path" ); return false; } - + $dbw = wfGetDB( DB_MASTER ); $storageMap = array( 'fa_storage_group' => $dbw->addQuotes( $group ), 'fa_storage_key' => $dbw->addQuotes( $key ), - + 'fa_deleted_user' => $dbw->addQuotes( $wgUser->getId() ), 'fa_deleted_timestamp' => $dbw->timestamp(), 'fa_deleted_reason' => $dbw->addQuotes( $reason ) ); $allFields = array_merge( $storageMap, $fieldMap ); - + try { if( $wgSaveDeletedFiles ) { $dbw->insertSelect( 'filearchive', $table, $allFields, $where, $fname ); @@ -2011,10 +2051,10 @@ class Image $transaction->rollback(); throw $e; } - + return $transaction; } - + /** * Restore all or specified deleted revisions to the given file. * Permissions and logging are left to the caller. @@ -2031,31 +2071,31 @@ class Image wfDebug( __METHOD__." could not acquire filestore lock\n" ); return false; } - + $transaction = new FSTransaction(); try { $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); - + // Re-confirm whether this image presently exists; // if no we'll need to create an image record for the // first item we restore. $exists = $dbw->selectField( 'image', '1', array( 'img_name' => $this->name ), __METHOD__ ); - + // Fetch all or selected archived revisions for the file, // sorted from the most recent to the oldest. $conditions = array( 'fa_name' => $this->name ); if( $versions ) { $conditions['fa_id'] = $versions; } - + $result = $dbw->select( 'filearchive', '*', $conditions, __METHOD__, array( 'ORDER BY' => 'fa_timestamp DESC' ) ); - + if( $dbw->numRows( $result ) < count( $versions ) ) { // There's some kind of conflict or confusion; // we can't restore everything we were asked to. @@ -2072,7 +2112,7 @@ class Image FileStore::unlock(); return true; } - + $revisions = 0; while( $row = $dbw->fetchObject( $result ) ) { $revisions++; @@ -2081,21 +2121,21 @@ class Image wfDebug( __METHOD__.": skipping row with no file.\n" ); continue; } - + if( $revisions == 1 && !$exists ) { $destDir = wfImageDir( $row->fa_name ); if ( !is_dir( $destDir ) ) { wfMkdirParents( $destDir ); } $destPath = $destDir . DIRECTORY_SEPARATOR . $row->fa_name; - + // We may have to fill in data if this was originally // an archived file revision. if( is_null( $row->fa_metadata ) ) { $tempFile = $store->filePath( $row->fa_storage_key ); $metadata = serialize( $this->retrieveExifData( $tempFile ) ); - - $magic = wfGetMimeMagic(); + + $magic = MimeMagic::singleton(); $mime = $magic->guessMimeType( $tempFile, true ); $media_type = $magic->getMediaType( $tempFile, $mime ); list( $major_mime, $minor_mime ) = self::splitMime( $mime ); @@ -2105,7 +2145,7 @@ class Image $minor_mime = $row->fa_minor_mime; $media_type = $row->fa_media_type; } - + $table = 'image'; $fields = array( 'img_name' => $row->fa_name, @@ -2136,7 +2176,7 @@ class Image wfMkdirParents( $destDir ); } $destPath = $destDir . DIRECTORY_SEPARATOR . $archiveName; - + $table = 'oldimage'; $fields = array( 'oi_name' => $row->fa_name, @@ -2150,13 +2190,13 @@ class Image 'oi_user_text' => $row->fa_user_text, 'oi_timestamp' => $row->fa_timestamp ); } - + $dbw->insert( $table, $fields, __METHOD__ ); /// @fixme this delete is not totally safe, potentially $dbw->delete( 'filearchive', array( 'fa_id' => $row->fa_id ), __METHOD__ ); - + // Check if any other stored revisions use this file; // if so, we shouldn't remove the file from the deletion // archives so they will still work. @@ -2172,44 +2212,127 @@ class Image } else { $flags = 0; } - + $transaction->add( $store->export( $row->fa_storage_key, $destPath, $flags ) ); } - + $dbw->immediateCommit(); } catch( MWException $e ) { wfDebug( __METHOD__." caught error, aborting\n" ); $transaction->rollback(); throw $e; } - + $transaction->commit(); FileStore::unlock(); - + if( $revisions > 0 ) { if( !$exists ) { wfDebug( __METHOD__." restored $revisions items, creating a new current\n" ); - + // Update site_stats $site_stats = $dbw->tableName( 'site_stats' ); $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ ); - + $this->purgeEverything(); } else { wfDebug( __METHOD__." restored $revisions as archived versions\n" ); $this->purgeDescription(); } } - + return $revisions; } - + + /** + * Select a page from a multipage document. Determines the page used for + * rendering thumbnails. + * + * @param $page Integer: page number, starting with 1 + */ + function selectPage( $page ) { + if( $this->initializeMultiPageXML() ) { + wfDebug( __METHOD__." selecting page $page \n" ); + $this->page = $page; + $o = $this->multiPageXML->BODY[0]->OBJECT[$page-1]; + $this->height = intval( $o['height'] ); + $this->width = intval( $o['width'] ); + } else { + wfDebug( __METHOD__." selectPage($page) for bogus multipage xml on '$this->name'\n" ); + return; + } + } + + /** + * Lazy-initialize multipage XML metadata for DjVu files. + * @return bool true if $this->multiPageXML is set up and ready; + * false if corrupt or otherwise failing + */ + function initializeMultiPageXML() { + $this->load(); + if ( isset( $this->multiPageXML ) ) { + return true; + } + + # + # Check for files uploaded prior to DJVU support activation, + # or damaged. + # + if( empty( $this->metadata ) || $this->metadata == serialize( array() ) ) { + $deja = new DjVuImage( $this->imagePath ); + $this->metadata = $deja->retrieveMetaData(); + $this->purgeMetadataCache(); + + # Update metadata in the database + $dbw = wfGetDB( DB_MASTER ); + $dbw->update( 'image', + array( 'img_metadata' => $this->metadata ), + array( 'img_name' => $this->name ), + __METHOD__ + ); + } + wfSuppressWarnings(); + try { + $this->multiPageXML = new SimpleXMLElement( $this->metadata ); + } catch( Exception $e ) { + wfDebug( "Bogus multipage XML metadata on '$this->name'\n" ); + $this->multiPageXML = null; + } + wfRestoreWarnings(); + return isset( $this->multiPageXML ); + } + + /** + * Returns 'true' if this image is a multipage document, e.g. a DJVU + * document. + * + * @return Bool + */ + function isMultipage() { + return ( $this->mime == 'image/vnd.djvu' ); + } + + /** + * Returns the number of pages of a multipage document, or NULL for + * documents which aren't multipage documents + */ + function pageCount() { + if ( ! $this->isMultipage() ) { + return null; + } + if( $this->initializeMultiPageXML() ) { + return count( $this->multiPageXML->xpath( '//OBJECT' ) ); + } else { + wfDebug( "Requested pageCount() for bogus multi-page metadata for '$this->name'\n" ); + return null; + } + } + } //class /** * Wrapper class for thumbnail images - * @package MediaWiki */ class ThumbnailImage { /**