<?php
/**
 * eGroupWare API - Preferences
 *
 * @link http://www.egroupware.org
 * @author Joseph Engo <jengo@phpgroupware.org>
 * @author Mark Peters <skeeter@phpgroupware.org>
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de> merging prefs on runtime, session prefs and reworked the class
 * Copyright (C) 2000, 2001 Joseph Engo
 * @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License
 * @package api
 * @version $Id: class.preferences.inc.php 36723 2011-09-26 10:01:53Z leithoff $
 */

/**
 * preferences class used for setting application preferences
 *
 * the prefs are read into 5 arrays:
 * 	 $data the effective prefs used everywhere in phpgw, they are merged from the other 3 arrays
 * 	 $user the stored user prefs, only used for manipulating and storeing the user prefs
 *   $group the stored prefs of users primary groupd, only used for manipulating and storeing the user prefs
 * 	 $default the default preferences, always used when the user has no own preference set
 * 	 $forced forced preferences set by the admin, they take precedence over user or default prefs
 *
 * To update the prefs of a certain group, not just the primary group of the user, you have to
 * create a new instance of preferences class, with the given id of the group. This takes into
 * account the offset of DEFAULT_ID, we are using currently for groups (as -1, and -2) are already
 * taken!
 */
class preferences
{
	/**
	 * account_id for default prefs
	 */
	const DEFAULT_ID = -2;
	/**
	 * account_id for forced prefs
	 */
	const FORCED_ID = -1;

	/**
	 * account the class is instanciated for
	 * @var int
	 */
	var $account_id;
	/**
	 * account-type u or g
	 * @var string
	 */
	var $account_type;
	/**
	 * effectiv user prefs, used by all apps
	 * @var array
	 */
	var $data = array();
	/**
	 * set user prefs for saveing (no defaults/forced prefs merged)
	 * @var array
	 */
	var $user = array();
	/**
	 * primary group prefs
	 * @var array
	 */
	var $group = array();
	/**
	 * default prefs
	 * @var array
	 */
	var $default = array();
	/**
	 * forced prefs
	 * @var array
	 */
	var $forced = array();
	/**
	 * session / tempory prefs
	 * @var array
	 */
	var $session = array();
	/**
	 * @var egw_db
	 */
	var $db;
	/**
	 * table-name
	 * @var string
	 */
	var $table = 'egw_preferences';

	var $values,$vars;	// standard notify substitues, will be set by standard_substitues()

	/**
	 * Contstructor
	 *
	 * @param int|string $account_id=''
	 * @return preferences
	 */
	function __construct($account_id = '')
	{
		if (isset($GLOBALS['egw']->db))
		{
			$this->db = $GLOBALS['egw']->db;
		}
		else
		{
			$this->db = $GLOBALS['egw_setup']->db;
			$this->table = $GLOBALS['egw_setup']->prefs_table;
		}
		// if we got instancated for a group, need to set offset of DEFAULT_ID!
		if ($account_id < 0 && $GLOBALS['egw']->accounts->exists($account_id) == 2)
		{
			$this->account_id = $account_id + self::DEFAULT_ID;
		}
		else
		{
			$this->account_id = get_account_id($account_id);
		}
	}

	/**
	 * Old PHP4 contstructor
	 *
	 * @param int|string $account_id=''
	 * @return preferences
	 * @deprecated
	 */
	function preferences($account_id = '')
	{
		self::__construct();
	}

	/**************************************************************************\
	* These are the standard $this->account_id specific functions              *
	\**************************************************************************/

	/**
	 * parses a notify and replaces the substitutes
	 *
	 * @param string $msg message to parse / substitute
	 * @param array $values=array() extra vars to replace in addition to $this->values, vars are in an array with \
	 * 	$key => $value pairs, $key does not include the $'s and is the *untranslated* name
	 * @param boolean $use_standard_values=true should the standard values are used
	 * @return string with parsed notify-msg
	 */
	function parse_notify($msg,$values=array(),$use_standard_values=True)
	{
		$vals = $values ? $values : array();

		if ($use_standard_values && is_array($this->values))
		{
			$vals += $this->values;
		}
		foreach($vals as $key => $val)
		{
			if ($this->debug) error_log(__METHOD__." replacing \$\$$key\$\$ with $val  ");
			$replace[] = '$$'.$key.'$$';
			$with[]    = $val;
		}
		return str_replace($replace,$with,$msg);
	}

	/**
	 * replaces the english key's with translated ones, or if $un_lang the opposite
	 *
	 * @param string $msg message to translate
	 * @param array $values=array() extra vars to replace in addition to $this->values, vars are in an array with \
	 * 	$key => $value pairs, $key does not include the $'s and is the *untranslated* name
	 * @param boolean $un_lang=false if true translate back
	 * @return string
	 */
	function lang_notify($msg,$vals=array(),$un_lang=False)
	{
		foreach($vals as $key => $val)
		{
			$lname = ($lname = lang($key)) == $key.'*' ? $key : $lname;
			if ($un_lang)
			{
				$langs[$lname] = '$$'.$key.'$$';
			}
			else
			{
				$langs[$key] = '$$'.$lname.'$$';
			}
		}
		return $this->parse_notify($msg,$langs,False);
	}

	/**
	 * define some standard substitues-values and use them on the prefs, if needed
	 */
	function standard_substitutes()
	{
		if ($this->debug) error_log(__METHOD__." is called ");
		if (!is_array(@$GLOBALS['egw_info']['user']['preferences']))
		{
			$GLOBALS['egw_info']['user']['preferences'] = $this->data;	// else no lang()
		}
		// we cant use phpgw_info/user/fullname, as it's not set when we run
		$GLOBALS['egw']->accounts->get_account_name($this->account_id,$lid,$fname,$lname);

		$this->values = array(	// standard notify replacements
			'fullname'  => $GLOBALS['egw']->common->display_fullname('',$fname,$lname),
			'firstname' => $fname,
			'lastname'  => $lname,
			'domain'    => $GLOBALS['egw_info']['server']['mail_suffix'],
			'email'     => $this->email_address($this->account_id),
			'date'      => $GLOBALS['egw']->common->show_date('',$GLOBALS['egw_info']['user']['preferences']['common']['dateformat']),
		);
		// do this first, as it might be already contain some substitues
		//
		$this->values['email'] = $this->parse_notify($this->values['email']);

		$this->vars = array(	// langs have to be in common !!!
			'fullname'  => lang('name of the user, eg. "%1"',$this->values['fullname']),
			'firstname' => lang('first name of the user, eg. "%1"',$this->values['firstname']),
			'lastname'  => lang('last name of the user, eg. "%1"',$this->values['lastname']),
			'domain'    => lang('domain name for mail-address, eg. "%1"',$this->values['domain']),
			'email'     => lang('email-address of the user, eg. "%1"',$this->values['email']),
			'date'      => lang('todays date, eg. "%1"',$this->values['date']),
		);
		if ($this->debug) error_log(__METHOD__.print_r($this->vars,true));
		// do the substituetion in the effective prefs (data)
		//
		foreach($this->data as $app => $data)
		{
			foreach($data as $key => $val)
			{
				if (!is_array($val) && strpos($val,'$$') !== False)
				{
					$this->data[$app][$key] = $this->parse_notify($val);
				}
				elseif (is_array($val))
				{
					foreach($val as $k => $v)
					{
						if (!is_array($v) && strpos($v,'$$') !== False)
						{
							$this->data[$app][$key][$k] = $this->parse_notify($v);
						}
					}
				}
			}
		}
	}

	/**
	 * unquote (stripslashes) recursivly the whole array
	 *
	 * @param array &$arr array to unquote (var-param!)
	 */
	function unquote(&$arr)
	{
		if (!is_array($arr))
		{
			$arr = stripslashes($arr);
			return;
		}
		foreach($arr as $key => $value)
		{
			if (is_array($value))
			{
				$this->unquote($arr[$key]);
			}
			else
			{
				$arr[$key] = stripslashes($value);
			}
		}
	}

	/**
	 * read preferences from the repository
	 *
	 * the function ready all 3 prefs user/default/forced and merges them to the effective ones
	 *
	 * @param boolean $use_session=true should the session prefs get used (default true) or not (false)
	 * @return array with effective prefs ($this->data)
	 */
	function read_repository($use_session=true)
	{
		$this->session = $use_session ? $GLOBALS['egw']->session->appsession('preferences','preferences') : array();
		if (!is_array($this->session))
		{
			$this->session = array();
		}
		$this->forced = $this->default = $this->user = $this->group = array();
		$primary_group = $GLOBALS['egw']->accounts->id2name($this->account_id,'account_primary_group');
		foreach($this->db->select($this->table,'*',array('preference_owner' => array(
			self::DEFAULT_ID,
			self::FORCED_ID,
			$this->account_id,
			$primary_group+self::DEFAULT_ID,	// need to offset it with DEFAULT_ID = -2!
		)),__LINE__,__FILE__) as $row)
		{
			// The following replacement is required for PostgreSQL to work
			$app = trim($row['preference_app']);
			$value = unserialize($row['preference_value']);
			if($value === false)
			{
				// manually retrieve the string lengths of the serialized array if unserialize failed
				$value = unserialize(preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.mb_strlen('$2','8bit').':\"$2\";'", $row['preference_value']));
			}
			$this->unquote($value);
			if (!is_array($value))
			{
				continue;
			}
			switch($row['preference_owner'])
			{
				case self::FORCED_ID:
					$this->forced[$app] = $value;
					break;
				case self::DEFAULT_ID:
					//if ($app=='common') error_log(__METHOD__.__LINE__.array2string($value));
					$this->default[$app] = $value;
					break;
				case $this->account_id:	// user
					$this->user[$app] = $value;
					break;
				default:
					$this->group[$app] = $value;
					break;
			}
		}
		$this->data = $this->user;

		// let the (temp.) session prefs. override the user prefs.
		//
		foreach($this->session as $app => $values)
		{
			foreach($values as $var => $value)
			{
				$this->data[$app][$var] = $value;
			}
		}

		// now use (primary) group defaults if needed (user-value unset or empty)
		//
		foreach($this->group as $app => $values)
		{
			foreach($values as $var => $value)
			{
				if (!isset($this->data[$app][$var]) || $this->data[$app][$var] === '')
				{
					$this->data[$app][$var] = $value;
				}
			}
		}
		// now use defaults if needed (user-value unset or empty)
		//
		foreach($this->default as $app => $values)
		{
			foreach($values as $var => $value)
			{
				if (!isset($this->data[$app][$var]) || $this->data[$app][$var] === '')
				{
					//if ($var=='remote_application_url') error_log(__METHOD__.__LINE__.' default for '.$var.' with '.$value);
					$this->data[$app][$var] = $value;
				}
			}
		}
		// now set/force forced values
		//
		foreach($this->forced as $app => $values)
		{
			foreach($values as $var => $value)
			{
				$this->data[$app][$var] = $value;
			}
		}
		// setup the standard substitutes and substitutes the data in $this->data
		//
		$this->standard_substitutes();

		// This is to supress warnings during login
		if (is_array($this->data))
		{
			reset($this->data);
		}
		if (isset($this->debug) && substr($GLOBALS['egw_info']['flags']['currentapp'],0,3) != 'log')
		{
			echo 'user<pre>';     print_r($this->user); echo "</pre>\n";
			echo 'forced<pre>';   print_r($this->forced); echo "</pre>\n";
			echo 'default<pre>';  print_r($this->default); echo "</pre>\n";
			echo 'group<pre>';    print_r($this->group); echo "</pre>\n";
			echo 'effectiv<pre>'; print_r($this->data); echo "</pre>\n";
		}
//error_log(__METHOD__.__LINE__.'->user: remote_application_url:'.array2string($this->user['common']['remote_application_url']));
//error_log(__METHOD__.__LINE__.'->default: remote_application_url:'.array2string($this->default['common']['remote_application_url']));
//error_log(__METHOD__.__LINE__.'->forced: remote_application_url:'.array2string($this->forced['common']['remote_application_url']));
//error_log(__METHOD__.__LINE__.'->effective: remote_application_url:'.array2string($this->data['common']['remote_application_url']));
		$this->check_set_tz_offset();

		return $this->data;
	}

	/**
	 * Checking new timezone ('tz') pref and setting old tz_offset pref from it
	 *
	 */
	function check_set_tz_offset()
	{
		$prefs =& $this->data['common'];

		if (!empty($prefs['tz']))
		{
			egw_time::setUserPrefs($prefs['tz'],$prefs['date_format'],$prefs['time_format']);
			// set the old preference for compatibilty with old code
			$GLOBALS['egw_info']['user']['preferences']['common']['tz_offset'] = egw_time::tz_offset_s()/3600;
			//echo "<p>".__METHOD__."() tz=$prefs[tz] --> tz_offset={$GLOBALS['egw_info']['user']['preferences']['common']['tz_offset']}</p>\n";

			// ToDo: get rid of that
			if (isset($GLOBALS['egw']) && ($GLOBALS['egw'] instanceof egw))
			{
				$GLOBALS['egw']->unset_datetime();	// to force an update
			}
		}
	}

	/**
	 * Set user timezone, if we get restored from session
	 *
	 */
	function __wakeup()
	{
		$this->check_set_tz_offset();
	}

	/**
	 * read preferences from repository and stores in an array
	 *
	 * @return array containing the effective user preferences
	 */
	function read()
	{
		if (count($this->data) == 0)
		{
			$this->read_repository();
		}
		reset ($this->data);
		return $this->data;
	}

	/**
	 * add preference to $app_name a particular app
	 *
	 * the effective prefs ($this->data) are updated to reflect the change
	 *
	 * @param string $app_name name of the app
	 * @param string $var name of preference to be stored
	 * @param mixed $value='##undef##' value of the preference, if not given $GLOBALS[$var] is used
	 * @param $type='user' of preference to set: forced, default, user
	 * @return array with new effective prefs (even when forced or default prefs are set !)
	 */
	function add($app_name,$var,$value = '##undef##',$type='user')
	{
		//echo "<p>add('$app_name','$var','$value')</p>\n";
		if ($value === '##undef##')
		{
			$value = $GLOBALS[$var];
		}

		switch ($type)
		{
			case 'session':
				if (!isset($this->forced[$app_name][$var]) || $this->forced[$app_name][$var] === '')
				{
					$this->session[$app_name][$var] = $this->data[$app_name][$var] = $value;
					$GLOBALS['egw']->session->appsession('preferences','preferences',$this->session);
					if (method_exists($GLOBALS['egw'],'invalidate_session_cache'))	// egw object in setup is limited
					{
						$GLOBALS['egw']->invalidate_session_cache();	// in case with cache the egw_info array in the session
					}
				}
				break;

			case 'forced':
				$this->data[$app_name][$var] = $this->forced[$app_name][$var] = $value;
				break;

			case 'default':
				$this->default[$app_name][$var] = $value;
				if ((!isset($this->forced[$app_name][$var]) || $this->forced[$app_name][$var] === '') &&
					(!isset($this->user[$app_name][$var]) || $this->user[$app_name][$var] === ''))
				{
					$this->data[$app_name][$var] = $value;
				}
				break;

			case 'user':
			default:
				$this->user[$app_name][$var] = $value;
				if (!isset($this->forced[$app_name][$var]) || $this->forced[$app_name][$var] === '')
				{
					$this->data[$app_name][$var] = $value;
				}
				break;
		}
		reset($this->data);
		return $this->data;
	}

	/**
	 * delete preference from $app_name
	 *
	 * the effektive prefs ($this->data) are updated to reflect the change
	 *
	 * @param string $app_name name of app
	 * @param string $var=false variable to be deleted
	 * @param string $type='user' of preference to set: forced, default, user
	 * @return array with new effective prefs (even when forced or default prefs are deleted!)
	 */
	function delete($app_name, $var = False,$type = 'user')
	{
		//echo "<p>delete('$app_name','$var','$type')</p>\n";
		$set_via = array(
			'forced'  => array('user','default'),
			'default' => array('forced','user'),
			'user'    => array('forced','group','default'),
			'group'   => array('forced'),
		);
		if (!isset($set_via[$type]))
		{
			$type = 'user';
		}
		$pref = &$this->$type;

		if (($all = empty($var))) // to check if $var is regarded as empty (false, 0, '', null, array() should do the trick
		{
			unset($pref[$app_name]);
			unset($this->data[$app_name]);
		}
		else
		{
			unset($pref[$app_name][$var]);
			unset($this->data[$app_name][$var]);
		}
		// set the effectiv pref again if needed
		//
		foreach ($set_via[$type] as $set_from)
		{
			$arr = &$this->$set_from;
			if ($all)
			{
				if (isset($arr[$app_name]))
				{
					$this->data[$app_name] = $arr[$app_name];
					break;
				}
			}
			else
			{
				if($var && @isset($arr[$app_name][$var]) && $arr[$app_name][$var] !== '')
				{
					$this->data[$app_name][$var] = $arr[$app_name][$var];
					break;
				}
			}
			unset($arr);
		}
		reset ($this->data);
		return $this->data;
	}

	/**
	 * delete all prefs of a given user
	 *
	 * @param int $accountid
	 */
	function delete_user($accountid)
	{
		if ($account_id > 0)
		{
			$this->delete($this->table,array('preference_owner' => $accountid),__LINE__,__FILE__);
		}
	}

	/**
	 * delete all prefs of a given group
	 *
	 * @param int $accountid
	 */
	function delete_group($accountid)
	{
		if ($account_id < 0)
		{
			$this->delete($this->table,array('preference_owner' => $accountid+self::DEFAULT_ID),__LINE__,__FILE__);
		}
	}

	/**
	 * add complex array data preference to $app_name a particular app
	 *
	 * @deprecated we can now correctly store arrays in the prefs, AFAIK only used in email
	 *
	 * @param string $app_name name of the app
	 * @param string $var array keys separated by '/', eg. 'ex_accounts/1'
	 * @param mixed $value='' value of the preference
	 * @return array with new effective prefs (even when forced or default prefs are deleted!)
	 */
	function add_struct($app_name,$var,$value = '')
	{
		$parts = explode('/',str_replace(array('][','[',']','"',"'"),array('/','','','',''),$var));
		$data = &$this->data[$app_name];
		$user = &$this->user[$app_name];
		foreach($parts as $name)
		{
			$data = &$data[$name];
			$user = &$user[$name];
		}
		$data = $user = $value;
		print_debug('class.preferences: add_struct: $this->data[$app_name] dump:', $this->data[$app_name],'api');
		reset($this->data);
		return $this->data;
	}

	/**
	 * delete complex array data preference from $app_name
	 *
	 * @deprecated we can now correctly store arrays in the prefs, AFAIK only used in email
	 *
	 * @param $app_name name of app
	 * @param $var array keys separated by '/', eg. 'ex_accounts/1'
	 * @return array with new effective prefs (even when forced or default prefs are deleted!)
	 */
	function delete_struct($app_name, $var = '')
	{
		$parts = explode('/',str_replace(array('][','[',']','"',"'"),array('/','','','',''),$var));
		$last = array_pop($parts);
		$data = &$this->data[$app_name];
		$user = &$this->user[$app_name];
		foreach($parts as $name)
		{
			$data = &$data[$name];
			$user = &$user[$name];
		}
		unset($user[$last]);
		print_debug('* $this->data[$app_name] dump:', $this->data[$app_name],'api');
		reset ($this->data);
		return $this->data;
	}

	/**
	 * quote (addslashes) recursivly the whole array
	 *
	 * @param array &$arr array to quote (var-param!)
	 */
	function quote(&$arr)
	{
		if (!is_array($arr))
		{
			$arr = addslashes($arr);
			return;
		}
		foreach($arr as $key => $value)
		{
			if (is_array($value))
			{
				$this->quote($arr[$key]);
			}
			else
			{
				$arr[$key] = addslashes($value);
			}
		}
	}

	/**
	 * Save the the preferences to the repository
	 *
	 * User prefs for saveing are in $this->user not in $this->data, which are the effectiv prefs only!
	 *
	 * @param boolean $update_session_info=false old param, seems not to be used (not used anymore)
	 * @param string $type='user' which prefs to update: user/default/forced
	 * @param boolean $invalid_cache=true should we invalidate the cache, default true (not used anymore)
	 * @return array with new effective prefs (even when forced or default prefs are deleted!)
	 */
	function save_repository($update_session_info = False,$type='user',$invalid_cache=true)
	{
		switch($type)
		{
			case 'forced':
				$account_id = self::FORCED_ID;
				$prefs = &$this->forced;
				break;
			case 'default':
				$account_id = self::DEFAULT_ID;
				$prefs = &$this->default;
				break;
			case 'group':
				$account_id = $GLOBALS['egw']->accounts->id2name($this->account_id,'account_primary_group')+self::DEFAULT_ID;
				$prefs = &$this->group;
				break;
			default:
				$account_id = (int)$this->account_id;
				$prefs = &$this->user;	// we use the user-array as data contains default values too
				break;
		}
		//echo "<p>preferences::save_repository(,$type): account_id=$account_id, prefs="; print_r($prefs); echo "</p>\n";

		if (!$GLOBALS['egw']->acl->check('session_only_preferences',1,'preferences'))
		{
			$this->db->transaction_begin();
			$this->db->delete($this->table,array('preference_owner' => $account_id),__LINE__,__FILE__);

			foreach($prefs as $app => $value)
			{
				if (!is_array($value))
				{
					continue;
				}
				$this->quote($value);	// this quote-ing is for serialize, not for the db

				$this->db->insert($this->table,array(
					'preference_value' => serialize($value),
				),array(
					'preference_owner' => $account_id,
					'preference_app'   => $app,
				),__LINE__,__FILE__);
			}
			$this->db->transaction_commit();

			// no need to invalidate session cache, if we write the prefs to the session too
			$egw = unserialize($_SESSION[egw_session::EGW_OBJECT_CACHE]);
			$egw->preferences = $this;
			$_SESSION[egw_session::EGW_OBJECT_CACHE] = serialize($egw);
		}
		$_SESSION[egw_session::EGW_INFO_CACHE]['user']['preferences'] = $GLOBALS['egw_info']['user']['preferences'] = $this->data;

		return $this->data;
	}

	/**
	 * insert a copy of the default preferences for use by real account_id
	 *
	 * @deprecated not longer needed, as the defaults are merged in on runtime
	 *
	 * @param int $account_id numerical id of account for which to create the prefs
	 */
	function create_defaults($account_id)
	{
	}

	/**
	 * update the preferences array
	 *
	 * @param array $data array of preferences
	 * @return array with new effective prefs (even when forced or default prefs are deleted!)
	 */
	function update_data($data)
	{
		$this->data = $data;
		reset($this->data);
		return $this->data;
	}

	/* legacy support */
	function change($app_name,$var,$value = "")
	{
		return $this->add($app_name,$var,$value);
	}
	function commit($update_session_info = True)
	{
		//return $this->save_repository($update_session_info);
	}

	/**************************************************************************\
	* These are the non-standard $this->account_id specific functions          *
	\**************************************************************************/

	/**
	 * verify basic settings
	 *
	 * @discussion
	 */
	function verify_basic_settings()
	{
		if (!@is_array($GLOBALS['egw_info']['user']['preferences']))
		{
			$GLOBALS['egw_info']['user']['preferences'] = array();
		}
		/* This takes care of new users who don't have proper default prefs setup */
		if (!isset($GLOBALS['egw_info']['flags']['nocommon_preferences']) ||
			!$GLOBALS['egw_info']['flags']['nocommon_preferences'])
		{
			$preferences_update = False;
			if (!isset($GLOBALS['egw_info']['user']['preferences']['common']['maxmatchs']) ||
				!$GLOBALS['egw_info']['user']['preferences']['common']['maxmatchs'])
			{
				$this->add('common','maxmatchs',15);
				$preferences_update = True;
			}
			if (!isset($GLOBALS['egw_info']['user']['preferences']['common']['theme']) ||
				!$GLOBALS['egw_info']['user']['preferences']['common']['theme'])
			{
				$this->add('common','theme','default');
				$preferences_update = True;
			}
			if (!isset($GLOBALS['egw_info']['user']['preferences']['common']['template_set']) ||
				!$GLOBALS['egw_info']['user']['preferences']['common']['template_set'])
			{
				$this->add('common','template_set','default');
				$preferences_update = True;
			}
			if (!isset($GLOBALS['egw_info']['user']['preferences']['common']['dateformat']) ||
				!$GLOBALS['egw_info']['user']['preferences']['common']['dateformat'])
			{
				$this->add('common','dateformat','m/d/Y');
				$preferences_update = True;
			}
			if (!isset($GLOBALS['egw_info']['user']['preferences']['common']['timeformat']) ||
				!$GLOBALS['egw_info']['user']['preferences']['common']['timeformat'])
			{
				$this->add('common','timeformat',12);
				$preferences_update = True;
			}
			if (!isset($GLOBALS['egw_info']['user']['preferences']['common']['lang']) ||
				!$GLOBALS['egw_info']['user']['preferences']['common']['lang'])
			{
				$this->add('common','lang',$GLOBALS['egw']->common->getPreferredLanguage());
				$preferences_update = True;
			}
			if ($preferences_update)
			{
				$this->save_repository();
			}
			unset($preferences_update);
		}
	}

	/****************************************************\
	* Email Preferences and Private Support Functions   *
	\****************************************************/

	/**
	 * Helper function for create_email_preferences, gets mail server port number.
	 *
	 * This will generate the appropriate port number to access a
	 * mail server of type pop3, pop3s, imap, imaps users value from
	 * $phpgw_info['user']['preferences']['email']['mail_port'].
	 * if that value is not set, it generates a default port for the given $server_type.
	 * Someday, this *MAY* be
	 * (a) a se4rver wide admin setting, or
	 * (b)user custom preference
	 * Until then, simply set the port number based on the mail_server_type, thereof
	 * ONLY call this function AFTER ['email']['mail_server_type'] has been set.
	 * @param $prefs - user preferences array based on element ['email'][]
	 * @author  Angles
	 * @access Private
	 */
	function sub_get_mailsvr_port($prefs, $acctnum=0)
	{
		// first we try the port number supplied in preferences
		if((isset($prefs['email']['accounts'][$acctnum]['mail_port'])) &&
			($prefs['email']['accounts'][$acctnum]['mail_port'] != ''))
		{
			$port_number = $prefs['email']['accounts'][$acctnum]['mail_port'];
		}
		// preferences does not have a port number, generate a default value
		else
		{
			if (!isset($prefs['email']['accounts'][$acctnum]['mail_server_type']))
			{
				$prefs['email']['accounts'][$acctnum]['mail_server_type'] = $prefs['email']['mail_server_type'];
			}

			switch($prefs['email']['accounts'][$acctnum]['mail_server_type'])
			{
				case 'pop3s':
					// POP3 over SSL
					$port_number = 995;
					break;
				case 'pop3':
					// POP3 normal connection, No SSL
					// ( same string as normal imap above)
					$port_number = 110;
					break;
				case 'nntp':
					// NNTP news server port
					$port_number = 119;
					break;
				case 'imaps':
					// IMAP over SSL
					$port_number = 993;
					break;
				case 'imap':
					// IMAP normal connection, No SSL
				default:
					// UNKNOWN SERVER in Preferences, return a
					// default value that is likely to work
					// probably should raise some kind of error here
					$port_number = 143;
					break;
			}
		}
		return $port_number;
	}

	/**
	 * Helper function for create_email_preferences, gets default userid for email
	 *
	 * This will generate the appropriate userid for accessing an email server.
	 * In the absence of a custom ['email']['userid'], this function should be used to set it.
	 * @param $accountid - as determined in and/or passed to "create_email_preferences"
	 * @access Private
	 */
	function sub_default_userid($account_id='')
	{
		if ($GLOBALS['egw_info']['server']['mail_login_type'] == 'vmailmgr')
		{
			$prefs_email_userid = $GLOBALS['egw']->accounts->id2name($account_id)
				. '@' . $GLOBALS['egw_info']['server']['mail_suffix'];
		}
		else
		{
			$prefs_email_userid = $GLOBALS['egw']->accounts->id2name($account_id);
		}
		return $prefs_email_userid;
	}

	/**
	 * returns the custom email-address (if set) or generates a default one
	 *
	 * This will generate the appropriate email address used as the "From:"
	 * email address when the user sends email, the localpert * part. The "personal"
	 * part is generated elsewhere.
	 * In the absence of a custom ['email']['address'], this function should be used to set it.
	 *
	 * @access public
	 * @param int $accountid - as determined in and/or passed to "create_email_preferences"
	 * @return string with email-address
	 */
	function email_address($account_id='')
	{
		if (isset($this->data['email']['address']))
		{
			return $this->data['email']['address'];
		}
		// if email-address is set in the account, return it
		if (($email = $GLOBALS['egw']->accounts->id2name($account_id,'account_email')))
		{
			return $email;
		}
		$prefs_email_address = $GLOBALS['egw']->accounts->id2name($account_id);
		if (strpos($prefs_email_address,'@') === False)
		{
			$prefs_email_address .= '@' . $GLOBALS['egw_info']['server']['mail_suffix'];
		}
		return $prefs_email_address;
	}

	function sub_default_address($account_id='')
	{
		return $this->email_address($account_id);
	}

	/**
	 * create email preferences
	 *
	 * @param $account_id -optional defaults to : get_account_id()
	 * fills a local copy of ['email'][] prefs array which is then returned to the calling
	 * function, which the calling function generally tacks onto the $GLOBALS['egw_info'] array as such:
	 * 	$GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->create_email_preferences();
	 * which fills an array based at:
	 * 	$GLOBALS['egw_info']['user']['preferences']['email'][prefs_are_elements_here]
	 * Reading the raw preference DB data and comparing to the email preference schema defined in
	 * /email/class.bopreferences.inc.php (see discussion there and below) to create default preference values
	 * for the  in the ['email'][] pref data array in cases where the user has not supplied
	 * a preference value for any particular preference item available to the user.
	 * @access Public
	 */
	function create_email_preferences($accountid='', $acctnum=0)
	{
		print_debug('class.preferences: create_email_preferences: ENTERING<br>', 'messageonly','api');
		// we may need function "html_quotes_decode" from the mail_msg class
		$email_base =& CreateObject("email.mail_msg");

		$account_id = get_account_id($accountid);
		// If the current user is not the request user, grab the preferences
		// and reset back to current user.
		if($account_id != $this->account_id)
		{
			// Temporarily store the values to a temp, so when the
			// read_repository() is called, it doesn't destory the
			// current users settings.
			$temp_account_id = $this->account_id;
			$temp_data = $this->data;

			// Grab the new users settings, only if they are not the
			// current users settings.
			$this->account_id = $account_id;
			$prefs = $this->read_repository();

			// Reset the data to what it was prior to this call
			$this->account_id = $temp_account_id;
			$this->data = $temp_data;
		}
		else
		{
			$prefs = $this->data;
		}
		// are we dealing with the default email account or an extra email account?
		if ($acctnum != 0)
		{
			// prefs are actually a sub-element of the main email prefs
			// at location [email][ex_accounts][X][...pref names] => pref values
			// make this look like "prefs[email] so the code below code below will do its job transparently

			// obtain the desired sub-array of extra account prefs
			$sub_prefs = array();
			$sub_prefs['email'] = $prefs['email']['ex_accounts'][$acctnum];
			// make the switch, make it seem like top level email prefs
			$prefs = array();
			$prefs['email'] = $sub_prefs['email'];
			// since we return just $prefs, it's up to the calling program to put the sub prefs in the right place
		}
		print_debug('class.preferences: create_email_preferences: $acctnum: ['.$acctnum.'] ; raw $this->data dump', $this->data,'api');

		// = = = =  NOT-SIMPLE  PREFS  = = = =
		// Default Preferences info that is:
		// (a) not controlled by email prefs itself (mostly api and/or server level stuff)
		// (b) too complicated to be described in the email prefs data array instructions

		// ---  [server][mail_server_type]  ---
		// Set API Level Server Mail Type if not defined
		// if for some reason the API didnot have a mail server type set during initialization
		if (empty($GLOBALS['egw_info']['server']['mail_server_type']))
		{
			$GLOBALS['egw_info']['server']['mail_server_type'] = 'imap';
		}

		// ---  [server][mail_folder]  ---
		// ====  UWash Mail Folder Location used to be "mail", now it's changeable, but keep the
		// ====  default to "mail" so upgrades happen transparently
		// ---  TEMP MAKE DEFAULT UWASH MAIL FOLDER ~/mail (a.k.a. $HOME/mail)
		$GLOBALS['egw_info']['server']['mail_folder'] = 'mail';
		// ---  DELETE THE ABOVE WHEN THIS OPTION GETS INTO THE SYSTEM SETUP
		// pick up custom "mail_folder" if it exists (used for UWash and UWash Maildir servers)
		// else use the system default (which we temporarily hard coded to "mail" just above here)

		//---  [email][mail_port]  ---
		// These sets the mail_port server variable
		// someday (not currently) this may be a site-wide property set during site setup
		// additionally, someday (not currently) the user may be able to override this with
		// a custom email preference. Currently, we simply use standard port numbers
		// for the service in question.
		$prefs['email']['mail_port'] = $this->sub_get_mailsvr_port($prefs);

		//---  [email][fullname]  ---
		// we pick this up from phpgw api for the default account
		// the user does not directly manipulate this pref for the default email account
		if ((string)$acctnum == '0')
		{
			$prefs['email']['fullname'] = $GLOBALS['egw_info']['user']['fullname'];
		}

		// = = = =  SIMPLER PREFS  = = = =

		// Default Preferences info that is articulated in the email prefs schema array itself
		// such email prefs schema array is described and established in /email/class.bopreferences
		// by function "init_available_prefs", see the discussion there.

		// --- create the objectified /email/class.bopreferences.inc.php ---
		$bo_mail_prefs =& CreateObject('email.bopreferences');

		// --- bo_mail_prefs->init_available_prefs() ---
		// this fills object_email_bopreferences->std_prefs and ->cust_prefs
		// we will initialize the users preferences according to the rules and instructions
		// embodied in those prefs arrays, applying those rules to the unprocessed
		// data read from the preferences DB. By taking the raw data and applying those rules,
		// we will construct valid and known email preference data for this user.
		$bo_mail_prefs->init_available_prefs();

		// --- combine the two array (std and cust) for 1 pass handling ---
		// when this preference DB was submitted and saved, it was hopefully so well structured
		// that we can simply combine the two arrays, std_prefs and cust_prefs, and do a one
		// pass analysis and preparation of this users preferences.
		$avail_pref_array = $bo_mail_prefs->std_prefs;
		$c_cust_prefs = count($bo_mail_prefs->cust_prefs);
		for($i=0;$i<$c_cust_prefs;$i++)
		{
			// add each custom prefs to the std prefs array
			$next_idx = count($avail_pref_array);
			$avail_pref_array[$next_idx] = $bo_mail_prefs->cust_prefs[$i];
		}
		print_debug('class.preferences: create_email_preferences: std AND cust arrays combined:', $avail_pref_array,'api');

		// --- make the schema-based pref data for this user ---
		// user defined values and/or user specified custom email prefs are read from the
		// prefs DB with mininal manipulation of the data. Currently the only change to
		// users raw data is related to reversing the encoding of "database un-friendly" chars
		// which itself may become unnecessary if and when the database handlers can reliably
		// take care of this for us. Of course, password data requires special decoding,
		// but the password in the array [email][paswd] should be left in encrypted form
		// and only decrypted seperately when used to login in to an email server.

		// --- generating a default value if necessary ---
		// in the absence of a user defined custom email preference for a particular item, we can
		// determine the desired default value for that pref as such:
		// $this_avail_pref['init_default']  is a comma seperated seperated string which should
		// be exploded into an array containing 2 elements that are:
		// exploded[0] : an description of how to handle the next string element to get a default value.
		// Possible "instructional tokens" for exploded[0] (called $set_proc[0] below) are:
		//	string
		//	set_or_not
		//	function
		//	init_no_fill
		//	varEVAL
		// tells you how to handle the string in exploded[1] (called $set_proc[1] below) to get a valid
		// default value for a particular preference if one is needed (i.e. if no user custom
		// email preference exists that should override that default value, in which case we
		// do not even need to obtain such a default value as described in ['init_default'] anyway).

		// --- loop thru $avail_pref_array and process each pref item ---
		$c_prefs = count($avail_pref_array);
		for($i=0;$i<$c_prefs;$i++)
		{
			$this_avail_pref = $avail_pref_array[$i];
			print_debug('class.preferences: create_email_preferences: value from DB for $prefs[email]['.$this_avail_pref['id'].'] = ['.$prefs['email'][$this_avail_pref['id']].']', 'messageonly','api');
			print_debug('class.preferences: create_email_preferences: std/cust_prefs $this_avail_pref['.$i.'] dump:', $this_avail_pref,'api');

			// --- is there a value in the DB for this preference item ---
			// if the prefs DB has no value for this defined available preference, we must make one.
			// This occurs if (a) this is user's first login, or (b) this is a custom pref which the user
			// has not overriden, do a default (non-custom) value is needed.
			if (!isset($prefs['email'][$this_avail_pref['id']]))
			{
				// now we are analizing an individual pref that is available to the user
				// AND the user had no existing value in the prefs DB for this.

				// --- get instructions on how to generate a default value ---
				$set_proc = explode(',', $this_avail_pref['init_default']);
				print_debug(' * set_proc=['.serialize($set_proc).']', 'messageonly','api');

				// --- use "instructional token" in $set_proc[0] to take appropriate action ---
				// STRING
				if ($set_proc[0] == 'string')
				{
					// means this pref item's value type is string
					// which defined string default value is in $set_proc[1]
					print_debug('* handle "string" set_proc: ', serialize($set_proc),'api');
					if (trim($set_proc[1]) == '')
					{
						// this happens when $this_avail_pref['init_default'] = "string, "
						$this_string = '';
					}
					else
					{
						$this_string = $set_proc[1];
					}
					$prefs['email'][$this_avail_pref['id']] = $this_string;
				}
				// SET_OR_NOT
				elseif ($set_proc[0] == 'set_or_not')
				{
					// typical with boolean options, True = "set/exists" and False = unset
					print_debug('* handle "set_or_not" set_proc: ', serialize($set_proc),'api');
					if ($set_proc[1] == 'not_set')
					{
						// leave it NOT SET
					}
					else
					{
						// opposite of boolean not_set  = string "True" which simply sets a
						// value it exists in the users session [email][] preference array
						$prefs['email'][$this_avail_pref['id']] = 'True';
					}
				}
				// FUNCTION
				elseif ($set_proc[0] == 'function')
				{
					// string in $set_proc[1] should be "eval"uated as code, calling a function
					// which will give us a default value to put in users session [email][] prefs array
					print_debug(' * handle "function" set_proc: ', serialize($set_proc),'api');
					$evaled = '';
					//eval('$evaled = $this->'.$set_proc[1].'('.$account_id.');');

					$code = '$evaled = $this->'.$set_proc[1].'('.$account_id.');';
					print_debug(' * $code: ', $code,'api');
					eval($code);

					print_debug('* $evaled:', $evaled,'api');
					$prefs['email'][$this_avail_pref['id']] = $evaled;
				}
				// INIT_NO_FILL
				elseif ($set_proc[0] == 'init_no_fill')
				{
					// we have an available preference item that we may NOT fill with a default
					// value. Only the user may supply a value for this pref item.
					print_debug('* handle "init_no_fill" set_proc:', serialize($set_proc),'api');
					// we are FORBADE from filling this at this time!
				}
				// varEVAL
				elseif ($set_proc[0] == 'varEVAL')
				{
					// similar to "function" but used for array references, the string in $set_proc[1]
					// represents code which typically is an array referencing a system/api property
					print_debug('* handle "GLOBALS" set_proc:', serialize($set_proc),'api');
					$evaled = '';
					$code = '$evaled = '.$set_proc[1];
					print_debug(' * $code:', $code,'api');
					eval($code);
					print_debug('* $evaled:', $evaled,'api');
					$prefs['email'][$this_avail_pref['id']] = $evaled;
				}
				else
				{
					// error, no instructions on how to handle this element's default value creation
					echo 'class.preferences: create_email_preferences: set_proc ERROR: '.serialize($set_proc).'<br>';
				}
			}
			else
			{
				// we have a value in the database, do we need to prepare it in any way?
				// (the following discussion is unconfirmed:)
				// DO NOT ALTER the data in the prefs array!!!! or the next time we call
				// save_repository withOUT undoing what we might do here, the
				// prefs will permenantly LOOSE the very thing(s) we are un-doing
				/// here until the next OFFICIAL submit email prefs function, where it
				// will again get this preparation before being written to the database.

				// NOTE: if database de-fanging is eventually handled deeper in the
				// preferences class, then the following code would become depreciated
				// and should be removed in that case.
				if (($this_avail_pref['type'] == 'user_string') &&
					(stristr($this_avail_pref['write_props'], 'no_db_defang') == False))
				{
					// this value was "de-fanged" before putting it in the database
					// undo that defanging now
					$db_unfriendly = $email_base->html_quotes_decode($prefs['email'][$this_avail_pref['id']]);
					$prefs['email'][$this_avail_pref['id']] = $db_unfriendly;
				}
			}
		}
		// users preferences are now established to known structured values...

		// SANITY CHECK
		// ---  [email][use_trash_folder]  ---
		// ---  [email][use_sent_folder]  ---
		// is it possible to use Trash and Sent folders - i.e. using IMAP server
		// if not - force settings to false
		if (stristr($prefs['email']['mail_server_type'], 'imap') == False)
		{
			if (isset($prefs['email']['use_trash_folder']))
			{
				unset($prefs['email']['use_trash_folder']);
			}

			if (isset($prefs['email']['use_sent_folder']))
			{
				unset($prefs['email']['use_sent_folder']);
			}
		}

		// DEBUG : force some settings to test stuff
		//$prefs['email']['p_persistent'] = 'True';

		print_debug('class.preferences: $acctnum: ['.$acctnum.'] ; create_email_preferences: $prefs[email]', $prefs['email'],'api');
		print_debug('class.preferences: create_email_preferences: LEAVING', 'messageonly','api');
		return $prefs;
	}
}
