+ /**
+ * Return the full filesystem path to the file. Note that this does
+ * not mean that a file actually exists under that location.
+ *
+ * This path depends on whether directory hashing is active or not,
+ * i.e. whether the images are all found in the same directory,
+ * or in hashed paths like /images/3/3c.
+ *
+ * @access public
+ * @param boolean $fromSharedDirectory Return the path to the file
+ * in a shared repository (see $wgUseSharedRepository and related
+ * options in DefaultSettings.php) instead of a local one.
+ *
+ */
+ function getFullPath( $fromSharedRepository = false ) {
+ global $wgUploadDirectory, $wgSharedUploadDirectory;
+ global $wgHashedUploadDirectory, $wgHashedSharedUploadDirectory;
+
+ $dir = $fromSharedRepository ? $wgSharedUploadDirectory :
+ $wgUploadDirectory;
+
+ // $wgSharedUploadDirectory may be false, if thumb.php is used
+ if ( $dir ) {
+ $fullpath = $dir . wfGetHashPath($this->name, $fromSharedRepository) . $this->name;
+ } else {
+ $fullpath = false;
+ }
+
+ return $fullpath;
+ }
+
+ /**
+ * @return bool
+ * @static
+ */
+ function isHashed( $shared ) {
+ global $wgHashedUploadDirectory, $wgHashedSharedUploadDirectory;
+ return $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
+ }
+
+ /**
+ * @return bool
+ * @static
+ */
+ function isKnownImageExtension( $ext ) {
+ static $extensions = array( 'svg', 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'xbm' );
+ return in_array( $ext, $extensions );
+ }
+
+ /**
+ * Record an image upload in the upload log and the image table
+ */
+ function recordUpload( $oldver, $desc, $copyStatus = '', $source = '' ) {
+ global $wgUser, $wgLang, $wgTitle, $wgOut, $wgDeferredUpdateList;
+ global $wgUseCopyrightUpload, $wgUseSquid, $wgPostCommitUpdateList;
+
+ $fname = 'Image::recordUpload';
+ $dbw =& wfGetDB( DB_MASTER );
+
+ # img_name must be unique
+ if ( !$dbw->indexUnique( 'image', 'img_name' ) && !$dbw->indexExists('image','PRIMARY') ) {
+ wfDebugDieBacktrace( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' );
+ }
+
+ // Delete thumbnails and refresh the metadata cache
+ $this->purgeCache();
+
+ // Fail now if the image isn't there
+ if ( !$this->fileExists || $this->fromSharedDirectory ) {
+ return false;
+ }
+
+ if ( $wgUseCopyrightUpload ) {
+ $textdesc = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n" .
+ '== ' . wfMsg ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
+ '== ' . wfMsg ( 'filesource' ) . " ==\n" . $source ;
+ } else {
+ $textdesc = $desc;
+ }
+
+ $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
+ # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
+ $dbw->insert( 'image',
+ array(
+ 'img_name' => $this->name,
+ 'img_size'=> $this->size,
+ 'img_width' => $this->width,
+ 'img_height' => $this->height,
+ 'img_bits' => $this->bits,
+ 'img_type' => $this->type,
+ 'img_timestamp' => $now,
+ 'img_description' => $desc,
+ 'img_user' => $wgUser->getID(),
+ 'img_user_text' => $wgUser->getName(),
+ 'img_exif' => $this->exif,
+ ), $fname, 'IGNORE'
+ );
+ $descTitle = $this->getTitle();
+ $purgeURLs = array();
+
+ if ( $dbw->affectedRows() ) {
+ # Successfully inserted, this is a new image
+ $id = $descTitle->getArticleID();
+
+ if ( $id == 0 ) {
+ $article = new Article( $descTitle );
+ $article->insertNewArticle( $textdesc, $desc, false, false, true );
+ }
+ } else {
+ # Collision, this is an update of an image
+ # Insert previous contents into oldimage
+ $dbw->insertSelect( 'oldimage', 'image',
+ array(
+ 'oi_name' => 'img_name',
+ 'oi_archive_name' => $dbw->addQuotes( $oldver ),
+ 'oi_size' => 'img_size',
+ 'oi_width' => 'img_width',
+ 'oi_height' => 'img_height',
+ 'oi_bits' => 'img_bits',
+ 'oi_type' => 'img_type',
+ 'oi_timestamp' => 'img_timestamp',
+ 'oi_description' => 'img_description',
+ 'oi_user' => 'img_user',
+ 'oi_user_text' => 'img_user_text',
+ ), array( 'img_name' => $this->name ), $fname
+ );
+
+ # Update the current image row
+ $dbw->update( 'image',
+ array( /* SET */
+ 'img_size' => $this->size,
+ 'img_width' => $this->width,
+ 'img_height' => $this->height,
+ 'img_bits' => $this->bits,
+ 'img_type' => $this->type,
+ 'img_timestamp' => $now,
+ 'img_user' => $wgUser->getID(),
+ 'img_user_text' => $wgUser->getName(),
+ 'img_description' => $desc,
+ 'img_exif' => $this->exif,
+ ), array( /* WHERE */
+ 'img_name' => $this->name
+ ), $fname
+ );
+
+ # 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 );
+ $exif = $this->stripExifData( $exif );
+ return $exif ;
+ }
+
+ function getExifData () {
+ global $wgRequest, $wgShowEXIF;
+
+ if ( ! $wgShowEXIF ) return array ();
+
+ $action = $wgRequest->getVal( 'action' ); # Allow forced updates
+
+ $ret = unserialize ( $this->exif );
+
+ if ( count( $ret) == 0 || $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_exif' => $this->exif ),
+ array( 'img_name' => $this->name ),
+ $fname
+ );
+ }
+
+ /**
+ * Strip out potentially nasty exif data such as raw binaries
+ * (thumbnails), these values are from version 2.2 of the EXIF
+ * specification, note that I've commented some of them out, this is
+ * because their Type is "UNDEFINED", meaning that they potentially
+ * contain binary data.
+ *
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * @link http://exif.org/specifications.html
+ * @link http://exif.org/Exif2-2.PDF (see page 22 and 30)
+ *
+ * @param array $exif
+ * @return array
+ */
+ function stripExifData( $exif = array() ) {
+ $whitelist = array(
+ # Other tags
+ 'Make', # Image input equipment manufacturer
+ 'Model', # Image input equipment model
+ 'Software', # Software used
+ 'Artist', # Person who created the image
+ 'Copyright', # Copyright holder
+
+ # Tags relating to image structure
+ 'ImageWidth', # Image width
+ 'ImageLength', # Image height
+ 'Orientation', # Orientation of image
+ 'SamplesPerPixel', # Number of components
+ 'PlanarConfiguration', # Image data arrangement
+ 'YCbCrSubSampling', # Subsampling ratio of Y to C
+ 'YCbCrPositioning', # Y and C positioning
+ 'XResolution', # Image resolution in width direction
+ 'YResolution', # Image resolution in height direction
+ 'ResolutionUnit', # Unit of X and Y resolution
+
+ # Tags relating to recording offset
+ 'StripOffsets', # Image data location
+ 'RowsPerStrip', # Number of rows per strip
+ 'StripByteCounts', # Bytes per compressed strip
+ 'JPEGInterchangeFormat', # Offset to JPEG SOI
+ 'JPEGInterchangeFormatLength', # Bytes of JPEG data
+
+ # Tags relating to image data characteristics
+ 'TransferFunction', # Transfer function
+ 'WhitePoint', # White point chromaticity
+ 'PrimaryChromaticities', # Chromaticities of primarities
+ 'YCbCrCoefficients', # Color space transformation matrix coefficients
+ 'ReferenceBlackWhite', # Pair of black and white reference values
+
+ # Tags relating to version
+ #'ExifVersion', # Exif version
+ #'FlashpixVersion', # Supported Flashpix version
+
+ # Tags relating to Image Data Characteristics
+ 'ColorSpace', # Color space information
+ #'ComponentsConfiguration', # Meaning of each component
+ 'CompressedBitsPerPixel', # Image compression mode
+ 'PixelYDimension', # Valid image width
+ 'PixelXDimension', # Valind image height
+
+ # Tags relating to User Information
+ #'MakerNote', # Manufacturer notes
+ #'UserComment', # User commentss
+
+ # Tag relating to related file information
+ #'RelatedSoundFile', # Related audio file
+
+ # Other tags
+ 'ImageUniqueID', # Unique image ID
+
+ # Tags relating to picture-taking conditions
+ 'ExposureTime', # Exposure time
+ 'FNumber', # F Number
+ 'ExposureProgram', # Exposure Program
+ 'SpectralSensitivity', # Spectral sensitivity
+ 'ISOSpeedRatings', # ISO speed rating
+ #'OECF', # Optoelectronic conversion factor
+ 'ShutterSpeedValue', # Shutter speed
+ 'ApertureValue', # Aperture
+ 'BrightnessValue', # Brightness
+ 'ExposureBiasValue', # Exposure bias
+ 'MaxApertureValue', # Maximum land aperture
+ 'SubjectDistance', # Subject distance
+ 'MeteringMode', # Metering mode
+ 'LightSource', # Light source
+ 'Flash', # Flash
+ 'FocalLength', # Lens focal length
+ 'SubjectArea', # Subject area
+ 'FlashEnergy', # Flash energy
+ #'SpartialFrequencyResponse', # Spartial frequency response
+ 'FocalPlaneXRessolution', # Focal plane X resolution
+ 'FocalPlaneYRessolution', # Focal plane Y resolution
+ 'FocalPlaneResolutionUnit', # Focal plane resolution unit
+ 'SubjectLocation', # Subject location
+ 'ExposureIndex', # Exposure index
+ 'SensingMethod', # Sensing method
+ #'FileSource', # File source
+ #'SceneType', # Scene type
+ #'CFAPattern', # CFA pattern
+ 'CustomRendered', # Custom image processing
+ 'ExposureMode', # Exposure mode
+ 'WhiteBalance', # White Balance
+ 'DigitalZoomRatio', # Digital zoom ration
+ 'FocalLengthIn35mmFilm', # Focal length in 35 mm film
+ 'SceneCaptureType', # Scene capture type
+ 'GainControl', # Scene control
+ 'Contrast', # Contrast
+ 'Saturation', # Saturation
+ 'Sharpness', # Sharpness
+ #'DeviceSettingDescription', # Desice settings description
+ 'SubjectDistanceRange', # Subject distance range
+
+ # TODO: GPS attribute information on page 52 of the spec
+ );
+
+ $new = array();
+ foreach ($whitelist as $tag) {
+ if ( array_key_exists($tag, $exif) ) {
+ $new[$tag] = $exif[$tag];
+ }
+ }
+ unset($exif);
+ return $new;
+ }
+
+