Dotclear

source: inc/core/class.dc.update.php @ 2566:9bf417837888

Revision 2566:9bf417837888, 10.9 KB checked in by franck <carnet.franck.paul@…>, 12 years ago (diff)

Add some people in CREDITS, remove trailing spaces and tabs.

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

Sites map