1<?php 2 3/** 4 * Configuration objects 5 * @package framework 6 * @subpackage config 7 */ 8 9/** 10 * Base class for both site and user configuration data management 11 */ 12abstract class Hm_Config { 13 14 /* config source */ 15 protected $source = ''; 16 17 /* config data */ 18 protected $config = array('version' => VERSION); 19 20 /* flag indicating failed decryption */ 21 public $decrypt_failed = false; 22 23 /* if decryption fails, save the encrypted payload */ 24 public $encrypted_str; 25 26 /** 27 * This method must be overriden by classes extending this one 28 * @param string $source source or identifier to determine the source 29 * @param string $key encryption key 30 */ 31 abstract public function load($source, $key); 32 33 /** 34 * Return all config values 35 * @return array list of config values 36 */ 37 public function dump() { 38 return $this->config; 39 } 40 41 /** 42 * Delete a setting 43 * @param string $name config option name 44 * @return bool true on success 45 */ 46 public function del($name) { 47 if (array_key_exists($name, $this->config)) { 48 unset($this->config[$name]); 49 return true; 50 } 51 return false; 52 } 53 54 /** 55 * Return a versoin number 56 * @return float 57 */ 58 public function version() { 59 if (array_key_exists('version', $this->config)) { 60 return $this->config['version']; 61 } 62 return .1; 63 } 64 65 /** 66 * Set a config value 67 * @param string $name config value name 68 * @param string $value config value 69 * @return void 70 */ 71 public function set($name, $value) { 72 $this->config[$name] = $value; 73 } 74 75 /** 76 * Return a config value if it exists 77 * @param string $name config value name 78 * @param false|string $default value to return if the name is not found 79 * @return mixed found value, otherwise $default 80 */ 81 public function get($name, $default=false) { 82 return array_key_exists($name, $this->config) ? $this->config[$name] : $default; 83 } 84 85 /** 86 * Set the timezone 87 * @return void 88 */ 89 public function set_tz() { 90 date_default_timezone_set($this->get('timezone_setting', 'UTC')); 91 } 92 93 /** 94 * Shuffle the config value order 95 * @return void 96 */ 97 public function shuffle() { 98 $new_config = array(); 99 $keys = array_keys($this->config); 100 shuffle($keys); 101 foreach ($keys as $key) { 102 $new_config[$key] = $this->config[$key]; 103 } 104 $this->config = $new_config; 105 } 106 107 /** 108 * Decode user settings with json_decode or unserialize depending 109 * on the format 110 * @param string|false $data serialized or json encoded string 111 * @return mixed array, or false on failure 112 */ 113 public function decode($data) { 114 if (!is_string($data) || !trim($data)) { 115 return false; 116 } 117 return Hm_Transform::convert($data); 118 } 119 120 /** 121 * Filter out default auth and SMTP servers so they don't get saved 122 * to the permanent user config. These are dynamically reloaded on 123 * login 124 * @return array of items removed 125 */ 126 public function filter_servers() { 127 $removed = array(); 128 $excluded = array('pop3_servers', 'imap_servers','smtp_servers'); 129 $no_password = $this->get('no_password_save_setting', false); 130 foreach ($this->config as $key => $vals) { 131 if (in_array($key, $excluded, true)) { 132 foreach ($vals as $index => $server) { 133 if (array_key_exists('default', $server) && $server['default']) { 134 $removed[$key][$index] = $server; 135 unset($this->config[$key][$index]); 136 } 137 elseif (!array_key_exists('server', $server)) { 138 $removed[$key][$index] = $server; 139 unset($this->config[$key][$index]); 140 } 141 else { 142 $this->config[$key][$index]['object'] = false; 143 if ($no_password) { 144 if (!array_key_exists('auth', $server) || $server['auth'] != 'xoauth2') { 145 $removed[$key][$index]['pass'] = $server['pass']; 146 unset($this->config[$key][$index]['pass']); 147 } 148 } 149 } 150 } 151 } 152 } 153 return $removed; 154 } 155 156 /** 157 * Restore server definitions removed before saving 158 * @param array $removed server info to restore 159 * @return void 160 */ 161 public function restore_servers($removed) { 162 foreach ($removed as $key => $vals) { 163 foreach ($vals as $index => $server) { 164 if (is_array($server)) { 165 $this->config[$key][$index] = $server; 166 } 167 else { 168 $this->config[$key][$index]['pass'] = $server; 169 } 170 } 171 } 172 } 173} 174 175/** 176 * File based user settings 177 */ 178class Hm_User_Config_File extends Hm_Config { 179 180 /* config values */ 181 private $site_config; 182 183 /* encrption flag */ 184 private $crypt; 185 186 /* username */ 187 private $username; 188 189 /** 190 * Load site configuration 191 * @param object $config site config 192 */ 193 public function __construct($config) { 194 $this->crypt = crypt_state($config); 195 $this->site_config = $config; 196 $this->config = array_merge($this->config, $config->user_defaults); 197 } 198 199 /** 200 * Get the filesystem path for a user settings file 201 * @param string $username username 202 * @return string filepath to the user config file 203 */ 204 public function get_path($username) { 205 $path = $this->site_config->get('user_settings_dir', false); 206 return sprintf('%s/%s.txt', $path, $username); 207 } 208 209 /** 210 * Load the settings for a user 211 * @param string $username username 212 * @param string $key key to decrypt the user data 213 * @return void 214 */ 215 public function load($username, $key) { 216 $this->username = $username; 217 $source = $this->get_path($username); 218 if (is_readable($source)) { 219 $str_data = file_get_contents($source); 220 if ($str_data) { 221 if (!$this->crypt) { 222 $data = $this->decode($str_data); 223 } 224 else { 225 $data = $this->decode(Hm_Crypt::plaintext($str_data, $key)); 226 } 227 if (is_array($data)) { 228 $this->config = array_merge($this->config, $data); 229 $this->set_tz(); 230 } 231 else { 232 $this->decrypt_failed = true; 233 $this->encrypted_str = $str_data; 234 } 235 } 236 } 237 } 238 239 /** 240 * Reload from outside input 241 * @param array $data new user data 242 * @param string $username 243 * @return void 244 */ 245 public function reload($data, $username=false) { 246 $this->username = $username; 247 $this->config = $data; 248 $this->set_tz(); 249 } 250 251 /** 252 * Save user settings to a file 253 * @param string $username username 254 * @param string $key encryption key 255 * @return void 256 */ 257 public function save($username, $key) { 258 $this->shuffle(); 259 $destination = $this->get_path($username); 260 $removed = $this->filter_servers(); 261 if (!$this->crypt) { 262 $data = json_encode($this->config); 263 } 264 else { 265 $data = Hm_Crypt::ciphertext(json_encode($this->config), $key); 266 } 267 file_put_contents($destination, $data); 268 $this->restore_servers($removed); 269 } 270 271 /** 272 * Set a config value 273 * @param string $name config value name 274 * @param string $value config value 275 * @return void 276 */ 277 public function set($name, $value) { 278 $this->config[$name] = $value; 279 if (!$this->crypt) { 280 $this->save($this->username, false); 281 } 282 } 283} 284 285/** 286 * DB based user settings 287 */ 288class Hm_User_Config_DB extends Hm_Config { 289 290 /* site configuration */ 291 private $site_config; 292 293 /* DB connection handle */ 294 private $dbh; 295 296 /* encrption class */ 297 private $crypt; 298 299 /* username */ 300 private $username; 301 302 /** 303 * Load site config 304 * @param object $config site config 305 */ 306 public function __construct($config) { 307 $this->crypt = crypt_state($config); 308 $this->site_config = $config; 309 $this->config = array_merge($this->config, $config->user_defaults); 310 } 311 312 /** 313 * @param string $username 314 * @return boolean 315 */ 316 private function new_settings($username) { 317 $res = Hm_DB::execute($this->dbh, 'insert into hm_user_settings values(?,?)', array($username, '')); 318 Hm_Debug::add(sprintf("created new row in hm_user_settings for %s", $username)); 319 $this->config = array(); 320 return $res ? true : false; 321 } 322 323 /** 324 * @param array $data 325 * @param string $key 326 * @return boolean 327 */ 328 private function decrypt_settings($data, $key) { 329 if (!$this->crypt) { 330 $data = $this->decode($data['settings']); 331 } 332 else { 333 $data = $this->decode(Hm_Crypt::plaintext($data['settings'], $key)); 334 } 335 if (is_array($data)) { 336 $this->config = array_merge($this->config, $data); 337 $this->set_tz(); 338 return true; 339 } 340 else { 341 $this->decrypt_failed = true; 342 return false; 343 } 344 } 345 346 /** 347 * Load the user settings from the DB 348 * @param string $username username 349 * @param string $key encryption key 350 * @return boolean 351 */ 352 public function load($username, $key) { 353 $this->username = $username; 354 $this->connect(); 355 $data = Hm_DB::execute($this->dbh, 'select * from hm_user_settings where username=?', array($username)); 356 if (!$data || !array_key_exists('settings', $data)) { 357 return $this->new_settings($username); 358 } 359 return $this->decrypt_settings($data, $key); 360 } 361 362 /** 363 * Reload from outside input 364 * @param array $data new user data 365 * @param string $username 366 * @return void 367 */ 368 public function reload($data, $username=false) { 369 $this->username = $username; 370 $this->config = $data; 371 $this->set_tz(); 372 } 373 374 /** 375 * Connect to a configured DB 376 * @return bool true on success 377 */ 378 public function connect() { 379 return ($this->dbh = Hm_DB::connect($this->site_config)) ? true : false; 380 } 381 382 /** 383 * Save user settings to the DB 384 * @param string $username username 385 * @param string $key encryption key 386 * @return integer|boolean|array 387 */ 388 public function save($username, $key) { 389 $this->shuffle(); 390 $removed = $this->filter_servers(); 391 if (!$this->crypt) { 392 $config = json_encode($this->config); 393 } 394 else { 395 $config = Hm_Crypt::ciphertext(json_encode($this->config), $key); 396 } 397 $this->connect(); 398 if (Hm_DB::execute($this->dbh, 'update hm_user_settings set settings=? where username=?', array($config, $username))) { 399 Hm_Debug::add(sprintf("Saved user data to DB for %s", $username)); 400 $res = true; 401 } 402 else { 403 $res = Hm_DB::execute($this->dbh, 'insert into hm_user_settings values(?,?)', array($username, $config)); 404 } 405 $this->restore_servers($removed); 406 return $res; 407 } 408 409 /** 410 * Set a config value 411 * @param string $name config value name 412 * @param string $value config value 413 * @return void 414 */ 415 public function set($name, $value) { 416 $this->config[$name] = $value; 417 if (!$this->crypt) { 418 $this->save($this->username, false); 419 } 420 } 421} 422 423/** 424 * File based site configuration 425 */ 426class Hm_Site_Config_File extends Hm_Config { 427 428 public $user_defaults = array(); 429 430 /** 431 * Load data based on source 432 * @param string $source source location for site configuration 433 */ 434 public function __construct($source) { 435 $this->load($source, false); 436 } 437 438 /** 439 * Load site data from a file 440 * @param string $source file path to the site configuration 441 * @param string $key encryption key (unsued in this class) 442 * @return void 443 */ 444 public function load($source, $key) { 445 if (is_readable($source)) { 446 $data = $this->decode(file_get_contents($source)); 447 if ($data) { 448 $this->config = array_merge($this->config, $data); 449 $this->get_user_defaults(); 450 } 451 } 452 } 453 454 /* 455 * Determine default values for users without any settings 456 * @return void 457 */ 458 private function get_user_defaults() { 459 foreach ($this->config as $name => $val) { 460 if (substr($name, 0, 15) == 'default_setting') { 461 $this->user_defaults[substr($name, 16).'_setting'] = $val; 462 } 463 } 464 } 465 466 /** 467 * Return a list of modules as an array 468 * @return array|false 469 */ 470 public function get_modules() { 471 $mods = $this->get('modules'); 472 if (is_string($mods)) { 473 return explode(',', $mods); 474 } 475 return $mods; 476 } 477} 478 479/** 480 * Load a user config object 481 * @param object $config site configuration 482 * @return object 483 */ 484function load_user_config_object($config) { 485 $type = $config->get('user_config_type', 'file'); 486 if (strstr($type, ':')) { 487 list($type, $class) = explode(':', $type); 488 } 489 switch ($type) { 490 case 'DB': 491 $user_config = new Hm_User_Config_DB($config); 492 Hm_Debug::add("Using DB user configuration"); 493 break; 494 case 'custom': 495 if (class_exists($class)) { 496 $user_config = new $class($config); 497 Hm_Debug::add("Using custom user configuration: $class"); 498 break; 499 } else { 500 Hm_Debug::add("User configuration class does not exist: $class"); 501 } 502 default: 503 $user_config = new Hm_User_Config_File($config); 504 Hm_Debug::add("Using file based user configuration"); 505 break; 506 } 507 return $user_config; 508} 509 510/** 511 * Determine encryption for user settings 512 * @param object $config site configuration 513 * @return boolean 514 */ 515function crypt_state($config) { 516 if ($config->get('single_server_mode') && 517 in_array($config->get('auth_type'), array('IMAP', 'POP3'), true)) { 518 return false; 519 } 520 return true; 521} 522