X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;ds=sidebyside;f=includes%2FImage.php;h=581727285f45f9a7459b5f0dd691424709a9eca9;hb=5cfa89d97b8bb2a9d41eaf9af7a7c71749799b84;hp=b2d457fcc2c68578fb09b28064a747f1d4a80db6;hpb=8395a4e8e7393e70a065c51f74ffb230166a6968;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Image.php b/includes/Image.php index b2d457fcc2..581727285f 100644 --- a/includes/Image.php +++ b/includes/Image.php @@ -3,6 +3,17 @@ * @package MediaWiki */ +# NOTE FOR WINDOWS USERS: +# To enable EXIF functions, add the folloing lines to the +# "Windows extensions" section of php.ini: +# +# extension=extensions/php_mbstring.dll +# extension=extensions/php_exif.dll + +if ($wgShowEXIF) + require_once('Exif.php'); + + /** * Class to represent an image * @@ -29,6 +40,7 @@ class Image $type, # | $attr, # / $size, # Size in bytes (loadFromXxx) + $exif, # EXIF data $dataLoaded; # Whether or not all this has been loaded from the database (loadFromXxx) @@ -55,6 +67,7 @@ class Image function Image( $title ) { $this->title =& $title; $this->name = $title->getDBkey(); + $this->exif = serialize ( array() ) ; $n = strrpos( $this->name, '.' ); $this->extension = strtolower( $n ? substr( $this->name, $n + 1 ) : '' ); @@ -105,6 +118,7 @@ class Image $this->height = $commonsCachedValues['height']; $this->bits = $commonsCachedValues['bits']; $this->type = $commonsCachedValues['type']; + $this->exif = $commonsCachedValues['exif']; $this->size = $commonsCachedValues['size']; $this->fromSharedDirectory = true; $this->dataLoaded = true; @@ -120,6 +134,7 @@ class Image $this->height = $cachedValues['height']; $this->bits = $cachedValues['bits']; $this->type = $cachedValues['type']; + $this->exif = $cachedValues['exif']; $this->size = $cachedValues['size']; $this->fromSharedDirectory = false; $this->dataLoaded = true; @@ -150,6 +165,7 @@ class Image 'height' => $this->height, 'bits' => $this->bits, 'type' => $this->type, + 'exif' => $this->exif, 'size' => $this->size); $wgMemc->set( $keys[0], $cachedValues ); @@ -184,8 +200,7 @@ class Image if ( $this->fileExists ) { # Get size in bytes - $s = stat( $this->imagePath ); - $this->size = $s['size']; + $this->size = filesize( $this->imagePath ); # Height and width # Don't try to get the width and height of sound and video files, that's bad for performance @@ -206,10 +221,12 @@ class Image $this->height = 0; $this->bits = 0; $this->type = 0; + $this->exif = serialize ( array() ) ; } else { $this->width = $gis[0]; $this->height = $gis[1]; $this->type = $gis[2]; + $this->exif = serialize ( $this->retrieveExifData() ) ; if ( isset( $gis['bits'] ) ) { $this->bits = $gis['bits']; } else { @@ -230,13 +247,17 @@ class Image $dbr =& wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'image', - array( 'img_size', 'img_width', 'img_height', 'img_bits', 'img_type' ), + array( 'img_size', 'img_width', 'img_height', 'img_bits', 'img_type' , 'img_metadata' ), array( 'img_name' => $this->name ), $fname ); if ( $row ) { $this->fromSharedDirectory = false; $this->fileExists = true; $this->loadFromRow( $row ); $this->imagePath = $this->getFullPath(); + // Check for rows from a previous schema, quietly upgrade them + if ( $this->type == -1 ) { + $this->upgradeRow(); + } } elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) { # In case we're on a wgCapitalLinks=false wiki, we # capitalize the first letter of the filename before @@ -252,6 +273,11 @@ class Image $this->imagePath = $this->getFullPath(true); $this->name = $name; $this->loadFromRow( $row ); + + // Check for rows from a previous schema, quietly upgrade them + if ( $this->type == -1 ) { + $this->upgradeRow(); + } } } @@ -263,6 +289,7 @@ class Image $this->type = 0; $this->fileExists = false; $this->fromSharedDirectory = false; + $this->exif = serialize ( array() ) ; } # Unconditionally set loaded=true, we don't want the accessors constantly rechecking @@ -278,6 +305,8 @@ class Image $this->height = $row->img_height; $this->bits = $row->img_bits; $this->type = $row->img_type; + $this->exif = $row->img_metadata; + if ( $this->exif == "" ) $this->exif = serialize ( array() ) ; $this->dataLoaded = true; } @@ -285,10 +314,13 @@ class Image * Load image metadata from cache or DB, unless already loaded */ function load() { + global $wgSharedUploadDBname, $wgUseSharedUploads; if ( !$this->dataLoaded ) { if ( !$this->loadFromCache() ) { $this->loadFromDB(); - if ( $this->fileExists ) { + if ( !$wgSharedUploadDBname && $wgUseSharedUploads ) { + $this->loadFromFile(); + } elseif ( $this->fileExists ) { $this->saveToCache(); } } @@ -296,6 +328,39 @@ class Image } } + /** + * Metadata was loaded from the database, but the row had a marker indicating it needs to be + * upgraded from the 1.4 schema, which had no width, height, bits or type. Upgrade the row. + */ + function upgradeRow() { + global $wgDBname, $wgSharedUploadDBname; + $fname = 'Image::upgradeRow'; + $this->loadFromFile(); + $dbw =& wfGetDB( DB_MASTER ); + + if ( $this->fromSharedDirectory ) { + if ( !$wgSharedUploadDBname ) { + return; + } + + // Write to the other DB using selectDB, not database selectors + // This avoids breaking replication in MySQL + $dbw->selectDB( $wgSharedUploadDBname ); + } + $dbw->update( 'image', + array( + 'img_width' => $this->width, + 'img_height' => $this->height, + 'img_bits' => $this->bits, + 'img_type' => $this->type, + 'img_metadata' => $this->exif, + ), array( 'img_name' => $this->name ), $fname + ); + if ( $this->fromSharedDirectory ) { + $dbw->selectDB( $wgDBname ); + } + } + /** * Return the name of this image * @access public @@ -473,10 +538,14 @@ class Image $base = $wgUploadBaseUrl; $path = $wgUploadPath; } - $url = "{$base}{$path}/{$subdir}" . - wfGetHashPath($this->name, $this->fromSharedDirectory) - . $this->name.'/'.$name; - $url = wfUrlencode( $url ); + if ( Image::isHashed( $this->fromSharedDirectory ) ) { + $url = "{$base}{$path}/{$subdir}" . + wfGetHashPath($this->name, $this->fromSharedDirectory) + . $this->name.'/'.$name; + $url = wfUrlencode( $url ); + } else { + $url = "{$base}{$path}/{$subdir}/{$name}"; + } } return array( $script !== false, $url ); } @@ -581,7 +650,7 @@ class Image * @return ThumbnailImage * @access private */ - function /* private */ renderThumb( $width, $useScript = true ) { + function renderThumb( $width, $useScript = true ) { global $wgUseSquid, $wgInternalServer; global $wgThumbnailScriptPath, $wgSharedThumbnailScriptPath; @@ -652,7 +721,7 @@ class Image * * @access private */ - function /*private*/ reallyRenderThumb( $thumbPath, $width, $height ) { + function reallyRenderThumb( $thumbPath, $width, $height ) { global $wgSVGConverters, $wgSVGConverter, $wgUseImageMagick, $wgImageMagickConvertCommand; @@ -753,20 +822,24 @@ class Image * Get all thumbnail names previously generated for this image */ function getThumbnails( $shared = false ) { - $this->load(); - $files = array(); - $dir = wfImageThumbDir( $this->name, $shared ); + if ( Image::isHashed( $shared ) ) { + $this->load(); + $files = array(); + $dir = wfImageThumbDir( $this->name, $shared ); - // This generates an error on failure, hence the @ - $handle = @opendir( $dir ); - - if ( $handle ) { - while ( false !== ( $file = readdir($handle) ) ) { - if ( $file{0} != '.' ) { - $files[] = $file; + // This generates an error on failure, hence the @ + $handle = @opendir( $dir ); + + if ( $handle ) { + while ( false !== ( $file = readdir($handle) ) ) { + if ( $file{0} != '.' ) { + $files[] = $file; + } } + closedir( $handle ); } - closedir( $handle ); + } else { + $files = array(); } return $files; @@ -779,6 +852,7 @@ class Image global $wgInternalServer, $wgUseSquid; // Refresh metadata cache + clearstatcache(); $this->loadFromFile(); $this->saveToCache(); @@ -885,6 +959,15 @@ class Image return $fullpath; } + + /** + * @return bool + * @static + */ + function isHashed( $shared ) { + global $wgHashedUploadDirectory, $wgHashedSharedUploadDirectory; + return $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory; + } /** * @return bool @@ -900,7 +983,7 @@ class Image */ function recordUpload( $oldver, $desc, $copyStatus = '', $source = '' ) { global $wgUser, $wgLang, $wgTitle, $wgOut, $wgDeferredUpdateList; - global $wgUseCopyrightUpload; + global $wgUseCopyrightUpload, $wgUseSquid, $wgPostCommitUpdateList; $fname = 'Image::recordUpload'; $dbw =& wfGetDB( DB_MASTER ); @@ -910,7 +993,7 @@ class Image wfDebugDieBacktrace( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' ); } - // Delete thumbnails and refresh the cache + // Delete thumbnails and refresh the metadata cache $this->purgeCache(); // Fail now if the image isn't there @@ -926,7 +1009,7 @@ class Image $textdesc = $desc; } - $now = wfTimestampNow(); + $now = $dbw->timestamp(); # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also @@ -939,13 +1022,15 @@ class Image 'img_height' => $this->height, 'img_bits' => $this->bits, 'img_type' => $this->type, - 'img_timestamp' => $dbw->timestamp($now), + 'img_timestamp' => $now, 'img_description' => $desc, 'img_user' => $wgUser->getID(), 'img_user_text' => $wgUser->getName(), + 'img_metadata' => $this->exif, ), $fname, 'IGNORE' ); $descTitle = $this->getTitle(); + $purgeURLs = array(); if ( $dbw->affectedRows() ) { # Successfully inserted, this is a new image @@ -982,10 +1067,11 @@ class Image 'img_height' => $this->height, 'img_bits' => $this->bits, 'img_type' => $this->type, - 'img_timestamp' => $dbw->timestamp(), + 'img_timestamp' => $now, 'img_user' => $wgUser->getID(), 'img_user_text' => $wgUser->getName(), 'img_description' => $desc, + 'img_metadata' => $this->exif, ), array( /* WHERE */ 'img_name' => $this->name ), $fname @@ -993,14 +1079,111 @@ class Image # Invalidate the cache for the description page $descTitle->invalidateCache(); + $purgeURLs[] = $descTitle->getInternalURL(); } + # Invalidate cache for all pages using this image + $linksTo = $this->getLinksTo(); + + if ( $wgUseSquid ) { + $u = SquidUpdate::newFromTitles( $linksTo, $purgeURLs ); + array_push( $wgPostCommitUpdateList, $u ); + } + Title::touchArray( $linksTo ); + $log = new LogPage( 'upload' ); $log->addEntry( 'upload', $descTitle, $desc ); return true; } + + /** + * Get an array of Title objects which are articles which use this image + * Also adds their IDs to the link cache + * + * This is mostly copied from Title::getLinksTo() + */ + function getLinksTo( $options = '' ) { + global $wgLinkCache; + $fname = 'Image::getLinksTo'; + wfProfileIn( $fname ); + + if ( $options ) { + $db =& wfGetDB( DB_MASTER ); + } else { + $db =& wfGetDB( DB_SLAVE ); + } + + extract( $db->tableNames( '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, $fname ); + + $retVal = array(); + if ( $db->numRows( $res ) ) { + while ( $row = $db->fetchObject( $res ) ) { + if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) { + $wgLinkCache->addGoodLink( $row->page_id, $titleObj->getPrefixedDBkey() ); + $retVal[] = $titleObj; + } + } + } + $db->freeResult( $res ); + return $retVal; + } + function retrieveExifData () { + global $wgShowEXIF ; + if ( ! $wgShowEXIF ) return array (); + if ( $this->type !== '2' ) return array (); + + $exif = exif_read_data( $this->imagePath ); + + $obj = new Exif; + // Some of the type checks in validate will spew warnings on invalid data + wfSuppressWarnings(); + foreach($exif as $k => $v) { + if ( !in_array($k, $obj->mValidExif) || !$obj->validate($k, $v) ) + unset($exif[$k]); + } + wfRestoreWarnings(); + + return $exif; + } + + function getExifData () { + global $wgRequest; + + $ret = unserialize ( $this->exif ); + + if ( count( $ret) == 0 || $wgRequest->getVal( 'action' ) == 'purge' ) { # No EXIF data was stored for this image + $this->updateExifData() ; + $ret = unserialize ( $this->exif ) ; + } + + return $ret ; + } + + function updateExifData () { + global $wgShowEXIF ; + if ( ! $wgShowEXIF ) return ; + if ( false === $this->getImagePath() ) return ; # Not a local image + + $fname = "Image:updateExifData" ; + + # Get EXIF data from image + $exif = $this->retrieveExifData () ; + $this->exif = serialize ( $exif ) ; + + # Update EXIF data in database + $dbw =& wfGetDB( DB_MASTER ); + $dbw->update( 'image', + array( 'img_metadata' => $this->exif ), + array( 'img_name' => $this->name ), + $fname + ); + } + } //class @@ -1043,12 +1226,25 @@ function wfImageDir( $fname ) { * @access public */ function wfImageThumbDir( $fname, $shared = false ) { - $dir = wfImageArchiveDir( $fname, 'thumb', $shared ) . "/$fname"; - if ( ! is_dir( $dir ) ) { - $oldumask = umask(0); - @mkdir( $dir, 0777 ); - umask( $oldumask ); + $base = wfImageArchiveDir( $fname, 'thumb', $shared ); + if ( Image::isHashed( $shared ) ) { + $dir = "$base/$fname"; + + if ( !is_dir( $base ) ) { + $oldumask = umask(0); + @mkdir( $base, 0777 ); + umask( $oldumask ); + } + + if ( ! is_dir( $dir ) ) { + $oldumask = umask(0); + @mkdir( $dir, 0777 ); + umask( $oldumask ); + } + } else { + $dir = $base; } + return $dir; } @@ -1109,9 +1305,7 @@ function wfGetHashPath ( $dbkey, $fromSharedDirectory = false ) { global $wgHashedSharedUploadDirectory, $wgSharedUploadDirectory; global $wgHashedUploadDirectory; - $ishashed = $fromSharedDirectory ? $wgHashedSharedUploadDirectory : - $wgHashedUploadDirectory; - if($ishashed) { + if( Image::isHashed( $fromSharedDirectory ) ) { $hash = md5($dbkey); return '/' . $hash{0} . '/' . substr( $hash, 0, 2 ) . '/'; } else { @@ -1203,21 +1397,27 @@ function wfGetSVGsize( $filename ) { } /** - * Is an image on the bad image list? + * Determine if an image exists on the 'bad image list' + * + * @param string $name The image to check + * @return bool */ function wfIsBadImage( $name ) { - global $wgLang; - - $lines = explode("\n", wfMsgForContent( 'bad_image_list' )); - foreach ( $lines as $line ) { - if ( preg_match( '/^\*\s*\[\[:(' . $wgLang->getNsText( NS_IMAGE ) . ':.*(?=]]))\]\]/', $line, $m ) ) { - $t = Title::newFromText( $m[1] ); - if ( $t->getDBkey() == $name ) { - return true; + global $wgContLang; + static $titleList = false; + if ( $titleList === false ) { + $titleList = array(); + + $lines = explode("\n", wfMsgForContent( 'bad_image_list' )); + foreach ( $lines as $line ) { + if ( preg_match( '/^\*\s*\[{2}:(' . $wgContLang->getNsText( NS_IMAGE ) . ':.*?)\]{2}/', $line, $m ) ) { + $t = Title::newFromText( $m[1] ); + $titleList[$t->getDBkey()] = 1; } } } - return false; + + return array_key_exists( $name, $titleList ); }