mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-08-16 19:41:45 +02:00
Refractured eTemplate to use:
- the etemplate_request object which stores the request data in the a) session (as before) or b) compressed and encrypted in the form transmitted to the user Benefit of b) is that the session does not grow and the form can be submitted as long as the session exists, as we need no garbadge collection. Of cause more data needs to be submitt between browser and webserver. b) is choosen automatic if mcrypt and gzcompress are available, but can be turned off via setting etemplate_request::$request_class = 'etemplate_request_session'; - static class variables instead of the before used global ones --> This new version of eTemplate is fully backward compatible with 1.6!
This commit is contained in:
@ -1,36 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* eGroupWare - eTemplate request object
|
||||
* eGroupWare - eTemplate request object storing request-data directly in the form itself
|
||||
*
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package etemplate
|
||||
* @subpackage api
|
||||
* @link http://www.egroupware.org
|
||||
* @author Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @copyright (c) 2007 by Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @copyright (c) 2007-9 by Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to represent the persitent information stored on the server for each eTemplate request
|
||||
*
|
||||
* The information is stored in the users session
|
||||
*
|
||||
* There are two ways to instanciate a request object:
|
||||
*
|
||||
* a) a new request:
|
||||
*
|
||||
* $request = new etemplate_request(); $id = $request->id();
|
||||
*
|
||||
* b) open or modify an existing request:
|
||||
*
|
||||
* if (!($request = etemplate_request::read($id)))
|
||||
* Class to represent the persitent information of an eTemplate request
|
||||
*
|
||||
* This class stores the request-data direct in a hidden var in the form.
|
||||
* As this would allow an evil user to manipulate it and therefore compromise the security
|
||||
* of an EGroupware instance, this class should only be used, if mcrypt is available
|
||||
* to encrypt that data. The factory method etemplate_request::read() ensures that,
|
||||
* by using etemplate_request_session instead.
|
||||
*
|
||||
* The key used to encrypt the request can be set in header.inc.php by setting
|
||||
*
|
||||
* $GLOBALS['egw_info']['server']['etemplate_form_key'] = 'something secret';
|
||||
*
|
||||
* if this var is not set, the db_pass and EGW_SERVER_ROOT is used instead.
|
||||
*
|
||||
* The request object should be instancated only via the factory method etemplate::request($id=null)
|
||||
*
|
||||
* $request = etemplate::request();
|
||||
*
|
||||
* // add request data
|
||||
*
|
||||
* $id = $request->id();
|
||||
*
|
||||
* b) open or modify an existing request:
|
||||
*
|
||||
* if (!($request = etemplate::request($id)))
|
||||
* {
|
||||
* // request not found
|
||||
* }
|
||||
*
|
||||
* Ajax request can use this object to open the original request by using the id, they have to transmitt back,
|
||||
* and register further variables, modify the registered ones or delete them.
|
||||
*
|
||||
*
|
||||
* Ajax requests can use this object to open the original request by using the id, they have to transmitt back,
|
||||
* and register further variables, modify the registered ones or delete them AND then update the id, if it changed:
|
||||
*
|
||||
* if (($new_id = $request->id()) != $id)
|
||||
* {
|
||||
* $response->addAssign('etemplate_exec_id','value',$new_id);
|
||||
* }
|
||||
*
|
||||
* For an example look in link_widget::ajax_search()
|
||||
*/
|
||||
class etemplate_request
|
||||
@ -40,61 +58,132 @@ class etemplate_request
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data=array();
|
||||
protected $data=array();
|
||||
/**
|
||||
* Flag if data has been modified and therefor need to be stored again in the session
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private $data_modified=false;
|
||||
protected $data_modified=false;
|
||||
/**
|
||||
* request id
|
||||
* mcrypt resource
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
static protected $mcrypt;
|
||||
|
||||
/**
|
||||
* See gzcompress, set it to 0 to not compress
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
static public $compression_level = 6;
|
||||
|
||||
/**
|
||||
* Name of request class used
|
||||
*
|
||||
* Can be set here to force a certain class, otherwise the factory method chooses one
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
static public $request_class;// = 'etemplate_request_session';
|
||||
|
||||
/**
|
||||
* Enter description here...
|
||||
* Factory method to get a new request object or the one for an existing request
|
||||
*
|
||||
* @param array $id_data
|
||||
* If mcrypt AND gzcompress is available this factory method chooses etemplate_request,
|
||||
* which stores the request data encrypted in a hidden var directly in the form,
|
||||
* over etemplate_request_session, which stores the data in the session (and causing
|
||||
* the sesison to constantly grow).
|
||||
*
|
||||
* @param string $id=null
|
||||
* @return etemplate_request
|
||||
*/
|
||||
function __construct($id=null)
|
||||
public static function read($id=null)
|
||||
{
|
||||
if (!$id) $id = self::request_id();
|
||||
|
||||
$this->id = $id;
|
||||
if (is_null(self::$request_class))
|
||||
{
|
||||
self::$request_class = extension_loaded('mcrypt') && function_exists('gzcompress') &&
|
||||
self::init_crypt() ? __CLASS__ : 'etemplate_request_session';
|
||||
}
|
||||
if (self::$request_class != __CLASS__)
|
||||
{
|
||||
return call_user_method('read',self::$request_class,$id);
|
||||
}
|
||||
$request = new etemplate_request();
|
||||
|
||||
if (!is_null($id))
|
||||
{
|
||||
$id = base64_decode($id);
|
||||
|
||||
// decrypt the data if available
|
||||
if (self::init_crypt())
|
||||
{
|
||||
$id = trim(mdecrypt_generic(self::$mcrypt,$id));
|
||||
}
|
||||
// uncompress the data if available
|
||||
if (self::$compression_level && function_exists('gzcompress'))
|
||||
{
|
||||
//$len_compressed = bytes($id);
|
||||
//$time = microtime(true);
|
||||
$id = gzuncompress($id);
|
||||
//$time = number_format(1000.0 * (microtime(true) - $time),1);
|
||||
//$len_uncompressed = bytes($id);
|
||||
//error_log(__METHOD__."() uncompressed from $len_compressed to $len_uncompressed bytes $time ms");
|
||||
}
|
||||
$request->data = unserialize($id);
|
||||
|
||||
if (!$request->data)
|
||||
{
|
||||
error_log(__METHOD__."() id not valid!");
|
||||
return false;
|
||||
}
|
||||
//error_log(__METHOD__."() size of request = ".bytes($id));
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor to force the instancation of this class only via it's static factory method read
|
||||
*
|
||||
* @param string $id=null
|
||||
*/
|
||||
private function __construct($id=null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* return the id of this request
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function id()
|
||||
public function &id()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the request via it's id, returns a request_object or false
|
||||
*
|
||||
* @param string $id
|
||||
* @return etempalte_request|boolean the object or false if $id is not found
|
||||
*/
|
||||
static function read($id)
|
||||
{
|
||||
if (!($data = $GLOBALS['egw']->session->appsession($id,'etemplate')))
|
||||
$id = serialize($this->data);
|
||||
|
||||
// compress the data if available
|
||||
if (self::$compression_level && function_exists('gzcompress'))
|
||||
{
|
||||
return false; // request not found
|
||||
//$len_uncompressed = bytes($id);
|
||||
//$time = microtime(true);
|
||||
$id = gzcompress($id,self::$compression_level);
|
||||
//$time = number_format(1000.0 * (microtime(true) - $time),1);
|
||||
//$len_compressed = bytes($id);
|
||||
//error_log(__METHOD__."() compressed from $len_uncompressed to $len_compressed bytes in $time ms");
|
||||
}
|
||||
$request = new etemplate_request($id);
|
||||
$request->data = $data;
|
||||
|
||||
return $request;
|
||||
// encrypt the data if available
|
||||
if (self::init_crypt())
|
||||
{
|
||||
$id = mcrypt_generic(self::$mcrypt,$id);
|
||||
}
|
||||
$id = base64_encode($id);
|
||||
|
||||
//error_log(__METHOD__."() #$this->id: size of request = ".bytes($id));//.", id='$id'");
|
||||
//self::debug();
|
||||
return $id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a form-variable to be processed
|
||||
*
|
||||
@ -105,13 +194,36 @@ class etemplate_request
|
||||
public function set_to_process($form_name,$type,$data=array())
|
||||
{
|
||||
if (!$form_name || !$type) return;
|
||||
|
||||
|
||||
$data['type'] = $type;
|
||||
|
||||
|
||||
$this->data['to_process'][$form_name] = $data;
|
||||
$this->data_modified = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set an attribute of a to-process record
|
||||
*
|
||||
* @param string $form_name form-name
|
||||
* @param string $attribute etemplate type
|
||||
* @param array $value
|
||||
* @param boolean $add_to_array=false should $value be added to the attribute array
|
||||
*/
|
||||
public function set_to_process_attribute($form_name,$attribute,$value,$add_to_array=false)
|
||||
{
|
||||
if (!$form_name) return;
|
||||
|
||||
if ($add_to_array)
|
||||
{
|
||||
$this->data['to_process'][$form_name][$attribute][] = $value;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->data['to_process'][$form_name][$attribute] = $value;
|
||||
}
|
||||
$this->data_modified = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a form-variable to be no longer processed
|
||||
*
|
||||
@ -122,7 +234,7 @@ class etemplate_request
|
||||
unset($this->data['to_process'][$form_name]);
|
||||
$this->data_modified = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* return the data of a form-var to process or the whole array
|
||||
*
|
||||
@ -133,7 +245,7 @@ class etemplate_request
|
||||
{
|
||||
return $form_name ? $this->data['to_process'][$form_name] : $this->data['to_process'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* check if something set for a given $form_name
|
||||
*
|
||||
@ -144,14 +256,14 @@ class etemplate_request
|
||||
{
|
||||
return isset($this->data['to_process'][$form_name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* magic function to set all request-vars, used eg. as $request->method = 'app.class.method';
|
||||
*
|
||||
* @param string $var
|
||||
* @param mixed $val
|
||||
*/
|
||||
function __set($var,$val)
|
||||
public function __set($var,$val)
|
||||
{
|
||||
if ($this->data[$var] !== $val)
|
||||
{
|
||||
@ -159,90 +271,112 @@ class etemplate_request
|
||||
$this->data_modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* magic function to access the request-vars, used eg. as $method = $request->method;
|
||||
*
|
||||
* @param string $var
|
||||
* @return mixed
|
||||
*/
|
||||
function __get($var)
|
||||
public function &__get($var)
|
||||
{
|
||||
return $this->data[$var];
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a new request-id via microtime()
|
||||
*
|
||||
* @return string
|
||||
* Get the names / keys of existing variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static function request_id()
|
||||
public function names()
|
||||
{
|
||||
list($msec,$sec) = explode(' ',microtime());
|
||||
$time = 100 * $sec + (int)(100 * $msec); // gives precision of 1/100 sec
|
||||
$id = $GLOBALS['egw_info']['flags']['currentapp'] .':'. $time;
|
||||
|
||||
return $id;
|
||||
return array_keys($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* saves content,readonlys,template-keys, ... via eGW's appsession function
|
||||
* Output the size-wise important parts of a request
|
||||
*
|
||||
* As a user may open several windows with the same content/template wie generate a location-id from microtime
|
||||
* which is used as location for request to descriminate between the different windows. This location-id
|
||||
* is then saved as a hidden-var in the form. The above mentions session-id has nothing to do / is different
|
||||
* from the session-id which is constant for all windows opened in one session.
|
||||
* @param double $min_share minimum share to be reported (in percent of the whole request)
|
||||
* @param double $dump_share minimum share from which on a variable get output
|
||||
*/
|
||||
function __destruct()
|
||||
public function debug($min_share=1.0,$dump_share=25.0)
|
||||
{
|
||||
if ($this->data_modified) $GLOBALS['egw']->session->appsession($this->id,'etemplate',$this->data);
|
||||
|
||||
if (substr($GLOBALS['egw_info']['server']['sessions_type'],0,4) == 'php4' && !$this->garbage_collection_done)
|
||||
echo "<p><b>total size request data = ".($total=strlen(serialize($this->data)))."</b></p>\n";
|
||||
echo "<p>shares bigger then $min_share% percent of it:</p>\n";
|
||||
foreach($this->data as $key => $val)
|
||||
{
|
||||
$this->_php4_request_garbage_collection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* a little bit of garbage collection for php4 sessions (their size is limited by memory_limit)
|
||||
*
|
||||
* With constant eTemplate use it can grow quite big and lead to unusable sessions (php terminates
|
||||
* before any output with "Allowed memory size of ... exhausted").
|
||||
* We delete now sessions once used after 10min and sessions never or multiple used after 60min.
|
||||
*/
|
||||
private function _php4_request_garbage_collection()
|
||||
{
|
||||
// now we are on php4 sessions and do a bit of garbage collection
|
||||
$appsessions =& $_SESSION[EGW_SESSION_VAR]['appsessions']['etemplate'];
|
||||
$session_used =& $appsessions['session_used'];
|
||||
|
||||
if ($this->id)
|
||||
{
|
||||
//echo "session_used[$id_used]='".$session_used[$id_used]."'<br/>\n";
|
||||
++$session_used[$this->id]; // count the number of times a session got used
|
||||
}
|
||||
$this->garbage_collection_done = true;
|
||||
|
||||
if (count($appsessions) < 20) return; // we dont need to care
|
||||
|
||||
list($msec,$sec) = explode(' ',microtime());
|
||||
$now = 100 * $sec + (int)(100 * $msec); // gives precision of 1/100 sec
|
||||
|
||||
foreach(array_keys($appsessions) as $id)
|
||||
{
|
||||
list(,$time) = explode(':',$id);
|
||||
|
||||
if (!$time) continue; // other data, no session
|
||||
|
||||
//echo ++$n.') '.$id.': '.(($now-$time)/100.0)."secs old, used=".$session_used[$id].", size=".strlen($appsessions[$id])."<br>\n";
|
||||
|
||||
if ($session_used[$id] == 1 && $time < $now - 10*6000 || // session used and older then 10min
|
||||
$time < $now - 60*6000) // session not used and older then 1h
|
||||
$len = strlen(is_array($val) ? serialize($val) : $val);
|
||||
$len .= ' ('.sprintf('%2.1lf',($percent = 100.0 * $len / $total)).'%)';
|
||||
if ($percent < $min_share) continue;
|
||||
echo "<p><b>$key</b>: strlen(\$val)=$len</p>\n";
|
||||
if ($percent >= $dump_share) _debug_array($val);
|
||||
if (is_array($val) && $len > 2000)
|
||||
{
|
||||
//echo "<p>boetemplate::php4_session_garbage_collection('$id_used'): unsetting session '$id' (now=$now)</p>\n";
|
||||
unset($appsessions[$id]);
|
||||
unset($session_used[$id]);
|
||||
foreach($val as $k => $v)
|
||||
{
|
||||
$l = strlen(is_array($v) ? serialize($v) : $v);
|
||||
$l .= ' ('.sprintf('%2.1lf',($p = 100.0 * $l / $total)).'%)';
|
||||
if ($p < $min_share) continue;
|
||||
echo "<p> - {$key}[$k]: strlen(\$v)=$l</p>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if session encryption is configured, possible and initialise it
|
||||
*
|
||||
* @param string $algo='tripledes'
|
||||
* @param string $mode='ecb'
|
||||
* @return boolean true if encryption is used, false otherwise
|
||||
*/
|
||||
static public function init_crypt($algo='tripledes',$mode='ecb')
|
||||
{
|
||||
if (is_null(self::$mcrypt))
|
||||
{
|
||||
if (isset($GLOBALS['egw_info']['server']['etemplate_form_key']))
|
||||
{
|
||||
$key = $GLOBALS['egw_info']['server']['etemplate_form_key'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$key = $GLOBALS['egw_info']['server']['db_pass'].EGW_SERVER_ROOT;
|
||||
}
|
||||
if (!extension_loaded('mcrypt') && (!function_exists('dl') || !@dl(PHP_SHLIB_PREFIX.'mcrypt'.'.'.PHP_SHLIB_SUFFIX)))
|
||||
{
|
||||
error_log(__METHOD__."() required PHP extension mcrypt not loaded and can not be loaded, eTemplate requests get NOT encrypted!");
|
||||
return false;
|
||||
}
|
||||
if (!(self::$mcrypt = mcrypt_module_open($algo, '', $mode, '')))
|
||||
{
|
||||
error_log(__METHOD__."() could not mcrypt_module_open(algo='$algo','',mode='$mode',''), eTemplate requests get NOT encrypted!");
|
||||
return false;
|
||||
}
|
||||
$iv_size = mcrypt_enc_get_iv_size(self::$mcrypt);
|
||||
$iv = !isset($GLOBALS['egw_info']['server']['mcrypt_iv']) || strlen($GLOBALS['egw_info']['server']['mcrypt_iv']) < $iv_size ?
|
||||
mcrypt_create_iv ($iv_size, MCRYPT_RAND) : substr($GLOBALS['egw_info']['server']['mcrypt_iv'],0,$iv_size);
|
||||
|
||||
$key_size = mcrypt_enc_get_key_size(self::$mcrypt);
|
||||
if (strlen($key) > $key_size) $key = substr($key,0,$key_size);
|
||||
|
||||
if (mcrypt_generic_init(self::$mcrypt,$key, $iv) < 0)
|
||||
{
|
||||
error_log(__METHOD__."() could not initialise mcrypt, sessions get NOT encrypted!");
|
||||
return self::$mcrypt = false;
|
||||
}
|
||||
}
|
||||
return is_resource(self::$mcrypt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
function __destruct()
|
||||
{
|
||||
if (self::$mcrypt)
|
||||
{
|
||||
mcrypt_generic_deinit(self::$mcrypt);
|
||||
self::$mcrypt = null;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user