Fixed typo in LocalFileDeleteBatch::execute() which was causing complete breakage...
[lhc/web/wiklou.git] / includes / filerepo / ICRepo.php
1 <?php
2
3 /**
4 * A repository for files accessible via InstantCommons.
5 */
6
7 class ICRepo extends LocalRepo {
8 var $directory, $url, $hashLevels, $cache;
9 var $fileFactory = array( 'ICFile', 'newFromTitle' );
10 var $oldFileFactory = false;
11
12 function __construct( $info ) {
13 parent::__construct( $info );
14 // Required settings
15 $this->directory = $info['directory'];
16 $this->url = $info['url'];
17 $this->hashLevels = $info['hashLevels'];
18 if(isset($info['cache'])){
19 $this->cache = getcwd().'/images/'.$info['cache'];
20 }
21 }
22 }
23
24 /**
25 * A file loaded from InstantCommons
26 */
27 class ICFile extends LocalFile{
28 static function newFromTitle($title,$repo){
29 return new self($title, $repo);
30 }
31
32 /**
33 * Returns true if the file comes from the local file repository.
34 *
35 * @return bool
36 */
37 function isLocal() {
38 return true;
39 }
40
41 function load(){
42 if (!$this->dataLoaded ) {
43 if ( !$this->loadFromCache() ) {
44 if(!$this->loadFromDB()){
45 $this->loadFromIC();
46 }
47 $this->saveToCache();
48 }
49 $this->dataLoaded = true;
50 }
51 }
52
53 /**
54 * Load file metadata from the DB
55 */
56 function loadFromDB() {
57 wfProfileIn( __METHOD__ );
58
59 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
60 $this->dataLoaded = true;
61
62 $dbr = $this->repo->getSlaveDB();
63
64 $row = $dbr->selectRow( 'ic_image', $this->getCacheFields( 'img_' ),
65 array( 'img_name' => $this->getName() ), __METHOD__ );
66 if ( $row ) {
67 if (trim($row->img_media_type)==NULL) {
68 $this->upgradeRow();
69 $this->upgraded = true;
70 }
71 $this->loadFromRow( $row );
72 //This means that these files are local so the repository locations are local
73 $this->setUrlPathLocal();
74 $this->fileExists = true;
75 //var_dump($this); exit;
76 } else {
77 $this->fileExists = false;
78 }
79
80 wfProfileOut( __METHOD__ );
81
82 return $this->fileExists;
83 }
84
85 /**
86 * Fix assorted version-related problems with the image row by reloading it from the file
87 */
88 function upgradeRow() {
89 wfProfileIn( __METHOD__ );
90
91 $this->loadFromIC();
92
93 $dbw = $this->repo->getMasterDB();
94 list( $major, $minor ) = self::splitMime( $this->mime );
95
96 wfDebug(__METHOD__.': upgrading '.$this->getName()." to the current schema\n");
97
98 $dbw->update( 'ic_image',
99 array(
100 'img_width' => $this->width,
101 'img_height' => $this->height,
102 'img_bits' => $this->bits,
103 'img_media_type' => $this->type,
104 'img_major_mime' => $major,
105 'img_minor_mime' => $minor,
106 'img_metadata' => $this->metadata,
107 ), array( 'img_name' => $this->getName() ),
108 __METHOD__
109 );
110 $this->saveToCache();
111 wfProfileOut( __METHOD__ );
112 }
113
114 function exists(){
115 $this->load();
116 return $this->fileExists;
117
118 }
119
120 /**
121 * Fetch the file from the repository. Check local ic_images table first. If not available, check remote server
122 */
123 function loadFromIC(){
124 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
125 $this->dataLoaded = true;
126 $icUrl = $this->repo->directory.'&media='.$this->title->mDbkeyform;
127 if($h = @fopen($icUrl, 'rb')){
128 $contents = fread($h, 3000);
129 $image = $this->api_xml_to_array($contents);
130 if($image['fileExists']){
131 foreach($image as $property=>$value){
132 if($property=="url"){$value=$this->repo->url.$value; }
133 $this->$property = $value;
134 }
135 if($this->curl_file_get_contents($this->repo->url.$image['url'], $this->repo->cache.'/'.$image['name'])){
136 //Record the image
137 $this->recordDownload("Downloaded with InstantCommons");
138
139 //Then cache it
140 }else{//set fileExists back to false
141 $this->fileExists = false;
142 }
143 }
144 }
145 }
146
147
148 function setUrlPathLocal(){
149 global $wgScriptPath;
150 $path = $wgScriptPath.'/'.substr($this->repo->cache, strlen($wgScriptPath));
151 $this->repo->url = $path;//.'/'.rawurlencode($this->title->mDbkeyform);
152 $this->repo->directory = $this->repo->cache;//.'/'.rawurlencode($this->title->mDbkeyform);
153
154 }
155
156 function getThumbPath( $suffix=false ){
157 $path = $this->repo->cache;
158 if ( $suffix !== false ) {
159 $path .= '/thumb/' . rawurlencode( $suffix );
160 }
161 return $path;
162 }
163 function getThumbUrl( $suffix=false ){
164 global $wgScriptPath;
165 $path = $wgScriptPath.'/'.substr($this->repo->cache, strlen($wgScriptPath));
166 if ( $suffix !== false ) {
167 $path .= '/thumb/' . rawurlencode( $suffix );
168 }
169 return $path;
170 }
171
172 /**
173 * Convert the InstantCommons Server API XML Response to an associative array
174 */
175 function api_xml_to_array($xml){
176 preg_match("/<instantcommons><image(.*?)<\/instantcommons>/",$xml,$match);
177 preg_match_all("/(.*?=\".*?\")/",$match[1], $matches);
178 foreach($matches[1] as $match){
179 list($key,$value) = split("=",$match);
180 $image[trim($key,'<" ')]=trim($value,' "');
181 }
182 return $image;
183 }
184
185 /**
186 * Use cURL to read the content of a URL into a string
187 * ref: http://groups-beta.google.com/group/comp.lang.php/browse_thread/thread/8efbbaced3c45e3c/d63c7891cf8e380b?lnk=raot
188 * @param string $url - the URL to fetch
189 * @param resource $fp - filename to write file contents to
190 * @param boolean $bg - call cURL in the background (don't hang page until complete)
191 * @param int $timeout - cURL connect timeout
192 */
193 function curl_file_get_contents($url, $fp, $bg=TRUE, $timeout = 1) {
194 {
195 # Call curl in the background to download the file
196 $cmd = 'curl '.wfEscapeShellArg($url).' -o '.$fp.' &';
197 wfDebug('Curl download initiated='.$cmd );
198 $success = false;
199 $file_contents = array();
200 $file_contents['err'] = wfShellExec($cmd, $file_contents['return']);
201 if($file_contents['err']==0){//Success
202 $success = true;
203 }
204 }
205 return $success;
206 }
207
208 function getMasterDB() {
209 if ( !isset( $this->dbConn ) ) {
210 $class = 'Database' . ucfirst( $this->dbType );
211 $this->dbConn = new $class( $this->dbServer, $this->dbUser,
212 $this->dbPassword, $this->dbName, false, $this->dbFlags,
213 $this->tablePrefix );
214 }
215 return $this->dbConn;
216 }
217
218 /**
219 * Record a file upload in the upload log and the image table
220 */
221 private function recordDownload($comment='', $timestamp = false ){
222 global $wgUser;
223
224 $dbw = $this->repo->getMasterDB();
225
226 if ( $timestamp === false ) {
227 $timestamp = $dbw->timestamp();
228 }
229 list( $major, $minor ) = self::splitMime( $this->mime );
230
231 # Test to see if the row exists using INSERT IGNORE
232 # This avoids race conditions by locking the row until the commit, and also
233 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
234 $dbw->insert( 'ic_image',
235 array(
236 'img_name' => $this->getName(),
237 'img_size'=> $this->size,
238 'img_width' => intval( $this->width ),
239 'img_height' => intval( $this->height ),
240 'img_bits' => $this->bits,
241 'img_media_type' => $this->type,
242 'img_major_mime' => $major,
243 'img_minor_mime' => $minor,
244 'img_timestamp' => $timestamp,
245 'img_description' => $comment,
246 'img_user' => $wgUser->getID(),
247 'img_user_text' => $wgUser->getName(),
248 'img_metadata' => $this->metadata,
249 ),
250 __METHOD__,
251 'IGNORE'
252 );
253
254 if( $dbw->affectedRows() == 0 ) {
255 # Collision, this is an update of a file
256 # Update the current image row
257 $dbw->update( 'ic_image',
258 array( /* SET */
259 'img_size' => $this->size,
260 'img_width' => intval( $this->width ),
261 'img_height' => intval( $this->height ),
262 'img_bits' => $this->bits,
263 'img_media_type' => $this->media_type,
264 'img_major_mime' => $this->major_mime,
265 'img_minor_mime' => $this->minor_mime,
266 'img_timestamp' => $timestamp,
267 'img_description' => $comment,
268 'img_user' => $wgUser->getID(),
269 'img_user_text' => $wgUser->getName(),
270 'img_metadata' => $this->metadata,
271 ), array( /* WHERE */
272 'img_name' => $this->getName()
273 ), __METHOD__
274 );
275 } else {
276 # This is a new file
277 # Update the image count
278 $site_stats = $dbw->tableName( 'site_stats' );
279 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
280 }
281
282 $descTitle = $this->getTitle();
283 $article = new Article( $descTitle );
284
285 # Add the log entry
286 $log = new LogPage( 'icdownload' );
287 $log->addEntry( 'InstantCommons download', $descTitle, $comment );
288
289 if( $descTitle->exists() ) {
290 # Create a null revision
291 $nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(), $log->getRcComment(), false );
292 $nullRevision->insertOn( $dbw );
293 $article->updateRevisionOn( $dbw, $nullRevision );
294
295 # Invalidate the cache for the description page
296 $descTitle->invalidateCache();
297 $descTitle->purgeSquid();
298 }
299
300
301 # Commit the transaction now, in case something goes wrong later
302 # The most important thing is that files don't get lost, especially archives
303 $dbw->immediateCommit();
304
305 # Invalidate cache for all pages using this file
306 $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
307 $update->doUpdate();
308
309 return true;
310 }
311
312 }
313