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:
Ralf Becker 2009-03-16 12:58:24 +00:00
parent 97126e417f
commit 486a32e86d
14 changed files with 1609 additions and 1428 deletions

View File

@ -1,5 +1,5 @@
<?php
/**
/**
* eGroupWare eTemplate Extension - AJAX Select Widget
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@ -10,7 +10,7 @@
* @version $Id$
*/
/**
/**
* AJAX Select Widget
*
* Using AJAX, this widget allows a type-ahead find similar to a ComboBox, where as the user enters information,
@ -21,8 +21,8 @@
* This widget can get data from any function that can provide data to a nextmatch widget.
* This widget is generating html, so it does not work (without an extra implementation) in an other UI
*/
class ajax_select_widget
{
class ajax_select_widget
{
var $public_functions = array(
'pre_process' => True,
'post_process' => True,
@ -220,7 +220,7 @@
$value = $results[0][$extension_data['options']['id_field']];
return true;
} elseif ($count > 1) {
$GLOBALS['egw_info']['etemplate']['validation_errors'][$name] = lang("More than 1 match for '%1'",$value_in['search']);
etemplate::set_validation_error($name,lang("More than 1 match for '%1'",$value_in['search']));
$loop = true;
return false;
} else {
@ -241,19 +241,16 @@
if(!$return) {
$value = $extension_data['old_value'];
$GLOBALS['egw_info']['etemplate']['validation_errors'][$name] = lang('Required');
etemplate::set_validation_error($name,lang('Required'));
$loop = true;
}
// Loop if some other widget wants to loop, or if this is required and they gave no value
$loop = $GLOBALS['egw_info']['etemplate']['loop'] || !$return;
if($this->debug && $loop) {
echo 'Looping...<br />Returning ' . $return . '<br />';
}
return $return;
} else {
$value = $value_in['value'];
$loop = $GLOBALS['egw_info']['etemplate']['loop'] || false;
return true;
}
}
@ -331,5 +328,4 @@
}
return $response->getXML();
}
}
?>
}

View File

@ -4,7 +4,7 @@
*
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker@outdoor-training.de>
* @copyright 2002-8 by RalfBecker@outdoor-training.de
* @copyright 2002-9 by RalfBecker@outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
@ -13,12 +13,10 @@
/**
* Business Object for eTemplates, extending the Storage Object
*
* Not so much so far, as the most logic is still in the UI-class
*/
class boetemplate extends soetemplate
{
var $types = array(
static public $types = array(
'label' => 'Label', // Label $cell['label'] is (to be translated) textual content
'text' => 'Text', // Textfield 1 Line (size = [length][,maxlength])
'int' => 'Integer', // like text, but only numbers (size = [min][,max])
@ -45,7 +43,22 @@ class boetemplate extends soetemplate
'deck' => 'Deck', // a container of elements where only one is visible, size = # of elem.
'passwd' => 'Password' // a text of type password
);
private static $garbage_collection_done;
/**
* Flag if form validation requires to loop
*
* @var boolean
*/
static public $loop = false;
/**
* Request object of the currecntly created request
*
* It's a static variable as etemplates can contain further etemplates (rendered by a different object)
*
* @var etemplate_request
*/
static public $request;
/**
* constructor of class
@ -55,9 +68,9 @@ class boetemplate extends soetemplate
* @param string/array $name name of etemplate or array with name and other keys
* @param string/array $load_via name or array with keys of other etemplate to load in order to get $name
*/
function boetemplate($name='',$load_via='')
function __construct($name='',$load_via='')
{
$this->soetemplate();
parent::__construct();
$tname = &$name;
if (is_array($name))
@ -223,107 +236,6 @@ class boetemplate extends soetemplate
return $Ok;
}
/**
* creates a new appsession-id via microtime()
* @return string
*/
protected static function appsession_id()
{
list($msec,$sec) = explode(' ',microtime());
$time = 100 * $sec + (int)(100 * $msec); // gives precision of 1/100 sec
$id = $GLOBALS['egw_info']['flags']['currentapp'] .':'. $time;
//echo "<p>microtime()=".microtime().", sec=$sec, msec=$msec, id=$id</p>\n";
return $id;
}
/**
* saves content,readonlys,template-keys, ... via the appsession function
*
* 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 appsession 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 array $data the data to save
* @param string $id the id to use or '' to generate a new id
* @return string location-id
*/
protected static function save_appsession($data,$id='')
{
if (!$id)
{
$id = self::appsession_id();
}
$GLOBALS['egw']->session->appsession($id,'etemplate',$data);
if (substr($GLOBALS['egw_info']['server']['sessions_type'],0,4) == 'php4' && !self::$garbage_collection_done)
{
return self::php_session_garbage_collection();
}
return $id;
}
/**
* gets content,readonlys,template-keys, ... back from the appsession function
*
* @param string $id the location-id
* @return array with session-data
*/
protected function get_appsession($id)
{
$data = egw_session::appsession($id,'etemplate');
//echo "boetemplate::get_appsession('$id')"; _debug_array($data);
self::php_session_garbage_collection($id);
return $data;
}
/**
* 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.
*
* @param string $id_used id of session just read by get_appsession to increment the usage counter
*/
static private function php_session_garbage_collection($id_used='')
{
// now we are on php4 sessions and do a bit of garbage collection
$app_sessions =& $_SESSION[egw_session::EGW_APPSESSION_VAR]['etemplate'];
$session_used =& $app_sessions['session_used'];
if ($id_used)
{
//echo "session_used[$id_used]='".$session_used[$id_used]."'<br/>\n";
++$session_used[$id_used]; // count the number of times a session got used
}
self::$garbage_collection_done = true;
if (count($app_sessions) < 20) return $data; // 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($app_sessions) as $id)
{
list($app,$time) = explode(':',$id);
if (!$time) continue; // other data, no session
//echo ++$n.') '.$id.': '.(($now-$time)/100.0)."secs old, used=".$session_used[$id].", size=".egw_vfs::hsize(strlen(serialize($app_sessions[$id])))."<br>\n";
if ($session_used[$id] == 1 && $time < $now - 10*6000 || // session used and older then 10min
$time < $now - 30*6000) // session not used and older then 30min
{
//echo "<p>boetemplate::php_session_garbage_collection('$id_used'): unsetting session '$id' (now=$now)</p>\n";
unset($app_sessions[$id]);
unset($session_used[$id]);
}
}
}
/**
* gets an attribute in a named cell
*
@ -453,6 +365,13 @@ class boetemplate extends soetemplate
$this->set_column_attributes($c,0,!$enable,$path);
}
/**
* Cache for extension objects
*
* @var array
*/
static private $extensions = array();
/**
* trys to load the Extension / Widget-class from the app or etemplate
*
@ -482,12 +401,12 @@ class boetemplate extends soetemplate
if (!file_exists(EGW_SERVER_ROOT."/$app/inc/class.$class.inc.php"))
{
//echo "<p>boetemplate::loadExtension($type) extension not found</p>\n";
return $GLOBALS['egw_info']['etemplate']['extension'][$type] = False;
return self::$extensions[$type] = False;
}
$GLOBALS['egw_info']['etemplate']['extension'][$type] =& CreateObject($app.'.'.$class,$ui='html');
self::$extensions[$type] =& CreateObject($app.'.'.$class,$ui='html');
//echo "<p>boetemplate::loadExtension($type) extension found in App. $app</p>\n";
return $GLOBALS['egw_info']['etemplate']['extension'][$type]->human_name;
return self::$extensions[$type]->human_name;
}
/**
@ -500,8 +419,8 @@ class boetemplate extends soetemplate
*/
protected function haveExtension($type,$function='')
{
return ($GLOBALS['egw_info']['etemplate']['extension'][$type] || $this->loadExtension($type,$ui)) &&
($function == '' || $GLOBALS['egw_info']['etemplate']['extension'][$type]->public_functions[$function]);
return (self::$extensions[$type] || $this->loadExtension($type,$ui)) &&
($function == '' || self::$extensions[$type]->public_functions[$function]);
}
/**
@ -522,11 +441,11 @@ class boetemplate extends soetemplate
}
// only supply extension data for non-readonly widgets or if it's already set
// otherwise lists store >10k unnecessary data in each etemplate-session
if (!($cell['readonly'] || $readonlys && !is_array($readonlys)) || isset($GLOBALS['egw_info']['etemplate']['extension_data'][$name]))
if (!($cell['readonly'] || $readonlys && !is_array($readonlys)) || isset(self::$request->extension_data[$name]))
{
$extension_data =& $GLOBALS['egw_info']['etemplate']['extension_data'][$name];
$extension_data =& self::$request->extension_data[$name];
}
return $GLOBALS['egw_info']['etemplate']['extension'][$type]->pre_process($name,$value,$cell,$readonlys,$extension_data,$this);
return self::$extensions[$type]->pre_process($name,$value,$cell,$readonlys,$extension_data,$this);
}
/**
@ -544,9 +463,9 @@ class boetemplate extends soetemplate
{
return True;
}
return $GLOBALS['egw_info']['etemplate']['extension'][$type]->post_process($name,$value,
$GLOBALS['egw_info']['etemplate']['extension_data'][$name],
$GLOBALS['egw_info']['etemplate']['loop'],$this,$value_in);
return self::$extensions[$type]->post_process($name,$value,
self::$request->extension_data[$name],
self::$loop,$this,$value_in);
}
/**
@ -565,8 +484,8 @@ class boetemplate extends soetemplate
{
return False;
}
return $GLOBALS['egw_info']['etemplate']['extension'][$type]->render($cell,$name,$value,$readonly,
$GLOBALS['egw_info']['etemplate']['extension_data'][$name],$this);
return self::$extensions[$type]->render($cell,$name,$value,$readonly,
self::$request->extension_data[$name],$this);
}
/**
@ -798,12 +717,19 @@ class boetemplate extends soetemplate
}
/**
* stores the etemplate in the cache in phpgw_info
* Cache for already read etemplates
*
* @var array
*/
private static $template_cache = array();
/**
* stores the etemplate in the cache in egw_info
*/
private function store_in_cache()
{
//echo "<p>store_in_cache('$this->name','$this->template','$this->lang','$this->version')</p>\n";
$GLOBALS['egw_info']['etemplate']['cache'][$this->cache_name()] = $this->as_array(1);
self::$template_cache[$this->cache_name()] = $this->as_array(1);
}
/**
@ -812,7 +738,7 @@ class boetemplate extends soetemplate
private function delete_in_cache()
{
//echo "<p>delete_in_cache('$this->name','$this->template','$this->lang','$this->version')</p>\n";
unset($GLOBALS['egw_info']['etemplate']['cache'][$this->cache_name()]);
unset(self::$template_cache[$this->cache_name()]);
}
/**
@ -833,8 +759,8 @@ class boetemplate extends soetemplate
$version = $name['version'];
$name = $name['name'];
}
if (!isset($GLOBALS['egw_info']['etemplate']['cache'][$cname]) ||
!empty($version) && $GLOBALS['egw_info']['etemplate']['cache'][$cname]['version'] != $version)
if (!isset(self::$template_cache[$cname]) ||
!empty($version) && self::$template_cache[$cname]['version'] != $version)
{
//echo " NOT found in cache</p>\n";
return False;
@ -860,7 +786,7 @@ class boetemplate extends soetemplate
//if (is_array($name)) $version = $name['version']; echo "<p>read_from_cache(,,,version='$version'): ";
if ($cname = $this->in_cache($name,$template,$lang,$group))
{
$this->init($GLOBALS['egw_info']['etemplate']['cache'][$cname]);
$this->init(self::$template_cache[$cname]);
return True;
}
@ -956,7 +882,6 @@ class boetemplate extends soetemplate
*/
static function _init_static()
{
self::$garbage_collection_done =& $GLOBALS['egw_info']['etemplate']['garbage_collection_done'];
}
}
boetemplate::_init_static();
@ -997,3 +922,5 @@ if (!function_exists('set_cell_attribute_helper'))
}
}
}
// just in case someone still uses the old var
$GLOBALS['egw_info']['flags']['etemplate']['loop'] =& boetemplate::$loop;

View File

@ -5,7 +5,7 @@
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker@outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @copyright 2002-8 by RalfBecker@outdoor-training.de
* @copyright 2002-9 by RalfBecker@outdoor-training.de
* @package etemplate
* @subpackage extensions
* @version $Id$
@ -231,10 +231,9 @@ class date_widget
}
if ($cell['needed'])
{
$GLOBALS['egw_info']['etemplate']['to_process'][$name] = array(
'type' => 'ext-'.$type,
etemplate::$request->set_to_process($name,'ext-'.$type,array(
'needed' => $cell['needed'],
);
));
}
$tpl =& new etemplate;
$tpl->init('*** generated fields for date','','',0,'',0,0); // make an empty template
@ -539,7 +538,7 @@ class date_widget
}
elseif (!preg_match('/^-?[0-9]*[,.]?[0-9]*'.($extension_data['percent_allowed'] ? '%?' : '').'$/',$value_in))
{
$GLOBALS['egw_info']['etemplate']['validation_errors'][$name] = lang("'%1' is not a valid floatingpoint number !!!",$value_in);
etemplate::set_validation_error($name,lang("'%1' is not a valid floatingpoint number !!!",$value_in));
return false;
}
else
@ -613,8 +612,8 @@ class date_widget
// checking the date is a correct one
if (!checkdate($value['m'],$value['d'],$value['Y']))
{
$GLOBALS['egw_info']['etemplate']['validation_errors'][$name] .= lang("'%1' is not a valid date !!!",
$GLOBALS['egw']->common->dateformatorder($value['Y'],$value['m'],$value['d'],true));
etemplate::set_validation_error($name,lang("'%1' is not a valid date !!!",
common::dateformatorder($value['Y'],$value['m'],$value['d'],true)));
}
$data_format = $extension_data['data_format'];
if (empty($data_format))

View File

@ -430,7 +430,7 @@ class editor
$additional = array();
if ($app == 'etemplate')
{
$additional = $this->etemplate->types + $this->extensions + $this->aligns + $this->valigns +
$additional = etemplate::$types + $this->extensions + $this->aligns + $this->valigns +
$this->edit_menu + $this->box_menu + $this->row_menu + $this->column_menu + $this->onclick_types + $this->onchange_types;
}
else // try to call the writeLangFile function of the app's ui-layer
@ -1360,7 +1360,7 @@ class editor
$GLOBALS['egw_info']['flags']['java_script'] = "<script>window.focus();</script>\n";
$GLOBALS['egw_info']['flags']['app_header'] = lang('Editable Templates - Editor');
$editor->exec('etemplate.editor.widget',$content,array(
'type' => array_merge($this->etemplate->types,$this->extensions),
'type' => array_merge(etemplate::$types,$this->extensions),
'align' => &$this->aligns,
'valign' => &$this->valigns,
'part' => $allowed_parts,

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,53 @@
<?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
* Class to represent the persitent information of an eTemplate request
*
* The information is stored in the users session
* 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.
*
* There are two ways to instanciate a request object:
* The key used to encrypt the request can be set in header.inc.php by setting
*
* a) a new request:
* $GLOBALS['egw_info']['server']['etemplate_form_key'] = 'something secret';
*
* $request = new etemplate_request(); $id = $request->id();
* 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::read($id)))
* 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()
*/
@ -40,31 +58,99 @@ 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 (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)
{
if (!$id) $id = self::request_id();
$this->id = $id;
}
/**
@ -72,27 +158,30 @@ class etemplate_request
*
* @return string
*/
function id()
public function &id()
{
return $this->id;
}
$id = serialize($this->data);
/**
* 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)
// compress the data if available
if (self::$compression_level && function_exists('gzcompress'))
{
if (!($data = $GLOBALS['egw']->session->appsession($id,'etemplate')))
{
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;
// encrypt the data if available
if (self::init_crypt())
{
$id = mcrypt_generic(self::$mcrypt,$id);
}
$id = base64_encode($id);
return $request;
//error_log(__METHOD__."() #$this->id: size of request = ".bytes($id));//.", id='$id'");
//self::debug();
return $id;
}
/**
@ -112,6 +201,29 @@ class etemplate_request
$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
*
@ -151,7 +263,7 @@ class etemplate_request
* @param string $var
* @param mixed $val
*/
function __set($var,$val)
public function __set($var,$val)
{
if ($this->data[$var] !== $val)
{
@ -166,83 +278,105 @@ class etemplate_request
* @param string $var
* @return mixed
*/
function __get($var)
public function &__get($var)
{
return $this->data[$var];
}
/**
* creates a new request-id via microtime()
* Get the names / keys of existing variables
*
* @return string
* @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
*/
public function debug($min_share=1.0,$dump_share=25.0)
{
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)
{
$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)
{
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>&nbsp;- {$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 ($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)
if (self::$mcrypt)
{
$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
{
//echo "<p>boetemplate::php4_session_garbage_collection('$id_used'): unsetting session '$id' (now=$now)</p>\n";
unset($appsessions[$id]);
unset($session_used[$id]);
}
mcrypt_generic_deinit(self::$mcrypt);
self::$mcrypt = null;
}
}
}

View File

@ -0,0 +1,171 @@
<?php
/**
* eGroupWare - eTemplate request object storing the data in the session
*
* @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-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, which causes the session to constantly grow.
* We implement here some garbadge collection to remove old requests.
*
* The request object should be instancated only via the factory method etemplate_request::read($id=null)
*
* $request = etemplate_request::read();
*
* // add request data
*
* $id = $request->id();
*
* b) open or modify an existing request:
*
* if (!($request = etemplate_request::read($id)))
* {
* // request not found
* }
*
* 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_session extends etemplate_request
{
/**
* request id
*
* @var string
*/
protected $id;
/**
* Private constructor to force the instancation of this class only via it's static factory method read
*
* @param array $id
*/
private function __construct($id=null)
{
if (!$id) $id = self::request_id();
$this->id = $id;
}
/**
* return the id of this request
*
* @return string
*/
public function id()
{
//error_log(__METHOD__."() id=$this->id");
return $this->id;
}
/**
* Factory method to get a new request object or the one for an existing request
*
* @param string $id=null
* @return etemplate_request|boolean the object or false if $id is not found
*/
static function read($id=null)
{
$request = new etemplate_request_session($id);
if (!is_null($id))
{
if (!($data = $GLOBALS['egw']->session->appsession($id,'etemplate')))
{
return false; // request not found
}
$request->data = $data;
}
//error_log(__METHOD__."(id=$id");
return $request;
}
/**
* creates a new request-id via microtime()
*
* @return string
*/
static function request_id()
{
$time = (int) (100 * microtime(true)); // gives precision of 1/100 sec
$id = $GLOBALS['egw_info']['flags']['currentapp'] .':'. $time;
return $id;
}
/**
* saves content,readonlys,template-keys, ... via eGW's appsession function
*
* 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.
*/
function __destruct()
{
if ($this->data_modified) $GLOBALS['egw']->session->appsession($this->id,'etemplate',$this->data);
if (!$this->garbage_collection_done)
{
$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.
*/
protected function _php4_request_garbage_collection()
{
// now we are on php4 sessions and do a bit of garbage collection
$appsessions =& $_SESSION[egw_session::EGW_APPSESSION_VAR]['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
$now = (int) (100 * microtime(true)); // 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 - 30*6000) // session not used and older then 30min
{
//echo "<p>boetemplate::php4_session_garbage_collection('$id_used'): unsetting session '$id' (now=$now)</p>\n";
unset($appsessions[$id]);
unset($session_used[$id]);
}
}
}
}

View File

@ -7,6 +7,7 @@
* @subpackage extensions
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker@outdoor-training.de>
* @copyright 2002-9 by RalfBecker@outdoor-training.de
* @version $Id$
*/
@ -114,6 +115,7 @@ class link_widget
// readonly ==> omit the whole widget
$value = '';
$cell = $tmpl->empty_cell();
$extension_data = null;
return;
}
if (!is_array($value) && in_array($type,array('link-to','link-list','link-add')))
@ -133,6 +135,7 @@ class link_widget
{
case 'link':
$cell['readonly'] = True; // set it readonly to NOT call our post_process function
$extension_data = null;
$cell['no_lang'] = 1;
$link = $target = $popup = '';
if (!is_array($value) && $value && isset($GLOBALS['egw_info']['apps'][$cell['size']]))
@ -152,7 +155,7 @@ class link_widget
$link .= '&'.$var.'='.$val;
}
if (!($popup = egw_link::is_popup($value['app'],'view')) &&
$GLOBALS['egw_info']['etemplate']['output_mode'] == 2) // we are in a popup
etemplate::$request->output_mode == 2) // we are in a popup
{
$target = '_blank';
}
@ -166,6 +169,7 @@ class link_widget
{
$cell = $tmpl->empty_cell();
$cell['readonly'] = True; // set it readonly to NOT call our post_process function
$extension_data = null;
return;
}
$cell['type'] = 'label';
@ -208,7 +212,7 @@ class link_widget
list($w,$h) = explode('x',$popup);
$options = ' onclick="window.open(this,this.target,\'width='.(int)$w.',height='.(int)$h.',location=no,menubar=no,toolbar=no,scrollbars=yes,status=yes\'); return false;"';
}
elseif ($GLOBALS['egw_info']['etemplate']['output_mode'] == 2 || // we are in a popup
elseif (etemplate::$request->output_mode == 2 || // we are in a popup
$link['app'] == egw_link::VFS_APPNAME) // or it's a link to an attachment
{
$options = ' target="_blank"';
@ -220,6 +224,7 @@ class link_widget
}
$cell['type'] = 'html';
$cell['readonly'] = True; // set it readonly to NOT call our post_process function
$extension_data = null;
$value = $str;
return True;
@ -292,7 +297,7 @@ class link_widget
{
$value[$row]['view'] = egw_link::view($link['app'],$link['id'],$link);
if (!($value[$row]['popup'] = egw_link::is_popup($link['app'],'view')) &&
$GLOBALS['egw_info']['etemplate']['output_mode'] == 2) // we are in a popup
etemplate::$request->output_mode == 2) // we are in a popup
{
$value[$row]['target'] = '_blank'; // we create a new window as the linked page is no popup
}
@ -656,12 +661,17 @@ class link_widget
$response->addScript($script);
}
// store new allowed id's in the eT request
if ($etemplate_exec_id && ($et_request = etemplate_request::read($etemplate_exec_id)))
if ($etemplate_exec_id && ($request = etemplate_request::read($etemplate_exec_id)))
{
$data = $et_request->get_to_process($id_res);
$data = $request->get_to_process($id_res);
//error_log($id_res.'='.array2string($data));
$data['allowed'] = $found ? array_keys($found) : array();
$et_request->set_to_process($id_res,$data);
$request->set_to_process($id_res,$data);
// update id, if request changed it (happens if the request data is stored direct in the form)
if ($etemplate_exec_id != ($new_id = $request->id()))
{
$response->addAssign('etemplate_exec_id','value',$new_id);
}
}
return $response->getXML();
}

View File

@ -3,7 +3,7 @@
* eGroupWare eTemplate Extension - Nextmatch Widget
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @copyright 2002-8 by RalfBecker@outdoor-training.de
* @copyright 2002-9 by RalfBecker@outdoor-training.de
* @package etemplate
* @subpackage extensions
* @link http://www.egroupware.org
@ -11,8 +11,6 @@
* @version $Id$
*/
require_once(EGW_INCLUDE_ROOT. '/etemplate/inc/class.etemplate.inc.php');
/**
* eTemplate Extension: Widget that show only a certain number of data-rows and allows to modifiy the rows shown (scroll).
*
@ -313,7 +311,7 @@ class nextmatch_widget
$rows = array();
if (!is_object($obj) || !method_exists($obj,$method))
{
$GLOBALS['egw_info']['etemplate']['validation_errors'][$name] = "nextmatch_widget::pre_process($cell[name]): '$value[get_rows]' is no valid method !!!";
etemplate::set_validation_error($name,"nextmatch_widget::pre_process($cell[name]): '$value[get_rows]' is no valid method !!!");
}
else
{
@ -393,7 +391,7 @@ class nextmatch_widget
{
// make each letter internally behave like a button
$form_name = $name.'[searchletter]['.($key === 'all' ? $key : $letter).']';
$GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = 'button';
etemplate::$request->set_to_process($form_name,'button');
if (!$key) $letterbox =& $lettersearch[1]; // to re-use the first child
$letterbox = etemplate::empty_cell('label',$letter,array(
@ -402,13 +400,13 @@ class nextmatch_widget
$key === 'all' && !$value['searchletter'] ? '_active' : ''),
'no_lang' => 2,
'align' => $key == 'all' ? 'right' : '',
'onclick' => "return submitit($tmpl->name_form,'$form_name');",
'onclick' => 'return submitit('.etemplate::$name_form.",'$form_name');",
));
// if not the first (re-used) child, add it to the parent
if ($key) etemplate::add_child($lettersearch,$letterbox);
unset($letterbox);
}
//_debug_array($GLOBALS['egw_info']['etemplate']['to_process']);
//_debug_array(etemplate::$request->to_process);
}
if(isset($value['no_search'])) $value['no_start_search'] = $value['no_search'];
foreach(array('no_cat'=>'cat_id','no_filter'=>'filter','no_filter2'=>'filter2', 'no_search' => 'search', 'no_start_search' => 'start_search' ) as $val_name => $cell_name)
@ -896,7 +894,7 @@ class nextmatch_widget
}
if (!is_object($obj) || !method_exists($obj,$method))
{
$GLOBALS['egw_info']['etemplate']['validation_errors'][$name] = "nextmatch_widget::pre_process($cell[name]): '$value[get_rows]' is no valid method !!!";
etemplate::set_validation_error($name,"nextmatch_widget::pre_process($cell[name]): '$value[get_rows]' is no valid method !!!");
return false;
}
$this->charset = $this->charset_out = $GLOBALS['egw']->translation->charset();
@ -922,7 +920,7 @@ class nextmatch_widget
}
if ($export_limit && (!is_numeric($export_limit) || $export_limit < $total))
{
$GLOBALS['egw_info']['etemplate']['validation_errors'][$name] = lang('You are not allowed to export more then %1 entries!',$export_limit);
etemplate::set_validation_error($name,lang('You are not allowed to export more then %1 entries!',$export_limit));
return false;
}
if (!isset($value['no_csv_support'])) $value['no_csv_support'] = !is_array($value['csv_fields']);

View File

@ -1,27 +1,24 @@
<?php
/**
/**
* eGroupWare eTemplate Extension - Select Widgets
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage extensions
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker@outdoor-training.de>
* @copyright 2002-9 by RalfBecker@outdoor-training.de
* @version $Id$
*/
/**
/**
* eTemplate Extension: several select-boxes with predefined eGW specific content
*
* This widgets replaces the not longer exiting phpgwapi.sbox class. The widgets are independent of the UI,
* as they only uses etemplate-widgets and therefor have no render-function.
*
* @package etemplate
* @subpackage extensions
* @author RalfBecker-AT-outdoor-training.de
* @license GPL
* as they only uses etemplate-widgets and therefore have no render-function.
*/
class select_widget
{
class select_widget
{
/**
* exported methods of this class
* @var array
@ -254,7 +251,7 @@
}
$cell['type'] = 'html';
$cell['size'] = ''; // is interpreted as link otherwise
$GLOBALS['egw_info']['etemplate']['to_process'][$name] = 'select';
etemplate::$request->set_to_process($name,'select');
break;
}
$cell['no_lang'] = True;
@ -358,7 +355,7 @@
}
if (!$readonly)
{
$GLOBALS['egw_info']['etemplate']['to_process'][$name] = 'ext-select-dow';
etemplate::$request->set_to_process($name,'ext-select-dow');
}
$cell['size'] = $rows.($type2 ? ','.$type2 : '');
break;
@ -552,4 +549,4 @@
//echo "<p>select_widget::post_process('$name',,'$extension_data',,,'$value_in'): value='$value', is_null(value)=".(int)is_null($value)."</p>\n";
return true;
}
}
}

View File

@ -4,7 +4,7 @@
*
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker@outdoor-training.de>
* @copyright 2002-8 by RalfBecker@outdoor-training.de
* @copyright 2002-9 by RalfBecker@outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
@ -30,20 +30,20 @@
*/
class soetemplate
{
var $debug; // =1 show some debug-messages, = 'app.name' show messages only for eTemplate 'app.name'
var $name; // name of the template, e.g. 'infolog.edit'
var $template; // '' = default (not 'default')
var $lang; // '' if general template else language short, e.g. 'de'
var $group; // 0 = not specific else groupId or if < 0 userId
var $version; // like 0.9.13.001
var $style; // embeded CSS style-sheet
var $children; // array with children
var $data; // depricated: first grid of the children
var $size; // depricated: witdh,height,border of first grid
public $debug; // =1 show some debug-messages, = 'app.name' show messages only for eTemplate 'app.name'
public $name; // name of the template, e.g. 'infolog.edit'
public $template; // '' = default (not 'default')
public $lang; // '' if general template else language short, e.g. 'de'
public $group; // 0 = not specific else groupId or if < 0 userId
public $version; // like 0.9.13.001
public $style; // embeded CSS style-sheet
public $children; // array with children
public $data; // depricated: first grid of the children
public $size; // depricated: witdh,height,border of first grid
/**
* private reference to the global db-object
*
* @var egw_db
* @public egw_db
*/
private $db;
/**
@ -68,7 +68,7 @@ class soetemplate
* widgets that contain other widgets, eg. for tree_walk method
* widget-type is the key, the value specifys how the children are stored.
*
* @var array
* @public array
*/
static $widgets_with_children = array(
'template' => 'template',
@ -94,9 +94,9 @@ class soetemplate
* @param int $cols initial size of the template, default 1, only used if no name given !!!
* @return soetemplate
*/
function soetemplate($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1)
function __construct($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1)
{
if (is_object($GLOBALS['egw']->db))
if (isset($GLOBALS['egw']->db))
{
$this->db = $GLOBALS['egw']->db;
}
@ -674,6 +674,8 @@ class soetemplate
$this->set_rows_cols();
}
static private $compress_array_recursion = array();
/**
* all empty values and objects in the array got unset (to save space in the db )
*
@ -686,6 +688,8 @@ class soetemplate
*/
function compress_array($arr,$remove_objs=false)
{
static $recursion = array();
if (!is_array($arr))
{
return $arr;
@ -702,7 +706,7 @@ class soetemplate
}
elseif (!$remove_objs && $key == 'obj' && is_object($val) && method_exists($val,'as_array') &&
// this test prevents an infinit recursion of templates calling itself, atm. etemplate.editor.new
$GLOBALS['egw_info']['etemplate']['as_array'][$this->name]++ < 2)
self::$compress_array_recursion[$this->name]++ < 2)
{
$arr['obj'] = $val->as_array(2);
}
@ -1077,6 +1081,8 @@ class soetemplate
return lang("%1 new eTemplates imported for Application '%2'",$n,$app);
}
static private $import_tested = array();
/**
* test if new template-import necessary for app and does the import
*
@ -1090,11 +1096,11 @@ class soetemplate
{
list($app) = explode('.',$app);
if (!$app || $GLOBALS['egw_info']['etemplate']['import_tested'][$app])
if (!$app || self::$import_tested[$app])
{
return ''; // ensure test is done only once per call and app
}
$GLOBALS['egw_info']['etemplate']['import_tested'][$app] = True; // need to be done before new ...
self::$import_tested[$app] = True; // need to be done before new ...
$path = EGW_SERVER_ROOT."/$app/setup/etemplates.inc.php";

View File

@ -61,7 +61,7 @@
if (!$cell['onchange']) // onchange allows to use the old behavior (submit for each new tab)
{
$dom_enabled = isset($GLOBALS['egw_info']['etemplate']['dom_enabled']) ? $GLOBALS['egw_info']['etemplate']['dom_enabled'] : true;
$dom_enabled = true;
}
$labels = explode('|',$cell['label']);
$helps = explode('|',$cell['help']);

View File

@ -24,7 +24,7 @@
* @param $debug enables debug messages: 0=no, 1=calls to show and process_show, 2=content of process_show
* @param 3=calls to show_cell OR template- or cell-type name
*/
class etemplate extends boetemplate
class gtk_etemplate extends boetemplate
{
var $debug;//='etemplate.editor.edit'; // 1=calls to show and process_show, 2=content after process_show,
// 3=calls to show_cell and process_show_cell, or template-name or cell-type

View File

@ -508,7 +508,7 @@
// save tmpl to the cache, as the file may contain more then one tmpl
$cname = ($etempl->template == '' ? 'default' : $etempl->template).'/'.$etempl->name.
($etempl->lang == '' ? '' : '.'.$etempl->lang);
$GLOBALS['egw_info']['etemplate']['cache'][$cname] = $etempl->as_array(1);
boetemplate::$template_cache[$cname] = $etempl->as_array(1);
if ($this->debug)
{
$etempl->echo_tmpl();