r22198 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r22197‎ | r22198 | r22199 >
Date:01:39, 16 May 2007
Author:suuch
Status:old
Tags:
Comment:
Manually merged all InstantCommons patches to Image.php@r22191
Modified paths:
  • /branches/instantcommons/includes/Image.php (modified) (history)

Diff [purge]

Index: branches/instantcommons/includes/Image.php
@@ -1,5 +1,8 @@
22 <?php
33 /**
 4+ */
 5+
 6+/**
47 * NOTE FOR WINDOWS USERS:
58 * To enable EXIF functions, add the folloing lines to the
69 * "Windows extensions" section of php.ini:
@@ -11,16 +14,24 @@
1215 /**
1316 * Bump this number when serialized cache records may be incompatible.
1417 */
15 -define( 'MW_IMAGE_VERSION', 1 );
 18+define( 'MW_IMAGE_VERSION', 2 );
1619
1720 /**
1821 * Class to represent an image
1922 *
2023 * Provides methods to retrieve paths (physical, logical, URL),
2124 * to generate thumbnails or for uploading.
 25+ *
 26+ * @addtogroup Media
2227 */
2328 class Image
2429 {
 30+ const DELETED_FILE = 1;
 31+ const DELETED_COMMENT = 2;
 32+ const DELETED_USER = 4;
 33+ const DELETED_RESTRICTED = 8;
 34+ const RENDER_NOW = 1;
 35+
2536 /**#@+
2637 * @private
2738 */
@@ -30,8 +41,7 @@
3142 $title, # Title object for this image (constructor)
3243 $fileExists, # does the image file exist on disk? (loadFromXxx)
3344 $fromSharedDirectory, # load this image from $wgSharedUploadDirectory (loadFromXxx)
34 - $fromInstantCommons, # load this image from the InstantCommons repository
35 - $description, #added in for instantCommons
 45+ $fromInstantCommons, # load this image from the InstantCommons server
3646 $historyLine, # Number of line to return by nextHistoryLine() (constructor)
3747 $historyRes, # result of the query for the image's history (nextHistoryLine)
3848 $width, # \
@@ -40,6 +50,7 @@
4151 $attr, # /
4252 $type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
4353 $mime, # MIME type, determined by MimeMagic::guessMimeType
 54+ $extension, # The file extension (constructor)
4455 $size, # Size in bytes (loadFromXxx)
4556 $metadata, # Metadata
4657 $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
@@ -78,18 +89,16 @@
7990 }
8091 $this->title =& $title;
8192 $this->name = $title->getDBkey();
82 - $this->metadata = serialize ( array() ) ;
 93+ $this->metadata = '';
8394
8495 $n = strrpos( $this->name, '.' );
8596 $this->extension = Image::normalizeExtension( $n ?
8697 substr( $this->name, $n + 1 ) : '' );
8798 $this->historyLine = 0;
88 - $this->page = 1;
8999
90100 $this->dataLoaded = false;
91101 }
92102
93 -
94103 /**
95104 * Normalize a file extension to the common form, and ensure it's clean.
96105 * Extensions with non-alphanumeric characters will be discarded.
@@ -247,14 +256,13 @@
248257 # In case we're on a wgCapitalLinks=false wiki, we
249258 # capitalize the first letter of the filename before
250259 # looking it up in the shared repository.
251 - $sharedImage = Image::newFromName( $wgContLang->ucfirst($this->name) );
 260+ $sharedImage = Image::newFromName( $wgContLang->ucfirst($this->name) );
252261 $this->fileExists = $sharedImage && file_exists( $sharedImage->getFullPath(true) );
253262 if ( $this->fileExists ) {
254263 $this->name = $sharedImage->name;
255264 $this->imagePath = $this->getFullPath(true);
256265 $this->fromSharedDirectory = true;
257266 }
258 -
259267 }
260268
261269
@@ -263,32 +271,26 @@
264272
265273 $this->mime = $magic->guessMimeType($this->imagePath,true);
266274 $this->type = $magic->getMediaType($this->imagePath,$this->mime);
 275+ $handler = MediaHandler::getHandler( $this->mime );
267276
268277 # Get size in bytes
269278 $this->size = filesize( $this->imagePath );
270279
271 - $magic=& MimeMagic::singleton();
272 -
273 - # Height and width
274 - wfSuppressWarnings();
275 - if( $this->mime == 'image/svg' ) {
276 - $gis = wfGetSVGsize( $this->imagePath );
277 - } elseif( $this->mime == 'image/vnd.djvu' ) {
278 - $deja = new DjVuImage( $this->imagePath );
279 - $gis = $deja->getImageSize();
280 - } elseif ( !$magic->isPHPImageType( $this->mime ) ) {
281 - # Don't try to get the width and height of sound and video files, that's bad for performance
282 - $gis = false;
 280+ # Height, width and metadata
 281+ if ( $handler ) {
 282+ $gis = $handler->getImageSize( $this, $this->imagePath );
 283+ $this->metadata = $handler->getMetadata( $this, $this->imagePath );
283284 } else {
284 - $gis = getimagesize( $this->imagePath );
 285+ $gis = false;
 286+ $this->metadata = '';
285287 }
286 - wfRestoreWarnings();
287288
288289 wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n");
289290 }
290291 else {
291292 $this->mime = NULL;
292293 $this->type = MEDIATYPE_UNKNOWN;
 294+ $this->metadata = '';
293295 wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n");
294296 }
295297
@@ -307,13 +309,6 @@
308310 # as ther's only one thread of execution, this should be safe anyway.
309311 $this->dataLoaded = true;
310312
311 -
312 - if ( $this->mime == 'image/vnd.djvu' ) {
313 - $this->metadata = $deja->retrieveMetaData();
314 - } else {
315 - $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) );
316 - }
317 -
318313 if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
319314 else $this->bits = 0;
320315
@@ -322,16 +317,16 @@
323318
324319 /**
325320 * Load image metadata from the DB
 321+ * @param $src - when using IC distinguish IC images from form images ($src='upload')
326322 */
327323 function loadFromDB($src="") {
328324 global $wgUseSharedUploads, $wgSharedUploadDBname, $wgSharedUploadDBprefix, $wgContLang, $wgUseInstantCommons, $wgInstantCommonsServerPath, $wgUploadDirectory;
329325 wfProfileIn( __METHOD__ );
330326
331327 $dbr = wfGetDB( DB_SLAVE );
332 - $this->checkDBSchema($dbr);
333328
334329 $row = $dbr->selectRow( 'image',
335 - array( 'img_size', 'img_width', 'img_height', 'img_bits',
 330+ array( 'img_size', 'img_width', 'img_height', 'img_bits',
336331 'img_media_type', 'img_major_mime', 'img_minor_mime', 'img_metadata','img_description' ),
337332 array( 'img_name' => $this->name ), __METHOD__ );
338333 if ( $row ) {
@@ -340,12 +335,8 @@
341336 $this->loadFromRow( $row );
342337 $this->imagePath = $this->getFullPath();
343338 // Check for rows from a previous schema, quietly upgrade them
344 - // IC: When an image is uploaded in the background, it's height/width/size would have been set to 0
345 - // so force a reload from file
346 - if ( is_null($this->type) || !($this->width && $this->height && $this->size)) {
347 - $this->upgradeRow();
348 - }
349 - } elseif ( ($wgUseSharedUploads && $wgSharedUploadDBname)) {
 339+ $this->maybeUpgradeRow();
 340+ } elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) {
350341 # In case we're on a wgCapitalLinks=false wiki, we
351342 # capitalize the first letter of the filename before
352343 # looking it up in the shared repository.
@@ -365,94 +356,89 @@
366357 $this->loadFromRow( $row );
367358
368359 // Check for rows from a previous schema, quietly upgrade them
369 - if ( is_null($this->type) ) {
370 - $this->upgradeRow();
371 - }
 360+ $this->maybeUpgradeRow();
372361 }
373 - } elseif ( $wgUseInstantCommons && $wgInstantCommonsServerPath ){
374 - //NB: We enter into this loop even when we're uploading to the wiki.
375 - //so skip if it's an upload
376 - if($src!="upload") {
377 - if($_POST['wpPreview']=='Show preview') {
378 - //do not record the image. Show a placeholder image instead
379 - }else{
380 - //TODO: Replace placeholder image with the file from the InstantCommonsServer
381 - //store it in the image database and return an Image object It should
382 - //return an object identical to a database row as above
383 -
384 - $rname=tempnam(wfTempDir(),'icresponse.xml');
385 - $url = $wgInstantCommonsServerPath.'/api.php?action=instantcommons&format=xml&media='.$this->name;
386 - $fp = fopen($rname, "w");
387 - $xmlString = $this-> my_file_get_contents($url, $fp);
388 -
 362+ }elseif ( $wgUseInstantCommons && $wgInstantCommonsServerPath ){
 363+ //NB: We enter into this loop even when we're uploading to the wiki.
 364+ //so skip if it's an upload
 365+ if($src!="upload") {
 366+ if($_POST['wpPreview']=='Show preview') {
 367+ //do not record the image. Show a placeholder image instead
 368+ }else{
 369+ //TODO: Replace placeholder image with the file from the InstantCommonsServer
 370+ //store it in the image database and return an Image object It should
 371+ //return an object identical to a database row as above
 372+ $rname=tempnam(wfTempDir(),'icresponse.xml');
 373+ $url = $wgInstantCommonsServerPath.'/api.php?action=instantcommons&format=xml&media='.$this->name;
 374+ $fp = fopen($rname, "w");
 375+ $xmlString = $this-> my_file_get_contents($url, $fp);
389376 $p =& new ApiInstantCommons('instantcommons', 'maint');
390 - fclose($fp);
391 - //TODO: Replace the xml-parser
392 - if(trim($xmlString)!=""){
393 - $row = ($p->parse($xmlString));
394 - $row = $row[0]['children'][0]['children'][0]['attrs'];
395 - }
396 - if ( $row['NAME'] ) {
397 - //create the local file directory ($this->mSavedFile)
398 - UploadForm::saveUploadedFile( $row['NAME'],
399 - $row['NAME']
400 - ); //this hack just creates the path locally
401 - //now download the file to the final location
402 - //TODO: This has to be done in the background! Otherwise the page
403 - //hangs until the download is complete
404 -
405 - //TODO: As a workaround, check the size of the file returned. If greater than
406 - //2.5kb (typical thumbnail size on my test wiki), show "Download in progress"
407 - //image instead until the download is complete
408 - $icFileUrl = $wgInstantCommonsServerPath.$row['URL'];
409 - $fp = fopen("{$this->mSavedFile}", "w");
410 - if($row['SIZE'] > 3000) {wfDebug(join($row, ' | '));
411 - $this-> my_file_get_contents($icFileUrl, $this->mSavedFile, TRUE);
412 - }else {
413 - /*
414 - $icFileUrl = $wgInstantCommonsServerPath.$row['URL'];
415 - $icFp = fopen("{$this->mSavedFile}", "w");
416 - my_file_get_contents($icFileUrl, $icFp);
417 - fclose($icFp);*/
418 -
419 - $ch = curl_init($wgInstantCommonsServerPath.$row['URL']);
420 - //$fp = fopen("{$this->mSavedFile}", "w");
421 - curl_setopt($ch, CURLOPT_FILE, $fp);
422 - curl_setopt($ch, CURLOPT_HEADER, 0);
423 - curl_exec($ch);
424 - curl_close($ch);
425 - }
426 -
427 - //set further properties
428 - $this->fromInstantCommons = true; //TODO:THIS IS NOT STORED. For future use only!
429 - $this->fileExists = true;
430 - $this->imagePath = $this->getFullPath(false, true);
431 - $this->width = $row['WIDTH'];
432 - $this->height = $row['HEIGHT'];
433 - $this->metadata = stripslashes($row['METADATA']);
434 - $this->bits = $row['BITS'];
435 - $this->type = $row['TYPE'];
436 - $this->mime = $row['MIME'];
437 - $this->size = $row['SIZE'];
438 - $this->dataLoaded = $row['DATALOADED'];
439 - $this->attr = $row['ATTR'];
440 - $this->historyLine = $row['HISTORYLINE'];
441 - $this->historyRes = $row['HISTORYRES'];
442 - /**
443 - * Update the upload log and create the description page
444 - * if it's a new file.
445 - */
446 -
447 - $success = $this->recordUpload('Downloaded with InstantCommons!', $row['DESCRIPTION']);
448 - if ( $success ) {
449 - wfRunHooks( 'UploadComplete', array( &$this ) );
450 - //TODO: purge cache to show the image
451 - }
452 - }
453 - }
454 - }
 377+ fclose($fp);
 378+ //TODO: Replace the xml-parser with a preg_grep since it's a single line of XML that's returned.
 379+ if(trim($xmlString)!=""){
 380+ $row = ($p->parse($xmlString));
 381+ $row = $row[0]['children'][0]['children'][0]['attrs'];
 382+ }
 383+ if ( $row['NAME'] ) {
 384+ //create the local file directory ($this->mSavedFile)
 385+ UploadForm::saveUploadedFile( $row['NAME'],
 386+ $row['NAME']
 387+ ); //this hack just creates the path locally
 388+ //now download the file to the final location
 389+ //TODO: This has to be done in the background! Otherwise the page
 390+ //hangs until the download is complete
 391+
 392+ //TODO: As a workaround, check the size of the file returned. If greater than
 393+ //2.5kb (typical thumbnail size on my test wiki), show "Download in progress"
 394+ //image instead until the download is complete
 395+ $icFileUrl = $wgInstantCommonsServerPath.$row['URL'];
 396+ $fp = fopen("{$this->mSavedFile}", "w");
 397+ if($row['SIZE'] > 3000) {wfDebug(join($row, ' | '));
 398+ $this-> my_file_get_contents($icFileUrl, $this->mSavedFile, TRUE);
 399+ }else {
 400+ /*
 401+ $icFileUrl = $wgInstantCommonsServerPath.$row['URL'];
 402+ $icFp = fopen("{$this->mSavedFile}", "w");
 403+ my_file_get_contents($icFileUrl, $icFp);
 404+ fclose($icFp);*/
 405+
 406+ $ch = curl_init($wgInstantCommonsServerPath.$row['URL']);
 407+ //$fp = fopen("{$this->mSavedFile}", "w");
 408+ curl_setopt($ch, CURLOPT_FILE, $fp);
 409+ curl_setopt($ch, CURLOPT_HEADER, 0);
 410+ curl_exec($ch);
 411+ curl_close($ch);
 412+ }
 413+
 414+ //set further properties
 415+ $this->fromInstantCommons = true; //TODO:THIS IS NOT STORED. For future use only!
 416+ $this->fileExists = true;
 417+ $this->imagePath = $this->getFullPath(false, true);
 418+ $this->width = $row['WIDTH'];
 419+ $this->height = $row['HEIGHT'];
 420+ $this->metadata = stripslashes($row['METADATA']);
 421+ $this->bits = $row['BITS'];
 422+ $this->type = $row['TYPE'];
 423+ $this->mime = $row['MIME'];
 424+ $this->size = $row['SIZE'];
 425+ $this->dataLoaded = $row['DATALOADED'];
 426+ $this->attr = $row['ATTR'];
 427+ $this->historyLine = $row['HISTORYLINE'];
 428+ $this->historyRes = $row['HISTORYRES'];
 429+ /**
 430+ * Update the upload log and create the description page
 431+ * if it's a new file.
 432+ */
 433+
 434+ $success = $this->recordUpload('Downloaded with InstantCommons!', $row['DESCRIPTION']);
 435+ if ( $success ) {
 436+ wfRunHooks( 'UploadComplete', array( &$this ) );
 437+ //TODO: purge cache to show the image
 438+ }
 439+ }
 440+ }
 441+ }
455442 }
456 -
457443
458444 if ( !$row ) {
459445 $this->size = 0;
@@ -462,7 +448,7 @@
463449 $this->type = 0;
464450 $this->fileExists = false;
465451 $this->fromSharedDirectory = false;
466 - $this->metadata = serialize ( array() ) ;
 452+ $this->metadata = '';
467453 $this->mime = false;
468454 }
469455
@@ -470,23 +456,7 @@
471457 $this->dataLoaded = true;
472458 wfProfileOut( __METHOD__ );
473459 }
474 -function my_file_get_contents($url, $fp, $bg=FALSE, $timeout = 1){//ref: http://groups-beta.google.com/group/comp.lang.php/browse_thread/thread/8efbbaced3c45e3c/d63c7891cf8e380b?lnk=raot
475 - if(!$bg){
476 - $ch = curl_init();
477 - curl_setopt ($ch, CURLOPT_URL, $url);
478 - curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
479 - curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
480 - curl_setopt ($ch, CURLOPT_TIMEOUT, $timeout);
481 - $file_contents = curl_exec($ch);
482 - curl_close($ch);
483 - } else {//call curl in the background to download the file
484 - $cmd = 'curl '.escapeshellcmd($url).' -o '.$fp.'&';
485 - wfDebug('Curl download initiated='.$cmd );
486 - pclose(popen($cmd, 'r'));
487 - $file_contents = 1;
488 - }
489 - return $file_contents;
490 - }
 460+
491461 /*
492462 * Load image metadata from a DB result row
493463 */
@@ -497,7 +467,7 @@
498468 $this->bits = $row->img_bits;
499469 $this->type = $row->img_media_type;
500470 $this->description = $row->img_description;
501 -
 471+
502472 $major= $row->img_major_mime;
503473 $minor= $row->img_minor_mime;
504474
@@ -506,23 +476,22 @@
507477 if (!$minor) $minor= "unknown";
508478 $this->mime = $major.'/'.$minor;
509479 }
510 -
511480 $this->metadata = $row->img_metadata;
512 - if ( $this->metadata == "" ) $this->metadata = serialize ( array() ) ;
513481
514482 $this->dataLoaded = true;
515483 }
516484
517485 /**
518486 * Load image metadata from cache or DB, unless already loaded
 487+ * @param $src - when using IC distinguish IC images from form images ($src='upload')
519488 */
520489 function load($src="") {
521490 global $wgSharedUploadDBname, $wgUseSharedUploads, $wgUseInstantCommons;
522 - if ( !$this->dataLoaded ) {
 491+ if ( !$this->dataLoaded ) {
523492 if ( !$this->loadFromCache() ) {
524493 $this->loadFromDB($src);
525494 if ( !$wgSharedUploadDBname && $wgUseSharedUploads ) {
526 - $this->loadFromFile();
 495+ $this->loadFromFile();
527496 } elseif ( $this->fileExists || !$wgUseSharedUploads ) {
528497 // We can do negative caching for local images, because the cache
529498 // will be purged on upload. But we can't do it when shared images
@@ -535,8 +504,23 @@
536505 }
537506
538507 /**
539 - * Metadata was loaded from the database, but the row had a marker indicating it needs to be
540 - * upgraded from the 1.4 schema, which had no width, height, bits or type. Upgrade the row.
 508+ * Upgrade a row if it needs it
 509+ * IC: When an image is uploaded in the background, it's height/width/size would have been set to 0
 510+ * so force a reload from file
 511+ */
 512+ function maybeUpgradeRow() {
 513+ if ( is_null($this->type) || $this->mime == 'image/svg' || !($this->width && $this->height && $this->size)) {
 514+ $this->upgradeRow();
 515+ } else {
 516+ $handler = $this->getHandler();
 517+ if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
 518+ $this->upgradeRow();
 519+ }
 520+ }
 521+ }
 522+
 523+ /**
 524+ * Fix assorted version-related problems with the image row by reloading it from the file
541525 */
542526 function upgradeRow() {
543527 global $wgDBname, $wgSharedUploadDBname;
@@ -557,11 +541,9 @@
558542 $dbw = wfGetDB( DB_MASTER );
559543 }
560544
561 - $this->checkDBSchema($dbw);
562 -
563545 list( $major, $minor ) = self::splitMime( $this->mime );
564546
565 - wfDebug(__METHOD__.': upgrading '.$this->name." to 1.5 schema\n");
 547+ wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n");
566548
567549 $dbw->update( 'image',
568550 array(//IC: include image size
@@ -655,26 +637,52 @@
656638 /**
657639 * Return the width of the image
658640 *
659 - * Returns -1 if the file specified is not a known image type
 641+ * Returns false on error
660642 * @public
661643 */
662 - function getWidth() {
 644+ function getWidth( $page = 1 ) {
663645 $this->load();
664 - return $this->width;
 646+ if ( $this->isMultipage() ) {
 647+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
 648+ if ( $dim ) {
 649+ return $dim['width'];
 650+ } else {
 651+ return false;
 652+ }
 653+ } else {
 654+ return $this->width;
 655+ }
665656 }
666657
667658 /**
668659 * Return the height of the image
669660 *
670 - * Returns -1 if the file specified is not a known image type
 661+ * Returns false on error
671662 * @public
672663 */
673 - function getHeight() {
 664+ function getHeight( $page = 1 ) {
674665 $this->load();
675 - return $this->height;
 666+ if ( $this->isMultipage() ) {
 667+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
 668+ if ( $dim ) {
 669+ return $dim['height'];
 670+ } else {
 671+ return false;
 672+ }
 673+ } else {
 674+ return $this->height;
 675+ }
676676 }
677677
678678 /**
 679+ * Get handler-specific metadata
 680+ */
 681+ function getMetadata() {
 682+ $this->load();
 683+ return $this->metadata;
 684+ }
 685+
 686+ /**
679687 * Return the size of the image file, in bytes
680688 * @public
681689 */
@@ -711,58 +719,10 @@
712720 * @todo remember the result of this check.
713721 */
714722 function canRender() {
715 - global $wgUseImageMagick, $wgDjvuRenderer;
716 -
717 - if( $this->getWidth()<=0 || $this->getHeight()<=0 ) return false;
718 -
719 - $mime= $this->getMimeType();
720 -
721 - if (!$mime || $mime==='unknown' || $mime==='unknown/unknown') return false;
722 -
723 - #if it's SVG, check if there's a converter enabled
724 - if ($mime === 'image/svg') {
725 - global $wgSVGConverters, $wgSVGConverter;
726 -
727 - if ($wgSVGConverter && isset( $wgSVGConverters[$wgSVGConverter])) {
728 - wfDebug( "Image::canRender: SVG is ready!\n" );
729 - return true;
730 - } else {
731 - wfDebug( "Image::canRender: SVG renderer missing\n" );
732 - }
733 - }
734 -
735 - #image formats available on ALL browsers
736 - if ( $mime === 'image/gif'
737 - || $mime === 'image/png'
738 - || $mime === 'image/jpeg' ) return true;
739 -
740 - #image formats that can be converted to the above formats
741 - if ($wgUseImageMagick) {
742 - #convertable by ImageMagick (there are more...)
743 - if ( $mime === 'image/vnd.wap.wbmp'
744 - || $mime === 'image/x-xbitmap'
745 - || $mime === 'image/x-xpixmap'
746 - #|| $mime === 'image/x-icon' #file may be split into multiple parts
747 - || $mime === 'image/x-portable-anymap'
748 - || $mime === 'image/x-portable-bitmap'
749 - || $mime === 'image/x-portable-graymap'
750 - || $mime === 'image/x-portable-pixmap'
751 - #|| $mime === 'image/x-photoshop' #this takes a lot of CPU and RAM!
752 - || $mime === 'image/x-rgb'
753 - || $mime === 'image/x-bmp'
754 - || $mime === 'image/tiff' ) return true;
755 - }
756 - else {
757 - #convertable by the PHP GD image lib
758 - if ( $mime === 'image/vnd.wap.wbmp'
759 - || $mime === 'image/x-xbitmap' ) return true;
760 - }
761 - if ( $mime === 'image/vnd.djvu' && isset( $wgDjvuRenderer ) && $wgDjvuRenderer ) return true;
762 -
763 - return false;
 723+ $handler = $this->getHandler();
 724+ return $handler && $handler->canRender();
764725 }
765726
766 -
767727 /**
768728 * Return true if the file is of a type that can't be directly
769729 * rendered by typical browsers and needs to be re-rasterized.
@@ -774,13 +734,8 @@
775735 * @return bool
776736 */
777737 function mustRender() {
778 - $mime= $this->getMimeType();
779 -
780 - if ( $mime === "image/gif"
781 - || $mime === "image/png"
782 - || $mime === "image/jpeg" ) return false;
783 -
784 - return true;
 738+ $handler = $this->getHandler();
 739+ return $handler && $handler->mustRender();
785740 }
786741
787742 /**
@@ -846,15 +801,7 @@
847802 * @public
848803 */
849804 function getEscapeLocalURL( $query=false) {
850 - $this->getTitle();
851 - if ( $query === false ) {
852 - if ( $this->page != 1 ) {
853 - $query = 'page=' . $this->page;
854 - } else {
855 - $query = '';
856 - }
857 - }
858 - return $this->title->escapeLocalURL( $query );
 805+ return $this->getTitle()->escapeLocalURL( $query );
859806 }
860807
861808 /**
@@ -871,17 +818,17 @@
872819 *
873820 * @param string $name Name of the image, without the leading "Image:"
874821 * @param boolean $fromSharedDirectory Should this be in $wgSharedUploadPath?
 822+ * @param boolean $fromInstantCommons Indicate whether this is an IC image
875823 * @return string URL of $name image
876824 * @public
877 - * @static
878825 */
879 - function imageUrl( $name, $fromSharedDirectory = false, $fromInstantCommons = false ) {
 826+ static function imageUrl( $name, $fromSharedDirectory = false, $fromInstantCommons = false ) {
880827 global $wgUploadPath,$wgUploadBaseUrl,$wgSharedUploadPath, $wgInstantCommonsServerPath;
881828 if($fromInstantCommons) { //check if this is set first before checking shared directory
882829 $base = '';
883830 $path = $wgInstantCommonsServerPath;
884831 }
885 - else if($fromSharedDirectory) {
 832+ if($fromSharedDirectory) {
886833 $base = '';
887834 $path = $wgSharedUploadPath;
888835 } else {
@@ -897,7 +844,7 @@
898845 * @return boolean Whether image file exist on disk.
899846 * @public
900847 */
901 - function exists($src="") {
 848+ function exists($src="") {
902849 $this->load($src);
903850 return $this->fileExists;
904851 }
@@ -906,74 +853,83 @@
907854 * @todo document
908855 * @private
909856 */
910 - function thumbUrl( $width, $subdir='thumb') {
 857+ function thumbUrlFromName( $thumbName, $subdir = 'thumb' ) {
911858 global $wgUploadPath, $wgUploadBaseUrl, $wgSharedUploadPath, $wgInstantCommonsServerPath;
912 - global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
 859+ if($this->fromSharedDirectory) {
 860+ $base = '';
 861+ $path = $wgSharedUploadPath;
 862+ } else {
 863+ $base = $wgUploadBaseUrl;
 864+ $path = $wgUploadPath;
 865+ }
 866+ if ( Image::isHashed( $this->fromSharedDirectory ) ) {
 867+ $hashdir = wfGetHashPath($this->name, $this->fromSharedDirectory, $this->fromInstantCommons ) .
 868+ wfUrlencode( $this->name );
 869+ } else {
 870+ $hashdir = '';
 871+ }
 872+ $url = "{$base}{$path}/{$subdir}{$hashdir}/" . wfUrlencode( $thumbName );
 873+ return $url;
 874+ }
913875
914 - // Generate thumb.php URL if possible
915 - $script = false;
916 - $url = false;
 876+ /**
 877+ * @deprecated Use $image->transform()->getUrl() or thumbUrlFromName()
 878+ */
 879+ function thumbUrl( $width, $subdir = 'thumb' ) {
 880+ $name = $this->thumbName( array( 'width' => $width ) );
 881+ if ( strval( $name ) !== '' ) {
 882+ return array( false, $this->thumbUrlFromName( $name, $subdir ) );
 883+ } else {
 884+ return array( false, false );
 885+ }
 886+ }
917887
 888+ function getTransformScript() {
 889+ global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
918890 if ( $this->fromSharedDirectory ) {
919 - if ( $wgSharedThumbnailScriptPath ) {
920 - $script = $wgSharedThumbnailScriptPath;
921 - }
 891+ $script = $wgSharedThumbnailScriptPath;
922892 } else {
923 - if ( $wgThumbnailScriptPath ) {
924 - $script = $wgThumbnailScriptPath;
925 - }
 893+ $script = $wgThumbnailScriptPath;
926894 }
927895 if ( $script ) {
928 - $url = $script . '?f=' . urlencode( $this->name ) . '&w=' . urlencode( $width );
929 - if( $this->mustRender() ) {
930 - $url.= '&r=1';
931 - }
 896+ return "$script?f=" . urlencode( $this->name );
932897 } else {
933 - $name = $this->thumbName( $width );
934 - if($this->fromSharedDirectory) {
935 - $base = '';
936 - $path = $wgSharedUploadPath;
937 - } else {
938 - $base = $wgUploadBaseUrl;
939 - $path = $wgUploadPath;
940 - }
941 - if ( Image::isHashed( $this->fromSharedDirectory ) ) {
942 - $url = "{$base}{$path}/{$subdir}" .
943 - wfGetHashPath($this->name, $this->fromSharedDirectory, $this->fromInstantCommons)
944 - . $this->name.'/'.$name;
945 - $url = wfUrlencode( $url );
946 - } else {
947 - $url = "{$base}{$path}/{$subdir}/{$name}";
948 - }
 898+ return false;
949899 }
950 - return array( $script !== false, $url );
951900 }
952901
953902 /**
954 - * Return the file name of a thumbnail of the specified width
 903+ * Get a ThumbnailImage which is the same size as the source
 904+ */
 905+ function getUnscaledThumb( $page = false ) {
 906+ if ( $page ) {
 907+ $params = array(
 908+ 'page' => $page,
 909+ 'width' => $this->getWidth( $page )
 910+ );
 911+ } else {
 912+ $params = array( 'width' => $this->getWidth() );
 913+ }
 914+ return $this->transform( $params );
 915+ }
 916+
 917+ /**
 918+ * Return the file name of a thumbnail with the specified parameters
955919 *
956 - * @param integer $width Width of the thumbnail image
957 - * @param boolean $shared Does the thumbnail come from the shared repository?
 920+ * @param array $params Handler-specific parameters
958921 * @private
959922 */
960 - function thumbName( $width ) {
961 - $thumb = $width."px-".$this->name;
962 - if ( $this->page != 1 ) {
963 - $thumb = "page{$this->page}-$thumb";
 923+ function thumbName( $params ) {
 924+ $handler = $this->getHandler();
 925+ if ( !$handler ) {
 926+ return null;
964927 }
965 -
966 - if( $this->mustRender() ) {
967 - if( $this->canRender() ) {
968 - # Rasterize to PNG (for SVG vector images, etc)
969 - $thumb .= '.png';
970 - }
971 - else {
972 - #should we use iconThumb here to get a symbolic thumbnail?
973 - #or should we fail with an internal error?
974 - return NULL; //can't make bitmap
975 - }
 928+ list( $thumbExt, /* $thumbMime */ ) = self::getThumbType( $this->extension, $this->mime );
 929+ $thumbName = $handler->makeParamString( $params ) . '-' . $this->name;
 930+ if ( $thumbExt != $this->extension ) {
 931+ $thumbName .= ".$thumbExt";
976932 }
977 - return $thumb;
 933+ return $thumbName;
978934 }
979935
980936 /**
@@ -992,9 +948,13 @@
993949 * @param integer $height maximum height of the image (optional)
994950 * @public
995951 */
996 - function createThumb( $width, $height=-1 ) {
997 - $thumb = $this->getThumbnail( $width, $height );
998 - if( is_null( $thumb ) ) return '';
 952+ function createThumb( $width, $height = -1 ) {
 953+ $params = array( 'width' => $width );
 954+ if ( $height != -1 ) {
 955+ $params['height'] = $height;
 956+ }
 957+ $thumb = $this->transform( $params );
 958+ if( is_null( $thumb ) || $thumb->isError() ) return '';
999959 return $thumb->getUrl();
1000960 }
1001961
@@ -1012,149 +972,92 @@
1013973 *
1014974 * @return ThumbnailImage or null on failure
1015975 * @public
 976+ *
 977+ * @deprecated use transform()
1016978 */
1017979 function getThumbnail( $width, $height=-1, $render = true ) {
1018 - wfProfileIn( __METHOD__ );
1019 - if ($this->canRender()) {
1020 - if ( $height > 0 ) {
1021 - $this->load();
1022 - if ( $width > $this->width * $height / $this->height ) {
1023 - $width = wfFitBoxWidth( $this->width, $this->height, $height );
1024 - }
1025 - }
1026 - if ( $render ) {
1027 - $thumb = $this->renderThumb( $width );
1028 - } else {
1029 - // Don't render, just return the URL
1030 - if ( $this->validateThumbParams( $width, $height ) ) {
1031 - if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
1032 - $url = $this->getURL();
1033 - } else {
1034 - list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $width );
1035 - }
1036 - $thumb = new ThumbnailImage( $url, $width, $height );
1037 - } else {
1038 - $thumb = null;
1039 - }
1040 - }
1041 - } else {
1042 - // not a bitmap or renderable image, don't try.
1043 - $thumb = $this->iconThumb();
 980+ $params = array( 'width' => $width );
 981+ if ( $height != -1 ) {
 982+ $params['height'] = $height;
1044983 }
1045 - wfProfileOut( __METHOD__ );
1046 - return $thumb;
 984+ $flags = $render ? self::RENDER_NOW : 0;
 985+ return $this->transform( $params, $flags );
1047986 }
1048 -
 987+
1049988 /**
1050 - * @return ThumbnailImage
 989+ * Transform a media file
 990+ *
 991+ * @param array $params An associative array of handler-specific parameters. Typical
 992+ * keys are width, height and page.
 993+ * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
 994+ * @return MediaTransformOutput
1051995 */
1052 - function iconThumb() {
1053 - global $wgStylePath, $wgStyleDirectory;
 996+ function transform( $params, $flags = 0 ) {
 997+ global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors;
1054998
1055 - $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
1056 - foreach( $try as $icon ) {
1057 - $path = '/common/images/icons/' . $icon;
1058 - $filepath = $wgStyleDirectory . $path;
1059 - if( file_exists( $filepath ) ) {
1060 - return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
 999+ wfProfileIn( __METHOD__ );
 1000+ do {
 1001+ $handler = $this->getHandler();
 1002+ if ( !$handler || !$handler->canRender() ) {
 1003+ // not a bitmap or renderable image, don't try.
 1004+ $thumb = $this->iconThumb();
 1005+ break;
10611006 }
1062 - }
1063 - return null;
1064 - }
10651007
1066 - /**
1067 - * Validate thumbnail parameters and fill in the correct height
1068 - *
1069 - * @param integer &$width Specified width (input/output)
1070 - * @param integer &$height Height (output only)
1071 - * @return false to indicate that an error should be returned to the user.
1072 - */
1073 - function validateThumbParams( &$width, &$height ) {
1074 - global $wgSVGMaxSize, $wgMaxImageArea;
 1008+ $script = $this->getTransformScript();
 1009+ if ( $script && !($flags & self::RENDER_NOW) ) {
 1010+ // Use a script to transform on client request
 1011+ $thumb = $handler->getScriptedTransform( $this, $script, $params );
 1012+ break;
 1013+ }
10751014
1076 - $this->load();
 1015+ $normalisedParams = $params;
 1016+ $handler->normaliseParams( $this, $normalisedParams );
 1017+ list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime );
 1018+ $thumbName = $this->thumbName( $normalisedParams );
 1019+ $thumbPath = wfImageThumbDir( $this->name, $this->fromSharedDirectory ) . "/$thumbName";
 1020+ $thumbUrl = $this->thumbUrlFromName( $thumbName );
10771021
1078 - if ( ! $this->exists() )
1079 - {
1080 - # If there is no image, there will be no thumbnail
1081 - return false;
1082 - }
10831022
1084 - $width = intval( $width );
 1023+ if ( !$wgGenerateThumbnailOnParse && !($flags & self::RENDER_NOW ) ) {
 1024+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
 1025+ break;
 1026+ }
 1027+
 1028+ wfDebug( "Doing stat for $thumbPath\n" );
 1029+ $this->migrateThumbFile( $thumbName );
 1030+ if ( file_exists( $thumbPath ) ) {
 1031+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
 1032+ break;
 1033+ }
10851034
1086 - # Sanity check $width
1087 - if( $width <= 0 || $this->width <= 0) {
1088 - # BZZZT
1089 - return false;
1090 - }
 1035+ $thumb = $handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
10911036
1092 - # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
1093 - # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
1094 - # an exception for it.
1095 - if ( $this->getMediaType() == MEDIATYPE_BITMAP &&
1096 - $this->getMimeType() !== 'image/jpeg' &&
1097 - $this->width * $this->height > $wgMaxImageArea )
1098 - {
1099 - return false;
1100 - }
 1037+ // Ignore errors if requested
 1038+ if ( !$thumb ) {
 1039+ $thumb = null;
 1040+ } elseif ( $thumb->isError() ) {
 1041+ $this->lastError = $thumb->toText();
 1042+ if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
 1043+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
 1044+ }
 1045+ }
 1046+
 1047+ if ( $wgUseSquid ) {
 1048+ wfPurgeSquidServers( array( $thumbUrl ) );
 1049+ }
 1050+ } while (false);
11011051
1102 - # Don't make an image bigger than the source, or wgMaxSVGSize for SVGs
1103 - if ( $this->mustRender() ) {
1104 - $width = min( $width, $wgSVGMaxSize );
1105 - } elseif ( $width > $this->width - 1 ) {
1106 - $width = $this->width;
1107 - $height = $this->height;
1108 - return true;
1109 - }
1110 -
1111 - $height = round( $this->height * $width / $this->width );
1112 - return true;
 1052+ wfProfileOut( __METHOD__ );
 1053+ return $thumb;
11131054 }
11141055
11151056 /**
1116 - * Create a thumbnail of the image having the specified width.
1117 - * The thumbnail will not be created if the width is larger than the
1118 - * image's width. Let the browser do the scaling in this case.
1119 - * The thumbnail is stored on disk and is only computed if the thumbnail
1120 - * file does not exist OR if it is older than the image.
1121 - * Returns an object which can return the pathname, URL, and physical
1122 - * pixel size of the thumbnail -- or null on failure.
1123 - *
1124 - * @return ThumbnailImage or null on failure
1125 - * @private
 1057+ * Fix thumbnail files from 1.4 or before, with extreme prejudice
11261058 */
1127 - function renderThumb( $width, $useScript = true ) {
1128 - global $wgUseSquid, $wgThumbnailEpoch;
1129 -
1130 - wfProfileIn( __METHOD__ );
1131 -
1132 - $this->load();
1133 - $height = -1;
1134 - if ( !$this->validateThumbParams( $width, $height ) ) {
1135 - # Validation error
1136 - wfProfileOut( __METHOD__ );
1137 - return null;
1138 - }
1139 -
1140 - if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
1141 - # validateThumbParams (or the user) wants us to return the unscaled image
1142 - $thumb = new ThumbnailImage( $this->getURL(), $width, $height );
1143 - wfProfileOut( __METHOD__ );
1144 - return $thumb;
1145 - }
1146 -
1147 - list( $isScriptUrl, $url ) = $this->thumbUrl( $width );
1148 - if ( $isScriptUrl && $useScript ) {
1149 - // Use thumb.php to render the image
1150 - $thumb = new ThumbnailImage( $url, $width, $height );
1151 - wfProfileOut( __METHOD__ );
1152 - return $thumb;
1153 - }
1154 -
1155 - $thumbName = $this->thumbName( $width, $this->fromSharedDirectory );
 1059+ function migrateThumbFile( $thumbName ) {
11561060 $thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory );
1157 - $thumbPath = $thumbDir.'/'.$thumbName;
1158 -
 1061+ $thumbPath = "$thumbDir/$thumbName";
11591062 if ( is_dir( $thumbPath ) ) {
11601063 // Directory where file should be
11611064 // This happened occasionally due to broken migration code in 1.5
@@ -1167,254 +1070,50 @@
11681071 break;
11691072 }
11701073 }
1171 - // Code below will ask if it exists, and the answer is now no
 1074+ // Doesn't exist anymore
11721075 clearstatcache();
11731076 }
1174 -
1175 - $done = true;
1176 - if ( !file_exists( $thumbPath ) ||
1177 - filemtime( $thumbPath ) < wfTimestamp( TS_UNIX, $wgThumbnailEpoch ) )
1178 - {
1179 - // Create the directory if it doesn't exist
1180 - if ( is_file( $thumbDir ) ) {
1181 - // File where thumb directory should be, destroy if possible
1182 - @unlink( $thumbDir );
1183 - }
1184 - wfMkdirParents( $thumbDir );
1185 -
1186 - $oldThumbPath = wfDeprecatedThumbDir( $thumbName, 'thumb', $this->fromSharedDirectory ).
1187 - '/'.$thumbName;
1188 - $done = false;
1189 -
1190 - // Migration from old directory structure
1191 - if ( is_file( $oldThumbPath ) ) {
1192 - if ( filemtime($oldThumbPath) >= filemtime($this->imagePath) ) {
1193 - if ( file_exists( $thumbPath ) ) {
1194 - if ( !is_dir( $thumbPath ) ) {
1195 - // Old image in the way of rename
1196 - unlink( $thumbPath );
1197 - } else {
1198 - // This should have been dealt with already
1199 - throw new MWException( "Directory where image should be: $thumbPath" );
1200 - }
1201 - }
1202 - // Rename the old image into the new location
1203 - rename( $oldThumbPath, $thumbPath );
1204 - $done = true;
1205 - } else {
1206 - unlink( $oldThumbPath );
1207 - }
1208 - }
1209 - if ( !$done ) {
1210 - $this->lastError = $this->reallyRenderThumb( $thumbPath, $width, $height );
1211 - if ( $this->lastError === true ) {
1212 - $done = true;
1213 - } elseif( $GLOBALS['wgIgnoreImageErrors'] ) {
1214 - // Log the error but output anyway.
1215 - // With luck it's a transitory error...
1216 - $done = true;
1217 - }
1218 -
1219 - # Purge squid
1220 - # This has to be done after the image is updated and present for all machines on NFS,
1221 - # or else the old version might be stored into the squid again
1222 - if ( $wgUseSquid ) {
1223 - $urlArr = array( $url );
1224 - wfPurgeSquidServers($urlArr);
1225 - }
1226 - }
 1077+ if ( is_file( $thumbDir ) ) {
 1078+ // File where directory should be
 1079+ unlink( $thumbDir );
 1080+ // Doesn't exist anymore
 1081+ clearstatcache();
12271082 }
 1083+ }
12281084
1229 - if ( $done ) {
1230 - $thumb = new ThumbnailImage( $url, $width, $height, $thumbPath );
1231 - } else {
1232 - $thumb = null;
1233 - }
1234 - wfProfileOut( __METHOD__ );
1235 - return $thumb;
1236 - } // END OF function renderThumb
 1085+ /**
 1086+ * Get a MediaHandler instance for this image
 1087+ */
 1088+ function getHandler() {
 1089+ return MediaHandler::getHandler( $this->getMimeType() );
 1090+ }
12371091
12381092 /**
1239 - * Really render a thumbnail
1240 - * Call this only for images for which canRender() returns true.
1241 - *
1242 - * @param string $thumbPath Path to thumbnail
1243 - * @param int $width Desired width in pixels
1244 - * @param int $height Desired height in pixels
1245 - * @return bool True on error, false or error string on failure.
1246 - * @private
 1093+ * Get a ThumbnailImage representing a file type icon
 1094+ * @return ThumbnailImage
12471095 */
1248 - function reallyRenderThumb( $thumbPath, $width, $height ) {
1249 - global $wgSVGConverters, $wgSVGConverter;
1250 - global $wgUseImageMagick, $wgImageMagickConvertCommand;
1251 - global $wgCustomConvertCommand;
1252 - global $wgDjvuRenderer, $wgDjvuPostProcessor;
 1096+ function iconThumb() {
 1097+ global $wgStylePath, $wgStyleDirectory;
12531098
1254 - $this->load();
1255 -
1256 - $err = false;
1257 - $cmd = "";
1258 - $retval = 0;
1259 -
1260 - if( $this->mime === "image/svg" ) {
1261 - #Right now we have only SVG
1262 -
1263 - global $wgSVGConverters, $wgSVGConverter;
1264 - if( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
1265 - global $wgSVGConverterPath;
1266 - $cmd = str_replace(
1267 - array( '$path/', '$width', '$height', '$input', '$output' ),
1268 - array( $wgSVGConverterPath ? "$wgSVGConverterPath/" : "",
1269 - intval( $width ),
1270 - intval( $height ),
1271 - wfEscapeShellArg( $this->imagePath ),
1272 - wfEscapeShellArg( $thumbPath ) ),
1273 - $wgSVGConverters[$wgSVGConverter] );
1274 - wfProfileIn( 'rsvg' );
1275 - wfDebug( "reallyRenderThumb SVG: $cmd\n" );
1276 - $err = wfShellExec( $cmd, $retval );
1277 - wfProfileOut( 'rsvg' );
 1099+ $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
 1100+ foreach( $try as $icon ) {
 1101+ $path = '/common/images/icons/' . $icon;
 1102+ $filepath = $wgStyleDirectory . $path;
 1103+ if( file_exists( $filepath ) ) {
 1104+ return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
12781105 }
1279 - } else {
1280 - if ( $this->mime === "image/vnd.djvu" && $wgDjvuRenderer ) {
1281 - // DJVU image
1282 - // The file contains several images. First, extract the
1283 - // page in hi-res, if it doesn't yet exist. Then, thumbnail
1284 - // it.
1285 -
1286 - $cmd = "{$wgDjvuRenderer} -page={$this->page} -size=${width}x${height} " .
1287 - wfEscapeShellArg( $this->imagePath ) .
1288 - " | {$wgDjvuPostProcessor} > " . wfEscapeShellArg($thumbPath);
1289 - wfProfileIn( 'ddjvu' );
1290 - wfDebug( "reallyRenderThumb DJVU: $cmd\n" );
1291 - $err = wfShellExec( $cmd, $retval );
1292 - wfProfileOut( 'ddjvu' );
1293 -
1294 - } elseif ( $wgUseImageMagick ) {
1295 - # use ImageMagick
1296 -
1297 - if ( $this->mime == 'image/jpeg' ) {
1298 - $quality = "-quality 80"; // 80%
1299 - } elseif ( $this->mime == 'image/png' ) {
1300 - $quality = "-quality 95"; // zlib 9, adaptive filtering
1301 - } else {
1302 - $quality = ''; // default
1303 - }
1304 -
1305 - # Specify white background color, will be used for transparent images
1306 - # in Internet Explorer/Windows instead of default black.
1307 -
1308 - # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}".
1309 - # It seems that ImageMagick has a bug wherein it produces thumbnails of
1310 - # the wrong size in the second case.
1311 -
1312 - $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) .
1313 - " {$quality} -background white -size {$width} ".
1314 - wfEscapeShellArg($this->imagePath) .
1315 - // Coalesce is needed to scale animated GIFs properly (bug 1017).
1316 - ' -coalesce ' .
1317 - // For the -resize option a "!" is needed to force exact size,
1318 - // or ImageMagick may decide your ratio is wrong and slice off
1319 - // a pixel.
1320 - " -thumbnail " . wfEscapeShellArg( "{$width}x{$height}!" ) .
1321 - " -depth 8 " .
1322 - wfEscapeShellArg($thumbPath) . " 2>&1";
1323 - wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n");
1324 - wfProfileIn( 'convert' );
1325 - $err = wfShellExec( $cmd, $retval );
1326 - wfProfileOut( 'convert' );
1327 - } elseif( $wgCustomConvertCommand ) {
1328 - # Use a custom convert command
1329 - # Variables: %s %d %w %h
1330 - $src = wfEscapeShellArg( $this->imagePath );
1331 - $dst = wfEscapeShellArg( $thumbPath );
1332 - $cmd = $wgCustomConvertCommand;
1333 - $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
1334 - $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size
1335 - wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" );
1336 - wfProfileIn( 'convert' );
1337 - $err = wfShellExec( $cmd, $retval );
1338 - wfProfileOut( 'convert' );
1339 - } else {
1340 - # Use PHP's builtin GD library functions.
1341 - #
1342 - # First find out what kind of file this is, and select the correct
1343 - # input routine for this.
1344 -
1345 - $typemap = array(
1346 - 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
1347 - 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( &$this, 'imageJpegWrapper' ) ),
1348 - 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
1349 - 'image/vnd.wap.wmbp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
1350 - 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
1351 - );
1352 - if( !isset( $typemap[$this->mime] ) ) {
1353 - $err = 'Image type not supported';
1354 - wfDebug( "$err\n" );
1355 - return $err;
1356 - }
1357 - list( $loader, $colorStyle, $saveType ) = $typemap[$this->mime];
1358 -
1359 - if( !function_exists( $loader ) ) {
1360 - $err = "Incomplete GD library configuration: missing function $loader";
1361 - wfDebug( "$err\n" );
1362 - return $err;
1363 - }
1364 - if( $colorStyle == 'palette' ) {
1365 - $truecolor = false;
1366 - } elseif( $colorStyle == 'truecolor' ) {
1367 - $truecolor = true;
1368 - } elseif( $colorStyle == 'bits' ) {
1369 - $truecolor = ( $this->bits > 8 );
1370 - }
1371 -
1372 - $src_image = call_user_func( $loader, $this->imagePath );
1373 - if ( $truecolor ) {
1374 - $dst_image = imagecreatetruecolor( $width, $height );
1375 - } else {
1376 - $dst_image = imagecreate( $width, $height );
1377 - }
1378 - imagecopyresampled( $dst_image, $src_image,
1379 - 0,0,0,0,
1380 - $width, $height, $this->width, $this->height );
1381 - call_user_func( $saveType, $dst_image, $thumbPath );
1382 - imagedestroy( $dst_image );
1383 - imagedestroy( $src_image );
1384 - }
13851106 }
1386 -
1387 - #
1388 - # Check for zero-sized thumbnails. Those can be generated when
1389 - # no disk space is available or some other error occurs
1390 - #
1391 - if( file_exists( $thumbPath ) ) {
1392 - $thumbstat = stat( $thumbPath );
1393 - if( $thumbstat['size'] == 0 || $retval != 0 ) {
1394 - wfDebugLog( 'thumbnail',
1395 - sprintf( 'Removing bad %d-byte thumbnail "%s"',
1396 - $thumbstat['size'], $thumbPath ) );
1397 - unlink( $thumbPath );
1398 - }
1399 - }
1400 - if ( $retval != 0 ) {
1401 - wfDebugLog( 'thumbnail',
1402 - sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
1403 - wfHostname(), $retval, trim($err), $cmd ) );
1404 - return wfMsg( 'thumbnail_error', $err );
1405 - } else {
1406 - return true;
1407 - }
 1107+ return null;
14081108 }
14091109
 1110+ /**
 1111+ * Get last thumbnailing error.
 1112+ * Largely obsolete.
 1113+ */
14101114 function getLastError() {
14111115 return $this->lastError;
14121116 }
14131117
1414 - function imageJpegWrapper( $dst_image, $thumbPath ) {
1415 - imageinterlace( $dst_image );
1416 - imagejpeg( $dst_image, $thumbPath, 95 );
1417 - }
1418 -
14191118 /**
14201119 * Get all thumbnail names previously generated for this image
14211120 */
@@ -1466,9 +1165,10 @@
14671166 $dir = wfImageThumbDir( $this->name, $shared );
14681167 $urls = array();
14691168 foreach ( $files as $file ) {
1470 - $m = array();
1471 - if ( preg_match( '/^(\d+)px/', $file, $m ) ) {
1472 - list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] );
 1169+ # Check that the base image name is part of the thumb name
 1170+ # This is a basic sanity check to avoid erasing unrelated directories
 1171+ if ( strpos( $file, $this->name ) !== false ) {
 1172+ $url = $this->thumbUrlFromName( $file );
14731173 $urls[] = $url;
14741174 @unlink( "$dir/$file" );
14751175 }
@@ -1511,35 +1211,6 @@
15121212 $update->doUpdate();
15131213 }
15141214
1515 - function checkDBSchema(&$db) {
1516 - static $checkDone = false;
1517 - global $wgCheckDBSchema;
1518 - if (!$wgCheckDBSchema || $checkDone) {
1519 - return;
1520 - }
1521 - # img_name must be unique
1522 - if ( !$db->indexUnique( 'image', 'img_name' ) && !$db->indexExists('image','PRIMARY') ) {
1523 - throw new MWException( 'Database schema not up to date, please run maintenance/archives/patch-image_name_unique.sql' );
1524 - }
1525 - $checkDone = true;
1526 -
1527 - # new fields must exist
1528 - #
1529 - # Not really, there's hundreds of checks like this that we could do and they're all pointless, because
1530 - # if the fields are missing, the database will loudly report a query error, the first time you try to do
1531 - # something. The only reason I put the above schema check in was because the absence of that particular
1532 - # index would lead to an annoying subtle bug. No error message, just some very odd behaviour on duplicate
1533 - # uploads. -- TS
1534 - /*
1535 - if ( !$db->fieldExists( 'image', 'img_media_type' )
1536 - || !$db->fieldExists( 'image', 'img_metadata' )
1537 - || !$db->fieldExists( 'image', 'img_width' ) ) {
1538 -
1539 - throw new MWException( 'Database schema not up to date, please run maintenance/update.php' );
1540 - }
1541 - */
1542 - }
1543 -
15441215 /**
15451216 * Return the image history of this image, line by line.
15461217 * starts with current version, then old versions.
@@ -1553,8 +1224,6 @@
15541225 function nextHistoryLine() {
15551226 $dbr = wfGetDB( DB_SLAVE );
15561227
1557 - $this->checkDBSchema($dbr);
1558 -
15591228 if ( $this->historyLine == 0 ) {// called for the first time, return line from cur
15601229 $this->historyRes = $dbr->select( 'image',
15611230 array(
@@ -1614,35 +1283,27 @@
16151284 * @param boolean $fromSharedDirectory Return the path to the file
16161285 * in a shared repository (see $wgUseSharedRepository and related
16171286 * options in DefaultSettings.php) instead of a local one.
1618 - *
 1287+ * @param boolean $fromInstantCommons Use local repository paths only if
 1288+ * InstantCommons is not available
16191289 */
16201290 function getFullPath( $fromSharedRepository = false, $fromInstantCommons = false ) {
16211291 global $wgUploadDirectory, $wgSharedUploadDirectory, $wgInstantCommonsServerPath;
1622 - //use local repository paths only if InstantCommons path is not available
1623 - $dir = $fromInstantCommons ? $wgInstantCommonsServerPath : ($fromSharedRepository ? $wgSharedUploadDirectory :
1624 - $wgUploadDirectory);
16251292
 1293+ $dir = $fromInstantCommons ? $wgInstantCommonsServerPath : ($fromSharedRepository ? $wgSharedUploadDirectory :
 1294+ $wgUploadDirectory);
 1295+
16261296 // $wgSharedUploadDirectory may be false, if thumb.php is used
16271297 if ( $dir ) {
1628 - if($fromInstantCommons)
1629 - {//TODO: Not the best thing to do here as it adds another 10k to
1630 - //the default mediawiki install. Maybe point it to the default logo?
1631 - $fullpath = 'downloading.png';
1632 - }
1633 - else
1634 - {
1635 - $fullpath = $dir . wfGetHashPath($this->name, $fromSharedRepository, $fromInstantCommons) . $this->name;
1636 - }
 1298+ $fullpath = $dir . wfGetHashPath($this->name, $fromSharedRepository, $fromInstantCommons) . $this->name;
16371299 } else {
16381300 $fullpath = false;
16391301 }
1640 -
 1302+
16411303 return $fullpath;
16421304 }
16431305
16441306 /**
16451307 * @return bool
1646 - * @static
16471308 */
16481309 public static function isHashed( $shared ) {
16491310 global $wgHashedUploadDirectory, $wgHashedSharedUploadDirectory;
@@ -1657,8 +1318,6 @@
16581319
16591320 $dbw = wfGetDB( DB_MASTER );
16601321
1661 - $this->checkDBSchema($dbw);
1662 -
16631322 // Delete thumbnails and refresh the metadata cache
16641323 $this->purgeCache();
16651324
@@ -1740,7 +1399,7 @@
17411400
17421401 # Update the current image row
17431402 $dbw->update( 'image',
1744 - array( // SET
 1403+ array( /* SET */
17451404 'img_size' => $this->size,
17461405 'img_width' => intval( $this->width ),
17471406 'img_height' => intval( $this->height ),
@@ -1753,7 +1412,7 @@
17541413 'img_user' => $wgUser->getID(),
17551414 'img_user_text' => $wgUser->getName(),
17561415 'img_metadata' => $this->metadata,
1757 - ), array( // WHERE
 1416+ ), array( /* WHERE */
17581417 'img_name' => $this->name
17591418 ), __METHOD__
17601419 );
@@ -1784,6 +1443,9 @@
17851444 $article->insertNewArticle( $textdesc, $desc, $minor, $watch, $suppressRC );
17861445 }
17871446
 1447+ # Hooks, hooks, the magic of hooks...
 1448+ wfRunHooks( 'FileUpload', array( $this ) );
 1449+
17881450 # Add the log entry
17891451 $log = new LogPage( 'upload' );
17901452 $log->addEntry( 'upload', $descTitle, $desc );
@@ -1791,11 +1453,11 @@
17921454 # Commit the transaction now, in case something goes wrong later
17931455 # The most important thing is that images don't get lost, especially archives
17941456 $dbw->immediateCommit();
1795 -
 1457+
17961458 # Invalidate cache for all pages using this image
17971459 $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
17981460 $update->doUpdate();
1799 -
 1461+
18001462 return true;
18011463 }
18021464
@@ -1836,76 +1498,24 @@
18371499 return $retVal;
18381500 }
18391501
1840 - /**
1841 - * Retrive Exif data from the file and prune unrecognized tags
1842 - * and/or tags with invalid contents
1843 - *
1844 - * @param $filename
1845 - * @return array
1846 - */
1847 - private function retrieveExifData( $filename ) {
1848 - global $wgShowEXIF;
1849 -
1850 - /*
1851 - if ( $this->getMimeType() !== "image/jpeg" )
 1502+ function getExifData() {
 1503+ $handler = $this->getHandler();
 1504+ if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) {
18521505 return array();
1853 - */
1854 -
1855 - if( $wgShowEXIF && file_exists( $filename ) ) {
1856 - $exif = new Exif( $filename );
1857 - return $exif->getFilteredData();
18581506 }
1859 -
1860 - return array();
1861 - }
1862 -
1863 - function getExifData() {
1864 - global $wgRequest;
1865 - if ( $this->metadata === '0' || $this->mime == 'image/vnd.djvu' )
 1507+ if ( !$this->metadata ) {
18661508 return array();
1867 -
1868 - $purge = $wgRequest->getVal( 'action' ) == 'purge';
1869 - $ret = unserialize( $this->metadata );
1870 -
1871 - $oldver = isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) ? $ret['MEDIAWIKI_EXIF_VERSION'] : 0;
1872 - $newver = Exif::version();
1873 -
1874 - if ( !count( $ret ) || $purge || $oldver != $newver ) {
1875 - $this->purgeMetadataCache();
1876 - $this->updateExifData( $newver );
18771509 }
1878 - if ( isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) )
1879 - unset( $ret['MEDIAWIKI_EXIF_VERSION'] );
1880 - $format = new FormatExif( $ret );
 1510+ $exif = unserialize( $this->metadata );
 1511+ if ( !$exif ) {
 1512+ return array();
 1513+ }
 1514+ unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
 1515+ $format = new FormatExif( $exif );
18811516
18821517 return $format->getFormattedData();
18831518 }
18841519
1885 - function updateExifData( $version ) {
1886 - if ( $this->getImagePath() === false ) # Not a local image
1887 - return;
1888 -
1889 - # Get EXIF data from image
1890 - $exif = $this->retrieveExifData( $this->imagePath );
1891 - if ( count( $exif ) ) {
1892 - $exif['MEDIAWIKI_EXIF_VERSION'] = $version;
1893 - $this->metadata = serialize( $exif );
1894 - } else {
1895 - $this->metadata = '0';
1896 - }
1897 -
1898 - # Update EXIF data in database
1899 - $dbw = wfGetDB( DB_MASTER );
1900 -
1901 - $this->checkDBSchema($dbw);
1902 -
1903 - $dbw->update( 'image',
1904 - array( 'img_metadata' => $this->metadata ),
1905 - array( 'img_name' => $this->name ),
1906 - __METHOD__
1907 - );
1908 - }
1909 -
19101520 /**
19111521 * Returns true if the image does not come from the shared
19121522 * image repository.
@@ -1937,7 +1547,7 @@
19381548 * @param $reason
19391549 * @return true on success, false on some kind of failure
19401550 */
1941 - function delete( $reason ) {
 1551+ function delete( $reason, $suppress=false ) {
19421552 $transaction = new FSTransaction();
19431553 $urlArr = array( $this->getURL() );
19441554
@@ -1958,7 +1568,7 @@
19591569 while( $row = $dbw->fetchObject( $result ) ) {
19601570 $oldName = $row->oi_archive_name;
19611571
1962 - $transaction->add( $this->prepareDeleteOld( $oldName, $reason ) );
 1572+ $transaction->add( $this->prepareDeleteOld( $oldName, $reason, $suppress ) );
19631573
19641574 // We'll need to purge this URL from caches...
19651575 $urlArr[] = wfImageArchiveUrl( $oldName );
@@ -1966,7 +1576,7 @@
19671577 $dbw->freeResult( $result );
19681578
19691579 // And the current version...
1970 - $transaction->add( $this->prepareDeleteCurrent( $reason ) );
 1580+ $transaction->add( $this->prepareDeleteCurrent( $reason, $suppress ) );
19711581
19721582 $dbw->immediateCommit();
19731583 } catch( MWException $e ) {
@@ -2003,7 +1613,7 @@
20041614 * @throws MWException or FSException on database or filestore failure
20051615 * @return true on success, false on some kind of failure
20061616 */
2007 - function deleteOld( $archiveName, $reason ) {
 1617+ function deleteOld( $archiveName, $reason, $suppress=false ) {
20081618 $transaction = new FSTransaction();
20091619 $urlArr = array();
20101620
@@ -2016,7 +1626,7 @@
20171627 try {
20181628 $dbw = wfGetDB( DB_MASTER );
20191629 $dbw->begin();
2020 - $transaction->add( $this->prepareDeleteOld( $archiveName, $reason ) );
 1630+ $transaction->add( $this->prepareDeleteOld( $archiveName, $reason, $suppress ) );
20211631 $dbw->immediateCommit();
20221632 } catch( MWException $e ) {
20231633 wfDebug( __METHOD__.": db error, rolling back file transaction\n" );
@@ -2047,7 +1657,7 @@
20481658 * May throw a database error.
20491659 * @return true on success, false on failure
20501660 */
2051 - private function prepareDeleteCurrent( $reason ) {
 1661+ private function prepareDeleteCurrent( $reason, $suppress=false ) {
20521662 return $this->prepareDeleteVersion(
20531663 $this->getFullPath(),
20541664 $reason,
@@ -2068,6 +1678,7 @@
20691679 'fa_user_text' => 'img_user_text',
20701680 'fa_timestamp' => 'img_timestamp' ),
20711681 array( 'img_name' => $this->name ),
 1682+ $suppress,
20721683 __METHOD__ );
20731684 }
20741685
@@ -2076,7 +1687,7 @@
20771688 * May throw a database error.
20781689 * @return true on success, false on failure
20791690 */
2080 - private function prepareDeleteOld( $archiveName, $reason ) {
 1691+ private function prepareDeleteOld( $archiveName, $reason, $suppress=false ) {
20811692 $oldpath = wfImageArchiveDir( $this->name ) .
20821693 DIRECTORY_SEPARATOR . $archiveName;
20831694 return $this->prepareDeleteVersion(
@@ -2101,6 +1712,7 @@
21021713 array(
21031714 'oi_name' => $this->name,
21041715 'oi_archive_name' => $archiveName ),
 1716+ $suppress,
21051717 __METHOD__ );
21061718 }
21071719
@@ -2113,7 +1725,7 @@
21141726 *
21151727 * @return FSTransaction
21161728 */
2117 - private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $fname ) {
 1729+ private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $suppress=false, $fname ) {
21181730 global $wgUser, $wgSaveDeletedFiles;
21191731
21201732 // Dupe the file into the file store
@@ -2143,6 +1755,17 @@
21441756 throw new MWException( "Could not archive and delete file $path" );
21451757 return false;
21461758 }
 1759+
 1760+ // Bitfields to further supress the image content
 1761+ // Note that currently, live images are stored elsewhere
 1762+ // and cannot be partially deleted
 1763+ $bitfield = 0;
 1764+ if ( $suppress ) {
 1765+ $bitfield |= self::DELETED_FILE;
 1766+ $bitfield |= self::DELETED_COMMENT;
 1767+ $bitfield |= self::DELETED_USER;
 1768+ $bitfield |= self::DELETED_RESTRICTED;
 1769+ }
21471770
21481771 $dbw = wfGetDB( DB_MASTER );
21491772 $storageMap = array(
@@ -2151,7 +1774,8 @@
21521775
21531776 'fa_deleted_user' => $dbw->addQuotes( $wgUser->getId() ),
21541777 'fa_deleted_timestamp' => $dbw->timestamp(),
2155 - 'fa_deleted_reason' => $dbw->addQuotes( $reason ) );
 1778+ 'fa_deleted_reason' => $dbw->addQuotes( $reason ),
 1779+ 'fa_deleted' => $bitfield);
21561780 $allFields = array_merge( $storageMap, $fieldMap );
21571781
21581782 try {
@@ -2181,7 +1805,9 @@
21821806 * @return the number of file revisions restored if successful,
21831807 * or false on failure
21841808 */
2185 - function restore( $versions=array() ) {
 1809+ function restore( $versions=array(), $Unsuppress=false ) {
 1810+ global $wgUser;
 1811+
21861812 if( !FileStore::lock() ) {
21871813 wfDebug( __METHOD__." could not acquire filestore lock\n" );
21881814 return false;
@@ -2230,6 +1856,12 @@
22311857
22321858 $revisions = 0;
22331859 while( $row = $dbw->fetchObject( $result ) ) {
 1860+ if ( $Unsuppress ) {
 1861+ // Currently, fa_deleted flags fall off upon restore, lets be careful about this
 1862+ } else if ( ($row->fa_deleted & Revision::DELETED_RESTRICTED) && !$wgUser->isAllowed('hiderevision') ) {
 1863+ // Skip restoring file revisions that the user cannot restore
 1864+ continue;
 1865+ }
22341866 $revisions++;
22351867 $store = FileStore::get( $row->fa_storage_group );
22361868 if( !$store ) {
@@ -2248,12 +1880,17 @@
22491881 // an archived file revision.
22501882 if( is_null( $row->fa_metadata ) ) {
22511883 $tempFile = $store->filePath( $row->fa_storage_key );
2252 - $metadata = serialize( $this->retrieveExifData( $tempFile ) );
22531884
22541885 $magic = MimeMagic::singleton();
22551886 $mime = $magic->guessMimeType( $tempFile, true );
22561887 $media_type = $magic->getMediaType( $tempFile, $mime );
22571888 list( $major_mime, $minor_mime ) = self::splitMime( $mime );
 1889+ $handler = MediaHandler::getHandler( $mime );
 1890+ if ( $handler ) {
 1891+ $metadata = $handler->getMetadata( $image, $tempFile );
 1892+ } else {
 1893+ $metadata = '';
 1894+ }
22581895 } else {
22591896 $metadata = $row->fa_metadata;
22601897 $major_mime = $row->fa_major_mime;
@@ -2307,7 +1944,7 @@
23081945 }
23091946
23101947 $dbw->insert( $table, $fields, __METHOD__ );
2311 - /// @fixme this delete is not totally safe, potentially
 1948+ // @todo this delete is not totally safe, potentially
23121949 $dbw->delete( 'filearchive',
23131950 array( 'fa_id' => $row->fa_id ),
23141951 __METHOD__ );
@@ -2361,71 +1998,14 @@
23621999 }
23632000
23642001 /**
2365 - * Select a page from a multipage document. Determines the page used for
2366 - * rendering thumbnails.
2367 - *
2368 - * @param $page Integer: page number, starting with 1
2369 - */
2370 - function selectPage( $page ) {
2371 - if( $this->initializeMultiPageXML() ) {
2372 - wfDebug( __METHOD__." selecting page $page \n" );
2373 - $this->page = $page;
2374 - $o = $this->multiPageXML->BODY[0]->OBJECT[$page-1];
2375 - $this->height = intval( $o['height'] );
2376 - $this->width = intval( $o['width'] );
2377 - } else {
2378 - wfDebug( __METHOD__." selectPage($page) for bogus multipage xml on '$this->name'\n" );
2379 - return;
2380 - }
2381 - }
2382 -
2383 - /**
2384 - * Lazy-initialize multipage XML metadata for DjVu files.
2385 - * @return bool true if $this->multiPageXML is set up and ready;
2386 - * false if corrupt or otherwise failing
2387 - */
2388 - function initializeMultiPageXML() {
2389 - $this->load();
2390 - if ( isset( $this->multiPageXML ) ) {
2391 - return true;
2392 - }
2393 -
2394 - #
2395 - # Check for files uploaded prior to DJVU support activation,
2396 - # or damaged.
2397 - #
2398 - if( empty( $this->metadata ) || $this->metadata == serialize( array() ) ) {
2399 - $deja = new DjVuImage( $this->imagePath );
2400 - $this->metadata = $deja->retrieveMetaData();
2401 - $this->purgeMetadataCache();
2402 -
2403 - # Update metadata in the database
2404 - $dbw = wfGetDB( DB_MASTER );
2405 - $dbw->update( 'image',
2406 - array( 'img_metadata' => $this->metadata ),
2407 - array( 'img_name' => $this->name ),
2408 - __METHOD__
2409 - );
2410 - }
2411 - wfSuppressWarnings();
2412 - try {
2413 - $this->multiPageXML = new SimpleXMLElement( $this->metadata );
2414 - } catch( Exception $e ) {
2415 - wfDebug( "Bogus multipage XML metadata on '$this->name'\n" );
2416 - $this->multiPageXML = null;
2417 - }
2418 - wfRestoreWarnings();
2419 - return isset( $this->multiPageXML );
2420 - }
2421 -
2422 - /**
24232002 * Returns 'true' if this image is a multipage document, e.g. a DJVU
24242003 * document.
24252004 *
24262005 * @return Bool
24272006 */
24282007 function isMultipage() {
2429 - return ( $this->mime == 'image/vnd.djvu' );
 2008+ $handler = $this->getHandler();
 2009+ return $handler && $handler->isMultiPage();
24302010 }
24312011
24322012 /**
@@ -2433,13 +2013,10 @@
24342014 * documents which aren't multipage documents
24352015 */
24362016 function pageCount() {
2437 - if ( ! $this->isMultipage() ) {
2438 - return null;
2439 - }
2440 - if( $this->initializeMultiPageXML() ) {
2441 - return count( $this->multiPageXML->xpath( '//OBJECT' ) );
 2017+ $handler = $this->getHandler();
 2018+ if ( $handler && $handler->isMultiPage() ) {
 2019+ return $handler->pageCount( $this );
24422020 } else {
2443 - wfDebug( "Requested pageCount() for bogus multi-page metadata for '$this->name'\n" );
24442021 return null;
24452022 }
24462023 }
@@ -2456,60 +2033,161 @@
24572034 return $dbc;
24582035 }
24592036
 2037+ /**
 2038+ * Calculate the height of a thumbnail using the source and destination width
 2039+ */
 2040+ static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
 2041+ // Exact integer multiply followed by division
 2042+ if ( $srcWidth == 0 ) {
 2043+ return 0;
 2044+ } else {
 2045+ return round( $srcHeight * $dstWidth / $srcWidth );
 2046+ }
 2047+ }
 2048+
 2049+ /**
 2050+ * Get an image size array like that returned by getimagesize(), or false if it
 2051+ * can't be determined.
 2052+ *
 2053+ * @param string $fileName The filename
 2054+ * @return array
 2055+ */
 2056+ function getImageSize( $fileName ) {
 2057+ $handler = $this->getHandler();
 2058+ return $handler->getImageSize( $this, $fileName );
 2059+ }
 2060+
 2061+ /**
 2062+ * Get the thumbnail extension and MIME type for a given source MIME type
 2063+ * @return array thumbnail extension and MIME type
 2064+ */
 2065+ static function getThumbType( $ext, $mime ) {
 2066+ $handler = MediaHandler::getHandler( $mime );
 2067+ if ( $handler ) {
 2068+ return $handler->getThumbType( $ext, $mime );
 2069+ } else {
 2070+ return array( $ext, $mime );
 2071+ }
 2072+ }
 2073+
24602074 } //class
24612075
 2076+
24622077 /**
2463 - * Wrapper class for thumbnail images
 2078+ * @addtogroup Media
24642079 */
2465 -class ThumbnailImage {
 2080+class ArchivedFile
 2081+{
24662082 /**
2467 - * @param string $path Filesystem path to the thumb
2468 - * @param string $url URL path to the thumb
2469 - * @private
 2083+ * Returns a file object from the filearchive table
 2084+ * In the future, all current and old image storage
 2085+ * may use FileStore. There will be a "old" storage
 2086+ * for current and previous file revisions as well as
 2087+ * the "deleted" group for archived revisions
 2088+ * @param $title, the corresponding image page title
 2089+ * @param $id, the image id, a unique key
 2090+ * @param $key, optional storage key
 2091+ * @return ResultWrapper
24702092 */
2471 - function ThumbnailImage( $url, $width, $height, $path = false ) {
2472 - $this->url = $url;
2473 - $this->width = round( $width );
2474 - $this->height = round( $height );
2475 - # These should be integers when they get here.
2476 - # If not, there's a bug somewhere. But let's at
2477 - # least produce valid HTML code regardless.
2478 - $this->path = $path;
 2093+ function ArchivedFile( $title, $id=0, $key='' ) {
 2094+ if( !is_object( $title ) ) {
 2095+ throw new MWException( 'Image constructor given bogus title.' );
 2096+ }
 2097+ $conds = ($id) ? "fa_id = $id" : "fa_storage_key = '$key'";
 2098+ if( $title->getNamespace() == NS_IMAGE ) {
 2099+ $dbr = wfGetDB( DB_SLAVE );
 2100+ $res = $dbr->select( 'filearchive',
 2101+ array(
 2102+ 'fa_id',
 2103+ 'fa_name',
 2104+ 'fa_storage_key',
 2105+ 'fa_storage_group',
 2106+ 'fa_size',
 2107+ 'fa_bits',
 2108+ 'fa_width',
 2109+ 'fa_height',
 2110+ 'fa_metadata',
 2111+ 'fa_media_type',
 2112+ 'fa_major_mime',
 2113+ 'fa_minor_mime',
 2114+ 'fa_description',
 2115+ 'fa_user',
 2116+ 'fa_user_text',
 2117+ 'fa_timestamp',
 2118+ 'fa_deleted' ),
 2119+ array(
 2120+ 'fa_name' => $title->getDbKey(),
 2121+ $conds ),
 2122+ __METHOD__,
 2123+ array( 'ORDER BY' => 'fa_timestamp DESC' ) );
 2124+
 2125+ if ( $dbr->numRows( $res ) == 0 ) {
 2126+ // this revision does not exist?
 2127+ return;
 2128+ }
 2129+ $ret = $dbr->resultObject( $res );
 2130+ $row = $ret->fetchObject();
 2131+
 2132+ // initialize fields for filestore image object
 2133+ $this->mId = intval($row->fa_id);
 2134+ $this->mName = $row->fa_name;
 2135+ $this->mGroup = $row->fa_storage_group;
 2136+ $this->mKey = $row->fa_storage_key;
 2137+ $this->mSize = $row->fa_size;
 2138+ $this->mBits = $row->fa_bits;
 2139+ $this->mWidth = $row->fa_width;
 2140+ $this->mHeight = $row->fa_height;
 2141+ $this->mMetaData = $row->fa_metadata;
 2142+ $this->mMime = "$row->fa_major_mime/$row->fa_minor_mime";
 2143+ $this->mType = $row->fa_media_type;
 2144+ $this->mDescription = $row->fa_description;
 2145+ $this->mUser = $row->fa_user;
 2146+ $this->mUserText = $row->fa_user_text;
 2147+ $this->mTimestamp = $row->fa_timestamp;
 2148+ $this->mDeleted = $row->fa_deleted;
 2149+ } else {
 2150+ throw new MWException( 'This title does not correspond to an image page.' );
 2151+ return;
 2152+ }
 2153+ return true;
24792154 }
24802155
24812156 /**
2482 - * @return string The thumbnail URL
 2157+ * int $field one of DELETED_* bitfield constants
 2158+ * for file or revision rows
 2159+ * @return bool
24832160 */
2484 - function getUrl() {
2485 - return $this->url;
 2161+ function isDeleted( $field ) {
 2162+ return ($this->mDeleted & $field) == $field;
24862163 }
2487 -
 2164+
24882165 /**
2489 - * Return HTML <img ... /> tag for the thumbnail, will include
2490 - * width and height attributes and a blank alt text (as required).
2491 - *
2492 - * You can set or override additional attributes by passing an
2493 - * associative array of name => data pairs. The data will be escaped
2494 - * for HTML output, so should be in plaintext.
2495 - *
2496 - * @param array $attribs
2497 - * @return string
2498 - * @public
 2166+ * Determine if the current user is allowed to view a particular
 2167+ * field of this FileStore image file, if it's marked as deleted.
 2168+ * @param int $field
 2169+ * @return bool
24992170 */
2500 - function toHtml( $attribs = array() ) {
2501 - $attribs['src'] = $this->url;
2502 - $attribs['width'] = $this->width;
2503 - $attribs['height'] = $this->height;
2504 - if( !isset( $attribs['alt'] ) ) $attribs['alt'] = '';
2505 -
2506 - $html = '<img ';
2507 - foreach( $attribs as $name => $data ) {
2508 - $html .= $name . '="' . htmlspecialchars( $data ) . '" ';
 2171+ function userCan( $field ) {
 2172+ if( isset($this->mDeleted) && ($this->mDeleted & $field) == $field ) {
 2173+ // images
 2174+ global $wgUser;
 2175+ $permission = ( $this->mDeleted & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED
 2176+ ? 'hiderevision'
 2177+ : 'deleterevision';
 2178+ wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
 2179+ return $wgUser->isAllowed( $permission );
 2180+ } else {
 2181+ return true;
25092182 }
2510 - $html .= '/>';
2511 - return $html;
25122183 }
2513 -
25142184 }
25152185
 2186+/**
 2187+ * Aliases for backwards compatibility with 1.6
 2188+ */
 2189+define( 'MW_IMG_DELETED_FILE', Image::DELETED_FILE );
 2190+define( 'MW_IMG_DELETED_COMMENT', Image::DELETED_COMMENT );
 2191+define( 'MW_IMG_DELETED_USER', Image::DELETED_USER );
 2192+define( 'MW_IMG_DELETED_RESTRICTED', Image::DELETED_RESTRICTED );
 2193+
25162194 ?>

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r22191byte -> byteagreg18:46, 15 May 2007

Status & tagging log