*Redo newRevisionFromEditComplete hook. Pass an Article instead of a title
[lhc/web/wiklou.git] / includes / filerepo / ICRepo.php
1 <?php
2
3 /**
4 * A repository for files accessible via InstantCommons.
5 * @ingroup FileRepo
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 * Fetch the file from the repository. Check local ic_images table first. If not available, check remote server
121 */
122 function loadFromIC(){
123 # Unconditionally set loaded=true, we don't want the accessors constantly rechecking
124 $this->dataLoaded = true;
125 $icUrl = $this->repo->directory.'&media='.$this->title->mDbkeyform;
126 if($h = @fopen($icUrl, 'rb')){
127 $contents = fread($h, 3000);
128 $image = $this->api_xml_to_array($contents);
129 if($image['fileExists']){
130 foreach($image as $property=>$value){
131 if($property=="url"){$value=$this->repo->url.$value; }
132 $this->$property = $value;
133 }
134 if($this->curl_file_get_contents($this->repo->url.$image['url'], $this->repo->cache.'/'.$image['name'])){
135 //Record the image
136 $this->recordDownload("Downloaded with InstantCommons");
137
138 //Then cache it
139 }else{//set fileExists back to false
140 $this->fileExists = false;
141 }
142 }
143 }
144 }
145
146 function setUrlPathLocal(){
147 global $wgScriptPath;
148 $path = $wgScriptPath.'/'.substr($this->repo->cache, strlen($wgScriptPath));
149 $this->repo->url = $path;//.'/'.rawurlencode($this->title->mDbkeyform);
150 $this->repo->directory = $this->repo->cache;//.'/'.rawurlencode($this->title->mDbkeyform);
151
152 }
153
154 function getThumbPath( $suffix=false ){
155 $path = $this->repo->cache;
156 if ( $suffix !== false ) {
157 $path .= '/thumb/' . rawurlencode( $suffix );
158 }
159 return $path;
160 }
161 function getThumbUrl( $suffix=false ){
162 global $wgScriptPath;
163 $path = $wgScriptPath.'/'.substr($this->repo->cache, strlen($wgScriptPath));
164 if ( $suffix !== false ) {
165 $path .= '/thumb/' . rawurlencode( $suffix );
166 }
167 return $path;
168 }
169
170 /**
171 * Convert the InstantCommons Server API XML Response to an associative array
172 */
173 function api_xml_to_array($xml){
174 preg_match("/<instantcommons><image(.*?)<\/instantcommons>/",$xml,$match);
175 preg_match_all("/(.*?=\".*?\")/",$match[1], $matches);
176 foreach($matches[1] as $match){
177 list($key,$value) = split("=",$match);
178 $image[trim($key,'<" ')]=trim($value,' "');
179 }
180 return $image;
181 }
182
183 /**
184 * Use cURL to read the content of a URL into a string
185 * ref: http://groups-beta.google.com/group/comp.lang.php/browse_thread/thread/8efbbaced3c45e3c/d63c7891cf8e380b?lnk=raot
186 * @param string $url - the URL to fetch
187 * @param resource $fp - filename to write file contents to
188 * @param boolean $bg - call cURL in the background (don't hang page until complete)
189 * @param int $timeout - cURL connect timeout
190 */
191 function curl_file_get_contents($url, $fp, $bg=TRUE, $timeout = 1) {
192 # Call curl in the background to download the file
193 $cmd = 'curl '.wfEscapeShellArg($url).' -o '.$fp.' &';
194 wfDebug('Curl download initiated='.$cmd );
195 $success = false;
196 $file_contents = array();
197 $file_contents['err'] = wfShellExec($cmd, $file_contents['return']);
198 if($file_contents['err']==0){//Success
199 $success = true;
200 }
201 return $success;
202 }
203
204 function getMasterDB() {
205 if ( !isset( $this->dbConn ) ) {
206 $class = 'Database' . ucfirst( $this->dbType );
207 $this->dbConn = new $class( $this->dbServer, $this->dbUser,
208 $this->dbPassword, $this->dbName, false, $this->dbFlags,
209 $this->tablePrefix );
210 }
211 return $this->dbConn;
212 }
213
214 /**
215 * Record a file upload in the upload log and the image table
216 */
217 private function recordDownload($comment='', $timestamp = false ){
218 global $wgUser;
219
220 $dbw = $this->repo->getMasterDB();
221
222 if ( $timestamp === false ) {
223 $timestamp = $dbw->timestamp();
224 }
225 list( $major, $minor ) = self::splitMime( $this->mime );
226
227 # Test to see if the row exists using INSERT IGNORE
228 # This avoids race conditions by locking the row until the commit, and also
229 # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
230 $dbw->insert( 'ic_image',
231 array(
232 'img_name' => $this->getName(),
233 'img_size'=> $this->size,
234 'img_width' => intval( $this->width ),
235 'img_height' => intval( $this->height ),
236 'img_bits' => $this->bits,
237 'img_media_type' => $this->type,
238 'img_major_mime' => $major,
239 'img_minor_mime' => $minor,
240 'img_timestamp' => $timestamp,
241 'img_description' => $comment,
242 'img_user' => $wgUser->getID(),
243 'img_user_text' => $wgUser->getName(),
244 'img_metadata' => $this->metadata,
245 ),
246 __METHOD__,
247 'IGNORE'
248 );
249
250 if( $dbw->affectedRows() == 0 ) {
251 # Collision, this is an update of a file
252 # Update the current image row
253 $dbw->update( 'ic_image',
254 array( /* SET */
255 'img_size' => $this->size,
256 'img_width' => intval( $this->width ),
257 'img_height' => intval( $this->height ),
258 'img_bits' => $this->bits,
259 'img_media_type' => $this->media_type,
260 'img_major_mime' => $this->major_mime,
261 'img_minor_mime' => $this->minor_mime,
262 'img_timestamp' => $timestamp,
263 'img_description' => $comment,
264 'img_user' => $wgUser->getID(),
265 'img_user_text' => $wgUser->getName(),
266 'img_metadata' => $this->metadata,
267 ), array( /* WHERE */
268 'img_name' => $this->getName()
269 ), __METHOD__
270 );
271 } else {
272 # This is a new file
273 # Update the image count
274 $site_stats = $dbw->tableName( 'site_stats' );
275 $dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
276 }
277
278 $descTitle = $this->getTitle();
279 $article = new Article( $descTitle );
280
281 # Add the log entry
282 $log = new LogPage( 'icdownload' );
283 $log->addEntry( 'InstantCommons download', $descTitle, $comment );
284
285 if( $descTitle->exists() ) {
286 # Create a null revision
287 $nullRevision = Revision::newNullRevision( $dbw, $descTitle->getArticleId(), $log->getRcComment(), false );
288 $nullRevision->insertOn( $dbw );
289
290 wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, false) );
291
292 $article->updateRevisionOn( $dbw, $nullRevision );
293
294 # Invalidate the cache for the description page
295 $descTitle->invalidateCache();
296 $descTitle->purgeSquid();
297 }
298
299
300 # Commit the transaction now, in case something goes wrong later
301 # The most important thing is that files don't get lost, especially archives
302 $dbw->immediateCommit();
303
304 # Invalidate cache for all pages using this file
305 $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
306 $update->doUpdate();
307
308 return true;
309 }
310 }