diff --git a/api/src/Etemplate.php b/api/src/Etemplate.php new file mode 100644 index 0000000000..788973f2ec --- /dev/null +++ b/api/src/Etemplate.php @@ -0,0 +1,727 @@ + + * @copyright 2002-16 by RalfBecker@outdoor-training.de + * @version $Id$ + */ + +namespace EGroupware\Api; + +// explicitly import old not yet ported classes +use egw; +use egw_framework; +use egw_json_response; +use categories; // css + +/** + * 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='') + { + egw::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 = echo without navbar (eg. for popups) + * 3 = return eGW independent html site + * 4 = json response + * @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 html for $output_mode == 1, 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 = $GLOBALS['egw']->hooks->process( + array('hook_location' => 'etemplate2_before_exec') + + array('location_name' => $this->name) + + array('location_object' => &$this) + + $content + ); + + foreach($hook_data as $extras) + { + if (!$extras) continue; + + foreach(isset($extras[0]) ? $extras : array($extras) as $extra) + { + if ($extra['data'] && is_array($extra['data'])) + { + $content = array_merge($content, $extra['data']); + } + + if ($extra['preserve'] && is_array($extra['preserve'])) + { + $preserv = array_merge($preserv, $extra['preserve']); + } + + if ($extra['readonlys'] && is_array($extra['readonlys'])) + { + $readonlys = array_merge($readonlys, $extra['readonlys']); + } + } + } + unset($hook_data); + + // Include the etemplate2 javascript code + egw_framework::validate_file('.', 'etemplate2', 'etemplate'); + + if (!$this->rel_path) throw new Exception\AssertionFailed("No (valid) template '$this->name' found!"); + + if ($output_mode == 4) + { + $output_mode = 0; + self::$response = egw_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->laod_via); + if (!$template) throw new Exception\AssertionFailed("Template $this->name not instanciable! Maybe you forgot to rename template id."); + Translation::add_app('etemplate'); + $template->run('beforeSendToClient', array('', array('cont'=>$content))); + + // 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']; + + // 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, + ); + + // 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+egw_framework::get_extra()); + egw_framework::clear_extra(); // to not send/set it twice for multiple etemplates (eg. CRM view) + } + else // first call + { + // missing dependency, thought egw:uses jquery.jquery.tools does NOT work, maybe we should rename it to jquery-tools + // egw_framework::validate_file('jquery','jquery.tools.min'); + + // Include the jQuery-UI CSS - many more complex widgets use it + $theme = 'redmond'; + egw_framework::includeCSS("/phpgwapi/js/jquery/jquery-ui/$theme/jquery-ui-1.10.3.custom.css"); + // Load our CSS after jQuery-UI, so we can override it + egw_framework::includeCSS('/etemplate/templates/default/etemplate2.css'); + + // check if application of template has a app.js file --> load it + list($app) = explode('.',$this->name); + if (file_exists(EGW_SERVER_ROOT.'/'.$app.'/js/app.js')) + { + egw_framework::validate_file('.','app',$app,false); + } + // 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/src/Etemplate/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)) + { + $content = '
'."\n". + ''; + // add server-side page-generation times + if($GLOBALS['egw_info']['user']['preferences']['common']['show_generation_time']) + { + $vars = $GLOBALS['egw']->framework->_get_footer(); + $content .= "\n".$vars['page_generation_time']; + } + $GLOBALS['egw']->framework->response->generic("data", array($content)); + $GLOBALS['egw']->framework->response->generic('et2_load',$load_array+egw_framework::get_extra()); + egw_framework::clear_extra(); // to not send/set it twice for multiple etemplates (eg. CRM view) + self::$request = null; + return; + } + // let framework know, if we are a popup or not ('popup' not true, which is allways used by index.php!) + if (!isset($GLOBALS['egw_info']['flags']['nonavbar']) || is_bool($GLOBALS['egw_info']['flags']['nonavbar'])) + { + $GLOBALS['egw_info']['flags']['nonavbar'] = $output_mode == 2 ? 'popup' : false; + } + echo $GLOBALS['egw']->framework->header(); + if ($output_mode != 2 && !$GLOBALS['egw_info']['flags']['nonavbar']) + { + parse_navbar(); + } + else // mark popups as such, by enclosing everything in div#popupMainDiv + { + echo '