r22580 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r22579‎ | r22580 | r22581 >
Date:21:02, 30 May 2007
Author:tstarling
Status:old
Tags:
Comment:
Merged filerepo-work branch:
* Added support for configuration of an arbitrary number of commons-style file repositories.
* Split Image.php into filerepo/File.php and filerepo/LocalFile.php
* Renamed Image::getImagePath() to File::getPath()
* Added initial support for timestamp-based file fetching (OldLocalFile), to be expanded upon by aaron.
* Changed the interface for Image/File object creation: use wfFindFile() or wfLocalFile() depending on semantics
* ImageGallery::add() now accepts a title object as the first parameter
* Moved file handling operations on upload from SpecialUpload to File
* Removed path-related functions from ImageFunctions.php. Removed static path accessors from File.
* Added a Content-Disposition header to thumb.php output
* Improved thumb.php error handling
* Updated the unit test suite to kind of partially work with modern computers. RunTests.php doesn't work just yet. Fixed an actual regression that the test suite detected -- moved some defines to Defines.php where they will be loaded consistently.
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/StartProfiler.php (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/CategoryPage.php (modified) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/Defines.php (modified) (history)
  • /trunk/phase3/includes/ExternalEdit.php (modified) (history)
  • /trunk/phase3/includes/GlobalFunctions.php (modified) (history)
  • /trunk/phase3/includes/Image.php (deleted) (history)
  • /trunk/phase3/includes/ImageFunctions.php (modified) (history)
  • /trunk/phase3/includes/ImageGallery.php (modified) (history)
  • /trunk/phase3/includes/ImagePage.php (modified) (history)
  • /trunk/phase3/includes/ImageQueryPage.php (modified) (history)
  • /trunk/phase3/includes/Linker.php (modified) (history)
  • /trunk/phase3/includes/MediaTransformOutput.php (modified) (history)
  • /trunk/phase3/includes/Parser.php (modified) (history)
  • /trunk/phase3/includes/SearchEngine.php (modified) (history)
  • /trunk/phase3/includes/Setup.php (modified) (history)
  • /trunk/phase3/includes/Skin.php (modified) (history)
  • /trunk/phase3/includes/SpecialImagelist.php (modified) (history)
  • /trunk/phase3/includes/SpecialMIMEsearch.php (modified) (history)
  • /trunk/phase3/includes/SpecialNewimages.php (modified) (history)
  • /trunk/phase3/includes/SpecialUndelete.php (modified) (history)
  • /trunk/phase3/includes/SpecialUpload.php (modified) (history)
  • /trunk/phase3/includes/StreamFile.php (modified) (history)
  • /trunk/phase3/includes/filerepo (added) (history)
  • /trunk/phase3/includes/filerepo (added) (history)
  • /trunk/phase3/includes/filerepo/LocalFile.php (modified) (history)
  • /trunk/phase3/includes/media/Bitmap.php (modified) (history)
  • /trunk/phase3/includes/media/DjVu.php (modified) (history)
  • /trunk/phase3/includes/media/Generic.php (modified) (history)
  • /trunk/phase3/includes/media/SVG.php (modified) (history)
  • /trunk/phase3/includes/normal/UtfNormal.php (modified) (history)
  • /trunk/phase3/maintenance/FiveUpgrade.inc (modified) (history)
  • /trunk/phase3/maintenance/cleanupImages.php (modified) (history)
  • /trunk/phase3/maintenance/importImages.inc.php (modified) (history)
  • /trunk/phase3/maintenance/importImages.php (modified) (history)
  • /trunk/phase3/maintenance/rebuildImages.php (modified) (history)
  • /trunk/phase3/tests/ArticleTest.php (modified) (history)
  • /trunk/phase3/tests/DatabaseTest.php (modified) (history)
  • /trunk/phase3/tests/GlobalTest.php (modified) (history)
  • /trunk/phase3/tests/ImageFunctionsTest.php (added) (history)
  • /trunk/phase3/tests/ImageFunctionsTest.php (added) (history)
  • /trunk/phase3/tests/ImageTest.php (deleted) (history)
  • /trunk/phase3/tests/README (modified) (history)
  • /trunk/phase3/tests/RunTests.php (modified) (history)
  • /trunk/phase3/tests/SanitizerTest.php (modified) (history)
  • /trunk/phase3/tests/SearchEngineTest.php (modified) (history)
  • /trunk/phase3/tests/SearchMySQL4Test.php (modified) (history)
  • /trunk/phase3/tests/run-test.php (added) (history)
  • /trunk/phase3/thumb.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/importImages.php
@@ -42,56 +42,40 @@
4343 $license = isset( $options['license'] ) ? $options['license'] : '';
4444
4545 # Batch "upload" operation
 46+ global $wgUploadDirectory;
4647 foreach( $files as $file ) {
47 -
4848 $base = wfBaseName( $file );
4949
5050 # Validate a title
5151 $title = Title::makeTitleSafe( NS_IMAGE, $base );
52 - if( is_object( $title ) ) {
 52+ if( !is_object( $title ) ) {
 53+ echo( "{$base} could not be imported; a valid title cannot be produced\n" );
 54+ continue;
 55+ }
5356
54 - # Check existence
55 - $image = new Image( $title );
56 - if( !$image->exists() ) {
 57+ # Check existence
 58+ $image = wfLocalFile( $title );
 59+ if( $image->exists() ) {
 60+ echo( "{$base} could not be imported; a file with this name exists in the wiki\n" );
 61+ continue;
 62+ }
5763
58 - global $wgUploadDirectory;
 64+ # Stash the file
 65+ echo( "Saving {$base}..." );
5966
60 - # copy() doesn't create paths so if the hash path doesn't exist, we
61 - # have to create it
62 - makeHashPath( wfGetHashPath( $image->name ) );
 67+ $archive = $image->publish( $file );
 68+ if ( WikiError::isError( $archive ) ) {
 69+ echo( "failed.\n" );
 70+ continue;
 71+ }
 72+ echo( "importing..." );
6373
64 - # Stash the file
65 - echo( "Saving {$base}..." );
66 -
67 - if( copy( $file, $image->getFullPath() ) ) {
68 -
69 - echo( "importing..." );
70 -
71 - # Grab the metadata
72 - $image->loadFromFile();
73 -
74 - # Record the upload
75 - if( $image->recordUpload( '', $comment, $license ) ) {
76 -
77 - # We're done!
78 - echo( "done.\n" );
79 -
80 - } else {
81 - echo( "failed.\n" );
82 - }
83 -
84 - } else {
85 - echo( "failed.\n" );
86 - }
87 -
88 - } else {
89 - echo( "{$base} could not be imported; a file with this name exists in the wiki\n" );
90 - }
91 -
 74+ if ( $image->recordUpload( $archive, $comment, $license ) ) {
 75+ # We're done!
 76+ echo( "done.\n" );
9277 } else {
93 - echo( "{$base} could not be imported; a valid title cannot be produced\n" );
 78+ echo( "failed.\n" );
9479 }
95 -
9680 }
9781
9882 } else {
Index: trunk/phase3/maintenance/importImages.inc.php
@@ -47,20 +47,4 @@
4848 return array( $fname, $ext );
4949 }
5050
51 -/**
52 - * Given an image hash, check that the structure exists to save the image file
53 - * and create it if it doesn't
54 - *
55 - * @param $hash Part of an image hash, e.g. /f/fd/
56 - */
57 -function makeHashPath( $hash ) {
58 - global $wgUploadDirectory;
59 - $parts = explode( '/', substr( $hash, 1, strlen( $hash ) - 2 ) );
60 - if( !is_dir( $wgUploadDirectory . '/' . $parts[0] ) )
61 - mkdir( $wgUploadDirectory . '/' . $parts[0] );
62 - if( !is_dir( $wgUploadDirectory . '/' . $hash ) )
63 - mkdir( $wgUploadDirectory . '/' . $hash );
64 -}
65 -
66 -
67 -?>
\ No newline at end of file
 51+?>
Index: trunk/phase3/maintenance/cleanupImages.php
@@ -89,7 +89,10 @@
9090 }
9191
9292 function filePath( $name ) {
93 - return wfImageDir( $name ) . "/$name";
 93+ if ( !isset( $this->repo ) ) {
 94+ $this->repo = RepoGroup::singleton()->getLocalRepo();
 95+ }
 96+ return $this->repo->getRootDirectory() . '/' . $this->repo->getHashPath( $name ) . $name;
9497 }
9598
9699 function pokeFile( $orig, $new ) {
Index: trunk/phase3/maintenance/FiveUpgrade.inc
@@ -693,10 +693,7 @@
694694 return $copy;
695695 }
696696
697 - function imageInfo( $name, $subdirCallback='wfImageDir', $basename = null ) {
698 - if( is_null( $basename ) ) $basename = $name;
699 - $dir = call_user_func( $subdirCallback, $basename );
700 - $filename = $dir . '/' . $name;
 697+ function imageInfo( $filename ) {
701698 $info = array(
702699 'width' => 0,
703700 'height' => 0,
@@ -711,20 +708,13 @@
712709
713710 $info['media'] = $magic->getMediaType( $filename, $mime );
714711
715 - # Height and width
716 - $gis = false;
717 - if( $mime == 'image/svg' ) {
718 - $gis = wfGetSVGsize( $filename );
719 - } elseif( $magic->isPHPImageType( $mime ) ) {
720 - $gis = getimagesize( $filename );
721 - } else {
722 - $this->log( "Surprising mime type: $mime" );
723 - }
724 - if( $gis ) {
725 - $info['width' ] = $gis[0];
726 - $info['height'] = $gis[1];
727 - }
728 - if( isset( $gis['bits'] ) ) {
 712+ $image = UnregisteredLocalFile::newFromPath( $filename, $mime );
 713+
 714+ $info['width'] = $image->getWidth();
 715+ $info['height'] = $image->getHeight();
 716+
 717+ $gis = $image->getImageSize();
 718+ if ( isset( $gis['bits'] ) ) {
729719 $info['bits'] = $gis['bits'];
730720 }
731721
@@ -896,7 +886,7 @@
897887 }
898888
899889 function upgradeLogging() {
900 - $tabledef = <<<END
 890+ $tabledef = <<<ENDS
901891 CREATE TABLE $1 (
902892 -- Symbolic keys for the general log type and the action type
903893 -- within the log. The output format will be controlled by the
@@ -926,7 +916,7 @@
927917 KEY page_time (log_namespace, log_title, log_timestamp)
928918
929919 ) TYPE=InnoDB
930 -END;
 920+ENDS;
931921 $fields = array(
932922 'log_type' => MW_UPGRADE_COPY,
933923 'log_action' => MW_UPGRADE_COPY,
@@ -940,7 +930,7 @@
941931 }
942932
943933 function upgradeArchive() {
944 - $tabledef = <<<END
 934+ $tabledef = <<<ENDS
945935 CREATE TABLE $1 (
946936 ar_namespace int NOT NULL default '0',
947937 ar_title varchar(255) binary NOT NULL default '',
@@ -960,7 +950,7 @@
961951 KEY name_title_timestamp (ar_namespace,ar_title,ar_timestamp)
962952
963953 ) TYPE=InnoDB
964 -END;
 954+ENDS;
965955 $fields = array(
966956 'ar_namespace' => MW_UPGRADE_COPY,
967957 'ar_title' => MW_UPGRADE_ENCODE,
@@ -979,7 +969,7 @@
980970 function upgradeImagelinks() {
981971 global $wgUseLatin1;
982972 if( $wgUseLatin1 ) {
983 - $tabledef = <<<END
 973+ $tabledef = <<<ENDS
984974 CREATE TABLE $1 (
985975 -- Key to page_id of the page containing the image / media link.
986976 il_from int(8) unsigned NOT NULL default '0',
@@ -993,7 +983,7 @@
994984 KEY (il_to)
995985
996986 ) TYPE=InnoDB
997 -END;
 987+ENDS;
998988 $fields = array(
999989 'il_from' => MW_UPGRADE_COPY,
1000990 'il_to' => MW_UPGRADE_ENCODE );
@@ -1004,7 +994,7 @@
1005995 function upgradeCategorylinks() {
1006996 global $wgUseLatin1;
1007997 if( $wgUseLatin1 ) {
1008 - $tabledef = <<<END
 998+ $tabledef = <<<ENDS
1009999 CREATE TABLE $1 (
10101000 cl_from int(8) unsigned NOT NULL default '0',
10111001 cl_to varchar(255) binary NOT NULL default '',
@@ -1015,7 +1005,7 @@
10161006 KEY cl_sortkey(cl_to,cl_sortkey),
10171007 KEY cl_timestamp(cl_to,cl_timestamp)
10181008 ) TYPE=InnoDB
1019 -END;
 1009+ENDS;
10201010 $fields = array(
10211011 'cl_from' => MW_UPGRADE_COPY,
10221012 'cl_to' => MW_UPGRADE_ENCODE,
@@ -1028,7 +1018,7 @@
10291019 function upgradeIpblocks() {
10301020 global $wgUseLatin1;
10311021 if( $wgUseLatin1 ) {
1032 - $tabledef = <<<END
 1022+ $tabledef = <<<ENDS
10331023 CREATE TABLE $1 (
10341024 ipb_id int(8) NOT NULL auto_increment,
10351025 ipb_address varchar(40) binary NOT NULL default '',
@@ -1044,7 +1034,7 @@
10451035 INDEX ipb_user (ipb_user)
10461036
10471037 ) TYPE=InnoDB
1048 -END;
 1038+ENDS;
10491039 $fields = array(
10501040 'ipb_id' => MW_UPGRADE_COPY,
10511041 'ipb_address' => MW_UPGRADE_COPY,
@@ -1060,7 +1050,7 @@
10611051
10621052 function upgradeRecentchanges() {
10631053 // There's a format change in the namespace field
1064 - $tabledef = <<<END
 1054+ $tabledef = <<<ENDS
10651055 CREATE TABLE $1 (
10661056 rc_id int(8) NOT NULL auto_increment,
10671057 rc_timestamp varchar(14) binary NOT NULL default '',
@@ -1098,7 +1088,7 @@
10991089 INDEX rc_ip (rc_ip)
11001090
11011091 ) TYPE=InnoDB
1102 -END;
 1092+ENDS;
11031093 $fields = array(
11041094 'rc_id' => MW_UPGRADE_COPY,
11051095 'rc_timestamp' => MW_UPGRADE_COPY,
@@ -1124,7 +1114,7 @@
11251115
11261116 function upgradeQuerycache() {
11271117 // There's a format change in the namespace field
1128 - $tabledef = <<<END
 1118+ $tabledef = <<<ENDS
11291119 CREATE TABLE $1 (
11301120 -- A key name, generally the base name of of the special page.
11311121 qc_type char(32) NOT NULL,
@@ -1139,7 +1129,7 @@
11401130 KEY (qc_type,qc_value)
11411131
11421132 ) TYPE=InnoDB
1143 -END;
 1133+ENDS;
11441134 $fields = array(
11451135 'qc_type' => MW_UPGRADE_COPY,
11461136 'qc_value' => MW_UPGRADE_COPY,
Index: trunk/phase3/maintenance/rebuildImages.php
@@ -40,8 +40,18 @@
4141
4242 $this->maxLag = 10; # if slaves are lagged more than 10 secs, wait
4343 $this->dryrun = $dryrun;
 44+ if ( $dryrun ) {
 45+ $GLOBALS['wgReadOnly'] = 'Dry run mode, image upgrades are suppressed';
 46+ }
4447 }
4548
 49+ function getRepo() {
 50+ if ( !isset( $this->repo ) ) {
 51+ $this->repo = RepoGroup::singleton()->getLocalRepo();
 52+ }
 53+ return $this->repo;
 54+ }
 55+
4656 function build() {
4757 $this->buildImage();
4858 $this->buildOldImage();
@@ -94,13 +104,7 @@
95105
96106 while( $row = $this->dbr->fetchObject( $result ) ) {
97107 $update = call_user_func( $callback, $row );
98 - if( is_array( $update ) ) {
99 - if( !$this->dryrun ) {
100 - $this->dbw->update( $table,
101 - $update,
102 - array( $key => $row->$key ),
103 - $fname );
104 - }
 108+ if( $update ) {
105109 $this->progress( 1 );
106110 } else {
107111 $this->progress( 0 );
@@ -116,97 +120,43 @@
117121 }
118122
119123 function imageCallback( $row ) {
120 - if( $row->img_width ) {
121 - // Already processed
122 - return null;
123 - }
124 -
125 - // Fill in the new image info fields
126 - $info = $this->imageInfo( $row->img_name );
127 -
128 - global $wgMemc;
129 - $key = wfMemcKey( "Image", md5( $row->img_name ) );
130 - $wgMemc->delete( $key );
131 -
132 - return array(
133 - 'img_width' => $info['width'],
134 - 'img_height' => $info['height'],
135 - 'img_bits' => $info['bits'],
136 - 'img_media_type' => $info['media'],
137 - 'img_major_mime' => $info['major'],
138 - 'img_minor_mime' => $info['minor'] );
 124+ // Create a File object from the row
 125+ // This will also upgrade it
 126+ $file = $this->getRepo()->newFileFromRow( $row );
 127+ return $file->getUpgraded();
139128 }
140129
141 -
142130 function buildOldImage() {
143131 $this->buildTable( 'oldimage', 'oi_archive_name',
144132 array( &$this, 'oldimageCallback' ) );
145133 }
146134
147135 function oldimageCallback( $row ) {
148 - if( $row->oi_width ) {
149 - return null;
 136+ // Create a File object from the row
 137+ // This will also upgrade it
 138+ if ( $row->oi_archive_name == '' ) {
 139+ $this->log( "Empty oi_archive_name for oi_name={$row->oi_name}" );
 140+ return false;
150141 }
151 -
152 - // Fill in the new image info fields
153 - $info = $this->imageInfo( $row->oi_archive_name, 'wfImageArchiveDir', $row->oi_name );
154 - return array(
155 - 'oi_width' => $info['width' ],
156 - 'oi_height' => $info['height'],
157 - 'oi_bits' => $info['bits' ] );
 142+ $file = $this->getRepo()->newFileFromRow( $row );
 143+ return $file->getUpgraded();
158144 }
159145
160146 function crawlMissing() {
161 - global $wgUploadDirectory, $wgHashedUploadDirectory;
162 - if( $wgHashedUploadDirectory ) {
163 - for( $i = 0; $i < 16; $i++ ) {
164 - for( $j = 0; $j < 16; $j++ ) {
165 - $dir = sprintf( '%s%s%01x%s%02x',
166 - $wgUploadDirectory,
167 - DIRECTORY_SEPARATOR,
168 - $i,
169 - DIRECTORY_SEPARATOR,
170 - $i * 16 + $j );
171 - $this->crawlDirectory( $dir );
172 - }
173 - }
174 - } else {
175 - $this->crawlDirectory( $wgUploadDirectory );
176 - }
 147+ $repo = RepoGroup::singleton()->getLocalRepo();
 148+ $repo->enumFilesInFS( array( $this, 'checkMissingImage' ) );
177149 }
178150
179 - function crawlDirectory( $dir ) {
180 - if( !file_exists( $dir ) ) {
181 - return $this->log( "no directory, skipping $dir" );
 151+ function checkMissingImage( $fullpath ) {
 152+ $fname = 'ImageBuilder::checkMissingImage';
 153+ $filename = wfBaseName( $fullpath );
 154+ if( is_dir( $fullpath ) ) {
 155+ return;
182156 }
183 - if( !is_dir( $dir ) ) {
184 - return $this->log( "not a directory?! skipping $dir" );
 157+ if( is_link( $fullpath ) ) {
 158+ $this->log( "skipping symlink at $fullpath" );
 159+ return;
185160 }
186 - if( !is_readable( $dir ) ) {
187 - return $this->log( "dir not readable, skipping $dir" );
188 - }
189 - $source = opendir( $dir );
190 - if( $source === false ) {
191 - return $this->log( "couldn't open dir, skipping $dir" );
192 - }
193 -
194 - $this->log( "crawling $dir" );
195 - while( false !== ( $filename = readdir( $source ) ) ) {
196 - $fullpath = $dir . DIRECTORY_SEPARATOR . $filename;
197 - if( is_dir( $fullpath ) ) {
198 - continue;
199 - }
200 - if( is_link( $fullpath ) ) {
201 - $this->log( "skipping symlink at $fullpath" );
202 - continue;
203 - }
204 - $this->checkMissingImage( $filename, $fullpath );
205 - }
206 - closedir( $source );
207 - }
208 -
209 - function checkMissingImage( $filename, $fullpath ) {
210 - $fname = 'ImageBuilder::checkMissingImage';
211161 $row = $this->dbw->selectRow( 'image',
212162 array( 'img_name' ),
213163 array( 'img_name' => $filename ),
@@ -224,7 +174,7 @@
225175 $fname = 'ImageBuilder::addMissingImage';
226176
227177 $size = filesize( $fullpath );
228 - $info = $this->imageInfo( $filename );
 178+ $info = $this->imageInfo( $fullpath );
229179 $timestamp = $this->dbw->timestamp( filemtime( $fullpath ) );
230180
231181 global $wgContLang;
@@ -242,23 +192,14 @@
243193 $this->log( "Empty filename for $fullpath" );
244194 return;
245195 }
246 -
247 - $fields = array(
248 - 'img_name' => $filename,
249 - 'img_size' => $size,
250 - 'img_width' => $info['width'],
251 - 'img_height' => $info['height'],
252 - 'img_metadata' => '', // filled in on-demand
253 - 'img_bits' => $info['bits'],
254 - 'img_media_type' => $info['media'],
255 - 'img_major_mime' => $info['major'],
256 - 'img_minor_mime' => $info['minor'],
257 - 'img_description' => '(recovered file, missing upload log entry)',
258 - 'img_user' => 0,
259 - 'img_user_text' => 'Conversion script',
260 - 'img_timestamp' => $timestamp );
261 - if( !$this->dryrun ) {
262 - $this->dbw->insert( 'image', $fields, $fname );
 196+ if ( !$this->dryrun ) {
 197+ $file = wfLocalFile( $filename );
 198+ if ( !$file->recordUpload( '', '(recovered file, missing upload log entry)', '', '', '',
 199+ false, $timestamp ) )
 200+ {
 201+ $this->log( "Error uploading file $fullpath" );
 202+ return;
 203+ }
263204 }
264205 $this->log( $fullpath );
265206 }
Index: trunk/phase3/tests/ImageTest.php
@@ -1,67 +0,0 @@
2 -<?php
3 -
4 -require_once( 'PHPUnit.php' );
5 -require_once( '../includes/Defines.php' );
6 -#require_once( '../includes/Profiling.php' );
7 -require_once( '../includes/GlobalFunctions.php' );
8 -require_once( '../includes/Image.php' );
9 -require_once( '../includes/ImageFunctions.php' );
10 -
11 -class ImageTest extends PHPUnit_TestCase {
12 - function ImageTest( $name ) {
13 - $this->PHPUnit_TestCase( $name );
14 - }
15 -
16 - function setUp() {
17 - }
18 -
19 - function tearDown() {
20 - }
21 -
22 - function testFitBoxWidth() {
23 - $vals = array(
24 - array(
25 - 'width' => 50,
26 - 'height' => 50,
27 - 'tests' => array(
28 - 50 => 50,
29 - 17 => 17,
30 - 18 => 18 ) ),
31 - array(
32 - 'width' => 366,
33 - 'height' => 300,
34 - 'tests' => array(
35 - 50 => 61,
36 - 17 => 21,
37 - 18 => 22 ) ),
38 - array(
39 - 'width' => 300,
40 - 'height' => 366,
41 - 'tests' => array(
42 - 50 => 41,
43 - 17 => 14,
44 - 18 => 15 ) ),
45 - array(
46 - 'width' => 100,
47 - 'height' => 400,
48 - 'tests' => array(
49 - 50 => 12,
50 - 17 => 4,
51 - 18 => 4 ) ) );
52 - foreach( $vals as $row ) {
53 - extract( $row );
54 - foreach( $tests as $max => $expected ) {
55 - $y = round( $expected * $height / $width );
56 - $result = wfFitBoxWidth( $width, $height, $max );
57 - $y2 = round( $result * $height / $width );
58 - $this->assertEquals( $expected,
59 - $result,
60 - "($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" );
61 - }
62 - }
63 - }
64 -
65 - /* TODO: many more! */
66 -}
67 -
68 -?>
Index: trunk/phase3/tests/SearchEngineTest.php
@@ -1,20 +1,7 @@
22 <?php
33
4 -$IP = '..';
5 -require_once( 'PHPUnit.php' );
6 -require_once( '../includes/Defines.php' );
7 -require_once( '../includes/DefaultSettings.php' );
8 -#require_once( '../includes/Profiling.php' );
9 -require_once( '../includes/Hooks.php' );
10 -require_once( '../includes/MagicWord.php' );
11 -require_once( '../languages/Language.php' );
12 -
13 -require_once( '../includes/SearchEngine.php' );
14 -require_once( '../includes/SearchMySQL.php' );
15 -require_once( '../includes/SearchMySQL4.php' );
16 -
174 /** @todo document */
18 -class SearchEngine_TestCase extends PHPUnit_TestCase {
 5+class SearchEngine_TestCase extends PHPUnit_Framework_TestCase {
196 var $db, $search;
207
218 function insertSearchData() {
Index: trunk/phase3/tests/GlobalTest.php
@@ -1,32 +1,6 @@
22 <?php
33
4 -require_once( 'PHPUnit.php' );
5 -require_once( '../includes/Defines.php' );
6 -require_once( '../includes/GlobalFunctions.php' );
7 -require_once( '../includes/Exception.php' );
8 -
9 -class GlobalTest extends PHPUnit_TestCase {
10 - function GlobalTest( $name ) {
11 - $this->PHPUnit_TestCase( $name );
12 - }
13 -
14 - function setUp() {
15 - $this->save = array();
16 - $saveVars = array( 'wgReadOnlyFile' );
17 - foreach( $saveVars as $var ) {
18 - if( isset( $GLOBALS[$var] ) ) {
19 - $this->save[$var] = $GLOBALS[$var];
20 - }
21 - }
22 - $GLOBALS['wgReadOnlyFile'] = wfTempDir() . '/testReadOnly-' . mt_rand();
23 - }
24 -
25 - function tearDown() {
26 - foreach( $this->save as $var => $data ) {
27 - $GLOBALS[$var] = $data;
28 - }
29 - }
30 -
 4+class GlobalTest extends PHPUnit_Framework_TestCase {
315 function testRandom() {
326 # This could hypothetically fail, but it shouldn't ;)
337 $this->assertFalse(
Index: trunk/phase3/tests/DatabaseTest.php
@@ -1,25 +1,12 @@
22 <?php
33
4 -require_once( 'PHPUnit.php' );
5 -require_once( '../includes/Defines.php' );
6 -require_once( '../includes/Database.php' );
7 -require_once( '../includes/GlobalFunctions.php' );
8 -
9 -class DatabaseTest extends PHPUnit_TestCase {
 4+class DatabaseTest extends PHPUnit_Framework_TestCase {
105 var $db;
116
12 - function DatabaseTest( $name ) {
13 - $this->PHPUnit_TestCase( $name );
14 - }
15 -
167 function setUp() {
17 - $this->db = new Database();
 8+ $this->db = wfGetDB( DB_SLAVE );
189 }
1910
20 - function tearDown() {
21 - unset( $this->db );
22 - }
23 -
2411 function testAddQuotesNull() {
2512 $this->assertEquals(
2613 'NULL',
Index: trunk/phase3/tests/README
@@ -1,11 +1,9 @@
22 Some quickie unit tests done with the PHPUnit testing framework. To run the
33 test suite, run 'make test' in this dir or 'php RunTests.php'
44
5 -You can install PHPUnit via pear like this:
6 -Firstly, register phpunit channel (it only need to be done once):
 5+PHPUnit is no longer maintained by PEAR. To get the current version of
 6+PHPUnit, first uninstall any old version of PHPUnit or PHPUnit2 from PEAR,
 7+then install the current version from phpunit.de like this:
 8+
79 # pear channel-discover pear.phpunit.de
8 -Then install the package:
910 # pear install phpunit/PHPUnit
10 -
11 -Or fetch and install it manually:
12 -http://www.phpunit.de/
Index: trunk/phase3/tests/SanitizerTest.php
@@ -1,22 +1,6 @@
22 <?php
33
4 -require_once( 'PHPUnit.php' );
5 -require_once( '../includes/Defines.php' );
6 -#require_once( '../includes/Profiling.php' );
7 -require_once( '../includes/GlobalFunctions.php' );
8 -require_once( '../includes/Sanitizer.php' );
9 -
10 -class SanitizerTest extends PHPUnit_TestCase {
11 - function SanitizerTest( $name ) {
12 - $this->PHPUnit_TestCase( $name );
13 - }
14 -
15 - function setUp() {
16 - }
17 -
18 - function tearDown() {
19 - }
20 -
 4+class SanitizerTest extends PHPUnit_Framework_TestCase {
215 function testDecodeNamed() {
226 $this->assertEquals(
237 "\xc3\xa9cole",
Index: trunk/phase3/tests/ImageFunctionsTest.php
@@ -0,0 +1,48 @@
 2+<?php
 3+
 4+class ImageFunctionsTest extends PHPUnit_Framework_TestCase {
 5+ function testFitBoxWidth() {
 6+ $vals = array(
 7+ array(
 8+ 'width' => 50,
 9+ 'height' => 50,
 10+ 'tests' => array(
 11+ 50 => 50,
 12+ 17 => 17,
 13+ 18 => 18 ) ),
 14+ array(
 15+ 'width' => 366,
 16+ 'height' => 300,
 17+ 'tests' => array(
 18+ 50 => 61,
 19+ 17 => 21,
 20+ 18 => 22 ) ),
 21+ array(
 22+ 'width' => 300,
 23+ 'height' => 366,
 24+ 'tests' => array(
 25+ 50 => 41,
 26+ 17 => 14,
 27+ 18 => 15 ) ),
 28+ array(
 29+ 'width' => 100,
 30+ 'height' => 400,
 31+ 'tests' => array(
 32+ 50 => 12,
 33+ 17 => 4,
 34+ 18 => 4 ) ) );
 35+ foreach( $vals as $row ) {
 36+ extract( $row );
 37+ foreach( $tests as $max => $expected ) {
 38+ $y = round( $expected * $height / $width );
 39+ $result = wfFitBoxWidth( $width, $height, $max );
 40+ $y2 = round( $result * $height / $width );
 41+ $this->assertEquals( $expected,
 42+ $result,
 43+ "($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" );
 44+ }
 45+ }
 46+ }
 47+}
 48+
 49+?>
Property changes on: trunk/phase3/tests/ImageFunctionsTest.php
___________________________________________________________________
Added: svn:eol-style
150 + native
Added: svn:keywords
251 + Author Date Id Revision
Index: trunk/phase3/tests/SearchMySQL4Test.php
@@ -1,7 +1,5 @@
22 <?php
3 -
43 require_once( 'SearchEngineTest.php' );
5 -require_once( '../includes/SearchMySQL4.php' );
64
75 class SearchMySQL4Test extends SearchEngine_TestCase {
86 var $db;
Index: trunk/phase3/tests/run-test.php
@@ -0,0 +1,7 @@
 2+<?php
 3+
 4+require_once( dirname(__FILE__) . '/../maintenance/commandLine.inc' );
 5+ini_set( 'include_path', get_include_path() . PATH_SEPARATOR . /*$_SERVER['PHP_PEAR_INSTALL_DIR']*/ 'C:\php\pear' );
 6+require( 'PHPUnit/TextUI/Command.php' );
 7+
 8+?>
Property changes on: trunk/phase3/tests/run-test.php
___________________________________________________________________
Added: svn:eol-style
19 + native
Index: trunk/phase3/tests/ArticleTest.php
@@ -1,19 +1,8 @@
22 <?php
33
4 -require_once( 'PHPUnit.php' );
5 -require_once( '../includes/Defines.php' );
6 -require_once( '../includes/Article.php' );
7 -require_once( '../includes/Revision.php' );
8 -require_once( '../includes/ProfilerStub.php' );
9 -require_once( '../includes/normal/UtfNormal.php' );
10 -
11 -class ArticleTest extends PHPUnit_TestCase {
 4+class ArticleTest extends PHPUnit_Framework_TestCase {
125 var $saveGlobals = array();
136
14 - function ArticleTest( $name ) {
15 - $this->PHPUnit_TestCase( $name );
16 - }
17 -
187 function setUp() {
198 $globalSet = array(
209 'wgLegacyEncoding' => false,
@@ -104,20 +93,6 @@
10594 Revision::getRevisionText( $row ), "getRevisionText" );
10695 }
10796
108 - function testCompressRevisionTextLatin1() {
109 - $GLOBALS['wgUseLatin1'] = true;
110 - $row->old_text = "Wiki est l'\xe9cole superieur !";
111 - $row->old_flags = Revision::compressRevisionText( $row->old_text );
112 - $this->assertFalse( false !== strpos( $row->old_flags, 'utf-8' ),
113 - "Flags should not contain 'utf-8'" );
114 - $this->assertFalse( false !== strpos( $row->old_flags, 'gzip' ),
115 - "Flags should not contain 'gzip'" );
116 - $this->assertEquals( "Wiki est l'\xe9cole superieur !",
117 - $row->old_text, "Direct check" );
118 - $this->assertEquals( "Wiki est l'\xe9cole superieur !",
119 - Revision::getRevisionText( $row ), "getRevisionText" );
120 - }
121 -
12297 function testCompressRevisionTextUtf8Gzip() {
12398 $GLOBALS['wgCompressRevisions'] = true;
12499 $row->old_text = "Wiki est l'\xc3\xa9cole superieur !";
@@ -131,23 +106,6 @@
132107 $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !",
133108 Revision::getRevisionText( $row ), "getRevisionText" );
134109 }
135 -
136 - function testCompressRevisionTextLatin1Gzip() {
137 - $GLOBALS['wgCompressRevisions'] = true;
138 - $GLOBALS['wgUseLatin1'] = true;
139 - $row = new stdClass;
140 - $row->old_text = "Wiki est l'\xe9cole superieur !";
141 - $row->old_flags = Revision::compressRevisionText( $row->old_text );
142 - $this->assertFalse( false !== strpos( $row->old_flags, 'utf-8' ),
143 - "Flags should not contain 'utf-8'" );
144 - $this->assertTrue( false !== strpos( $row->old_flags, 'gzip' ),
145 - "Flags should contain 'gzip'" );
146 - $this->assertEquals( "Wiki est l'\xe9cole superieur !",
147 - gzinflate( $row->old_text ), "Direct check" );
148 - $this->assertEquals( "Wiki est l'\xe9cole superieur !",
149 - Revision::getRevisionText( $row ), "getRevisionText" );
150 - }
151 -
152110 }
153111
154112 ?>
Index: trunk/phase3/tests/RunTests.php
@@ -1,26 +1,12 @@
22 <?php
33
4 -if( php_sapi_name() != 'cli' ) {
5 - echo 'Must be run from the command line.';
6 - die( -1 );
7 -}
 4+die( "This is broken, use run-test.php for now.\n" );
85
 6+require_once( dirname( __FILE__ ) . '/../maintenance/commandLine.inc' );
 7+ini_set( 'include_path', get_include_path() . PATH_SEPARATOR . /*$_SERVER['PHP_PEAR_INSTALL_DIR']*/ 'C:\php\pear' );
98 error_reporting( E_ALL );
10 -define( "MEDIAWIKI", true );
 9+require_once( 'PHPUnit/Framework.php' );
1110
12 -set_include_path( get_include_path() . PATH_SEPARATOR . 'PHPUnit' );
13 -set_include_path( get_include_path() . PATH_SEPARATOR . '..' );
14 -
15 -// Error handling when requiring PHPUnit.php
16 -function phpunitErrorHandler( $erno, $errstr, $errfile, $errline) {
17 - echo "Unable to include PHPUnit.php, you should install it first (see README).\n";
18 - exit(1);
19 -}
20 -
21 -set_error_handler('phpunitErrorHandler');
22 -require_once( 'PHPUnit.php' );
23 -restore_error_handler();
24 -
2511 $testOptions = array(
2612 'mysql4' => array(
2713 'server' => null,
@@ -34,10 +20,6 @@
3521 'database' => null ),
3622 );
3723
38 -if( file_exists( 'LocalTestSettings.php' ) ) {
39 - include( './LocalTestSettings.php' );
40 -}
41 -
4224 $tests = array(
4325 'GlobalTest',
4426 'DatabaseTest',
@@ -47,14 +29,14 @@
4830 'ImageTest'
4931 );
5032
51 -if( isset( $_SERVER['argv'][1] ) ) {
 33+if( count( $args ) ) {
5234 // to override...
53 - $tests = array( $_SERVER['argv'][1] );
 35+ $tests = $args;
5436 }
5537
5638 foreach( $tests as $test ) {
5739 require_once( $test . '.php' );
58 - $suite = new PHPUnit_TestSuite( $test );
 40+ $suite = new PHPUnit_Framework_TestSuite( $test );
5941 $result = PHPUnit::run( $suite );
6042 echo $result->toString();
6143 }
Index: trunk/phase3/includes/Image.php
@@ -1,2154 +0,0 @@
2 -<?php
3 -/**
4 - */
5 -
6 -/**
7 - * NOTE FOR WINDOWS USERS:
8 - * To enable EXIF functions, add the folloing lines to the
9 - * "Windows extensions" section of php.ini:
10 - *
11 - * extension=extensions/php_mbstring.dll
12 - * extension=extensions/php_exif.dll
13 - */
14 -
15 -/**
16 - * Bump this number when serialized cache records may be incompatible.
17 - */
18 -define( 'MW_IMAGE_VERSION', 2 );
19 -
20 -/**
21 - * Class to represent an image
22 - *
23 - * Provides methods to retrieve paths (physical, logical, URL),
24 - * to generate thumbnails or for uploading.
25 - *
26 - * @addtogroup Media
27 - */
28 -class Image
29 -{
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 -
36 - /**#@+
37 - * @private
38 - */
39 - var $name, # name of the image (constructor)
40 - $imagePath, # Path of the image (loadFromXxx)
41 - $url, # Image URL (accessor)
42 - $title, # Title object for this image (constructor)
43 - $fileExists, # does the image file exist on disk? (loadFromXxx)
44 - $fromSharedDirectory, # load this image from $wgSharedUploadDirectory (loadFromXxx)
45 - $historyLine, # Number of line to return by nextHistoryLine() (constructor)
46 - $historyRes, # result of the query for the image's history (nextHistoryLine)
47 - $width, # \
48 - $height, # |
49 - $bits, # --- returned by getimagesize (loadFromXxx)
50 - $attr, # /
51 - $type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
52 - $mime, # MIME type, determined by MimeMagic::guessMimeType
53 - $extension, # The file extension (constructor)
54 - $size, # Size in bytes (loadFromXxx)
55 - $metadata, # Metadata
56 - $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
57 - $page, # Page to render when creating thumbnails
58 - $lastError; # Error string associated with a thumbnail display error
59 -
60 -
61 - /**#@-*/
62 -
63 - /**
64 - * Create an Image object from an image name
65 - *
66 - * @param string $name name of the image, used to create a title object using Title::makeTitleSafe
67 - * @return Image
68 - * @public
69 - */
70 - public static function newFromName( $name ) {
71 - $title = Title::makeTitleSafe( NS_IMAGE, $name );
72 - if ( is_object( $title ) ) {
73 - return new Image( $title );
74 - } else {
75 - return NULL;
76 - }
77 - }
78 -
79 - /**
80 - * Obsolete factory function, use constructor
81 - * @param Title $title
82 - * @return Image
83 - * @deprecated
84 - */
85 - function newFromTitle( $title ) {
86 - return new Image( $title );
87 - }
88 -
89 - /**
90 - * Constructor
91 - * @param Title $title
92 - * @return void
93 - */
94 - function Image( $title ) {
95 - if( !is_object( $title ) ) {
96 - throw new MWException( 'Image constructor given bogus title.' );
97 - }
98 - $this->title =& $title;
99 - $this->name = $title->getDBkey();
100 - $this->metadata = '';
101 -
102 - $n = strrpos( $this->name, '.' );
103 - $this->extension = Image::normalizeExtension( $n ?
104 - substr( $this->name, $n + 1 ) : '' );
105 - $this->historyLine = 0;
106 -
107 - $this->dataLoaded = false;
108 - }
109 -
110 - /**
111 - * Normalize a file extension to the common form, and ensure it's clean.
112 - * Extensions with non-alphanumeric characters will be discarded.
113 - *
114 - * @param string $ext (without the .)
115 - * @return string
116 - */
117 - static function normalizeExtension( $ext ) {
118 - $lower = strtolower( $ext );
119 - $squish = array(
120 - 'htm' => 'html',
121 - 'jpeg' => 'jpg',
122 - 'mpeg' => 'mpg',
123 - 'tiff' => 'tif' );
124 - if( isset( $squish[$lower] ) ) {
125 - return $squish[$lower];
126 - } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
127 - return $lower;
128 - } else {
129 - return '';
130 - }
131 - }
132 -
133 - /**
134 - * Get the memcached keys
135 - * @return array[int]mixed Returns an array, first element is the local cache key, second is the shared cache key, if there is one
136 - */
137 - function getCacheKeys( ) {
138 - global $wgUseSharedUploads, $wgSharedUploadDBname, $wgCacheSharedUploads;
139 -
140 - $hashedName = md5($this->name);
141 - $keys = array( wfMemcKey( 'Image', $hashedName ) );
142 - if ( $wgUseSharedUploads && $wgSharedUploadDBname && $wgCacheSharedUploads ) {
143 - $keys[] = wfForeignMemcKey( $wgSharedUploadDBname, false, 'Image', $hashedName );
144 - }
145 - return $keys;
146 - }
147 -
148 - /**
149 - * Try to load image metadata from memcached. Returns true on success.
150 - */
151 - function loadFromCache() {
152 - global $wgUseSharedUploads, $wgMemc;
153 - wfProfileIn( __METHOD__ );
154 - $this->dataLoaded = false;
155 - $keys = $this->getCacheKeys();
156 - $cachedValues = $wgMemc->get( $keys[0] );
157 -
158 - // Check if the key existed and belongs to this version of MediaWiki
159 - if (!empty($cachedValues) && is_array($cachedValues)
160 - && isset($cachedValues['version']) && ( $cachedValues['version'] == MW_IMAGE_VERSION )
161 - && isset( $cachedValues['mime'] ) && isset( $cachedValues['metadata'] ) )
162 - {
163 - if ( $wgUseSharedUploads && $cachedValues['fromShared']) {
164 - # if this is shared file, we need to check if image
165 - # in shared repository has not changed
166 - if ( isset( $keys[1] ) ) {
167 - $commonsCachedValues = $wgMemc->get( $keys[1] );
168 - if (!empty($commonsCachedValues) && is_array($commonsCachedValues)
169 - && isset($commonsCachedValues['version'])
170 - && ( $commonsCachedValues['version'] == MW_IMAGE_VERSION )
171 - && isset($commonsCachedValues['mime'])) {
172 - wfDebug( "Pulling image metadata from shared repository cache\n" );
173 - $this->name = $commonsCachedValues['name'];
174 - $this->imagePath = $commonsCachedValues['imagePath'];
175 - $this->fileExists = $commonsCachedValues['fileExists'];
176 - $this->width = $commonsCachedValues['width'];
177 - $this->height = $commonsCachedValues['height'];
178 - $this->bits = $commonsCachedValues['bits'];
179 - $this->type = $commonsCachedValues['type'];
180 - $this->mime = $commonsCachedValues['mime'];
181 - $this->metadata = $commonsCachedValues['metadata'];
182 - $this->size = $commonsCachedValues['size'];
183 - $this->fromSharedDirectory = true;
184 - $this->dataLoaded = true;
185 - $this->imagePath = $this->getFullPath(true);
186 - }
187 - }
188 - } else {
189 - wfDebug( "Pulling image metadata from local cache\n" );
190 - $this->name = $cachedValues['name'];
191 - $this->imagePath = $cachedValues['imagePath'];
192 - $this->fileExists = $cachedValues['fileExists'];
193 - $this->width = $cachedValues['width'];
194 - $this->height = $cachedValues['height'];
195 - $this->bits = $cachedValues['bits'];
196 - $this->type = $cachedValues['type'];
197 - $this->mime = $cachedValues['mime'];
198 - $this->metadata = $cachedValues['metadata'];
199 - $this->size = $cachedValues['size'];
200 - $this->fromSharedDirectory = false;
201 - $this->dataLoaded = true;
202 - $this->imagePath = $this->getFullPath();
203 - }
204 - }
205 - if ( $this->dataLoaded ) {
206 - wfIncrStats( 'image_cache_hit' );
207 - } else {
208 - wfIncrStats( 'image_cache_miss' );
209 - }
210 -
211 - wfProfileOut( __METHOD__ );
212 - return $this->dataLoaded;
213 - }
214 -
215 - /**
216 - * Save the image metadata to memcached
217 - */
218 - function saveToCache() {
219 - global $wgMemc, $wgUseSharedUploads;
220 - $this->load();
221 - $keys = $this->getCacheKeys();
222 - // We can't cache negative metadata for non-existent files,
223 - // because if the file later appears in commons, the local
224 - // keys won't be purged.
225 - if ( $this->fileExists || !$wgUseSharedUploads ) {
226 - $cachedValues = array(
227 - 'version' => MW_IMAGE_VERSION,
228 - 'name' => $this->name,
229 - 'imagePath' => $this->imagePath,
230 - 'fileExists' => $this->fileExists,
231 - 'fromShared' => $this->fromSharedDirectory,
232 - 'width' => $this->width,
233 - 'height' => $this->height,
234 - 'bits' => $this->bits,
235 - 'type' => $this->type,
236 - 'mime' => $this->mime,
237 - 'metadata' => $this->metadata,
238 - 'size' => $this->size );
239 -
240 - $wgMemc->set( $keys[0], $cachedValues, 60 * 60 * 24 * 7 ); // A week
241 - } else {
242 - // However we should clear them, so they aren't leftover
243 - // if we've deleted the file.
244 - $wgMemc->delete( $keys[0] );
245 - }
246 - }
247 -
248 - /**
249 - * Load metadata from the file itself
250 - */
251 - function loadFromFile() {
252 - global $wgUseSharedUploads, $wgSharedUploadDirectory, $wgContLang;
253 - wfProfileIn( __METHOD__ );
254 - $this->imagePath = $this->getFullPath();
255 - $this->fileExists = file_exists( $this->imagePath );
256 - $this->fromSharedDirectory = false;
257 - $gis = array();
258 -
259 - if (!$this->fileExists) wfDebug(__METHOD__.': '.$this->imagePath." not found locally!\n");
260 -
261 - # If the file is not found, and a shared upload directory is used, look for it there.
262 - if (!$this->fileExists && $wgUseSharedUploads && $wgSharedUploadDirectory) {
263 - # In case we're on a wgCapitalLinks=false wiki, we
264 - # capitalize the first letter of the filename before
265 - # looking it up in the shared repository.
266 - $sharedImage = Image::newFromName( $wgContLang->ucfirst($this->name) );
267 - $this->fileExists = $sharedImage && file_exists( $sharedImage->getFullPath(true) );
268 - if ( $this->fileExists ) {
269 - $this->name = $sharedImage->name;
270 - $this->imagePath = $this->getFullPath(true);
271 - $this->fromSharedDirectory = true;
272 - }
273 - }
274 -
275 -
276 - if ( $this->fileExists ) {
277 - $magic=& MimeMagic::singleton();
278 -
279 - $this->mime = $magic->guessMimeType($this->imagePath,true);
280 - $this->type = $magic->getMediaType($this->imagePath,$this->mime);
281 - $handler = MediaHandler::getHandler( $this->mime );
282 -
283 - # Get size in bytes
284 - $this->size = filesize( $this->imagePath );
285 -
286 - # Height, width and metadata
287 - if ( $handler ) {
288 - $gis = $handler->getImageSize( $this, $this->imagePath );
289 - $this->metadata = $handler->getMetadata( $this, $this->imagePath );
290 - } else {
291 - $gis = false;
292 - $this->metadata = '';
293 - }
294 -
295 - wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n");
296 - }
297 - else {
298 - $this->mime = NULL;
299 - $this->type = MEDIATYPE_UNKNOWN;
300 - $this->metadata = '';
301 - wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n");
302 - }
303 -
304 - if( $gis ) {
305 - $this->width = $gis[0];
306 - $this->height = $gis[1];
307 - } else {
308 - $this->width = 0;
309 - $this->height = 0;
310 - }
311 -
312 - #NOTE: $gis[2] contains a code for the image type. This is no longer used.
313 -
314 - #NOTE: we have to set this flag early to avoid load() to be called
315 - # be some of the functions below. This may lead to recursion or other bad things!
316 - # as ther's only one thread of execution, this should be safe anyway.
317 - $this->dataLoaded = true;
318 -
319 - if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
320 - else $this->bits = 0;
321 -
322 - wfProfileOut( __METHOD__ );
323 - }
324 -
325 - /**
326 - * Load image metadata from the DB
327 - */
328 - function loadFromDB() {
329 - global $wgUseSharedUploads, $wgSharedUploadDBname, $wgSharedUploadDBprefix, $wgContLang;
330 - wfProfileIn( __METHOD__ );
331 -
332 - $dbr = wfGetDB( DB_SLAVE );
333 -
334 - $row = $dbr->selectRow( 'image',
335 - array( 'img_size', 'img_width', 'img_height', 'img_bits',
336 - 'img_media_type', 'img_major_mime', 'img_minor_mime', 'img_metadata' ),
337 - array( 'img_name' => $this->name ), __METHOD__ );
338 - if ( $row ) {
339 - $this->fromSharedDirectory = false;
340 - $this->fileExists = true;
341 - $this->loadFromRow( $row );
342 - $this->imagePath = $this->getFullPath();
343 - // Check for rows from a previous schema, quietly upgrade them
344 - $this->maybeUpgradeRow();
345 - } elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) {
346 - # In case we're on a wgCapitalLinks=false wiki, we
347 - # capitalize the first letter of the filename before
348 - # looking it up in the shared repository.
349 - $name = $wgContLang->ucfirst($this->name);
350 - $dbc = Image::getCommonsDB();
351 -
352 - $row = $dbc->selectRow( "`$wgSharedUploadDBname`.{$wgSharedUploadDBprefix}image",
353 - array(
354 - 'img_size', 'img_width', 'img_height', 'img_bits',
355 - 'img_media_type', 'img_major_mime', 'img_minor_mime', 'img_metadata' ),
356 - array( 'img_name' => $name ), __METHOD__ );
357 - if ( $row ) {
358 - $this->fromSharedDirectory = true;
359 - $this->fileExists = true;
360 - $this->imagePath = $this->getFullPath(true);
361 - $this->name = $name;
362 - $this->loadFromRow( $row );
363 -
364 - // Check for rows from a previous schema, quietly upgrade them
365 - $this->maybeUpgradeRow();
366 - }
367 - }
368 -
369 - if ( !$row ) {
370 - $this->size = 0;
371 - $this->width = 0;
372 - $this->height = 0;
373 - $this->bits = 0;
374 - $this->type = 0;
375 - $this->fileExists = false;
376 - $this->fromSharedDirectory = false;
377 - $this->metadata = '';
378 - $this->mime = false;
379 - }
380 -
381 - # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
382 - $this->dataLoaded = true;
383 - wfProfileOut( __METHOD__ );
384 - }
385 -
386 - /*
387 - * Load image metadata from a DB result row
388 - */
389 - function loadFromRow( &$row ) {
390 - $this->size = $row->img_size;
391 - $this->width = $row->img_width;
392 - $this->height = $row->img_height;
393 - $this->bits = $row->img_bits;
394 - $this->type = $row->img_media_type;
395 -
396 - $major= $row->img_major_mime;
397 - $minor= $row->img_minor_mime;
398 -
399 - if (!$major) $this->mime = "unknown/unknown";
400 - else {
401 - if (!$minor) $minor= "unknown";
402 - $this->mime = $major.'/'.$minor;
403 - }
404 - $this->metadata = $row->img_metadata;
405 -
406 - $this->dataLoaded = true;
407 - }
408 -
409 - /**
410 - * Load image metadata from cache or DB, unless already loaded
411 - */
412 - function load() {
413 - global $wgSharedUploadDBname, $wgUseSharedUploads;
414 - if ( !$this->dataLoaded ) {
415 - if ( !$this->loadFromCache() ) {
416 - $this->loadFromDB();
417 - if ( !$wgSharedUploadDBname && $wgUseSharedUploads ) {
418 - $this->loadFromFile();
419 - } elseif ( $this->fileExists || !$wgUseSharedUploads ) {
420 - // We can do negative caching for local images, because the cache
421 - // will be purged on upload. But we can't do it when shared images
422 - // are enabled, since updates to that won't purge foreign caches.
423 - $this->saveToCache();
424 - }
425 - }
426 - $this->dataLoaded = true;
427 - }
428 - }
429 -
430 - /**
431 - * Upgrade a row if it needs it
432 - * @return void
433 - */
434 - function maybeUpgradeRow() {
435 - if ( is_null($this->type) || $this->mime == 'image/svg' ) {
436 - $this->upgradeRow();
437 - } else {
438 - $handler = $this->getHandler();
439 - if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
440 - $this->upgradeRow();
441 - }
442 - }
443 - }
444 -
445 - /**
446 - * Fix assorted version-related problems with the image row by reloading it from the file
447 - */
448 - function upgradeRow() {
449 - global $wgDBname, $wgSharedUploadDBname;
450 - wfProfileIn( __METHOD__ );
451 -
452 - $this->loadFromFile();
453 -
454 - if ( $this->fromSharedDirectory ) {
455 - if ( !$wgSharedUploadDBname ) {
456 - wfProfileOut( __METHOD__ );
457 - return;
458 - }
459 -
460 - // Write to the other DB using selectDB, not database selectors
461 - // This avoids breaking replication in MySQL
462 - $dbw = Image::getCommonsDB();
463 - } else {
464 - $dbw = wfGetDB( DB_MASTER );
465 - }
466 -
467 - list( $major, $minor ) = self::splitMime( $this->mime );
468 -
469 - wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n");
470 -
471 - $dbw->update( 'image',
472 - array(
473 - 'img_width' => $this->width,
474 - 'img_height' => $this->height,
475 - 'img_bits' => $this->bits,
476 - 'img_media_type' => $this->type,
477 - 'img_major_mime' => $major,
478 - 'img_minor_mime' => $minor,
479 - 'img_metadata' => $this->metadata,
480 - ), array( 'img_name' => $this->name ), __METHOD__
481 - );
482 - if ( $this->fromSharedDirectory ) {
483 - $dbw->selectDB( $wgDBname );
484 - }
485 - wfProfileOut( __METHOD__ );
486 - }
487 -
488 - /**
489 - * Split an internet media type into its two components; if not
490 - * a two-part name, set the minor type to 'unknown'.
491 - *
492 - * @param string $mime "text/html" etc
493 - * @return array ("text", "html") etc
494 - */
495 - static function splitMime( $mime ) {
496 - if( strpos( $mime, '/' ) !== false ) {
497 - return explode( '/', $mime, 2 );
498 - } else {
499 - return array( $mime, 'unknown' );
500 - }
501 - }
502 -
503 - /**
504 - * Return the name of this image
505 - * @public
506 - */
507 - function getName() {
508 - return $this->name;
509 - }
510 -
511 - /**
512 - * Return the associated title object
513 - * @public
514 - */
515 - function getTitle() {
516 - return $this->title;
517 - }
518 -
519 - /**
520 - * Return the URL of the image file
521 - * @public
522 - */
523 - function getURL() {
524 - if ( !$this->url ) {
525 - $this->load();
526 - if($this->fileExists) {
527 - $this->url = Image::imageUrl( $this->name, $this->fromSharedDirectory );
528 - } else {
529 - $this->url = '';
530 - }
531 - }
532 - return $this->url;
533 - }
534 -
535 - function getViewURL() {
536 - if( $this->mustRender()) {
537 - if( $this->canRender() ) {
538 - return $this->createThumb( $this->getWidth() );
539 - }
540 - else {
541 - wfDebug('Image::getViewURL(): supposed to render '.$this->name.' ('.$this->mime."), but can't!\n");
542 - return $this->getURL(); #hm... return NULL?
543 - }
544 - } else {
545 - return $this->getURL();
546 - }
547 - }
548 -
549 - /**
550 - * Return the image path of the image in the
551 - * local file system as an absolute path
552 - * @public
553 - */
554 - function getImagePath() {
555 - $this->load();
556 - return $this->imagePath;
557 - }
558 -
559 - /**
560 - * @return mixed Return the width of the image; returns false on error.
561 - * @param int $page Page number to find the width of.
562 - * @public
563 - */
564 - function getWidth( $page = 1 ) {
565 - $this->load();
566 - if ( $this->isMultipage() ) {
567 - $dim = $this->getHandler()->getPageDimensions( $this, $page );
568 - if ( $dim ) {
569 - return $dim['width'];
570 - } else {
571 - return false;
572 - }
573 - } else {
574 - return $this->width;
575 - }
576 - }
577 -
578 - /**
579 - * @return mixed Return the height of the image; Returns false on error.
580 - * @param int $page Page number to find the height of.
581 - * @public
582 - */
583 - function getHeight( $page = 1 ) {
584 - $this->load();
585 - if ( $this->isMultipage() ) {
586 - $dim = $this->getHandler()->getPageDimensions( $this, $page );
587 - if ( $dim ) {
588 - return $dim['height'];
589 - } else {
590 - return false;
591 - }
592 - } else {
593 - return $this->height;
594 - }
595 - }
596 -
597 - /**
598 - * Get handler-specific metadata
599 - */
600 - function getMetadata() {
601 - $this->load();
602 - return $this->metadata;
603 - }
604 -
605 - /**
606 - * @return int the size of the image file, in bytes
607 - * @public
608 - */
609 - function getSize() {
610 - $this->load();
611 - return $this->size;
612 - }
613 -
614 - /**
615 - * @return string the mime type of the file.
616 - */
617 - function getMimeType() {
618 - $this->load();
619 - return $this->mime;
620 - }
621 -
622 - /**
623 - * Return the type of the media in the file.
624 - * Use the value returned by this function with the MEDIATYPE_xxx constants.
625 - */
626 - function getMediaType() {
627 - $this->load();
628 - return $this->type;
629 - }
630 -
631 - /**
632 - * Checks if the file can be presented to the browser as a bitmap.
633 - *
634 - * Currently, this checks if the file is an image format
635 - * that can be converted to a format
636 - * supported by all browsers (namely GIF, PNG and JPEG),
637 - * or if it is an SVG image and SVG conversion is enabled.
638 - *
639 - * @todo remember the result of this check.
640 - * @return boolean
641 - */
642 - function canRender() {
643 - $handler = $this->getHandler();
644 - return $handler && $handler->canRender();
645 - }
646 -
647 - /**
648 - * Return true if the file is of a type that can't be directly
649 - * rendered by typical browsers and needs to be re-rasterized.
650 - *
651 - * This returns true for everything but the bitmap types
652 - * supported by all browsers, i.e. JPEG; GIF and PNG. It will
653 - * also return true for any non-image formats.
654 - *
655 - * @return bool
656 - */
657 - function mustRender() {
658 - $handler = $this->getHandler();
659 - return $handler && $handler->mustRender();
660 - }
661 -
662 - /**
663 - * Determines if this media file may be shown inline on a page.
664 - *
665 - * This is currently synonymous to canRender(), but this could be
666 - * extended to also allow inline display of other media,
667 - * like flash animations or videos. If you do so, please keep in mind that
668 - * that could be a security risk.
669 - */
670 - function allowInlineDisplay() {
671 - return $this->canRender();
672 - }
673 -
674 - /**
675 - * Determines if this media file is in a format that is unlikely to
676 - * contain viruses or malicious content. It uses the global
677 - * $wgTrustedMediaFormats list to determine if the file is safe.
678 - *
679 - * This is used to show a warning on the description page of non-safe files.
680 - * It may also be used to disallow direct [[media:...]] links to such files.
681 - *
682 - * Note that this function will always return true if allowInlineDisplay()
683 - * or isTrustedFile() is true for this file.
684 - *
685 - * @return boolean
686 - */
687 - function isSafeFile() {
688 - if ($this->allowInlineDisplay()) return true;
689 - if ($this->isTrustedFile()) return true;
690 -
691 - global $wgTrustedMediaFormats;
692 -
693 - $type= $this->getMediaType();
694 - $mime= $this->getMimeType();
695 - #wfDebug("Image::isSafeFile: type= $type, mime= $mime\n");
696 -
697 - if (!$type || $type===MEDIATYPE_UNKNOWN) return false; #unknown type, not trusted
698 - if ( in_array( $type, $wgTrustedMediaFormats) ) return true;
699 -
700 - if ($mime==="unknown/unknown") return false; #unknown type, not trusted
701 - if ( in_array( $mime, $wgTrustedMediaFormats) ) return true;
702 -
703 - return false;
704 - }
705 -
706 - /**
707 - * Returns true if the file is flagged as trusted. Files flagged that way
708 - * can be linked to directly, even if that is not allowed for this type of
709 - * file normally.
710 - *
711 - * This is a dummy function right now and always returns false. It could be
712 - * implemented to extract a flag from the database. The trusted flag could be
713 - * set on upload, if the user has sufficient privileges, to bypass script-
714 - * and html-filters. It may even be coupled with cryptographics signatures
715 - * or such.
716 - * @return boolean
717 - */
718 - function isTrustedFile() {
719 - #this could be implemented to check a flag in the database,
720 - #look for signatures, etc
721 - return false;
722 - }
723 -
724 - /**
725 - * Return the escapeLocalURL of this image
726 - * @param string $query URL query string
727 - * @public
728 - */
729 - function getEscapeLocalURL( $query=false) {
730 - return $this->getTitle()->escapeLocalURL( $query );
731 - }
732 -
733 - /**
734 - * Return the escapeFullURL of this image
735 - * @public
736 - */
737 - function getEscapeFullURL() {
738 - $this->getTitle();
739 - return $this->title->escapeFullURL();
740 - }
741 -
742 - /**
743 - * Return the URL of an image, provided its name.
744 - *
745 - * @param string $name Name of the image, without the leading "Image:"
746 - * @param boolean $fromSharedDirectory Should this be in $wgSharedUploadPath?
747 - * @return string URL of $name image
748 - * @public
749 - */
750 - static function imageUrl( $name, $fromSharedDirectory = false ) {
751 - global $wgUploadPath,$wgUploadBaseUrl,$wgSharedUploadPath;
752 - if($fromSharedDirectory) {
753 - $base = '';
754 - $path = $wgSharedUploadPath;
755 - } else {
756 - $base = $wgUploadBaseUrl;
757 - $path = $wgUploadPath;
758 - }
759 - $url = "{$base}{$path}" . wfGetHashPath($name, $fromSharedDirectory) . "{$name}";
760 - return wfUrlencode( $url );
761 - }
762 -
763 - /**
764 - * Returns true if the image file exists on disk.
765 - * @return boolean Whether image file exist on disk.
766 - * @public
767 - */
768 - function exists() {
769 - $this->load();
770 - return $this->fileExists;
771 - }
772 -
773 - /**
774 - * @todo document
775 - * @param string $thumbName
776 - * @param string $subdir
777 - * @return string
778 - * @private
779 - */
780 - function thumbUrlFromName( $thumbName, $subdir = 'thumb' ) {
781 - global $wgUploadPath, $wgUploadBaseUrl, $wgSharedUploadPath;
782 - if($this->fromSharedDirectory) {
783 - $base = '';
784 - $path = $wgSharedUploadPath;
785 - } else {
786 - $base = $wgUploadBaseUrl;
787 - $path = $wgUploadPath;
788 - }
789 - if ( Image::isHashed( $this->fromSharedDirectory ) ) {
790 - $hashdir = wfGetHashPath($this->name, $this->fromSharedDirectory) .
791 - wfUrlencode( $this->name );
792 - } else {
793 - $hashdir = '';
794 - }
795 - $url = "{$base}{$path}/{$subdir}{$hashdir}/" . wfUrlencode( $thumbName );
796 - return $url;
797 - }
798 -
799 - /**
800 - * @deprecated Use $image->transform()->getUrl() or thumbUrlFromName()
801 - */
802 - function thumbUrl( $width, $subdir = 'thumb' ) {
803 - $name = $this->thumbName( array( 'width' => $width ) );
804 - if ( strval( $name ) !== '' ) {
805 - return array( false, $this->thumbUrlFromName( $name, $subdir ) );
806 - } else {
807 - return array( false, false );
808 - }
809 - }
810 -
811 - /**
812 - * @return mixed
813 - */
814 - function getTransformScript() {
815 - global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
816 - if ( $this->fromSharedDirectory ) {
817 - $script = $wgSharedThumbnailScriptPath;
818 - } else {
819 - $script = $wgThumbnailScriptPath;
820 - }
821 - if ( $script ) {
822 - return "$script?f=" . urlencode( $this->name );
823 - } else {
824 - return false;
825 - }
826 - }
827 -
828 - /**
829 - * Get a ThumbnailImage which is the same size as the source
830 - * @param mixed $page
831 - * @return MediaTransformOutput
832 - */
833 - function getUnscaledThumb( $page = false ) {
834 - if ( $page ) {
835 - $params = array(
836 - 'page' => $page,
837 - 'width' => $this->getWidth( $page )
838 - );
839 - } else {
840 - $params = array( 'width' => $this->getWidth() );
841 - }
842 - return $this->transform( $params );
843 - }
844 -
845 - /**
846 - * Return the file name of a thumbnail with the specified parameters
847 - *
848 - * @param array $params Handler-specific parameters
849 - * @return string file name of a thumbnail with the specified parameters
850 - * @private
851 - */
852 - function thumbName( $params ) {
853 - $handler = $this->getHandler();
854 - if ( !$handler ) {
855 - return null;
856 - }
857 - list( $thumbExt, /* $thumbMime */ ) = self::getThumbType( $this->extension, $this->mime );
858 - $thumbName = $handler->makeParamString( $params ) . '-' . $this->name;
859 - if ( $thumbExt != $this->extension ) {
860 - $thumbName .= ".$thumbExt";
861 - }
862 - return $thumbName;
863 - }
864 -
865 - /**
866 - * Create a thumbnail of the image having the specified width/height.
867 - * The thumbnail will not be created if the width is larger than the
868 - * image's width. Let the browser do the scaling in this case.
869 - * The thumbnail is stored on disk and is only computed if the thumbnail
870 - * file does not exist OR if it is older than the image.
871 - * Returns the URL.
872 - *
873 - * Keeps aspect ratio of original image. If both width and height are
874 - * specified, the generated image will be no bigger than width x height,
875 - * and will also have correct aspect ratio.
876 - *
877 - * @param integer $width maximum width of the generated thumbnail
878 - * @param integer $height maximum height of the image (optional)
879 - * @public
880 - */
881 - function createThumb( $width, $height = -1 ) {
882 - $params = array( 'width' => $width );
883 - if ( $height != -1 ) {
884 - $params['height'] = $height;
885 - }
886 - $thumb = $this->transform( $params );
887 - if( is_null( $thumb ) || $thumb->isError() ) return '';
888 - return $thumb->getUrl();
889 - }
890 -
891 - /**
892 - * As createThumb, but returns a ThumbnailImage object. This can
893 - * provide access to the actual file, the real size of the thumb,
894 - * and can produce a convenient <img> tag for you.
895 - *
896 - * For non-image formats, this may return a filetype-specific icon.
897 - *
898 - * @param integer $width maximum width of the generated thumbnail
899 - * @param integer $height maximum height of the image (optional)
900 - * @param boolean $render True to render the thumbnail if it doesn't exist,
901 - * false to just return the URL
902 - *
903 - * @return ThumbnailImage or null on failure
904 - * @public
905 - *
906 - * @deprecated use transform()
907 - */
908 - function getThumbnail( $width, $height=-1, $render = true ) {
909 - $params = array( 'width' => $width );
910 - if ( $height != -1 ) {
911 - $params['height'] = $height;
912 - }
913 - $flags = $render ? self::RENDER_NOW : 0;
914 - return $this->transform( $params, $flags );
915 - }
916 -
917 - /**
918 - * Transform a media file
919 - *
920 - * @param array[string]mixed $params An associative array of handler-specific parameters.
921 - * Typical keys are width, height and page.
922 - * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
923 - * @return MediaTransformOutput
924 - */
925 - function transform( $params, $flags = 0 ) {
926 - global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors;
927 -
928 - wfProfileIn( __METHOD__ );
929 - do {
930 - $handler = $this->getHandler();
931 - if ( !$handler || !$handler->canRender() ) {
932 - // not a bitmap or renderable image, don't try.
933 - $thumb = $this->iconThumb();
934 - break;
935 - }
936 -
937 - $script = $this->getTransformScript();
938 - if ( $script && !($flags & self::RENDER_NOW) ) {
939 - // Use a script to transform on client request
940 - $thumb = $handler->getScriptedTransform( $this, $script, $params );
941 - break;
942 - }
943 -
944 - $normalisedParams = $params;
945 - $handler->normaliseParams( $this, $normalisedParams );
946 - $thumbName = $this->thumbName( $normalisedParams );
947 - $thumbPath = wfImageThumbDir( $this->name, $this->fromSharedDirectory ) . "/$thumbName";
948 - $thumbUrl = $this->thumbUrlFromName( $thumbName );
949 -
950 -
951 - if ( !$wgGenerateThumbnailOnParse && !($flags & self::RENDER_NOW ) ) {
952 - $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
953 - break;
954 - }
955 -
956 - wfDebug( "Doing stat for $thumbPath\n" );
957 - $this->migrateThumbFile( $thumbName );
958 - if ( file_exists( $thumbPath ) ) {
959 - $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
960 - break;
961 - }
962 -
963 - $thumb = $handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
964 -
965 - // Ignore errors if requested
966 - if ( !$thumb ) {
967 - $thumb = null;
968 - } elseif ( $thumb->isError() ) {
969 - $this->lastError = $thumb->toText();
970 - if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
971 - $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
972 - }
973 - }
974 -
975 - if ( $wgUseSquid ) {
976 - wfPurgeSquidServers( array( $thumbUrl ) );
977 - }
978 - } while (false);
979 -
980 - wfProfileOut( __METHOD__ );
981 - return $thumb;
982 - }
983 -
984 - /**
985 - * Fix thumbnail files from 1.4 or before, with extreme prejudice
986 - * @param string $thumbName File name of thumbnail.
987 - * @return void
988 - */
989 - function migrateThumbFile( $thumbName ) {
990 - $thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory );
991 - $thumbPath = "$thumbDir/$thumbName";
992 - if ( is_dir( $thumbPath ) ) {
993 - // Directory where file should be
994 - // This happened occasionally due to broken migration code in 1.5
995 - // Rename to broken-*
996 - global $wgUploadDirectory;
997 - for ( $i = 0; $i < 100 ; $i++ ) {
998 - $broken = "$wgUploadDirectory/broken-$i-$thumbName";
999 - if ( !file_exists( $broken ) ) {
1000 - rename( $thumbPath, $broken );
1001 - break;
1002 - }
1003 - }
1004 - // Doesn't exist anymore
1005 - clearstatcache();
1006 - }
1007 - if ( is_file( $thumbDir ) ) {
1008 - // File where directory should be
1009 - unlink( $thumbDir );
1010 - // Doesn't exist anymore
1011 - clearstatcache();
1012 - }
1013 - }
1014 -
1015 - /**
1016 - * Get a MediaHandler instance for this image
1017 - */
1018 - function getHandler() {
1019 - return MediaHandler::getHandler( $this->getMimeType() );
1020 - }
1021 -
1022 - /**
1023 - * Get a ThumbnailImage representing a file type icon
1024 - * @return ThumbnailImage
1025 - */
1026 - function iconThumb() {
1027 - global $wgStylePath, $wgStyleDirectory;
1028 -
1029 - $icons = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
1030 - foreach( $icons as $icon ) {
1031 - $path = '/common/images/icons/' . $icon;
1032 - $filepath = $wgStyleDirectory . $path;
1033 - if( file_exists( $filepath ) ) {
1034 - return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
1035 - }
1036 - }
1037 - return null;
1038 - }
1039 -
1040 - /**
1041 - * Get last thumbnailing error.
1042 - * Largely obsolete.
1043 - * @return mixed
1044 - */
1045 - function getLastError() {
1046 - return $this->lastError;
1047 - }
1048 -
1049 - /**
1050 - * Get all thumbnail names previously generated for this image
1051 - * @param boolean $shared
1052 - * @return array[]string
1053 - */
1054 - function getThumbnails( $shared = false ) {
1055 - if ( Image::isHashed( $shared ) ) {
1056 - $this->load();
1057 - $files = array();
1058 - $dir = wfImageThumbDir( $this->name, $shared );
1059 -
1060 - if ( is_dir( $dir ) ) {
1061 - $handle = opendir( $dir );
1062 -
1063 - if ( $handle ) {
1064 - while ( false !== ( $file = readdir($handle) ) ) {
1065 - if ( $file[0] != '.' ) {
1066 - $files[] = $file;
1067 - }
1068 - }
1069 - closedir( $handle );
1070 - }
1071 - }
1072 - } else {
1073 - $files = array();
1074 - }
1075 -
1076 - return $files;
1077 - }
1078 -
1079 - /**
1080 - * Refresh metadata in memcached, but don't touch thumbnails or squid
1081 - * @return void
1082 - */
1083 - function purgeMetadataCache() {
1084 - clearstatcache();
1085 - $this->loadFromFile();
1086 - $this->saveToCache();
1087 - }
1088 -
1089 - /**
1090 - * Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid
1091 - * @param array $archiveFiles
1092 - * @param boolean $shared
1093 - * @return void
1094 - */
1095 - function purgeCache( $archiveFiles = array(), $shared = false ) {
1096 - global $wgUseSquid;
1097 -
1098 - // Refresh metadata cache
1099 - $this->purgeMetadataCache();
1100 -
1101 - // Delete thumbnails
1102 - $files = $this->getThumbnails( $shared );
1103 - $dir = wfImageThumbDir( $this->name, $shared );
1104 - $urls = array();
1105 - foreach ( $files as $file ) {
1106 - # Check that the base image name is part of the thumb name
1107 - # This is a basic sanity check to avoid erasing unrelated directories
1108 - if ( strpos( $file, $this->name ) !== false ) {
1109 - $url = $this->thumbUrlFromName( $file );
1110 - $urls[] = $url;
1111 - @unlink( "$dir/$file" );
1112 - }
1113 - }
1114 -
1115 - // Purge the squid
1116 - if ( $wgUseSquid ) {
1117 - $urls[] = $this->getURL();
1118 - foreach ( $archiveFiles as $file ) {
1119 - $urls[] = wfImageArchiveUrl( $file );
1120 - }
1121 - wfPurgeSquidServers( $urls );
1122 - }
1123 - }
1124 -
1125 - /**
1126 - * Purge the image description page, but don't go after
1127 - * pages using the image. Use when modifying file history
1128 - * but not the current data.
1129 - * @return void
1130 - */
1131 - function purgeDescription() {
1132 - $page = Title::makeTitle( NS_IMAGE, $this->name );
1133 - $page->invalidateCache();
1134 - $page->purgeSquid();
1135 - }
1136 -
1137 - /**
1138 - * Purge metadata and all affected pages when the image is created,
1139 - * deleted, or majorly updated.
1140 - * @param array $urlArray A set of additional URLs may be passed to purge,
1141 - * such as specific image files which have changed (param not used?)
1142 - * @return void
1143 - */
1144 - function purgeEverything( $urlArr=array() ) {
1145 - // Delete thumbnails and refresh image metadata cache
1146 - $this->purgeCache();
1147 - $this->purgeDescription();
1148 -
1149 - // Purge cache of all pages using this image
1150 - $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
1151 - $update->doUpdate();
1152 - }
1153 -
1154 - /**
1155 - * Return the image history of this image, line by line.
1156 - * starts with current version, then old versions.
1157 - * uses $this->historyLine to check which line to return:
1158 - * 0 return line for current version
1159 - * 1 query for old versions, return first one
1160 - * 2, ... return next old version from above query
1161 - *
1162 - * @public
1163 - * @return mixed false on no next history, object otherwise.
1164 - */
1165 - function nextHistoryLine() {
1166 - $dbr = wfGetDB( DB_SLAVE );
1167 -
1168 - if ( $this->historyLine == 0 ) {// called for the first time, return line from cur
1169 - $this->historyRes = $dbr->select( 'image',
1170 - array(
1171 - 'img_size',
1172 - 'img_description',
1173 - 'img_user','img_user_text',
1174 - 'img_timestamp',
1175 - 'img_width',
1176 - 'img_height',
1177 - "'' AS oi_archive_name"
1178 - ),
1179 - array( 'img_name' => $this->title->getDBkey() ),
1180 - __METHOD__
1181 - );
1182 - if ( 0 == $dbr->numRows( $this->historyRes ) ) {
1183 - return FALSE;
1184 - }
1185 - } else if ( $this->historyLine == 1 ) {
1186 - $this->historyRes = $dbr->select( 'oldimage',
1187 - array(
1188 - 'oi_size AS img_size',
1189 - 'oi_description AS img_description',
1190 - 'oi_user AS img_user',
1191 - 'oi_user_text AS img_user_text',
1192 - 'oi_timestamp AS img_timestamp',
1193 - 'oi_width as img_width',
1194 - 'oi_height as img_height',
1195 - 'oi_archive_name'
1196 - ),
1197 - array( 'oi_name' => $this->title->getDBkey() ),
1198 - __METHOD__,
1199 - array( 'ORDER BY' => 'oi_timestamp DESC' )
1200 - );
1201 - }
1202 - $this->historyLine ++;
1203 -
1204 - return $dbr->fetchObject( $this->historyRes );
1205 - }
1206 -
1207 - /**
1208 - * Reset the history pointer to the first element of the history
1209 - * @public
1210 - * @return void
1211 - */
1212 - function resetHistory() {
1213 - $this->historyLine = 0;
1214 - }
1215 -
1216 - /**
1217 - * Return the full filesystem path to the file. Note that this does
1218 - * not mean that a file actually exists under that location.
1219 - *
1220 - * This path depends on whether directory hashing is active or not,
1221 - * i.e. whether the images are all found in the same directory,
1222 - * or in hashed paths like /images/3/3c.
1223 - *
1224 - * @public
1225 - * @param boolean $fromSharedDirectory Return the path to the file
1226 - * in a shared repository (see $wgUseSharedRepository and related
1227 - * options in DefaultSettings.php) instead of a local one.
1228 - * @return string Full filesystem path to the file.
1229 - */
1230 - function getFullPath( $fromSharedRepository = false ) {
1231 - global $wgUploadDirectory, $wgSharedUploadDirectory;
1232 -
1233 - $dir = $fromSharedRepository ? $wgSharedUploadDirectory :
1234 - $wgUploadDirectory;
1235 -
1236 - // $wgSharedUploadDirectory may be false, if thumb.php is used
1237 - if ( $dir ) {
1238 - $fullpath = $dir . wfGetHashPath($this->name, $fromSharedRepository) . $this->name;
1239 - } else {
1240 - $fullpath = false;
1241 - }
1242 -
1243 - return $fullpath;
1244 - }
1245 -
1246 - /**
1247 - * @param boolean $shared
1248 - * @return bool
1249 - */
1250 - public static function isHashed( $shared ) {
1251 - global $wgHashedUploadDirectory, $wgHashedSharedUploadDirectory;
1252 - return $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
1253 - }
1254 -
1255 - /**
1256 - * Record an image upload in the upload log and the image table
1257 - * @param string $oldver
1258 - * @param string $desc
1259 - * @param string $license
1260 - * @param string $copyStatus
1261 - * @param string $source
1262 - * @param boolean $watch
1263 - * @return boolean
1264 - */
1265 - function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
1266 - global $wgUser, $wgUseCopyrightUpload;
1267 -
1268 - $dbw = wfGetDB( DB_MASTER );
1269 -
1270 - // Delete thumbnails and refresh the metadata cache
1271 - $this->purgeCache();
1272 -
1273 - // Fail now if the image isn't there
1274 - if ( !$this->fileExists || $this->fromSharedDirectory ) {
1275 - wfDebug( "Image::recordUpload: File ".$this->imagePath." went missing!\n" );
1276 - return false;
1277 - }
1278 -
1279 - if ( $wgUseCopyrightUpload ) {
1280 - if ( $license != '' ) {
1281 - $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
1282 - }
1283 - $textdesc = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n" .
1284 - '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
1285 - "$licensetxt" .
1286 - '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
1287 - } else {
1288 - if ( $license != '' ) {
1289 - $filedesc = $desc == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n";
1290 - $textdesc = $filedesc .
1291 - '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
1292 - } else {
1293 - $textdesc = $desc;
1294 - }
1295 - }
1296 -
1297 - $now = $dbw->timestamp();
1298 -
1299 - #split mime type
1300 - if (strpos($this->mime,'/')!==false) {
1301 - list($major,$minor)= explode('/',$this->mime,2);
1302 - }
1303 - else {
1304 - $major= $this->mime;
1305 - $minor= "unknown";
1306 - }
1307 -
1308 - # Test to see if the row exists using INSERT IGNORE
1309 - # This avoids race conditions by locking the row until the commit, and also
1310 - # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
1311 - $dbw->insert( 'image',
1312 - array(
1313 - 'img_name' => $this->name,
1314 - 'img_size'=> $this->size,
1315 - 'img_width' => intval( $this->width ),
1316 - 'img_height' => intval( $this->height ),
1317 - 'img_bits' => $this->bits,
1318 - 'img_media_type' => $this->type,
1319 - 'img_major_mime' => $major,
1320 - 'img_minor_mime' => $minor,
1321 - 'img_timestamp' => $now,
1322 - 'img_description' => $desc,
1323 - 'img_user' => $wgUser->getID(),
1324 - 'img_user_text' => $wgUser->getName(),
1325 - 'img_metadata' => $this->metadata,
1326 - ),
1327 - __METHOD__,
1328 - 'IGNORE'
1329 - );
1330 -
1331 - if( $dbw->affectedRows() == 0 ) {
1332 - # Collision, this is an update of an image
1333 - # Insert previous contents into oldimage
1334 - $dbw->insertSelect( 'oldimage', 'image',
1335 - array(
1336 - 'oi_name' => 'img_name',
1337 - 'oi_archive_name' => $dbw->addQuotes( $oldver ),
1338 - 'oi_size' => 'img_size',
1339 - 'oi_width' => 'img_width',
1340 - 'oi_height' => 'img_height',
1341 - 'oi_bits' => 'img_bits',
1342 - 'oi_timestamp' => 'img_timestamp',
1343 - 'oi_description' => 'img_description',
1344 - 'oi_user' => 'img_user',
1345 - 'oi_user_text' => 'img_user_text',
1346 - ), array( 'img_name' => $this->name ), __METHOD__
1347 - );
1348 -
1349 - # Update the current image row
1350 - $dbw->update( 'image',
1351 - array( /* SET */
1352 - 'img_size' => $this->size,
1353 - 'img_width' => intval( $this->width ),
1354 - 'img_height' => intval( $this->height ),
1355 - 'img_bits' => $this->bits,
1356 - 'img_media_type' => $this->type,
1357 - 'img_major_mime' => $major,
1358 - 'img_minor_mime' => $minor,
1359 - 'img_timestamp' => $now,
1360 - 'img_description' => $desc,
1361 - 'img_user' => $wgUser->getID(),
1362 - 'img_user_text' => $wgUser->getName(),
1363 - 'img_metadata' => $this->metadata,
1364 - ), array( /* WHERE */
1365 - 'img_name' => $this->name
1366 - ), __METHOD__
1367 - );
1368 - } else {
1369 - # This is a new image
1370 - # Update the image count
1371 - $site_stats = $dbw->tableName( 'site_stats' );
1372 - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
1373 - }
1374 -
1375 - $descTitle = $this->getTitle();
1376 - $article = new Article( $descTitle );
1377 - $minor = false;
1378 - $watch = $watch || $wgUser->isWatched( $descTitle );
1379 - $suppressRC = true; // There's already a log entry, so don't double the RC load
1380 -
1381 - if( $descTitle->exists() ) {
1382 - // TODO: insert a null revision into the page history for this update.
1383 - if( $watch ) {
1384 - $wgUser->addWatch( $descTitle );
1385 - }
1386 -
1387 - # Invalidate the cache for the description page
1388 - $descTitle->invalidateCache();
1389 - $descTitle->purgeSquid();
1390 - } else {
1391 - // New image; create the description page.
1392 - $article->insertNewArticle( $textdesc, $desc, $minor, $watch, $suppressRC );
1393 - }
1394 -
1395 - # Hooks, hooks, the magic of hooks...
1396 - wfRunHooks( 'FileUpload', array( $this ) );
1397 -
1398 - # Add the log entry
1399 - $log = new LogPage( 'upload' );
1400 - $log->addEntry( 'upload', $descTitle, $desc );
1401 -
1402 - # Commit the transaction now, in case something goes wrong later
1403 - # The most important thing is that images don't get lost, especially archives
1404 - $dbw->immediateCommit();
1405 -
1406 - # Invalidate cache for all pages using this image
1407 - $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
1408 - $update->doUpdate();
1409 -
1410 - return true;
1411 - }
1412 -
1413 - /**
1414 - * Get an array of Title objects which are articles which use this image
1415 - * Also adds their IDs to the link cache
1416 - *
1417 - * This is mostly copied from Title::getLinksTo()
1418 - *
1419 - * @deprecated Use HTMLCacheUpdate, this function uses too much memory
1420 - * @param string $options
1421 - * @return array[int]Title
1422 - */
1423 - function getLinksTo( $options = '' ) {
1424 - wfProfileIn( __METHOD__ );
1425 -
1426 - if ( $options ) {
1427 - $db = wfGetDB( DB_MASTER );
1428 - } else {
1429 - $db = wfGetDB( DB_SLAVE );
1430 - }
1431 - $linkCache =& LinkCache::singleton();
1432 -
1433 - list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' );
1434 - $encName = $db->addQuotes( $this->name );
1435 - $sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options";
1436 - $res = $db->query( $sql, __METHOD__ );
1437 -
1438 - $retVal = array();
1439 - if ( $db->numRows( $res ) ) {
1440 - while ( $row = $db->fetchObject( $res ) ) {
1441 - if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) {
1442 - $linkCache->addGoodLinkObj( $row->page_id, $titleObj );
1443 - $retVal[] = $titleObj;
1444 - }
1445 - }
1446 - }
1447 - $db->freeResult( $res );
1448 - wfProfileOut( __METHOD__ );
1449 - return $retVal;
1450 - }
1451 -
1452 - /**
1453 - * @return array
1454 - */
1455 - function getExifData() {
1456 - $handler = $this->getHandler();
1457 - if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) {
1458 - return array();
1459 - }
1460 - if ( !$this->metadata ) {
1461 - return array();
1462 - }
1463 - $exif = unserialize( $this->metadata );
1464 - if ( !$exif ) {
1465 - return array();
1466 - }
1467 - unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
1468 - $format = new FormatExif( $exif );
1469 -
1470 - return $format->getFormattedData();
1471 - }
1472 -
1473 - /**
1474 - * Returns true if the image does not come from the shared
1475 - * image repository.
1476 - *
1477 - * @return bool
1478 - */
1479 - function isLocal() {
1480 - return !$this->fromSharedDirectory;
1481 - }
1482 -
1483 - /**
1484 - * Was this image ever deleted from the wiki?
1485 - *
1486 - * @return bool
1487 - */
1488 - function wasDeleted() {
1489 - $title = Title::makeTitle( NS_IMAGE, $this->name );
1490 - return ( $title->isDeleted() > 0 );
1491 - }
1492 -
1493 - /**
1494 - * Delete all versions of the image.
1495 - *
1496 - * Moves the files into an archive directory (or deletes them)
1497 - * and removes the database rows.
1498 - *
1499 - * Cache purging is done; logging is caller's responsibility.
1500 - *
1501 - * @param string $reason
1502 - * @param boolean $suppress
1503 - * @return boolean true on success, false on some kind of failure
1504 - */
1505 - function delete( $reason, $suppress=false ) {
1506 - $transaction = new FSTransaction();
1507 - $urlArr = array( $this->getURL() );
1508 -
1509 - if( !FileStore::lock() ) {
1510 - wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
1511 - return false;
1512 - }
1513 -
1514 - try {
1515 - $dbw = wfGetDB( DB_MASTER );
1516 - $dbw->begin();
1517 -
1518 - // Delete old versions
1519 - $result = $dbw->select( 'oldimage',
1520 - array( 'oi_archive_name' ),
1521 - array( 'oi_name' => $this->name ) );
1522 -
1523 - while( $row = $dbw->fetchObject( $result ) ) {
1524 - $oldName = $row->oi_archive_name;
1525 -
1526 - $transaction->add( $this->prepareDeleteOld( $oldName, $reason, $suppress ) );
1527 -
1528 - // We'll need to purge this URL from caches...
1529 - $urlArr[] = wfImageArchiveUrl( $oldName );
1530 - }
1531 - $dbw->freeResult( $result );
1532 -
1533 - // And the current version...
1534 - $transaction->add( $this->prepareDeleteCurrent( $reason, $suppress ) );
1535 -
1536 - $dbw->immediateCommit();
1537 - } catch( MWException $e ) {
1538 - wfDebug( __METHOD__.": db error, rolling back file transactions\n" );
1539 - $transaction->rollback();
1540 - FileStore::unlock();
1541 - throw $e;
1542 - }
1543 -
1544 - wfDebug( __METHOD__.": deleted db items, applying file transactions\n" );
1545 - $transaction->commit();
1546 - FileStore::unlock();
1547 -
1548 -
1549 - // Update site_stats
1550 - $site_stats = $dbw->tableName( 'site_stats' );
1551 - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ );
1552 -
1553 - $this->purgeEverything( $urlArr );
1554 -
1555 - return true;
1556 - }
1557 -
1558 -
1559 - /**
1560 - * Delete an old version of the image.
1561 - *
1562 - * Moves the file into an archive directory (or deletes it)
1563 - * and removes the database row.
1564 - *
1565 - * Cache purging is done; logging is caller's responsibility.
1566 - *
1567 - * @param string $archiveName
1568 - * @param string $reason
1569 - * @param boolean $suppress
1570 - * @throws MWException or FSException on database or filestore failure
1571 - * @return boolean true on success, false on some kind of failure
1572 - */
1573 - function deleteOld( $archiveName, $reason, $suppress=false ) {
1574 - $transaction = new FSTransaction();
1575 - $urlArr = array();
1576 -
1577 - if( !FileStore::lock() ) {
1578 - wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
1579 - return false;
1580 - }
1581 -
1582 - $transaction = new FSTransaction();
1583 - try {
1584 - $dbw = wfGetDB( DB_MASTER );
1585 - $dbw->begin();
1586 - $transaction->add( $this->prepareDeleteOld( $archiveName, $reason, $suppress ) );
1587 - $dbw->immediateCommit();
1588 - } catch( MWException $e ) {
1589 - wfDebug( __METHOD__.": db error, rolling back file transaction\n" );
1590 - $transaction->rollback();
1591 - FileStore::unlock();
1592 - throw $e;
1593 - }
1594 -
1595 - wfDebug( __METHOD__.": deleted db items, applying file transaction\n" );
1596 - $transaction->commit();
1597 - FileStore::unlock();
1598 -
1599 - $this->purgeDescription();
1600 -
1601 - // Squid purging
1602 - global $wgUseSquid;
1603 - if ( $wgUseSquid ) {
1604 - $urlArr = array(
1605 - wfImageArchiveUrl( $archiveName ),
1606 - );
1607 - wfPurgeSquidServers( $urlArr );
1608 - }
1609 - return true;
1610 - }
1611 -
1612 - /**
1613 - * Delete the current version of a file.
1614 - * May throw a database error.
1615 - * @param string $reason
1616 - * @param boolean $suppress
1617 - * @return boolean true on success, false on failure
1618 - */
1619 - private function prepareDeleteCurrent( $reason, $suppress=false ) {
1620 - return $this->prepareDeleteVersion(
1621 - $this->getFullPath(),
1622 - $reason,
1623 - 'image',
1624 - array(
1625 - 'fa_name' => 'img_name',
1626 - 'fa_archive_name' => 'NULL',
1627 - 'fa_size' => 'img_size',
1628 - 'fa_width' => 'img_width',
1629 - 'fa_height' => 'img_height',
1630 - 'fa_metadata' => 'img_metadata',
1631 - 'fa_bits' => 'img_bits',
1632 - 'fa_media_type' => 'img_media_type',
1633 - 'fa_major_mime' => 'img_major_mime',
1634 - 'fa_minor_mime' => 'img_minor_mime',
1635 - 'fa_description' => 'img_description',
1636 - 'fa_user' => 'img_user',
1637 - 'fa_user_text' => 'img_user_text',
1638 - 'fa_timestamp' => 'img_timestamp' ),
1639 - array( 'img_name' => $this->name ),
1640 - $suppress,
1641 - __METHOD__ );
1642 - }
1643 -
1644 - /**
1645 - * Delete a given older version of a file.
1646 - * May throw a database error.
1647 - * @param string $archiveName
1648 - * @param string $reason
1649 - * @param boolean $suppress
1650 - * @return boolean true on success, false on failure
1651 - */
1652 - private function prepareDeleteOld( $archiveName, $reason, $suppress=false ) {
1653 - $oldpath = wfImageArchiveDir( $this->name ) .
1654 - DIRECTORY_SEPARATOR . $archiveName;
1655 - return $this->prepareDeleteVersion(
1656 - $oldpath,
1657 - $reason,
1658 - 'oldimage',
1659 - array(
1660 - 'fa_name' => 'oi_name',
1661 - 'fa_archive_name' => 'oi_archive_name',
1662 - 'fa_size' => 'oi_size',
1663 - 'fa_width' => 'oi_width',
1664 - 'fa_height' => 'oi_height',
1665 - 'fa_metadata' => 'NULL',
1666 - 'fa_bits' => 'oi_bits',
1667 - 'fa_media_type' => 'NULL',
1668 - 'fa_major_mime' => 'NULL',
1669 - 'fa_minor_mime' => 'NULL',
1670 - 'fa_description' => 'oi_description',
1671 - 'fa_user' => 'oi_user',
1672 - 'fa_user_text' => 'oi_user_text',
1673 - 'fa_timestamp' => 'oi_timestamp' ),
1674 - array(
1675 - 'oi_name' => $this->name,
1676 - 'oi_archive_name' => $archiveName ),
1677 - $suppress,
1678 - __METHOD__ );
1679 - }
1680 -
1681 - /**
1682 - * Do the dirty work of backing up an image row and its file
1683 - * (if $wgSaveDeletedFiles is on) and removing the originals.
1684 - *
1685 - * Must be run while the file store is locked and a database
1686 - * transaction is open to avoid race conditions.
1687 - *
1688 - * @return FSTransaction
1689 - */
1690 - private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $suppress=false, $fname ) {
1691 - global $wgUser, $wgSaveDeletedFiles;
1692 -
1693 - // Dupe the file into the file store
1694 - if( file_exists( $path ) ) {
1695 - if( $wgSaveDeletedFiles ) {
1696 - $group = 'deleted';
1697 -
1698 - $store = FileStore::get( $group );
1699 - $key = FileStore::calculateKey( $path, $this->extension );
1700 - $transaction = $store->insert( $key, $path,
1701 - FileStore::DELETE_ORIGINAL );
1702 - } else {
1703 - $group = null;
1704 - $key = null;
1705 - $transaction = FileStore::deleteFile( $path );
1706 - }
1707 - } else {
1708 - wfDebug( __METHOD__." deleting already-missing '$path'; moving on to database\n" );
1709 - $group = null;
1710 - $key = null;
1711 - $transaction = new FSTransaction(); // empty
1712 - }
1713 -
1714 - if( $transaction === false ) {
1715 - // Fail to restore?
1716 - wfDebug( __METHOD__.": import to file store failed, aborting\n" );
1717 - throw new MWException( "Could not archive and delete file $path" );
1718 - return false;
1719 - }
1720 -
1721 - // Bitfields to further supress the image content
1722 - // Note that currently, live images are stored elsewhere
1723 - // and cannot be partially deleted
1724 - $bitfield = 0;
1725 - if ( $suppress ) {
1726 - $bitfield |= self::DELETED_FILE;
1727 - $bitfield |= self::DELETED_COMMENT;
1728 - $bitfield |= self::DELETED_USER;
1729 - $bitfield |= self::DELETED_RESTRICTED;
1730 - }
1731 -
1732 - $dbw = wfGetDB( DB_MASTER );
1733 - $storageMap = array(
1734 - 'fa_storage_group' => $dbw->addQuotes( $group ),
1735 - 'fa_storage_key' => $dbw->addQuotes( $key ),
1736 -
1737 - 'fa_deleted_user' => $dbw->addQuotes( $wgUser->getId() ),
1738 - 'fa_deleted_timestamp' => $dbw->timestamp(),
1739 - 'fa_deleted_reason' => $dbw->addQuotes( $reason ),
1740 - 'fa_deleted' => $bitfield);
1741 - $allFields = array_merge( $storageMap, $fieldMap );
1742 -
1743 - try {
1744 - if( $wgSaveDeletedFiles ) {
1745 - $dbw->insertSelect( 'filearchive', $table, $allFields, $where, $fname );
1746 - }
1747 - $dbw->delete( $table, $where, $fname );
1748 - } catch( DBQueryError $e ) {
1749 - // Something went horribly wrong!
1750 - // Leave the file as it was...
1751 - wfDebug( __METHOD__.": database error, rolling back file transaction\n" );
1752 - $transaction->rollback();
1753 - throw $e;
1754 - }
1755 -
1756 - return $transaction;
1757 - }
1758 -
1759 - /**
1760 - * Restore all or specified deleted revisions to the given file.
1761 - * Permissions and logging are left to the caller.
1762 - *
1763 - * May throw database exceptions on error.
1764 - *
1765 - * @param $versions set of record ids of deleted items to restore,
1766 - * or empty to restore all revisions.
1767 - * @return the number of file revisions restored if successful,
1768 - * or false on failure
1769 - */
1770 - function restore( $versions=array(), $Unsuppress=false ) {
1771 - global $wgUser;
1772 -
1773 - if( !FileStore::lock() ) {
1774 - wfDebug( __METHOD__." could not acquire filestore lock\n" );
1775 - return false;
1776 - }
1777 -
1778 - $transaction = new FSTransaction();
1779 - try {
1780 - $dbw = wfGetDB( DB_MASTER );
1781 - $dbw->begin();
1782 -
1783 - // Re-confirm whether this image presently exists;
1784 - // if no we'll need to create an image record for the
1785 - // first item we restore.
1786 - $exists = $dbw->selectField( 'image', '1',
1787 - array( 'img_name' => $this->name ),
1788 - __METHOD__ );
1789 -
1790 - // Fetch all or selected archived revisions for the file,
1791 - // sorted from the most recent to the oldest.
1792 - $conditions = array( 'fa_name' => $this->name );
1793 - if( $versions ) {
1794 - $conditions['fa_id'] = $versions;
1795 - }
1796 -
1797 - $result = $dbw->select( 'filearchive', '*',
1798 - $conditions,
1799 - __METHOD__,
1800 - array( 'ORDER BY' => 'fa_timestamp DESC' ) );
1801 -
1802 - if( $dbw->numRows( $result ) < count( $versions ) ) {
1803 - // There's some kind of conflict or confusion;
1804 - // we can't restore everything we were asked to.
1805 - wfDebug( __METHOD__.": couldn't find requested items\n" );
1806 - $dbw->rollback();
1807 - FileStore::unlock();
1808 - return false;
1809 - }
1810 -
1811 - if( $dbw->numRows( $result ) == 0 ) {
1812 - // Nothing to do.
1813 - wfDebug( __METHOD__.": nothing to do\n" );
1814 - $dbw->rollback();
1815 - FileStore::unlock();
1816 - return true;
1817 - }
1818 -
1819 - $revisions = 0;
1820 - while( $row = $dbw->fetchObject( $result ) ) {
1821 - if ( $Unsuppress ) {
1822 - // Currently, fa_deleted flags fall off upon restore, lets be careful about this
1823 - } else if ( ($row->fa_deleted & Revision::DELETED_RESTRICTED) && !$wgUser->isAllowed('hiderevision') ) {
1824 - // Skip restoring file revisions that the user cannot restore
1825 - continue;
1826 - }
1827 - $revisions++;
1828 - $store = FileStore::get( $row->fa_storage_group );
1829 - if( !$store ) {
1830 - wfDebug( __METHOD__.": skipping row with no file.\n" );
1831 - continue;
1832 - }
1833 -
1834 - if( $revisions == 1 && !$exists ) {
1835 - $destDir = wfImageDir( $row->fa_name );
1836 - if ( !is_dir( $destDir ) ) {
1837 - wfMkdirParents( $destDir );
1838 - }
1839 - $destPath = $destDir . DIRECTORY_SEPARATOR . $row->fa_name;
1840 -
1841 - // We may have to fill in data if this was originally
1842 - // an archived file revision.
1843 - if( is_null( $row->fa_metadata ) ) {
1844 - $tempFile = $store->filePath( $row->fa_storage_key );
1845 -
1846 - $magic = MimeMagic::singleton();
1847 - $mime = $magic->guessMimeType( $tempFile, true );
1848 - $media_type = $magic->getMediaType( $tempFile, $mime );
1849 - list( $major_mime, $minor_mime ) = self::splitMime( $mime );
1850 - $handler = MediaHandler::getHandler( $mime );
1851 - if ( $handler ) {
1852 - $metadata = $handler->getMetadata( $image, $tempFile );
1853 - } else {
1854 - $metadata = '';
1855 - }
1856 - } else {
1857 - $metadata = $row->fa_metadata;
1858 - $major_mime = $row->fa_major_mime;
1859 - $minor_mime = $row->fa_minor_mime;
1860 - $media_type = $row->fa_media_type;
1861 - }
1862 -
1863 - $table = 'image';
1864 - $fields = array(
1865 - 'img_name' => $row->fa_name,
1866 - 'img_size' => $row->fa_size,
1867 - 'img_width' => $row->fa_width,
1868 - 'img_height' => $row->fa_height,
1869 - 'img_metadata' => $metadata,
1870 - 'img_bits' => $row->fa_bits,
1871 - 'img_media_type' => $media_type,
1872 - 'img_major_mime' => $major_mime,
1873 - 'img_minor_mime' => $minor_mime,
1874 - 'img_description' => $row->fa_description,
1875 - 'img_user' => $row->fa_user,
1876 - 'img_user_text' => $row->fa_user_text,
1877 - 'img_timestamp' => $row->fa_timestamp );
1878 - } else {
1879 - $archiveName = $row->fa_archive_name;
1880 - if( $archiveName == '' ) {
1881 - // This was originally a current version; we
1882 - // have to devise a new archive name for it.
1883 - // Format is <timestamp of archiving>!<name>
1884 - $archiveName =
1885 - wfTimestamp( TS_MW, $row->fa_deleted_timestamp ) .
1886 - '!' . $row->fa_name;
1887 - }
1888 - $destDir = wfImageArchiveDir( $row->fa_name );
1889 - if ( !is_dir( $destDir ) ) {
1890 - wfMkdirParents( $destDir );
1891 - }
1892 - $destPath = $destDir . DIRECTORY_SEPARATOR . $archiveName;
1893 -
1894 - $table = 'oldimage';
1895 - $fields = array(
1896 - 'oi_name' => $row->fa_name,
1897 - 'oi_archive_name' => $archiveName,
1898 - 'oi_size' => $row->fa_size,
1899 - 'oi_width' => $row->fa_width,
1900 - 'oi_height' => $row->fa_height,
1901 - 'oi_bits' => $row->fa_bits,
1902 - 'oi_description' => $row->fa_description,
1903 - 'oi_user' => $row->fa_user,
1904 - 'oi_user_text' => $row->fa_user_text,
1905 - 'oi_timestamp' => $row->fa_timestamp );
1906 - }
1907 -
1908 - $dbw->insert( $table, $fields, __METHOD__ );
1909 - // @todo this delete is not totally safe, potentially
1910 - $dbw->delete( 'filearchive',
1911 - array( 'fa_id' => $row->fa_id ),
1912 - __METHOD__ );
1913 -
1914 - // Check if any other stored revisions use this file;
1915 - // if so, we shouldn't remove the file from the deletion
1916 - // archives so they will still work.
1917 - $useCount = $dbw->selectField( 'filearchive',
1918 - 'COUNT(*)',
1919 - array(
1920 - 'fa_storage_group' => $row->fa_storage_group,
1921 - 'fa_storage_key' => $row->fa_storage_key ),
1922 - __METHOD__ );
1923 - if( $useCount == 0 ) {
1924 - wfDebug( __METHOD__.": nothing else using {$row->fa_storage_key}, will deleting after\n" );
1925 - $flags = FileStore::DELETE_ORIGINAL;
1926 - } else {
1927 - $flags = 0;
1928 - }
1929 -
1930 - $transaction->add( $store->export( $row->fa_storage_key,
1931 - $destPath, $flags ) );
1932 - }
1933 -
1934 - $dbw->immediateCommit();
1935 - } catch( MWException $e ) {
1936 - wfDebug( __METHOD__." caught error, aborting\n" );
1937 - $transaction->rollback();
1938 - throw $e;
1939 - }
1940 -
1941 - $transaction->commit();
1942 - FileStore::unlock();
1943 -
1944 - if( $revisions > 0 ) {
1945 - if( !$exists ) {
1946 - wfDebug( __METHOD__." restored $revisions items, creating a new current\n" );
1947 -
1948 - // Update site_stats
1949 - $site_stats = $dbw->tableName( 'site_stats' );
1950 - $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
1951 -
1952 - $this->purgeEverything();
1953 - } else {
1954 - wfDebug( __METHOD__." restored $revisions as archived versions\n" );
1955 - $this->purgeDescription();
1956 - }
1957 - }
1958 -
1959 - return $revisions;
1960 - }
1961 -
1962 - /**
1963 - * Returns 'true' if this image is a multipage document, e.g. a DJVU
1964 - * document.
1965 - *
1966 - * @return Bool
1967 - */
1968 - function isMultipage() {
1969 - $handler = $this->getHandler();
1970 - return $handler && $handler->isMultiPage();
1971 - }
1972 -
1973 - /**
1974 - * Returns the number of pages of a multipage document, or NULL for
1975 - * documents which aren't multipage documents
1976 - */
1977 - function pageCount() {
1978 - $handler = $this->getHandler();
1979 - if ( $handler && $handler->isMultiPage() ) {
1980 - return $handler->pageCount( $this );
1981 - } else {
1982 - return null;
1983 - }
1984 - }
1985 -
1986 - static function getCommonsDB() {
1987 - static $dbc;
1988 - global $wgLoadBalancer, $wgSharedUploadDBname;
1989 - if ( !isset( $dbc ) ) {
1990 - $i = $wgLoadBalancer->getGroupIndex( 'commons' );
1991 - $dbinfo = $wgLoadBalancer->mServers[$i];
1992 - $dbc = new Database( $dbinfo['host'], $dbinfo['user'],
1993 - $dbinfo['password'], $wgSharedUploadDBname );
1994 - }
1995 - return $dbc;
1996 - }
1997 -
1998 - /**
1999 - * Calculate the height of a thumbnail using the source and destination width
2000 - */
2001 - static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
2002 - // Exact integer multiply followed by division
2003 - if ( $srcWidth == 0 ) {
2004 - return 0;
2005 - } else {
2006 - return round( $srcHeight * $dstWidth / $srcWidth );
2007 - }
2008 - }
2009 -
2010 - /**
2011 - * Get an image size array like that returned by getimagesize(), or false if it
2012 - * can't be determined.
2013 - *
2014 - * @param string $fileName The filename
2015 - * @return array
2016 - */
2017 - function getImageSize( $fileName ) {
2018 - $handler = $this->getHandler();
2019 - return $handler->getImageSize( $this, $fileName );
2020 - }
2021 -
2022 - /**
2023 - * Get the thumbnail extension and MIME type for a given source MIME type
2024 - * @return array thumbnail extension and MIME type
2025 - */
2026 - static function getThumbType( $ext, $mime ) {
2027 - $handler = MediaHandler::getHandler( $mime );
2028 - if ( $handler ) {
2029 - return $handler->getThumbType( $ext, $mime );
2030 - } else {
2031 - return array( $ext, $mime );
2032 - }
2033 - }
2034 -
2035 -} //class
2036 -
2037 -
2038 -/**
2039 - * @addtogroup Media
2040 - */
2041 -class ArchivedFile
2042 -{
2043 - /**
2044 - * Returns a file object from the filearchive table
2045 - * In the future, all current and old image storage
2046 - * may use FileStore. There will be a "old" storage
2047 - * for current and previous file revisions as well as
2048 - * the "deleted" group for archived revisions
2049 - * @param $title, the corresponding image page title
2050 - * @param $id, the image id, a unique key
2051 - * @param $key, optional storage key
2052 - * @return ResultWrapper
2053 - */
2054 - function ArchivedFile( $title, $id=0, $key='' ) {
2055 - if( !is_object( $title ) ) {
2056 - throw new MWException( 'Image constructor given bogus title.' );
2057 - }
2058 - $conds = ($id) ? "fa_id = $id" : "fa_storage_key = '$key'";
2059 - if( $title->getNamespace() == NS_IMAGE ) {
2060 - $dbr = wfGetDB( DB_SLAVE );
2061 - $res = $dbr->select( 'filearchive',
2062 - array(
2063 - 'fa_id',
2064 - 'fa_name',
2065 - 'fa_storage_key',
2066 - 'fa_storage_group',
2067 - 'fa_size',
2068 - 'fa_bits',
2069 - 'fa_width',
2070 - 'fa_height',
2071 - 'fa_metadata',
2072 - 'fa_media_type',
2073 - 'fa_major_mime',
2074 - 'fa_minor_mime',
2075 - 'fa_description',
2076 - 'fa_user',
2077 - 'fa_user_text',
2078 - 'fa_timestamp',
2079 - 'fa_deleted' ),
2080 - array(
2081 - 'fa_name' => $title->getDbKey(),
2082 - $conds ),
2083 - __METHOD__,
2084 - array( 'ORDER BY' => 'fa_timestamp DESC' ) );
2085 -
2086 - if ( $dbr->numRows( $res ) == 0 ) {
2087 - // this revision does not exist?
2088 - return;
2089 - }
2090 - $ret = $dbr->resultObject( $res );
2091 - $row = $ret->fetchObject();
2092 -
2093 - // initialize fields for filestore image object
2094 - $this->mId = intval($row->fa_id);
2095 - $this->mName = $row->fa_name;
2096 - $this->mGroup = $row->fa_storage_group;
2097 - $this->mKey = $row->fa_storage_key;
2098 - $this->mSize = $row->fa_size;
2099 - $this->mBits = $row->fa_bits;
2100 - $this->mWidth = $row->fa_width;
2101 - $this->mHeight = $row->fa_height;
2102 - $this->mMetaData = $row->fa_metadata;
2103 - $this->mMime = "$row->fa_major_mime/$row->fa_minor_mime";
2104 - $this->mType = $row->fa_media_type;
2105 - $this->mDescription = $row->fa_description;
2106 - $this->mUser = $row->fa_user;
2107 - $this->mUserText = $row->fa_user_text;
2108 - $this->mTimestamp = $row->fa_timestamp;
2109 - $this->mDeleted = $row->fa_deleted;
2110 - } else {
2111 - throw new MWException( 'This title does not correspond to an image page.' );
2112 - return;
2113 - }
2114 - return true;
2115 - }
2116 -
2117 - /**
2118 - * int $field one of DELETED_* bitfield constants
2119 - * for file or revision rows
2120 - * @return bool
2121 - */
2122 - function isDeleted( $field ) {
2123 - return ($this->mDeleted & $field) == $field;
2124 - }
2125 -
2126 - /**
2127 - * Determine if the current user is allowed to view a particular
2128 - * field of this FileStore image file, if it's marked as deleted.
2129 - * @param int $field
2130 - * @return bool
2131 - */
2132 - function userCan( $field ) {
2133 - if( isset($this->mDeleted) && ($this->mDeleted & $field) == $field ) {
2134 - // images
2135 - global $wgUser;
2136 - $permission = ( $this->mDeleted & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED
2137 - ? 'hiderevision'
2138 - : 'deleterevision';
2139 - wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
2140 - return $wgUser->isAllowed( $permission );
2141 - } else {
2142 - return true;
2143 - }
2144 - }
2145 -}
2146 -
2147 -/**
2148 - * Aliases for backwards compatibility with 1.6
2149 - */
2150 -define( 'MW_IMG_DELETED_FILE', Image::DELETED_FILE );
2151 -define( 'MW_IMG_DELETED_COMMENT', Image::DELETED_COMMENT );
2152 -define( 'MW_IMG_DELETED_USER', Image::DELETED_USER );
2153 -define( 'MW_IMG_DELETED_RESTRICTED', Image::DELETED_RESTRICTED );
2154 -
2155 -?>
Index: trunk/phase3/includes/ImageQueryPage.php
@@ -30,8 +30,8 @@
3131 # $num [should update this to use a Pager]
3232 for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
3333 $image = $this->prepareImage( $row );
34 - if( $image instanceof Image ) {
35 - $gallery->add( $image, $this->getCellHtml( $row ) );
 34+ if( $image ) {
 35+ $gallery->add( $image->getTitle(), $this->getCellHtml( $row ) );
3636 }
3737 }
3838
@@ -49,7 +49,7 @@
5050 $namespace = isset( $row->namespace ) ? $row->namespace : NS_IMAGE;
5151 $title = Title::makeTitleSafe( $namespace, $row->title );
5252 return ( $title instanceof Title && $title->getNamespace() == NS_IMAGE )
53 - ? new Image( $title )
 53+ ? wfFindFile( $title )
5454 : null;
5555 }
5656
Index: trunk/phase3/includes/CategoryPage.php
@@ -147,7 +147,7 @@
148148 /**
149149 * Add a page in the image namespace
150150 */
151 - function addImage( $title, $sortkey, $pageLength, $isRedirect = false ) {
 151+ function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) {
152152 if ( $this->showGallery ) {
153153 $image = new Image( $title );
154154 if( $this->flip ) {
@@ -222,7 +222,7 @@
223223
224224 if( $title->getNamespace() == NS_CATEGORY ) {
225225 $this->addSubcategory( $title, $x->cl_sortkey, $x->page_len );
226 - } elseif( $title->getNamespace() == NS_IMAGE ) {
 226+ } elseif( $this->showGallery && $title->getNamespace() == NS_IMAGE ) {
227227 $this->addImage( $title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect );
228228 } else {
229229 $this->addPage( $title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect );
Index: trunk/phase3/includes/Defines.php
@@ -205,5 +205,61 @@
206206 define( 'LIST_NAMES', 3);
207207 define( 'LIST_OR', 4);
208208
 209+/**
 210+ * Unicode and normalisation related
 211+ */
 212+define( 'UNICODE_HANGUL_FIRST', 0xac00 );
 213+define( 'UNICODE_HANGUL_LAST', 0xd7a3 );
209214
 215+define( 'UNICODE_HANGUL_LBASE', 0x1100 );
 216+define( 'UNICODE_HANGUL_VBASE', 0x1161 );
 217+define( 'UNICODE_HANGUL_TBASE', 0x11a7 );
 218+
 219+define( 'UNICODE_HANGUL_LCOUNT', 19 );
 220+define( 'UNICODE_HANGUL_VCOUNT', 21 );
 221+define( 'UNICODE_HANGUL_TCOUNT', 28 );
 222+define( 'UNICODE_HANGUL_NCOUNT', UNICODE_HANGUL_VCOUNT * UNICODE_HANGUL_TCOUNT );
 223+
 224+define( 'UNICODE_HANGUL_LEND', UNICODE_HANGUL_LBASE + UNICODE_HANGUL_LCOUNT - 1 );
 225+define( 'UNICODE_HANGUL_VEND', UNICODE_HANGUL_VBASE + UNICODE_HANGUL_VCOUNT - 1 );
 226+define( 'UNICODE_HANGUL_TEND', UNICODE_HANGUL_TBASE + UNICODE_HANGUL_TCOUNT - 1 );
 227+
 228+define( 'UNICODE_SURROGATE_FIRST', 0xd800 );
 229+define( 'UNICODE_SURROGATE_LAST', 0xdfff );
 230+define( 'UNICODE_MAX', 0x10ffff );
 231+define( 'UNICODE_REPLACEMENT', 0xfffd );
 232+
 233+
 234+define( 'UTF8_HANGUL_FIRST', "\xea\xb0\x80" /*codepointToUtf8( UNICODE_HANGUL_FIRST )*/ );
 235+define( 'UTF8_HANGUL_LAST', "\xed\x9e\xa3" /*codepointToUtf8( UNICODE_HANGUL_LAST )*/ );
 236+
 237+define( 'UTF8_HANGUL_LBASE', "\xe1\x84\x80" /*codepointToUtf8( UNICODE_HANGUL_LBASE )*/ );
 238+define( 'UTF8_HANGUL_VBASE', "\xe1\x85\xa1" /*codepointToUtf8( UNICODE_HANGUL_VBASE )*/ );
 239+define( 'UTF8_HANGUL_TBASE', "\xe1\x86\xa7" /*codepointToUtf8( UNICODE_HANGUL_TBASE )*/ );
 240+
 241+define( 'UTF8_HANGUL_LEND', "\xe1\x84\x92" /*codepointToUtf8( UNICODE_HANGUL_LEND )*/ );
 242+define( 'UTF8_HANGUL_VEND', "\xe1\x85\xb5" /*codepointToUtf8( UNICODE_HANGUL_VEND )*/ );
 243+define( 'UTF8_HANGUL_TEND', "\xe1\x87\x82" /*codepointToUtf8( UNICODE_HANGUL_TEND )*/ );
 244+
 245+define( 'UTF8_SURROGATE_FIRST', "\xed\xa0\x80" /*codepointToUtf8( UNICODE_SURROGATE_FIRST )*/ );
 246+define( 'UTF8_SURROGATE_LAST', "\xed\xbf\xbf" /*codepointToUtf8( UNICODE_SURROGATE_LAST )*/ );
 247+define( 'UTF8_MAX', "\xf4\x8f\xbf\xbf" /*codepointToUtf8( UNICODE_MAX )*/ );
 248+define( 'UTF8_REPLACEMENT', "\xef\xbf\xbd" /*codepointToUtf8( UNICODE_REPLACEMENT )*/ );
 249+#define( 'UTF8_REPLACEMENT', '!' );
 250+
 251+define( 'UTF8_OVERLONG_A', "\xc1\xbf" );
 252+define( 'UTF8_OVERLONG_B', "\xe0\x9f\xbf" );
 253+define( 'UTF8_OVERLONG_C', "\xf0\x8f\xbf\xbf" );
 254+
 255+# These two ranges are illegal
 256+define( 'UTF8_FDD0', "\xef\xb7\x90" /*codepointToUtf8( 0xfdd0 )*/ );
 257+define( 'UTF8_FDEF', "\xef\xb7\xaf" /*codepointToUtf8( 0xfdef )*/ );
 258+define( 'UTF8_FFFE', "\xef\xbf\xbe" /*codepointToUtf8( 0xfffe )*/ );
 259+define( 'UTF8_FFFF', "\xef\xbf\xbf" /*codepointToUtf8( 0xffff )*/ );
 260+
 261+define( 'UTF8_HEAD', false );
 262+define( 'UTF8_TAIL', true );
 263+
 264+
 265+
210266 ?>
Index: trunk/phase3/includes/GlobalFunctions.php
@@ -2272,4 +2272,26 @@
22732273 $ret = $wgLoadBalancer->getConnection( $db, true, $groups );
22742274 return $ret;
22752275 }
 2276+
 2277+/**
 2278+ * Find a file.
 2279+ * Shortcut for RepoGroup::singleton()->findFile()
 2280+ * @param mixed $title Title object or string. May be interwiki.
 2281+ * @param mixed $time Requested time for an archived image, or false for the
 2282+ * current version. An image object will be returned which
 2283+ * existed at or before the specified time.
 2284+ * @return File, or false if the file does not exist
 2285+ */
 2286+function wfFindFile( $title, $time = false ) {
 2287+ return RepoGroup::singleton()->findFile( $title, $time );
 2288+}
 2289+
 2290+/**
 2291+ * Get an object referring to a locally registered file.
 2292+ * Returns a valid placeholder object if the file does not exist.
 2293+ */
 2294+function wfLocalFile( $title ) {
 2295+ return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
 2296+}
 2297+
22762298 ?>
Index: trunk/phase3/includes/ImagePage.php
@@ -18,6 +18,14 @@
1919 /* private */ var $img; // Image object this page is shown for
2020 var $mExtraDescription = false;
2121
 22+ function __construct( $title ) {
 23+ parent::__construct( $title );
 24+ $this->img = wfFindFile( $this->mTitle );
 25+ if ( !$this->img ) {
 26+ $this->img = wfLocalFile( $this->mTitle );
 27+ }
 28+ }
 29+
2230 /**
2331 * Handler for action=render
2432 * Include body text only; none of the image extras
@@ -31,8 +39,6 @@
3240 function view() {
3341 global $wgOut, $wgShowEXIF, $wgRequest, $wgUser;
3442
35 - $this->img = new Image( $this->mTitle );
36 -
3743 $diff = $wgRequest->getVal( 'diff' );
3844 $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
3945
@@ -160,7 +166,7 @@
161167 * shared upload server if possible.
162168 */
163169 function getContent() {
164 - if( $this->img && $this->img->fromSharedDirectory && 0 == $this->getID() ) {
 170+ if( $this->img && !$this->img->isLocal() && 0 == $this->getID() ) {
165171 return '';
166172 }
167173 return Article::getContent();
@@ -332,26 +338,26 @@
333339 $dirmark = $wgContLang->getDirMark();
334340 if (!$this->img->isSafeFile()) {
335341 $warning = wfMsg( 'mediawarning' );
336 - $wgOut->addWikiText( <<<END
 342+ $wgOut->addWikiText( <<<EOT
337343 <div class="fullMedia">$infores
338344 <span class="dangerousLink">[[Media:$filename|$filename]]</span>$dirmark
339345 <span class="fileInfo"> $info</span>
340346 </div>
341347
342348 <div class="mediaWarning">$warning</div>
343 -END
 349+EOT
344350 );
345351 } else {
346 - $wgOut->addWikiText( <<<END
 352+ $wgOut->addWikiText( <<<EOT
347353 <div class="fullMedia">$infores
348354 [[Media:$filename|$filename]]$dirmark <span class="fileInfo"> $info</span>
349355 </div>
350 -END
 356+EOT
351357 );
352358 }
353359 }
354360
355 - if($this->img->fromSharedDirectory) {
 361+ if(!$this->img->isLocal()) {
356362 $this->printSharedImageText();
357363 }
358364 } else {
@@ -365,27 +371,21 @@
366372 }
367373
368374 function printSharedImageText() {
369 - global $wgRepositoryBaseUrl, $wgFetchCommonsDescriptions, $wgOut, $wgUser;
 375+ global $wgOut, $wgUser;
370376
371 - $url = $wgRepositoryBaseUrl . urlencode($this->mTitle->getDBkey());
372 - $sharedtext = "<div class='sharedUploadNotice'>" . wfMsgWikiHtml("sharedupload");
373 - if ($wgRepositoryBaseUrl && !$wgFetchCommonsDescriptions) {
374 -
 377+ $descUrl = $this->img->getDescriptionUrl();
 378+ $descText = $this->img->getDescriptionText();
 379+ $s = "<div class='sharedUploadNotice'>" . wfMsgWikiHtml("sharedupload");
 380+ if ( $descUrl && !$descText) {
375381 $sk = $wgUser->getSkin();
376 - $title = SpecialPage::getTitleFor( 'Upload' );
377 - $link = $sk->makeKnownLinkObj($title, wfMsgHtml('shareduploadwiki-linktext'),
378 - array( 'wpDestFile' => urlencode( $this->img->getName() )));
379 - $sharedtext .= " " . wfMsgWikiHtml('shareduploadwiki', $link);
 382+ $link = $sk->makeExternalLink( $descUrl, wfMsg('shareduploadwiki-linktext') );
 383+ $s .= " " . wfMsgWikiHtml('shareduploadwiki', $link);
380384 }
381 - $sharedtext .= "</div>";
382 - $wgOut->addHTML($sharedtext);
 385+ $s .= "</div>";
 386+ $wgOut->addHTML($s);
383387
384 - if ($wgRepositoryBaseUrl && $wgFetchCommonsDescriptions) {
385 - $renderUrl = wfAppendQuery( $url, 'action=render' );
386 - wfDebug( "Fetching shared description from $renderUrl\n" );
387 - $text = Http::get( $renderUrl );
388 - if ($text)
389 - $this->mExtraDescription = $text;
 388+ if ( $descText ) {
 389+ $this->mExtraDescription = $descText;
390390 }
391391 }
392392
@@ -402,7 +402,7 @@
403403 function uploadLinksBox() {
404404 global $wgUser, $wgOut;
405405
406 - if( $this->img->fromSharedDirectory )
 406+ if( !$this->img->isLocal() )
407407 return;
408408
409409 $sk = $wgUser->getSkin();
@@ -441,7 +441,7 @@
442442 $line = $this->img->nextHistoryLine();
443443
444444 if ( $line ) {
445 - $list = new ImageHistoryList( $sk );
 445+ $list = new ImageHistoryList( $sk, $this->img );
446446 $s = $list->beginImageHistoryList() .
447447 $list->imageHistoryLine( true, wfTimestamp(TS_MW, $line->img_timestamp),
448448 $this->mTitle->getDBkey(), $line->img_user,
@@ -530,8 +530,6 @@
531531 return;
532532 }
533533
534 - $this->img = new Image( $this->mTitle );
535 -
536534 # Deleting old images doesn't require confirmation
537535 if ( !is_null( $oldimage ) || $confirm ) {
538536 if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $oldimage ) ) {
@@ -655,39 +653,23 @@
656654 $wgOut->showErrorPage( 'internalerror', 'sessionfailure' );
657655 return;
658656 }
659 - $name = substr( $oldimage, 15 );
660657
661 - $dest = wfImageDir( $name );
662 - $archive = wfImageArchiveDir( $name );
663 - $curfile = "{$dest}/{$name}";
 658+ $sourcePath = $this->img->getArchiveVirtualUrl( $oldimage );
 659+ $result = $this->img->publish( $sourcePath );
664660
665 - if ( !is_dir( $dest ) ) wfMkdirParents( $dest );
666 - if ( !is_dir( $archive ) ) wfMkdirParents( $archive );
667 -
668 - if ( ! is_file( $curfile ) ) {
669 - $wgOut->showFileNotFoundError( htmlspecialchars( $curfile ) );
 661+ if ( WikiError::isError( $result ) ) {
 662+ $this->showError( $result );
670663 return;
671664 }
672 - $oldver = wfTimestampNow() . "!{$name}";
673665
674 - if ( ! rename( $curfile, "${archive}/{$oldver}" ) ) {
675 - $wgOut->showFileRenameError( $curfile, "${archive}/{$oldver}" );
676 - return;
677 - }
678 - if ( ! copy( "{$archive}/{$oldimage}", $curfile ) ) {
679 - $wgOut->showFileCopyError( "${archive}/{$oldimage}", $curfile );
680 - return;
681 - }
682 -
683666 # Record upload and update metadata cache
684 - $img = Image::newFromName( $name );
685 - $img->recordUpload( $oldver, wfMsg( "reverted" ) );
 667+ $this->img->recordUpload( $result, wfMsg( "reverted" ) );
686668
687669 $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
688670 $wgOut->setRobotpolicy( 'noindex,nofollow' );
689671 $wgOut->addHTML( wfMsg( 'imagereverted' ) );
690672
691 - $descTitle = $img->getTitle();
 673+ $descTitle = $this->img->getTitle();
692674 $wgOut->returnToMain( false, $descTitle->getPrefixedText() );
693675 }
694676
@@ -695,7 +677,6 @@
696678 * Override handling of action=purge
697679 */
698680 function doPurge() {
699 - $this->img = new Image( $this->mTitle );
700681 if( $this->img->exists() ) {
701682 wfDebug( "ImagePage::doPurge purging " . $this->img->getName() . "\n" );
702683 $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
@@ -708,6 +689,18 @@
709690 parent::doPurge();
710691 }
711692
 693+ /**
 694+ * Display an error from a wikitext-formatted WikiError object
 695+ */
 696+ function showError( WikiError $error ) {
 697+ global $wgOut;
 698+ $wgOut->setPageTitle( wfMsg( "internalerror" ) );
 699+ $wgOut->setRobotpolicy( "noindex,nofollow" );
 700+ $wgOut->setArticleRelated( false );
 701+ $wgOut->enableClientCache( false );
 702+ $wgOut->addWikiText( $error->getMessage() );
 703+ }
 704+
712705 }
713706
714707 /**
@@ -715,8 +708,10 @@
716709 * @addtogroup Media
717710 */
718711 class ImageHistoryList {
719 - function ImageHistoryList( &$skin ) {
720 - $this->skin =& $skin;
 712+ var $img, $skin;
 713+ function ImageHistoryList( $skin, $img ) {
 714+ $this->skin = $skin;
 715+ $this->img = $img;
721716 }
722717
723718 function beginImageHistoryList() {
@@ -738,11 +733,12 @@
739734 $del = wfMsgHtml( 'deleteimg' );
740735 $delall = wfMsgHtml( 'deleteimgcompletely' );
741736 $cur = wfMsgHtml( 'cur' );
 737+ $local = $this->img->isLocal();
742738
743739 if ( $iscur ) {
744 - $url = Image::imageUrl( $img );
 740+ $url = htmlspecialchars( $this->img->getURL() );
745741 $rlink = $cur;
746 - if ( $wgUser->isAllowed('delete') ) {
 742+ if ( $local && $wgUser->isAllowed('delete') ) {
747743 $link = $wgTitle->escapeLocalURL( 'image=' . $wgTitle->getPartialURL() .
748744 '&action=delete' );
749745 $style = $this->skin->getInternalLinkAttributes( $link, $delall );
@@ -752,8 +748,8 @@
753749 $dlink = $del;
754750 }
755751 } else {
756 - $url = htmlspecialchars( wfImageArchiveUrl( $img ) );
757 - if( $wgUser->getID() != 0 && $wgTitle->userCan( 'edit' ) ) {
 752+ $url = htmlspecialchars( $this->img->getArchiveUrl( $img ) );
 753+ if( $local && $wgUser->getID() != 0 && $wgTitle->userCan( 'edit' ) ) {
758754 $token = urlencode( $wgUser->editToken( $img ) );
759755 $rlink = $this->skin->makeKnownLinkObj( $wgTitle,
760756 wfMsgHtml( 'revertimg' ), 'action=revert&oldimage=' .
@@ -769,8 +765,12 @@
770766 $dlink = $del;
771767 }
772768 }
773 -
774 - $userlink = $this->skin->userLink( $user, $usertext ) . $this->skin->userToolLinks( $user, $usertext );
 769+
 770+ if ( $local ) {
 771+ $userlink = $this->skin->userLink( $user, $usertext ) . $this->skin->userToolLinks( $user, $usertext );
 772+ } else {
 773+ $userlink = htmlspecialchars( $usertext );
 774+ }
775775 $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
776776 $wgLang->formatNum( $size ) );
777777 $widthheight = wfMsgHtml( 'widthheight', $width, $height );
@@ -782,7 +782,6 @@
783783 $s .= "</li>\n";
784784 return $s;
785785 }
786 -
787786 }
788787
789788
Index: trunk/phase3/includes/Linker.php
@@ -440,13 +440,14 @@
441441 * @return string
442442 */
443443 function makeImageLinkObj( $nt, $label, $alt, $align = '', $params = array(), $framed = false,
444 - $thumb = false, $manual_thumb = '', $valign = '' )
 444+ $thumb = false, $manual_thumb = '', $valign = '', $time = false )
445445 {
446446 global $wgContLang, $wgUser, $wgThumbLimits, $wgThumbUpright;
447447
448 - $img = new Image( $nt );
 448+ $img = wfFindFile( $nt, $time );
449449
450 - if ( !$img->allowInlineDisplay() && $img->exists() ) {
 450+ if ( $img && !$img->allowInlineDisplay() ) {
 451+ wfDebug( __METHOD__.': '.$nt->getPrefixedDBkey()." does not allow inline display\n" );
451452 return $this->makeKnownLinkObj( $nt );
452453 }
453454
@@ -459,7 +460,7 @@
460461 $postfix = '</div>';
461462 $align = 'none';
462463 }
463 - if ( !isset( $params['width'] ) ) {
 464+ if ( $img && !isset( $params['width'] ) ) {
464465 $params['width'] = $img->getWidth( $page );
465466 if( $thumb || $framed || isset( $params['frameless'] ) ) {
466467 $wopt = $wgUser->getOption( 'thumbsize' );
@@ -490,10 +491,10 @@
491492 if ( $align == '' ) {
492493 $align = $wgContLang->isRTL() ? 'left' : 'right';
493494 }
494 - return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix;
 495+ return $prefix.$this->makeThumbLinkObj( $nt, $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix;
495496 }
496497
497 - if ( $params['width'] && $img->exists() ) {
 498+ if ( $img && $params['width'] ) {
498499 # Create a resized image, without the additional thumbnail features
499500 $thumb = $img->transform( $params );
500501 } else {
@@ -524,7 +525,7 @@
525526 );
526527
527528 if ( !$thumb ) {
528 - $s = $this->makeBrokenImageLinkObj( $img->getTitle() );
 529+ $s = $this->makeBrokenImageLinkObj( $nt );
529530 } else {
530531 $s = $thumb->toHtml( $imgAttribs, $linkAttribs );
531532 }
@@ -536,10 +537,12 @@
537538
538539 /**
539540 * Make HTML for a thumbnail including image, border and caption
540 - * $img is an Image object
 541+ * @param Title $nt
 542+ * @param Image $img Image object or false if it doesn't exist
541543 */
542 - function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) {
 544+ function makeThumbLinkObj( Title $nt, $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) {
543545 global $wgStylePath, $wgContLang;
 546+ $exists = $img && $img->exists();
544547
545548 $page = isset( $params['page'] ) ? $params['page'] : false;
546549
@@ -548,46 +551,55 @@
549552 $params['width'] = isset( $params['upright'] ) ? 130 : 180;
550553 }
551554 $thumb = false;
552 - if ( $manual_thumb != '' ) {
553 - # Use manually specified thumbnail
554 - $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
555 - if( $manual_title ) {
556 - $manual_img = new Image( $manual_title );
557 - $thumb = $manual_img->getUnscaledThumb();
558 - }
559 - } elseif ( $framed ) {
560 - // Use image dimensions, don't scale
561 - $thumb = $img->getUnscaledThumb( $page );
 555+
 556+ if ( !$exists ) {
 557+ $outerWidth = $params['width'] + 2;
562558 } else {
563 - # Do not present an image bigger than the source, for bitmap-style images
564 - # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
565 - $srcWidth = $img->getWidth( $page );
566 - if ( $srcWidth && !$img->mustRender() && $params['width'] > $srcWidth ) {
567 - $params['width'] = $srcWidth;
 559+ if ( $manual_thumb != '' ) {
 560+ # Use manually specified thumbnail
 561+ $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
 562+ if( $manual_title ) {
 563+ $manual_img = wfFindFile( $manual_title );
 564+ if ( $manual_img ) {
 565+ $thumb = $manual_img->getUnscaledThumb();
 566+ } else {
 567+ $exists = false;
 568+ }
 569+ }
 570+ } elseif ( $framed ) {
 571+ // Use image dimensions, don't scale
 572+ $thumb = $img->getUnscaledThumb( $page );
 573+ } else {
 574+ # Do not present an image bigger than the source, for bitmap-style images
 575+ # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
 576+ $srcWidth = $img->getWidth( $page );
 577+ if ( $srcWidth && !$img->mustRender() && $params['width'] > $srcWidth ) {
 578+ $params['width'] = $srcWidth;
 579+ }
 580+ $thumb = $img->transform( $params );
568581 }
569 - $thumb = $img->transform( $params );
570 - }
571582
572 - if ( $thumb ) {
573 - $outerWidth = $thumb->getWidth() + 2;
574 - } else {
575 - $outerWidth = $params['width'] + 2;
 583+ if ( $thumb ) {
 584+ $outerWidth = $thumb->getWidth() + 2;
 585+ } else {
 586+ $outerWidth = $params['width'] + 2;
 587+ }
576588 }
577589
578590 $query = $page ? 'page=' . urlencode( $page ) : '';
579 - $u = $img->getTitle()->getLocalURL( $query );
 591+ $u = $nt->getLocalURL( $query );
580592
581593 $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
582594 $magnifyalign = $wgContLang->isRTL() ? 'left' : 'right';
583595 $textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : '';
584596
585597 $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
586 - if ( !$thumb ) {
 598+ if( !$exists ) {
 599+ $s .= $this->makeBrokenImageLinkObj( $nt );
 600+ $zoomicon = '';
 601+ } elseif ( !$thumb ) {
587602 $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
588603 $zoomicon = '';
589 - } elseif( !$img->exists() ) {
590 - $s .= $this->makeBrokenImageLinkObj( $img->getTitle() );
591 - $zoomicon = '';
592604 } else {
593605 $imgAttribs = array(
594606 'alt' => $alt,
@@ -645,10 +657,10 @@
646658 return $s;
647659 }
648660
649 - /** @todo document */
650 - function makeMediaLink( $name, /* wtf?! */ $url, $alt = '' ) {
 661+ /** @deprecated use Linker::makeMediaLinkObj() */
 662+ function makeMediaLink( $name, $unused = '', $text = '' ) {
651663 $nt = Title::makeTitleSafe( NS_IMAGE, $name );
652 - return $this->makeMediaLinkObj( $nt, $alt );
 664+ return $this->makeMediaLinkObj( $nt, $text );
653665 }
654666
655667 /**
@@ -666,13 +678,13 @@
667679 ### HOTFIX. Instead of breaking, return empty string.
668680 return $text;
669681 } else {
670 - $img = new Image( $title );
671 - if( $img->exists() ) {
 682+ $img = wfFindFile( $title );
 683+ if( $img ) {
672684 $url = $img->getURL();
673685 $class = 'internal';
674686 } else {
675687 $upload = SpecialPage::getTitleFor( 'Upload' );
676 - $url = $upload->getLocalUrl( 'wpDestFile=' . urlencode( $img->getName() ) );
 688+ $url = $upload->getLocalUrl( 'wpDestFile=' . urlencode( $title->getText() ) );
677689 $class = 'new';
678690 }
679691 $alt = htmlspecialchars( $title->getText() );
Index: trunk/phase3/includes/ExternalEdit.php
@@ -46,7 +46,7 @@
4747 $extension="wiki";
4848 } elseif($this->mMode=="file") {
4949 $type="Edit file";
50 - $image = new Image( $this->mTitle );
 50+ $image = wfLocalFile( $this->mTitle );
5151 $img_url = $image->getURL();
5252 if(strpos($img_url,"://")) {
5353 $url = $img_url;
Index: trunk/phase3/includes/SearchEngine.php
@@ -122,8 +122,8 @@
123123 # There may have been a funny upload, or it may be on a shared
124124 # file repository such as Wikimedia Commons.
125125 if( $title->getNamespace() == NS_IMAGE ) {
126 - $image = new Image( $title );
127 - if( $image->exists() ) {
 126+ $image = wfFindFile( $title );
 127+ if( $image ) {
128128 return $title;
129129 }
130130 }
Index: trunk/phase3/includes/Parser.php
@@ -1806,8 +1806,8 @@
18071807 }
18081808 continue;
18091809 } elseif( $ns == NS_IMAGE ) {
1810 - $img = new Image( $nt );
1811 - if( $img->exists() ) {
 1810+ $img = wfFindFile( $nt );
 1811+ if( $img ) {
18121812 // Force a blue link if the file exists; may be a remote
18131813 // upload on the shared repository, and we want to see its
18141814 // auto-generated page.
@@ -4393,7 +4393,7 @@
43944394 );
43954395 $html = $pout->getText();
43964396
4397 - $ig->add( new Image( $nt ), $html );
 4397+ $ig->add( $nt, $html );
43984398
43994399 # Only add real images (bug #5586)
44004400 if ( $nt->getNamespace() == NS_IMAGE ) {
Index: trunk/phase3/includes/Setup.php
@@ -54,6 +54,59 @@
5555 if( $wgReadOnlyFile === false ) $wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR";
5656 if( $wgFileCacheDirectory === false ) $wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
5757
 58+/**
 59+ * Initialise $wgLocalFileRepo from backwards-compatible settings
 60+ */
 61+if ( !$wgLocalFileRepo ) {
 62+ $wgLocalFileRepo = array(
 63+ 'class' => 'LocalRepo',
 64+ 'name' => 'local',
 65+ 'directory' => $wgUploadDirectory,
 66+ 'url' => $wgUploadBaseUrl ? $wgUploadBaseUrl . $wgUploadPath : $wgUploadPath,
 67+ 'hashLevels' => $wgHashedUploadDirectory ? 2 : 0,
 68+ 'thumbScriptUrl' => $wgThumbnailScriptPath,
 69+ 'transformVia404' => !$wgGenerateThumbnailOnParse,
 70+ );
 71+}
 72+/**
 73+ * Initialise shared repo from backwards-compatible settings
 74+ */
 75+if ( $wgUseSharedUploads ) {
 76+ if ( $wgSharedUploadDBname ) {
 77+ $wgForeignFileRepos[] = array(
 78+ 'class' => 'ForeignDBRepo',
 79+ 'name' => 'shared',
 80+ 'directory' => $wgSharedUploadDirectory,
 81+ 'url' => $wgSharedUploadPath,
 82+ 'hashLevels' => $wgHashedSharedUploadDirectory ? 2 : 0,
 83+ 'thumbScriptUrl' => $wgSharedThumbnailScriptPath,
 84+ 'transformVia404' => !$wgGenerateThumbnailOnParse,
 85+ 'dbType' => $wgDBtype,
 86+ 'dbServer' => $wgDBserver,
 87+ 'dbUser' => $wgDBuser,
 88+ 'dbPassword' => $wgDBpassword,
 89+ 'dbName' => $wgSharedUploadDBname,
 90+ 'dbFlags' => DBO_DEFAULT,
 91+ 'tablePrefix' => $wgSharedUploadDBprefix,
 92+ 'hasSharedCache' => $wgCacheSharedUploads,
 93+ 'descBaseUrl' => $wgRepositoryBaseUrl,
 94+ 'fetchDescription' => $wgFetchCommonsDescriptions,
 95+ );
 96+ } else {
 97+ $wgForeignFileRepos[] = array(
 98+ 'class' => 'FSRepo',
 99+ 'name' => 'shared',
 100+ 'directory' => $wgSharedUploadDirectory,
 101+ 'url' => $wgSharedUploadPath,
 102+ 'hashLevels' => $wgHashedSharedUploadDirectory ? 2 : 0,
 103+ 'thumbScriptUrl' => $wgSharedThumbnailScriptPath,
 104+ 'transformVia404' => !$wgGenerateThumbnailOnParse,
 105+ 'descBaseUrl' => $wgRepositoryBaseUrl,
 106+ 'fetchDescription' => $wgFetchCommonsDescriptions,
 107+ );
 108+ }
 109+}
 110+
58111 require_once( "$IP/includes/AutoLoader.php" );
59112
60113 wfProfileIn( $fname.'-exception' );
Index: trunk/phase3/includes/filerepo/OldLocalFile.php
@@ -0,0 +1,222 @@
 2+<?php
 3+
 4+/**
 5+ * Class to represent a file in the oldimage table
 6+ *
 7+ * @addtogroup FileRepo
 8+ */
 9+class OldLocalFile extends LocalFile {
 10+ var $requestedTime, $archive_name;
 11+
 12+ const CACHE_VERSION = 1;
 13+ const MAX_CACHE_ROWS = 20;
 14+
 15+ function newFromTitle( $title, $repo, $time ) {
 16+ return new self( $title, $repo, $time, null );
 17+ }
 18+
 19+ function newFromArchiveName( $title, $repo, $archiveName ) {
 20+ return new self( $title, $repo, null, $archiveName );
 21+ }
 22+
 23+ function newFromRow( $row, $repo ) {
 24+ $title = Title::makeTitle( NS_IMAGE, $row->oi_name );
 25+ $file = new self( $title, $repo, null, $row->oi_archive_name );
 26+ $file->loadFromRow( $row, 'oi_' );
 27+ return $file;
 28+ }
 29+
 30+ /**
 31+ * @param Title $title
 32+ * @param FileRepo $repo
 33+ * @param string $time Timestamp or null to load by archive name
 34+ * @param string $archiveName Archive name or null to load by timestamp
 35+ */
 36+ function __construct( $title, $repo, $time, $archiveName ) {
 37+ parent::__construct( $title, $repo );
 38+ $this->requestedTime = $time;
 39+ $this->archive_name = $archiveName;
 40+ if ( is_null( $time ) && is_null( $archiveName ) ) {
 41+ throw new MWException( __METHOD__.': must specify at least one of $time or $archiveName' );
 42+ }
 43+ }
 44+
 45+ function getCacheKey() {
 46+ $hashedName = md5($this->getName());
 47+ return wfMemcKey( 'oldfile', $hashedName );
 48+ }
 49+
 50+ function getArchiveName() {
 51+ if ( !isset( $this->archive_name ) ) {
 52+ $this->load();
 53+ }
 54+ return $this->archive_name;
 55+ }
 56+
 57+ function isOld() {
 58+ return true;
 59+ }
 60+
 61+ /**
 62+ * Try to load file metadata from memcached. Returns true on success.
 63+ */
 64+ function loadFromCache() {
 65+ global $wgMemc;
 66+ wfProfileIn( __METHOD__ );
 67+ $this->dataLoaded = false;
 68+ $key = $this->getCacheKey();
 69+ if ( !$key ) {
 70+ return false;
 71+ }
 72+ $oldImages = $wgMemc->get( $key );
 73+
 74+ if ( isset( $oldImages['version'] ) && $oldImages['version'] == MW_OLDFILE_VERSION ) {
 75+ unset( $oldImages['version'] );
 76+ $more = isset( $oldImages['more'] );
 77+ unset( $oldImages['more'] );
 78+ $found = false;
 79+ if ( is_null( $this->requestedTime ) ) {
 80+ foreach ( $oldImages as $timestamp => $info ) {
 81+ if ( $info['archive_name'] == $this->archive_name ) {
 82+ $found = true;
 83+ break;
 84+ }
 85+ }
 86+ } else {
 87+ krsort( $oldImages );
 88+ foreach ( $oldImages as $timestamp => $info ) {
 89+ if ( $timestamp <= $this->requestedTime ) {
 90+ $found = true;
 91+ break;
 92+ }
 93+ }
 94+ }
 95+ if ( $found ) {
 96+ wfDebug( "Pulling file metadata from cache key {$key}[{$timestamp}]\n" );
 97+ $this->dataLoaded = true;
 98+ foreach ( $cachedValues as $name => $value ) {
 99+ $this->$name = $value;
 100+ }
 101+ } elseif ( $more ) {
 102+ wfDebug( "Cache key was truncated, oldimage row might be found in the database\n" );
 103+ } else {
 104+ wfDebug( "Image did not exist at the specified time.\n" );
 105+ $this->fileExists = false;
 106+ $this->dataLoaded = true;
 107+ }
 108+ }
 109+
 110+ if ( $this->dataLoaded ) {
 111+ wfIncrStats( 'image_cache_hit' );
 112+ } else {
 113+ wfIncrStats( 'image_cache_miss' );
 114+ }
 115+
 116+ wfProfileOut( __METHOD__ );
 117+ return $this->dataLoaded;
 118+ }
 119+
 120+ function saveToCache() {
 121+ // Cache the entire history of the image (up to MAX_CACHE_ROWS).
 122+ // This is expensive, so we only do it if $wgMemc is real
 123+ global $wgMemc;
 124+ if ( $wgMemc instanceof FakeMemcachedClient ) {
 125+ return;
 126+ }
 127+ $key = $this->getCacheKey();
 128+ if ( !$key ) {
 129+ return;
 130+ }
 131+ wfProfileIn( __METHOD__ );
 132+
 133+ $dbr = $this->repo->getSlaveDB();
 134+ $res = $dbr->select( 'oldimage', $this->getCacheFields(),
 135+ array( 'oi_name' => $this->getName() ), __METHOD__,
 136+ array(
 137+ 'LIMIT' => self::MAX_CACHE_ROWS + 1,
 138+ 'ORDER BY' => 'oi_timestamp DESC',
 139+ ));
 140+ $cache = array( 'version' => self::CACHE_VERSION );
 141+ $numRows = $dbr->numRows( $res );
 142+ if ( $numRows > self::MAX_CACHE_ROWS ) {
 143+ $cache['more'] = true;
 144+ $numRows--;
 145+ }
 146+ for ( $i = 0; $i < $numRows; $i++ ) {
 147+ $row = $dbr->fetchObject( $res );
 148+ $this->decodeRow( $row, 'oi_' );
 149+ $cache[$row->oi_timestamp] = $row;
 150+ }
 151+ $dbr->freeResult( $res );
 152+ $wgMemc->set( $key, $cache, 7*86400 /* 1 week */ );
 153+ wfProfileOut( __METHOD__ );
 154+ }
 155+
 156+ function loadFromDB() {
 157+ wfProfileIn( __METHOD__ );
 158+ $dbr = $this->repo->getSlaveDB();
 159+ $conds = array( 'oi_name' => $this->getName() );
 160+ if ( is_null( $this->requestedTimestamp ) ) {
 161+ $conds['oi_archive_name'] = $this->archive_name;
 162+ } else {
 163+ $conds[] = 'oi_timestamp <= ' . $dbr->addQuotes( $this->requestedTimestamp );
 164+ }
 165+ $row = $dbr->selectRow( 'oldimage', $this->getCacheFields( 'oi_' ),
 166+ $conds, __METHOD__, array( 'ORDER BY' => 'oi_timestamp DESC' ) );
 167+ if ( $row ) {
 168+ $this->loadFromRow( $row, 'oi_' );
 169+ } else {
 170+ $this->fileExists = false;
 171+ }
 172+ $this->dataLoaded = true;
 173+ }
 174+
 175+ function getCacheFields( $prefix = 'img_' ) {
 176+ $fields = parent::getCacheFields( $prefix );
 177+ $fields[] = $prefix . 'archive_name';
 178+
 179+ // XXX: Temporary hack before schema update
 180+ $fields = array_diff( $fields, array(
 181+ 'oi_media_type', 'oi_major_mime', 'oi_minor_mime', 'oi_metadata' ) );
 182+ return $fields;
 183+ }
 184+
 185+ function getRel() {
 186+ return 'archive/' . $this->getHashPath() . $this->getArchiveName();
 187+ }
 188+
 189+ function getUrlRel() {
 190+ return 'archive/' . $this->getHashPath() . urlencode( $this->getArchiveName() );
 191+ }
 192+
 193+ function upgradeRow() {
 194+ wfProfileIn( __METHOD__ );
 195+
 196+ $this->loadFromFile();
 197+
 198+ $dbw = $this->repo->getMasterDB();
 199+ list( $major, $minor ) = self::splitMime( $this->mime );
 200+
 201+ wfDebug(__METHOD__.': upgrading '.$this->archive_name." to the current schema\n");
 202+ $dbw->update( 'oldimage',
 203+ array(
 204+ 'oi_width' => $this->width,
 205+ 'oi_height' => $this->height,
 206+ 'oi_bits' => $this->bits,
 207+ #'oi_media_type' => $this->media_type,
 208+ #'oi_major_mime' => $major,
 209+ #'oi_minor_mime' => $minor,
 210+ #'oi_metadata' => $this->metadata,
 211+ ), array( 'oi_name' => $this->getName(), 'oi_timestamp' => $this->requestedTime ),
 212+ __METHOD__
 213+ );
 214+ wfProfileOut( __METHOD__ );
 215+ }
 216+
 217+ // XXX: Temporary hack before schema update
 218+ function maybeUpgradeRow() {}
 219+
 220+}
 221+
 222+
 223+?>
Index: trunk/phase3/includes/filerepo/LocalFile.php
@@ -0,0 +1,1331 @@
 2+<?php
 3+/**
 4+ */
 5+
 6+/**
 7+ * Bump this number when serialized cache records may be incompatible.
 8+ */
 9+define( 'MW_FILE_VERSION', 4 );
 10+
 11+/**
 12+ * Class to represent a local file in the wiki's own database
 13+ *
 14+ * Provides methods to retrieve paths (physical, logical, URL),
 15+ * to generate image thumbnails or for uploading.
 16+ *
 17+ * @addtogroup FileRepo
 18+ */
 19+class LocalFile extends File
 20+{
 21+ /**#@+
 22+ * @private
 23+ */
 24+ var $fileExists, # does the file file exist on disk? (loadFromXxx)
 25+ $historyLine, # Number of line to return by nextHistoryLine() (constructor)
 26+ $historyRes, # result of the query for the file's history (nextHistoryLine)
 27+ $width, # \
 28+ $height, # |
 29+ $bits, # --- returned by getimagesize (loadFromXxx)
 30+ $attr, # /
 31+ $media_type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
 32+ $mime, # MIME type, determined by MimeMagic::guessMimeType
 33+ $major_mime, # Major mime type
 34+ $minor_mine, # Minor mime type
 35+ $size, # Size in bytes (loadFromXxx)
 36+ $metadata, # Metadata
 37+ $timestamp, # Upload timestamp
 38+ $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
 39+ $upgraded; # Whether the row was upgraded on load
 40+
 41+ /**#@-*/
 42+
 43+ function newFromTitle( $title, $repo ) {
 44+ return new self( $title, $repo );
 45+ }
 46+
 47+ function newFromRow( $row, $repo ) {
 48+ $title = Title::makeTitle( NS_IMAGE, $row->img_name );
 49+ $file = new self( $title, $repo );
 50+ $file->loadFromRow( $row );
 51+ return $file;
 52+ }
 53+
 54+ function __construct( $title, $repo ) {
 55+ if( !is_object( $title ) ) {
 56+ throw new MWException( __CLASS__.' constructor given bogus title.' );
 57+ }
 58+ parent::__construct( $title, $repo );
 59+ $this->metadata = '';
 60+ $this->historyLine = 0;
 61+ $this->dataLoaded = false;
 62+ }
 63+
 64+ /**
 65+ * Get the memcached key
 66+ */
 67+ function getCacheKey() {
 68+ $hashedName = md5($this->getName());
 69+ return wfMemcKey( 'file', $hashedName );
 70+ }
 71+
 72+ /**
 73+ * Try to load file metadata from memcached. Returns true on success.
 74+ */
 75+ function loadFromCache() {
 76+ global $wgMemc;
 77+ wfProfileIn( __METHOD__ );
 78+ $this->dataLoaded = false;
 79+ $key = $this->getCacheKey();
 80+ if ( !$key ) {
 81+ return false;
 82+ }
 83+ $cachedValues = $wgMemc->get( $key );
 84+
 85+ // Check if the key existed and belongs to this version of MediaWiki
 86+ if ( isset($cachedValues['version']) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) {
 87+ wfDebug( "Pulling file metadata from cache key $key\n" );
 88+ $this->fileExists = $cachedValues['fileExists'];
 89+ if ( $this->fileExists ) {
 90+ unset( $cachedValues['version'] );
 91+ unset( $cachedValues['fileExists'] );
 92+ foreach ( $cachedValues as $name => $value ) {
 93+ $this->$name = $value;
 94+ }
 95+ }
 96+ }
 97+ if ( $this->dataLoaded ) {
 98+ wfIncrStats( 'image_cache_hit' );
 99+ } else {
 100+ wfIncrStats( 'image_cache_miss' );
 101+ }
 102+
 103+ wfProfileOut( __METHOD__ );
 104+ return $this->dataLoaded;
 105+ }
 106+
 107+ /**
 108+ * Save the file metadata to memcached
 109+ */
 110+ function saveToCache() {
 111+ global $wgMemc;
 112+ $this->load();
 113+ $key = $this->getCacheKey();
 114+ if ( !$key ) {
 115+ return;
 116+ }
 117+ $fields = $this->getCacheFields( '' );
 118+ $cache = array( 'version' => MW_FILE_VERSION );
 119+ $cache['fileExists'] = $this->fileExists;
 120+ if ( $this->fileExists ) {
 121+ foreach ( $fields as $field ) {
 122+ $cache[$field] = $this->$field;
 123+ }
 124+ }
 125+
 126+ $wgMemc->set( $key, $cache, 60 * 60 * 24 * 7 ); // A week
 127+ }
 128+
 129+ /**
 130+ * Load metadata from the file itself
 131+ */
 132+ function loadFromFile() {
 133+ wfProfileIn( __METHOD__ );
 134+ $path = $this->getPath();
 135+ $this->fileExists = file_exists( $path );
 136+ $gis = array();
 137+
 138+ if ( $this->fileExists ) {
 139+ $magic=& MimeMagic::singleton();
 140+
 141+ $this->mime = $magic->guessMimeType($path,true);
 142+ list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
 143+ $this->media_type = $magic->getMediaType($path,$this->mime);
 144+ $handler = MediaHandler::getHandler( $this->mime );
 145+
 146+ # Get size in bytes
 147+ $this->size = filesize( $path );
 148+
 149+ # Height, width and metadata
 150+ if ( $handler ) {
 151+ $gis = $handler->getImageSize( $this, $path );
 152+ $this->metadata = $handler->getMetadata( $this, $path );
 153+ } else {
 154+ $gis = false;
 155+ $this->metadata = '';
 156+ }
 157+
 158+ wfDebug(__METHOD__.": $path loaded, {$this->size} bytes, {$this->mime}.\n");
 159+ } else {
 160+ $this->mime = NULL;
 161+ $this->media_type = MEDIATYPE_UNKNOWN;
 162+ $this->metadata = '';
 163+ wfDebug(__METHOD__.": $path NOT FOUND!\n");
 164+ }
 165+
 166+ if( $gis ) {
 167+ $this->width = $gis[0];
 168+ $this->height = $gis[1];
 169+ } else {
 170+ $this->width = 0;
 171+ $this->height = 0;
 172+ }
 173+
 174+ #NOTE: $gis[2] contains a code for the image type. This is no longer used.
 175+
 176+ #NOTE: we have to set this flag early to avoid load() to be called
 177+ # be some of the functions below. This may lead to recursion or other bad things!
 178+ # as ther's only one thread of execution, this should be safe anyway.
 179+ $this->dataLoaded = true;
 180+
 181+ if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
 182+ else $this->bits = 0;
 183+
 184+ wfProfileOut( __METHOD__ );
 185+ }
 186+
 187+ function getCacheFields( $prefix = 'img_' ) {
 188+ static $fields = array( 'size', 'width', 'height', 'bits', 'media_type',
 189+ 'major_mime', 'minor_mime', 'metadata', 'timestamp' );
 190+ static $results = array();
 191+ if ( $prefix == '' ) {
 192+ return $fields;
 193+ }
 194+ if ( !isset( $results[$prefix] ) ) {
 195+ $prefixedFields = array();
 196+ foreach ( $fields as $field ) {
 197+ $prefixedFields[] = $prefix . $field;
 198+ }
 199+ $results[$prefix] = $prefixedFields;
 200+ }
 201+ return $results[$prefix];
 202+ }
 203+
 204+ /**
 205+ * Load file metadata from the DB
 206+ */
 207+ function loadFromDB() {
 208+ wfProfileIn( __METHOD__ );
 209+
 210+ # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
 211+ $this->dataLoaded = true;
 212+
 213+ $dbr = $this->repo->getSlaveDB();
 214+
 215+ $row = $dbr->selectRow( 'image', $this->getCacheFields( 'img_' ),
 216+ array( 'img_name' => $this->getName() ), __METHOD__ );
 217+ if ( $row ) {
 218+ $this->loadFromRow( $row );
 219+ } else {
 220+ $this->fileExists = false;
 221+ }
 222+
 223+ wfProfileOut( __METHOD__ );
 224+ }
 225+
 226+ /**
 227+ * Decode a row from the database (either object or array) to an array
 228+ * with timestamps and MIME types decoded, and the field prefix removed.
 229+ */
 230+ function decodeRow( $row, $prefix = 'img_' ) {
 231+ $array = (array)$row;
 232+ $prefixLength = strlen( $prefix );
 233+ // Sanity check prefix once
 234+ if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
 235+ throw new MWException( __METHOD__. ': incorrect $prefix parameter' );
 236+ }
 237+ $decoded = array();
 238+ foreach ( $array as $name => $value ) {
 239+ $deprefixedName = substr( $name, $prefixLength );
 240+ $decoded[substr( $name, $prefixLength )] = $value;
 241+ }
 242+ $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
 243+ if ( empty( $decoded['major_mime'] ) ) {
 244+ $decoded['mime'] = "unknown/unknown";
 245+ } else {
 246+ if (!$decoded['minor_mime']) {
 247+ $decoded['minor_mime'] = "unknown";
 248+ }
 249+ $decoded['mime'] = $decoded['major_mime'].'/'.$decoded['minor_mime'];
 250+ }
 251+ return $decoded;
 252+ }
 253+
 254+ /*
 255+ * Load file metadata from a DB result row
 256+ */
 257+ function loadFromRow( $row, $prefix = 'img_' ) {
 258+ $array = $this->decodeRow( $row, $prefix );
 259+ foreach ( $array as $name => $value ) {
 260+ $this->$name = $value;
 261+ }
 262+ $this->fileExists = true;
 263+ // Check for rows from a previous schema, quietly upgrade them
 264+ $this->maybeUpgradeRow();
 265+ }
 266+
 267+ /**
 268+ * Load file metadata from cache or DB, unless already loaded
 269+ */
 270+ function load() {
 271+ if ( !$this->dataLoaded ) {
 272+ if ( !$this->loadFromCache() ) {
 273+ $this->loadFromDB();
 274+ $this->saveToCache();
 275+ }
 276+ $this->dataLoaded = true;
 277+ }
 278+ }
 279+
 280+ /**
 281+ * Upgrade a row if it needs it
 282+ */
 283+ function maybeUpgradeRow() {
 284+ if ( wfReadOnly() ) {
 285+ return;
 286+ }
 287+ if ( is_null($this->media_type) || $this->mime == 'image/svg' ) {
 288+ $this->upgradeRow();
 289+ $this->upgraded = true;
 290+ } else {
 291+ $handler = $this->getHandler();
 292+ if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
 293+ $this->upgradeRow();
 294+ $this->upgraded = true;
 295+ }
 296+ }
 297+ }
 298+
 299+ function getUpgraded() {
 300+ return $this->upgraded;
 301+ }
 302+
 303+ /**
 304+ * Fix assorted version-related problems with the image row by reloading it from the file
 305+ */
 306+ function upgradeRow() {
 307+ wfProfileIn( __METHOD__ );
 308+
 309+ $this->loadFromFile();
 310+
 311+ $dbw = $this->repo->getMasterDB();
 312+ list( $major, $minor ) = self::splitMime( $this->mime );
 313+
 314+ wfDebug(__METHOD__.': upgrading '.$this->getName()." to the current schema\n");
 315+
 316+ $dbw->update( 'image',
 317+ array(
 318+ 'img_width' => $this->width,
 319+ 'img_height' => $this->height,
 320+ 'img_bits' => $this->bits,
 321+ 'img_media_type' => $this->media_type,
 322+ 'img_major_mime' => $major,
 323+ 'img_minor_mime' => $minor,
 324+ 'img_metadata' => $this->metadata,
 325+ ), array( 'img_name' => $this->getName() ),
 326+ __METHOD__
 327+ );
 328+ $this->saveToCache();
 329+ wfProfileOut( __METHOD__ );
 330+ }
 331+
 332+ /** splitMime inherited */
 333+ /** getName inherited */
 334+ /** getTitle inherited */
 335+ /** getURL inherited */
 336+ /** getViewURL inherited */
 337+ /** getPath inherited */
 338+
 339+ /**
 340+ * Return the width of the image
 341+ *
 342+ * Returns false on error
 343+ * @public
 344+ */
 345+ function getWidth( $page = 1 ) {
 346+ $this->load();
 347+ if ( $this->isMultipage() ) {
 348+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
 349+ if ( $dim ) {
 350+ return $dim['width'];
 351+ } else {
 352+ return false;
 353+ }
 354+ } else {
 355+ return $this->width;
 356+ }
 357+ }
 358+
 359+ /**
 360+ * Return the height of the image
 361+ *
 362+ * Returns false on error
 363+ * @public
 364+ */
 365+ function getHeight( $page = 1 ) {
 366+ $this->load();
 367+ if ( $this->isMultipage() ) {
 368+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
 369+ if ( $dim ) {
 370+ return $dim['height'];
 371+ } else {
 372+ return false;
 373+ }
 374+ } else {
 375+ return $this->height;
 376+ }
 377+ }
 378+
 379+ /**
 380+ * Get handler-specific metadata
 381+ */
 382+ function getMetadata() {
 383+ $this->load();
 384+ return $this->metadata;
 385+ }
 386+
 387+ /**
 388+ * Return the size of the image file, in bytes
 389+ * @public
 390+ */
 391+ function getSize() {
 392+ $this->load();
 393+ return $this->size;
 394+ }
 395+
 396+ /**
 397+ * Returns the mime type of the file.
 398+ */
 399+ function getMimeType() {
 400+ $this->load();
 401+ return $this->mime;
 402+ }
 403+
 404+ /**
 405+ * Return the type of the media in the file.
 406+ * Use the value returned by this function with the MEDIATYPE_xxx constants.
 407+ */
 408+ function getMediaType() {
 409+ $this->load();
 410+ return $this->media_type;
 411+ }
 412+
 413+ /** canRender inherited */
 414+ /** mustRender inherited */
 415+ /** allowInlineDisplay inherited */
 416+ /** isSafeFile inherited */
 417+ /** isTrustedFile inherited */
 418+
 419+ /**
 420+ * Returns true if the file file exists on disk.
 421+ * @return boolean Whether file file exist on disk.
 422+ * @public
 423+ */
 424+ function exists() {
 425+ $this->load();
 426+ return $this->fileExists;
 427+ }
 428+
 429+ /** getTransformScript inherited */
 430+ /** getUnscaledThumb inherited */
 431+ /** thumbName inherited */
 432+ /** createThumb inherited */
 433+ /** getThumbnail inherited */
 434+ /** transform inherited */
 435+
 436+ /**
 437+ * Fix thumbnail files from 1.4 or before, with extreme prejudice
 438+ */
 439+ function migrateThumbFile( $thumbName ) {
 440+ $thumbDir = $this->getThumbPath();
 441+ $thumbPath = "$thumbDir/$thumbName";
 442+ if ( is_dir( $thumbPath ) ) {
 443+ // Directory where file should be
 444+ // This happened occasionally due to broken migration code in 1.5
 445+ // Rename to broken-*
 446+ for ( $i = 0; $i < 100 ; $i++ ) {
 447+ $broken = $this->repo->getZonePath('public') . "/broken-$i-$thumbName";
 448+ if ( !file_exists( $broken ) ) {
 449+ rename( $thumbPath, $broken );
 450+ break;
 451+ }
 452+ }
 453+ // Doesn't exist anymore
 454+ clearstatcache();
 455+ }
 456+ if ( is_file( $thumbDir ) ) {
 457+ // File where directory should be
 458+ unlink( $thumbDir );
 459+ // Doesn't exist anymore
 460+ clearstatcache();
 461+ }
 462+ }
 463+
 464+ /** getHandler inherited */
 465+ /** iconThumb inherited */
 466+ /** getLastError inherited */
 467+
 468+ /**
 469+ * Get all thumbnail names previously generated for this file
 470+ */
 471+ function getThumbnails() {
 472+ if ( $this->isHashed() ) {
 473+ $this->load();
 474+ $files = array();
 475+ $dir = $this->getThumbPath();
 476+
 477+ if ( is_dir( $dir ) ) {
 478+ $handle = opendir( $dir );
 479+
 480+ if ( $handle ) {
 481+ while ( false !== ( $file = readdir($handle) ) ) {
 482+ if ( $file{0} != '.' ) {
 483+ $files[] = $file;
 484+ }
 485+ }
 486+ closedir( $handle );
 487+ }
 488+ }
 489+ } else {
 490+ $files = array();
 491+ }
 492+
 493+ return $files;
 494+ }
 495+
 496+ /**
 497+ * Refresh metadata in memcached, but don't touch thumbnails or squid
 498+ */
 499+ function purgeMetadataCache() {
 500+ clearstatcache();
 501+ $this->loadFromFile();
 502+ $this->saveToCache();
 503+ }
 504+
 505+ /**
 506+ * Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid
 507+ */
 508+ function purgeCache( $archiveFiles = array() ) {
 509+ global $wgUseSquid;
 510+
 511+ // Refresh metadata cache
 512+ $this->purgeMetadataCache();
 513+
 514+ // Delete thumbnails
 515+ $files = $this->getThumbnails();
 516+ $dir = $this->getThumbPath();
 517+ $urls = array();
 518+ foreach ( $files as $file ) {
 519+ $m = array();
 520+ # Check that the base file name is part of the thumb name
 521+ # This is a basic sanity check to avoid erasing unrelated directories
 522+ if ( strpos( $file, $this->getName() ) !== false ) {
 523+ $url = $this->getThumbUrl( $file );
 524+ $urls[] = $url;
 525+ @unlink( "$dir/$file" );
 526+ }
 527+ }
 528+
 529+ // Purge the squid
 530+ if ( $wgUseSquid ) {
 531+ $urls[] = $this->getURL();
 532+ foreach ( $archiveFiles as $file ) {
 533+ $urls[] = $this->getArchiveUrl( $file );
 534+ }
 535+ wfPurgeSquidServers( $urls );
 536+ }
 537+ }
 538+
 539+ /** purgeDescription inherited */
 540+ /** purgeEverything inherited */
 541+
 542+ /**
 543+ * Return the history of this file, line by line.
 544+ * starts with current version, then old versions.
 545+ * uses $this->historyLine to check which line to return:
 546+ * 0 return line for current version
 547+ * 1 query for old versions, return first one
 548+ * 2, ... return next old version from above query
 549+ *
 550+ * @public
 551+ */
 552+ function nextHistoryLine() {
 553+ $dbr = $this->repo->getSlaveDB();
 554+
 555+ if ( $this->historyLine == 0 ) {// called for the first time, return line from cur
 556+ $this->historyRes = $dbr->select( 'image',
 557+ array(
 558+ 'img_size',
 559+ 'img_description',
 560+ 'img_user','img_user_text',
 561+ 'img_timestamp',
 562+ 'img_width',
 563+ 'img_height',
 564+ "'' AS oi_archive_name"
 565+ ),
 566+ array( 'img_name' => $this->title->getDBkey() ),
 567+ __METHOD__
 568+ );
 569+ if ( 0 == $dbr->numRows( $this->historyRes ) ) {
 570+ return FALSE;
 571+ }
 572+ } else if ( $this->historyLine == 1 ) {
 573+ $this->historyRes = $dbr->select( 'oldimage',
 574+ array(
 575+ 'oi_size AS img_size',
 576+ 'oi_description AS img_description',
 577+ 'oi_user AS img_user',
 578+ 'oi_user_text AS img_user_text',
 579+ 'oi_timestamp AS img_timestamp',
 580+ 'oi_width as img_width',
 581+ 'oi_height as img_height',
 582+ 'oi_archive_name'
 583+ ),
 584+ array( 'oi_name' => $this->title->getDBkey() ),
 585+ __METHOD__,
 586+ array( 'ORDER BY' => 'oi_timestamp DESC' )
 587+ );
 588+ }
 589+ $this->historyLine ++;
 590+
 591+ return $dbr->fetchObject( $this->historyRes );
 592+ }
 593+
 594+ /**
 595+ * Reset the history pointer to the first element of the history
 596+ * @public
 597+ */
 598+ function resetHistory() {
 599+ $this->historyLine = 0;
 600+ }
 601+
 602+ /** getFullPath inherited */
 603+ /** getHashPath inherited */
 604+ /** getRel inherited */
 605+ /** getUrlRel inherited */
 606+ /** getArchivePath inherited */
 607+ /** getThumbPath inherited */
 608+ /** getArchiveUrl inherited */
 609+ /** getThumbUrl inherited */
 610+ /** getArchiveVirtualUrl inherited */
 611+ /** getThumbVirtualUrl inherited */
 612+ /** isHashed inherited */
 613+
 614+ /**
 615+ * Record a file upload in the upload log and the image table
 616+ */
 617+ function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
 618+ $watch = false, $timestamp = false )
 619+ {
 620+ global $wgUser, $wgUseCopyrightUpload;
 621+
 622+ $dbw = $this->repo->getMasterDB();
 623+
 624+ // Delete thumbnails and refresh the metadata cache
 625+ $this->purgeCache();
 626+
 627+ // Fail now if the file isn't there
 628+ if ( !$this->fileExists ) {
 629+ wfDebug( __METHOD__.": File ".$this->getPath()." went missing!\n" );
 630+ return false;
 631+ }
 632+
 633+ if ( $wgUseCopyrightUpload ) {
 634+ if ( $license != '' ) {
 635+ $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
 636+ }
 637+ $textdesc = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n" .
 638+ '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
 639+ "$licensetxt" .
 640+ '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
 641+ } else {
 642+ if ( $license != '' ) {
 643+ $filedesc = $desc == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $desc . "\n";
 644+ $textdesc = $filedesc .
 645+ '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
 646+ } else {
 647+ $textdesc = $desc;
 648+ }
 649+ }
 650+
 651+ if ( $timestamp === false ) {
 652+ $timestamp = $dbw->timestamp();
 653+ }
 654+
 655+ #split mime type
 656+ if (strpos($this->mime,'/')!==false) {
 657+ list($major,$minor)= explode('/',$this->mime,2);
 658+ }
 659+ else {
 660+ $major= $this->mime;
 661+ $minor= "unknown";
 662+ }
 663+
 664+ # Test to see if the row exists using INSERT IGNORE
 665+ # This avoids race conditions by locking the row until the commit, and also
 666+ # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
 667+ $dbw->insert( 'image',
 668+ array(
 669+ 'img_name' => $this->getName(),
 670+ 'img_size'=> $this->size,
 671+ 'img_width' => intval( $this->width ),
 672+ 'img_height' => intval( $this->height ),
 673+ 'img_bits' => $this->bits,
 674+ 'img_media_type' => $this->media_type,
 675+ 'img_major_mime' => $major,
 676+ 'img_minor_mime' => $minor,
 677+ 'img_timestamp' => $timestamp,
 678+ 'img_description' => $desc,
 679+ 'img_user' => $wgUser->getID(),
 680+ 'img_user_text' => $wgUser->getName(),
 681+ 'img_metadata' => $this->metadata,
 682+ ),
 683+ __METHOD__,
 684+ 'IGNORE'
 685+ );
 686+
 687+ if( $dbw->affectedRows() == 0 ) {
 688+ # Collision, this is an update of a file
 689+ # Insert previous contents into oldimage
 690+ $dbw->insertSelect( 'oldimage', 'image',
 691+ array(
 692+ 'oi_name' => 'img_name',
 693+ 'oi_archive_name' => $dbw->addQuotes( $oldver ),
 694+ 'oi_size' => 'img_size',
 695+ 'oi_width' => 'img_width',
 696+ 'oi_height' => 'img_height',
 697+ 'oi_bits' => 'img_bits',
 698+ 'oi_timestamp' => 'img_timestamp',
 699+ 'oi_description' => 'img_description',
 700+ 'oi_user' => 'img_user',
 701+ 'oi_user_text' => 'img_user_text',
 702+ ), array( 'img_name' => $this->getName() ), __METHOD__
 703+ );
 704+
 705+ # Update the current image row
 706+ $dbw->update( 'image',
 707+ array( /* SET */
 708+ 'img_size' => $this->size,
 709+ 'img_width' => intval( $this->width ),
 710+ 'img_height' => intval( $this->height ),
 711+ 'img_bits' => $this->bits,
 712+ 'img_media_type' => $this->media_type,
 713+ 'img_major_mime' => $major,
 714+ 'img_minor_mime' => $minor,
 715+ 'img_timestamp' => $timestamp,
 716+ 'img_description' => $desc,
 717+ 'img_user' => $wgUser->getID(),
 718+ 'img_user_text' => $wgUser->getName(),
 719+ 'img_metadata' => $this->metadata,
 720+ ), array( /* WHERE */
 721+ 'img_name' => $this->getName()
 722+ ), __METHOD__
 723+ );
 724+ } else {
 725+ # This is a new file
 726+ # Update the image count
 727+ $site_stats = $dbw->tableName( 'site_stats' );
 728+ $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
 729+ }
 730+
 731+ $descTitle = $this->getTitle();
 732+ $article = new Article( $descTitle );
 733+ $minor = false;
 734+ $watch = $watch || $wgUser->isWatched( $descTitle );
 735+ $suppressRC = true; // There's already a log entry, so don't double the RC load
 736+
 737+ if( $descTitle->exists() ) {
 738+ // TODO: insert a null revision into the page history for this update.
 739+ if( $watch ) {
 740+ $wgUser->addWatch( $descTitle );
 741+ }
 742+
 743+ # Invalidate the cache for the description page
 744+ $descTitle->invalidateCache();
 745+ $descTitle->purgeSquid();
 746+ } else {
 747+ // New file; create the description page.
 748+ $article->insertNewArticle( $textdesc, $desc, $minor, $watch, $suppressRC );
 749+ }
 750+
 751+ # Hooks, hooks, the magic of hooks...
 752+ wfRunHooks( 'FileUpload', array( $this ) );
 753+
 754+ # Add the log entry
 755+ $log = new LogPage( 'upload' );
 756+ $log->addEntry( 'upload', $descTitle, $desc );
 757+
 758+ # Commit the transaction now, in case something goes wrong later
 759+ # The most important thing is that files don't get lost, especially archives
 760+ $dbw->immediateCommit();
 761+
 762+ # Invalidate cache for all pages using this file
 763+ $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
 764+ $update->doUpdate();
 765+
 766+ return true;
 767+ }
 768+
 769+ /**
 770+ * Move or copy a file to its public location. If a file exists at the
 771+ * destination, move it to an archive. Returns the archive name on success
 772+ * or an empty string if it was a new file, and a wikitext-formatted
 773+ * WikiError object on failure.
 774+ *
 775+ * The archive name should be passed through to recordUpload for database
 776+ * registration.
 777+ *
 778+ * @param string $sourcePath Local filesystem path to the source image
 779+ * @param integer $flags A bitwise combination of:
 780+ * File::DELETE_SOURCE Delete the source file, i.e. move
 781+ * rather than copy
 782+ * @return The archive name on success or an empty string if it was a new
 783+ * file, and a wikitext-formatted WikiError object on failure.
 784+ */
 785+ function publish( $srcPath, $flags = 0 ) {
 786+ $dstPath = $this->getFullPath();
 787+ $archiveName = gmdate( 'YmdHis' ) . '!'. $this->getName();
 788+ $archivePath = $this->getArchivePath( $archiveName );
 789+ $flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
 790+ $status = $this->repo->publish( $srcPath, $dstPath, $archivePath, $flags );
 791+ if ( WikiError::isError( $status ) ) {
 792+ return $status;
 793+ } elseif ( $status == 'new' ) {
 794+ return '';
 795+ } else {
 796+ return $archiveName;
 797+ }
 798+ }
 799+
 800+ /** getLinksTo inherited */
 801+ /** getExifData inherited */
 802+ /** isLocal inherited */
 803+ /** wasDeleted inherited */
 804+
 805+ /**
 806+ * Delete all versions of the file.
 807+ *
 808+ * Moves the files into an archive directory (or deletes them)
 809+ * and removes the database rows.
 810+ *
 811+ * Cache purging is done; logging is caller's responsibility.
 812+ *
 813+ * @param $reason
 814+ * @return true on success, false on some kind of failure
 815+ */
 816+ function delete( $reason, $suppress=false ) {
 817+ $transaction = new FSTransaction();
 818+ $urlArr = array( $this->getURL() );
 819+
 820+ if( !FileStore::lock() ) {
 821+ wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
 822+ return false;
 823+ }
 824+
 825+ try {
 826+ $dbw = $this->repo->getMasterDB();
 827+ $dbw->begin();
 828+
 829+ // Delete old versions
 830+ $result = $dbw->select( 'oldimage',
 831+ array( 'oi_archive_name' ),
 832+ array( 'oi_name' => $this->getName() ) );
 833+
 834+ while( $row = $dbw->fetchObject( $result ) ) {
 835+ $oldName = $row->oi_archive_name;
 836+
 837+ $transaction->add( $this->prepareDeleteOld( $oldName, $reason, $suppress ) );
 838+
 839+ // We'll need to purge this URL from caches...
 840+ $urlArr[] = $this->getArchiveUrl( $oldName );
 841+ }
 842+ $dbw->freeResult( $result );
 843+
 844+ // And the current version...
 845+ $transaction->add( $this->prepareDeleteCurrent( $reason, $suppress ) );
 846+
 847+ $dbw->immediateCommit();
 848+ } catch( MWException $e ) {
 849+ wfDebug( __METHOD__.": db error, rolling back file transactions\n" );
 850+ $transaction->rollback();
 851+ FileStore::unlock();
 852+ throw $e;
 853+ }
 854+
 855+ wfDebug( __METHOD__.": deleted db items, applying file transactions\n" );
 856+ $transaction->commit();
 857+ FileStore::unlock();
 858+
 859+
 860+ // Update site_stats
 861+ $site_stats = $dbw->tableName( 'site_stats' );
 862+ $dbw->query( "UPDATE $site_stats SET ss_images=ss_images-1", __METHOD__ );
 863+
 864+ $this->purgeEverything( $urlArr );
 865+
 866+ return true;
 867+ }
 868+
 869+
 870+ /**
 871+ * Delete an old version of the file.
 872+ *
 873+ * Moves the file into an archive directory (or deletes it)
 874+ * and removes the database row.
 875+ *
 876+ * Cache purging is done; logging is caller's responsibility.
 877+ *
 878+ * @param $reason
 879+ * @throws MWException or FSException on database or filestore failure
 880+ * @return true on success, false on some kind of failure
 881+ */
 882+ function deleteOld( $archiveName, $reason, $suppress=false ) {
 883+ $transaction = new FSTransaction();
 884+ $urlArr = array();
 885+
 886+ if( !FileStore::lock() ) {
 887+ wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
 888+ return false;
 889+ }
 890+
 891+ $transaction = new FSTransaction();
 892+ try {
 893+ $dbw = $this->repo->getMasterDB();
 894+ $dbw->begin();
 895+ $transaction->add( $this->prepareDeleteOld( $archiveName, $reason, $suppress ) );
 896+ $dbw->immediateCommit();
 897+ } catch( MWException $e ) {
 898+ wfDebug( __METHOD__.": db error, rolling back file transaction\n" );
 899+ $transaction->rollback();
 900+ FileStore::unlock();
 901+ throw $e;
 902+ }
 903+
 904+ wfDebug( __METHOD__.": deleted db items, applying file transaction\n" );
 905+ $transaction->commit();
 906+ FileStore::unlock();
 907+
 908+ $this->purgeDescription();
 909+
 910+ // Squid purging
 911+ global $wgUseSquid;
 912+ if ( $wgUseSquid ) {
 913+ $urlArr = array(
 914+ $this->getArchiveUrl( $archiveName ),
 915+ );
 916+ wfPurgeSquidServers( $urlArr );
 917+ }
 918+ return true;
 919+ }
 920+
 921+ /**
 922+ * Delete the current version of a file.
 923+ * May throw a database error.
 924+ * @return true on success, false on failure
 925+ */
 926+ private function prepareDeleteCurrent( $reason, $suppress=false ) {
 927+ return $this->prepareDeleteVersion(
 928+ $this->getFullPath(),
 929+ $reason,
 930+ 'image',
 931+ array(
 932+ 'fa_name' => 'img_name',
 933+ 'fa_archive_name' => 'NULL',
 934+ 'fa_size' => 'img_size',
 935+ 'fa_width' => 'img_width',
 936+ 'fa_height' => 'img_height',
 937+ 'fa_metadata' => 'img_metadata',
 938+ 'fa_bits' => 'img_bits',
 939+ 'fa_media_type' => 'img_media_type',
 940+ 'fa_major_mime' => 'img_major_mime',
 941+ 'fa_minor_mime' => 'img_minor_mime',
 942+ 'fa_description' => 'img_description',
 943+ 'fa_user' => 'img_user',
 944+ 'fa_user_text' => 'img_user_text',
 945+ 'fa_timestamp' => 'img_timestamp' ),
 946+ array( 'img_name' => $this->getName() ),
 947+ $suppress,
 948+ __METHOD__ );
 949+ }
 950+
 951+ /**
 952+ * Delete a given older version of a file.
 953+ * May throw a database error.
 954+ * @return true on success, false on failure
 955+ */
 956+ private function prepareDeleteOld( $archiveName, $reason, $suppress=false ) {
 957+ $oldpath = $this->getArchivePath() .
 958+ DIRECTORY_SEPARATOR . $archiveName;
 959+ return $this->prepareDeleteVersion(
 960+ $oldpath,
 961+ $reason,
 962+ 'oldimage',
 963+ array(
 964+ 'fa_name' => 'oi_name',
 965+ 'fa_archive_name' => 'oi_archive_name',
 966+ 'fa_size' => 'oi_size',
 967+ 'fa_width' => 'oi_width',
 968+ 'fa_height' => 'oi_height',
 969+ 'fa_metadata' => 'NULL',
 970+ 'fa_bits' => 'oi_bits',
 971+ 'fa_media_type' => 'NULL',
 972+ 'fa_major_mime' => 'NULL',
 973+ 'fa_minor_mime' => 'NULL',
 974+ 'fa_description' => 'oi_description',
 975+ 'fa_user' => 'oi_user',
 976+ 'fa_user_text' => 'oi_user_text',
 977+ 'fa_timestamp' => 'oi_timestamp' ),
 978+ array(
 979+ 'oi_name' => $this->getName(),
 980+ 'oi_archive_name' => $archiveName ),
 981+ $suppress,
 982+ __METHOD__ );
 983+ }
 984+
 985+ /**
 986+ * Do the dirty work of backing up an image row and its file
 987+ * (if $wgSaveDeletedFiles is on) and removing the originals.
 988+ *
 989+ * Must be run while the file store is locked and a database
 990+ * transaction is open to avoid race conditions.
 991+ *
 992+ * @return FSTransaction
 993+ */
 994+ private function prepareDeleteVersion( $path, $reason, $table, $fieldMap, $where, $suppress=false, $fname ) {
 995+ global $wgUser, $wgSaveDeletedFiles;
 996+
 997+ // Dupe the file into the file store
 998+ if( file_exists( $path ) ) {
 999+ if( $wgSaveDeletedFiles ) {
 1000+ $group = 'deleted';
 1001+
 1002+ $store = FileStore::get( $group );
 1003+ $key = FileStore::calculateKey( $path, $this->getExtension() );
 1004+ $transaction = $store->insert( $key, $path,
 1005+ FileStore::DELETE_ORIGINAL );
 1006+ } else {
 1007+ $group = null;
 1008+ $key = null;
 1009+ $transaction = FileStore::deleteFile( $path );
 1010+ }
 1011+ } else {
 1012+ wfDebug( __METHOD__." deleting already-missing '$path'; moving on to database\n" );
 1013+ $group = null;
 1014+ $key = null;
 1015+ $transaction = new FSTransaction(); // empty
 1016+ }
 1017+
 1018+ if( $transaction === false ) {
 1019+ // Fail to restore?
 1020+ wfDebug( __METHOD__.": import to file store failed, aborting\n" );
 1021+ throw new MWException( "Could not archive and delete file $path" );
 1022+ return false;
 1023+ }
 1024+
 1025+ // Bitfields to further supress the file content
 1026+ // Note that currently, live files are stored elsewhere
 1027+ // and cannot be partially deleted
 1028+ $bitfield = 0;
 1029+ if ( $suppress ) {
 1030+ $bitfield |= self::DELETED_FILE;
 1031+ $bitfield |= self::DELETED_COMMENT;
 1032+ $bitfield |= self::DELETED_USER;
 1033+ $bitfield |= self::DELETED_RESTRICTED;
 1034+ }
 1035+
 1036+ $dbw = $this->repo->getMasterDB();
 1037+ $storageMap = array(
 1038+ 'fa_storage_group' => $dbw->addQuotes( $group ),
 1039+ 'fa_storage_key' => $dbw->addQuotes( $key ),
 1040+
 1041+ 'fa_deleted_user' => $dbw->addQuotes( $wgUser->getId() ),
 1042+ 'fa_deleted_timestamp' => $dbw->timestamp(),
 1043+ 'fa_deleted_reason' => $dbw->addQuotes( $reason ),
 1044+ 'fa_deleted' => $bitfield);
 1045+ $allFields = array_merge( $storageMap, $fieldMap );
 1046+
 1047+ try {
 1048+ if( $wgSaveDeletedFiles ) {
 1049+ $dbw->insertSelect( 'filearchive', $table, $allFields, $where, $fname );
 1050+ }
 1051+ $dbw->delete( $table, $where, $fname );
 1052+ } catch( DBQueryError $e ) {
 1053+ // Something went horribly wrong!
 1054+ // Leave the file as it was...
 1055+ wfDebug( __METHOD__.": database error, rolling back file transaction\n" );
 1056+ $transaction->rollback();
 1057+ throw $e;
 1058+ }
 1059+
 1060+ return $transaction;
 1061+ }
 1062+
 1063+ /**
 1064+ * Restore all or specified deleted revisions to the given file.
 1065+ * Permissions and logging are left to the caller.
 1066+ *
 1067+ * May throw database exceptions on error.
 1068+ *
 1069+ * @param $versions set of record ids of deleted items to restore,
 1070+ * or empty to restore all revisions.
 1071+ * @return the number of file revisions restored if successful,
 1072+ * or false on failure
 1073+ */
 1074+ function restore( $versions=array(), $Unsuppress=false ) {
 1075+ global $wgUser;
 1076+
 1077+ if( !FileStore::lock() ) {
 1078+ wfDebug( __METHOD__." could not acquire filestore lock\n" );
 1079+ return false;
 1080+ }
 1081+
 1082+ $transaction = new FSTransaction();
 1083+ try {
 1084+ $dbw = $this->repo->getMasterDB();
 1085+ $dbw->begin();
 1086+
 1087+ // Re-confirm whether this file presently exists;
 1088+ // if no we'll need to create an file record for the
 1089+ // first item we restore.
 1090+ $exists = $dbw->selectField( 'image', '1',
 1091+ array( 'img_name' => $this->getName() ),
 1092+ __METHOD__ );
 1093+
 1094+ // Fetch all or selected archived revisions for the file,
 1095+ // sorted from the most recent to the oldest.
 1096+ $conditions = array( 'fa_name' => $this->getName() );
 1097+ if( $versions ) {
 1098+ $conditions['fa_id'] = $versions;
 1099+ }
 1100+
 1101+ $result = $dbw->select( 'filearchive', '*',
 1102+ $conditions,
 1103+ __METHOD__,
 1104+ array( 'ORDER BY' => 'fa_timestamp DESC' ) );
 1105+
 1106+ if( $dbw->numRows( $result ) < count( $versions ) ) {
 1107+ // There's some kind of conflict or confusion;
 1108+ // we can't restore everything we were asked to.
 1109+ wfDebug( __METHOD__.": couldn't find requested items\n" );
 1110+ $dbw->rollback();
 1111+ FileStore::unlock();
 1112+ return false;
 1113+ }
 1114+
 1115+ if( $dbw->numRows( $result ) == 0 ) {
 1116+ // Nothing to do.
 1117+ wfDebug( __METHOD__.": nothing to do\n" );
 1118+ $dbw->rollback();
 1119+ FileStore::unlock();
 1120+ return true;
 1121+ }
 1122+
 1123+ $revisions = 0;
 1124+ while( $row = $dbw->fetchObject( $result ) ) {
 1125+ if ( $Unsuppress ) {
 1126+ // Currently, fa_deleted flags fall off upon restore, lets be careful about this
 1127+ } else if ( ($row->fa_deleted & Revision::DELETED_RESTRICTED) && !$wgUser->isAllowed('hiderevision') ) {
 1128+ // Skip restoring file revisions that the user cannot restore
 1129+ continue;
 1130+ }
 1131+ $revisions++;
 1132+ $store = FileStore::get( $row->fa_storage_group );
 1133+ if( !$store ) {
 1134+ wfDebug( __METHOD__.": skipping row with no file.\n" );
 1135+ continue;
 1136+ }
 1137+
 1138+ $restoredImage = new self( $row->fa_name, $this->repo );
 1139+
 1140+ if( $revisions == 1 && !$exists ) {
 1141+ $destPath = $restoredImage->getFullPath();
 1142+ $destDir = dirname( $destPath );
 1143+ if ( !is_dir( $destDir ) ) {
 1144+ wfMkdirParents( $destDir );
 1145+ }
 1146+
 1147+ // We may have to fill in data if this was originally
 1148+ // an archived file revision.
 1149+ if( is_null( $row->fa_metadata ) ) {
 1150+ $tempFile = $store->filePath( $row->fa_storage_key );
 1151+
 1152+ $magic = MimeMagic::singleton();
 1153+ $mime = $magic->guessMimeType( $tempFile, true );
 1154+ $media_type = $magic->getMediaType( $tempFile, $mime );
 1155+ list( $major_mime, $minor_mime ) = self::splitMime( $mime );
 1156+ $handler = MediaHandler::getHandler( $mime );
 1157+ if ( $handler ) {
 1158+ $metadata = $handler->getMetadata( false, $tempFile );
 1159+ } else {
 1160+ $metadata = '';
 1161+ }
 1162+ } else {
 1163+ $metadata = $row->fa_metadata;
 1164+ $major_mime = $row->fa_major_mime;
 1165+ $minor_mime = $row->fa_minor_mime;
 1166+ $media_type = $row->fa_media_type;
 1167+ }
 1168+
 1169+ $table = 'image';
 1170+ $fields = array(
 1171+ 'img_name' => $row->fa_name,
 1172+ 'img_size' => $row->fa_size,
 1173+ 'img_width' => $row->fa_width,
 1174+ 'img_height' => $row->fa_height,
 1175+ 'img_metadata' => $metadata,
 1176+ 'img_bits' => $row->fa_bits,
 1177+ 'img_media_type' => $media_type,
 1178+ 'img_major_mime' => $major_mime,
 1179+ 'img_minor_mime' => $minor_mime,
 1180+ 'img_description' => $row->fa_description,
 1181+ 'img_user' => $row->fa_user,
 1182+ 'img_user_text' => $row->fa_user_text,
 1183+ 'img_timestamp' => $row->fa_timestamp );
 1184+ } else {
 1185+ $archiveName = $row->fa_archive_name;
 1186+ if( $archiveName == '' ) {
 1187+ // This was originally a current version; we
 1188+ // have to devise a new archive name for it.
 1189+ // Format is <timestamp of archiving>!<name>
 1190+ $archiveName =
 1191+ wfTimestamp( TS_MW, $row->fa_deleted_timestamp ) .
 1192+ '!' . $row->fa_name;
 1193+ }
 1194+ $restoredImage = new self( $row->fa_name, $this->repo );
 1195+ $destDir = $restoredImage->getArchivePath();
 1196+ if ( !is_dir( $destDir ) ) {
 1197+ wfMkdirParents( $destDir );
 1198+ }
 1199+ $destPath = $destDir . DIRECTORY_SEPARATOR . $archiveName;
 1200+
 1201+ $table = 'oldimage';
 1202+ $fields = array(
 1203+ 'oi_name' => $row->fa_name,
 1204+ 'oi_archive_name' => $archiveName,
 1205+ 'oi_size' => $row->fa_size,
 1206+ 'oi_width' => $row->fa_width,
 1207+ 'oi_height' => $row->fa_height,
 1208+ 'oi_bits' => $row->fa_bits,
 1209+ 'oi_description' => $row->fa_description,
 1210+ 'oi_user' => $row->fa_user,
 1211+ 'oi_user_text' => $row->fa_user_text,
 1212+ 'oi_timestamp' => $row->fa_timestamp );
 1213+ }
 1214+
 1215+ $dbw->insert( $table, $fields, __METHOD__ );
 1216+ // @todo this delete is not totally safe, potentially
 1217+ $dbw->delete( 'filearchive',
 1218+ array( 'fa_id' => $row->fa_id ),
 1219+ __METHOD__ );
 1220+
 1221+ // Check if any other stored revisions use this file;
 1222+ // if so, we shouldn't remove the file from the deletion
 1223+ // archives so they will still work.
 1224+ $useCount = $dbw->selectField( 'filearchive',
 1225+ 'COUNT(*)',
 1226+ array(
 1227+ 'fa_storage_group' => $row->fa_storage_group,
 1228+ 'fa_storage_key' => $row->fa_storage_key ),
 1229+ __METHOD__ );
 1230+ if( $useCount == 0 ) {
 1231+ wfDebug( __METHOD__.": nothing else using {$row->fa_storage_key}, will deleting after\n" );
 1232+ $flags = FileStore::DELETE_ORIGINAL;
 1233+ } else {
 1234+ $flags = 0;
 1235+ }
 1236+
 1237+ $transaction->add( $store->export( $row->fa_storage_key,
 1238+ $destPath, $flags ) );
 1239+ }
 1240+
 1241+ $dbw->immediateCommit();
 1242+ } catch( MWException $e ) {
 1243+ wfDebug( __METHOD__." caught error, aborting\n" );
 1244+ $transaction->rollback();
 1245+ throw $e;
 1246+ }
 1247+
 1248+ $transaction->commit();
 1249+ FileStore::unlock();
 1250+
 1251+ if( $revisions > 0 ) {
 1252+ if( !$exists ) {
 1253+ wfDebug( __METHOD__." restored $revisions items, creating a new current\n" );
 1254+
 1255+ // Update site_stats
 1256+ $site_stats = $dbw->tableName( 'site_stats' );
 1257+ $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
 1258+
 1259+ $this->purgeEverything();
 1260+ } else {
 1261+ wfDebug( __METHOD__." restored $revisions as archived versions\n" );
 1262+ $this->purgeDescription();
 1263+ }
 1264+ }
 1265+
 1266+ return $revisions;
 1267+ }
 1268+
 1269+ /** isMultipage inherited */
 1270+ /** pageCount inherited */
 1271+ /** scaleHeight inherited */
 1272+ /** getImageSize inherited */
 1273+
 1274+ /**
 1275+ * Get the URL of the file description page.
 1276+ */
 1277+ function getDescriptionUrl() {
 1278+ return $this->title->getLocalUrl();
 1279+ }
 1280+
 1281+ /**
 1282+ * Get the HTML text of the description page
 1283+ * This is not used by ImagePage for local files, since (among other things)
 1284+ * it skips the parser cache.
 1285+ */
 1286+ function getDescriptionText() {
 1287+ global $wgParser;
 1288+ $revision = Revision::newFromTitle( $this->title );
 1289+ if ( !$revision ) return false;
 1290+ $text = $revision->getText();
 1291+ if ( !$text ) return false;
 1292+ $html = $wgParser->parse( $text, new ParserOptions );
 1293+ return $html;
 1294+ }
 1295+
 1296+ function getTimestamp() {
 1297+ $this->load();
 1298+ return $this->timestamp;
 1299+ }
 1300+} // LocalFile class
 1301+
 1302+/**
 1303+ * Backwards compatibility class
 1304+ */
 1305+class Image extends LocalFile {
 1306+ function __construct( $title ) {
 1307+ $repo = FileRepoGroup::singleton()->getLocalRepo();
 1308+ parent::__construct( $title, $repo );
 1309+ }
 1310+
 1311+ /**
 1312+ * Wrapper for wfFindFile(), for backwards-compatibility only
 1313+ * Do not use in core code.
 1314+ */
 1315+ function newFromTitle( $title, $time = false ) {
 1316+ $img = wfFindFile( $title, $time );
 1317+ if ( !$img ) {
 1318+ $img = wfLocalFile( $title );
 1319+ }
 1320+ return $img;
 1321+ }
 1322+}
 1323+
 1324+/**
 1325+ * Aliases for backwards compatibility with 1.6
 1326+ */
 1327+define( 'MW_IMG_DELETED_FILE', File::DELETED_FILE );
 1328+define( 'MW_IMG_DELETED_COMMENT', File::DELETED_COMMENT );
 1329+define( 'MW_IMG_DELETED_USER', File::DELETED_USER );
 1330+define( 'MW_IMG_DELETED_RESTRICTED', File::DELETED_RESTRICTED );
 1331+
 1332+?>
Property changes on: trunk/phase3/includes/filerepo/LocalFile.php
___________________________________________________________________
Added: svn:eol-style
11333 + native
Index: trunk/phase3/includes/filerepo/UnregisteredLocalFile.php
@@ -0,0 +1,109 @@
 2+<?php
 3+
 4+/**
 5+ * A file object referring to either a standalone local file, or a file in a
 6+ * local repository with no database, for example an FSRepo repository.
 7+ *
 8+ * Read-only.
 9+ *
 10+ * TODO: Currently it doesn't really work in the repository role, there are
 11+ * lots of functions missing. It is used by the WebStore extension in the
 12+ * standalone role.
 13+ */
 14+class UnregisteredLocalFile extends File {
 15+ var $title, $path, $mime, $handler, $dims;
 16+
 17+ function newFromPath( $path, $mime ) {
 18+ return new UnregisteredLocalFile( false, false, $path, $mime );
 19+ }
 20+
 21+ function newFromTitle( $title, $repo ) {
 22+ return new UnregisteredLocalFile( $title, $repo, false, false );
 23+ }
 24+
 25+ function __construct( $title = false, $repo = false, $path = false, $mime = false ) {
 26+ if ( !( $title && $repo ) && !$path ) {
 27+ throw new MWException( __METHOD__.': not enough parameters, must specify title and repo, or a full path' );
 28+ }
 29+ if ( $title ) {
 30+ $this->title = $title;
 31+ $this->name = $title->getDBkey();
 32+ } else {
 33+ $this->name = basename( $path );
 34+ $this->title = Title::makeTitleSafe( NS_IMAGE, $this->name );
 35+ }
 36+ $this->repo = $repo;
 37+ if ( $path ) {
 38+ $this->path = $path;
 39+ } else {
 40+ $this->path = $repo->getRootDirectory() . '/' . $repo->getHashPath( $this->name ) . $this->name;
 41+ }
 42+ if ( $mime ) {
 43+ $this->mime = $mime;
 44+ }
 45+ $this->dims = array();
 46+ }
 47+
 48+ function getPageDimensions( $page = 1 ) {
 49+ if ( !isset( $this->dims[$page] ) ) {
 50+ if ( !$this->getHandler() ) {
 51+ return false;
 52+ }
 53+ $this->dims[$page] = $this->handler->getPageDimensions( $this, $page );
 54+ }
 55+ return $this->dims[$page];
 56+ }
 57+
 58+ function getWidth( $page = 1 ) {
 59+ $dim = $this->getPageDimensions( $page );
 60+ return $dim['width'];
 61+ }
 62+
 63+ function getHeight( $page = 1 ) {
 64+ $dim = $this->getPageDimensions( $page );
 65+ return $dim['height'];
 66+ }
 67+
 68+ function getMimeType() {
 69+ if ( !isset( $this->mime ) ) {
 70+ $magic = MimeMagic::singleton();
 71+ $this->mime = $magic->guessMimeType( $this->path );
 72+ }
 73+ return $this->mime;
 74+ }
 75+
 76+ function getImageSize() {
 77+ if ( !$this->getHandler() ) {
 78+ return false;
 79+ }
 80+ return $this->handler->getImageSize( $this, $this->getPath() );
 81+ }
 82+
 83+ function getMetadata() {
 84+ if ( !isset( $this->metadata ) ) {
 85+ if ( !$this->getHandler() ) {
 86+ $this->metadata = false;
 87+ } else {
 88+ $this->metadata = $this->handler->getMetadata( $this, $this->getPath() );
 89+ }
 90+ }
 91+ return $this->metadata;
 92+ }
 93+
 94+ function getURL() {
 95+ if ( $this->repo ) {
 96+ return $this->repo->getZoneUrl( 'public' ) . $this->repo->getHashPath( $this->name ) . urlencode( $this->name );
 97+ } else {
 98+ return false;
 99+ }
 100+ }
 101+
 102+ function getSize() {
 103+ if ( file_exists( $this->path ) ) {
 104+ return filesize( $this->path );
 105+ } else {
 106+ return false;
 107+ }
 108+ }
 109+}
 110+?>
Property changes on: trunk/phase3/includes/filerepo/UnregisteredLocalFile.php
___________________________________________________________________
Added: svn:eol-style
1111 + native
Index: trunk/phase3/includes/filerepo/FSRepo.php
@@ -0,0 +1,368 @@
 2+<?php
 3+
 4+/**
 5+ * A repository for files accessible via the local filesystem. Does not support
 6+ * database access or registration.
 7+ */
 8+
 9+class FSRepo {
 10+ const DELETE_SOURCE = 1;
 11+
 12+ var $directory, $url, $hashLevels, $thumbScriptUrl, $transformVia404;
 13+ var $descBaseUrl, $scriptDirUrl, $articleUrl, $fetchDescription;
 14+ var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
 15+
 16+ function __construct( $info ) {
 17+ // Required settings
 18+ $this->name = $info['name'];
 19+ $this->directory = $info['directory'];
 20+ $this->url = $info['url'];
 21+ $this->hashLevels = $info['hashLevels'];
 22+ $this->transformVia404 = !empty( $info['transformVia404'] );
 23+
 24+ // Optional settings
 25+ foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
 26+ 'thumbScriptUrl' ) as $var )
 27+ {
 28+ if ( isset( $info[$var] ) ) {
 29+ $this->$var = $info[$var];
 30+ }
 31+ }
 32+ }
 33+
 34+ /**
 35+ * Create a new File object from the local repository
 36+ * @param mixed $title Title object or string
 37+ * @param mixed $time Time at which the image is supposed to have existed.
 38+ * If this is specified, the returned object will be an
 39+ * instance of the repository's old file class instead of
 40+ * a current file. Repositories not supporting version
 41+ * control should return false if this parameter is set.
 42+ */
 43+ function newFile( $title, $time = false ) {
 44+ if ( !($title instanceof Title) ) {
 45+ $title = Title::makeTitleSafe( NS_IMAGE, $title );
 46+ if ( !is_object( $title ) ) {
 47+ return null;
 48+ }
 49+ }
 50+ if ( $time ) {
 51+ return call_user_func( $this->oldFileFactor, $title, $this, $time );
 52+ } else {
 53+ return call_user_func( $this->fileFactory, $title, $this );
 54+ }
 55+ }
 56+
 57+ /**
 58+ * Find an instance of the named file that existed at the specified time
 59+ * Returns false if the file did not exist. Repositories not supporting
 60+ * version control should return false if the time is specified.
 61+ *
 62+ * @param mixed $time 14-character timestamp, or false for the current version
 63+ */
 64+ function findFile( $title, $time = false ) {
 65+ $img = $this->newFile( $title );
 66+ if ( !$img ) {
 67+ return false;
 68+ }
 69+ if ( $img->exists() && $img->getTimestamp() <= $time ) {
 70+ return $img;
 71+ }
 72+ $img = $this->newFile( $title, $time );
 73+ if ( $img->exists() ) {
 74+ return $img;
 75+ }
 76+ }
 77+
 78+ function getRootDirectory() {
 79+ return $this->directory;
 80+ }
 81+
 82+ function getRootUrl() {
 83+ return $this->url;
 84+ }
 85+
 86+ function isHashed() {
 87+ return (bool)$this->hashLevels;
 88+ }
 89+
 90+ function getThumbScriptUrl() {
 91+ return $this->thumbScriptUrl;
 92+ }
 93+
 94+ function canTransformVia404() {
 95+ return $this->transformVia404;
 96+ }
 97+
 98+ function getZonePath( $zone ) {
 99+ switch ( $zone ) {
 100+ case 'public':
 101+ return $this->directory;
 102+ case 'temp':
 103+ return "{$this->directory}/temp";
 104+ case 'deleted':
 105+ return $GLOBALS['wgFileStore']['deleted']['directory'];
 106+ default:
 107+ return false;
 108+ }
 109+ }
 110+
 111+ function getZoneUrl( $zone ) {
 112+ switch ( $zone ) {
 113+ case 'public':
 114+ return $this->url;
 115+ case 'temp':
 116+ return "{$this->url}/temp";
 117+ case 'deleted':
 118+ return $GLOBALS['wgFileStore']['deleted']['url'];
 119+ default:
 120+ return false;
 121+ }
 122+ }
 123+
 124+ /**
 125+ * Get a URL referring to this repository, with the private mwrepo protocol.
 126+ */
 127+ function getVirtualUrl( $suffix = false ) {
 128+ $path = 'mwrepo://';
 129+ if ( $suffix !== false ) {
 130+ $path .= '/' . $suffix;
 131+ }
 132+ return $path;
 133+ }
 134+
 135+ /**
 136+ * Get the local path corresponding to a virtual URL
 137+ */
 138+ function resolveVirtualUrl( $url ) {
 139+ if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
 140+ throw new MWException( __METHOD__.': unknown protoocl' );
 141+ }
 142+
 143+ $bits = explode( '/', substr( $url, 9 ), 3 );
 144+ if ( count( $bits ) != 3 ) {
 145+ throw new MWException( __METHOD__.": invalid mwrepo URL: $url" );
 146+ }
 147+ list( $host, $zone, $rel ) = $bits;
 148+ if ( $host !== '' ) {
 149+ throw new MWException( __METHOD__.": fetching from a foreign repo is not supported" );
 150+ }
 151+ $base = $this->getZonePath( $zone );
 152+ if ( !$base ) {
 153+ throw new MWException( __METHOD__.": invalid zone: $zone" );
 154+ }
 155+ return $base . '/' . urldecode( $rel );
 156+ }
 157+
 158+ /**
 159+ * Store a file to a given destination.
 160+ */
 161+ function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
 162+ $root = $this->getZonePath( $dstZone );
 163+ if ( !$root ) {
 164+ throw new MWException( "Invalid zone: $dstZone" );
 165+ }
 166+ $dstPath = "$root/$dstRel";
 167+
 168+ if ( !is_dir( dirname( $dstPath ) ) ) {
 169+ wfMkdirParents( dirname( $dstPath ) );
 170+ }
 171+
 172+ if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) {
 173+ $srcPath = $this->resolveVirtualUrl( $srcPath );
 174+ }
 175+
 176+ if ( $flags & self::DELETE_SOURCE ) {
 177+ if ( !rename( $srcPath, $dstPath ) ) {
 178+ return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
 179+ wfEscapeWikiText( $dstPath ) );
 180+ }
 181+ } else {
 182+ if ( !copy( $srcPath, $dstPath ) ) {
 183+ return new WikiErrorMsg( 'filecopyerror', wfEscapeWikiText( $srcPath ),
 184+ wfEscapeWikiText( $dstPath ) );
 185+ }
 186+ }
 187+ chmod( $dstPath, 0644 );
 188+ return true;
 189+ }
 190+
 191+ /**
 192+ * Pick a random name in the temp zone and store a file to it.
 193+ * Returns the URL, or a WikiError on failure.
 194+ * @param string $originalName The base name of the file as specified
 195+ * by the user. The file extension will be maintained.
 196+ * @param string $srcPath The current location of the file.
 197+ */
 198+ function storeTemp( $originalName, $srcPath ) {
 199+ $dstRel = $this->getHashPath( $originalName ) .
 200+ gmdate( "YmdHis" ) . '!' . $originalName;
 201+ $result = $this->store( $srcPath, 'temp', $dstRel );
 202+ if ( WikiError::isError( $result ) ) {
 203+ return $result;
 204+ } else {
 205+ return $this->getVirtualUrl( "temp/$dstRel" );
 206+ }
 207+ }
 208+
 209+ function publish( $srcPath, $dstPath, $archivePath, $flags = 0 ) {
 210+ if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) {
 211+ $srcPath = $this->resolveVirtualUrl( $srcPath );
 212+ }
 213+ $dstDir = dirname( $dstPath );
 214+ if ( !is_dir( $dstDir ) ) wfMkdirParents( $dstDir );
 215+
 216+ if( is_file( $dstPath ) ) {
 217+ $archiveDir = dirname( $archivePath );
 218+ if ( !is_dir( $archiveDir ) ) wfMkdirParents( $archiveDir );
 219+ wfSuppressWarnings();
 220+ $success = rename( $dstPath, $archivePath );
 221+ wfRestoreWarnings();
 222+
 223+ if( ! $success ) {
 224+ return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $dstPath ),
 225+ wfEscapeWikiText( $archivePath ) );
 226+ }
 227+ else wfDebug(__METHOD__.": moved file $dstPath to $archivePath\n");
 228+ $status = 'archived';
 229+ }
 230+ else {
 231+ $status = 'new';
 232+ }
 233+
 234+ $error = false;
 235+ wfSuppressWarnings();
 236+ if ( $flags & self::DELETE_SOURCE ) {
 237+ if ( !rename( $srcPath, $dstPath ) ) {
 238+ $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
 239+ wfEscapeWikiText( $dstPath ) );
 240+ }
 241+ } else {
 242+ if ( !copy( $srcPath, $dstPath ) ) {
 243+ $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
 244+ wfEscapeWikiText( $dstPath ) );
 245+ }
 246+ }
 247+ wfRestoreWarnings();
 248+
 249+ if( $error ) {
 250+ return $error;
 251+ } else {
 252+ wfDebug(__METHOD__.": wrote tempfile $srcPath to $dstPath\n");
 253+ }
 254+
 255+ chmod( $dstPath, 0644 );
 256+ return $status;
 257+ }
 258+
 259+ /**
 260+ * Get a relative path including trailing slash, e.g. f/fa/
 261+ * If the repo is not hashed, returns an empty string
 262+ */
 263+ function getHashPath( $name ) {
 264+ if ( $this->isHashed() ) {
 265+ $hash = md5( $name );
 266+ $path = '';
 267+ for ( $i = 1; $i <= $this->hashLevels; $i++ ) {
 268+ $path .= substr( $hash, 0, $i ) . '/';
 269+ }
 270+ return $path;
 271+ } else {
 272+ return '';
 273+ }
 274+ }
 275+
 276+ function getName() {
 277+ return $this->name;
 278+ }
 279+
 280+ /**
 281+ * Get the file description page base URL, or false if there isn't one.
 282+ * @private
 283+ */
 284+ function getDescBaseUrl() {
 285+ if ( is_null( $this->descBaseUrl ) ) {
 286+ if ( !is_null( $this->articleUrl ) ) {
 287+ $this->descBaseUrl = str_replace( '$1',
 288+ urlencode( Namespace::getCanonicalName( NS_IMAGE ) ) . ':', $this->articleUrl );
 289+ } elseif ( !is_null( $this->scriptDirUrl ) ) {
 290+ $this->descBaseUrl = $this->scriptDirUrl . '/index.php?title=' .
 291+ urlencode( Namespace::getCanonicalName( NS_IMAGE ) ) . ':';
 292+ } else {
 293+ $this->descBaseUrl = false;
 294+ }
 295+ }
 296+ return $this->descBaseUrl;
 297+ }
 298+
 299+ /**
 300+ * Get the URL of an image description page. May return false if it is
 301+ * unknown or not applicable. In general this should only be called by the
 302+ * File class, since it may return invalid results for certain kinds of
 303+ * repositories. Use File::getDescriptionUrl() in user code.
 304+ *
 305+ * In particular, it uses the article paths as specified to the repository
 306+ * constructor, whereas local repositories use the local Title functions.
 307+ */
 308+ function getDescriptionUrl( $name ) {
 309+ $base = $this->getDescBaseUrl();
 310+ if ( $base ) {
 311+ return $base . wfUrlencode( $name );
 312+ } else {
 313+ return false;
 314+ }
 315+ }
 316+
 317+ /**
 318+ * Get the URL of the content-only fragment of the description page. For
 319+ * MediaWiki this means action=render. This should only be called by the
 320+ * repository's file class, since it may return invalid results. User code
 321+ * should use File::getDescriptionText().
 322+ */
 323+ function getDescriptionRenderUrl( $name ) {
 324+ if ( isset( $this->scriptDirUrl ) ) {
 325+ return $this->scriptDirUrl . '/index.php?title=' .
 326+ wfUrlencode( Namespace::getCanonicalName( NS_IMAGE ) . ':' . $name ) .
 327+ '&action=render';
 328+ } else {
 329+ $descBase = $this->getDescBaseUrl();
 330+ if ( $descBase ) {
 331+ return wfAppendQuery( $descBase . wfUrlencode( $name ), 'action=render' );
 332+ } else {
 333+ return false;
 334+ }
 335+ }
 336+ }
 337+
 338+ /**
 339+ * Call a callback function for every file in the repository.
 340+ * Uses the filesystem even in child classes.
 341+ */
 342+ function enumFilesInFS( $callback ) {
 343+ $numDirs = 1 << ( $this->hashLevels * 4 );
 344+ for ( $flatIndex = 0; $flatIndex < $numDirs; $flatIndex++ ) {
 345+ $hexString = sprintf( "%0{$this->hashLevels}x", $flatIndex );
 346+ $path = $this->directory;
 347+ for ( $hexPos = 0; $hexPos < $this->hashLevels; $hexPos++ ) {
 348+ $path .= '/' . substr( $hexString, 0, $hexPos + 1 );
 349+ }
 350+ if ( !file_exists( $path ) || !is_dir( $path ) ) {
 351+ continue;
 352+ }
 353+ $dir = opendir( $path );
 354+ while ( false !== ( $name = readdir( $dir ) ) ) {
 355+ call_user_func( $callback, $path . '/' . $name );
 356+ }
 357+ }
 358+ }
 359+
 360+ /**
 361+ * Call a callaback function for every file in the repository
 362+ * May use either the database or the filesystem
 363+ */
 364+ function enumFiles( $callback ) {
 365+ $this->enumFilesInFS( $callback );
 366+ }
 367+}
 368+
 369+?>
Property changes on: trunk/phase3/includes/filerepo/FSRepo.php
___________________________________________________________________
Added: svn:eol-style
1370 + native
Index: trunk/phase3/includes/filerepo/File.php
@@ -0,0 +1,985 @@
 2+<?php
 3+
 4+/**
 5+ * Base file class. Do not instantiate.
 6+ *
 7+ * Implements some public methods and some protected utility functions which
 8+ * are required by multiple child classes. Contains stub functionality for
 9+ * unimplemented public methods.
 10+ *
 11+ * Stub functions which should be overridden are marked with STUB. Some more
 12+ * concrete functions are also typically overridden by child classes.
 13+ *
 14+ *
 15+ * NOTE FOR WINDOWS USERS:
 16+ * To enable EXIF functions, add the folloing lines to the
 17+ * "Windows extensions" section of php.ini:
 18+ *
 19+ * extension=extensions/php_mbstring.dll
 20+ * extension=extensions/php_exif.dll
 21+ *
 22+ * @addtogroup FileRepo
 23+ */
 24+class File {
 25+ const DELETED_FILE = 1;
 26+ const DELETED_COMMENT = 2;
 27+ const DELETED_USER = 4;
 28+ const DELETED_RESTRICTED = 8;
 29+ const RENDER_NOW = 1;
 30+
 31+ const DELETE_SOURCE = 1;
 32+
 33+ /**
 34+ * Some member variables can be lazy-initialised using __get(). The
 35+ * initialisation function for these variables is always a function named
 36+ * like getVar(), where Var is the variable name with upper-case first
 37+ * letter.
 38+ *
 39+ * The following variables are initialised in this way in this base class:
 40+ * name, extension, handler, path, canRender, isSafeFile,
 41+ * transformScript, hashPath, pageCount, url
 42+ *
 43+ * Code within this class should generally use the accessor function
 44+ * directly, since __get() isn't re-entrant and therefore causes bugs that
 45+ * depend on initialisation order.
 46+ */
 47+
 48+ /**
 49+ * The following member variables are not lazy-initialised
 50+ */
 51+ var $repo, $title, $lastError;
 52+
 53+ function __construct( $title, $repo ) {
 54+ $this->title = $title;
 55+ $this->repo = $repo;
 56+ }
 57+
 58+ function __get( $name ) {
 59+ $function = array( $this, 'get' . ucfirst( $name ) );
 60+ if ( !is_callable( $function ) ) {
 61+ return null;
 62+ } else {
 63+ $this->$name = call_user_func( $function );
 64+ return $this->$name;
 65+ }
 66+ }
 67+
 68+ /**
 69+ * Normalize a file extension to the common form, and ensure it's clean.
 70+ * Extensions with non-alphanumeric characters will be discarded.
 71+ *
 72+ * @param $ext string (without the .)
 73+ * @return string
 74+ */
 75+ static function normalizeExtension( $ext ) {
 76+ $lower = strtolower( $ext );
 77+ $squish = array(
 78+ 'htm' => 'html',
 79+ 'jpeg' => 'jpg',
 80+ 'mpeg' => 'mpg',
 81+ 'tiff' => 'tif' );
 82+ if( isset( $squish[$lower] ) ) {
 83+ return $squish[$lower];
 84+ } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
 85+ return $lower;
 86+ } else {
 87+ return '';
 88+ }
 89+ }
 90+
 91+ /**
 92+ * Upgrade the database row if there is one
 93+ * Called by ImagePage
 94+ * STUB
 95+ */
 96+ function upgradeRow() {}
 97+
 98+ /**
 99+ * Split an internet media type into its two components; if not
 100+ * a two-part name, set the minor type to 'unknown'.
 101+ *
 102+ * @param $mime "text/html" etc
 103+ * @return array ("text", "html") etc
 104+ */
 105+ static function splitMime( $mime ) {
 106+ if( strpos( $mime, '/' ) !== false ) {
 107+ return explode( '/', $mime, 2 );
 108+ } else {
 109+ return array( $mime, 'unknown' );
 110+ }
 111+ }
 112+
 113+ /**
 114+ * Return the name of this file
 115+ * @public
 116+ */
 117+ function getName() {
 118+ if ( !isset( $this->name ) ) {
 119+ $this->name = $this->title->getDBkey();
 120+ }
 121+ return $this->name;
 122+ }
 123+
 124+ /**
 125+ * Get the file extension, e.g. "svg"
 126+ */
 127+ function getExtension() {
 128+ if ( !isset( $this->extension ) ) {
 129+ $n = strrpos( $this->getName(), '.' );
 130+ $this->extension = self::normalizeExtension(
 131+ $n ? substr( $this->getName(), $n + 1 ) : '' );
 132+ }
 133+ return $this->extension;
 134+ }
 135+
 136+ /**
 137+ * Return the associated title object
 138+ * @public
 139+ */
 140+ function getTitle() { return $this->title; }
 141+
 142+ /**
 143+ * Return the URL of the file
 144+ * @public
 145+ */
 146+ function getUrl() {
 147+ if ( !isset( $this->url ) ) {
 148+ $this->url = $this->repo->getZoneUrl( 'public' ) . '/' . $this->getUrlRel();
 149+ }
 150+ return $this->url;
 151+ }
 152+
 153+ function getViewURL() {
 154+ if( $this->mustRender()) {
 155+ if( $this->canRender() ) {
 156+ return $this->createThumb( $this->getWidth() );
 157+ }
 158+ else {
 159+ wfDebug(__METHOD__.': supposed to render '.$this->getName().' ('.$this->getMimeType()."), but can't!\n");
 160+ return $this->getURL(); #hm... return NULL?
 161+ }
 162+ } else {
 163+ return $this->getURL();
 164+ }
 165+ }
 166+
 167+ /**
 168+ * Return the full filesystem path to the file. Note that this does
 169+ * not mean that a file actually exists under that location.
 170+ *
 171+ * This path depends on whether directory hashing is active or not,
 172+ * i.e. whether the files are all found in the same directory,
 173+ * or in hashed paths like /images/3/3c.
 174+ *
 175+ * May return false if the file is not locally accessible.
 176+ *
 177+ * @public
 178+ */
 179+ function getPath() {
 180+ if ( !isset( $this->path ) ) {
 181+ $this->path = $this->repo->getZonePath('public') . '/' . $this->getRel();
 182+ }
 183+ return $this->path;
 184+ }
 185+
 186+ /**
 187+ * Alias for getPath()
 188+ * @public
 189+ */
 190+ function getFullPath() {
 191+ return $this->getPath();
 192+ }
 193+
 194+ /**
 195+ * Return the width of the image. Returns false if the width is unknown
 196+ * or undefined.
 197+ *
 198+ * STUB
 199+ * Overridden by LocalFile, UnregisteredLocalFile
 200+ * @public
 201+ */
 202+ function getWidth( $page = 1 ) { return false; }
 203+
 204+ /**
 205+ * Return the height of the image. Returns false if the height is unknown
 206+ * or undefined
 207+ *
 208+ * STUB
 209+ * Overridden by LocalFile, UnregisteredLocalFile
 210+ * @public
 211+ */
 212+ function getHeight( $page = 1 ) { return false; }
 213+
 214+ /**
 215+ * Get handler-specific metadata
 216+ * Overridden by LocalFile, UnregisteredLocalFile
 217+ * STUB
 218+ */
 219+ function getMetadata() { return false; }
 220+
 221+ /**
 222+ * Return the size of the image file, in bytes
 223+ * Overridden by LocalFile, UnregisteredLocalFile
 224+ * STUB
 225+ * @public
 226+ */
 227+ function getSize() { return false; }
 228+
 229+ /**
 230+ * Returns the mime type of the file.
 231+ * Overridden by LocalFile, UnregisteredLocalFile
 232+ * STUB
 233+ */
 234+ function getMimeType() { return 'unknown/unknown'; }
 235+
 236+ /**
 237+ * Return the type of the media in the file.
 238+ * Use the value returned by this function with the MEDIATYPE_xxx constants.
 239+ * Overridden by LocalFile,
 240+ * STUB
 241+ */
 242+ function getMediaType() { return MEDIATYPE_UNKNOWN; }
 243+
 244+ /**
 245+ * Checks if the file can be presented to the browser as a bitmap.
 246+ *
 247+ * Currently, this checks if the file is an image format
 248+ * that can be converted to a format
 249+ * supported by all browsers (namely GIF, PNG and JPEG),
 250+ * or if it is an SVG image and SVG conversion is enabled.
 251+ */
 252+ function canRender() {
 253+ if ( !isset( $this->canRender ) ) {
 254+ $this->canRender = $this->getHandler() && $this->handler->canRender();
 255+ }
 256+ return $this->canRender;
 257+ }
 258+
 259+ /**
 260+ * Accessor for __get()
 261+ */
 262+ protected function getCanRender() {
 263+ return $this->canRender();
 264+ }
 265+
 266+ /**
 267+ * Return true if the file is of a type that can't be directly
 268+ * rendered by typical browsers and needs to be re-rasterized.
 269+ *
 270+ * This returns true for everything but the bitmap types
 271+ * supported by all browsers, i.e. JPEG; GIF and PNG. It will
 272+ * also return true for any non-image formats.
 273+ *
 274+ * @return bool
 275+ */
 276+ function mustRender() {
 277+ return $this->getHandler() && $this->handler->mustRender();
 278+ }
 279+
 280+ /**
 281+ * Determines if this media file may be shown inline on a page.
 282+ *
 283+ * This is currently synonymous to canRender(), but this could be
 284+ * extended to also allow inline display of other media,
 285+ * like flash animations or videos. If you do so, please keep in mind that
 286+ * that could be a security risk.
 287+ */
 288+ function allowInlineDisplay() {
 289+ return $this->canRender();
 290+ }
 291+
 292+ /**
 293+ * Determines if this media file is in a format that is unlikely to
 294+ * contain viruses or malicious content. It uses the global
 295+ * $wgTrustedMediaFormats list to determine if the file is safe.
 296+ *
 297+ * This is used to show a warning on the description page of non-safe files.
 298+ * It may also be used to disallow direct [[media:...]] links to such files.
 299+ *
 300+ * Note that this function will always return true if allowInlineDisplay()
 301+ * or isTrustedFile() is true for this file.
 302+ */
 303+ function isSafeFile() {
 304+ if ( !isset( $this->isSafeFile ) ) {
 305+ $this->isSafeFile = $this->_getIsSafeFile();
 306+ }
 307+ return $this->isSafeFile;
 308+ }
 309+
 310+ /** Accessor for __get() */
 311+ protected function getIsSafeFile() {
 312+ return $this->isSafeFile();
 313+ }
 314+
 315+ /** Uncached accessor */
 316+ protected function _getIsSafeFile() {
 317+ if ($this->allowInlineDisplay()) return true;
 318+ if ($this->isTrustedFile()) return true;
 319+
 320+ global $wgTrustedMediaFormats;
 321+
 322+ $type= $this->getMediaType();
 323+ $mime= $this->getMimeType();
 324+ #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
 325+
 326+ if (!$type || $type===MEDIATYPE_UNKNOWN) return false; #unknown type, not trusted
 327+ if ( in_array( $type, $wgTrustedMediaFormats) ) return true;
 328+
 329+ if ($mime==="unknown/unknown") return false; #unknown type, not trusted
 330+ if ( in_array( $mime, $wgTrustedMediaFormats) ) return true;
 331+
 332+ return false;
 333+ }
 334+
 335+ /** Returns true if the file is flagged as trusted. Files flagged that way
 336+ * can be linked to directly, even if that is not allowed for this type of
 337+ * file normally.
 338+ *
 339+ * This is a dummy function right now and always returns false. It could be
 340+ * implemented to extract a flag from the database. The trusted flag could be
 341+ * set on upload, if the user has sufficient privileges, to bypass script-
 342+ * and html-filters. It may even be coupled with cryptographics signatures
 343+ * or such.
 344+ */
 345+ function isTrustedFile() {
 346+ #this could be implemented to check a flag in the databas,
 347+ #look for signatures, etc
 348+ return false;
 349+ }
 350+
 351+ /**
 352+ * Returns true if file exists in the repository.
 353+ *
 354+ * Overridden by LocalFile to avoid unnecessary stat calls.
 355+ *
 356+ * @return boolean Whether file exists in the repository.
 357+ * @public
 358+ */
 359+ function exists() {
 360+ return $this->getPath() && file_exists( $this->path );
 361+ }
 362+
 363+ function getTransformScript() {
 364+ if ( !isset( $this->transformScript ) ) {
 365+ $this->transformScript = false;
 366+ if ( $this->repo ) {
 367+ $script = $this->repo->getThumbScriptUrl();
 368+ if ( $script ) {
 369+ $this->transformScript = "$script?f=" . urlencode( $this->getName() );
 370+ }
 371+ }
 372+ }
 373+ return $this->transformScript;
 374+ }
 375+
 376+ /**
 377+ * Get a ThumbnailImage which is the same size as the source
 378+ */
 379+ function getUnscaledThumb( $page = false ) {
 380+ $width = $this->getWidth( $page );
 381+ if ( !$width ) {
 382+ return $this->iconThumb();
 383+ }
 384+ if ( $page ) {
 385+ $params = array(
 386+ 'page' => $page,
 387+ 'width' => $this->getWidth( $page )
 388+ );
 389+ } else {
 390+ $params = array( 'width' => $this->getWidth() );
 391+ }
 392+ return $this->transform( $params );
 393+ }
 394+
 395+ /**
 396+ * Return the file name of a thumbnail with the specified parameters
 397+ *
 398+ * @param array $params Handler-specific parameters
 399+ * @private
 400+ */
 401+ function thumbName( $params ) {
 402+ if ( !$this->getHandler() ) {
 403+ return null;
 404+ }
 405+ $extension = $this->getExtension();
 406+ list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType() );
 407+ $thumbName = $this->handler->makeParamString( $params ) . '-' . $this->getName();
 408+ if ( $thumbExt != $extension ) {
 409+ $thumbName .= ".$thumbExt";
 410+ }
 411+ return $thumbName;
 412+ }
 413+
 414+ /**
 415+ * Create a thumbnail of the image having the specified width/height.
 416+ * The thumbnail will not be created if the width is larger than the
 417+ * image's width. Let the browser do the scaling in this case.
 418+ * The thumbnail is stored on disk and is only computed if the thumbnail
 419+ * file does not exist OR if it is older than the image.
 420+ * Returns the URL.
 421+ *
 422+ * Keeps aspect ratio of original image. If both width and height are
 423+ * specified, the generated image will be no bigger than width x height,
 424+ * and will also have correct aspect ratio.
 425+ *
 426+ * @param integer $width maximum width of the generated thumbnail
 427+ * @param integer $height maximum height of the image (optional)
 428+ * @public
 429+ */
 430+ function createThumb( $width, $height = -1 ) {
 431+ $params = array( 'width' => $width );
 432+ if ( $height != -1 ) {
 433+ $params['height'] = $height;
 434+ }
 435+ $thumb = $this->transform( $params );
 436+ if( is_null( $thumb ) || $thumb->isError() ) return '';
 437+ return $thumb->getUrl();
 438+ }
 439+
 440+ /**
 441+ * As createThumb, but returns a ThumbnailImage object. This can
 442+ * provide access to the actual file, the real size of the thumb,
 443+ * and can produce a convenient <img> tag for you.
 444+ *
 445+ * For non-image formats, this may return a filetype-specific icon.
 446+ *
 447+ * @param integer $width maximum width of the generated thumbnail
 448+ * @param integer $height maximum height of the image (optional)
 449+ * @param boolean $render True to render the thumbnail if it doesn't exist,
 450+ * false to just return the URL
 451+ *
 452+ * @return ThumbnailImage or null on failure
 453+ * @public
 454+ *
 455+ * @deprecated use transform()
 456+ */
 457+ function getThumbnail( $width, $height=-1, $render = true ) {
 458+ $params = array( 'width' => $width );
 459+ if ( $height != -1 ) {
 460+ $params['height'] = $height;
 461+ }
 462+ $flags = $render ? self::RENDER_NOW : 0;
 463+ return $this->transform( $params, $flags );
 464+ }
 465+
 466+ /**
 467+ * Transform a media file
 468+ *
 469+ * @param array $params An associative array of handler-specific parameters. Typical
 470+ * keys are width, height and page.
 471+ * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
 472+ * @return MediaTransformOutput
 473+ */
 474+ function transform( $params, $flags = 0 ) {
 475+ global $wgUseSquid, $wgIgnoreImageErrors;
 476+
 477+ wfProfileIn( __METHOD__ );
 478+ do {
 479+ if ( !$this->getHandler() || !$this->handler->canRender() ) {
 480+ // not a bitmap or renderable image, don't try.
 481+ $thumb = $this->iconThumb();
 482+ break;
 483+ }
 484+
 485+ $script = $this->getTransformScript();
 486+ if ( $script && !($flags & self::RENDER_NOW) ) {
 487+ // Use a script to transform on client request
 488+ $thumb = $this->handler->getScriptedTransform( $this, $script, $params );
 489+ break;
 490+ }
 491+
 492+ $normalisedParams = $params;
 493+ $this->handler->normaliseParams( $this, $normalisedParams );
 494+ $thumbName = $this->thumbName( $normalisedParams );
 495+ $thumbPath = $this->getThumbPath( $thumbName );
 496+ $thumbUrl = $this->getThumbUrl( $thumbName );
 497+
 498+ if ( $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
 499+ $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
 500+ break;
 501+ }
 502+
 503+ wfDebug( "Doing stat for $thumbPath\n" );
 504+ $this->migrateThumbFile( $thumbName );
 505+ if ( file_exists( $thumbPath ) ) {
 506+ $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
 507+ break;
 508+ }
 509+ $thumb = $this->handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
 510+
 511+ // Ignore errors if requested
 512+ if ( !$thumb ) {
 513+ $thumb = null;
 514+ } elseif ( $thumb->isError() ) {
 515+ $this->lastError = $thumb->toText();
 516+ if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
 517+ $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
 518+ }
 519+ }
 520+
 521+ if ( $wgUseSquid ) {
 522+ wfPurgeSquidServers( array( $thumbUrl ) );
 523+ }
 524+ } while (false);
 525+
 526+ wfProfileOut( __METHOD__ );
 527+ return $thumb;
 528+ }
 529+
 530+ /**
 531+ * Hook into transform() to allow migration of thumbnail files
 532+ * STUB
 533+ * Overridden by LocalFile
 534+ */
 535+ function migrateThumbFile() {}
 536+
 537+ /**
 538+ * Get a MediaHandler instance for this file
 539+ */
 540+ function getHandler() {
 541+ if ( !isset( $this->handler ) ) {
 542+ $this->handler = MediaHandler::getHandler( $this->getMimeType() );
 543+ }
 544+ return $this->handler;
 545+ }
 546+
 547+ /**
 548+ * Get a ThumbnailImage representing a file type icon
 549+ * @return ThumbnailImage
 550+ */
 551+ function iconThumb() {
 552+ global $wgStylePath, $wgStyleDirectory;
 553+
 554+ $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
 555+ foreach( $try as $icon ) {
 556+ $path = '/common/images/icons/' . $icon;
 557+ $filepath = $wgStyleDirectory . $path;
 558+ if( file_exists( $filepath ) ) {
 559+ return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
 560+ }
 561+ }
 562+ return null;
 563+ }
 564+
 565+ /**
 566+ * Get last thumbnailing error.
 567+ * Largely obsolete.
 568+ */
 569+ function getLastError() {
 570+ return $this->lastError;
 571+ }
 572+
 573+ /**
 574+ * Get all thumbnail names previously generated for this file
 575+ * STUB
 576+ * Overridden by LocalFile
 577+ */
 578+ function getThumbnails() { return array(); }
 579+
 580+ /**
 581+ * Purge shared caches such as thumbnails and DB data caching
 582+ * STUB
 583+ * Overridden by LocalFile
 584+ */
 585+ function purgeCache( $archiveFiles = array() ) {}
 586+
 587+ /**
 588+ * Purge the file description page, but don't go after
 589+ * pages using the file. Use when modifying file history
 590+ * but not the current data.
 591+ */
 592+ function purgeDescription() {
 593+ $title = $this->getTitle();
 594+ if ( $title ) {
 595+ $title->invalidateCache();
 596+ $title->purgeSquid();
 597+ }
 598+ }
 599+
 600+ /**
 601+ * Purge metadata and all affected pages when the file is created,
 602+ * deleted, or majorly updated. A set of additional URLs may be
 603+ * passed to purge, such as specific file files which have changed.
 604+ * @param $urlArray array
 605+ */
 606+ function purgeEverything( $urlArr=array() ) {
 607+ // Delete thumbnails and refresh file metadata cache
 608+ $this->purgeCache();
 609+ $this->purgeDescription();
 610+
 611+ // Purge cache of all pages using this file
 612+ $title = $this->getTitle();
 613+ if ( $title ) {
 614+ $update = new HTMLCacheUpdate( $title, 'imagelinks' );
 615+ $update->doUpdate();
 616+ }
 617+ }
 618+
 619+ /**
 620+ * Return the history of this file, line by line. Starts with current version,
 621+ * then old versions. Should return an object similar to an image/oldimage
 622+ * database row.
 623+ *
 624+ * @public
 625+ * STUB
 626+ * Overridden in LocalFile
 627+ */
 628+ function nextHistoryLine() {
 629+ return false;
 630+ }
 631+
 632+ /**
 633+ * Reset the history pointer to the first element of the history
 634+ * @public
 635+ * STUB
 636+ * Overridden in LocalFile.
 637+ */
 638+ function resetHistory() {}
 639+
 640+ /**
 641+ * Get the filename hash component of the directory including trailing slash,
 642+ * e.g. f/fa/
 643+ * If the repository is not hashed, returns an empty string.
 644+ */
 645+ function getHashPath() {
 646+ if ( !isset( $this->hashPath ) ) {
 647+ $this->hashPath = $this->repo->getHashPath( $this->getName() );
 648+ }
 649+ return $this->hashPath;
 650+ }
 651+
 652+ /**
 653+ * Get the path of the file relative to the public zone root
 654+ */
 655+ function getRel() {
 656+ return $this->getHashPath() . $this->getName();
 657+ }
 658+
 659+ /**
 660+ * Get urlencoded relative path of the file
 661+ */
 662+ function getUrlRel() {
 663+ return $this->getHashPath() . urlencode( $this->getName() );
 664+ }
 665+
 666+ /** Get the path of the archive directory, or a particular file if $suffix is specified */
 667+ function getArchivePath( $suffix = false ) {
 668+ $path = $this->repo->getZonePath('public') . '/archive/' . $this->getHashPath();
 669+ if ( $suffix !== false ) {
 670+ $path .= '/' . $suffix;
 671+ }
 672+ return $path;
 673+ }
 674+
 675+ /** Get the path of the thumbnail directory, or a particular file if $suffix is specified */
 676+ function getThumbPath( $suffix = false ) {
 677+ $path = $this->repo->getZonePath('public') . '/thumb/' . $this->getRel();
 678+ if ( $suffix !== false ) {
 679+ $path .= '/' . $suffix;
 680+ }
 681+ return $path;
 682+ }
 683+
 684+ /** Get the URL of the archive directory, or a particular file if $suffix is specified */
 685+ function getArchiveUrl( $suffix = false ) {
 686+ $path = $this->repo->getZoneUrl('public') . '/archive/' . $this->getHashPath();
 687+ if ( $suffix !== false ) {
 688+ $path .= '/' . urlencode( $suffix );
 689+ }
 690+ return $path;
 691+ }
 692+
 693+ /** Get the URL of the thumbnail directory, or a particular file if $suffix is specified */
 694+ function getThumbUrl( $suffix = false ) {
 695+ $path = $this->repo->getZoneUrl('public') . '/thumb/' . $this->getUrlRel();
 696+ if ( $suffix !== false ) {
 697+ $path .= '/' . urlencode( $suffix );
 698+ }
 699+ return $path;
 700+ }
 701+
 702+ /** Get the virtual URL for an archive file or directory */
 703+ function getArchiveVirtualUrl( $suffix = false ) {
 704+ $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
 705+ if ( $suffix !== false ) {
 706+ $path .= '/' . urlencode( $suffix );
 707+ }
 708+ return $path;
 709+ }
 710+
 711+ /** Get the virtual URL for a thumbnail file or directory */
 712+ function getThumbVirtualUrl( $suffix = false ) {
 713+ $path = $this->repo->getVirtualUrl() . '/public/thumb/' . $this->getHashPath();
 714+ if ( $suffix !== false ) {
 715+ $path .= '/' . urlencode( $suffix );
 716+ }
 717+ return $path;
 718+ }
 719+
 720+ /**
 721+ * @return bool
 722+ */
 723+ function isHashed() {
 724+ return $this->repo->isHashed();
 725+ }
 726+
 727+ function readOnlyError() {
 728+ throw new MWException( get_class($this) . ': write operations are not supported' );
 729+ }
 730+
 731+ /**
 732+ * Record a file upload in the upload log and the image table
 733+ * STUB
 734+ * Overridden by LocalFile
 735+ */
 736+ function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
 737+ $this->readOnlyError();
 738+ }
 739+
 740+ /**
 741+ * Move or copy a file to its public location. If a file exists at the
 742+ * destination, move it to an archive. Returns the archive name on success
 743+ * or an empty string if it was a new file, and a wikitext-formatted
 744+ * WikiError object on failure.
 745+ *
 746+ * The archive name should be passed through to recordUpload for database
 747+ * registration.
 748+ *
 749+ * @param string $sourcePath Local filesystem path to the source image
 750+ * @param integer $flags A bitwise combination of:
 751+ * File::DELETE_SOURCE Delete the source file, i.e. move
 752+ * rather than copy
 753+ * @return The archive name on success or an empty string if it was a new
 754+ * file, and a wikitext-formatted WikiError object on failure.
 755+ *
 756+ * STUB
 757+ * Overridden by LocalFile
 758+ */
 759+ function publish( $srcPath, $flags = 0 ) {
 760+ $this->readOnlyError();
 761+ }
 762+
 763+ /**
 764+ * Get an array of Title objects which are articles which use this file
 765+ * Also adds their IDs to the link cache
 766+ *
 767+ * This is mostly copied from Title::getLinksTo()
 768+ *
 769+ * @deprecated Use HTMLCacheUpdate, this function uses too much memory
 770+ */
 771+ function getLinksTo( $options = '' ) {
 772+ wfProfileIn( __METHOD__ );
 773+
 774+ // Note: use local DB not repo DB, we want to know local links
 775+ if ( $options ) {
 776+ $db = wfGetDB( DB_MASTER );
 777+ } else {
 778+ $db = wfGetDB( DB_SLAVE );
 779+ }
 780+ $linkCache =& LinkCache::singleton();
 781+
 782+ list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' );
 783+ $encName = $db->addQuotes( $this->getName() );
 784+ $sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options";
 785+ $res = $db->query( $sql, __METHOD__ );
 786+
 787+ $retVal = array();
 788+ if ( $db->numRows( $res ) ) {
 789+ while ( $row = $db->fetchObject( $res ) ) {
 790+ if ( $titleObj = Title::makeTitle( $row->page_namespace, $row->page_title ) ) {
 791+ $linkCache->addGoodLinkObj( $row->page_id, $titleObj );
 792+ $retVal[] = $titleObj;
 793+ }
 794+ }
 795+ }
 796+ $db->freeResult( $res );
 797+ wfProfileOut( __METHOD__ );
 798+ return $retVal;
 799+ }
 800+
 801+ function getExifData() {
 802+ if ( !$this->getHandler() || $this->handler->getMetadataType( $this ) != 'exif' ) {
 803+ return array();
 804+ }
 805+ $metadata = $this->getMetadata();
 806+ if ( !$metadata ) {
 807+ return array();
 808+ }
 809+ $exif = unserialize( $metadata );
 810+ if ( !$exif ) {
 811+ return array();
 812+ }
 813+ unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
 814+ $format = new FormatExif( $exif );
 815+
 816+ return $format->getFormattedData();
 817+ }
 818+
 819+ /**
 820+ * Returns true if the file comes from the local file repository.
 821+ *
 822+ * @return bool
 823+ */
 824+ function isLocal() {
 825+ return $this->repo && $this->repo->getName() == 'local';
 826+ }
 827+
 828+ /**
 829+ * Returns true if the image is an old version
 830+ * STUB
 831+ */
 832+ function isOld() {
 833+ return false;
 834+ }
 835+
 836+ /**
 837+ * Is this file a "deleted" file in a private archive?
 838+ * STUB
 839+ */
 840+ function isDeleted( $field ) {
 841+ return false;
 842+ }
 843+
 844+ /**
 845+ * Was this file ever deleted from the wiki?
 846+ *
 847+ * @return bool
 848+ */
 849+ function wasDeleted() {
 850+ $title = $this->getTitle();
 851+ return $title && $title->isDeleted() > 0;
 852+ }
 853+
 854+ /**
 855+ * Delete all versions of the file.
 856+ *
 857+ * Moves the files into an archive directory (or deletes them)
 858+ * and removes the database rows.
 859+ *
 860+ * Cache purging is done; logging is caller's responsibility.
 861+ *
 862+ * @param $reason
 863+ * @return true on success, false on some kind of failure
 864+ * STUB
 865+ * Overridden by LocalFile
 866+ */
 867+ function delete( $reason, $suppress=false ) {
 868+ $this->readOnlyError();
 869+ }
 870+
 871+ /**
 872+ * Restore all or specified deleted revisions to the given file.
 873+ * Permissions and logging are left to the caller.
 874+ *
 875+ * May throw database exceptions on error.
 876+ *
 877+ * @param $versions set of record ids of deleted items to restore,
 878+ * or empty to restore all revisions.
 879+ * @return the number of file revisions restored if successful,
 880+ * or false on failure
 881+ * STUB
 882+ * Overridden by LocalFile
 883+ */
 884+ function restore( $versions=array(), $Unsuppress=false ) {
 885+ $this->readOnlyError();
 886+ }
 887+
 888+ /**
 889+ * Returns 'true' if this image is a multipage document, e.g. a DJVU
 890+ * document.
 891+ *
 892+ * @return Bool
 893+ */
 894+ function isMultipage() {
 895+ return $this->getHandler() && $this->handler->isMultiPage();
 896+ }
 897+
 898+ /**
 899+ * Returns the number of pages of a multipage document, or NULL for
 900+ * documents which aren't multipage documents
 901+ */
 902+ function pageCount() {
 903+ if ( !isset( $this->pageCount ) ) {
 904+ if ( $this->getHandler() && $this->handler->isMultiPage() ) {
 905+ $this->pageCount = $this->handler->pageCount( $this );
 906+ } else {
 907+ $this->pageCount = false;
 908+ }
 909+ }
 910+ return $this->pageCount;
 911+ }
 912+
 913+ /**
 914+ * Calculate the height of a thumbnail using the source and destination width
 915+ */
 916+ static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
 917+ // Exact integer multiply followed by division
 918+ if ( $srcWidth == 0 ) {
 919+ return 0;
 920+ } else {
 921+ return round( $srcHeight * $dstWidth / $srcWidth );
 922+ }
 923+ }
 924+
 925+ /**
 926+ * Get an image size array like that returned by getimagesize(), or false if it
 927+ * can't be determined.
 928+ *
 929+ * @param string $fileName The filename
 930+ * @return array
 931+ */
 932+ function getImageSize( $fileName ) {
 933+ if ( !$this->getHandler() ) {
 934+ return false;
 935+ }
 936+ return $this->handler->getImageSize( $this, $fileName );
 937+ }
 938+
 939+ /**
 940+ * Get the URL of the image description page. May return false if it is
 941+ * unknown or not applicable.
 942+ */
 943+ function getDescriptionUrl() {
 944+ return $this->repo->getDescriptionUrl( $this->getName() );
 945+ }
 946+
 947+ /**
 948+ * Get the HTML text of the description page, if available
 949+ */
 950+ function getDescriptionText() {
 951+ if ( !$this->repo->fetchDescription ) {
 952+ return false;
 953+ }
 954+ $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName() );
 955+ if ( $renderUrl ) {
 956+ wfDebug( "Fetching shared description from $renderUrl\n" );
 957+ return Http::get( $renderUrl );
 958+ } else {
 959+ return false;
 960+ }
 961+ }
 962+
 963+ /**
 964+ * Get the 14-character timestamp of the file upload, or false if
 965+ */
 966+ function getTimestmap() {
 967+ $path = $this->getPath();
 968+ if ( !file_exists( $path ) ) {
 969+ return false;
 970+ }
 971+ return wfTimestamp( filemtime( $path ) );
 972+ }
 973+
 974+ /**
 975+ * Determine if the current user is allowed to view a particular
 976+ * field of this file, if it's marked as deleted.
 977+ * STUB
 978+ * @param int $field
 979+ * @return bool
 980+ */
 981+ function userCan( $field ) {
 982+ return true;
 983+ }
 984+}
 985+
 986+?>
Property changes on: trunk/phase3/includes/filerepo/File.php
___________________________________________________________________
Added: svn:keywords
1987 + Author Date Id Revision
Added: svn:eol-style
2988 + native
Index: trunk/phase3/includes/filerepo/ForeignDBFile.php
@@ -0,0 +1,39 @@
 2+<?php
 3+
 4+class ForeignDBFile extends LocalFile {
 5+ function newFromTitle( $title, $repo ) {
 6+ return new self( $title, $repo );
 7+ }
 8+
 9+ function getCacheKey() {
 10+ if ( $this->repo->hasSharedCache ) {
 11+ $hashedName = md5($this->name);
 12+ return wfForeignMemcKey( $this->repo->dbName, $this->repo->tablePrefix,
 13+ 'file', $hashedName );
 14+ } else {
 15+ return false;
 16+ }
 17+ }
 18+
 19+ function publish( /*...*/ ) {
 20+ $this->readOnlyError();
 21+ }
 22+
 23+ function recordUpload( /*...*/ ) {
 24+ $this->readOnlyError();
 25+ }
 26+ function restore( /*...*/ ) {
 27+ $this->readOnlyError();
 28+ }
 29+
 30+ function getDescriptionUrl() {
 31+ // Restore remote behaviour
 32+ return File::getDescriptionUrl();
 33+ }
 34+
 35+ function getDescriptionText() {
 36+ // Restore remote behaviour
 37+ return File::getDescriptionText();
 38+ }
 39+}
 40+?>
Property changes on: trunk/phase3/includes/filerepo/ForeignDBFile.php
___________________________________________________________________
Added: svn:eol-style
141 + native
Index: trunk/phase3/includes/filerepo/LocalRepo.php
@@ -0,0 +1,26 @@
 2+<?php
 3+/**
 4+ * A repository that stores files in the local filesystem and registers them
 5+ * in the wiki's own database. This is the most commonly used repository class.
 6+ */
 7+class LocalRepo extends FSRepo {
 8+ var $fileFactory = array( 'LocalFile', 'newFromTitle' );
 9+
 10+ function getSlaveDB() {
 11+ return wfGetDB( DB_SLAVE );
 12+ }
 13+
 14+ function getMasterDB() {
 15+ return wfGetDB( DB_MASTER );
 16+ }
 17+
 18+ function newFileFromRow( $row ) {
 19+ if ( isset( $row->img_name ) ) {
 20+ return LocalFile::newFromRow( $row, $this );
 21+ } elseif ( isset( $row->oi_name ) ) {
 22+ return OldLocalFile::newFromRow( $row, $this );
 23+ } else {
 24+ throw new MWException( __METHOD__.': invalid row' );
 25+ }
 26+ }
 27+}
Property changes on: trunk/phase3/includes/filerepo/LocalRepo.php
___________________________________________________________________
Added: svn:eol-style
128 + native
Index: trunk/phase3/includes/filerepo/RepoGroup.php
@@ -0,0 +1,98 @@
 2+<?php
 3+
 4+class RepoGroup {
 5+ var $localRepo, $foreignRepos, $reposInitialised = false;
 6+ var $localInfo, $foreignInfo;
 7+
 8+ protected static $instance;
 9+
 10+ function singleton() {
 11+ if ( self::$instance ) {
 12+ return self::$instance;
 13+ }
 14+ global $wgLocalFileRepo, $wgForeignFileRepos;
 15+ self::$instance = new RepoGroup( $wgLocalFileRepo, $wgForeignFileRepos );
 16+ return self::$instance;
 17+ }
 18+
 19+ /**
 20+ * Construct a group of file repositories.
 21+ * @param array $data Array of repository info arrays.
 22+ * Each info array is an associative array with the 'class' member
 23+ * giving the class name. The entire array is passed to the repository
 24+ * constructor as the first parameter.
 25+ */
 26+ function __construct( $localInfo, $foreignInfo ) {
 27+ $this->localInfo = $localInfo;
 28+ $this->foreignInfo = $foreignInfo;
 29+ }
 30+
 31+ /**
 32+ * Search repositories for an image.
 33+ * You can also use wfGetFile() to do this.
 34+ * @param mixed $title Title object or string
 35+ * @param mixed $time The 14-char timestamp before which the file should
 36+ * have been uploaded, or false for the current version
 37+ * @return File object or false if it is not found
 38+ */
 39+ function findFile( $title, $time = false ) {
 40+ if ( !$this->reposInitialised ) {
 41+ $this->initialiseRepos();
 42+ }
 43+
 44+ $image = $this->localRepo->findFile( $title, $time );
 45+ if ( $image ) {
 46+ return $image;
 47+ }
 48+ foreach ( $this->foreignRepos as $repo ) {
 49+ $image = $repo->findFile( $image, $time );
 50+ if ( $image ) {
 51+ return $image;
 52+ }
 53+ }
 54+ return false;
 55+ }
 56+
 57+ /**
 58+ * Get the repo instance with a given key.
 59+ */
 60+ function getRepo( $index ) {
 61+ if ( !$this->reposInitialised ) {
 62+ $this->initialiseRepos();
 63+ }
 64+ if ( $index == 'local' ) {
 65+ return $this->localRepo;
 66+ } elseif ( isset( $this->foreignRepos[$index] ) ) {
 67+ return $this->foreignRepos[$index];
 68+ } else {
 69+ return false;
 70+ }
 71+ }
 72+
 73+ function getLocalRepo() {
 74+ return $this->getRepo( 'local' );
 75+ }
 76+
 77+ /**
 78+ * Initialise the $repos array
 79+ */
 80+ function initialiseRepos() {
 81+ if ( $this->reposInitialised ) {
 82+ return;
 83+ }
 84+ $this->reposInitialised = true;
 85+
 86+ $this->localRepo = $this->newRepo( $this->localInfo );
 87+ $this->foreignRepos = array();
 88+ foreach ( $this->foreignInfo as $key => $info ) {
 89+ $this->foreignRepos[$key] = $this->newRepo( $info );
 90+ }
 91+ }
 92+
 93+ function newRepo( $info ) {
 94+ $class = $info['class'];
 95+ return new $class( $info );
 96+ }
 97+}
 98+
 99+?>
Property changes on: trunk/phase3/includes/filerepo/RepoGroup.php
___________________________________________________________________
Added: svn:eol-style
1100 + native
Index: trunk/phase3/includes/filerepo/ArchivedFile.php
@@ -0,0 +1,112 @@
 2+<?php
 3+
 4+/**
 5+ * @addtogroup Media
 6+ */
 7+class ArchivedFile
 8+{
 9+ /**
 10+ * Returns a file object from the filearchive table
 11+ * In the future, all current and old image storage
 12+ * may use FileStore. There will be a "old" storage
 13+ * for current and previous file revisions as well as
 14+ * the "deleted" group for archived revisions
 15+ * @param $title, the corresponding image page title
 16+ * @param $id, the image id, a unique key
 17+ * @param $key, optional storage key
 18+ * @return ResultWrapper
 19+ */
 20+ function ArchivedFile( $title, $id=0, $key='' ) {
 21+ if( !is_object( $title ) ) {
 22+ throw new MWException( 'ArchivedFile constructor given bogus title.' );
 23+ }
 24+ $conds = ($id) ? "fa_id = $id" : "fa_storage_key = '$key'";
 25+ if( $title->getNamespace() == NS_IMAGE ) {
 26+ $dbr = wfGetDB( DB_SLAVE );
 27+ $res = $dbr->select( 'filearchive',
 28+ array(
 29+ 'fa_id',
 30+ 'fa_name',
 31+ 'fa_storage_key',
 32+ 'fa_storage_group',
 33+ 'fa_size',
 34+ 'fa_bits',
 35+ 'fa_width',
 36+ 'fa_height',
 37+ 'fa_metadata',
 38+ 'fa_media_type',
 39+ 'fa_major_mime',
 40+ 'fa_minor_mime',
 41+ 'fa_description',
 42+ 'fa_user',
 43+ 'fa_user_text',
 44+ 'fa_timestamp',
 45+ 'fa_deleted' ),
 46+ array(
 47+ 'fa_name' => $title->getDbKey(),
 48+ $conds ),
 49+ __METHOD__,
 50+ array( 'ORDER BY' => 'fa_timestamp DESC' ) );
 51+
 52+ if ( $dbr->numRows( $res ) == 0 ) {
 53+ // this revision does not exist?
 54+ return;
 55+ }
 56+ $ret = $dbr->resultObject( $res );
 57+ $row = $ret->fetchObject();
 58+
 59+ // initialize fields for filestore image object
 60+ $this->mId = intval($row->fa_id);
 61+ $this->mName = $row->fa_name;
 62+ $this->mGroup = $row->fa_storage_group;
 63+ $this->mKey = $row->fa_storage_key;
 64+ $this->mSize = $row->fa_size;
 65+ $this->mBits = $row->fa_bits;
 66+ $this->mWidth = $row->fa_width;
 67+ $this->mHeight = $row->fa_height;
 68+ $this->mMetaData = $row->fa_metadata;
 69+ $this->mMime = "$row->fa_major_mime/$row->fa_minor_mime";
 70+ $this->mType = $row->fa_media_type;
 71+ $this->mDescription = $row->fa_description;
 72+ $this->mUser = $row->fa_user;
 73+ $this->mUserText = $row->fa_user_text;
 74+ $this->mTimestamp = $row->fa_timestamp;
 75+ $this->mDeleted = $row->fa_deleted;
 76+ } else {
 77+ throw new MWException( 'This title does not correspond to an image page.' );
 78+ return;
 79+ }
 80+ return true;
 81+ }
 82+
 83+ /**
 84+ * int $field one of DELETED_* bitfield constants
 85+ * for file or revision rows
 86+ * @return bool
 87+ */
 88+ function isDeleted( $field ) {
 89+ return ($this->mDeleted & $field) == $field;
 90+ }
 91+
 92+ /**
 93+ * Determine if the current user is allowed to view a particular
 94+ * field of this FileStore image file, if it's marked as deleted.
 95+ * @param int $field
 96+ * @return bool
 97+ */
 98+ function userCan( $field ) {
 99+ if( isset($this->mDeleted) && ($this->mDeleted & $field) == $field ) {
 100+ // images
 101+ global $wgUser;
 102+ $permission = ( $this->mDeleted & File::DELETED_RESTRICTED ) == File::DELETED_RESTRICTED
 103+ ? 'hiderevision'
 104+ : 'deleterevision';
 105+ wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
 106+ return $wgUser->isAllowed( $permission );
 107+ } else {
 108+ return true;
 109+ }
 110+ }
 111+}
 112+
 113+?>
Property changes on: trunk/phase3/includes/filerepo/ArchivedFile.php
___________________________________________________________________
Added: svn:eol-style
1114 + native
Index: trunk/phase3/includes/filerepo/ForeignDBRepo.php
@@ -0,0 +1,51 @@
 2+<?php
 3+
 4+/**
 5+ * A foreign repository with an accessible MediaWiki database
 6+ */
 7+
 8+class ForeignDBRepo extends LocalRepo {
 9+ # Settings
 10+ var $dbType, $dbServer, $dbUser, $dbPassword, $dbName, $dbFlags,
 11+ $tablePrefix, $hasSharedCache;
 12+
 13+ # Other stuff
 14+ var $dbConn;
 15+ var $fileFactory = array( 'ForeignDBFile', 'newFromTitle' );
 16+
 17+ function __construct( $info ) {
 18+ parent::__construct( $info );
 19+ $this->dbType = $info['dbType'];
 20+ $this->dbServer = $info['dbServer'];
 21+ $this->dbUser = $info['dbUser'];
 22+ $this->dbPassword = $info['dbPassword'];
 23+ $this->dbName = $info['dbName'];
 24+ $this->dbFlags = $info['dbFlags'];
 25+ $this->tablePrefix = $info['tablePrefix'];
 26+ $this->hasSharedCache = $info['hasSharedCache'];
 27+ }
 28+
 29+ function getMasterDB() {
 30+ if ( !isset( $this->dbConn ) ) {
 31+ $class = 'Database' . ucfirst( $this->dbType );
 32+ $this->dbConn = new $class( $this->dbServer, $this->dbUser,
 33+ $this->dbPassword, $this->dbName, false, $this->dbFlags,
 34+ $this->tablePrefix );
 35+ }
 36+ return $this->dbConn;
 37+ }
 38+
 39+ function getSlaveDB() {
 40+ return $this->getMasterDB();
 41+ }
 42+
 43+ function hasSharedCache() {
 44+ return $this->hasSharedCache;
 45+ }
 46+
 47+ function store( /*...*/ ) {
 48+ throw new MWException( get_class($this) . ': write operations are not supported' );
 49+ }
 50+}
 51+
 52+?>
Property changes on: trunk/phase3/includes/filerepo/ForeignDBRepo.php
___________________________________________________________________
Added: svn:eol-style
153 + native
Index: trunk/phase3/includes/SpecialUpload.php
@@ -27,6 +27,7 @@
2828 var $mUploadCopyStatus, $mUploadSource, $mReUpload, $mAction, $mUpload;
2929 var $mOname, $mSessionKey, $mStashed, $mDestFile, $mRemoveTempFile, $mSourceType;
3030 var $mUploadTempFileSize = 0;
 31+ var $mImage;
3132
3233 # Placeholders for text injection by hooks (must be HTML)
3334 # extensions should take care to _append_ to the present value
@@ -412,13 +413,13 @@
413414
414415 global $wgUser;
415416 $sk = $wgUser->getSkin();
416 - $image = new Image( $nt );
 417+ $image = wfLocalFile( $nt );
417418
418419 // Check for uppercase extension. We allow these filenames but check if an image
419420 // with lowercase extension exists already
420421 if ( $finalExt != strtolower( $finalExt ) ) {
421422 $nt_lc = Title::newFromText( $partname . '.' . strtolower( $finalExt ) );
422 - $image_lc = new Image( $nt_lc );
 423+ $image_lc = wfLocalFile( $nt_lc );
423424 }
424425
425426 if( $image->exists() ) {
@@ -452,7 +453,7 @@
453454 } elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' ) && ereg( "[0-9]{2}" , substr( $partname , 0, 2) ) ) {
454455 # Check for filenames like 50px- or 180px-, these are mostly thumbnails
455456 $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $finalExt );
456 - $image_thb = new Image( $nt_thb );
 457+ $image_thb = wfLocalFile( $nt_thb );
457458 if ($image_thb->exists() ) {
458459 # Check if an image without leading '180px-' (or similiar) exists
459460 $dlink = $sk->makeKnownLinkObj( $nt_thb);
@@ -500,8 +501,8 @@
501502 * Update the upload log and create the description page
502503 * if it's a new file.
503504 */
504 - $img = Image::newFromName( $this->mUploadSaveName );
505 - $success = $img->recordUpload( $this->mUploadOldVersion,
 505+ $this->mImage = wfLocalFile( $this->mUploadSaveName );
 506+ $success = $this->mImage->recordUpload( $this->mUploadOldVersion,
506507 $this->mUploadDescription,
507508 $this->mLicense,
508509 $this->mUploadCopyStatus,
@@ -512,7 +513,7 @@
513514 $this->showSuccess();
514515 wfRunHooks( 'UploadComplete', array( &$img ) );
515516 } else {
516 - // Image::recordUpload() fails if the image went missing, which is
 517+ // File::recordUpload() fails if the image went missing, which is
517518 // unlikely, hence the lack of a specialised message
518519 $wgOut->showFileNotFoundError( $this->mUploadSaveName );
519520 }
@@ -534,48 +535,13 @@
535536 function saveUploadedFile( $saveName, $tempName, $useRename = false ) {
536537 global $wgOut, $wgAllowCopyUploads;
537538
538 - if ( !$useRename AND $wgAllowCopyUploads AND $this->mSourceType == 'web' ) $useRename = true;
539 -
540 - $fname= "SpecialUpload::saveUploadedFile";
541 -
542 - $dest = wfImageDir( $saveName );
543 - $archive = wfImageArchiveDir( $saveName );
544 - if ( !is_dir( $dest ) ) wfMkdirParents( $dest );
545 - if ( !is_dir( $archive ) ) wfMkdirParents( $archive );
546 -
547 - $this->mSavedFile = "{$dest}/{$saveName}";
548 -
549 - if( is_file( $this->mSavedFile ) ) {
550 - $this->mUploadOldVersion = gmdate( 'YmdHis' ) . "!{$saveName}";
551 - wfSuppressWarnings();
552 - $success = rename( $this->mSavedFile, "${archive}/{$this->mUploadOldVersion}" );
553 - wfRestoreWarnings();
554 -
555 - if( ! $success ) {
556 - $wgOut->showFileRenameError( $this->mSavedFile,
557 - "${archive}/{$this->mUploadOldVersion}" );
558 - return false;
559 - }
560 - else wfDebug("$fname: moved file ".$this->mSavedFile." to ${archive}/{$this->mUploadOldVersion}\n");
561 - }
562 - else {
563 - $this->mUploadOldVersion = '';
564 - }
565 -
566 - wfSuppressWarnings();
567 - $success = $useRename
568 - ? rename( $tempName, $this->mSavedFile )
569 - : move_uploaded_file( $tempName, $this->mSavedFile );
570 - wfRestoreWarnings();
571 -
572 - if( ! $success ) {
573 - $wgOut->showFileCopyError( $tempName, $this->mSavedFile );
 539+ $image = wfLocalFile( $saveName );
 540+ $archiveName = $image->publish( $tempName, File::DELETE_SOURCE );
 541+ if ( WikiError::isError( $archiveName ) ) {
 542+ $this->showError( $archiveName );
574543 return false;
575 - } else {
576 - wfDebug("$fname: wrote tempfile $tempName to ".$this->mSavedFile."\n");
577544 }
578 -
579 - chmod( $this->mSavedFile, 0644 );
 545+ $this->mUploadOldVersion = $archiveName;
580546 return true;
581547 }
582548
@@ -593,19 +559,14 @@
594560 */
595561 function saveTempUploadedFile( $saveName, $tempName ) {
596562 global $wgOut;
597 - $archive = wfImageArchiveDir( $saveName, 'temp' );
598 - if ( !is_dir ( $archive ) ) wfMkdirParents( $archive );
599 - $stash = $archive . '/' . gmdate( "YmdHis" ) . '!' . $saveName;
600 -
601 - $success = $this->mRemoveTempFile
602 - ? rename( $tempName, $stash )
603 - : move_uploaded_file( $tempName, $stash );
604 - if ( !$success ) {
605 - $wgOut->showFileCopyError( $tempName, $stash );
 563+ $repo = RepoGroup::singleton()->getLocalRepo();
 564+ $result = $repo->storeTemp( $saveName, $tempName );
 565+ if ( WikiError::isError( $result ) ) {
 566+ $this->showError( $result );
606567 return false;
 568+ } else {
 569+ return $result;
607570 }
608 -
609 - return $stash;
610571 }
611572
612573 /**
@@ -662,7 +623,7 @@
663624 global $wgUser, $wgOut, $wgContLang;
664625
665626 $sk = $wgUser->getSkin();
666 - $ilink = $sk->makeMediaLink( $this->mUploadSaveName, Image::imageUrl( $this->mUploadSaveName ) );
 627+ $ilink = $sk->makeMediaLinkObj( $this->mImage->getTitle() );
667628 $dname = $wgContLang->getNsText( NS_IMAGE ) . ':'.$this->mUploadSaveName;
668629 $dlink = $sk->makeKnownLink( $dname, $dname );
669630
@@ -1274,15 +1235,10 @@
12751236 * @access private
12761237 */
12771238 function checkOverwrite( $name ) {
1278 - $img = Image::newFromName( $name );
1279 - if( is_null( $img ) ) {
1280 - // Uh... this shouldn't happen ;)
1281 - // But if it does, fall through to previous behavior
1282 - return false;
1283 - }
 1239+ $img = wfFindFile( $name );
12841240
12851241 $error = '';
1286 - if( $img->exists() ) {
 1242+ if( $img ) {
12871243 global $wgUser, $wgOut;
12881244 if( $img->isLocal() ) {
12891245 if( !self::userCanReUpload( $wgUser, $img->name ) ) {
@@ -1328,5 +1284,17 @@
13291285
13301286 return $user->getID() == $row->img_user;
13311287 }
 1288+
 1289+ /**
 1290+ * Display an error from a wikitext-formatted WikiError object
 1291+ */
 1292+ function showError( WikiError $error ) {
 1293+ global $wgOut;
 1294+ $wgOut->setPageTitle( wfMsg( "internalerror" ) );
 1295+ $wgOut->setRobotpolicy( "noindex,nofollow" );
 1296+ $wgOut->setArticleRelated( false );
 1297+ $wgOut->enableClientCache( false );
 1298+ $wgOut->addWikiText( $error->getMessage() );
 1299+ }
13321300 }
13331301 ?>
Index: trunk/phase3/includes/SpecialMIMEsearch.php
@@ -66,7 +66,7 @@
6767 $text = $wgContLang->convert( $nt->getText() );
6868 $plink = $skin->makeLink( $nt->getPrefixedText(), $text );
6969
70 - $download = $skin->makeMediaLink( $nt->getText(), 'fuck me!', wfMsgHtml( 'download' ) );
 70+ $download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) );
7171 $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
7272 $wgLang->formatNum( $result->img_size ) );
7373 $dimensions = wfMsgHtml( 'widthheight', $wgLang->formatNum( $result->img_width ),
Index: trunk/phase3/includes/SpecialNewimages.php
@@ -135,10 +135,9 @@
136136 $ut = $s->img_user_text;
137137
138138 $nt = Title::newFromText( $name, NS_IMAGE );
139 - $img = new Image( $nt );
140139 $ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut );
141140
142 - $gallery->add( $img, "$ul<br />\n<i>".$wgLang->timeanddate( $s->img_timestamp, true )."</i><br />\n" );
 141+ $gallery->add( $nt, "$ul<br />\n<i>".$wgLang->timeanddate( $s->img_timestamp, true )."</i><br />\n" );
143142
144143 $timestamp = wfTimestamp( TS_MW, $s->img_timestamp );
145144 if( empty( $firstTimestamp ) ) {
Index: trunk/phase3/includes/media/Bitmap.php
@@ -51,7 +51,7 @@
5252 $srcWidth = $image->getWidth();
5353 $srcHeight = $image->getHeight();
5454 $mimeType = $image->getMimeType();
55 - $srcPath = $image->getImagePath();
 55+ $srcPath = $image->getPath();
5656 $retval = 0;
5757 wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
5858
@@ -61,7 +61,10 @@
6262 return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath );
6363 }
6464
65 - if ( $wgUseImageMagick ) {
 65+ if ( !$dstPath ) {
 66+ // No output path available, client side scaling only
 67+ $scaler = 'client';
 68+ } elseif ( $wgUseImageMagick ) {
6669 $scaler = 'im';
6770 } elseif ( $wgCustomConvertCommand ) {
6871 $scaler = 'custom';
Index: trunk/phase3/includes/media/Generic.php
@@ -152,7 +152,7 @@
153153 * Returns false if unknown or if the document is not multi-page.
154154 */
155155 function getPageDimensions( $image, $page ) {
156 - $gis = $this->getImageSize( $image, $image->getImagePath() );
 156+ $gis = $this->getImageSize( $image, $image->getPath() );
157157 return array(
158158 'width' => $gis[0],
159159 'height' => $gis[1]
@@ -220,7 +220,7 @@
221221 $params['width'] = wfFitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
222222 }
223223 }
224 - $params['height'] = Image::scaleHeight( $srcWidth, $srcHeight, $params['width'] );
 224+ $params['height'] = File::scaleHeight( $srcWidth, $srcHeight, $params['width'] );
225225 if ( !$this->validateThumbParams( $params['width'], $params['height'], $srcWidth, $srcHeight, $mimeType ) ) {
226226 return false;
227227 }
@@ -254,7 +254,7 @@
255255 return false;
256256 }
257257
258 - $height = Image::scaleHeight( $srcWidth, $srcHeight, $width );
 258+ $height = File::scaleHeight( $srcWidth, $srcHeight, $width );
259259 return true;
260260 }
261261
Index: trunk/phase3/includes/media/SVG.php
@@ -31,7 +31,7 @@
3232 $srcWidth = $image->getWidth( $params['page'] );
3333 $srcHeight = $image->getHeight( $params['page'] );
3434 $params['physicalWidth'] = $wgSVGMaxSize;
35 - $params['physicalHeight'] = Image::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
 35+ $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
3636 }
3737 return true;
3838 }
@@ -46,7 +46,7 @@
4747 $clientHeight = $params['height'];
4848 $physicalWidth = $params['physicalWidth'];
4949 $physicalHeight = $params['physicalHeight'];
50 - $srcPath = $image->getImagePath();
 50+ $srcPath = $image->getPath();
5151
5252 if ( $flags & self::TRANSFORM_LATER ) {
5353 return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath );
Index: trunk/phase3/includes/media/DjVu.php
@@ -69,7 +69,7 @@
7070 }
7171 $width = $params['width'];
7272 $height = $params['height'];
73 - $srcPath = $image->getImagePath();
 73+ $srcPath = $image->getPath();
7474 $page = $params['page'];
7575 if ( $page > $this->pageCount( $image ) ) {
7676 return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'djvu_page_error' ) );
Index: trunk/phase3/includes/AutoLoader.php
@@ -96,8 +96,6 @@
9797 'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php',
9898 'EnotifNotifyJob' => 'includes/JobQueue.php',
9999 'Http' => 'includes/HttpFunctions.php',
100 - 'Image' => 'includes/Image.php',
101 - 'ArchivedFile' => 'includes/Image.php',
102100 'IP' => 'includes/IP.php',
103101 'ThumbnailImage' => 'includes/Image.php',
104102 'ImageGallery' => 'includes/ImageGallery.php',
@@ -250,6 +248,19 @@
251249 'memcached' => 'includes/memcached-client.php',
252250 'EmaillingJob' => 'includes/JobQueue.php',
253251
 252+ # filerepo
 253+ 'ArchivedFile' => 'includes/filerepo/ArchivedFile.php',
 254+ 'File' => 'includes/filerepo/File.php',
 255+ 'ForeignDBFile' => 'includes/filerepo/ForeignDBFile.php',
 256+ 'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php',
 257+ 'FSRepo' => 'includes/filerepo/FSRepo.php',
 258+ 'Image' => 'includes/filerepo/LocalFile.php',
 259+ 'LocalFile' => 'includes/filerepo/LocalFile.php',
 260+ 'LocalRepo' => 'includes/filerepo/LocalRepo.php',
 261+ 'OldLocalFile' => 'includes/filerepo/OldLocalFile.php',
 262+ 'RepoGroup' => 'includes/filerepo/RepoGroup.php',
 263+ 'UnregisteredLocalFile' => 'includes/filerepo/UnregisteredLocalFile.php',
 264+
254265 # Media
255266 'BitmapHandler' => 'includes/media/Bitmap.php',
256267 'BmpHandler' => 'includes/media/BMP.php',
Index: trunk/phase3/includes/SpecialUndelete.php
@@ -269,7 +269,7 @@
270270 $restoreFiles = $restoreAll || !empty( $fileVersions );
271271
272272 if( $restoreFiles && $this->title->getNamespace() == NS_IMAGE ) {
273 - $img = new Image( $this->title );
 273+ $img = wfLocalFile( $this->title );
274274 $filesRestored = $img->restore( $fileVersions );
275275 } else {
276276 $filesRestored = 0;
Index: trunk/phase3/includes/SpecialImagelist.php
@@ -115,7 +115,9 @@
116116 case 'img_name':
117117 $name = $this->mCurrentRow->img_name;
118118 $link = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_IMAGE, $name ), $value );
119 - $download = Xml::element('a', array( "href" => Image::imageUrl( $name ) ), $this->mMessages['imgfile'] );
 119+ $image = wfLocalFile( $value );
 120+ $url = $image->getURL();
 121+ $download = Xml::element('a', array( "href" => $url ), $this->mMessages['imgfile'] );
120122 return "$link ($download)";
121123 case 'img_user_text':
122124 if ( $this->mCurrentRow->img_user ) {
Index: trunk/phase3/includes/StreamFile.php
@@ -31,6 +31,9 @@
3232 header('Content-type: application/x-wiki');
3333 }
3434
 35+ global $wgContLanguageCode;
 36+ header( "Content-Disposition: inline;filename*=utf-8'$wgContLanguageCode'" . urlencode( basename( $fname ) ) );
 37+
3538 if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
3639 $modsince = preg_replace( '/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
3740 $sinceTime = strtotime( $modsince );
Index: trunk/phase3/includes/DefaultSettings.php
@@ -184,6 +184,49 @@
185185 $wgFileStore['deleted']['url'] = null; // Private
186186 $wgFileStore['deleted']['hash'] = 3; // 3-level subdirectory split
187187
 188+/**#@+
 189+ * File repository structures
 190+ *
 191+ * $wgLocalFileRepo is a single repository structure, and $wgForeignFileRepo is
 192+ * a an array of such structures. Each repository structure is an associative
 193+ * array of properties configuring the repository.
 194+ *
 195+ * Properties required for all repos:
 196+ * class The class name for the repository. May come from the core or an extension.
 197+ * The core repository classes are LocalRepo, ForeignDBRepo, FSRepo.
 198+ *
 199+ * name A unique name for the repository.
 200+ *
 201+ * For all core repos:
 202+ * url Base public URL
 203+ * hashLevels The number of directory levels for hash-based division of files
 204+ * thumbScriptUrl The URL for thumb.php (optional, not recommended)
 205+ * transformVia404 Whether to skip media file transformation on parse and rely on a 404
 206+ * handler instead.
 207+ *
 208+ * These settings describe a foreign MediaWiki installation. They are optional, and will be ignored
 209+ * for local repositories:
 210+ * descBaseUrl URL of image description pages, e.g. http://en.wikipedia.org/wiki/Image:
 211+ * scriptDirUrl URL of the MediaWiki installation, equivalent to $wgScriptPath, e.g.
 212+ * http://en.wikipedia.org/w
 213+ *
 214+ * articleUrl Equivalent to $wgArticlePath, e.g. http://en.wikipedia.org/wiki/$1
 215+ * fetchDescription Fetch the text of the remote file description page. Equivalent to
 216+ * $wgFetchCommonsDescriptions.
 217+ *
 218+ * ForeignDBRepo:
 219+ * dbType, dbServer, dbUser, dbPassword, dbName, dbFlags
 220+ * equivalent to the corresponding member of $wgDBservers
 221+ * tablePrefix Table prefix, the foreign wiki's $wgDBprefix
 222+ * hasSharedCache True if the wiki's shared cache is accessible via the local $wgMemc
 223+ *
 224+ * The default is to initialise these arrays from the MW<1.11 backwards compatible settings:
 225+ * $wgUploadPath, $wgThumbnailScriptPath, $wgSharedUploadDirectory, etc.
 226+ */
 227+$wgLocalFileRepo = false;
 228+$wgForeignFileRepos = array();
 229+/**#@-*/
 230+
188231 /**
189232 * Allowed title characters -- regex character class
190233 * Don't change this unless you know what you're doing
@@ -355,6 +398,10 @@
356399 * no file of the given name is found in the local repository (for [[Image:..]],
357400 * [[Media:..]] links). Thumbnails will also be looked for and generated in this
358401 * directory.
 402+ *
 403+ * Note that these configuration settings can now be defined on a per-
 404+ * repository basis for an arbitrary number of file repositories, using the
 405+ * $wgForeignFileRepos variable.
359406 */
360407 $wgUseSharedUploads = false;
361408 /** Full path on the web server where shared uploads can be found */
Index: trunk/phase3/includes/Skin.php
@@ -740,8 +740,8 @@
741741 if ( $wgOut->isArticleRelated() ) {
742742 if ( $wgTitle->getNamespace() == NS_IMAGE ) {
743743 $name = $wgTitle->getDBkey();
744 - $image = new Image( $wgTitle );
745 - if( $image->exists() ) {
 744+ $image = wfFindFile( $wgTitle );
 745+ if( $image ) {
746746 $link = htmlspecialchars( $image->getURL() );
747747 $style = $this->getInternalLinkAttributes( $link, $name );
748748 $s .= " | <a href=\"{$link}\"{$style}>{$name}</a>";
Index: trunk/phase3/includes/ImageFunctions.php
@@ -1,114 +1,5 @@
22 <?php
3 -
43 /**
5 - * Returns the image directory of an image
6 - * The result is an absolute path.
7 - *
8 - * This function is called from thumb.php before Setup.php is included
9 - *
10 - * @param $fname String: file name of the image file.
11 - * @public
12 - */
13 -function wfImageDir( $fname ) {
14 - global $wgUploadDirectory, $wgHashedUploadDirectory;
15 -
16 - if (!$wgHashedUploadDirectory) { return $wgUploadDirectory; }
17 -
18 - $hash = md5( $fname );
19 - $dest = $wgUploadDirectory . '/' . $hash{0} . '/' . substr( $hash, 0, 2 );
20 -
21 - return $dest;
22 -}
23 -
24 -/**
25 - * Returns the image directory of an image's thumbnail
26 - * The result is an absolute path.
27 - *
28 - * This function is called from thumb.php before Setup.php is included
29 - *
30 - * @param $fname String: file name of the original image file
31 - * @param $shared Boolean: (optional) use the shared upload directory (default: 'false').
32 - * @public
33 - */
34 -function wfImageThumbDir( $fname, $shared = false ) {
35 - $base = wfImageArchiveDir( $fname, 'thumb', $shared );
36 - if ( Image::isHashed( $shared ) ) {
37 - $dir = "$base/$fname";
38 - } else {
39 - $dir = $base;
40 - }
41 -
42 - return $dir;
43 -}
44 -
45 -/**
46 - * Old thumbnail directory, kept for conversion
47 - */
48 -function wfDeprecatedThumbDir( $thumbName , $subdir='thumb', $shared=false) {
49 - return wfImageArchiveDir( $thumbName, $subdir, $shared );
50 -}
51 -
52 -/**
53 - * Returns the image directory of an image's old version
54 - * The result is an absolute path.
55 - *
56 - * This function is called from thumb.php before Setup.php is included
57 - *
58 - * @param $fname String: file name of the thumbnail file, including file size prefix.
59 - * @param $subdir String: subdirectory of the image upload directory that should be used for storing the old version. Default is 'archive'.
60 - * @param $shared Boolean use the shared upload directory (only relevant for other functions which call this one). Default is 'false'.
61 - * @public
62 - */
63 -function wfImageArchiveDir( $fname , $subdir='archive', $shared=false ) {
64 - global $wgUploadDirectory, $wgHashedUploadDirectory;
65 - global $wgSharedUploadDirectory, $wgHashedSharedUploadDirectory;
66 - $dir = $shared ? $wgSharedUploadDirectory : $wgUploadDirectory;
67 - $hashdir = $shared ? $wgHashedSharedUploadDirectory : $wgHashedUploadDirectory;
68 - if (!$hashdir) { return $dir.'/'.$subdir; }
69 - $hash = md5( $fname );
70 -
71 - return $dir.'/'.$subdir.'/'.$hash[0].'/'.substr( $hash, 0, 2 );
72 -}
73 -
74 -
75 -/*
76 - * Return the hash path component of an image path (URL or filesystem),
77 - * e.g. "/3/3c/", or just "/" if hashing is not used.
78 - *
79 - * @param $dbkey The filesystem / database name of the file
80 - * @param $fromSharedDirectory Use the shared file repository? It may
81 - * use different hash settings from the local one.
82 - */
83 -function wfGetHashPath ( $dbkey, $fromSharedDirectory = false ) {
84 - if( Image::isHashed( $fromSharedDirectory ) ) {
85 - $hash = md5($dbkey);
86 - return '/' . $hash{0} . '/' . substr( $hash, 0, 2 ) . '/';
87 - } else {
88 - return '/';
89 - }
90 -}
91 -
92 -/**
93 - * Returns the image URL of an image's old version
94 - *
95 - * @param $name String: file name of the image file
96 - * @param $subdir String: (optional) subdirectory of the image upload directory that is used by the old version. Default is 'archive'
97 - * @public
98 - */
99 -function wfImageArchiveUrl( $name, $subdir='archive' ) {
100 - global $wgUploadPath, $wgHashedUploadDirectory;
101 -
102 - if ($wgHashedUploadDirectory) {
103 - $hash = md5( substr( $name, 15) );
104 - $url = $wgUploadPath.'/'.$subdir.'/' . $hash{0} . '/' .
105 - substr( $hash, 0, 2 ) . '/'.$name;
106 - } else {
107 - $url = $wgUploadPath.'/'.$subdir.'/'.$name;
108 - }
109 - return wfUrlencode($url);
110 -}
111 -
112 -/**
1134 * Return a rounded pixel equivalent for a labeled CSS/SVG length.
1145 * http://www.w3.org/TR/SVG11/coords.html#UnitIdentifiers
1156 *
Index: trunk/phase3/includes/MediaTransformOutput.php
@@ -1,7 +1,7 @@
22 <?php
33
44 /**
5 - * Base class for the output of MediaHandler::doTransform() and Image::transform().
 5+ * Base class for the output of MediaHandler::doTransform() and File::transform().
66 *
77 * @addtogroup Media
88 */
Index: trunk/phase3/includes/ImageGallery.php
@@ -127,22 +127,26 @@
128128 /**
129129 * Add an image to the gallery.
130130 *
131 - * @param $image Image object that is added to the gallery
 131+ * @param $title Title object of the image that is added to the gallery
132132 * @param $html String: additional HTML text to be shown. The name and size of the image are always shown.
133133 */
134 - function add( $image, $html='' ) {
135 - $this->mImages[] = array( &$image, $html );
136 - wfDebug( "ImageGallery::add " . $image->getName() . "\n" );
 134+ function add( $title, $html='' ) {
 135+ if ( $title instanceof File ) {
 136+ // Old calling convention
 137+ $title = $title->getTitle();
 138+ }
 139+ $this->mImages[] = array( $title, $html );
 140+ wfDebug( "ImageGallery::add " . $title->getText() . "\n" );
137141 }
138142
139143 /**
140144 * Add an image at the beginning of the gallery.
141145 *
142 - * @param $image Image object that is added to the gallery
 146+ * @param $title Title object of the image that is added to the gallery
143147 * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
144148 */
145 - function insert( $image, $html='' ) {
146 - array_unshift( $this->mImages, array( &$image, $html ) );
 149+ function insert( $title, $html='' ) {
 150+ array_unshift( $this->mImages, array( &$title, $html ) );
147151 }
148152
149153
@@ -195,12 +199,12 @@
196200 $params = array( 'width' => $this->mWidths, 'height' => $this->mHeights );
197201 $i = 0;
198202 foreach ( $this->mImages as $pair ) {
199 - $img =& $pair[0];
 203+ $nt = $pair[0];
200204 $text = $pair[1];
201205
202 - $nt = $img->getTitle();
 206+ $img = wfFindFile( $nt );
203207
204 - if( $nt->getNamespace() != NS_IMAGE ) {
 208+ if( $nt->getNamespace() != NS_IMAGE || !$img ) {
205209 # We're dealing with a non-image, spit out the name and be done with it.
206210 $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
207211 . htmlspecialchars( $nt->getText() ) . '</div>';
@@ -222,7 +226,7 @@
223227 //$ul = $sk->makeLink( $wgContLang->getNsText( Namespace::getUser() ) . ":{$ut}", $ut );
224228
225229 if( $this->mShowBytes ) {
226 - if( $img->exists() ) {
 230+ if( $img ) {
227231 $nb = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
228232 $wgLang->formatNum( $img->getSize() ) );
229233 } else {
Index: trunk/phase3/includes/normal/UtfNormal.php
@@ -29,59 +29,6 @@
3030 global $utfCompatibilityDecomp;
3131 $utfCompatibilityDecomp = NULL;
3232
33 -define( 'UNICODE_HANGUL_FIRST', 0xac00 );
34 -define( 'UNICODE_HANGUL_LAST', 0xd7a3 );
35 -
36 -define( 'UNICODE_HANGUL_LBASE', 0x1100 );
37 -define( 'UNICODE_HANGUL_VBASE', 0x1161 );
38 -define( 'UNICODE_HANGUL_TBASE', 0x11a7 );
39 -
40 -define( 'UNICODE_HANGUL_LCOUNT', 19 );
41 -define( 'UNICODE_HANGUL_VCOUNT', 21 );
42 -define( 'UNICODE_HANGUL_TCOUNT', 28 );
43 -define( 'UNICODE_HANGUL_NCOUNT', UNICODE_HANGUL_VCOUNT * UNICODE_HANGUL_TCOUNT );
44 -
45 -define( 'UNICODE_HANGUL_LEND', UNICODE_HANGUL_LBASE + UNICODE_HANGUL_LCOUNT - 1 );
46 -define( 'UNICODE_HANGUL_VEND', UNICODE_HANGUL_VBASE + UNICODE_HANGUL_VCOUNT - 1 );
47 -define( 'UNICODE_HANGUL_TEND', UNICODE_HANGUL_TBASE + UNICODE_HANGUL_TCOUNT - 1 );
48 -
49 -define( 'UNICODE_SURROGATE_FIRST', 0xd800 );
50 -define( 'UNICODE_SURROGATE_LAST', 0xdfff );
51 -define( 'UNICODE_MAX', 0x10ffff );
52 -define( 'UNICODE_REPLACEMENT', 0xfffd );
53 -
54 -
55 -define( 'UTF8_HANGUL_FIRST', "\xea\xb0\x80" /*codepointToUtf8( UNICODE_HANGUL_FIRST )*/ );
56 -define( 'UTF8_HANGUL_LAST', "\xed\x9e\xa3" /*codepointToUtf8( UNICODE_HANGUL_LAST )*/ );
57 -
58 -define( 'UTF8_HANGUL_LBASE', "\xe1\x84\x80" /*codepointToUtf8( UNICODE_HANGUL_LBASE )*/ );
59 -define( 'UTF8_HANGUL_VBASE', "\xe1\x85\xa1" /*codepointToUtf8( UNICODE_HANGUL_VBASE )*/ );
60 -define( 'UTF8_HANGUL_TBASE', "\xe1\x86\xa7" /*codepointToUtf8( UNICODE_HANGUL_TBASE )*/ );
61 -
62 -define( 'UTF8_HANGUL_LEND', "\xe1\x84\x92" /*codepointToUtf8( UNICODE_HANGUL_LEND )*/ );
63 -define( 'UTF8_HANGUL_VEND', "\xe1\x85\xb5" /*codepointToUtf8( UNICODE_HANGUL_VEND )*/ );
64 -define( 'UTF8_HANGUL_TEND', "\xe1\x87\x82" /*codepointToUtf8( UNICODE_HANGUL_TEND )*/ );
65 -
66 -define( 'UTF8_SURROGATE_FIRST', "\xed\xa0\x80" /*codepointToUtf8( UNICODE_SURROGATE_FIRST )*/ );
67 -define( 'UTF8_SURROGATE_LAST', "\xed\xbf\xbf" /*codepointToUtf8( UNICODE_SURROGATE_LAST )*/ );
68 -define( 'UTF8_MAX', "\xf4\x8f\xbf\xbf" /*codepointToUtf8( UNICODE_MAX )*/ );
69 -define( 'UTF8_REPLACEMENT', "\xef\xbf\xbd" /*codepointToUtf8( UNICODE_REPLACEMENT )*/ );
70 -#define( 'UTF8_REPLACEMENT', '!' );
71 -
72 -define( 'UTF8_OVERLONG_A', "\xc1\xbf" );
73 -define( 'UTF8_OVERLONG_B', "\xe0\x9f\xbf" );
74 -define( 'UTF8_OVERLONG_C', "\xf0\x8f\xbf\xbf" );
75 -
76 -# These two ranges are illegal
77 -define( 'UTF8_FDD0', "\xef\xb7\x90" /*codepointToUtf8( 0xfdd0 )*/ );
78 -define( 'UTF8_FDEF', "\xef\xb7\xaf" /*codepointToUtf8( 0xfdef )*/ );
79 -define( 'UTF8_FFFE', "\xef\xbf\xbe" /*codepointToUtf8( 0xfffe )*/ );
80 -define( 'UTF8_FFFF', "\xef\xbf\xbf" /*codepointToUtf8( 0xffff )*/ );
81 -
82 -define( 'UTF8_HEAD', false );
83 -define( 'UTF8_TAIL', true );
84 -
85 -
8633 /**
8734 * For using the ICU wrapper
8835 */
Index: trunk/phase3/StartProfiler.php
@@ -1,22 +1,24 @@
22 <?php
33
4 -require_once( dirname(__FILE__).'/includes/ProfilerStub.php' );
 4+#require_once( './includes/ProfilerStub.php' );
55
66 /**
77 * To use a profiler, delete the line above and add something like this:
88 *
9 - * require_once( dirname(__FILE__).'/includes/Profiler.php' );
 9+ * require_once( './includes/Profiler.php' );
1010 * $wgProfiler = new Profiler;
1111 *
1212 * Or for a sampling profiler:
1313 * if ( !mt_rand( 0, 100 ) ) {
14 - * require_once( dirname(__FILE__).'/includes/Profiler.php' );
 14+ * require_once( './includes/Profiler.php' );
1515 * $wgProfiler = new Profiler;
1616 * } else {
17 - * require_once( dirname(__FILE__).'/includes/ProfilerStub.php' );
 17+ * require_once( './includes/ProfilerStub.php' );
1818 * }
1919 *
2020 * Configuration of the profiler output can be done in LocalSettings.php
2121 */
 22+require_once( dirname(__FILE__).'/includes/Profiler.php' );
 23+$wgProfiler = new Profiler;
2224
2325 ?>
Index: trunk/phase3/thumb.php
@@ -2,20 +2,15 @@
33
44 /**
55 * PHP script to stream out an image thumbnail.
6 - * If the file exists, we make do with abridged MediaWiki initialisation.
76 */
8 -define( 'MW_NO_SETUP', 1 );
97 define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
108 require_once( './includes/WebStart.php' );
119 wfProfileIn( 'thumb.php' );
1210 wfProfileIn( 'thumb.php-start' );
13 -require_once( "$IP/includes/GlobalFunctions.php" );
14 -require_once( "$IP/includes/ImageFunctions.php" );
1511
1612 $wgTrivialMimeDetection = true; //don't use fancy mime detection, just check the file extension for jpg/gif/png.
1713
1814 require_once( "$IP/includes/StreamFile.php" );
19 -require_once( "$IP/includes/AutoLoader.php" );
2015
2116 // Get input parameters
2217 if ( get_magic_quotes_gpc() ) {
@@ -40,36 +35,30 @@
4136 // Some basic input validation
4237 $fileName = strtr( $fileName, '\\/', '__' );
4338
44 -// Work out paths, carefully avoiding constructing an Image object because that won't work yet
 39+// Stream the file if it exists already
4540 try {
46 - $handler = thumbGetHandler( $fileName );
47 - if ( $handler ) {
48 - $imagePath = wfImageDir( $fileName ) . '/' . $fileName;
49 - $thumbName = $handler->makeParamString( $params ) . "-$fileName";
50 - $thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;
 41+ $img = wfLocalFile( $fileName );
 42+ if ( $img && false != ( $thumbName = $img->thumbName( $params ) ) ) {
 43+ $thumbPath = $img->getThumbPath( $thumbName );
5144
52 - if ( is_file( $thumbPath ) && filemtime( $thumbPath ) >= filemtime( $imagePath ) ) {
 45+ if ( is_file( $thumbPath ) ) {
5346 wfStreamFile( $thumbPath );
54 - // Can't log profiling data with no Setup.php
 47+ wfLogProfilingData();
5548 exit;
5649 }
5750 }
5851 } catch ( MWException $e ) {
59 - require_once( './includes/Setup.php' );
6052 thumbInternalError( $e->getHTML() );
 53+ wfLogProfilingData();
6154 exit;
6255 }
6356
64 -
65 -// OK, no valid thumbnail, time to get out the heavy machinery
6657 wfProfileOut( 'thumb.php-start' );
67 -require_once( './includes/Setup.php' );
6858 wfProfileIn( 'thumb.php-render' );
6959
70 -$img = Image::newFromName( $fileName );
7160 try {
7261 if ( $img ) {
73 - $thumb = $img->transform( $params, Image::RENDER_NOW );
 62+ $thumb = $img->transform( $params, File::RENDER_NOW );
7463 } else {
7564 $thumb = false;
7665 }
@@ -80,9 +69,11 @@
8170
8271 if ( $thumb && $thumb->getPath() && file_exists( $thumb->getPath() ) ) {
8372 wfStreamFile( $thumb->getPath() );
84 -} elseif ( $img ) {
85 - if ( !$thumb ) {
86 - $msg = wfMsgHtml( 'thumbnail_error', 'Image::transform() returned false' );
 73+} else {
 74+ if ( !$img ) {
 75+ $msg = wfMsg( 'badtitletext' );
 76+ } elseif ( !$thumb ) {
 77+ $msg = wfMsgHtml( 'thumbnail_error', 'File::transform() returned false' );
8778 } elseif ( $thumb->isError() ) {
8879 $msg = $thumb->getHtmlMsg();
8980 } elseif ( !$thumb->getPath() ) {
@@ -91,19 +82,6 @@
9283 $msg = wfMsgHtml( 'thumbnail_error', 'Output file missing' );
9384 }
9485 thumbInternalError( $msg );
95 -} else {
96 - $badtitle = wfMsg( 'badtitle' );
97 - $badtitletext = wfMsg( 'badtitletext' );
98 - header( 'Cache-Control: no-cache' );
99 - header( 'Content-Type: text/html; charset=utf-8' );
100 - header( 'HTTP/1.1 500 Internal server error' );
101 - echo "<html><head>
102 - <title>$badtitle</title>
103 - <body>
104 -<h1>$badtitle</h1>
105 -<p>$badtitletext</p>
106 -</body></html>
107 -";
10886 }
10987
11088 wfProfileOut( 'thumb.php-render' );
@@ -112,17 +90,6 @@
11391
11492 //--------------------------------------------------------------------------
11593
116 -function thumbGetHandler( $fileName ) {
117 - // Determine type
118 - $magic = MimeMagic::singleton();
119 - $extPos = strrpos( $fileName, '.' );
120 - if ( $extPos === false ) {
121 - return false;
122 - }
123 - $mime = $magic->guessTypesForExtension( substr( $fileName, $extPos + 1 ) );
124 - return MediaHandler::getHandler( $mime );
125 -}
126 -
12794 function thumbInternalError( $msg ) {
12895 header( 'Cache-Control: no-cache' );
12996 header( 'Content-Type: text/html; charset=utf-8' );
Index: trunk/phase3/RELEASE-NOTES
@@ -43,7 +43,12 @@
4444 * Introducing 'frameless' keyword to [[Image:]] syntax which respects the
4545 user preferences for image width like 'thumb' but without a frame.
4646 * (bug 7960) Link to "what links here" for each "what links here" entry
 47+* Added support for configuration of an arbitrary number of commons-style
 48+ file repositories.
 49+* Added a Content-Disposition header to thumb.php output
 50+* Improved thumb.php error handling
4751
 52+
4853 == Bugfixes since 1.10 ==
4954
5055 * (bug 9712) Use Arabic comma in date/time formats for Arabic and Farsi

Follow-up revisions

RevisionCommit summaryAuthorDate
r22587Merged revisions 22555-22586 via svnmerge from...david03:19, 31 May 2007
r72865Moved FiveUpgrade.inc to upgrade1_5.php and made FiveUpgrade extends Maintena...ialex15:32, 12 September 2010