thomascube
2008-10-03 e107123780c12b4d43d0264cb9c6180fb02d9ab8
commit | author | age
354978 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | rcube_install.php                                                     |
6  |                                                                       |
7  | This file is part of the RoundCube Webmail package                    |
8  | Copyright (C) 2008, RoundCube Dev. - Switzerland                      |
9  | Licensed under the GNU Public License                                 |
10  +-----------------------------------------------------------------------+
11
12  $Id:  $
13
14 */
15
16
17 /**
18  * Class to control the installation process of the RoundCube Webmail package
19  *
20  * @category Install
21  * @package  RoundCube
22  * @author Thomas Bruederli
23  */
24 class rcube_install
25 {
26   var $step;
237119 27   var $is_post = false;
354978 28   var $failures = 0;
c5042d 29   var $config = array();
b3f9df 30   var $configured = false;
c5042d 31   var $last_error = null;
ad43e6 32   var $email_pattern = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9])';
27564f 33   var $config_props = array();
e10712 34
T 35   var $obsolete_config = array('db_backend');
36   var $replaced_config = array('skin_path' => 'skin', 'locale_string' => 'language');
37   
38   // these config options are optional or can be set to null
39   var $optional_config = array(
40     'log_driver', 'syslog_id', 'syslog_facility', 'imap_auth_type',
41     'smtp_helo_host', 'sendmail_delay', 'double_auth', 'language',
42     'mail_header_delimiter', 'create_default_folders',
43     'quota_zero_as_unlimited', 'spellcheck_uri', 'spellcheck_languages',
44     'http_received_header', 'session_domain', 'mime_magic', 'log_logins',
45     'enable_installer', 'skin_include_php');
354978 46   
T 47   /**
48    * Constructor
49    */
50   function rcube_install()
51   {
52     $this->step = intval($_REQUEST['_step']);
237119 53     $this->is_post = $_SERVER['REQUEST_METHOD'] == 'POST';
354978 54   }
T 55   
c5042d 56   /**
T 57    * Singleton getter
58    */
59   function get_instance()
60   {
61     static $inst;
62     
63     if (!$inst)
64       $inst = new rcube_install();
65     
66     return $inst;
67   }
354978 68   
T 69   /**
c5042d 70    * Read the default config files and store properties
354978 71    */
190e97 72   function load_defaults()
354978 73   {
c5042d 74     $this->_load_config('.php.dist');
T 75   }
76
77
78   /**
79    * Read the local config files and store properties
80    */
81   function load_config()
82   {
b3f9df 83     $this->config = array();
c5042d 84     $this->_load_config('.php');
b3f9df 85     $this->configured = !empty($this->config);
c5042d 86   }
T 87
88   /**
89    * Read the default config file and store properties
90    * @access private
91    */
92   function _load_config($suffix)
93   {
bba657 94     @include RCMAIL_CONFIG_DIR . '/main.inc' . $suffix;
354978 95     if (is_array($rcmail_config)) {
190e97 96       $this->config += $rcmail_config;
354978 97     }
T 98       
bba657 99     @include RCMAIL_CONFIG_DIR . '/db.inc'. $suffix;
354978 100     if (is_array($rcmail_config)) {
c5042d 101       $this->config += $rcmail_config;
354978 102     }
T 103   }
104   
105   
106   /**
107    * Getter for a certain config property
108    *
109    * @param string Property name
ad43e6 110    * @param string Default value
354978 111    * @return string The property value
T 112    */
fa7539 113   function getprop($name, $default = '')
354978 114   {
b77d0d 115     $value = $this->config[$name];
354978 116     
ccb412 117     if ($name == 'des_key' && !$this->configured && !isset($_REQUEST["_$name"]))
807d17 118       $value = rcube_install::random_key(24);
354978 119     
fa7539 120     return $value !== null && $value !== '' ? $value : $default;
354978 121   }
T 122   
123   
124   /**
125    * Take the default config file and replace the parameters
126    * with the submitted form data
127    *
128    * @param string Which config file (either 'main' or 'db')
129    * @return string The complete config file content
130    */
e10712 131   function create_config($which, $force = false)
354978 132   {
T 133     $out = file_get_contents("../config/{$which}.inc.php.dist");
134     
135     if (!$out)
136       return '[Warning: could not read the template file]';
0c3bde 137
c5042d 138     foreach ($this->config as $prop => $default) {
27564f 139       $value = (isset($_POST["_$prop"]) || $this->config_props[$prop]) ? $_POST["_$prop"] : $default;
354978 140       
T 141       // convert some form data
b77d0d 142       if ($prop == 'debug_level') {
354978 143         $val = 0;
7d7f67 144         if (is_array($value))
T 145           foreach ($value as $dbgval)
b77d0d 146             $val += intval($dbgval);
7d7f67 147         $value = $val;
354978 148       }
0c3bde 149       else if ($which == 'db' && $prop == 'db_dsnw' && !empty($_POST['_dbtype'])) {
237119 150         if ($_POST['_dbtype'] == 'sqlite')
T 151           $value = sprintf('%s://%s?mode=0646', $_POST['_dbtype'], $_POST['_dbname']{0} == '/' ? '/' . $_POST['_dbname'] : $_POST['_dbname']);
152         else
b61965 153           $value = sprintf('%s://%s:%s@%s/%s', $_POST['_dbtype'], 
7d7f67 154             rawurlencode($_POST['_dbuser']), rawurlencode($_POST['_dbpass']), $_POST['_dbhost'], $_POST['_dbname']);
c5042d 155       }
T 156       else if ($prop == 'smtp_auth_type' && $value == '0') {
157         $value = '';
158       }
159       else if ($prop == 'default_host' && is_array($value)) {
807d17 160         $value = rcube_install::_clean_array($value);
c5042d 161         if (count($value) <= 1)
T 162           $value = $value[0];
163       }
ccb412 164       else if ($prop == 'pagesize') {
T 165         $value = max(2, intval($value));
166       }
c5042d 167       else if ($prop == 'smtp_user' && !empty($_POST['_smtp_user_u'])) {
T 168         $value = '%u';
169       }
170       else if ($prop == 'smtp_pass' && !empty($_POST['_smtp_user_u'])) {
171         $value = '%p';
172       }
173       else if (is_bool($default)) {
27564f 174         $value = (bool)$value;
T 175       }
176       else if (is_numeric($value)) {
177         $value = intval($value);
c5042d 178       }
T 179       
180       // skip this property
e10712 181       if (!$force && ($value == $default))
c5042d 182         continue;
b77d0d 183
A 184       // save change
185       $this->config[$prop] = $value;
186
354978 187       // replace the matching line in config file
T 188       $out = preg_replace(
189         '/(\$rcmail_config\[\''.preg_quote($prop).'\'\])\s+=\s+(.+);/Uie',
190         "'\\1 = ' . var_export(\$value, true) . ';'",
191         $out);
192     }
0c3bde 193
967b34 194     return trim($out);
c5042d 195   }
e10712 196
T 197
198   /**
199    * Check the current configuration for missing properties
200    * and deprecated or obsolete settings
201    *
202    * @return array List with problems detected
203    */
204   function check_config()
205   {
206     $this->config = array();
207     $this->load_defaults();
208     $defaults = $this->config;
209     
210     $this->load_config();
211     if (!$this->configured)
212       return null;
213     
214     $out = $seen = array();
215     $optional = array_flip($this->optional_config);
216     
217     // ireate over the current configuration
218     foreach ($this->config as $prop => $value) {
219       if ($replacement = $this->replaced_config[$prop]) {
220         $out['replaced'][] = array('prop' => $prop, 'replacement' => $replacement);
221         $seen[$replacement] = true;
222       }
223       else if (!$seen[$prop] && in_array($prop, $this->obsolete_config)) {
224         $out['obsolete'][] = array('prop' => $prop);
225         $seen[$prop] = true;
226       }
227     }
228     
229     // iterate over default config
230     foreach ($defaults as $prop => $value) {
231       if (!$seen[$prop] && !isset($this->config[$prop]) && !isset($optional[$prop]))
232         $out['missing'][] = array('prop' => $prop);
233     }
234     
235     return $out;
236   }
237   
238   
239   /**
240    * Merge the current configuration with the defaults
241    * and copy replaced values to the new options.
242    */
243   function merge_config()
244   {
245     $current = $this->config;
246     $this->config = array();
247     $this->load_defaults();
248     
249     foreach ($this->replaced_config as $prop => $replacement)
250       if (isset($current[$prop])) {
251         if ($prop == 'skin_path')
252           $this->config[$replacement] = preg_replace('#skins/(\w+)/?$#', '\\1', $current[$prop]);
253         else
254           $this->config[$replacement] = $current[$prop];
255         
256         unset($current[$prop]);
257     }
258     
259     foreach ($this->obsolete_config as $prop) {
260       unset($current[$prop]);
261     }
262     
263     $this->config  = array_merge($current, $this->config);
264   }
c5042d 265   
T 266   
267   /**
268    * Getter for the last error message
269    *
270    * @return string Error message or null if none exists
271    */
272   function get_error()
273   {
274       return $this->last_error['message'];
354978 275   }
T 276   
277   
278   /**
112c54 279    * Return a list with all imap hosts configured
T 280    *
281    * @return array Clean list with imap hosts
282    */
283   function get_hostlist()
284   {
285     $default_hosts = (array)$this->getprop('default_host');
286     $out = array();
287     
288     foreach ($default_hosts as $key => $name) {
289       if (!empty($name))
290         $out[] = is_numeric($key) ? $name : $key;
291     }
292     
293     return $out;
294   }
295   
296   
297   /**
354978 298    * Display OK status
T 299    *
300    * @param string Test name
301    * @param string Confirm message
302    */
303   function pass($name, $message = '')
304   {
305     echo Q($name) . ':&nbsp; <span class="success">OK</span>';
6557d3 306     $this->_showhint($message);
354978 307   }
T 308   
309   
310   /**
311    * Display an error status and increase failure count
312    *
313    * @param string Test name
314    * @param string Error message
315    * @param string URL for details
316    */
317   function fail($name, $message = '', $url = '')
318   {
319     $this->failures++;
320     
321     echo Q($name) . ':&nbsp; <span class="fail">NOT OK</span>';
6557d3 322     $this->_showhint($message, $url);
354978 323   }
T 324   
325   
326   /**
327    * Display warning status
328    *
329    * @param string Test name
330    * @param string Warning message
331    * @param string URL for details
332    */
6557d3 333   function na($name, $message = '', $url = '')
354978 334   {
6557d3 335     echo Q($name) . ':&nbsp; <span class="na">NOT AVAILABLE</span>';
T 336     $this->_showhint($message, $url);
337   }
338   
339   
340   function _showhint($message, $url = '')
341   {
342     $hint = Q($message);
343     
354978 344     if ($url)
6557d3 345       $hint .= ($hint ? '; ' : '') . 'See <a href="' . Q($url) . '" target="_blank">' . Q($url) . '</a>';
T 346       
347     if ($hint)
348       echo '<span class="indent">(' . $hint . ')</span>';
354978 349   }
T 350   
351   
c5042d 352   function _clean_array($arr)
T 353   {
354     $out = array();
355     
356     foreach (array_unique($arr) as $i => $val)
357       if (!empty($val))
358         $out[] = $val;
359     
360     return $out;
361   }
362   
190e97 363   
T 364   /**
365    * Initialize the database with the according schema
366    *
367    * @param object rcube_db Database connection
368    * @return boolen True on success, False on error
369    */
370   function init_db($DB)
371   {
372     $db_map = array('pgsql' => 'postgres', 'mysqli' => 'mysql');
373     $engine = isset($db_map[$DB->db_provider]) ? $db_map[$DB->db_provider] : $DB->db_provider;
374     
375     // find out db version
376     if ($engine == 'mysql') {
377       $DB->query('SELECT VERSION() AS version');
378       $sql_arr = $DB->fetch_assoc();
379       $version = floatval($sql_arr['version']);
380       
381       if ($version >= 4.1)
382         $engine = 'mysql5';
383     }
384     
385     // read schema file from /SQL/*
386     $fname = "../SQL/$engine.initial.sql";
387     if ($lines = @file($fname, FILE_SKIP_EMPTY_LINES)) {
388       $buff = '';
389       foreach ($lines as $i => $line) {
390         if (eregi('^--', $line))
391           continue;
392           
393         $buff .= $line . "\n";
394         if (eregi(';$', trim($line))) {
395           $DB->query($buff);
396           $buff = '';
c0dc90 397           if ($this->get_error())
T 398             break;
190e97 399         }
T 400       }
401     }
402     else {
403       $this->fail('DB Schema', "Cannot read the schema file: $fname");
404       return false;
405     }
406     
407     if ($err = $this->get_error()) {
408       $this->fail('DB Schema', "Error creating database schema: $err");
409       return false;
410     }
411
412     return true;
413   }
414   
c5042d 415   /**
T 416    * Handler for RoundCube errors
417    */
418   function raise_error($p)
419   {
420       $this->last_error = $p;
421   }
422   
423   
354978 424   /**
T 425    * Generarte a ramdom string to be used as encryption key
426    *
427    * @param int Key length
428    * @return string The generated random string
429    * @static
430    */
431   function random_key($length)
432   {
433     $alpha = 'ABCDEFGHIJKLMNOPQERSTUVXYZabcdefghijklmnopqrtsuvwxyz0123456789+*%&?!$-_=';
434     $out = '';
435     
436     for ($i=0; $i < $length; $i++)
437       $out .= $alpha{rand(0, strlen($alpha)-1)};
438     
439     return $out;
440   }
441   
442 }
443
444
445 /**
446  * Shortcut function for htmlentities()
447  *
448  * @param string String to quote
449  * @return string The html-encoded string
450  */
451 function Q($string)
452 {
453   return htmlentities($string);
454 }
455
c5042d 456
T 457 /**
458  * Fake rinternal error handler to catch errors
459  */
460 function raise_error($p)
461 {
462   $rci = rcube_install::get_instance();
463   $rci->raise_error($p);
464 }
465