mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-26 00:29:38 +01:00
new default eTemplate request storate in EGroupware cache with an expiration time of 4 hours.
Benefit over old default is, that we get a short etemplate_exec_id giving better performance for ajax requests. Drawback is currently poor garbadge collection only removing requests not used in 4 hours or which call egw_framework::window_close on server-side. We can improve garbadge-collection by binding to window on(before)unload sending a (synchronious) request to server to remove concerned eT2 request.
This commit is contained in:
parent
f9c2c73432
commit
b56175a0f4
@ -51,11 +51,7 @@ class etemplate_new extends etemplate_widget_template
|
||||
if ($name) $this->read($name,$template='default',$lang='default',$group=0,$version='',$load_via);
|
||||
|
||||
// generate new etemplate request object, if not already existing
|
||||
if(!self::$request)
|
||||
{
|
||||
self::$request = etemplate_request::read();
|
||||
self::$request->content = array();
|
||||
}
|
||||
if(!isset(self::$request)) self::$request = etemplate_request::read();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -294,6 +290,10 @@ class etemplate_new extends etemplate_widget_template
|
||||
self::$response->generic('et2_validation_error', self::$validation_errors);
|
||||
exit;
|
||||
}
|
||||
|
||||
// tell request call to remove request, if it is not modified eg. by call to exec in callback
|
||||
self::$request->remove_if_not_modified();
|
||||
|
||||
//error_log(__METHOD__."(,".array2string($content).')');
|
||||
//error_log(' validated='.array2string($validated));
|
||||
$content = ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated));
|
||||
@ -346,6 +346,9 @@ class etemplate_new extends etemplate_widget_template
|
||||
error_log(__METHOD__."(,".array2string($content).')');
|
||||
error_log(' validated='.array2string($validated));
|
||||
|
||||
// tell request call to remove request, if it is not modified eg. by call to exec in callback
|
||||
self::$request->remove_if_not_modified();
|
||||
|
||||
return ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated));
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* @subpackage api
|
||||
* @link http://www.egroupware.org
|
||||
* @author Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @copyright (c) 2007-13 by Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @copyright (c) 2007-14 by Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
@ -64,6 +64,8 @@
|
||||
* }
|
||||
*
|
||||
* For an example look in link_widget::ajax_search()
|
||||
*
|
||||
* @property-read boolean $data_modified true if data was modified and therefore needs saving
|
||||
*/
|
||||
class etemplate_request
|
||||
{
|
||||
@ -79,6 +81,12 @@ class etemplate_request
|
||||
* @var boolean
|
||||
*/
|
||||
protected $data_modified=false;
|
||||
/**
|
||||
* Flag that stored data should be removed by destructor, if not modified.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $remove_if_not_modified=false;
|
||||
/**
|
||||
* mcrypt resource
|
||||
*
|
||||
@ -105,6 +113,9 @@ class etemplate_request
|
||||
/**
|
||||
* Factory method to get a new request object or the one for an existing request
|
||||
*
|
||||
* New default is to use egw_cache to store requests and no longer request or
|
||||
* session documented below:
|
||||
*
|
||||
* 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
|
||||
@ -117,42 +128,58 @@ class etemplate_request
|
||||
{
|
||||
if (is_null(self::$request_class))
|
||||
{
|
||||
// new default to use egw_cache to store requests
|
||||
self::$request_class = 'etemplate_request_cache';
|
||||
/* old default to use request if mcrypt and gzcompress are available and session if not
|
||||
self::$request_class = check_load_extension('mcrypt') && function_exists('gzcompress') &&
|
||||
self::init_crypt() ? __CLASS__ : 'etemplate_request_session';
|
||||
*/
|
||||
}
|
||||
if (self::$request_class != __CLASS__)
|
||||
{
|
||||
return call_user_func(array(self::$request_class,'read'),$id);
|
||||
$request = call_user_func(self::$request_class.'::read', $id);
|
||||
}
|
||||
$request = new etemplate_request();
|
||||
|
||||
if (!is_null($id))
|
||||
else
|
||||
{
|
||||
$id = base64_decode($id);
|
||||
$request = new etemplate_request();
|
||||
|
||||
// decrypt the data if available
|
||||
if (self::init_crypt())
|
||||
if (!is_null($id))
|
||||
{
|
||||
$id = 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);
|
||||
$id = base64_decode($id);
|
||||
|
||||
if (!$request->data)
|
||||
{
|
||||
error_log(__METHOD__."() id not valid!");
|
||||
return false;
|
||||
// decrypt the data if available
|
||||
if (self::init_crypt())
|
||||
{
|
||||
$id = 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!");
|
||||
$request = false;
|
||||
}
|
||||
//error_log(__METHOD__."() size of request = ".bytes($id));
|
||||
}
|
||||
//error_log(__METHOD__."() size of request = ".bytes($id));
|
||||
}
|
||||
if (!$request) // eT2 request/session expired
|
||||
{
|
||||
// redirect to index-url of app
|
||||
error_log(__METHOD__."('$id', ...) eT2 request not found --> redirect to index-url");
|
||||
list($app) = explode('.', $_GET['menuaction']);
|
||||
$index_url = isset($GLOBALS['egw_info']['apps'][$app]['index']) ?
|
||||
'/index.php?menuaction='.$GLOBALS['egw_info']['apps'][$app]['index'] : '/'.$app.'/index.php';
|
||||
egw_framework::redirect_link($index_url);
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
@ -174,14 +201,14 @@ class etemplate_request
|
||||
*/
|
||||
public function &id()
|
||||
{
|
||||
$id = serialize($this->data);
|
||||
$data = serialize($this->data);
|
||||
|
||||
// compress the data if available
|
||||
if (self::$compression_level && function_exists('gzcompress'))
|
||||
{
|
||||
//$len_uncompressed = bytes($id);
|
||||
//$time = microtime(true);
|
||||
$id = gzcompress($id,self::$compression_level);
|
||||
$data = gzcompress($data, 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");
|
||||
@ -189,9 +216,9 @@ class etemplate_request
|
||||
// encrypt the data if available
|
||||
if (self::init_crypt())
|
||||
{
|
||||
$id = mcrypt_generic(self::$mcrypt,$id);
|
||||
$data = mcrypt_generic(self::$mcrypt, $data);
|
||||
}
|
||||
$id = base64_encode($id);
|
||||
$id = base64_encode($data);
|
||||
|
||||
//error_log(__METHOD__."() #$this->id: size of request = ".bytes($id));//.", id='$id'");
|
||||
//self::debug();
|
||||
@ -293,6 +320,7 @@ class etemplate_request
|
||||
if ($this->data[$var] !== $val)
|
||||
{
|
||||
$this->data[$var] = $val;
|
||||
//error_log(__METHOD__."('$var', ...) data of id=$this->id changed ...");
|
||||
$this->data_modified = true;
|
||||
}
|
||||
}
|
||||
@ -305,6 +333,8 @@ class etemplate_request
|
||||
*/
|
||||
public function &__get($var)
|
||||
{
|
||||
if ($var == 'data_modified') return $this->data_modified;
|
||||
|
||||
return $this->data[$var];
|
||||
}
|
||||
|
||||
@ -416,4 +446,14 @@ class etemplate_request
|
||||
self::$mcrypt = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark request as to destroy, if it does not get modified before destructor is called
|
||||
*
|
||||
* If that function is called, request is removed from storage, further modification will work!
|
||||
*/
|
||||
public function remove_if_not_modified()
|
||||
{
|
||||
$this->remove_if_not_modified = true;
|
||||
}
|
||||
}
|
||||
|
147
etemplate/inc/class.etemplate_request_cache.inc.php
Normal file
147
etemplate/inc/class.etemplate_request_cache.inc.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* eGroupWare - eTemplate request object storing the data in EGroupware instance cache
|
||||
*
|
||||
* @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) 2014 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 EGroupware tree cache with a given fixed expiration time.
|
||||
* We use tree cache with install-id as part of key to not loose all requests, if admin
|
||||
* clears instance cache (Admin >> Clear cache and register hooks)!
|
||||
*
|
||||
* To enable the use of this handler, you have to set (in etemplate/inc/class.etemplate_request.inc.php):
|
||||
*
|
||||
* etemplate_request::$request_class = 'etemplate_request_cache';
|
||||
*
|
||||
* 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 transmit 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_cache extends etemplate_request
|
||||
{
|
||||
/**
|
||||
* Expiration time of 4 hours
|
||||
*/
|
||||
const EXPIRATION = 7200;
|
||||
|
||||
/**
|
||||
* request id
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Private constructor to force the instancation of this class only via it's static factory method read
|
||||
*
|
||||
* @param string $_id
|
||||
*/
|
||||
private function __construct($_id=null)
|
||||
{
|
||||
$this->id = $_id ? $_id : self::request_id();
|
||||
//error_log(__METHOD__."($_id) this->id=$this->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_cache($id);
|
||||
|
||||
if (!is_null($id))
|
||||
{
|
||||
//error_log(__METHOD__."() reading $id");
|
||||
if (!($request->data = egw_cache::getTree($GLOBALS['egw_info']['server']['install_id'].'_etemplate', $id)))
|
||||
{
|
||||
error_log("Error reading etemplate request data for id=$id!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//error_log(__METHOD__."(id=$id) returning ".array2string($request));
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a new unique request-id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
static function request_id()
|
||||
{
|
||||
return uniqid($GLOBALS['egw_info']['flags']['currentapp'].'_'.$GLOBALS['egw_info']['user']['account_lid'].'_',true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->remove_if_not_modified && !$this->data_modified && isset($this->data['last_saved']))
|
||||
{
|
||||
//error_log(__METHOD__."() destroying $this->id");
|
||||
egw_cache::unsetTree($GLOBALS['egw_info']['server']['install_id'].'_etemplate', $this->id);
|
||||
}
|
||||
elseif (($this->data_modified ||
|
||||
// if half of expiration time is over, save it anyway, to restart expiration time
|
||||
isset($this->data['last_saved']) && (time()-$this->data['last_saved']) > self::EXPIRATION/2))
|
||||
{
|
||||
//error_log(__METHOD__."() saving $this->id");
|
||||
$this->data['last_saved'] = time();
|
||||
if (!egw_cache::setTree($GLOBALS['egw_info']['server']['install_id'].'_etemplate', $this->id, $this->data, self::EXPIRATION))
|
||||
{
|
||||
error_log("Error storing etemplate request data for id=$this->id!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
* @subpackage api
|
||||
* @link http://www.egroupware.org
|
||||
* @author Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @copyright (c) 2009 by Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @copyright (c) 2009-14 by Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
@ -139,7 +139,13 @@ class etemplate_request_files extends etemplate_request
|
||||
*/
|
||||
function __destruct()
|
||||
{
|
||||
if (!file_put_contents($filename = self::$directory.'/'.$this->id,serialize($this->data)))
|
||||
if ($this->remove_if_not_modified && !$this->data_modified)
|
||||
{
|
||||
//error_log(__METHOD__."() destroying $this->id");
|
||||
@unlink(self::$directory.'/'.$this->id);
|
||||
}
|
||||
elseif (!$this->destroyed && $this->data_modified &&
|
||||
!file_put_contents($filename = self::$directory.'/'.$this->id,serialize($this->data)))
|
||||
{
|
||||
error_log("Error opening '$filename' to store the etemplate request data!");
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* @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>
|
||||
* @copyright (c) 2007-14 by Ralf Becker <RalfBecker@outdoor-training.de>
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
@ -119,8 +119,15 @@ class etemplate_request_session extends etemplate_request
|
||||
*/
|
||||
function __destruct()
|
||||
{
|
||||
if ($this->data_modified) $GLOBALS['egw']->session->appsession($this->id,'etemplate',$this->data);
|
||||
|
||||
if ($this->remove_if_not_modified && !$this->data_modified)
|
||||
{
|
||||
//error_log(__METHOD__."() destroying $this->id");
|
||||
egw_cache::unsetSession('etemplate', $this->id);
|
||||
}
|
||||
elseif (!$this->destroyed && $this->data_modified)
|
||||
{
|
||||
egw_cache::setSession('etemplate', $this->id, $this->data);
|
||||
}
|
||||
if (!$this->garbage_collection_done)
|
||||
{
|
||||
$this->_php4_request_garbage_collection();
|
||||
|
Loading…
Reference in New Issue
Block a user