* @copyright 2002-16 by RalfBecker@outdoor-training.de
* @version $Id$
*/
namespace EGroupware\Api;
/**
* New eTemplate serverside contains:
* - main server methods like read, exec
* -
*
* Not longer available methods:
* - set_(row|column)_attributes modifies template on run-time, was only used internally by etemplate itself
* - disable_(row|column) dto.
*/
class Etemplate extends Etemplate\Widget\Template
{
/**
* Are we running as sitemgr module or not
*
* @public boolean
*/
public $sitemgr=false;
/**
* Tell egw framework it's ok to call this
*/
public $public_functions = array(
'process_exec' => true
);
/**
* constructor of etemplate class, reads an eTemplate if $name is given
*
* @param string $name of etemplate or array with name and other keys
* @param string|array $load_via with keys of other etemplate to load in order to get $name
*/
function __construct($name='',$load_via='')
{
// we do NOT call parent consturctor, as we only want to enherit it's (static) methods
if (false) parent::__construct ($name); // satisfy IDE, as we dont call parent constructor
$this->sitemgr = isset($GLOBALS['Common_BO']) && is_object($GLOBALS['Common_BO']);
if ($name) $this->read($name,$template='default','default',0,'',$load_via);
// generate new etemplate request object, if not already existing
if(!isset(self::$request)) self::$request = Etemplate\Request::read();
}
/**
* Abstracts a html-location-header call
*
* In other UI's than html this needs to call the methode, defined by menuaction or
* open a browser-window for any other links.
*
* @param string|array $params url or array with get-params incl. menuaction
*/
static function location($params='')
{
Framework::redirect_link(is_array($params) ? '/index.php' : $params,
is_array($params) ? $params : '');
}
/**
* Generates a Dialog from an eTemplate - abstract the UI-layer
*
* This is the only function an application should use, all other are INTERNAL and
* do NOT abstract the UI-layer, because they return HTML.
* Generates a webpage with a form from the template and puts process_exec in the
* form as submit-url to call process_show for the template before it
* ExecuteMethod's the given $method of the caller.
*
* @param string $method Methode (e.g. 'etemplate.editor.edit') to be called if form is submitted
* @param array $content with content to fill the input-fields of template, eg. the text-field
* with name 'name' gets its content from $content['name']
* @param $sel_options array or arrays with the options for each select-field, keys are the
* field-names, eg. array('name' => array(1 => 'one',2 => 'two')) set the
* options for field 'name'. ($content['options-name'] is possible too !!!)
* @param array $readonlys with field-names as keys for fields with should be readonly
* (eg. to implement ACL grants on field-level or to remove buttons not applicable)
* @param array $preserv with vars which should be transported to the $method-call (eg. an id) array('id' => $id) sets $_POST['id'] for the $method-call
* @param int $output_mode
* 0 = echo incl. navbar
* 1 = return html
* -1 = first time return html, after use 0 (echo html incl. navbar), eg. for home
* 2 = popup or show in dialog (echo without navbar)
* 3 = return eGW independent html site
* 4 = json response
* 5 = return Request object
* @param string $ignore_validation if not empty regular expression for validation-errors to ignore
* @param array $changes change made in the last call if looping, only used internaly by process_exec
* @return string|Etemplate\Request html for $output_mode == 1, Etemplate\Request for $output_mode == 5, else nothing
*/
function exec($method,array $content,array $sel_options=null,array $readonlys=null,array $preserv=null,$output_mode=0,$ignore_validation='',array $changes=null)
{
$hook_data = Hooks::process(
array('hook_location' => 'etemplate2_before_exec') +
array('location_name' => $this->name) +
array('location_object' => &$this) +
array('sel_options' => $sel_options) +
$content
);
foreach($hook_data as $extras)
{
if (!$extras) continue;
foreach(count(array_filter(array_keys($extras), 'is_int')) ? $extras : array($extras) as $extra)
{
if (!empty($extra['data']) && is_array($extra['data']))
{
$content = self::complete_array_merge($content, $extra['data']);
}
if (!empty($extra['preserve']) && is_array($extra['preserve']))
{
$preserv = self::complete_array_merge($preserv, $extra['preserve']);
}
if (!empty($extra['readonlys']) && is_array($extra['readonlys']))
{
$readonlys = self::complete_array_merge($readonlys, $extra['readonlys']);
}
if (!empty($extra['sel_options']) && is_array($extra['sel_options']))
{
$sel_options = self::complete_array_merge($sel_options, $extra['sel_options']);
}
}
}
unset($hook_data);
// Include the etemplate2 javascript code
//Framework::includeJS('etemplate', 'etemplate2', 'api');
if (!$this->rel_path) throw new Exception\AssertionFailed("No (valid) template '$this->name' found!");
if ($output_mode == 4)
{
$output_mode = 0;
self::$response = Json\Response::get();
}
self::$request->output_mode = $output_mode; // let extensions "know" they are run eg. in a popup
self::$request->content = self::$cont = $content;
self::$request->changes = $changes;
self::$request->sel_options = is_array($sel_options) ? self::fix_sel_options($sel_options) : array();
self::$request->readonlys = $readonlys ? $readonlys : array();
self::$request->preserv = $preserv ? $preserv : array();
self::$request->method = $method;
self::$request->ignore_validation = $ignore_validation;
if (self::$request->output_mode == -1) self::$request->output_mode = 0;
self::$request->template = $this->as_array();
if (empty($this->name)) throw new Exception\AssertionFailed("Template name is not set '$this->name' !");
// instanciate template to fill self::$request->sel_options for select-* widgets
// not sure if we want to handle it this way, thought otherwise we will have a few ajax request for each dialog fetching predefined selectboxes
$template = self::instance($this->name, $this->template_set, $this->version, $this->load_via);
if (!$template) throw new Exception\AssertionFailed("Template $this->name not instanciable! Maybe you forgot to rename template id.");
$this->children = array($template);
$template->run('beforeSendToClient', array('', array('cont'=>$content)));
if ($output_mode == 5)
{
$request = self::$request;
self::$request = null;
return $request;
}
// some apps (eg. InfoLog) set app_header only in get_rows depending on filter settings
self::$request->app_header = $GLOBALS['egw_info']['flags']['app_header'] ?? null;
// compile required translations translations
$currentapp = $GLOBALS['egw_info']['flags']['currentapp'];
$langRequire = array('common' => array(), 'etemplate' => array()); // keep that order
foreach(Translation::$loaded_apps as $l_app => $lang)
{
if (!in_array($l_app, array($currentapp, 'custom')))
{
$langRequire[$l_app] = array('app' => $l_app, 'lang' => $lang, 'etag' => Translation::etag($l_app, $lang));
}
}
foreach(array($currentapp, 'custom') as $l_app)
{
if (isset(Translation::$loaded_apps[$l_app]))
{
$langRequire[$l_app] = array('app' => $l_app, 'lang' => Translation::$loaded_apps[$l_app], 'etag' => Translation::etag($l_app, Translation::$loaded_apps[$l_app]));
}
}
$data = array(
'etemplate_exec_id' => self::$request->id(),
'app_header' => self::$request->app_header,
'content' => self::$request->content,
'sel_options' => self::$request->sel_options,
'readonlys' => self::$request->readonlys,
'modifications' => self::$request->modifications,
'validation_errors' => self::$validation_errors,
'langRequire' => array_values($langRequire),
'currentapp' => $currentapp,
'menuaction' => $method.(strpos($method, '::') !== false ? '::' : '.').'et2_process',
);
if (!empty($data['content']['nm']['rows']) && is_array($data['content']['nm']['rows']))
{
// Deep copy rows so we don't lose them when request is set to null
// (some content by reference)
$data['content']['nm'] = self::deep_copy($data['content']['nm']);
}
// Info required to load the etemplate client-side
$dom_id = str_replace('.','-',$this->dom_id);
$load_array = array(
'name' => $this->name,
'url' => self::rel2url($this->rel_path),
'data' => $data,
'DOMNodeID' => $dom_id,
);
if (self::$response) // call is within an ajax event / form submit
{
//error_log("Ajax " . __LINE__);
self::$response->generic('et2_load', $load_array+Framework::get_extra());
Framework::clear_extra(); // to not send/set it twice for multiple etemplates (eg. CRM view)
}
else // first call
{
// check if application of template has a app.js file --> load it, preferring local min file if there
list($app) = explode('.',$this->name);
if (file_exists(EGW_SERVER_ROOT.($path = '/'.$app.'/js/app.min.js')) ||
file_exists(EGW_SERVER_ROOT.($path = '/'.$app.'/js/app.js')))
{
Framework::includeJS($path);
}
// if app has no app.ts/js, we need to load etemplate2.js, otherwise popups wont work!
else
{
Framework::includeJS('/api/js/etemplate/etemplate2.js');
}
// Category styles
Categories::css($app);
// set action attribute for autocomplete form tag
// as firefox complains on about:balnk action, thus we have to literaly submit the form to a blank html
$form_action = "about:blank";
if (in_array(Header\UserAgent::type(), array('firefox', 'safari')))
{
$form_action = $GLOBALS['egw_info']['server']['webserver_url'].'/api/templates/default/empty.html';
}
// check if we are in an ajax-exec call from jdots template (or future other tabbed templates)
if (isset($GLOBALS['egw']->framework->response))
{
if ($output_mode == 2)
{
$content = '