Dotclear

source: inc/core/class.dc.update.php @ 765:a863bd0ca8d7

Revision 765:a863bd0ca8d7, 11.0 KB checked in by alex, 14 years ago (diff)

Improved syntax in code documentation

Line 
1<?php
2# -- BEGIN LICENSE BLOCK ---------------------------------------
3#
4# This file is part of Dotclear 2.
5#
6# Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
7# Licensed under the GPL version 2.0 license.
8# See LICENSE file or
9# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
10#
11# -- END LICENSE BLOCK -----------------------------------------
12if (!defined('DC_RC_PATH')) { return; }
13
14class dcUpdate
15{
16     const ERR_FILES_CHANGED = 101;
17     const ERR_FILES_UNREADABLE = 102;
18     const ERR_FILES_UNWRITALBE = 103;
19     
20     protected $url;
21     protected $subject;
22     protected $version;
23     protected $cache_file;
24     
25     protected $version_info = array(
26          'version' => null,
27          'href' => null,
28          'checksum' => null,
29          'info' => null,
30          'notify' => true
31     );
32     
33     protected $cache_ttl = '-6 hours';
34     protected $forced_files = array();
35     
36     /**
37      * Constructor
38      *
39      * @param url       string         Versions file URL
40      * @param subject        string         Subject to check
41      * @param version        string         Version type
42      * @param cache_dir string         Directory cache path
43      */
44     public function __construct($url,$subject,$version,$cache_dir)
45     {
46          $this->url = $url;
47          $this->subject = $subject;
48          $this->version = $version;
49          $this->cache_file = $cache_dir.'/'.$subject.'-'.$version;
50     }
51     
52     /**
53      * Checks for Dotclear updates.
54      * Returns latest version if available or false.
55      *
56      * @param version        string    Current version to compare
57      * @return string                  Latest version if available
58      */
59     public function check($version)
60     {
61          $this->getVersionInfo();
62          $v = $this->getVersion();
63          if ($v && version_compare($version,$v,'<')) {
64               return $v;
65          }
66         
67          return false;
68     }
69     
70     public function getVersionInfo()
71     {
72          # Check cached file
73          if (is_readable($this->cache_file) && filemtime($this->cache_file) > strtotime($this->cache_ttl))
74          {
75               $c = @file_get_contents($this->cache_file);
76               $c = @unserialize($c);
77               if (is_array($c)) {
78                    $this->version_info = $c;
79                    return;
80               }
81          }
82         
83          $cache_dir = dirname($this->cache_file);
84          $can_write = (!is_dir($cache_dir) && is_writable(dirname($cache_dir)))
85          || (!file_exists($this->cache_file) && is_writable($cache_dir))
86          || is_writable($this->cache_file);
87         
88          # If we can't write file, don't bug host with queries
89          if (!$can_write) {
90               return;
91          }
92         
93          if (!is_dir($cache_dir)) {
94               try {
95                    files::makeDir($cache_dir);
96               } catch (Exception $e) {
97                    return;
98               }
99          }
100         
101          # Try to get latest version number
102          try
103          {
104               $path = '';
105               $client = netHttp::initClient($this->url,$path);
106               if ($client !== false) {
107                    $client->setTimeout(4);
108                    $client->setUserAgent($_SERVER['HTTP_USER_AGENT']);
109                    $client->get($path);
110                   
111                    $this->readVersion($client->getContent());
112               }
113          }
114          catch (Exception $e) {}
115         
116          # Create cache
117          file_put_contents($this->cache_file,serialize($this->version_info));
118     }
119     
120     public function getVersion()
121     {return "2.5";
122          return $this->version_info['version'];
123     }
124     
125     public function getFileURL()
126     {
127          return $this->version_info['href'];
128     }
129     
130     public function getInfoURL()
131     {
132          return $this->version_info['info'];
133     }
134     
135     public function getChecksum()
136     {
137          return $this->version_info['checksum'];
138     }
139     
140     public function getNotify()
141     {
142          return $this->version_info['notify'];
143     }
144     
145     public function getForcedFiles()
146     {
147          return $this->forced_files;
148     }
149     
150     public function setForcedFiles()
151     {
152          $this->forced_files = func_get_args();
153     }
154     
155     /**
156      * Sets notification flag.
157      */
158     public function setNotify($n)
159     {
160         
161          if (!is_writable($this->cache_file)) {
162               return;
163          }
164         
165          $this->version_info['notify'] = (boolean) $n;
166          file_put_contents($this->cache_file,serialize($this->version_info));
167     }
168     
169     public function checkIntegrity($digests_file,$root)
170     {
171          if (!$digests_file) {
172               throw new Exception(__('Digests file not found.'));
173          }
174         
175          $changes = $this->md5sum($root,$digests_file);
176         
177          if (!empty($changes)) {
178               $e = new Exception('Some files have changed.',self::ERR_FILES_CHANGED);
179               $e->bad_files = $changes;
180               throw $e;
181          }
182         
183          return true;
184     }
185     
186     /**
187      * Downloads new version to destination $dest.
188      */
189     public function download($dest)
190     {
191          $url = $this->getFileURL();
192         
193          if (!$url) {
194               throw new Exception(__('No file to download'));
195          }
196         
197          if (!is_writable(dirname($dest))) {
198               throw new Exception(__('Root directory is not writable.'));
199          }
200         
201          try
202          {
203               $client = netHttp::initClient($url,$path);
204               $client->setTimeout(4);
205               $client->setUserAgent($_SERVER['HTTP_USER_AGENT']);
206               $client->useGzip(false);
207               $client->setPersistReferers(false);
208               $client->setOutput($dest);
209               $client->get($path);
210               
211               if ($client->getStatus() != 200) {
212                    @unlink($dest);
213                    throw new Exception();
214               }
215          }
216          catch (Exception $e)
217          {
218               throw new Exception(__('An error occurred while downloading archive.'));
219          }
220     }
221     
222     /**
223      * Checks if archive was successfully downloaded.
224      */
225     public function checkDownload($zip)
226     {
227          $cs = $this->getChecksum();
228         
229          return $cs && is_readable($zip) && md5_file($zip) == $cs;
230     }
231     
232     /**
233      * Backups changed files before an update.
234      */
235     public function backup($zip_file,$zip_digests,$root,$root_digests,$dest)
236     {
237          if (!is_readable($zip_file)) {
238               throw new Exception(__('Archive not found.'));
239          }
240         
241          if (!is_readable($root_digests)) {
242               @unlink($zip_file);
243               throw new Exception(__('Unable to read current digests file.'));
244          }
245         
246          # Stop everything if a backup already exists and can not be overrided
247          if (!is_writable(dirname($dest)) && !file_exists($dest)) {
248               throw new Exception(__('Root directory is not writable.'));
249          }
250         
251          if (file_exists($dest) && !is_writable($dest)) {
252               return false;
253          }
254         
255          $b_fp = @fopen($dest,'wb');
256          if ($b_fp === false) {
257               return false;
258          }
259         
260          $zip = new fileUnzip($zip_file);
261          $b_zip = new fileZip($b_fp);
262         
263          if (!$zip->hasFile($zip_digests))
264          {
265               @unlink($zip_file);
266               throw new Exception(__('Downloaded file does not seem to be a valid archive.'));
267          }
268         
269          $opts = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
270          $cur_digests = file($root_digests,$opts);
271          $new_digests = explode("\n",$zip->unzip($zip_digests));
272          $new_files = $this->getNewFiles($cur_digests,$new_digests);
273          $zip->close();
274          unset($opts,$cur_digests,$new_digests,$zip);
275         
276          $not_readable = array();
277         
278          if (!empty($this->forced_files)) {
279               $new_files = array_merge($new_files,$this->forced_files);
280          }
281         
282          foreach ($new_files as $file)
283          {
284               if (!$file || !file_exists($root.'/'.$file)) {
285                    continue;
286               }
287               
288               try {
289                    $b_zip->addFile($root.'/'.$file,$file);
290               } catch (Exception $e) {
291                    $not_readable[] = $file;
292               }
293          }
294         
295          # If only one file is not readable, stop everything now
296          if (!empty($not_readable)) {
297               $e = new Exception('Some files are not readable.',self::ERR_FILES_UNREADABLE);
298               $e->bad_files = $not_readable;
299               throw $e;
300          }
301         
302          $b_zip->write();
303          fclose($b_fp);
304          $b_zip->close();
305         
306          return true;
307     }
308     
309     /**
310      * Upgrade process.
311      */
312     public function performUpgrade($zip_file,$zip_digests,$zip_root,$root,$root_digests)
313     {
314          if (!is_readable($zip_file)) {
315               throw new Exception(__('Archive not found.'));
316          }
317         
318          if (!is_readable($root_digests)) {
319               @unlink($zip_file);
320               throw new Exception(__('Unable to read current digests file.'));
321          }
322         
323          $zip = new fileUnzip($zip_file);
324         
325          if (!$zip->hasFile($zip_digests))
326          {
327               @unlink($zip_file);
328               throw new Exception(__('Downloaded file does not seem to be a valid archive.'));
329          }
330         
331          $opts = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
332          $cur_digests = file($root_digests,$opts);
333          $new_digests = explode("\n",$zip->unzip($zip_digests));
334          $new_files = self::getNewFiles($cur_digests,$new_digests);
335         
336          if (!empty($this->forced_files)) {
337               $new_files = array_merge($new_files,$this->forced_files);
338          }
339         
340          $zip_files = array();
341          $not_writable = array();
342         
343          foreach ($new_files as $file)
344          {
345               if (!$file) {
346                    continue;
347               }
348               
349               if (!$zip->hasFile($zip_root.'/'.$file)) {
350                    @unlink($zip_file);
351                    throw new Exception(__('Incomplete archive.'));
352               }
353               
354               $dest = $dest_dir = $root.'/'.$file;
355               while (!is_dir($dest_dir = dirname($dest_dir)));
356               
357               if ((file_exists($dest) && !is_writable($dest)) ||
358               (!file_exists($dest) && !is_writable($dest_dir))) {
359                    $not_writable[] = $file;
360                    continue;
361               }
362               
363               $zip_files[] = $file;
364          }
365         
366          # If only one file is not writable, stop everything now
367          if (!empty($not_writable)) {
368               $e = new Exception('Some files are not writable',self::ERR_FILES_UNWRITALBE);
369               $e->bad_files = $not_writable;
370               throw $e;
371          }
372         
373          # Everything's fine, we can write files, then do it now
374          $can_touch = function_exists('touch');
375          foreach ($zip_files as $file) {
376               $zip->unzip($zip_root.'/'.$file, $root.'/'.$file);
377               if ($can_touch) {
378                    @touch($root.'/'.$file);
379               }
380          }
381          @unlink($zip_file);
382     }
383     
384     protected function getNewFiles($cur_digests,$new_digests)
385     {
386          $cur_md5 = $cur_path = $cur_digests;
387          $new_md5 = $new_path = $new_digests;
388         
389          array_walk($cur_md5, array($this,'parseLine'),1);
390          array_walk($cur_path,array($this,'parseLine'),2);
391          array_walk($new_md5, array($this,'parseLine'),1);
392          array_walk($new_path,array($this,'parseLine'),2);
393         
394          $cur = array_combine($cur_md5,$cur_path);
395          $new = array_combine($new_md5,$new_path);
396         
397          return array_values(array_diff_key($new,$cur));
398     }
399     
400     protected function readVersion($str)
401     {
402          try
403          {
404               $xml = new SimpleXMLElement($str,LIBXML_NOERROR);
405               $r = $xml->xpath("/versions/subject[@name='".$this->subject."']/release[@name='".$this->version."']");
406               
407               if (!empty($r) && is_array($r))
408               {
409                    $r = $r[0];
410                    $this->version_info['version'] = isset($r['version']) ? (string) $r['version'] : null;
411                    $this->version_info['href'] = isset($r['href']) ? (string) $r['href'] : null;
412                    $this->version_info['checksum'] = isset($r['checksum']) ? (string) $r['checksum'] : null;
413                    $this->version_info['info'] = isset($r['info']) ? (string) $r['info'] : null;
414               }
415          }
416          catch (Exception $e)
417          {
418               throw $e;
419          }
420     }
421     
422     protected function md5sum($root,$digests_file)
423     {
424          if (!is_readable($digests_file)) {
425               throw new Exception(__('Unable to read digests file.'));
426          }
427         
428          $opts = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
429          $contents = file($digests_file,$opts);
430         
431          $changes = array();
432         
433          foreach ($contents as $digest)
434          {
435               if (!preg_match('#^([\da-f]{32})\s+(.+?)$#',$digest,$m)) {
436                    continue;
437               }
438               
439               $md5 = $m[1];
440               $filename = $root.'/'.$m[2];
441               
442               # Invalid checksum
443               if (!is_readable($filename) || !self::md5_check($filename, $md5)) {
444                    $changes[] = substr($m[2],2);
445               }
446          }
447         
448          # No checksum found in digests file
449          if (empty($md5)) {
450               throw new Exception(__('Invalid digests file.'));
451          }
452         
453          return $changes;
454     }
455     
456     protected function parseLine(&$v,$k,$n)
457     {
458          if (!preg_match('#^([\da-f]{32})\s+(.+?)$#',$v,$m)) {
459               return;
460          }
461         
462          $v = $n == 1 ? md5($m[2].$m[1]) : substr($m[2],2);
463     }
464     
465     protected static function md5_check($filename,$md5)
466     {
467          if (md5_file($filename) == $md5) {
468               return true;
469          } else {
470               $filecontent = file_get_contents($filename);
471               $filecontent = str_replace ("\r\n","\n",$filecontent);
472               $filecontent = str_replace ("\r","\n",$filecontent);
473               if (md5($filecontent) == $md5) return true;
474          }
475          return false;
476     }
477}
478?>
Note: See TracBrowser for help on using the repository browser.

Sites map