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 '
'."\n"; + } + // Send any accumulated json responses - after flush to avoid sending the buffer as a response + if(egw_json_response::isJSONResponse()) + { + $load_array['response'] = egw_json_response::get()->returnResult(); + } + // '; + + if ($output_mode == 2) + { + echo "\n
\n"; + echo $GLOBALS['egw']->framework->footer(); + } + ob_flush(); + } + self::$request = null; + } + + /** + * Fix all sel_options, as Etemplate\Widget\Select::beforeSendToClient is not run for auto-repeated stuff not understood by server + * + * @param array $sel_options + * @return array + */ + static protected function fix_sel_options(array $sel_options) + { + foreach($sel_options as &$options) + { + if (!is_array($options)||empty($options)) continue; + foreach($options as $key => $value) + { + if (is_numeric($key) && (!is_array($value) || !isset($value['value']))) + { + Etemplate\Widget\Select::fix_encoded_options($options, true); + break; + } + } + } + return $sel_options; + } + + /** + * Process via Ajax submitted content + * + * @param string $etemplate_exec_id + * @param array $_content + * @param boolean $no_validation + * @throws Exception\WrongParameter + */ + static public function ajax_process_content($etemplate_exec_id, array $_content, $no_validation) + { + //error_log(__METHOD__."(".array2string($etemplate_exec_id).', '.array2string($_content).")"); + + self::$request = Etemplate\Request::read($etemplate_exec_id); + //error_log('request='.array2string(self::$request)); + + self::$response = egw_json_response::get(); + + if (!($template = self::instance(self::$request->template['name'], self::$request->template['template_set'], + self::$request->template['version'], self::$request->template['load_via']))) + { + throw new Exception\WrongParameter('Can NOT read template '.array2string(self::$request->template)); + } + + // Set current app for validation + list($app) = explode('.',self::$request->method); + if(!$app) list($app) = explode('::',self::$request->method); + if($app) + { + Translation::add_app($app); + $GLOBALS['egw_info']['flags']['currentapp'] = $app; + } + $validated = array(); + $expand = array( + 'cont' => &self::$request->content, + ); + $template->run('validate', array('', $expand, $_content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children + + if ($no_validation) + { + self::$validation_errors = array(); + } + elseif (self::validation_errors(self::$request->ignore_validation)) + { + error_log(__METHOD__."(,".array2string($_content).') validation_errors='.array2string(self::$validation_errors)); + 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(); + + foreach($GLOBALS['egw']->hooks->process(array( + 'hook_location' => 'etemplate2_before_process', + 'location_name' => $template->id, + ) + self::complete_array_merge(self::$request->preserv, $validated)) as $extras) + { + if (!$extras) continue; + + foreach(isset($extras[0]) ? $extras : array($extras) as $extra) + { + if ($extra['data'] && is_array($extra['data'])) + { + $validated = array_merge($validated, $extra['data']); + } + } + } + + //error_log(__METHOD__."(,".array2string($content).')'); + //error_log(' validated='.array2string($validated)); + $content = ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated)); + + $tcontent = is_array($content) ? $content : + self::complete_array_merge(self::$request->preserv, $validated); + + $hook_data = $GLOBALS['egw']->hooks->process( + array( + 'hook_location' => 'etemplate2_after_process', + 'location_name' => $template->id + ) + $tcontent); + + unset($tcontent); + + if (is_array($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']); + } + } + } + } + unset($hook_data); + + if (isset($GLOBALS['egw_info']['flags']['java_script'])) + { + // Strip out any script tags + $GLOBALS['egw_info']['flags']['java_script'] = preg_replace(array('/(]*>)([^<]*)/is','/<\/script>/'),array('$2',''),$GLOBALS['egw_info']['flags']['java_script']); + self::$response->script($GLOBALS['egw_info']['flags']['java_script']); + //error_log($app .' added javascript to $GLOBALS[egw_info][flags][java_script] - use egw_json_response->script() instead.'); + } + + return $content; + } + + /** + * Notify server that eT session/request is no longer needed, because user closed window + * + * @param string $_exec_id + */ + static public function ajax_destroy_session($_exec_id) + { + //error_log(__METHOD__."('$_exec_id')"); + if (($request = Etemplate\Request::read($_exec_id))) + { + $request->remove_if_not_modified(); + unset($request); + } + } + + /** + * Process via POST submitted content + */ + static public function process_exec() + { + if (get_magic_quotes_gpc()) $_POST['value'] = stripslashes($_POST['value']); + $content = json_decode($_POST['value'],true); + //error_log(__METHOD__."(".array2string($content).")"); + + self::$request = Etemplate\Request::read($_POST['etemplate_exec_id']); + + if (!($template = self::instance(self::$request->template['name'], self::$request->template['template_set'], + self::$request->template['version'], self::$request->template['load_via']))) + { + throw new Exception\WrongParameter('Can NOT read template '.array2string(self::$request->template)); + } + $validated = array(); + $expand = array( + 'cont' => &self::$request->content, + ); + $template->run('validate', array('', $expand, $content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children + if (self::validation_errors(self::$request->ignore_validation)) + { + error_log(__METHOD__."(,".array2string($content).') validation_errors='.array2string(self::$validation_errors)); + exit; + } + //error_log(__METHOD__."(,".array2string($content).')'); + //error_log(' validated='.array2string($validated)); + + return ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated)); + } + + public $name; + public $template_set; + public $version; + public $laod_via; + + /** + * + * @var string If the template needs a div named other than the template name, this is it + */ + protected $dom_id; + + /** + * Reads an eTemplate from filesystem or DB (not yet supported) + * + * @param string $name name of the eTemplate or array with the values for all keys + * @param string $template_set =null default try template-set from user and if not found "default" + * @param string $lang language, '' loads the pref. lang of the user, 'default' loads the default one '' in the db + * @param int $group id of the (primary) group of the user or 0 for none, not used at the moment !!! + * @param string $version version of the eTemplate + * @param mixed $load_via name/array of keys of etemplate to load in order to get $name (only as second try!) + * @return boolean True if a fitting template is found, else False + * + * @ToDo supported customized templates stored in DB + */ + public function read($name,$template_set=null,$lang='default',$group=0,$version='',$load_via='') + { + + // For mobile experience try to load custom mobile templates + if (Header\UserAgent::mobile()) + { + $template_set = "mobile"; + } + + unset($lang); unset($group); // not used, but in old signature + $this->rel_path = self::relPath($this->name=$name, $this->template_set=$template_set, + $this->version=$version, $this->laod_via = $load_via); + //error_log(__METHOD__."('$name', '$template_set', '$lang', $group, '$version', '$load_via') rel_path=".array2string($this->rel_path)); + + $this->dom_id = $name; + + return (boolean)$this->rel_path; + } + + /** + * Set the DOM ID for the etemplate div. If not set, it will be generated from the template name. + * + * @param string $new_id + */ + public function set_dom_id($new_id) + { + $this->dom_id = $new_id; + } + /** + * Get template data as array + * + * @return array + */ + public function as_array() + { + return array( + 'name' => $this->name, + 'template_set' => $this->template_set, + 'version' => $this->version, + 'load_via' => $this->load_via, + ); + } + + /** + * Returns reference to an attribute in a named cell + * + * Currently we always return a reference to an not set value, unless it was set before. + * We do not return a reference to the actual cell, as it get's contructed on client-side! + * + * @param string $name cell-name + * @param string $attr attribute-name + * @return mixed reference to attribute, usually NULL + * @deprecated use getElementAttribute($name, $attr) + */ + public function &get_cell_attribute($name,$attr) + { + return self::getElementAttribute($name, $attr); + } + + /** + * set an attribute in a named cell if val is not NULL else return the attribute + * + * @param string $name cell-name + * @param string $attr attribute-name + * @param mixed $val if not NULL sets attribute else returns it + * @return reference to attribute + * @deprecated use setElementAttribute($name, $attr, $val) + */ + public function &set_cell_attribute($name,$attr,$val) + { + return self::setElementAttribute($name, $attr, $val); + } + + /** + * disables all cells with name == $name + * + * @param sting $name cell-name + * @param boolean $disabled =true disable or enable a cell, default true=disable + * @return reference to attribute + * @deprecated use disableElement($name, $disabled=true) + */ + public function disable_cells($name,$disabled=True) + { + return self::disableElement($name, $disabled); + } + + /** + * merges $old and $new, content of $new has precedence over $old + * + * THIS IS NOT THE SAME AS PHP's functions: + * - array_merge, as it calls itself recursive for values which are arrays. + * - array_merge_recursive accumulates values with the same index and $new does NOT overwrite $old + * + * @param array $old + * @param array $new + * @return array the merged array + */ + public static function complete_array_merge($old,$new) + { + if (is_array($new)) + { + if (!is_array($old)) $old = (array) $old; + + foreach($new as $k => $v) + { + if (!is_array($v) || !isset($old[$k]) || // no array or a new array + isset($v[0]) && !is_array($v[0]) && isset($v[count($v)-1]) || // or no associative array, eg. selecting multiple accounts + is_array($v) && count($v) == 0) // Empty array replacing non-empty + { + $old[$k] = $v; + } + else + { + $old[$k] = self::complete_array_merge($old[$k],$v); + } + } + } + return $old; + } + + /** + * Debug callback just outputting content + * + * @param array $content =null + */ + public function debug(array $content=null) + { + $GLOBALS['egw']->framework->render(print_r($content, true)); + } + + /** + * Message containing the max Upload size from the current php.ini settings + * + * We have to take the smaler one of upload_max_filesize AND post_max_size-2800 into account. + * memory_limit does NOT matter any more, because of the stream-interface of the vfs. + * + * @param int &$max_upload=null on return max. upload size in byte + * @return string + */ + static function max_upload_size_message(&$max_upload=null) + { + $upload_max_filesize = ini_get('upload_max_filesize'); + $post_max_size = ini_get('post_max_size'); + $max_upload = min(self::km2int($upload_max_filesize),self::km2int($post_max_size)-2800); + + return lang('Maximum size for uploads').': '.Vfs::hsize($max_upload). + " (php.ini: upload_max_filesize=$upload_max_filesize, post_max_size=$post_max_size)"; + } + + /** + * Format a number according to user prefs with decimal and thousands separator (later only for readonly) + * + * @param int|float|string $number + * @param int $num_decimal_places =2 + * @param boolean $readonly =true + * @return string + */ + static public function number_format($number,$num_decimal_places=2,$readonly=true) + { + static $dec_separator=null,$thousands_separator=null; + if (is_null($dec_separator)) + { + $dec_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][0]; + if (empty($dec_separator)) $dec_separator = '.'; + $thousands_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][1]; + } + if ((string)$number === '') return ''; + + return number_format(str_replace(' ','',$number),$num_decimal_places,$dec_separator,$readonly ? $thousands_separator : ''); + } + + /** + * Convert numbers like '32M' or '512k' to integers + * + * @param string $size + * @return int + */ + private static function km2int($size) + { + if (!is_numeric($size)) + { + switch(strtolower(substr($size,-1))) + { + case 'm': + $size = 1024*1024*(int)$size; + break; + case 'k': + $size = 1024*(int)$size; + break; + } + } + return (int)$size; + } +} + +// Try to discover all widgets, as names don't always match tags (eg: listbox is in menupopup) +foreach(scandir($dir=__DIR__ . '/Etemplate/Widget') as $filename) +{ + if(substr($filname, -4) == '.php') + { + try + { + include_once($dir.$filename); + } + catch(Exception $e) + { + error_log($e->getMessage()); + } + } +} + +// Use hook to load custom widgets from other apps +$widgets = $GLOBALS['egw']->hooks->process('etemplate2_register_widgets'); +foreach($widgets as $app => $list) +{ + if (is_array($list)) + { + foreach($list as $class) + { + try + { + class_exists($class); // trigger autoloader + } + catch(Exception $e) + { + error_log($e->getMessage()); + } + } + } +} diff --git a/etemplate/inc/class.etemplate_request.inc.php b/api/src/Etemplate/Request.php similarity index 96% rename from etemplate/inc/class.etemplate_request.inc.php rename to api/src/Etemplate/Request.php index bd754808dd..ec2f0c70d6 100644 --- a/etemplate/inc/class.etemplate_request.inc.php +++ b/api/src/Etemplate/Request.php @@ -1,16 +1,23 @@ - * @copyright (c) 2007-15 by Ralf Becker + * @copyright (c) 2007-16 by Ralf Becker * @version $Id$ */ +namespace EGroupware\Api\Etemplate; + +// explicitly import old not yet ported classes +use egw_framework; +use egw_json_response; +use egw_json_request; + /** * Class to represent the persitent information of an eTemplate request * @@ -31,7 +38,7 @@ * * The request object should be instancated only via the factory method etemplate_request::read($id=null) * - * $request = etemplate_request::read(); + * $request = Api\Etemplate\Request::read(); * * // add request data * @@ -39,7 +46,7 @@ * * b) open or modify an existing request: * - * if (!($request = etemplate_request::read($id))) + * if (!($request = Api\Etemplate\Request::read($id))) * { * // request not found * } @@ -80,7 +87,7 @@ * @property array $template * @property string $app_header */ -class etemplate_request +class Request { /** * here is the request data stored @@ -135,14 +142,14 @@ class etemplate_request * the sesison to constantly grow). * * @param string $id =null - * @return etemplate_request + * @return Request */ public static function read($id=null) { if (is_null(self::$request_class)) { // new default to use egw_cache to store requests - self::$request_class = 'etemplate_request_cache'; + self::$request_class = __CLASS__.'\\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'; @@ -154,7 +161,7 @@ class etemplate_request } else { - $request = new etemplate_request(); + $request = new Request(); if (!is_null($id)) { diff --git a/etemplate/inc/class.etemplate_request_cache.inc.php b/api/src/Etemplate/Request/Cache.php similarity index 82% rename from etemplate/inc/class.etemplate_request_cache.inc.php rename to api/src/Etemplate/Request/Cache.php index 527f853c9e..de6394cd56 100644 --- a/etemplate/inc/class.etemplate_request_cache.inc.php +++ b/api/src/Etemplate/Request/Cache.php @@ -3,14 +3,19 @@ * 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 + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker - * @copyright (c) 2014 by Ralf Becker + * @copyright (c) 2014-16 by Ralf Becker * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Request; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + /** * Class to represent the persitent information stored on the server for each eTemplate request * @@ -20,11 +25,11 @@ * * 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'; + * Api\Etemplate\Request::$request_class = 'etemplate_request_cache'; * - * The request object should be instancated only via the factory method etemplate_request::read($id=null) + * The request object should be instancated only via the factory method Api\Etemplate\Request::read($id=null) * - * $request = etemplate_request::read(); + * $request = Api\Etemplate\Request::read(); * * // add request data * @@ -32,7 +37,7 @@ * * b) open or modify an existing request: * - * if (!($request = etemplate_request::read($id))) + * if (!($request = Api\Etemplate\Request::read($id))) * { * // request not found * } @@ -48,7 +53,7 @@ * For an example look in link_widget::ajax_search() */ -class etemplate_request_cache extends etemplate_request +class Cache extends Etemplate\Request { /** * Expiration time of 4 hours @@ -71,6 +76,9 @@ class etemplate_request_cache extends etemplate_request { $this->id = $_id ? $_id : self::request_id(); //error_log(__METHOD__."($_id) this->id=$this->id"); + + // hack to quiten IDE Warning for not calling parent::__construct, which we can not! + if (false) parent::__construct(); } /** @@ -88,16 +96,16 @@ class etemplate_request_cache extends etemplate_request * 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 + * @return Request|boolean the object or false if $id is not found */ static function read($id=null) { - $request = new etemplate_request_cache($id); + $request = new 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))) + if (!($request->data = Api\Cache::getTree($GLOBALS['egw_info']['server']['install_id'].'_etemplate', $id))) { error_log("Error reading etemplate request data for id=$id!"); return false; @@ -132,7 +140,7 @@ class etemplate_request_cache extends etemplate_request 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); + Api\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 @@ -140,7 +148,7 @@ class etemplate_request_cache extends etemplate_request { //error_log(__METHOD__."() saving $this->id".($this->data_modified?'':' data NOT modified, just keeping session alife')); $this->data['last_saved'] = time(); - if (!egw_cache::setTree($GLOBALS['egw_info']['server']['install_id'].'_etemplate', $this->id, $this->data, + if (!Api\Cache::setTree($GLOBALS['egw_info']['server']['install_id'].'_etemplate', $this->id, $this->data, // use bigger one of our own self::EXPIRATION=4h and session lifetime (session.gc_maxlifetime) as expiration time max(self::EXPIRATION, ini_get('session.gc_maxlifetime')))) { diff --git a/etemplate/inc/class.etemplate_request_files.inc.php b/api/src/Etemplate/Request/Files.php similarity index 85% rename from etemplate/inc/class.etemplate_request_files.inc.php rename to api/src/Etemplate/Request/Files.php index 101b2e836a..00e203487a 100644 --- a/etemplate/inc/class.etemplate_request_files.inc.php +++ b/api/src/Etemplate/Request/Files.php @@ -1,16 +1,20 @@ - * @copyright (c) 2009-14 by Ralf Becker + * @copyright (c) 2009-16 by Ralf Becker * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Request; + +use EGroupware\Api\Etemplate; + /** * Class to represent the persitent information stored on the server for each eTemplate request * @@ -19,11 +23,11 @@ * * 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_files'; + * Api\Etemplate\Request::$request_class = 'etemplate_request_files'; * - * The request object should be instancated only via the factory method etemplate_request::read($id=null) + * The request object should be instancated only via the factory method Api\Etemplate\Request::read($id=null) * - * $request = etemplate_request::read(); + * $request = Api\Etemplate\Request::read(); * * // add request data * @@ -31,7 +35,7 @@ * * b) open or modify an existing request: * - * if (!($request = etemplate_request::read($id))) + * if (!($request = Api\Etemplate\Request::read($id))) * { * // request not found * } @@ -46,7 +50,7 @@ * * For an example look in link_widget::ajax_search() */ -class etemplate_request_files extends etemplate_request +class Files extends Etemplate\Request { /** * request id @@ -76,6 +80,9 @@ class etemplate_request_files extends etemplate_request if (!$id) $id = self::request_id(); $this->id = $id; + + // hack to quiten IDE Warning for not calling parent::__construct, which we can not! + if (false) parent::__construct(); } /** @@ -97,7 +104,7 @@ class etemplate_request_files extends etemplate_request */ static function read($id=null) { - $request = new etemplate_request_files($id); + $request = new Files($id); if (!is_null($id)) { diff --git a/etemplate/inc/class.etemplate_request_session.inc.php b/api/src/Etemplate/Request/Session.php similarity index 84% rename from etemplate/inc/class.etemplate_request_session.inc.php rename to api/src/Etemplate/Request/Session.php index ec2f8dc894..87b1ff38ec 100644 --- a/etemplate/inc/class.etemplate_request_session.inc.php +++ b/api/src/Etemplate/Request/Session.php @@ -1,25 +1,30 @@ - * @copyright (c) 2007-14 by Ralf Becker + * @copyright (c) 2007-16 by Ralf Becker * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Request; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + /** * 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) + * The request object should be instancated only via the factory method Api\Etemplate\Request::read($id=null) * - * $request = etemplate_request::read(); + * $request = Api\Etemplate\Request::read(); * * // add request data * @@ -27,7 +32,7 @@ * * b) open or modify an existing request: * - * if (!($request = etemplate_request::read($id))) + * if (!($request = Api\Etemplate\Request::read($id))) * { * // request not found * } @@ -42,7 +47,7 @@ * * For an example look in link_widget::ajax_search() */ -class etemplate_request_session extends etemplate_request +class Session extends Etemplate\Request { /** * request id @@ -61,6 +66,9 @@ class etemplate_request_session extends etemplate_request if (!$id) $id = self::request_id(); $this->id = $id; + + // hack to quiten IDE Warning for not calling parent::__construct, which we can not! + if (false) parent::__construct(); } /** @@ -82,11 +90,11 @@ class etemplate_request_session extends etemplate_request */ static function read($id=null) { - $request = new etemplate_request_session($id); + $request = new Session($id); if (!is_null($id)) { - if (!($data = $GLOBALS['egw']->session->appsession($id,'etemplate'))) + if (!($data = Api\Cache::getSession('etemplate', $id))) { return false; // request not found } @@ -122,11 +130,11 @@ class etemplate_request_session extends etemplate_request if ($this->remove_if_not_modified && !$this->data_modified) { //error_log(__METHOD__."() destroying $this->id"); - egw_cache::unsetSession('etemplate', $this->id); + Api\Cache::unsetSession('etemplate', $this->id); } elseif (!$this->destroyed && $this->data_modified) { - egw_cache::setSession('etemplate', $this->id, $this->data); + Api\Cache::setSession('etemplate', $this->id, $this->data); } if (!$this->garbage_collection_done) { @@ -144,7 +152,7 @@ class etemplate_request_session extends etemplate_request 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']; + $appsessions =& $_SESSION[Api\Session::EGW_APPSESSION_VAR]['etemplate']; $session_used =& $appsessions['session_used']; if ($this->id) diff --git a/api/src/Etemplate/Widget.php b/api/src/Etemplate/Widget.php new file mode 100644 index 0000000000..22b3d06759 --- /dev/null +++ b/api/src/Etemplate/Widget.php @@ -0,0 +1,919 @@ + + * @copyright 2002-16 by RalfBecker@outdoor-training.de + * @version $Id$ + */ + +namespace EGroupware\Api\Etemplate; + +use EGroupware\Api; +use XMLReader; +use ReflectionMethod; + +// explicitly import old not yet ported classes +use egw_json_response; + + +/** + * eTemplate widget baseclass + */ +class Widget +{ + /** + * Widget type + * + * @var string + */ + public $type; + + /** + * Widget id + * + * @var string + */ + public $id; + + /** + * Widget attributes + * + * @var array + */ + public $attrs = array(); + + /** + * Children + * + * @var array + */ + protected $children = array(); + + /** + * (Array of) comma-separated list of legacy options to automatically replace when parsing with set_attrs + * + * @var string|array + */ + protected $legacy_options; + + /** + * Request object of the currently created request + * + * It's a static variable as etemplates can contain further etemplates (rendered by a different object) + * + * @var etemplate_request + */ + static protected $request; + + /** + * JSON response object, if we run via a JSON request + * + * @var egw_json_response + */ + static protected $response; + + /** + * Namespaced content array, used when trying to initialize + * + * This is pretty much a global static variable, used when reading + * a template with the content set. This allows variable expansion + * in the constructor. + * + * @protected $cont + */ + static protected $cont = null; + + /** + * Constructor + * + * @param string|XMLReader $xml string with xml or XMLReader positioned on the element to construct + * @throws Api\Exception\WrongParameter + */ + public function __construct($xml) + { + $reader = self::get_reader($xml); + $this->type = $reader->name; + $depth = $reader->depth; + + $this->id = $reader->getAttribute('id'); + + // Update content? + if(self::$cont == null) + self::$cont = is_array(self::$request->content) ? self::$request->content : array(); + if($this->id && is_array(self::$cont[$this->id])) + { + $old_cont = self::$cont; + self::$cont = self::$cont[$this->id]; + } + + // read all attributes + $this->set_attrs($reader); + + while($reader->read() && $reader->depth > $depth) + { + if ($reader->nodeType == XMLReader::ELEMENT && $reader->depth > $depth) + { + $this->children[] = self::factory($reader->name, $reader, $reader->getAttribute('id')); + } + } + + // Reset content as we leave + if($old_cont) { + self::$cont = $old_cont; + } + } + + /** + * Get XMLReader for given xml string + * + * @param string|XMLReader $xml string with xml or XMLReader positioned on an element + * @throws Api\Exception\WrongParameter + */ + protected static function get_reader($xml) + { + if (is_a($xml, 'XMLReader')) + { + $reader = $xml; + } + else + { + $reader = new XMLReader(); + if (!$reader->XML($xml)) + { + throw new Api\Exception\WrongParameter("Can't parse xml:\n$xml"); + } + } + return $reader; + } + + /** + * Parse and set extra attributes from xml in template object + * + * Returns a cloned template object, if any attribute needs to be set. + * This is necessary as templates can be used multiple time, so we can not alter the cached template! + * + * @param string|XMLReader $xml + * @param boolean $cloned =true true: object does NOT need to be cloned, false: to set attribute, set them in cloned object + * @return Template current object or clone, if any attribute was set + */ + public function set_attrs($xml, $cloned=true) + { + $reader = self::get_reader($xml); + + // check if we have to split legacy options (can be by type) + $legacy_options = $this->legacy_options; + if (is_array($legacy_options)) + { + if (!($type = $reader->getAttribute('type'))) + { + $type = $this->type; + } + $legacy_options = $legacy_options[$type]; + } + + // read and set all attributes + $template = $this; + while($reader->moveToNextAttribute()) + { + if ($reader->name != 'id' && $template->attr[$reader->name] != $reader->value) + { + if (!$cloned) + { + $template = clone($this); + $cloned = true; // only clone it once, otherwise we loose attributes! + } + // $reader->value is an object and therefore assigned by reference + // this is important to not loose content when validating dynamic generated tabs as in settings! + $template->attrs[$reader->name] = $value = $reader->value; + + // expand attributes values, otherwise eg. validation can not use attrs referencing to content + if ($value[0] == '@' || strpos($value, '$cont') !== false) + { + $value = self::expand_name($value, null, null, null, null, + isset(self::$cont) ? self::$cont : self::$request->content); + } + + // split legacy options + if ($legacy_options && $reader->name == 'options') + { + $legacy_options = explode(',', $legacy_options); + foreach(self::csv_split($value, count($legacy_options)) as $n => $val) + { + if ($legacy_options[$n] && (string)$val !== '') $template->attrs[$legacy_options[$n]] = $val; + } + } + } + } + + // Add in anything in the modification array + if(is_array(self::$request->modifications[$this->id])) + { + $this->attrs = array_merge($this->attrs,self::$request->modifications[$this->id]); + } + + return $template; + } + + /** + * Split a $delimiter-separated options string, which can contain parts with delimiters enclosed in $enclosure + * + * Examples: + * - csv_split('"1,2,3",2,3') === array('1,2,3','2','3') + * - csv_split('1,2,3',2) === array('1','2,3') + * - csv_split('"1,2,3",2,3',2) === array('1,2,3','2,3') + * - csv_split('"a""b,c",d') === array('a"b,c','d') // to escape enclosures double them! + * + * @param string $str + * @param int $num =null in how many parts to split maximal, parts over this number end up (unseparated) in the last part + * @param string $delimiter =',' + * @param string $enclosure ='"' + * @return array + */ + public static function csv_split($str,$num=null,$delimiter=',',$enclosure='"') + { + if (strpos($str,$enclosure) === false) + { + return is_null($num) ? explode($delimiter,$str) : explode($delimiter,$str,$num); // no need to run this more expensive code + } + $parts = explode($delimiter,$str); + for($n = 0; isset($parts[$n]); ++$n) + { + $part =& $parts[$n]; + if ($part[0] === $enclosure) + { + while (isset($parts[$n+1]) && substr($part,-1) !== $enclosure) + { + $part .= $delimiter.$parts[++$n]; + unset($parts[$n]); + } + $part = substr(str_replace($enclosure.$enclosure,$enclosure,$part),1,-1); + } + } + $parts_renum = array_values($parts); // renumber the parts (in case we had to concat them) + + if ($num > 0 && count($parts_renum) > $num) + { + $parts_renum[$num-1] = implode($delimiter,array_slice($parts_renum,$num-1,count($parts_renum)-$num+1)); + $parts_renum = array_slice($parts_renum,0,$num); + } + return $parts_renum; + } + + /** + * Registry of classes implementing widgets + * + * @var array + */ + static protected $widget_registry = array(); + + /** + * Register a given class for certain widgets + * + * Registration is only needed if widget (base-)name is not autoloadable, + * eg. class Etemplate\Widget\Template does NOT need to be registered. + * + * @param string $class + * @param string|array $widgets + */ + public static function registerWidget($class, $widgets) + { + if (!is_subclass_of($class, __CLASS__)) + { + throw new Api\Exception\WrongParameter(__METHOD__."('$class', ".array2string($widgets).") $class is no subclass of ".__CLASS__.'!'); + } + foreach((array)$widgets as $widget) + { + self::$widget_registry[$widget] = $class; + } + } + + /** + * Factory method to construct all widgets + * + * @param string $type + * @param string|XMLReader $xml + * @param string $id =null + */ + public static function factory($type, $xml, $id=null) + { + $class_name =& self::$widget_registry[$type]; + + if (!isset($class_name)) + { + list($basetype) = explode('-',$type); + if (//dont think this is used: !class_exists($class_name = 'etemplate_widget_'.str_replace('-','_',$type)) && + !class_exists($class_name = __CLASS__.'\\'.ucfirst($basetype)) && + // widgets supplied by application in class ${app}_widget_etemplate or ${app}_${subtype}_widget_etemplate + !(isset($GLOBALS['egw_info']['apps'][$basetype]) && + (class_exists($class_name = str_replace('-','_',$type).'_etemplate_widget') || + class_exists($class_name = $basetype.'_etemplate_widget')))) + { + // Try for base type, it's probably better than the root + if(self::$widget_registry[$basetype] && self::$widget_registry[$basetype] != $class_name) + { + $class_name = self::$widget_registry[$basetype]; + } + // Look for old widgets that were adapted but not renamed + else if (class_exists($class_name = $basetype.'_widget') && in_array(__CLASS__, class_parents($class_name))) + { + // Side-effects set $class_name + //error_log("Ported old widget: $class_name"); + } + else + { + // Fall back to widget class, we can not ignore it, as the widget may contain other widgets + $class_name = __CLASS__; + } + } + } + + if(!$xml) + { + if (empty($type)) $type = 'widget'; + $xml = "<$type id='$id'/>"; + } + //error_log(__METHOD__."('$type', ..., '$id') using $class_name"); + + // currently only overlays can contain templates, other widgets can only reference to templates via id + if ($type == 'template' && $id && ($template = Widget\Template::instance($id))) + { + // references can set different attributes like: class, span, content (namespace) + return $template->set_attrs($xml, false); // false = need to clone template, if attributs are set! + } + return new $class_name($xml); + } + + /** + * Iterate over children to find the one with the given id and optional type + * + * @param string $id + * @param string $type =null + * @return Widget|NULL + */ + public function getElementById($id, $type=null) + { + foreach($this->children as $child) + { + if ($child->id === $id && (is_null($type) || $child->type === $type)) + { + return $child; + } + if (($element = $child->getElementById($id, $type))) + { + return $element; + } + } + return null; + } + + /** + * Iterate over children to find the one with the given type + * + * @param string $type + * @return array of Widget or empty array + */ + public function getElementsByType($type) + { + $elements = array(); + foreach($this->children as $child) + { + if ($child->type === $type) + { + $elements[] = $child; + } + $elements += $child->getElementsByType($type); + } + return $elements; + } + + /** + * Run a given method on all children + * + * Default implementation only calls method on itself and run on all children + * + * @param string $method_name + * @param array $params =array('') parameter(s) first parameter has to be the cname, second $expand! + * @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children + */ + public function run($method_name, $params=array(''), $respect_disabled=false) + { + // maintain $expand array name-expansion + $cname = $params[0]; + $expand =& $params[1]; + if ($expand['cname'] && $expand['cname'] !== $cname) + { + $expand['cont'] =& self::get_array(self::$request->content, $cname); + $expand['cname'] = $cname; + } + if ($respect_disabled && ($disabled = $this->attrs['disabled'] && self::check_disabled($this->attrs['disabled'], $expand))) + { + //error_log(__METHOD__."('$method_name', ".array2string($params).', '.array2string($respect_disabled).") $this disabled='{$this->attrs['disabled']}'=".array2string($disabled).": NOT running"); + return; + } + if (method_exists($this, $method_name)) + { + // Some parameter checking to avoid fatal errors + $call = true; + $method = new ReflectionMethod($this, $method_name); + foreach($method->getParameters() as $index => $param) + { + if(!$param->isOptional() && !array_key_exists($index,$params)) + { + error_log("Missing required parameter {$param->getPosition()}: {$param->getName()}"); + $call = false; + } + if($param->isArray() && !is_array($params[$index])) + { + error_log("$method_name expects an array for {$param->getPosition()}: {$param->getName()}"); + $params[$index] = (array)$params[$index]; + } + } + if($call) call_user_func_array(array($this, $method_name), $params); + } + foreach($this->children as $child) + { + // If type has something that can be expanded, we need to expand it so the correct method is run + $this->expand_widget($child, $expand); + $child->run($method_name, $params, $respect_disabled); + } + } + + /** + * If a widget's type is expandable, we need to expand it to make sure we have + * the right class before running the method on it + * + * @param Widget $child Widget to check & expand if needed + * @param array& $expand Expansion array + */ + protected function expand_widget(Widget $child, array &$expand) + { + if(strpos($child->attrs['type'], '@') !== false || strpos($child->attrs['type'], '$') !== false) + { + $type = self::expand_name($child->attrs['type'],$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); + $id = self::expand_name($child->id,$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); + $attrs = $child->attrs; + unset($attrs['type']); + $expanded_child = self::factory($type, false,$id); + $expanded_child->id = $id; + $expanded_child->type = $type; + $expanded_child->attrs = $attrs + array('type' => $type); + $child = $expanded_child; + } + } + + /** + * Checks if a grid row or column is disabled + * + * Expression: [!][@]val[=[@]check] + * Parts in square brackets are optional, a ! negates the expression, @val evaluates to $content['val'] + * if no =check is given all set non-empty and non-zero strings are true (standard php behavior) + * + * @param string $disabled expression to check, eg. "!@var" for !$content['var'] + * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' + * @return boolean true if the row/col is disabled or false if not + */ + protected static function check_disabled($disabled, array $expand) + { + if (($not = $disabled[0] == '!')) + { + $disabled = substr($disabled,1); + } + list($value,$check) = $vals = explode('=',$disabled); + + // use expand_name to be able to use @ or $ + $val = self::expand_name($value, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); + $check_val = self::expand_name($check, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); + $result = count($vals) == 1 ? $val != '' : ($check_val[0] == '/' ? preg_match($check_val,$val) : $val == $check_val); + if ($not) $result = !$result; + + //error_log(__METHOD__."('".($not?'!':'')."$disabled' = '$val' ".(count($vals) == 1 ? '' : ($not?'!':'=')."= '$check_val'")." = ".($result?'True':'False')); + return $result; + } + + /** + * Regular expression matching a PHP variable in a string, eg. + * + * "replies[$row][reply_message]" should only match $row + * "delete[$row_cont[path]]" should match $row_cont[path] + */ + const PHP_VAR_PREG = '\$[A-Za-z0-9_]+(\[[A-Za-z0-9_]+\])*'; + + /** + * allows a few variables (eg. row-number) to be used in field-names + * + * This is mainly used for autorepeat, but other use is possible. + * You need to be aware of the rules PHP uses to expand vars in strings, a name + * of "Row$row[length]" will expand to 'Row' as $row is scalar, you need to use + * "Row${row}[length]" instead. Only one indirection is allowd in a string by php !!! + * Out of that reason we have now the variable $row_cont, which is $cont[$row] too. + * Attention !!! + * Using only number as index in field-names causes a lot trouble, as depending + * on the variable type (which php determines itself) you used filling and later + * accessing the array it can by the index or the key of an array element. + * To make it short and clear, use "Row$row" or "$col$row" not "$row" or "$row$col" !!! + * + * @param string $name the name to expand + * @param int $c is the column index starting with 0 (if you have row-headers, data-cells start at 1) + * @param int $row is the row number starting with 0 (if you have col-headers, data-cells start at 1) + * @param int $c_ is the value of the previous template-inclusion, + * eg. the column-headers in the eTemplate-editor are templates itself, + * to show the column-name in the header you can not use $col as it will + * be constant as it is always the same col in the header-template, + * what you want is the value of the previous template-inclusion. + * @param int $row_ is the value of the previous template-inclusion, + * @param array $cont content of the template, you might use it to generate button-names with id values in it: + * "del[$cont[id]]" expands to "del[123]" if $cont = array('id' => 123) + * @return string the expanded name + */ + protected static function expand_name($name,$c,$row,$c_='',$row_='',$cont='') + { + $is_index_in_content = $name[0] == '@'; + if (($pos_var=strpos($name,'$')) !== false) + { + if (!$cont) + { + $cont = array(); + } + if (!is_numeric($c)) $c = self::chrs2num($c); + $col = self::num2chrs($c-1); // $c-1 to get: 0:'@', 1:'A', ... + $col_ = self::num2chrs($c_-1); + $row_cont = $cont[$row]; + $col_row_cont = $cont[$col.$row]; + + eval('$name = "'.str_replace('"','\\"',$name).'";'); + unset($col_, $row_, $row_cont, $col_row_cont); // quiten IDE warning about used vars, they might be used in above eval! + } + if ($is_index_in_content) + { + if ($name[1] == '@' && is_array(self::$request->content)) + { + $name = self::get_array(self::$request->content,substr($name,2)); + } + elseif(is_array($cont)) + { + $name = self::get_array($cont,substr($name,1)); + } + else + { + // Content not set expands to '' + $name = ''; + } + } + return $name; + } + + /** + * generates column-names from index: 'A', 'B', ..., 'AA', 'AB', ..., 'ZZ' (not more!) + * + * @param string $chrs column letter to generate name from 'A' => 1 + * @return int the index + */ + static function chrs2num($chrs) + { + $min = ord('A'); + $max = ord('Z') - $min + 1; + + $num = 1+ord($chrs{0})-$min; + if (strlen($chrs) > 1) + { + $num *= 1 + $max - $min; + $num += 1+ord($chrs{1})-$min; + } + return $num; + } + + /** + * generates column-names from index: 'A', 'B', ..., 'AA', 'AB', ..., 'ZZ' (not more!) + * + * @param int $num numerical index to generate name from 1 => 'A' + * @return string the name + */ + static function num2chrs($num) + { + $min = ord('A'); + $max = ord('Z') - $min + 1; + if ($num >= $max) + { + $chrs = chr(($num / $max) + $min - 1); + } + $chrs .= chr(($num % $max) + $min); + + return $chrs; + } + + /** + * Convert object to string + * + * @return string + */ + public function __toString() + { + return '['.get_class($this).'] ' . + $this->type.($this->attrs['type'] && $this->attrs['type'] != $this->type ? '('.$this->attrs['type'].')' : '').'#'.$this->id; + } + + /** + * When cloning a widget, we also clone children + */ + public function __clone() + { + foreach($this->children as $child_num => $child) { + $this->children[$child_num] = clone $child; + } + } + + /** + * Convert widget (incl. children) to xml + * + * @param string $indent ='' + * @return string + */ + public function toXml($indent='') + { + echo "$indent<$this->type"; + if ($this->id) echo ' id="'.htmlspecialchars($this->id).'"'; + foreach($this->attrs as $name => $value) + { + if ($name == 'options' && $this->legacy_options && (!is_array($this->legacy_options) || + isset($this->legacy_options[$this->attrs['type'] ? $this->attrs['type'] : $this->type]))) + { + continue; // do NOT output already converted legacy options + } + echo ' '.$name.'="'.htmlspecialchars($value).'"'; + } + echo ' php-class="'.get_class($this).'"'; + + if ($this->children) + { + echo ">\n"; + foreach($this->children as $child) + { + $child->toXml($indent."\t"); + } + echo "$indenttype>\n"; + } + else + { + echo " />\n"; + } + } + + /** + * build the name of a form-element from a basename and name + * + * name and basename can contain sub-indices in square bracets, eg. basename="base[basesub1][basesub2]" + * and name = "name[sub]" gives "base[basesub1][basesub2][name][sub]" + * + * @param string $cname basename + * @param string $name name + * @param array $expand =null values for keys 'c', 'row', 'c_', 'row_', 'cont' + * @return string complete form-name + */ + static function form_name($cname,$name,array $expand=null) + { + if ($expand && !empty($name)) + { + $name = self::expand_name($name, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); + } + if (count($name_parts = explode('[', $name, 2)) > 1) + { + $name_parts = array_merge(array($name_parts[0]), explode('][', substr($name_parts[1],0,-1))); + } + if (!empty($cname)) + { + array_unshift($name_parts,$cname); + } + $form_name = array_shift($name_parts); + if (count($name_parts)) + { + // RB: not sure why this business with entity encoding for square brakets, it messes up validation + //$form_name .= '['.implode('][',$name_parts).']'; + $form_name .= '['.implode('][',$name_parts).']'; + } + return $form_name; + } + + /** + * return a reference to $arr[$idx] + * + * This works for non-trival indexes like 'a[b][c]' too: it returns &$arr[a][b][c] + * $sub = get_array($arr,'a[b]'); $sub = 'c'; is equivalent to $arr['a']['b'] = 'c'; + * + * @param array $arr the array to search, referenz as a referenz gets returned + * @param string $_idx the index, may contain sub-indices like a[b], see example below + * @param boolean $reference_into default False, if True none-existing sub-arrays/-indices get created to be returned as referenz, else False is returned + * @param bool $skip_empty returns false if $idx is not present in $arr + * @return mixed reference to $arr[$idx] or null if $idx is not set and not $reference_into + */ + static function &get_array(&$arr,$_idx,$reference_into=False,$skip_empty=False) + { + if (!is_array($arr)) + { + throw new Api\Exception\AssertionFailed(__METHOD__."(\$arr,'$_idx',$reference_into,$skip_empty) \$arr is no array!"); + } + if (is_object($_idx)) return false; // given an error in php5.2 + + // Make sure none of these are left + $idx = str_replace(array('[',']'), array('[',']'), $_idx); + + // Handle things expecting arrays - ends in [] + if(substr($idx,-2) == "[]") + { + $idx = substr($idx,0,-2); + } + + if (count($idxs = explode('[', $idx, 2)) > 1) + { + $idxs = array_merge(array($idxs[0]), explode('][', substr($idxs[1],0,-1))); + } + $pos = &$arr; + foreach($idxs as $idx) + { + if (!is_array($pos) && (!$reference_into || $reference_into && isset($pos))) + { + //if ($reference_into) error_log(__METHOD__."(".(strlen($s=array2string($arr))>512?substr($s,0,512).'...':$s).", '$idx', ".array2string($reference_into).", ".array2string($skip_empty).") ".function_backtrace()); + return null; + } + if($skip_empty && (!is_array($pos) || !isset($pos[$idx]))) return null; + $pos = &$pos[$idx]; + } + return $pos; + } + + /** + * return a reference to $arr[$idx] + * + * This works for non-trival indexes like 'a[b][c]' too: it returns &$arr[a][b][c] + * $sub = get_array($arr,'a[b]'); $sub = 'c'; is equivalent to $arr['a']['b'] = 'c'; + * + * @param array& $_arr the array to search, referenz as a referenz gets returned + * @param string $_idx the index, may contain sub-indices like a[b], see example below + * @param mixed $_value value to set + */ + static function set_array(&$_arr, $_idx, $_value) + { + $ref =& self::get_array($_arr, $_idx, true); + if (true) $ref = $_value; + } + + /** + * Checks if a widget is readonly: + * - readonly attribute set + * - $readonlys[__ALL__] set and $readonlys[$form_name] !== false + * - $readonlys[$form_name] evaluates to true + * + * @param string $cname ='' + * @param string $form_name =null form_name, to not calculate him again + * @return boolean + */ + public function is_readonly($cname='', $form_name=null) + { + if (!isset($form_name)) + { + $expand = array( + 'cont' => self::get_array(self::$request->content, $cname), + ); + $form_name = self::form_name($cname, $this->id, $expand); + } + $readonly = $this->attrs['readonly'] || self::$request->readonlys[$form_name] || + self::get_array(self::$request->readonlys,$form_name) === true || + isset(self::$request->readonlys['__ALL__']) && ( + // Exceptions to all + self::$request->readonlys[$form_name] !== false && + self::get_array(self::$request->readonlys,$form_name) !== false + ); + + //error_log(__METHOD__."('$cname') this->id='$this->id' --> form_name='$form_name': attrs[readonly]=".array2string($this->attrs['readonly']).", readonlys['$form_name']=".array2string(self::$request->readonlys[$form_name]).", readonlys[$form_name]=".array2string(self::get_array(self::$request->readonlys,$form_name)).", readonlys['__ALL__']=".array2string(self::$request->readonlys['__ALL__'])." returning ".array2string($readonly)); + return $readonly; + } + /** + * Validation errors from process_show and the extensions, should be set via etemplate::set_validation_error + * + * @public array form_name => message pairs + */ + static protected $validation_errors = array(); + + /** + * Sets a validation error, to be displayed in the next exec + * + * @param string $name (complete) name of the widget causing the error + * @param string|boolean $error error-message already translated or false to reset all existing error for given name + * @param string $cname =null set it to '', if the name is already a form-name, defaults to self::$name_vars + */ + public static function set_validation_error($name,$error,$cname=null) + { + // not yet used: if (is_null($cname)) $cname = self::$name_vars; + error_log(__METHOD__."('$name','$error','$cname') ".function_backtrace()); + + if ($cname) $name = self::form_name($cname,$name); + + if ($error === false) + { + unset(self::$validation_errors[$name]); + } + else + { + if (self::$validation_errors[$name]) + { + self::$validation_errors[$name] .= ', '; + } + self::$validation_errors[$name] .= $error; + } + } + + /** + * Check if we have not ignored validation errors + * + * @param string $ignore_validation ='' if not empty regular expression for validation-errors to ignore + * @param string $cname =null name-prefix, which need to be ignored, default self::$name_vars + * @return boolean true if there are not ignored validation errors, false otherwise + */ + public static function validation_errors($ignore_validation='',$cname='') + { + // not yet used: if (is_null($cname)) $cname = self::$name_vars; + //echo "

uietemplate::validation_errors('$ignore_validation','$cname') validation_error="; _debug_array(self::$validation_errors); + if (!$ignore_validation) return count(self::$validation_errors) > 0; + + foreach(array_values(self::$validation_errors) as $name) + { + if ($cname) $name = preg_replace('/^'.$cname.'\[([^\]]+)\](.*)$/','\\1\\2',$name); + + // treat $ignoare_validation only as regular expression, if it starts with a slash + if ($ignore_validation[0] == '/' && !preg_match($ignore_validation,$name) || + $ignore_validation[0] != '/' && $ignore_validation != $name) + { + //echo "

uietemplate::validation_errors('$ignore_validation','$cname') name='$name' ($error) not ignored!!!

\n"; + return true; + } + //echo "

uietemplate::validation_errors('$ignore_validation','$cname') name='$name' ($error) ignored

\n"; + } + return false; + } + + /** + * Returns reference to an attribute in a named cell + * + * Currently we always return a reference to an not set value, unless it was set before. + * We do not return a reference to the actual cell, as it get's contructed on client-side! + * + * @param string $name cell-name + * @param string $attr attribute-name + * @return mixed reference to attribute, usually NULL + */ + public function &getElementAttribute($name, $attr) + { + //error_log(__METHOD__."('$name', '$attr')"); + return self::$request->modifications[$name][$attr]; + } + + /** + * Set an attribute in a named cell if val is not NULL else return the attribute + * + * Can be called static, in which case it only sets modifications. + * + * @param string $name cell-name + * @param string $attr attribute-name + * @param mixed $val if not NULL sets attribute else returns it + * @return reference to attribute + */ + public static function &setElementAttribute($name,$attr,$val) + { + //error_log(__METHOD__."('$name', '$attr', ...) request=".get_class(self::$request).", response=".get_class(self::$response).function_backtrace()); + $ref =& self::$request->modifications[$name][$attr]; + if(self::$request && self::$response && (!isset($this) || $val != $this->attrs[$attr])) + { + // In an AJAX response - automatically add + self::$response->generic('assign',array( + 'etemplate_exec_id' => self::$request->id(), + 'id' => $name, + 'key' => $attr, + 'value' => $val + )); + // Don't delete it + self::$request->unset_to_process(''); + //error_log(__METHOD__."('$name', '$attr', ...) ".function_backtrace()); + } + if (isset($this)) $this->attrs[$attr] = $val; + if (!is_null($val)) $ref = $val; + + //error_log(__METHOD__."('$name', '$attr', ".array2string($val).')'); + return $ref; + } + + /** + * disables all cells with name == $name + * + * @param sting $name cell-name + * @param boolean $disabled =true disable or enable a cell, default true=disable + * @return reference to attribute + */ + public function disableElement($name,$disabled=True) + { + return self::setElementAttribute($name, 'disabled', $disabled); + } +} diff --git a/etemplate/inc/class.etemplate_widget_ajax_select.inc.php b/api/src/Etemplate/Widget/AjaxSelect.php similarity index 88% rename from etemplate/inc/class.etemplate_widget_ajax_select.inc.php rename to api/src/Etemplate/Widget/AjaxSelect.php index 8b2684ca5c..00f72fde64 100644 --- a/etemplate/inc/class.etemplate_widget_ajax_select.inc.php +++ b/api/src/Etemplate/Widget/AjaxSelect.php @@ -3,21 +3,23 @@ * EGroupware - eTemplate serverside ajax select widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Nathan Gray * @copyright 2015 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; + /** * eTemplate ajax select widget - * */ -class etemplate_widget_ajax_select extends etemplate_widget_menupopup +class AjaxSelect extends Select { - /** * Fill type options in self::$request->sel_options to be used on the client * @@ -50,15 +52,15 @@ class etemplate_widget_ajax_select extends etemplate_widget_menupopup { $form_name = self::form_name($cname, $this->id, $expand); } - + // Make sure  s, etc. are properly encoded when sent, and not double-encoded $options = (isset(self::$request->sel_options[$form_name]) ? $form_name : $this->id); if(is_array(self::$request->sel_options[$options])) { - + // Fix any custom options from application self::fix_encoded_options(self::$request->sel_options[$options],true); - + if(!self::$request->sel_options[$options]) { unset(self::$request->sel_options[$options]); @@ -66,5 +68,4 @@ class etemplate_widget_ajax_select extends etemplate_widget_menupopup } } } - -etemplate_widget::registerWidget('etemplate_widget_ajax_select', array('ajax_select')); +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\AjaxSelect', array('ajax_select')); diff --git a/api/src/Etemplate/Widget/Box.php b/api/src/Etemplate/Widget/Box.php new file mode 100644 index 0000000000..ea5cc72fab --- /dev/null +++ b/api/src/Etemplate/Widget/Box.php @@ -0,0 +1,136 @@ + + * @copyright 2002-16 by RalfBecker@outdoor-training.de + * @version $Id$ + */ + +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; + +/** + * *box widgets having an own namespace + */ +class Box extends Etemplate\Widget +{ + /** + * (Array of) comma-separated list of legacy options to automatically replace when parsing with set_attrs + * + * @var string|array + */ + protected $legacy_options = array( + 'box' => ',cellpadding,cellspacing,keep', + 'hbox' => 'cellpadding,cellspacing,keep', + 'vbox' => 'cellpadding,cellspacing,keep', + 'groupbox' => 'cellpadding,cellspacing,keep', + ); + + /** + * Run a given method on all children + * + * Reimplemented because grids and boxes can have an own namespace. + * GroupBox has no namespace! + * + * @param string $method_name + * @param array $params =array('') parameter(s) first parameter has to be cname! + * @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children + */ + public function run($method_name, $params=array(''), $respect_disabled=false) + { + $cname =& $params[0]; + $expand =& $params[1]; + $old_cname = $params[0]; + $old_expand = $params[1]; + + if ($this->id && $this->type != 'groupbox') $cname = self::form_name($cname, $this->id, $params[1]); + if ($expand['cname'] !== $cname && $cname) + { + $expand['cont'] =& self::get_array(self::$request->content, $cname); + $expand['cname'] = $cname; + } + if ($respect_disabled && ($disabled = $this->attrs['disabled'] && self::check_disabled($this->attrs['disabled'], $expand))) + { + //error_log(__METHOD__."('$method_name', ".array2string($params).', '.array2string($respect_disabled).") $this disabled='{$this->attrs['disabled']}'=".array2string($disabled).": NOT running"); + return; + } + if (method_exists($this, $method_name)) + { + call_user_func_array(array($this, $method_name), $params); + } + + // Expand children + $columns_disabled = null; + for($n = 0; ; ++$n) + { + if (isset($this->children[$n])) + { + $child =& $this->children[$n]; + // If type has something that can be expanded, we need to expand it so the correct method is run + $this->expand_widget($child, $expand); + } + // check if we need to autorepeat last row ($child) + elseif (isset($child) && $child->type == 'box' && $this->need_autorepeat($child, $cname, $expand)) + { + // Set row for repeating + $expand['row'] = $n; + // not breaking repeats last row/column ($child) + } + else + { + break; + } + //error_log('Running ' . $method_name . ' on child ' . $n . '(' . $child . ') ['.$expand['row'] . ','.$expand['c'] . ']'); + $disabled = $child->run($method_name, $params, $respect_disabled, $columns_disabled) === false; + } + + $params[0] = $old_cname; + $params[1] = $old_expand; + + return true; + } + + /** + * Check if a box child needs autorepeating, because still content left + * + * We only check passed widget and direct children. + * + * @param string $cname + * @param array $expand + */ + private function need_autorepeat(Etemplate\Widget $widget, $cname, array $expand) + { + foreach(array($widget) + $widget->children as $check_widget) + { + $pat = $check_widget->id; + while(($pattern = strstr($pat, '$'))) + { + $pat = substr($pattern,$pattern[1] == '{' ? 2 : 1); + + $Ok = $pat[0] == 'r' && !(substr($pat,0,2) == 'r_' || + substr($pat,0,4) == 'row_' && substr($pat,0,8) != 'row_cont'); + + if ($Ok && ($fname=self::form_name($cname, $check_widget->id, $expand)) && + // need to break if fname ends in [] as get_array() will ignore it and returns whole array + // for an id like "run[$row_cont[appname]]" + substr($fname, -2) != '[]' && + ($value = self::get_array(self::$request->content, $fname)) !== null) // null = not found (can be false!) + { + //error_log(__METHOD__."($widget,$cname) $this autorepeating row $expand[row] because of $check_widget->id = '$fname' is ".array2string($value)); + unset($value); + return true; + } + } + } + + return false; + } +} +// register class for layout widgets, which can have an own namespace +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Box', array('box', 'hbox', 'vbox', 'groupbox')); diff --git a/etemplate/inc/class.etemplate_widget_button.inc.php b/api/src/Etemplate/Widget/Button.php similarity index 84% rename from etemplate/inc/class.etemplate_widget_button.inc.php rename to api/src/Etemplate/Widget/Button.php index 5358d49185..183fe6da4b 100644 --- a/etemplate/inc/class.etemplate_widget_button.inc.php +++ b/api/src/Etemplate/Widget/Button.php @@ -3,18 +3,22 @@ * EGroupware - eTemplate serverside button widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-11 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; + /** * eTemplate button widget */ -class etemplate_widget_button extends etemplate_widget +class Button extends Etemplate\Widget { /** * True after submit-validation, if cancel button was pressed @@ -57,7 +61,7 @@ class etemplate_widget_button extends etemplate_widget ) { $valid =& self::get_array($validated, $form_name, true); - $valid = is_array($value) ? $value : 'pressed'; + if (true) $valid = is_array($value) ? $value : 'pressed'; // recorded pressed button globally, was in the template object before, put now as static on this object if ($this->type == 'cancel' || $form_name == 'cancel' || substr($form_name,-10) == '[cancel]') @@ -72,4 +76,4 @@ class etemplate_widget_button extends etemplate_widget } } } -etemplate_widget::registerWidget('etemplate_widget_button', array('button','buttononly')); +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Button', array('button','buttononly')); diff --git a/etemplate/inc/class.etemplate_widget_checkbox.inc.php b/api/src/Etemplate/Widget/Checkbox.php similarity index 91% rename from etemplate/inc/class.etemplate_widget_checkbox.inc.php rename to api/src/Etemplate/Widget/Checkbox.php index b15073d016..73cc3cf277 100644 --- a/etemplate/inc/class.etemplate_widget_checkbox.inc.php +++ b/api/src/Etemplate/Widget/Checkbox.php @@ -3,20 +3,24 @@ * EGroupware - eTemplate serverside checkbox widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-11 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; + /** * eTemplate checkbox widget * * Multiple checkbox widgets can have the same name ending in [], in which case an array with the selected_value's of the checked boxes get returned. */ -class etemplate_widget_checkbox extends etemplate_widget +class Checkbox extends Etemplate\Widget { /** * (Array of) comma-separated list of legacy options to automatically replace when parsing with set_attrs @@ -78,7 +82,7 @@ class etemplate_widget_checkbox extends etemplate_widget } if ($type == 'radio') { - $options = etemplate_widget_menupopup::selOptions($form_name, true); + $options = Select::selOptions($form_name, true); if (in_array($value, $options)) { $valid = $value; @@ -125,4 +129,4 @@ class etemplate_widget_checkbox extends etemplate_widget } } } -etemplate_widget::registerWidget('etemplate_widget_checkbox', array('checkbox', 'radio')); +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Checkbox', array('checkbox', 'radio')); diff --git a/api/src/Etemplate/Widget/Contact.php b/api/src/Etemplate/Widget/Contact.php new file mode 100644 index 0000000000..8b56dce729 --- /dev/null +++ b/api/src/Etemplate/Widget/Contact.php @@ -0,0 +1,177 @@ + + * @version $Id$ + */ + +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api; + +/** + * eTemplate Extension: Contact widget + * + * This widget can be used to fetch fields of a contact specified by contact-id + */ +class Contact extends Entry +{ + /** + * Instance of the contacts class + * + * @var Api\Contacts + */ + private $contacts; + + /** + * Array with a transformation description, based on attributes to modify. + * + * Exampels: + * + * * 'type' => array('some' => 'other') + * if 'type' attribute equals 'some' replace it with 'other' + * + * * 'type' => array('some' => array('type' => 'other', 'options' => 'otheroption') + * same as above, but additonally set 'options' attr to 'otheroption' + * + * --> leaf element is the action, if previous filters are matched: + * - if leaf is scalar, it just replaces the previous filter value + * - if leaf is an array, it contains assignments for (multiple) attributes: attr => value pairs + * + * * 'type' => array( + * 'some' => array(...), + * 'other' => array(...), + * '__default__' => array(...), + * ) + * it's possible to have a list of filters with actions to run, plus a '__default__' which matches all not explicitly named values + * + * * 'value' => array('__callback__' => 'app.class.method' || 'class::method' || 'method') + * run value through a *serverside* callback, eg. reading an entry based on it's given id + * + * * 'value' => array('__js__' => 'function(value) { return value+5; }') + * run value through a *clientside* callback running in the context of the widget + * + * * 'name' => '@name[@options]' + * replace value of 'name' attribute with itself (@name) plus value of options in square brackets + * + * --> attribute name prefixed with @ sign means value of given attribute + * + * @var array + */ + protected static $transformation = array( + 'type' => array( + 'contact-fields' => array( // contact-fields widget + 'sel_options' => array('__callback__' => 'get_contact_fields'), + 'type' => 'select', + 'no_lang' => true, + 'options' => 'None', + ), + 'contact-template' => array( + 'type' => 'template', + 'options' => '', + 'template' => array('__callback__' => 'parse_template'), + ), + '__default__' => array( + 'options' => array( + 'bday' => array('type' => 'date', 'options' => 'Y-m-d'), + 'owner' => array('type' => 'select-account', 'options' => ''), + 'modifier' => array('type' => 'select-account', 'options' => ''), + 'creator' => array('type' => 'select-account', 'options' => ''), + 'modifed' => array('type' => 'date-time', 'options' => ''), + 'created' => array('type' => 'date-time', 'options' => ''), + 'cat_id' => array('type' => 'select-cat', 'options' => ''), + '__default__' => array('type' => 'label', 'options' => ''), + ), + 'no_lang' => 1, + ), + ), + ); + + /** + * Constructor of the extension + * + * @param string $xml or 'html' for old etemplate + */ + public function __construct($xml) + { + if (is_a($xml, 'XMLReader') || $xml != '' && $xml != 'html') + { + parent::__construct($xml); + } + $this->contacts = $GLOBALS['egw']->contacts; + } + + /** + * Legacy support for putting the template name in 'label' param + * + * @param string $template + * @param array $attrs + */ + public function parse_template($template, &$attrs) + { + return sprintf($template ? $template : $attrs['label'], $attrs['value']); + } + + /** + * Get all contact-fields + * + * @return array + */ + public function get_contact_fields() + { + ApiTranslation::add_app('addressbook'); + + $this->contacts->__construct(); + $options = $this->contacts->contact_fields; + foreach($this->contacts->customfields as $name => $data) + { + $options['#'.$name] = $data['label']; + } + return $options; + } + + public function get_entry($value, array $attrs) + { + return $this->get_contact($value, $attrs); + } + /** + * Get contact data, if $value not already contains them + * + * @param int|string|array $value + * @param array $attrs + * @return array + */ + public function get_contact($value, array $attrs) + { + if (is_array($value) && !(array_key_exists('app',$value) && array_key_exists('id', $value))) return $value; + + if(is_array($value) && array_key_exists('app', $value) && array_key_exists('id', $value)) $value = $value['id']; + switch($attrs['type']) + { + case 'contact-account': + case 'contact-template': + if (substr($value,0,8) != 'account:') + { + $value = 'account:'.($attrs['name'] != 'account:' ? $value : $GLOBALS['egw_info']['user']['account_id']); + } + // fall-through + case 'contact-value': + default: + if (substr($value,0,12) == 'addressbook:') $value = substr($value,12); // link-entry syntax + if (!($contact = $this->contacts->read($value))) + { + $contact = array(); + } + break; + } + unset($contact['jpegphoto']); // makes no sense to return binary image + + //error_log(__METHOD__."('$value') returning ".array2string($contact)); + return $contact; + } +} diff --git a/etemplate/inc/class.etemplate_widget_customfields.inc.php b/api/src/Etemplate/Widget/Customfields.php similarity index 94% rename from etemplate/inc/class.etemplate_widget_customfields.inc.php rename to api/src/Etemplate/Widget/Customfields.php index 4f5c118fc9..daa8b40b16 100644 --- a/etemplate/inc/class.etemplate_widget_customfields.inc.php +++ b/api/src/Etemplate/Widget/Customfields.php @@ -3,19 +3,22 @@ * EGroupware - eTemplate custom fields widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Nathan Gray * @copyright 2011 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api; + /** * Widgets for custom fields and listing custom fields - * */ -class etemplate_widget_customfields extends etemplate_widget_transformer +class Customfields extends Transformer { /** @@ -123,17 +126,17 @@ class etemplate_widget_customfields extends etemplate_widget_transformer if(!$app) { $app =& $this->setElementAttribute(self::GLOBAL_VALS, 'app', $GLOBALS['egw_info']['flags']['currentapp']); - $customfields =& $this->setElementAttribute(self::GLOBAL_VALS, 'customfields', egw_customfields::get($app)); + $customfields =& $this->setElementAttribute(self::GLOBAL_VALS, 'customfields', Api\Storage\Customfields::get($app)); } // if we are in the etemplate editor or the app has no cf's, load the cf's from the app the tpl belongs too if ($app && $app != 'stylite' && $app != $GLOBALS['egw_info']['flags']['currentapp'] && !isset($customfields) && ( $GLOBALS['egw_info']['flags']['currentapp'] == 'etemplate' || !$this->attrs['customfields'] || - etemplate::$hooked + Etemplate::$hooked ) || !isset($customfields)) { // app changed - $customfields =& egw_customfields::get($app); + $customfields =& Api\Storage\Customfields::get($app); } // Filter fields if($this->attrs['field-names']) @@ -188,7 +191,7 @@ class etemplate_widget_customfields extends etemplate_widget_transformer $sel_options[$lname] = lang($label); $fields_with_vals[]=$lname; } - $link_types = array_intersect_key(egw_link::app_list('query'),egw_link::app_list('title')); + $link_types = array_intersect_key(Api\Link::app_list('query'), Api\Link::app_list('title')); // Explicitly add in filemanager, which does not support query or title $link_types['filemanager'] = lang('filemanager'); @@ -223,7 +226,7 @@ class etemplate_widget_customfields extends etemplate_widget_transformer { if (!empty($data['values'])) { - etemplate_widget_menupopup::fix_encoded_options($data['values']); + Select::fix_encoded_options($data['values']); } } if($fields != $customfields) @@ -240,7 +243,7 @@ class etemplate_widget_customfields extends etemplate_widget_transformer // Re-format date custom fields from Y-m-d $field_settings =& self::get_array(self::$request->modifications, "{$this->id}[customfields]",true); if (true) $field_settings = array(); - $link_types = egw_link::app_list(); + $link_types = Api\Link::app_list(); foreach($fields as $fname => $field) { // Run beforeSendToClient for each field @@ -257,12 +260,12 @@ class etemplate_widget_customfields extends etemplate_widget_transformer * * @param string $fname custom field name * @param array $field custom field data - * @return etemplate_widget + * @return Etemplate\Widget */ protected function _widget($fname, array $field) { static $link_types = null; - if (!isset($link_types)) $link_types = egw_link::app_list (); + if (!isset($link_types)) $link_types = Api\Link::app_list (); $type = $field['type']; // Link-tos needs to change from appname to link-to @@ -293,10 +296,10 @@ class etemplate_widget_customfields extends etemplate_widget_transformer case 'vfs-upload': $widget->attrs['path'] = $field['app'] . ':' . - self::expand_name('$cont['.egw_link::get_registry($field['app'],'view_id').']',0,0,0,0,self::$request->content). + self::expand_name('$cont['.Api\Link::get_registry($field['app'],'view_id').']',0,0,0,0,self::$request->content). ':'.$field['label']; break; - + case 'link-to': $widget->attrs['only_app'] = $field['type']; break; @@ -313,7 +316,7 @@ class etemplate_widget_customfields extends etemplate_widget_transformer case 'radio': if (count($field['values']) == 1 && isset($field['values']['@'])) { - $field['values'] = egw_customfields::get_options_from_file($field['values']['@']); + $field['values'] = Api\Storage\Customfields::get_options_from_file($field['values']['@']); } // keep extra values set by app code, eg. addressbook advanced search if (is_array(self::$request->sel_options[self::$prefix.$fname])) @@ -329,7 +332,7 @@ class etemplate_widget_customfields extends etemplate_widget_transformer $options = self::$request->sel_options[self::$prefix.$fname]; if (is_array($options)) { - etemplate_widget_menupopup::fix_encoded_options($options); + Select::fix_encoded_options($options); self::$request->sel_options[self::$prefix.$fname] = $options; } break; diff --git a/etemplate/inc/class.etemplate_widget_date.inc.php b/api/src/Etemplate/Widget/Date.php similarity index 88% rename from etemplate/inc/class.etemplate_widget_date.inc.php rename to api/src/Etemplate/Widget/Date.php index 4469ddeff9..96fc2d3c39 100644 --- a/etemplate/inc/class.etemplate_widget_date.inc.php +++ b/api/src/Etemplate/Widget/Date.php @@ -3,16 +3,20 @@ * EGroupware - eTemplate serverside date widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-11 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @author Nathan Gray * @copyright 2011 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api; + /** * eTemplate date widget * @@ -35,7 +39,7 @@ * for the field, the conversion is done as needed understand what the application * sends, and to give the application what it wants when the form is submitted. */ -class etemplate_widget_date extends etemplate_widget_transformer +class Date extends Transformer { protected static $transformation = array( 'type' => array('date-houronly' => 'select-hour') @@ -76,7 +80,7 @@ class etemplate_widget_date extends etemplate_widget_transformer * Perform any needed data manipulation on each row * before sending it to client. * - * This is used by etemplate_widget_nextmatch on each row to do any needed + * This is used by Nextmatch on each row to do any needed * adjustments. If not needed, don't implement it. * * @param type $cname @@ -91,7 +95,7 @@ class etemplate_widget_date extends etemplate_widget_transformer $form_name = self::form_name($cname, $this->id, $expand); $value =& $this->get_array($data, $form_name, true); - $value = $this->format_date($value); + if (true) $value = $this->format_date($value); } /** @@ -105,11 +109,11 @@ class etemplate_widget_date extends etemplate_widget_transformer if ($this->attrs['dataformat'] && !is_numeric($value)) { - $date = date_create_from_format($this->attrs['dataformat'], $value, egw_time::$user_timezone); + $date = Api\DateTime::createFromFormat($this->attrs['dataformat'], $value, Api\DateTime::$user_timezone); } else { - $date = new egw_time($value); + $date = new Api\DateTime($value); } if($this->type == 'date-timeonly') { @@ -160,17 +164,17 @@ class etemplate_widget_date extends etemplate_widget_transformer } if($value) { - $date = new egw_time($value); + $date = new Api\DateTime($value); } if (!empty($this->attrs['min'])) { if(is_numeric($this->attrs['min'])) { - $min = new egw_time(strtotime( $this->attrs['min'] . 'days')); + $min = new Api\DateTime(strtotime( $this->attrs['min'] . 'days')); } else { - $min = new egw_time(strtotime($this->attrs['min'])); + $min = new Api\DateTime(strtotime($this->attrs['min'])); } if($value < $min) { @@ -185,11 +189,11 @@ class etemplate_widget_date extends etemplate_widget_transformer { if(is_numeric($this->attrs['max'])) { - $max = new egw_time(strtotime( $this->attrs['max'] . 'days')); + $max = new Api\DateTime(strtotime( $this->attrs['max'] . 'days')); } else { - $max = new egw_time(strtotime($this->attrs['max'])); + $max = new Api\DateTime(strtotime($this->attrs['max'])); } if($value < $max) { @@ -219,7 +223,7 @@ class etemplate_widget_date extends etemplate_widget_transformer // this is not really a user error, but one of the clientside engine self::set_validation_error($form_name,lang("'%1' is not a valid date !!!", $value).' '.$this->dataformat); } - //error_log("$this : ($valid)" . egw_time::to($valid)); + //error_log("$this : ($valid)" . Api\DateTime::to($valid)); } } } diff --git a/api/src/Etemplate/Widget/Description.php b/api/src/Etemplate/Widget/Description.php new file mode 100644 index 0000000000..f77b959a84 --- /dev/null +++ b/api/src/Etemplate/Widget/Description.php @@ -0,0 +1,31 @@ + + * @copyright 2002-16 by RalfBecker@outdoor-training.de + * @version $Id$ + */ + +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; + +/** + * Description widget + * + * Reimplemented to set legacy options + */ +class Description extends Etemplate\Widget +{ + /** + * (Array of) comma-separated list of legacy options to automatically replace when parsing with set_attrs + * + * @var string|array + */ + protected $legacy_options = 'bold-italic,link,activate_links,label_for,link_target,link_popup_size,link_title'; +} diff --git a/etemplate/inc/class.etemplate_widget_entry.inc.php b/api/src/Etemplate/Widget/Entry.php similarity index 91% rename from etemplate/inc/class.etemplate_widget_entry.inc.php rename to api/src/Etemplate/Widget/Entry.php index 4ea21e8d90..0bcfa37b63 100644 --- a/etemplate/inc/class.etemplate_widget_entry.inc.php +++ b/api/src/Etemplate/Widget/Entry.php @@ -1,25 +1,27 @@ * @version $Id: class.contact_widget.inc.php 46844 2014-05-07 09:00:59Z ralfbecker $ */ +namespace EGroupware\Api\Etemplate\Widget; + /** - * eTemplate Extension: Entry widget + * eTemplate Entry widget * * This widget can be used to fetch fields of any entry specified by its ID. * The entry is loaded once and shared amoung widget that need it. */ -abstract class etemplate_widget_entry extends etemplate_widget_transformer +abstract class Entry extends Transformer { /** @@ -38,7 +40,7 @@ abstract class etemplate_widget_entry extends etemplate_widget_transformer /** * Array with a transformation description, based on attributes to modify. * - * @see etemplate_widget_transformer::$transformation + * @see Transformer::$transformation * * @var array */ @@ -64,7 +66,7 @@ abstract class etemplate_widget_entry extends etemplate_widget_transformer $attrs['type'] = $this->type; $attrs['id'] = $this->id; - + $form_name = self::form_name($cname, $this->id); $data_id = $attrs['value'] ? self::form_name($cname, $attrs['value']) : self::form_name($cname, self::ID_PREFIX . $this->id); @@ -91,7 +93,7 @@ abstract class etemplate_widget_entry extends etemplate_widget_transformer // Set the new value so transformer can find it. Use prefix to avoid changing the original value $new_value =& self::get_array(self::$request->content, self::ID_PREFIX .$this->id, true, false); - $new_value = $data; + if (true) $new_value = $data; $this->id = self::ID_PREFIX . $this->id . "[{$attrs['field']}]"; $old_type = self::getElementAttribute($this->id, 'type'); diff --git a/etemplate/inc/class.etemplate_widget_file.inc.php b/api/src/Etemplate/Widget/File.php similarity index 90% rename from etemplate/inc/class.etemplate_widget_file.inc.php rename to api/src/Etemplate/Widget/File.php index bedc48c546..1c9df80cdb 100644 --- a/etemplate/inc/class.etemplate_widget_file.inc.php +++ b/api/src/Etemplate/Widget/File.php @@ -3,19 +3,27 @@ * EGroupware - eTemplate serverside file upload widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Nathan Gray * @copyright 2011 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + +// explicitly import old not yet ported classes +use egw_json_response; + /** * eTemplate file upload widget * Uses AJAX to send file(s) to server, and stores for submit */ -class etemplate_widget_file extends etemplate_widget +class File extends Etemplate\Widget { /** * Constructor @@ -47,12 +55,12 @@ class etemplate_widget_file extends etemplate_widget $response = egw_json_response::get(); $request_id = str_replace(' ', '+', rawurldecode($_REQUEST['request_id'])); $widget_id = $_REQUEST['widget_id']; - if(!self::$request = etemplate_request::read($request_id)) { + if(!self::$request = Etemplate\Request::read($request_id)) { $response->error("Could not read session"); return; } - if (!($template = etemplate_widget_template::instance(self::$request->template['name'], self::$request->template['template_set'], + if (!($template = Template::instance(self::$request->template['name'], self::$request->template['template_set'], self::$request->template['version'], self::$request->template['load_via']))) { // Can't use callback @@ -167,7 +175,7 @@ class etemplate_widget_file extends etemplate_widget $temp_name = basename($file['tmp_name']); $file_data[$temp_name] = array( // Use egw_vfs to avoid UTF8 / non-ascii issues - 'name' => egw_vfs::basename($file['name']), + 'name' => Api\Vfs::basename($file['name']), 'type' => $file['type'] ); } @@ -237,22 +245,27 @@ class etemplate_widget_file extends etemplate_widget * @param string $dir - directory path * @link http://php.net/manual/en/function.rmdir.php */ - private static function rrmdir($dir) { - if (is_dir($dir)) { - $objects = scandir($dir); - foreach ($objects as $object) { - if ($object != "." && $object != "..") { - if (filetype($dir . "/" . $object) == "dir") { - rrmdir($dir . "/" . $object); - } else { - unlink($dir . "/" . $object); - } - } - } - reset($objects); - rmdir($dir); - } - } + private static function rrmdir($dir) + { + if (is_dir($dir)) + { + foreach (scandir($dir) as $object) + { + if ($object != "." && $object != "..") + { + if (filetype($dir . "/" . $object) == "dir") + { + self::rrmdir($dir . "/" . $object); + } + else + { + unlink($dir . "/" . $object); + } + } + } + rmdir($dir); + } + } /** * Validate input @@ -303,4 +316,3 @@ class etemplate_widget_file extends etemplate_widget } } } -etemplate_widget::registerWidget('etemplate_widget_file', array('file')); diff --git a/etemplate/inc/class.etemplate_widget_gantt.inc.php b/api/src/Etemplate/Widget/Gantt.php similarity index 78% rename from etemplate/inc/class.etemplate_widget_gantt.inc.php rename to api/src/Etemplate/Widget/Gantt.php index 84db5dd58f..eba42c5e44 100644 --- a/etemplate/inc/class.etemplate_widget_gantt.inc.php +++ b/api/src/Etemplate/Widget/Gantt.php @@ -3,14 +3,16 @@ * EGroupware - eTemplate serverside gantt widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Nathan Gray * @copyright 2014 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + egw_framework::includeCSS('/phpgwapi/js/dhtmlxGantt/codebase/dhtmlxgantt.css'); /** @@ -18,7 +20,7 @@ egw_framework::includeCSS('/phpgwapi/js/dhtmlxGantt/codebase/dhtmlxgantt.css'); * * The Gantt widget accepts children, and uses them as simple filters */ -class etemplate_widget_gantt extends etemplate_widget_box +class Gantt extends Box { // No legacy options protected $legacy_options = array(); @@ -34,6 +36,8 @@ class etemplate_widget_gantt extends etemplate_widget_box */ public function validate($cname, array $expand, array $content, &$validated=array()) { + unset($expand); // not used, but required by function signature + $value = self::get_array($content, $cname); $validated[$cname] = array( 'action' => $value['action'], @@ -42,5 +46,3 @@ class etemplate_widget_gantt extends etemplate_widget_box } } -// register class for layout widgets, which can have an own namespace -//etemplate_widget::registerWidget('etemplate_widget_box', array('box', 'hbox', 'vbox', 'groupbox')); \ No newline at end of file diff --git a/etemplate/inc/class.etemplate_widget_grid.inc.php b/api/src/Etemplate/Widget/Grid.php similarity index 89% rename from etemplate/inc/class.etemplate_widget_grid.inc.php rename to api/src/Etemplate/Widget/Grid.php index 4fb2736c78..11319e8ed0 100644 --- a/etemplate/inc/class.etemplate_widget_grid.inc.php +++ b/api/src/Etemplate/Widget/Grid.php @@ -3,14 +3,18 @@ * EGroupware - eTemplate serverside grid widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-14 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; + /** * eTemplate grid widget incl. row(s) and column(s) * @@ -36,12 +40,12 @@ * * */ -class etemplate_widget_grid extends etemplate_widget_box +class Grid extends Box { /** * (Array of) comma-separated list of legacy options to automatically replace when parsing with set_attrs * - * Not used for grid, just need to be set to unset array of etemplate_widget_box + * Not used for grid, just need to be set to unset array of extended Box * * @var string|array */ @@ -57,8 +61,8 @@ class etemplate_widget_grid extends etemplate_widget_box * - as a grid can contain other grid's as direct child, we have to backup and initialise $columns_disabled in grid run! * * @param string $method_name - * @param array $params=array('') parameter(s) first parameter has to be the cname, second $expand! - * @param boolean $respect_disabled=false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children + * @param array $params =array('') parameter(s) first parameter has to be the cname, second $expand! + * @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children * @param array $columns_disabled=array() disabled columns */ public function run($method_name, $params=array(''), $respect_disabled=false, &$columns_disabled=array()) @@ -83,7 +87,7 @@ class etemplate_widget_grid extends etemplate_widget_box $params[1] = $old_expand; return false; // return } - + if ($this->id) $cname = self::form_name($cname, $this->id, $expand); if ($expand['cname'] !== $cname && $cname) { @@ -176,9 +180,9 @@ class etemplate_widget_grid extends etemplate_widget_box foreach(array_merge(array($direct_child), $n ? array() : $direct_child->children) as $child) { $pat = $child->id; - while(($pat = strstr($pat, '$'))) + while(($patstr = strstr($pat, '$'))) { - $pat = substr($pat,$pat[1] == '{' ? 2 : 1); + $pat = substr($patstr,$patstr[1] == '{' ? 2 : 1); switch ($this->type) { @@ -201,6 +205,7 @@ class etemplate_widget_grid extends etemplate_widget_box ($value = self::get_array(self::$request->content,$fname)) !== null) // null = not found (can be false!) { //error_log(__METHOD__."('$cname', ) $this autorepeating row $expand[row] because of $child->id = '$fname' is ".array2string($value)); + unset($value); return true; } } @@ -210,4 +215,4 @@ class etemplate_widget_grid extends etemplate_widget_box return false; } } -etemplate_widget::registerWidget('etemplate_widget_grid', array('grid', 'rows', 'row', 'columns', 'column')); +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Grid', array('grid', 'rows', 'row', 'columns', 'column')); diff --git a/etemplate/inc/class.etemplate_widget_historylog.inc.php b/api/src/Etemplate/Widget/HistoryLog.php similarity index 89% rename from etemplate/inc/class.etemplate_widget_historylog.inc.php rename to api/src/Etemplate/Widget/HistoryLog.php index 5b60719eb1..ceb15b3dbb 100644 --- a/etemplate/inc/class.etemplate_widget_historylog.inc.php +++ b/api/src/Etemplate/Widget/HistoryLog.php @@ -1,22 +1,26 @@ id, $expand); $value = self::get_array($content, $form_name); $valid =& self::get_array($validated, $form_name, true); - $valid = $value; + if (true) $valid = $value; return true; } } +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\HistoryLog', 'historylog'); diff --git a/etemplate/inc/class.etemplate_widget_htmlarea.inc.php b/api/src/Etemplate/Widget/HtmlArea.php similarity index 78% rename from etemplate/inc/class.etemplate_widget_htmlarea.inc.php rename to api/src/Etemplate/Widget/HtmlArea.php index 0ca46a06de..472318a5cb 100644 --- a/etemplate/inc/class.etemplate_widget_htmlarea.inc.php +++ b/api/src/Etemplate/Widget/HtmlArea.php @@ -3,18 +3,23 @@ * EGroupware - eTemplate serverside htmlarea widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-11 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + /** * eTemplate htmlarea widget */ -class etemplate_widget_htmlarea extends etemplate_widget +class HtmlArea extends Etemplate\Widget { protected $legacy_options = 'mode,height,width,expand_toolbar,base_href'; @@ -32,12 +37,12 @@ class etemplate_widget_htmlarea extends etemplate_widget { $form_name = self::form_name($cname, $this->id); - $config = egw_ckeditor_config::get_ckeditor_config_array($this->attrs['mode'], $this->attrs['height'], + $config = Api\Html\CkEditorConfig::get_ckeditor_config_array($this->attrs['mode'], $this->attrs['height'], $this->attrs['expand_toolbar'],$this->attrs['base_href'] ); // User preferences $font = $GLOBALS['egw_info']['user']['preferences']['common']['rte_font']; - $font_size = egw_ckeditor_config::font_size_from_prefs(); + $font_size = Api\Html\CkEditorConfig::font_size_from_prefs(); $font_span = ''; @@ -70,10 +75,11 @@ class etemplate_widget_htmlarea extends etemplate_widget // only purify for html, mode "ascii" is NO html and content get lost! if ($this->attrs['mode'] != 'ascii') { - $value = html::purify($value, $this->attrs['validation_rules']); + $value = Api\Html::purify($value, $this->attrs['validation_rules']); } $valid =& self::get_array($validated, $form_name, true); if (true) $valid = $value; } } } +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\HtmlArea', 'htmlarea'); diff --git a/etemplate/inc/class.etemplate_widget_image.inc.php b/api/src/Etemplate/Widget/Image.php similarity index 84% rename from etemplate/inc/class.etemplate_widget_image.inc.php rename to api/src/Etemplate/Widget/Image.php index 2ee3a24889..7969ab83dc 100644 --- a/etemplate/inc/class.etemplate_widget_image.inc.php +++ b/api/src/Etemplate/Widget/Image.php @@ -3,19 +3,25 @@ * EGroupware - eTemplate serverside image widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Nathan Gray * @copyright 2011 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + /** * eTemplate image widget + * * Displays image from URL, vfs, or finds by name */ -class etemplate_widget_image extends etemplate_widget +class Image extends Etemplate\Widget { /** * Fill type options in self::$request->sel_options to be used on the client @@ -36,7 +42,7 @@ class etemplate_widget_image extends etemplate_widget $img = $image; list($app) = explode('.',$form_name); } - $src = common::find_image($app, $img); + $src = Api\Image::find($app, $img); if(!$this->id) { // self::setElementAttribute($this->attrs['src'], 'id', $this->attrs['src']); @@ -44,4 +50,3 @@ class etemplate_widget_image extends etemplate_widget self::setElementAttribute($this->attrs['src'], 'src', $src); } } -etemplate_widget::registerWidget('etemplate_widget_image', array('image')); diff --git a/etemplate/inc/class.etemplate_widget_itempicker.inc.php b/api/src/Etemplate/Widget/ItemPicker.php similarity index 69% rename from etemplate/inc/class.etemplate_widget_itempicker.inc.php rename to api/src/Etemplate/Widget/ItemPicker.php index 7c1cebaf04..1b27fdd6e6 100755 --- a/etemplate/inc/class.etemplate_widget_itempicker.inc.php +++ b/api/src/Etemplate/Widget/ItemPicker.php @@ -3,20 +3,28 @@ * EGroupware - eTemplate serverside itempicker widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker * @author Christian Binder - * @copyright 2002-11 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @copyright 2012 by Christian Binder * @version $Id: class.etemplate_widget_itempicker.inc.php 36221 2011-08-20 10:27:38Z jaytraxx $ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + +// explicitly import old not yet ported classes +use egw_json_response; + /** * eTemplate itempicker widget */ -class etemplate_widget_itempicker extends etemplate_widget +class ItemPicker extends Etemplate\Widget { /** * Constructor @@ -30,18 +38,18 @@ class etemplate_widget_itempicker extends etemplate_widget parent::__construct($xml); } } - + /** * Find items that match the given parameters * using the egw_link class */ - public static function ajax_item_search($app, $type, $pattern, $options=array()) { + public static function ajax_item_search($app, $type, $pattern, $options=array()) + { $options['type'] = $type ? $type : $options['type']; - $items = egw_link::query($app, $pattern, $options); + $items = Api\Link::query($app, $pattern, $options); $response = egw_json_response::get(); $response->data($items); } } - -etemplate_widget::registerWidget('etemplate_widget_itempicker', array('itempicker')); \ No newline at end of file +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\ItemPicker', 'itempicker'); \ No newline at end of file diff --git a/etemplate/inc/class.etemplate_widget_link.inc.php b/api/src/Etemplate/Widget/Link.php similarity index 79% rename from etemplate/inc/class.etemplate_widget_link.inc.php rename to api/src/Etemplate/Widget/Link.php index 977b94a25f..c6772f1c69 100644 --- a/etemplate/inc/class.etemplate_widget_link.inc.php +++ b/api/src/Etemplate/Widget/Link.php @@ -3,19 +3,28 @@ * EGroupware - eTemplate serverside of linking widgets * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Nathan Gray * @copyright 2011 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + +// explicitly import old not yet ported classes +use egw_json_response; +use common; // egw_exit + /** * eTemplate link widgets * Deals with creation and display of links between entries in various participating egw applications */ -class etemplate_widget_link extends etemplate_widget +class Link extends Etemplate\Widget { public $public_functions = array( @@ -28,7 +37,7 @@ class etemplate_widget_link extends etemplate_widget * Constructor * * @param string|XMLReader $xml string with xml or XMLReader positioned on the element to construct - * @throws egw_exception_wrong_parameter + * @throws Api\Exception\WrongParameter */ public function __construct($xml = '') { @@ -66,8 +75,9 @@ class etemplate_widget_link extends etemplate_widget if($value && !is_array($value) && !$this->attrs['only_app']) { // Try to explode - if(count(explode(':',$value)) < 2) { - throw new egw_exception_wrong_parameter("Wrong value sent to $this, needs to be an array. ".array2string($value)); + if(count(explode(':',$value)) < 2) + { + throw new Api\Exception\WrongParameter("Wrong value sent to $this, needs to be an array. ".array2string($value)); } list($app, $id) = explode(':', $value,2); $value = array('app' => $app, 'id' => $id); @@ -84,7 +94,7 @@ class etemplate_widget_link extends etemplate_widget if($attrs['type'] == 'link-list') { $app = $value['to_app']; $id = $value['to_id']; - $links = egw_link::get_links($app,$id,'','link_lastmod DESC',true, $value['show_deleted']); + $links = Api\Link::get_links($app,$id,'','link_lastmod DESC',true, $value['show_deleted']); foreach($links as $link) { $value[] = $link; } @@ -97,7 +107,7 @@ class etemplate_widget_link extends etemplate_widget public static function ajax_link_search($app, $type, $pattern, $options=array()) { $options['type'] = $type ? $type : $options['type']; if(!$options['num_rows']) $options['num_rows'] = 1000; - $links = egw_link::query($app, $pattern, $options); + $links = Api\Link::query($app, $pattern, $options); $linksc = array_combine(array_map(create_function('$k', 'return (string)" ".$k;'), array_keys($links)), $links); $response = egw_json_response::get(); $response->data($linksc); @@ -112,7 +122,7 @@ class etemplate_widget_link extends etemplate_widget */ public static function ajax_link_title($app,$id) { - $title = egw_link::title($app, $id); + $title = Api\Link::title($app, $id); //error_log(__METHOD__."('$app', '$id') = ".array2string($title)); egw_json_response::get()->data($title); } @@ -130,7 +140,7 @@ class etemplate_widget_link extends etemplate_widget { if(count($ids)) { - $response[$app] = egw_link::titles($app, $ids); + $response[$app] = Api\Link::titles($app, $ids); } else { @@ -143,10 +153,11 @@ class etemplate_widget_link extends etemplate_widget /** * Create links */ - public static function ajax_link($app, $id, Array $links) { + public static function ajax_link($app, $id, Array $links) + { // Files need to know full path in tmp directory foreach($links as $key => $link) { - if($link['app'] == egw_link::VFS_APPNAME) { + if($link['app'] == Api\Link::VFS_APPNAME) { if (is_dir($GLOBALS['egw_info']['server']['temp_dir']) && is_writable($GLOBALS['egw_info']['server']['temp_dir'])) { $path = $GLOBALS['egw_info']['server']['temp_dir'] . '/' . $link['id']; @@ -159,38 +170,38 @@ class etemplate_widget_link extends etemplate_widget $links[$key]['id'] = $link; } } - $result = egw_link::link($app, $id, $links); + $result = Api\Link::link($app, $id, $links); $response = egw_json_response::get(); $response->data(is_array($id) ? $id : $result !== false); } - public function ajax_link_list($value) { - + public function ajax_link_list($value) + { $app = $value['to_app']; $id = $value['to_id']; - $links = egw_link::get_links($app,$id,$value['only_app'],'link_lastmod DESC',true, $value['show_deleted']); + $links = Api\Link::get_links($app,$id,$value['only_app'],'link_lastmod DESC',true, $value['show_deleted']); foreach($links as &$link) { - $link['title'] = egw_link::title($link['app'],$link['id'],$link); - if ($link['app'] == egw_link::VFS_APPNAME) + $link['title'] = Api\Link::title($link['app'],$link['id'],$link); + if ($link['app'] == Api\Link::VFS_APPNAME) { $link['target'] = '_blank'; $link['label'] = 'Delete'; $link['help'] = lang('Delete this file'); - $link['title'] = egw_vfs::decodePath($link['title']); - $link['icon'] = egw_link::vfs_path($link['app2'],$link['id2'],$link['id'],true); - $link['download_url'] = egw_vfs::download_url($link['icon']); + $link['title'] = Api\Vfs::decodePath($link['title']); + $link['icon'] = Api\Link::vfs_path($link['app2'],$link['id2'],$link['id'],true); + $link['download_url'] = Api\Vfs::download_url($link['icon']); // Make links to directories load in filemanager - if($link['type'] == 'httpd/unix-directory') + if($link['type'] == Api\Vfs::DIR_MIME_TYPE) { $link['target'] = 'filemanager'; } } else { - $link['icon'] = egw_link::get_registry($link['app'], 'icon'); + $link['icon'] = Api\Link::get_registry($link['app'], 'icon'); $link['label'] = 'Unlink'; $link['help'] = lang('Remove this link (not the entry itself)'); } @@ -209,18 +220,18 @@ class etemplate_widget_link extends etemplate_widget $result = false; if((int)$link_id > 0) { - solink::update_remark((int)$link_id, $comment); + Api\Link::update_remark((int)$link_id, $comment); $result = true; } else { - $link = egw_link::get_link((int)$link_id); - if($link && $link['app'] == egw_link::VFS_APPNAME) + $link = Api\Link::get_link((int)$link_id); + if($link && $link['app'] == Api\Link::VFS_APPNAME) { - $files = egw_link::list_attached($link['app2'],$link['id2']); + $files = Api\Link::list_attached($link['app2'],$link['id2']); $file = $files[(int)$link_id]; - $path = egw_link::vfs_path($link['app2'],$link['id2'],$file['id']); - $result = egw_vfs::proppatch($path, array(array('name' => 'comment', 'val' => $comment))); + $path = Api\Link::vfs_path($link['app2'],$link['id2'],$file['id']); + $result = Api\Vfs::proppatch($path, array(array('name' => 'comment', 'val' => $comment))); } } $response = egw_json_response::get(); @@ -239,14 +250,16 @@ class etemplate_widget_link extends etemplate_widget } if(!is_array($files)) $files = array($files); - foreach($files as $target) { - egw_link::link_file($app, $id, $target); + foreach($files as $target) + { + Api\Link::link_file($app, $id, $target); } } - public function ajax_delete($value) { + public function ajax_delete($value) + { $response = egw_json_response::get(); - $response->data(egw_link::unlink($value)); + $response->data(Api\Link::unlink($value)); } /** @@ -261,17 +274,17 @@ class etemplate_widget_link extends etemplate_widget { $app = $_GET['app']; $id = $_GET['id']; - if(egw_link::file_access($app, $id)) + if(Api\Link::file_access($app, $id)) { - $app_path = egw_link::vfs_path($app,$id,'',true); + $app_path = Api\Link::vfs_path($app,$id,'',true); // Pass the files linked, not the entry path - $files = egw_vfs::find($app_path); + $files = Api\Vfs::find($app_path); if($files[0] == $app_path) { array_shift($files); } - egw_vfs::download_zip($files, egw_link::title($app, $id)); + Api\Vfs::download_zip($files, Api\Link::title($app, $id)); common::egw_exit(); } } @@ -299,7 +312,7 @@ class etemplate_widget_link extends etemplate_widget { $value = $value_in =& self::get_array($content, $form_name); - // keep values added into request by other ajax-functions, eg. files draged into htmlarea (etemplate_widget_vfs) + // keep values added into request by other ajax-functions, eg. files draged into htmlarea (Vfs) if ((!$value || !$value['to_id']) && is_array($expand['cont'][$this->id]) && !empty($expand['cont'][$this->id]['to_id'])) { $value['to_id'] = $expand['cont'][$this->id]['to_id']; @@ -318,7 +331,7 @@ class etemplate_widget_link extends etemplate_widget // Do we have enough information to link automatically? if(is_array($value) && $value['to_id']) { - egw_link::link($value['to_app'], $value['to_id'], $link['app'], $link['id']); + Api\Link::link($value['to_app'], $value['to_id'], $link['app'], $link['id']); } else { @@ -345,7 +358,7 @@ class etemplate_widget_link extends etemplate_widget { if(!is_array($value['to_id'])) $value['to_id'] = array(); $value['to_id'][] = array( - 'app' => egw_link::VFS_APPNAME, + 'app' => Api\Link::VFS_APPNAME, 'id' => array( 'name' => $attrs['name'], 'type' => $attrs['type'], diff --git a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php b/api/src/Etemplate/Widget/Nextmatch.php similarity index 95% rename from etemplate/inc/class.etemplate_widget_nextmatch.inc.php rename to api/src/Etemplate/Widget/Nextmatch.php index 166fc2ee17..e411ce01e9 100644 --- a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php +++ b/api/src/Etemplate/Widget/Nextmatch.php @@ -3,14 +3,25 @@ * EGroupware - eTemplate serverside implementation of the nextmatch widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-11 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + +// explicitly import old not yet ported classes +use egw_json_response; +use egw; +use egw_framework; // includeCSS +use categories; + /** * eTemplate serverside implementation of the nextmatch widget * @@ -78,7 +89,7 @@ * 'placeholder' => // I String Optional text to display in the empty row placeholder. If not provided, it's "No matches found." * 'placeholder_actions' => // I Array Optional list of actions allowed on the placeholder. If not provided, it's ["add"]. */ -class etemplate_widget_nextmatch extends etemplate_widget +class Nextmatch extends Etemplate\Widget { public function __construct($xml='') { @@ -198,15 +209,15 @@ class etemplate_widget_nextmatch extends etemplate_widget { $value['options-cat_id'][''] = lang('All categories'); } - $value['options-cat_id'] += etemplate_widget_menupopup::typeOptions('select-cat', ',,'.$cat_app,$no_lang=true,false,$value['cat_id']); - etemplate_widget_menupopup::fix_encoded_options($value['options-cat_id']); + $value['options-cat_id'] += Select::typeOptions('select-cat', ',,'.$cat_app,$no_lang=true,false,$value['cat_id']); + Select::fix_encoded_options($value['options-cat_id']); } // Favorite group for admins if($GLOBALS['egw_info']['apps']['admin'] && $value['favorites']) { self::$request->sel_options[$form_name]['favorite']['group'] = array('all' => lang('All users')) + - etemplate_widget_menupopup::typeOptions('select-account',',groups'); + Select::typeOptions('select-account',',groups'); } foreach($value as $name => &$_value) { @@ -217,7 +228,7 @@ class etemplate_widget_nextmatch extends etemplate_widget { self::$request->sel_options[$select] = array(); } - etemplate_widget_menupopup::fix_encoded_options($_value, TRUE); + Select::fix_encoded_options($_value, TRUE); self::$request->sel_options[$select] += $_value; // The client doesn't need them in content, but we can't unset them because // some apps don't send them on re-load, pulling them from the session @@ -274,7 +285,7 @@ class etemplate_widget_nextmatch extends etemplate_widget static public function ajax_get_rows($exec_id, array $queriedRange, array $filters = array(), $form_name='nm', array $knownUids=null, $lastModified=null) { - self::$request = etemplate_request::read($exec_id); + self::$request = Etemplate\Request::read($exec_id); // fix for somehow empty etemplate request content if (!is_array(self::$request->content)) { @@ -289,7 +300,7 @@ class etemplate_widget_nextmatch extends etemplate_widget } // Validate filters - if (($template = etemplate_widget_template::instance(self::$request->template['name'], self::$request->template['template_set'], + if (($template = Template::instance(self::$request->template['name'], self::$request->template['template_set'], self::$request->template['version'], self::$request->template['load_via']))) { $template = $template->getElementById($form_name, strpos($form_name, 'history') === 0 ? 'historylog' : 'nextmatch'); @@ -336,7 +347,7 @@ class etemplate_widget_nextmatch extends etemplate_widget if($app) { $GLOBALS['egw_info']['flags']['currentapp'] = $app; - translation::add_app($app); + Api\Translation::add_app($app); } // If specific data requested, just do that if (($row_id = $value['row_id']) && $queriedRange['refresh']) @@ -346,7 +357,7 @@ class etemplate_widget_nextmatch extends etemplate_widget } $rows = $result['data'] = $result['order'] = array(); $result['total'] = self::call_get_rows($value, $rows, $result['readonlys'], null, null, $template); - $result['lastModification'] = egw_time::to('now', 'ts')-1; + $result['lastModification'] = Api\DateTime::to('now', 'ts')-1; if (isset($GLOBALS['egw_info']['flags']['app_header']) && self::$request->app_header != $GLOBALS['egw_info']['flags']['app_header']) { @@ -371,11 +382,11 @@ class etemplate_widget_nextmatch extends etemplate_widget $modified = $row[$row_modified]; if (isset($modified) && !(is_int($modified) || is_string($modified) && is_numeric($modified))) { - $modified = egw_time::to(str_replace('Z', '', $modified), 'ts'); + $modified = Api\DateTime::to(str_replace('Z', '', $modified), 'ts'); } // check if we need to send the data - //error_log("$id Known: " . (array_search($id, $knownUids) !== false ? 'Yes' : 'No') . ' Modified: ' . egw_time::to($row[$row_modified]) . ' > ' . egw_time::to($lastModified).'? ' . ($row[$row_modified] > $lastModified ? 'Yes' : 'No')); + //error_log("$id Known: " . (array_search($id, $knownUids) !== false ? 'Yes' : 'No') . ' Modified: ' . Api\DateTime::to($row[$row_modified]) . ' > ' . Api\DateTime::to($lastModified).'? ' . ($row[$row_modified] > $lastModified ? 'Yes' : 'No')); if (!$row_id || !$knownUids || ($kUkey = array_search($id, $knownUids)) === false || !$lastModified || !isset($modified) || $modified > $lastModified) { @@ -392,7 +403,7 @@ class etemplate_widget_nextmatch extends etemplate_widget { foreach($row as &$options) { - etemplate_widget_menupopup::fix_encoded_options($options,true); + Select::fix_encoded_options($options,true); } } $result['rows'][$n] = $row; @@ -536,10 +547,10 @@ class etemplate_widget_nextmatch extends etemplate_widget * @param array &$readonlys =null * @param object $obj =null (internal) * @param string|array $method =null (internal) - * @param etemplate_widget $widget =null instanciated nextmatch widget to let it's widgets transform each row + * @param Etemplate\Widget $widget =null instanciated nextmatch widget to let it's widgets transform each row * @return int|boolean total items found of false on error ($value['get_rows'] not callable) */ - private static function call_get_rows(array &$value,array &$rows,array &$readonlys=null,$obj=null,$method=null, etemplate_widget $widget=null) + private static function call_get_rows(array &$value,array &$rows,array &$readonlys=null,$obj=null,$method=null, Etemplate\Widget $widget=null) { if (is_null($method)) $method = $value['get_rows']; @@ -597,7 +608,7 @@ class etemplate_widget_nextmatch extends etemplate_widget $row_template = $widget->getElementById($widget->attrs['template']); if(!$row_template) { - $row_template = etemplate_widget_template::instance($widget->attrs['template']); + $row_template = Template::instance($widget->attrs['template']); } // Try to find just the repeating part @@ -637,7 +648,7 @@ class etemplate_widget_nextmatch extends etemplate_widget $_row = array(1 => &$row); $repeating_row->run('set_row_value', array('',array('row' => 1), &$_row), true); } - else if (!$widget || get_class($widget) != 'etemplate_widget_historylog') + else if (!$widget || get_class($widget) != __NAMESPACE__.'\\HistoryLog') { // Fallback based on widget names //error_log(self::$request->template['name'] . ' had to fallback to run_beforeSendToClient() because it could not find the row'); @@ -651,7 +662,7 @@ class etemplate_widget_nextmatch extends etemplate_widget { foreach($row as &$options) { - etemplate_widget_menupopup::fix_encoded_options($options, true); + Select::fix_encoded_options($options, true); } } $rows[$n] = $row; @@ -681,7 +692,7 @@ class etemplate_widget_nextmatch extends etemplate_widget (is_int($value) || is_string($value) && is_numeric($value)) && ($value > 21000000 || $value < 19000000)) { - $value = egw_time::to($value, 'Y-m-d\TH:i:s\Z'); + $value = Api\DateTime::to($value, 'Y-m-d\TH:i:s\Z'); } } return $row; @@ -694,7 +705,7 @@ class etemplate_widget_nextmatch extends etemplate_widget */ private static function get_timestamps() { - return egw_cache::getTree(__CLASS__, 'timestamps', function() + return Api\Cache::getTree(__CLASS__, 'timestamps', function() { $timestamps = array(); foreach(scandir(EGW_SERVER_ROOT) as $app) @@ -1112,7 +1123,7 @@ class etemplate_widget_nextmatch extends etemplate_widget { if($this->attrs[$sub_template]) { - $row_template = etemplate_widget_template::instance($this->attrs[$sub_template]); + $row_template = Template::instance($this->attrs[$sub_template]); $row_template->run($method_name, $params, $respect_disabled); } } @@ -1143,31 +1154,31 @@ class etemplate_widget_nextmatch extends etemplate_widget { unset($row_ids, $type); // not used, but required by function signature - throw new Exception('Not yet implemented'); + throw new Api\Exception('Not yet implemented'); } } // Registration needs to go here, otherwise customfields won't be loaded until some other cf shows up -etemplate_widget::registerWidget('etemplate_widget_customfields', array('nextmatch-customfields')); +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Customfields', array('nextmatch-customfields')); /** * Extend selectbox so select options get parsed properly before being sent to client */ -class etemplate_widget_nextmatch_filterheader extends etemplate_widget_menupopup +class NextmatchFilterHeader extends Select { } +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\NextmatchFilterHeader', array('nextmatch-filterheader')); /** * Extend selectbox and change type so proper users / groups get loaded, according to preferences */ -class etemplate_widget_nextmatch_accountfilter extends etemplate_widget_menupopup +class NextmatchAccountFilter extends Select { /** * Parse and set extra attributes from xml in template object * * @param string|XMLReader $xml * @param boolean $cloned =true true: object does NOT need to be cloned, false: to set attribute, set them in cloned object - * @return etemplate_widget_template current object or clone, if any attribute was set */ public function set_attrs($xml, $cloned=true) { @@ -1176,11 +1187,12 @@ class etemplate_widget_nextmatch_accountfilter extends etemplate_widget_menupopu $this->attrs['type'] = 'select-account'; } } +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\NextmatchAccountFilter', array('nextmatch-accountfilter')); /** * A filter widget that fakes another (select) widget and turns it into a nextmatch filter widget. */ -class etemplate_widget_nextmatch_customfilter extends etemplate_widget_transformer +class NextmatchCustomFilter extends Transformer { protected $legacy_options = 'type,widget_options'; @@ -1202,7 +1214,7 @@ class etemplate_widget_nextmatch_customfilter extends etemplate_widget_transform list($type) = explode('-',$this->attrs['type']); if($type == 'select') { - if(in_array($this->attrs['type'], etemplate_widget_menupopup::$cached_types)) + if(in_array($this->attrs['type'], Select::$cached_types)) { $widget_type = $this->attrs['type']; } @@ -1222,4 +1234,4 @@ class etemplate_widget_nextmatch_customfilter extends etemplate_widget_transform parent::beforeSendToClient($cname, $expand); } } - +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\NextmatchCustomFilter', array('nextmatch-customfilter')); diff --git a/etemplate/inc/class.etemplate_widget_menupopup.inc.php b/api/src/Etemplate/Widget/Select.php similarity index 96% rename from etemplate/inc/class.etemplate_widget_menupopup.inc.php rename to api/src/Etemplate/Widget/Select.php index 81b8517f65..3b2b65e9fd 100644 --- a/etemplate/inc/class.etemplate_widget_menupopup.inc.php +++ b/api/src/Etemplate/Widget/Select.php @@ -3,20 +3,30 @@ * EGroupware - eTemplate serverside select widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-14 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + +// explicitly import old not yet ported classes +use categories; +use calendar_timezones; +use egw_json_response; + /** * eTemplate select widget * * @todo unavailable cats (eg. private cats of an other user) need to be preserved! */ -class etemplate_widget_menupopup extends etemplate_widget +class Select extends Etemplate\Widget { /** * If the selectbox has this many rows, give it a search box automatically @@ -85,7 +95,7 @@ class etemplate_widget_menupopup extends etemplate_widget * * @param string|XMLReader $xml * @param boolean $cloned =true true: object does NOT need to be cloned, false: to set attribute, set them in cloned object - * @return etemplate_widget_template current object or clone, if any attribute was set + * @return Template current object or clone, if any attribute was set * @todo Use legacy_attributes instead of leaving it to typeOptions method to parse them */ public function set_attrs($xml, $cloned=true) @@ -127,11 +137,11 @@ class etemplate_widget_menupopup extends etemplate_widget { $value = $value_in = self::get_array($content, $form_name); - $allowed = self::selOptions($form_name, true); // true = return array of option-values + $allowed2 = self::selOptions($form_name, true); // true = return array of option-values $type_options = self::typeOptions($this, // typeOptions thinks # of rows is the first thing in options ($this->attrs['rows'] && strpos($this->attrs['options'], $this->attrs['rows']) !== 0 ? $this->attrs['rows'].','.$this->attrs['options'] : $this->attrs['options'])); - $allowed = array_merge($allowed,array_keys($type_options)); + $allowed = array_merge($allowed2,array_keys($type_options)); if (!$this->attrs['multiple'] || !($this->attrs['options'] > 1)) $allowed[] = ''; @@ -467,7 +477,7 @@ class etemplate_widget_menupopup extends etemplate_widget /** * Fetch options for certain select-box types * - * @param string|etemplate_widget_menupopup $widget_type Type of widget, or actual widget to get attributes since $legacy_options are legacy + * @param string|Select $widget_type Type of widget, or actual widget to get attributes since $legacy_options are legacy * @param string $_legacy_options options string of widget * @param boolean $no_lang =false initial value of no_lang attribute (some types set it to true) * @param boolean $readonly =false for readonly we dont need to fetch all options, only the one for value @@ -717,7 +727,7 @@ class etemplate_widget_menupopup extends etemplate_widget break; case 'select-lang': - $options = translation::list_langs(); + $options = Api\Translation::list_langs(); $no_lang = True; break; @@ -732,7 +742,7 @@ class etemplate_widget_menupopup extends etemplate_widget } else { - $options = $type ? egw_time::getTimezones() : egw_time::getUserTimezones($value); + $options = $type ? Api\DateTime::getTimezones() : Api\DateTime::getUserTimezones($value); } break; } @@ -825,7 +835,7 @@ class etemplate_widget_menupopup extends etemplate_widget $info .= $acc['account_lid']; break; default: // use the phpgw default - $info = $GLOBALS['egw']->common->display_fullname($acc['account_lid'], + $info = Api\Accounts::format_username($acc['account_lid'], $acc['account_firstname'],$acc['account_lastname']); break; } @@ -855,5 +865,4 @@ class etemplate_widget_menupopup extends etemplate_widget $response->data($options); } } - -etemplate_widget::registerWidget('etemplate_widget_menupopup', array('selectbox','listbox','select','menupopup')); +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Select', array('selectbox', 'listbox', 'select', 'menupopup')); diff --git a/etemplate/inc/class.etemplate_widget_tabbox.inc.php b/api/src/Etemplate/Widget/Tabbox.php similarity index 92% rename from etemplate/inc/class.etemplate_widget_tabbox.inc.php rename to api/src/Etemplate/Widget/Tabbox.php index 3011af5ef7..e673a8bc08 100644 --- a/etemplate/inc/class.etemplate_widget_tabbox.inc.php +++ b/api/src/Etemplate/Widget/Tabbox.php @@ -3,14 +3,18 @@ * EGroupware - eTemplate serverside Tabs widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Nathan Gray * @copyright 2013 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; + /** * eTemplate Tabs widget stacks multiple sub-templates and lets you switch between them * @@ -25,7 +29,7 @@ * + id: optinal namespace (content attribute of template) * + add_tabs: true(default) add to given tabs to template, false replace tabs in template */ -class etemplate_widget_tabbox extends etemplate_widget +class Tabbox extends Etemplate\Widget { /** * Run a given method on all children @@ -60,10 +64,10 @@ class etemplate_widget_tabbox extends etemplate_widget //$this->tabs = array(); foreach($tabs as &$tab) { - $template= clone etemplate_widget_template::instance($tab['template']); + $template= clone Template::instance($tab['template']); if($tab['id']) $template->attrs['content'] = $tab['id']; $this->children[1]->children[] = $template; - $tab['url'] = etemplate_widget_template::rel2url($template->rel_path); + $tab['url'] = Template::rel2url($template->rel_path); //$this->tabs[] = $tab; unset($template); } @@ -112,4 +116,3 @@ class etemplate_widget_tabbox extends etemplate_widget } } } -etemplate_widget::registerWidget('etemplate_widget_tabbox', array('tabbox')); diff --git a/etemplate/inc/class.etemplate_widget_taglist.inc.php b/api/src/Etemplate/Widget/Taglist.php similarity index 87% rename from etemplate/inc/class.etemplate_widget_taglist.inc.php rename to api/src/Etemplate/Widget/Taglist.php index 8167738ac7..56e5f8bc30 100644 --- a/etemplate/inc/class.etemplate_widget_taglist.inc.php +++ b/api/src/Etemplate/Widget/Taglist.php @@ -3,18 +3,28 @@ * EGroupware - eTemplate serverside of tag list widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Nathan Gray * @copyright 2013 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + +// explicitly import old not yet ported classes +use egw_json_request; +use common; +use mail_compose; + /** * eTemplate tag list widget */ -class etemplate_widget_taglist extends etemplate_widget +class Taglist extends Etemplate\Widget { /** * Constructor @@ -38,7 +48,8 @@ class etemplate_widget_taglist extends etemplate_widget * Find entries that match query parameter (from link system) and format them * as the widget expects, a list of {id: ..., label: ...} objects */ - public static function ajax_search() { + public static function ajax_search() + { $app = $_REQUEST['app']; $type = $_REQUEST['type']; $query = $_REQUEST['query']; @@ -50,12 +61,12 @@ class etemplate_widget_taglist extends etemplate_widget if($query) { $options['account_type'] = $_REQUEST['account_type']; - $links = accounts::link_query($query, $options); + $links = Api\Accounts::link_query($query, $options); } } else { - $links = egw_link::query($app, $query, $options); + $links = Api\Link::query($app, $query, $options); } $results = array(); foreach($links as $id => $name) @@ -75,7 +86,8 @@ class etemplate_widget_taglist extends etemplate_widget * * Uses the mail application if available, or addressbook */ - public static function ajax_email() { + public static function ajax_email() + { // If no mail app access, use link system -> addressbook if(!$GLOBALS['egw_info']['apps']['mail']) { @@ -103,7 +115,7 @@ class etemplate_widget_taglist extends etemplate_widget if (!$this->is_readonly($cname, $form_name)) { $value = $value_in = self::get_array($content, $form_name); - $allowed = etemplate_widget_menupopup::selOptions($form_name); + $allowed = Select::selOptions($form_name); foreach((array) $value as $key => $val) { @@ -120,7 +132,7 @@ class etemplate_widget_taglist extends etemplate_widget self::set_validation_error($form_name,lang("'%1' is NOT allowed ('%2')!",$val,implode("','",array_keys($lists))),''); } } - else if($this->type == 'taglist-email' && !preg_match(etemplate_widget_url::EMAIL_PREG, $val) && + else if($this->type == 'taglist-email' && !preg_match(Url::EMAIL_PREG, $val) && // Allow merge placeholders. Might be a better way to do this though. !preg_match('/{{.+}}|\$\$.+\$\$/',$val) ) diff --git a/etemplate/inc/class.etemplate_widget_template.inc.php b/api/src/Etemplate/Widget/Template.php similarity index 92% rename from etemplate/inc/class.etemplate_widget_template.inc.php rename to api/src/Etemplate/Widget/Template.php index 51e4a5d3fa..6bbcb29144 100644 --- a/etemplate/inc/class.etemplate_widget_template.inc.php +++ b/api/src/Etemplate/Widget/Template.php @@ -3,15 +3,24 @@ * EGroupware - eTemplate serverside template widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ -// allow to call direct for tests (see end of class) +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; +use XMLReader; + +// explicitly import old not yet ported classes +use egw; // link + +/* allow to call direct for tests (see end of class) if (!isset($GLOBALS['egw_info'])) { $GLOBALS['egw_info'] = array( @@ -21,12 +30,12 @@ if (!isset($GLOBALS['egw_info'])) ) ); include_once '../../header.inc.php'; -} +} */ /** * eTemplate widget baseclass */ -class etemplate_widget_template extends etemplate_widget +class Template extends Etemplate\Widget { /** * Cache of already read templates @@ -49,12 +58,11 @@ class etemplate_widget_template extends etemplate_widget * @param string $template_set =null default try template-set from user and if not found "default" * @param string $version ='' * @param string $load_via ='' use given template to load $name - * @todo Reading customized templates from database - * @return etemplate_widget_template|boolean false if not found + * @return Template|boolean false if not found */ public static function instance($_name, $template_set=null, $version='', $load_via='') { - if (html::$ua_mobile) + if (Api\Header\UserAgent::mobile()) { $template_set = "mobile"; } @@ -107,7 +115,7 @@ class etemplate_widget_template extends etemplate_widget { if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'template') { - $template = new etemplate_widget_template($reader); + $template = new Template($reader); $template->rel_path = $path; //echo $template->id; _debug_array($template); @@ -148,7 +156,7 @@ class etemplate_widget_template extends etemplate_widget $template_path = '/'.$app.'/templates/'.$template_set.'/'.$rest.'.xet'; $default_path = '/'.$app.'/templates/default/'.$rest.'.xet'; - foreach(array(egw_vfs::PREFIX.self::VFS_TEMPLATE_PATH, EGW_SERVER_ROOT) as $prefix) + foreach(array(Api\Vfs::PREFIX.self::VFS_TEMPLATE_PATH, EGW_SERVER_ROOT) as $prefix) { if (file_exists($prefix.$template_path)) { @@ -201,7 +209,7 @@ class etemplate_widget_template extends etemplate_widget } else { - $url = egw_vfs::download_url($path); + $url = Api\Vfs::download_url($path); if ($url[0] == '/') $url = egw::link($url); @@ -238,7 +246,7 @@ class etemplate_widget_template extends etemplate_widget //error_log("$this running $method_name() cname: {$this->id} -> expand_name: $expand_name"); if($expand_name && $expand_name != $this->id) { - if (($row_template = etemplate_widget_template::instance($expand_name))) + if (($row_template = self::instance($expand_name))) { $row_template->run($method_name, $params, $respect_disabled); } @@ -269,7 +277,7 @@ class etemplate_widget_template extends etemplate_widget if ($GLOBALS['egw_info']['flags']['debug'] == 'etemplate_widget_template') { $name = isset($_GET['name']) ? $_GET['name'] : 'timesheet.edit'; - if (!($template = etemplate_widget_template::instance($name))) + if (!($template = Template::instance($name))) { header('HTTP-Status: 404 Not Found'); echo "Not Found

Not Found

The requested eTemplate '$name' was not found!

\n"; diff --git a/etemplate/inc/class.etemplate_widget_textbox.inc.php b/api/src/Etemplate/Widget/Textbox.php similarity index 91% rename from etemplate/inc/class.etemplate_widget_textbox.inc.php rename to api/src/Etemplate/Widget/Textbox.php index f90cf90da0..78b0896b07 100644 --- a/etemplate/inc/class.etemplate_widget_textbox.inc.php +++ b/api/src/Etemplate/Widget/Textbox.php @@ -3,14 +3,19 @@ * EGroupware - eTemplate serverside textbox widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-13 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use XMLReader; + /** * eTemplate textbox widget with following sub-types: * - textbox with optional multiline="true" and rows="123" @@ -21,7 +26,7 @@ * - passwd (passwords are never send back to client, instead a number of asterisks is send and replaced again!) * sub-types are either passed to constructor or set via 'type' attribute! */ -class etemplate_widget_textbox extends etemplate_widget +class Textbox extends Etemplate\Widget { /** * Constructor @@ -49,8 +54,8 @@ class etemplate_widget_textbox extends etemplate_widget * Reimplemented to handle legacy read-only by setting size < 0 * * @param string|XMLReader $xml - * @param boolean $cloned=true true: object does NOT need to be cloned, false: to set attribute, set them in cloned object - * @return etemplate_widget_template current object or clone, if any attribute was set + * @param boolean $cloned =true true: object does NOT need to be cloned, false: to set attribute, set them in cloned object + * @return Template current object or clone, if any attribute was set */ public function set_attrs($xml, $cloned=true) { @@ -192,4 +197,4 @@ class etemplate_widget_textbox extends etemplate_widget } } } -etemplate_widget::registerWidget('etemplate_widget_textbox', array('textbox','text','int','integer','float','passwd','hidden','colorpicker','hidden')); +Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Textbox', array('textbox','text','int','integer','float','passwd','hidden','colorpicker','hidden')); diff --git a/etemplate/inc/class.etemplate_widget_toolbar.inc.php b/api/src/Etemplate/Widget/Toolbar.php similarity index 87% rename from etemplate/inc/class.etemplate_widget_toolbar.inc.php rename to api/src/Etemplate/Widget/Toolbar.php index b756f66f29..d1dbd47f67 100644 --- a/etemplate/inc/class.etemplate_widget_toolbar.inc.php +++ b/api/src/Etemplate/Widget/Toolbar.php @@ -9,10 +9,14 @@ * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; + /** * eTemplate button widget */ -class etemplate_widget_toolbar extends etemplate_widget +class Toolbar extends Etemplate\Widget { /** * Validate toolbar @@ -37,4 +41,3 @@ class etemplate_widget_toolbar extends etemplate_widget } } } -etemplate_widget::registerWidget('etemplate_widget_toolbar', array('toolbar')); diff --git a/api/src/Etemplate/Widget/Transformer.php b/api/src/Etemplate/Widget/Transformer.php new file mode 100644 index 0000000000..b7e9ea6d89 --- /dev/null +++ b/api/src/Etemplate/Widget/Transformer.php @@ -0,0 +1,233 @@ + + * @copyright 2002-16 by RalfBecker@outdoor-training.de + * @version $Id$ + */ + +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + +/** + * eTemplate serverside base widget, to define new widgets using a transformation out of existing widgets + */ +abstract class Transformer extends Etemplate\Widget +{ + /** + * Array with a transformation description, based on attributes to modify. + * + * Exampels: + * + * * 'type' => array('some' => 'other') + * if 'type' attribute equals 'some' replace it with 'other' + * + * * 'type' => array('some' => array('type' => 'other', 'options' => 'otheroption') + * same as above, but additonally set 'options' attr to 'otheroption' + * + * --> leaf element is the action, if previous filters are matched: + * - if leaf is scalar, it just replaces the previous filter value + * - if leaf is an array, it contains assignments for (multiple) attributes: attr => value pairs + * + * * 'type' => array( + * 'some' => array(...), + * 'other' => array(...), + * '__default__' => array(...), + * ) + * it's possible to have a list of filters with actions to run, plus a '__default__' which matches all not explicitly named values + * + * * 'value' => array('__callback__' => 'app.class.method' || 'class::method' || 'method') + * run value through a *serverside* callback, eg. reading an entry based on it's given id + * callback signature: mixed function(mixed $attr[, array $attrs]) + * + * * 'value' => array('__js__' => 'function(value) { return value+5; }') + * run value through a *clientside* callback running in the context of the widget + * + * * 'name' => '@name[@options]' + * replace value of 'name' attribute with itself (@name) plus value of options in square brackets + * * 'value' => '@value[@options]' + * replace value array with value for key taken from value of options attribute + * + * --> attribute name prefixed with @ sign means value of given attribute + * + * @var array + */ + protected static $transformation = array(); + + /** + * Switching debug messages to error_log on/off + * + * @var boolean + */ + const DEBUG = false; + + /** + * Fill type options in self::$request->sel_options to be used on the client + * + * @param string $cname + */ + public function beforeSendToClient($cname, array $expand=array()) + { + $attrs = $this->attrs; + $form_name = self::form_name($cname, $this->id); + if (empty($this->id)) + { + error_log(__METHOD__."() $this has no id!"); + return; + } + $attrs['value'] = $value =& self::get_array(self::$request->content, $form_name, false, true); + $attrs['type'] = $this->type; + $attrs['id'] = $this->id; + + $unmodified = $attrs; + + // run the transformation + foreach(static::$transformation as $filter => $data) + { + $this->action($filter, $data, $attrs); + } + + //echo $this; _debug_array($unmodified); _debug_array($attrs); _debug_array(array_diff_assoc($attrs, $unmodified)); + // compute the difference and send it to the client as modifications + $type_changed = false; + foreach(array_diff_assoc($attrs, $unmodified) as $attr => $val) + { + switch($attr) + { + case 'value': + if ($val != $value) + { + $value = $val; // $value is reference to self::$request->content + } + break; + case 'sel_options': + self::$request->sel_options[$form_name] = $val; + break; + case 'type': // not an attribute in etemplate2 + $type_changed = true; + if($val == 'template') + { + // If the widget has been transformed into a template, we + // also need to try and instanciate & parse the template too + $transformed_template = Template::instance($attrs['template']); + if($transformed_template) + { + $this->expand_widget($transformed_template, $expand); + $transformed_template->run('beforeSendToClient',array($cname,$expand)); + } + $type_changed = false; + } + default: + self::setElementAttribute($form_name, $attr, $val); + break; + } + } + if($type_changed) + { + // Run the new widget type's beforeSendToClient + $expanded_child = self::factory($attrs['type'], false,$this->id); + $expanded_child->id = $this->id; + $expanded_child->type = $attrs['type']; + $expanded_child->attrs = $attrs; + $expanded_child->run('beforeSendToClient',array($cname,$expand)); + } + } + + /** + * Recursively run given action(s) on an attribute value + * + * @param string $attr attribute concerned + * @param int|string|array $action action to run + * @param array &$attrs attributes + * @throws Api\Exception\WrongParameter if $action is of wrong type + */ + protected function action($attr, $action, array &$attrs) + { + if (self::DEBUG) error_log(__METHOD__."('$attr', ".array2string($action).')'); + // action is an assignment + if (is_scalar($action) || is_null($action)) + { + // check if assignment contains placeholders --> replace them + if (strpos($action, '@') !== false) + { + $replace = array(); + foreach($attrs as $a => $v) + { + if (is_scalar($v) || is_null($v)) $replace['@'.$a] = $v; + } + $action = strtr($action, $replace); + // now replace with non-scalar value, eg. if values is an array: "@value", "@value[key] or "@value[@key]" + if (($a = strstr($action, '@'))) + { + $action = self::get_array($attrs, substr($a,1)); + } + } + $attrs[$attr] = $action; + if (self::DEBUG) error_log(__METHOD__."('$attr', ".array2string($action).") attrs['$attr'] = ".array2string($action).', attrs='.array2string($attrs)); + } + // action is a serverside callback + elseif(is_array($action) && isset($action['__callback__'])) + { + if (!is_string(($callback = $action['__callback__']))) + { + throw new Api\Exception\WrongParameter(__METHOD__."('$attr', ".array2string($action).', '.array2string($attrs).') wrong datatype for callback!'); + } + if (method_exists($this, $callback)) + { + $attrs[$attr] = $this->$callback($attrs[$attr], $attrs); + } + elseif(count(explode('.', $callback)) == 3) + { + $attrs[$attr] = ExecMethod($callback, $attrs[$attr], $attrs); + } + elseif (is_callable($callback, false)) + { + $attrs[$attr] = call_user_func($callback, $attrs[$attr], $attrs); + } + else + { + throw new Api\Exception\WrongParameter(__METHOD__."('$attr', ".array2string($action).', '.array2string($attrs).') wrong datatype for callback!'); + } + } + // action is a clientside callback + elseif(is_array($action) && isset($action['__js__'])) + { + // nothing to do here + } + // TODO: Might be a better way to handle when value to be set is an array + elseif(is_array($action) && $attr == 'sel_options') + { + $attrs[$attr] = $action; + } + // action is a switch --> check cases + elseif(is_array($action)) + { + // case matches --> run all actions + if (isset($action[$attrs[$attr]]) || !isset($action[$attrs[$attr]]) && isset($action['__default__'])) + { + $actions = isset($action[$attrs[$attr]]) ? $action[$attrs[$attr]] : $action['__default__']; + if(!is_array($actions)) + { + $attrs[$attr] = $actions; + $actions = array($attr => $actions); + } + if (self::DEBUG) error_log(__METHOD__."(attr='$attr', action=".array2string($action).") attrs['$attr']=='{$attrs[$attr]}' --> running actions"); + foreach($actions as $attr => $action) + { + $this->action($attr, $action, $attrs); + } + } + } + else + { + throw new Api\Exception\WrongParameter(__METHOD__."(attr='$attr', action=".array2string($action).', attrs='.array2string($attrs).') wrong datatype for action!'); + } + } +} diff --git a/etemplate/inc/class.etemplate_widget_tree.inc.php b/api/src/Etemplate/Widget/Tree.php similarity index 96% rename from etemplate/inc/class.etemplate_widget_tree.inc.php rename to api/src/Etemplate/Widget/Tree.php index 1712422573..7ba35e4ead 100644 --- a/etemplate/inc/class.etemplate_widget_tree.inc.php +++ b/api/src/Etemplate/Widget/Tree.php @@ -3,14 +3,25 @@ * EGroupware - eTemplate serverside tree widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package etemplate - * @subpackage api + * @package api + * @subpackage etemplate * @link http://www.egroupware.org * @author Nathan Gray * @copyright 2012 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + +// explicitly import old not yet ported classes +use egw_json_request; +use common; // egw_exit +use categories; +use egw_framework; + egw_framework::includeCSS('/phpgwapi/js/dhtmlxtree/codebase/dhtmlXTree.css'); /** @@ -20,7 +31,7 @@ egw_framework::includeCSS('/phpgwapi/js/dhtmlxtree/codebase/dhtmlXTree.css'); * * Example initialisation of tree via $sel_options array: * - * use \etemplate_widget_tree as tree; + * use Api\Etemplate\Widget\Tree as tree; * * $sel_options['tree'] = array( * tree::ID => 0, tree::CHILDREN => array( // ID of root has to be 0! @@ -51,11 +62,11 @@ egw_framework::includeCSS('/phpgwapi/js/dhtmlxtree/codebase/dhtmlXTree.css'); * - you can use attribute "std_images" to supply different standard images from default * [ "leaf.gif", "folderOpen.gif", "folderClosed.gif" ] * - images can also be specified as standard "app/image" string, client-side will convert them to url relativ to image_path - * - json autoloading uses identical data-structur and should use etemplate_widget_tree::send_quote_json($data) + * - json autoloading uses identical data-structur and should use Api\Etemplate\Widget\Tree::send_quote_json($data) * to send data to client, as it takes care of html-encoding of node text * - if autoloading is enabled, you have to validate returned results yourself, as widget does not know (all) valid id's */ -class etemplate_widget_tree extends etemplate_widget +class Tree extends Etemplate\Widget { /** * key for id of node, has to be unique, eg. a path, nummerical id is allowed too @@ -97,17 +108,17 @@ class etemplate_widget_tree extends etemplate_widget * key of flag if folder is open, default folder is closed */ const OPEN = 'open'; - + /** * key to check checkbox if exists (in case of three-state checkboxes values can be:0 unchecked- 1 - checked or -1 - unsure) */ const CHECKED = 'checked'; - + /** * key to instruct the component not to render checkbox for the related item, optional */ const NOCHECKBOX = 'nocheckbox'; - + /** * Parse and set extra attributes from xml in template object * @@ -115,7 +126,7 @@ class etemplate_widget_tree extends etemplate_widget * * @param string|XMLReader $xml * @param boolean $cloned =true true: object does NOT need to be cloned, false: to set attribute, set them in cloned object - * @return etemplate_widget_template current object or clone, if any attribute was set + * @return Etempalte\Widget current object or clone, if any attribute was set */ public function set_attrs($xml, $cloned=true) { @@ -157,7 +168,7 @@ class etemplate_widget_tree extends etemplate_widget */ public static function htmlencode_node(array $item) { - $item['text'] = html::htmlspecialchars($item['text']); + $item['text'] = Api\Html::htmlspecialchars($item['text']); if (isset($item['item']) && is_array($item['item'])) { @@ -198,7 +209,8 @@ class etemplate_widget_tree extends etemplate_widget */ public static function in_cats($id, array $cats) { - return (boolean)array_filter($cats, function($cat) use($id){ + return (boolean)array_filter($cats, function($cat) use($id) + { return $cat['id'] == $id; }); } diff --git a/etemplate/inc/class.etemplate_widget_url.inc.php b/api/src/Etemplate/Widget/Url.php similarity index 95% rename from etemplate/inc/class.etemplate_widget_url.inc.php rename to api/src/Etemplate/Widget/Url.php index 7b84558a63..65b4c76e3e 100644 --- a/etemplate/inc/class.etemplate_widget_url.inc.php +++ b/api/src/Etemplate/Widget/Url.php @@ -8,15 +8,19 @@ * @link http://www.egroupware.org * @author Ralf Becker * @author Nathan Gray - * @copyright 2002-14 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @copyright 2012 Nathan Gray * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; + /** * eTemplate URL widget handles URLs, emails & phone numbers */ -class etemplate_widget_url extends etemplate_widget +class Url extends Etemplate\Widget { /** * Regexes for validating email addresses incl. email in angle-brackets eg. @@ -110,9 +114,8 @@ class etemplate_widget_url extends etemplate_widget break; } } - $valid = $value; + if (true) $valid = $value; //error_log(__METHOD__."() $form_name: ".array2string($value_in).' --> '.array2string($value)); } } } -etemplate_widget::registerWidget('etemplate_widget_url', array('url')); diff --git a/etemplate/inc/class.etemplate_widget_vfs.inc.php b/api/src/Etemplate/Widget/Vfs.php similarity index 76% rename from etemplate/inc/class.etemplate_widget_vfs.inc.php rename to api/src/Etemplate/Widget/Vfs.php index e7cd1f5a3d..bea0c5aa58 100644 --- a/etemplate/inc/class.etemplate_widget_vfs.inc.php +++ b/api/src/Etemplate/Widget/Vfs.php @@ -11,17 +11,27 @@ * @version $Id$ */ +namespace EGroupware\Api\Etemplate\Widget; + +use EGroupware\Api\Etemplate; +use EGroupware\Api; + +// explicitly import old not yet ported classes +use egw_json_request; +use common; // egw_exit +use egw; // link + /** * eTemplate VFS widget * Deals with the Virtual File System */ -class etemplate_widget_vfs extends etemplate_widget_file +class Vfs extends File { - // Legacy option for vfs-upload protected $legacy_options = "mime"; - public function __construct($xml='') { + public function __construct($xml='') + { if($xml) parent::__construct($xml); } @@ -58,46 +68,46 @@ class etemplate_widget_vfs extends etemplate_widget_file } } $value =& self::get_array(self::$request->content, $form_name, true); - $path = egw_link::vfs_path($app,$id,'',true); + $path = Api\Link::vfs_path($app,$id,'',true); if (!empty($relpath)) $path .= '/'.$relpath; if (true) $value = array(); // Single file, already existing - if (substr($path,-1) != '/' && egw_vfs::file_exists($path) && !egw_vfs::is_dir($path)) + if (substr($path,-1) != '/' && Api\Vfs::file_exists($path) && !Api\Vfs::is_dir($path)) { - $file = egw_vfs::stat($path); + $file = Api\Vfs::stat($path); $file['path'] = $path; - $file['name'] = egw_vfs::basename($file['path']); - $file['mime'] = egw_vfs::mime_content_type($file['path']); + $file['name'] = Api\Vfs::basename($file['path']); + $file['mime'] = Api\Vfs::mime_content_type($file['path']); $value = array($file); } // Single file, missing extension in path - else if (substr($path, -1) != '/' && !egw_vfs::file_exists($path) && $relpath && substr($relpath,-4,1) !== '.') + else if (substr($path, -1) != '/' && !Api\Vfs::file_exists($path) && $relpath && substr($relpath,-4,1) !== '.') { - $find = egw_vfs::find(substr($path,0, - strlen($relpath)), array( + $find = Api\Vfs::find(substr($path,0, - strlen($relpath)), array( 'type' => 'f', 'maxdepth' => 1, 'name' => $relpath . '*' )); foreach($find as $file) { - $file_info = egw_vfs::stat($file); + $file_info = Api\Vfs::stat($file); $file_info['path'] = $file; - $file_info['name'] = egw_vfs::basename($file_info['path']); - $file_info['mime'] = egw_vfs::mime_content_type($file_info['path']); + $file_info['name'] = Api\Vfs::basename($file_info['path']); + $file_info['mime'] = Api\Vfs::mime_content_type($file_info['path']); $value[] = $file_info; } } - else if (substr($path, -1) == '/' && egw_vfs::is_dir($path)) + else if (substr($path, -1) == '/' && Api\Vfs::is_dir($path)) { - $scan = egw_vfs::scandir($path); + $scan = Api\Vfs::scandir($path); foreach($scan as $file) { - $file_info = egw_vfs::stat("$path$file"); + $file_info = Api\Vfs::stat("$path$file"); $file_info['path'] = "$path$file"; - $file_info['name'] = egw_vfs::basename($file_info['path']); - $file_info['mime'] = egw_vfs::mime_content_type($file_info['path']); + $file_info['name'] = Api\Vfs::basename($file_info['path']); + $file_info['mime'] = Api\Vfs::mime_content_type($file_info['path']); $value[] = $file_info; } } @@ -121,11 +131,11 @@ class etemplate_widget_vfs extends etemplate_widget_file { $request_id = urldecode($_REQUEST['request_id']); $widget_id = $_REQUEST['widget_id']; - if(!self::$request = etemplate_request::read($request_id)) + if(!self::$request = Etemplate\Request::read($request_id)) { $error = lang("Could not read session"); } - elseif (!($template = etemplate_widget_template::instance(self::$request->template['name'], self::$request->template['template_set'], + elseif (!($template = Template::instance(self::$request->template['name'], self::$request->template['template_set'], self::$request->template['version'], self::$request->template['load_via']))) { // Can't use callback @@ -143,10 +153,10 @@ class etemplate_widget_vfs extends etemplate_widget_file // store temp. vfs-path like links to be able to move it to correct location after entry is stored if (!$data['to_id'] || is_array($data['to_id'])) { - egw_link::link($data['to_app'], $data['to_id'], egw_link::VFS_APPNAME, array( + Api\Link::link($data['to_app'], $data['to_id'], Api\Link::VFS_APPNAME, array( 'name' => $_FILES['upload']['name'], 'type' => $_FILES['upload']['type'], - 'tmp_name' => egw_vfs::PREFIX.$path, + 'tmp_name' => Api\Vfs::PREFIX.$path, )); self::$request->content = array_merge(self::$request->content, array($widget_id => $data)); } @@ -156,8 +166,8 @@ class etemplate_widget_vfs extends etemplate_widget_file $file = array( "uploaded" => (int)empty($error), - "fileName" => html::htmlspecialchars($_FILES['upload']['name']), - "url" => egw::link(egw_vfs::download_url($path)), + "fileName" => Api\Html::htmlspecialchars($_FILES['upload']['name']), + "url" => egw::link(Api\Vfs::download_url($path)), "error" => array( "message" => $error, ) @@ -184,10 +194,10 @@ class etemplate_widget_vfs extends etemplate_widget_file foreach($links as $link) { $matches = null; - if (is_array($link) && preg_match('|^'.preg_quote(egw_vfs::PREFIX,'|').'('.preg_quote(self::get_temp_dir($app, ''), '|').'[^/]+)/|', $link['id']['tmp_name'], $matches)) + if (is_array($link) && preg_match('|^'.preg_quote(Api\Vfs::PREFIX,'|').'('.preg_quote(self::get_temp_dir($app, ''), '|').'[^/]+)/|', $link['id']['tmp_name'], $matches)) { - $replace[substr($link['id']['tmp_name'], strlen(egw_vfs::PREFIX))] = - egw_link::vfs_path($app, $id, egw_vfs::basename($link['id']['tmp_name']), true); + $replace[substr($link['id']['tmp_name'], strlen(Api\Vfs::PREFIX))] = + Api\Link::vfs_path($app, $id, Api\Vfs::basename($link['id']['tmp_name']), true); if (!in_array($matches[1], $remove_dir)) $remove_dir[] = $matches[1]; } @@ -198,7 +208,7 @@ class etemplate_widget_vfs extends etemplate_widget_file // remove all dirs foreach($remove_dir as $dir) { - egw_vfs::remove($dir); + Api\Vfs::remove($dir); } } return isset($old) && $old != $html; @@ -240,21 +250,21 @@ class etemplate_widget_vfs extends etemplate_widget_file { // add extension to path $parts = explode('.',$filename); - if (($extension = array_pop($parts)) && mime_magic::ext2mime($extension)) // really an extension --> add it to path + if (($extension = array_pop($parts)) && Api\MimeMagic::ext2mime($extension)) // really an extension --> add it to path { $path .= '.'.$extension; } } else // multiple upload with dir given (trailing slash) { - $path .= egw_vfs::encodePathComponent($filename); + $path .= Api\Vfs::encodePathComponent($filename); } - if (!egw_vfs::file_exists($dir = egw_vfs::dirname($path)) && !egw_vfs::mkdir($dir,null,STREAM_MKDIR_RECURSIVE)) + if (!Api\Vfs::file_exists($dir = Api\Vfs::dirname($path)) && !Api\Vfs::mkdir($dir,null,STREAM_MKDIR_RECURSIVE)) { - self::set_validation_error($name,lang('Error create parent directory %1!',egw_vfs::decodePath($dir))); + self::set_validation_error($name,lang('Error create parent directory %1!',Api\Vfs::decodePath($dir))); return false; } - if (!copy($file['tmp_name'],egw_vfs::PREFIX.$path)) + if (!copy($file['tmp_name'],Api\Vfs::PREFIX.$path)) { self::set_validation_error($name,lang('Error copying uploaded file to vfs!')); return false; @@ -292,10 +302,10 @@ class etemplate_widget_vfs extends etemplate_widget_file if(!is_array($value)) $value = array(); /* Check & skip files that made it asyncronously list($app,$id,$relpath) = explode(':',$this->id,3); - //... + //... foreach($value as $tmp => $file) { - if(egw_vfs::file_exists(self::get_vfs_path($id) . $relpath)) {} + if(Api\Vfs::file_exists(self::get_vfs_path($id) . $relpath)) {} }*/ parent::validate($cname, $content, $validated); break; @@ -317,10 +327,9 @@ class etemplate_widget_vfs extends etemplate_widget_file } else { - $path = egw_link::vfs_path($app,$id,'',true); + $path = Api\Link::vfs_path($app,$id,'',true); } if (!empty($relpath)) $path .= '/'.$relpath; return $path; } } -etemplate_widget::registerWidget('etemplate_widget_vfs', array('vfs-upload')); diff --git a/etemplate/empty.html b/api/src/Etemplate/empty.html similarity index 100% rename from etemplate/empty.html rename to api/src/Etemplate/empty.html diff --git a/api/src/Storage/Base.php b/api/src/Storage/Base.php index d98bd23777..ed8f26ba70 100644 --- a/api/src/Storage/Base.php +++ b/api/src/Storage/Base.php @@ -922,7 +922,7 @@ class Base // check if a db-internal name conversation necessary if (!is_int($col) && ($c = array_search($col,$this->db_cols))) { - $col = $c; + $col = $this->table_name . '.' . $c; } if(is_int($col)) { @@ -930,11 +930,11 @@ class Base } elseif ($val === "!''") { - $db_filter[] = $this->table_name . '.' .$col." != ''"; + $db_filter[] = $col." != ''"; } else { - $db_filter[$this->table_name . '.' .$col] = $val; + $db_filter[$col] = $val; } } } diff --git a/etemplate/inc/class.ajax_select_widget.inc.php b/etemplate/inc/class.ajax_select_widget.inc.php index 0ddcda9311..ff608bd4c0 100644 --- a/etemplate/inc/class.ajax_select_widget.inc.php +++ b/etemplate/inc/class.ajax_select_widget.inc.php @@ -10,6 +10,8 @@ * @version $Id$ */ +use EGroupware\Api; + /** * AJAX Select Widget * @@ -357,7 +359,7 @@ class ajax_select_widget $query['field_name'] = $base_id; // Check for a provided list of values - if($request = etemplate_request::read($etemplate_id)) { + if(($request = Api\Etemplate\Request::read($etemplate_id))) { $extension_data = $request->extension_data[$base_id]; if(is_array($extension_data) && $extension_data['values']) { self::$static_values[$base_id] = $extension_data['values']; diff --git a/etemplate/inc/class.boetemplate.inc.php b/etemplate/inc/class.boetemplate.inc.php index 5f617016bf..1f1c6e8474 100644 --- a/etemplate/inc/class.boetemplate.inc.php +++ b/etemplate/inc/class.boetemplate.inc.php @@ -4,13 +4,15 @@ * * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-11 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package etemplate * @subpackage api * @version $Id$ */ +use EGroupware\Api; + /** * Business Object for eTemplates, extending the Storage Object */ @@ -64,7 +66,7 @@ class boetemplate extends soetemplate * * It's a static variable as etemplates can contain further etemplates (rendered by a different object) * - * @var etemplate_request + * @var Api\Etemplate\Request */ static public $request; @@ -103,8 +105,8 @@ class boetemplate extends soetemplate * * @param string $disabled expression to check, eg. "!@var" for !$content['var'] * @param array $content the content-array in the context of the grid - * @param int $row=null to be able to use $row or $row_content in value of checks - * @param int $c=null to be able to use $row or $row_content in value of checks + * @param int $row =null to be able to use $row or $row_content in value of checks + * @param int $c =null to be able to use $row or $row_content in value of checks * @return boolean true if the row/col is disabled or false if not */ protected function check_disabled($disabled,$content,$row=null,$c=null) @@ -346,7 +348,7 @@ class boetemplate extends soetemplate * disables all cells with name == $name * * @param sting $name cell-name - * @param boolean $disabled=true disable or enable a cell, default true=disable + * @param boolean $disabled =true disable or enable a cell, default true=disable * @return mixed number of changed cells or False, if none changed */ function disableElement($name,$disabled=True) @@ -388,7 +390,7 @@ class boetemplate extends soetemplate * disables all cells with name == $name * * @param sting $name cell-name - * @param boolean $disabled=true disable or enable a cell, default true=disable + * @param boolean $disabled =true disable or enable a cell, default true=disable * @return reference to attribute * @deprecated use disableElement($name, $disabled=true) */ @@ -405,7 +407,7 @@ class boetemplate extends soetemplate * @param string $class name of css class (without the leading '.') or '' for no class * @param string $valign alignment (top,middle,bottom) or '' for none * @param boolean $disabled True or expression or False to disable or enable the row (Only the number 0 means dont change the attribute !!!) - * @param string $path='/0' default is the first widget in the tree of children + * @param string $path ='/0' default is the first widget in the tree of children * @return false if $path is no grid or array(height,class,valign,disabled) otherwise */ function set_row_attributes($n,$height=0,$class=0,$valign=0,$disabled=0,$path='/0') @@ -432,8 +434,8 @@ class boetemplate extends soetemplate * disables row $n * * @param int $n numerical row-number starting with 1 (!) - * @param boolean $enable=false can be used to re-enable a row if set to True - * @param string $path='/0' default is the first widget in the tree of children + * @param boolean $enable =false can be used to re-enable a row if set to True + * @param string $path ='/0' default is the first widget in the tree of children */ function disable_row($n,$enable=False,$path='/0') { @@ -445,8 +447,8 @@ class boetemplate extends soetemplate * * @param int|string $c numerical column-number starting with 0 (!), or the char-code starting with 'A' * @param string $width percent or pixel or '' for no height - * @param mixed $disabled=0 True or expression or False to disable or enable the column (Only the number 0 means dont change the attribute !!!) - * @param string $path='/0' default is the first widget in the tree of children + * @param mixed $disabled =0 True or expression or False to disable or enable the column (Only the number 0 means dont change the attribute !!!) + * @param string $path ='/0' default is the first widget in the tree of children * @return false if $path specifies no grid or array(width,disabled) otherwise */ function set_column_attributes($c,$width=0,$disabled=0,$path='/0') @@ -473,7 +475,7 @@ class boetemplate extends soetemplate * * @param int|string $c numerical column-number starting with 0 (!), or the char-code starting with 'A' * @param boolean $enable can be used to re-enable a column if set to True - * @param string $path='/0' default is the first widget in the tree of children + * @param string $path ='/0' default is the first widget in the tree of children */ function disable_column($c,$enable=False,$path='/0') { @@ -782,7 +784,7 @@ class boetemplate extends soetemplate * For the 3. Column in the 2. row of a grid which is the only widget in the children-tree it is eg.: "/0/2C" * * @param string $path path in the widget tree - * @param int $ancestor=0 0: widget itself, 1: parent, 2: grand-parent, ... + * @param int $ancestor =0 0: widget itself, 1: parent, 2: grand-parent, ... * @return array referenz to the widget spezified or null, if it's not found */ function &get_widget_by_path($path,$ancestor=0) @@ -838,9 +840,9 @@ class boetemplate extends soetemplate * - csv_split('"a""b,c",d') === array('a"b,c','d') // to escape enclosures double them! * * @param string $str - * @param int $num=null in how many parts to split maximal, parts over this number end up (unseparated) in the last part - * @param string $delimiter=',' - * @param string $enclosure='"' + * @param int $num =null in how many parts to split maximal, parts over this number end up (unseparated) in the last part + * @param string $delimiter =',' + * @param string $enclosure ='"' * @return array */ static function csv_split($str,$num=null,$delimiter=',',$enclosure='"') @@ -908,8 +910,8 @@ class boetemplate extends soetemplate /** * stores the etemplate in the cache in egw_info * - * @param boetemplate $tpl=null required parameter for static use! - * @param boolean $only_update_older=false true only update cache, if it contains an older template + * @param boetemplate $tpl =null required parameter for static use! + * @param boolean $only_update_older =false true only update cache, if it contains an older template */ public /*static*/ function store_in_cache(boetemplate $tpl=null, $only_update_older=false) { diff --git a/etemplate/inc/class.contact_widget.inc.php b/etemplate/inc/class.contact_widget.inc.php index 2a14d0ac61..4e0e0ac4b0 100644 --- a/etemplate/inc/class.contact_widget.inc.php +++ b/etemplate/inc/class.contact_widget.inc.php @@ -10,12 +10,14 @@ * @version $Id$ */ +use EGroupware\Api\Etemplate\Widget\Contact; + /** * eTemplate Extension: Contact widget * * This widget can be used to fetch fields of a contact specified by contact-id */ -class contact_widget extends etemplate_widget_entry +class contact_widget extends Contact { /** * exported methods of this class @@ -38,158 +40,6 @@ class contact_widget extends etemplate_widget_entry 'contact-template' => 'Account template', 'contact-fields' => 'Contact fields', ); - /** - * Instance of the contacts class - * - * @var contacts - */ - private $contacts; - - /** - * Array with a transformation description, based on attributes to modify. - * - * Exampels: - * - * * 'type' => array('some' => 'other') - * if 'type' attribute equals 'some' replace it with 'other' - * - * * 'type' => array('some' => array('type' => 'other', 'options' => 'otheroption') - * same as above, but additonally set 'options' attr to 'otheroption' - * - * --> leaf element is the action, if previous filters are matched: - * - if leaf is scalar, it just replaces the previous filter value - * - if leaf is an array, it contains assignments for (multiple) attributes: attr => value pairs - * - * * 'type' => array( - * 'some' => array(...), - * 'other' => array(...), - * '__default__' => array(...), - * ) - * it's possible to have a list of filters with actions to run, plus a '__default__' which matches all not explicitly named values - * - * * 'value' => array('__callback__' => 'app.class.method' || 'class::method' || 'method') - * run value through a *serverside* callback, eg. reading an entry based on it's given id - * - * * 'value' => array('__js__' => 'function(value) { return value+5; }') - * run value through a *clientside* callback running in the context of the widget - * - * * 'name' => '@name[@options]' - * replace value of 'name' attribute with itself (@name) plus value of options in square brackets - * - * --> attribute name prefixed with @ sign means value of given attribute - * - * @var array - */ - protected static $transformation = array( - 'type' => array( - 'contact-fields' => array( // contact-fields widget - 'sel_options' => array('__callback__' => 'get_contact_fields'), - 'type' => 'select', - 'no_lang' => true, - 'options' => 'None', - ), - 'contact-template' => array( - 'type' => 'template', - 'options' => '', - 'template' => array('__callback__' => 'parse_template'), - ), - '__default__' => array( - 'options' => array( - 'bday' => array('type' => 'date', 'options' => 'Y-m-d'), - 'owner' => array('type' => 'select-account', 'options' => ''), - 'modifier' => array('type' => 'select-account', 'options' => ''), - 'creator' => array('type' => 'select-account', 'options' => ''), - 'modifed' => array('type' => 'date-time', 'options' => ''), - 'created' => array('type' => 'date-time', 'options' => ''), - 'cat_id' => array('type' => 'select-cat', 'options' => ''), - '__default__' => array('type' => 'label', 'options' => ''), - ), - 'no_lang' => 1, - ), - ), - ); - - /** - * Constructor of the extension - * - * @param string $xml or 'html' for old etemplate - */ - public function __construct($xml) - { - if (is_a($xml, 'XMLReader') || $xml != '' && $xml != 'html') - { - parent::__construct($xml); - } - $this->contacts = $GLOBALS['egw']->contacts; - } - - /** - * Legacy support for putting the template name in 'label' param - * @param string $label - * @param array $attrs - */ - public function parse_template($template, &$attrs) - { - return sprintf($template ? $template : $attrs['label'], $attrs['value']); - } - - /** - * Get all contact-fields - * - * @return array - */ - public function get_contact_fields() - { - translation::add_app('addressbook'); - $this->contacts->__construct(); - $options = $this->contacts->contact_fields; - foreach($this->contacts->customfields as $name => $data) - { - $options['#'.$name] = $data['label']; - } - return $options; - } - - public function get_entry($value, array $attrs) - { - return $this->get_contact($value, $attrs); - } - /** - * Get contact data, if $value not already contains them - * - * @param int|string|array $value - * @param array $attrs - * @return array - */ - public function get_contact($value, array $attrs) - { - $field = $attrs['field'] ? $attrs['field'] : ''; - if (is_array($value) && !(array_key_exists('app',$value) && array_key_exists('id', $value))) return $value; - - if(is_array($value) && array_key_exists('app', $value) && array_key_exists('id', $value)) $value = $value['id']; - switch($attrs['type']) - { - case 'contact-account': - case 'contact-template': - if (substr($value,0,8) != 'account:') - { - $value = 'account:'.($attrs['name'] != 'account:' ? $value : $GLOBALS['egw_info']['user']['account_id']); - } - // fall-through - case 'contact-value': - default: - if (substr($value,0,12) == 'addressbook:') $value = substr($value,12); // link-entry syntax - if (!($contact = $this->contacts->read($value))) - { - $contact = array(); - } - break; - } - unset($contact['jpegphoto']); // makes no sense to return binary image - - //error_log(__METHOD__."('$value') returning ".array2string($contact)); - return $contact; - } /** * pre-processing of the extension @@ -206,6 +56,8 @@ class contact_widget extends etemplate_widget_entry */ function pre_process($name,&$value,&$cell,&$readonlys,&$extension_data,&$tmpl) { + unset($readonlys, $extension_data); // not used, but required by function signature + //echo "

contact_widget::pre_process('$name','$value',".print_r($cell,true).",...)

\n"; switch($type = $cell['type']) { @@ -285,5 +137,3 @@ class contact_widget extends etemplate_widget_entry return True; // extra label ok } } -// register widgets for etemplate2 -etemplate_widget::registerWidget('contact_widget',array('contact-value', 'contact-account', 'contact-template', 'contact-fields')); diff --git a/etemplate/inc/class.etemplate.inc.php b/etemplate/inc/class.etemplate.inc.php index a2225db873..cb398dfe9d 100644 --- a/etemplate/inc/class.etemplate.inc.php +++ b/etemplate/inc/class.etemplate.inc.php @@ -5,12 +5,14 @@ * @link http://www.egroupware.org * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @author Ralf Becker -* @copyright 2002-14 by RalfBecker@outdoor-training.de +* @copyright 2002-16 by RalfBecker@outdoor-training.de * @package etemplate * @subpackage api * @version $Id$ */ +use EGroupware\Api; + /** * creates dialogs / HTML-forms from eTemplate descriptions * @@ -132,7 +134,7 @@ class etemplate extends boetemplate * 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 + * @param string|array $load_via with keys of other etemplate to load in order to get $name */ function __construct($name='',$load_via='') { @@ -160,7 +162,7 @@ class etemplate extends boetemplate * 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 + * @param string|array $params url or array with get-params incl. menuaction */ static function location($params='') { @@ -231,7 +233,7 @@ class etemplate extends boetemplate } self::$name_forms[] = self::$name_form; - self::$request = etemplate_request::read(); + self::$request = Api\Etemplate\Request::read(); self::$request->output_mode = $output_mode; // let extensions "know" they are run eg. in a popup self::$request->readonlys = $readonlys; self::$request->content = $content; @@ -356,8 +358,8 @@ class etemplate extends boetemplate /** * Check if we have not ignored validation errors * - * @param string $ignore_validation='' if not empty regular expression for validation-errors to ignore - * @param string $cname=null name-prefix, which need to be ignored, default self::$name_vars + * @param string $ignore_validation ='' if not empty regular expression for validation-errors to ignore + * @param string $cname =null name-prefix, which need to be ignored, default self::$name_vars * @return boolean true if there are not ignored validation errors, false otherwise */ static function validation_errors($ignore_validation='',$cname=null) @@ -381,8 +383,8 @@ class etemplate extends boetemplate /** * Check if given form-name matches ai ignore-validation rule * - * @param string $ignore_validation='' if not empty regular expression for validation-errors to ignore - * @param string $cname=null name-prefix, which need to be ignored, default self::$name_vars + * @param string $ignore_validation ='' if not empty regular expression for validation-errors to ignore + * @param string $cname =null name-prefix, which need to be ignored, default self::$name_vars * @param string $cname * @return boolean */ @@ -415,7 +417,7 @@ class etemplate extends boetemplate if(!$exec) $exec = $_POST; //echo "process_exec: _POST ="; _debug_array($_POST); - if (!$etemplate_exec_id || !(self::$request = etemplate_request::read($etemplate_exec_id))) + if (!$etemplate_exec_id || !(self::$request = Api\Etemplate\Request::read($etemplate_exec_id))) { if ($this->sitemgr) return false; //echo "uitemplate::process_exec() id='$_POST[etemplate_exec_id]' invalid session-data !!!"; _debug_array($_SESSION); @@ -612,7 +614,7 @@ class etemplate extends boetemplate function process_values2url() { //echo "process_exec: _GET ="; _debug_array($_GET); - if (!$_GET['etemplate_exec_id'] || !($request = etemplate_request::read($_GET['etemplate_exec_id']))) + if (!$_GET['etemplate_exec_id'] || !($request = Api\Etemplate\Request::read($_GET['etemplate_exec_id']))) { return false; } @@ -705,7 +707,7 @@ class etemplate extends boetemplate * * For multiple cats, the first with a color is used * - * @param int/string $cats multiple comma-separated cat_id's + * @param int|string $cats multiple comma-separated cat_id's * @return string */ static function cats2color($cats) @@ -2009,8 +2011,8 @@ class etemplate extends boetemplate * (If no id is directly supplied internally.) * * @param string $form_name - * @param string $name=null - * @param string $id=null + * @param string $name =null + * @param string $id =null * @return string ' id="..."' or '' if no id found */ static public function get_id($form_name,$name=null,$id=null) @@ -2038,8 +2040,8 @@ class etemplate extends boetemplate * --> use . as decimal separator for browser supporting html5 input type=number * * @param int|float|string $number - * @param int $num_decimal_places=2 - * @param boolean $readonly=true + * @param int $num_decimal_places =2 + * @param boolean $readonly =true * @return string */ static public function number_format($number,$num_decimal_places=2,$readonly=true) @@ -2067,7 +2069,7 @@ class etemplate extends boetemplate * * @param array $cell * @param string $name - * @param array $content=array(); + * @param array $content =array(); * @return array */ function _sel_options($cell,$name,$content=array()) @@ -2235,8 +2237,8 @@ class etemplate extends boetemplate * @internal * @param array $content $_POST[$cname], on return the adjusted content * @param array $to_process list of widgets/form-fields to process - * @param string $cname='' basename of our returnt content (same as in call to show) - * @param string $_type='regular' type of request + * @param string $cname ='' basename of our returnt content (same as in call to show) + * @param string $_type ='regular' type of request * @return array with validation errors */ function process_show(&$content,$to_process,$cname='',$_type='regular') @@ -2484,7 +2486,7 @@ class etemplate extends boetemplate * * @param string $name (complete) name of the widget causing the error * @param string|boolean $error error-message already translated or false to reset all existing error for given name - * @param string $cname=null set it to '', if the name is already a form-name, defaults to self::$name_vars + * @param string $cname =null set it to '', if the name is already a form-name, defaults to self::$name_vars */ static function set_validation_error($name,$error,$cname=null) { diff --git a/etemplate/inc/class.etemplate_new.inc.php b/etemplate/inc/class.etemplate_new.inc.php index be86337cfe..297a146e09 100644 --- a/etemplate/inc/class.etemplate_new.inc.php +++ b/etemplate/inc/class.etemplate_new.inc.php @@ -11,6 +11,8 @@ * @version $Id$ */ +use EGroupware\Api; + /** * New eTemplate serverside contains: * - main server methods like read, exec @@ -20,708 +22,6 @@ * - set_(row|column)_attributes modifies template on run-time, was only used internally by etemplate itself * - disable_(row|column) dto. * - * @ToDo supported customized templates stored in DB, currently we only support xet files stored in filesystem + * @deprecated use Api\Etemplate */ -class etemplate_new 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 egw_exception_assertion_failed("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 egw_exception_assertion_failed("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 = etemplate_widget_template::instance($this->name, $this->template_set, $this->version, $this->laod_via); - if (!$template) throw new egw_exception_assertion_failed("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' => etemplate_widget_template::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 (html::$user_agent == 'firefox' || html::$user_agent == 'safari') $form_action = $GLOBALS['egw_info']['server']['webserver_url'].'/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 '
'."\n"; - } - // Send any accumulated json responses - after flush to avoid sending the buffer as a response - if(egw_json_response::isJSONResponse()) - { - $load_array['response'] = egw_json_response::get()->returnResult(); - } - // '; - - if ($output_mode == 2) - { - echo "\n
\n"; - echo $GLOBALS['egw']->framework->footer(); - } - ob_flush(); - } - self::$request = null; - } - - /** - * Fix all sel_options, as etemplate_widget_menupopup::beforeSendToClient is not run for auto-repeated stuff not understood by server - * - * @param array $sel_options - * @return array - */ - static protected function fix_sel_options(array $sel_options) - { - foreach($sel_options as &$options) - { - if (!is_array($options)||empty($options)) continue; - foreach($options as $key => $value) - { - if (is_numeric($key) && (!is_array($value) || !isset($value['value']))) - { - etemplate_widget_menupopup::fix_encoded_options($options, true); - break; - } - } - } - return $sel_options; - } - - /** - * Process via Ajax submitted content - * - * @param string $etemplate_exec_id - * @param array $_content - * @param boolean $no_validation - * @throws egw_exception_wrong_parameter - */ - static public function ajax_process_content($etemplate_exec_id, array $_content, $no_validation) - { - //error_log(__METHOD__."(".array2string($etemplate_exec_id).', '.array2string($_content).")"); - - self::$request = etemplate_request::read($etemplate_exec_id); - //error_log('request='.array2string(self::$request)); - - self::$response = egw_json_response::get(); - - if (!($template = self::instance(self::$request->template['name'], self::$request->template['template_set'], - self::$request->template['version'], self::$request->template['load_via']))) - { - throw new egw_exception_wrong_parameter('Can NOT read template '.array2string(self::$request->template)); - } - - // Set current app for validation - list($app) = explode('.',self::$request->method); - if(!$app) list($app) = explode('::',self::$request->method); - if($app) - { - translation::add_app($app); - $GLOBALS['egw_info']['flags']['currentapp'] = $app; - } - $validated = array(); - $expand = array( - 'cont' => &self::$request->content, - ); - $template->run('validate', array('', $expand, $_content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children - - if ($no_validation) - { - self::$validation_errors = array(); - } - elseif (self::validation_errors(self::$request->ignore_validation)) - { - error_log(__METHOD__."(,".array2string($_content).') validation_errors='.array2string(self::$validation_errors)); - 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(); - - foreach($GLOBALS['egw']->hooks->process(array( - 'hook_location' => 'etemplate2_before_process', - 'location_name' => $template->id, - ) + self::complete_array_merge(self::$request->preserv, $validated)) as $extras) - { - if (!$extras) continue; - - foreach(isset($extras[0]) ? $extras : array($extras) as $extra) - { - if ($extra['data'] && is_array($extra['data'])) - { - $validated = array_merge($validated, $extra['data']); - } - } - } - - //error_log(__METHOD__."(,".array2string($content).')'); - //error_log(' validated='.array2string($validated)); - $content = ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated)); - - $tcontent = is_array($content) ? $content : - self::complete_array_merge(self::$request->preserv, $validated); - - $hook_data = $GLOBALS['egw']->hooks->process( - array( - 'hook_location' => 'etemplate2_after_process', - 'location_name' => $template->id - ) + $tcontent); - - unset($tcontent); - - if (is_array($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']); - } - } - } - } - unset($hook_data); - - if (isset($GLOBALS['egw_info']['flags']['java_script'])) - { - // Strip out any script tags - $GLOBALS['egw_info']['flags']['java_script'] = preg_replace(array('/(]*>)([^<]*)/is','/<\/script>/'),array('$2',''),$GLOBALS['egw_info']['flags']['java_script']); - self::$response->script($GLOBALS['egw_info']['flags']['java_script']); - //error_log($app .' added javascript to $GLOBALS[egw_info][flags][java_script] - use egw_json_response->script() instead.'); - } - - return $content; - } - - /** - * Notify server that eT session/request is no longer needed, because user closed window - * - * @param string $_exec_id - */ - static public function ajax_destroy_session($_exec_id) - { - //error_log(__METHOD__."('$_exec_id')"); - if (($request = etemplate_request::read($_exec_id))) - { - $request->remove_if_not_modified(); - unset($request); - } - } - - /** - * Process via POST submitted content - */ - static public function process_exec() - { - if (get_magic_quotes_gpc()) $_POST['value'] = stripslashes($_POST['value']); - $content = json_decode($_POST['value'],true); - if($content == null && $_POST['exec']) - { - // Old etemplate submit - error_log("Old etemplate submitted"); - return ExecMethod('etemplate.etemplate.process_exec'); - } - //error_log(__METHOD__."(".array2string($content).")"); - - self::$request = etemplate_request::read($_POST['etemplate_exec_id']); - - if (!($template = self::instance(self::$request->template['name'], self::$request->template['template_set'], - self::$request->template['version'], self::$request->template['load_via']))) - { - throw new egw_exception_wrong_parameter('Can NOT read template '.array2string(self::$request->template)); - } - $validated = array(); - $expand = array( - 'cont' => &self::$request->content, - ); - $template->run('validate', array('', $expand, $content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children - if (self::validation_errors(self::$request->ignore_validation)) - { - error_log(__METHOD__."(,".array2string($content).') validation_errors='.array2string(self::$validation_errors)); - exit; - } - //error_log(__METHOD__."(,".array2string($content).')'); - //error_log(' validated='.array2string($validated)); - - return ExecMethod(self::$request->method, self::complete_array_merge(self::$request->preserv, $validated)); - } - - public $name; - public $template_set; - public $version; - public $laod_via; - - /** - * - * @var string If the template needs a div named other than the template name, this is it - */ - protected $dom_id; - - /** - * Reads an eTemplate from filesystem or DB (not yet supported) - * - * @param string $name name of the eTemplate or array with the values for all keys - * @param string $template_set =null default try template-set from user and if not found "default" - * @param string $lang language, '' loads the pref. lang of the user, 'default' loads the default one '' in the db - * @param int $group id of the (primary) group of the user or 0 for none, not used at the moment !!! - * @param string $version version of the eTemplate - * @param mixed $load_via name/array of keys of etemplate to load in order to get $name (only as second try!) - * @return boolean True if a fitting template is found, else False - * - * @ToDo supported customized templates stored in DB - */ - public function read($name,$template_set=null,$lang='default',$group=0,$version='',$load_via='') - { - - // For mobile experience try to load custom mobile templates - if (html::$ua_mobile) - { - $template_set = "mobile"; - } - - unset($lang); unset($group); // not used, but in old signature - $this->rel_path = self::relPath($this->name=$name, $this->template_set=$template_set, - $this->version=$version, $this->laod_via = $load_via); - //error_log(__METHOD__."('$name', '$template_set', '$lang', $group, '$version', '$load_via') rel_path=".array2string($this->rel_path)); - - $this->dom_id = $name; - - return (boolean)$this->rel_path; - } - - /** - * Set the DOM ID for the etemplate div. If not set, it will be generated from the template name. - * - * @param string $new_id - */ - public function set_dom_id($new_id) - { - $this->dom_id = $new_id; - } - /** - * Get template data as array - * - * @return array - */ - public function as_array() - { - return array( - 'name' => $this->name, - 'template_set' => $this->template_set, - 'version' => $this->version, - 'load_via' => $this->load_via, - ); - } - - /** - * Returns reference to an attribute in a named cell - * - * Currently we always return a reference to an not set value, unless it was set before. - * We do not return a reference to the actual cell, as it get's contructed on client-side! - * - * @param string $name cell-name - * @param string $attr attribute-name - * @return mixed reference to attribute, usually NULL - * @deprecated use getElementAttribute($name, $attr) - */ - public function &get_cell_attribute($name,$attr) - { - return self::getElementAttribute($name, $attr); - } - - /** - * set an attribute in a named cell if val is not NULL else return the attribute - * - * @param string $name cell-name - * @param string $attr attribute-name - * @param mixed $val if not NULL sets attribute else returns it - * @return reference to attribute - * @deprecated use setElementAttribute($name, $attr, $val) - */ - public function &set_cell_attribute($name,$attr,$val) - { - return self::setElementAttribute($name, $attr, $val); - } - - /** - * disables all cells with name == $name - * - * @param sting $name cell-name - * @param boolean $disabled =true disable or enable a cell, default true=disable - * @return reference to attribute - * @deprecated use disableElement($name, $disabled=true) - */ - public function disable_cells($name,$disabled=True) - { - return self::disableElement($name, $disabled); - } - - /** - * merges $old and $new, content of $new has precedence over $old - * - * THIS IS NOT THE SAME AS PHP's functions: - * - array_merge, as it calls itself recursive for values which are arrays. - * - array_merge_recursive accumulates values with the same index and $new does NOT overwrite $old - * - * @param array $old - * @param array $new - * @return array the merged array - */ - public static function complete_array_merge($old,$new) - { - if (is_array($new)) - { - if (!is_array($old)) $old = (array) $old; - - foreach($new as $k => $v) - { - if (!is_array($v) || !isset($old[$k]) || // no array or a new array - isset($v[0]) && !is_array($v[0]) && isset($v[count($v)-1]) || // or no associative array, eg. selecting multiple accounts - is_array($v) && count($v) == 0) // Empty array replacing non-empty - { - $old[$k] = $v; - } - else - { - $old[$k] = self::complete_array_merge($old[$k],$v); - } - } - } - return $old; - } - - /** - * Debug callback just outputting content - * - * @param array $content =null - */ - public function debug(array $content=null) - { - common::egw_header(); - _debug_array($content); - common::egw_footer(); - } - - /** - * Message containing the max Upload size from the current php.ini settings - * - * We have to take the smaler one of upload_max_filesize AND post_max_size-2800 into account. - * memory_limit does NOT matter any more, because of the stream-interface of the vfs. - * - * @param int &$max_upload=null on return max. upload size in byte - * @return string - */ - static function max_upload_size_message(&$max_upload=null) - { - $upload_max_filesize = ini_get('upload_max_filesize'); - $post_max_size = ini_get('post_max_size'); - $max_upload = min(self::km2int($upload_max_filesize),self::km2int($post_max_size)-2800); - - return lang('Maximum size for uploads').': '.egw_vfs::hsize($max_upload). - " (php.ini: upload_max_filesize=$upload_max_filesize, post_max_size=$post_max_size)"; - } - - /** - * Format a number according to user prefs with decimal and thousands separator (later only for readonly) - * - * @param int|float|string $number - * @param int $num_decimal_places =2 - * @param boolean $readonly =true - * @return string - */ - static public function number_format($number,$num_decimal_places=2,$readonly=true) - { - static $dec_separator=null,$thousands_separator=null; - if (is_null($dec_separator)) - { - $dec_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][0]; - if (empty($dec_separator)) $dec_separator = '.'; - $thousands_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][1]; - } - if ((string)$number === '') return ''; - - return number_format(str_replace(' ','',$number),$num_decimal_places,$dec_separator,$readonly ? $thousands_separator : ''); - } - - /** - * Convert numbers like '32M' or '512k' to integers - * - * @param string $size - * @return int - */ - private static function km2int($size) - { - if (!is_numeric($size)) - { - switch(strtolower(substr($size,-1))) - { - case 'm': - $size = 1024*1024*(int)$size; - break; - case 'k': - $size = 1024*(int)$size; - break; - } - } - return (int)$size; - } -} - -// Try to discover all widgets, as names don't always match tags (eg: listbox is in menupopup) -$files = scandir(EGW_INCLUDE_ROOT . '/etemplate/inc'); -foreach($files as $filename) -{ - if(strpos($filename, 'class.etemplate_widget') === 0) - { - try - { - include_once($filename); - } - catch(Exception $e) - { - error_log($e->getMessage()); - } - } -} - -// Use hook to load custom widgets from other apps -$widgets = $GLOBALS['egw']->hooks->process('etemplate2_register_widgets'); -foreach($widgets as $app => $list) -{ - if (is_array($list)) - { - foreach($list as $class) - { - try - { - class_exists($class); // trigger autoloader - } - catch(Exception $e) - { - error_log($e->getMessage()); - } - } - } -} +class etemplate_new extends Api\Etemplate {} diff --git a/etemplate/inc/class.etemplate_widget.inc.php b/etemplate/inc/class.etemplate_widget.inc.php index 3b37f8dcb2..9854f6178e 100644 --- a/etemplate/inc/class.etemplate_widget.inc.php +++ b/etemplate/inc/class.etemplate_widget.inc.php @@ -1,1026 +1,80 @@ - * @copyright 2002-14 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +use EGroupware\Api\Etemplate\Widget; + /** * eTemplate widget baseclass * - * @todo text content, eg. the styles of a template are not parsed, thought they are not used here either + * @deprecated use Api\Etemplate\Widget */ -class etemplate_widget -{ - /** - * Widget type - * - * @var string - */ - public $type; - - /** - * Widget id - * - * @var string - */ - public $id; - - /** - * Widget attributes - * - * @var array - */ - public $attrs = array(); - - /** - * Children - * - * @var array - */ - protected $children = array(); - - /** - * (Array of) comma-separated list of legacy options to automatically replace when parsing with set_attrs - * - * @var string|array - */ - protected $legacy_options; - - /** - * Request object of the currently created request - * - * It's a static variable as etemplates can contain further etemplates (rendered by a different object) - * - * @var etemplate_request - */ - static protected $request; - - /** - * JSON response object, if we run via a JSON request - * - * @var egw_json_response - */ - static protected $response; - - /** - * Namespaced content array, used when trying to initialize - * - * This is pretty much a global static variable, used when reading - * a template with the content set. This allows variable expansion - * in the constructor. - * - * @protected $cont - */ - static protected $cont = null; - - /** - * Constructor - * - * @param string|XMLReader $xml string with xml or XMLReader positioned on the element to construct - * @throws egw_exception_wrong_parameter - */ - public function __construct($xml) - { - $reader = self::get_reader($xml); - $this->type = $reader->name; - $depth = $reader->depth; - - $this->id = $reader->getAttribute('id'); - - // Update content? - if(self::$cont == null) - self::$cont = is_array(self::$request->content) ? self::$request->content : array(); - if($this->id && is_array(self::$cont[$this->id])) - { - $old_cont = self::$cont; - self::$cont = self::$cont[$this->id]; - } - - // read all attributes - $this->set_attrs($reader); - - while($reader->read() && $reader->depth > $depth) - { - if ($reader->nodeType == XMLReader::ELEMENT && $reader->depth > $depth) - { - $this->children[] = self::factory($reader->name, $reader, $reader->getAttribute('id')); - } - } - - // Reset content as we leave - if($old_cont) { - self::$cont = $old_cont; - } - } - - /** - * Get XMLReader for given xml string - * - * @param string|XMLReader $xml string with xml or XMLReader positioned on an element - * @throws egw_exception_wrong_parameter - */ - protected static function get_reader($xml) - { - if (is_a($xml, 'XMLReader')) - { - $reader = $xml; - } - else - { - $reader = new XMLReader(); - if (!$reader->XML($xml)) - { - throw new egw_exception_wrong_parameter("Can't parse xml:\n$xml"); - } - } - return $reader; - } - - /** - * Parse and set extra attributes from xml in template object - * - * Returns a cloned template object, if any attribute needs to be set. - * This is necessary as templates can be used multiple time, so we can not alter the cached template! - * - * @param string|XMLReader $xml - * @param boolean $cloned =true true: object does NOT need to be cloned, false: to set attribute, set them in cloned object - * @return etemplate_widget_template current object or clone, if any attribute was set - */ - public function set_attrs($xml, $cloned=true) - { - $reader = self::get_reader($xml); - - // check if we have to split legacy options (can be by type) - $legacy_options = $this->legacy_options; - if (is_array($legacy_options)) - { - if (!($type = $reader->getAttribute('type'))) - { - $type = $this->type; - } - $legacy_options = $legacy_options[$type]; - } - - // read and set all attributes - $template = $this; - while($reader->moveToNextAttribute()) - { - if ($reader->name != 'id' && $template->attr[$reader->name] != $reader->value) - { - if (!$cloned) - { - $template = clone($this); - $cloned = true; // only clone it once, otherwise we loose attributes! - } - // $reader->value is an object and therefore assigned by reference - // this is important to not loose content when validating dynamic generated tabs as in settings! - $template->attrs[$reader->name] = $value = $reader->value; - - // expand attributes values, otherwise eg. validation can not use attrs referencing to content - if ($value[0] == '@' || strpos($value, '$cont') !== false) - { - $value = self::expand_name($value, null, null, null, null, - isset(self::$cont) ? self::$cont : self::$request->content); - } - - // split legacy options - if ($legacy_options && $reader->name == 'options') - { - $legacy_options = explode(',', $legacy_options); - foreach(self::csv_split($value, count($legacy_options)) as $n => $val) - { - if ($legacy_options[$n] && (string)$val !== '') $template->attrs[$legacy_options[$n]] = $val; - } - } - } - } - - // Add in anything in the modification array - if(is_array(self::$request->modifications[$this->id])) - { - $this->attrs = array_merge($this->attrs,self::$request->modifications[$this->id]); - } - - return $template; - } - - /** - * Split a $delimiter-separated options string, which can contain parts with delimiters enclosed in $enclosure - * - * Examples: - * - csv_split('"1,2,3",2,3') === array('1,2,3','2','3') - * - csv_split('1,2,3',2) === array('1','2,3') - * - csv_split('"1,2,3",2,3',2) === array('1,2,3','2,3') - * - csv_split('"a""b,c",d') === array('a"b,c','d') // to escape enclosures double them! - * - * @param string $str - * @param int $num =null in how many parts to split maximal, parts over this number end up (unseparated) in the last part - * @param string $delimiter =',' - * @param string $enclosure ='"' - * @return array - */ - public static function csv_split($str,$num=null,$delimiter=',',$enclosure='"') - { - if (strpos($str,$enclosure) === false) - { - return is_null($num) ? explode($delimiter,$str) : explode($delimiter,$str,$num); // no need to run this more expensive code - } - $parts = explode($delimiter,$str); - for($n = 0; isset($parts[$n]); ++$n) - { - $part =& $parts[$n]; - if ($part[0] === $enclosure) - { - while (isset($parts[$n+1]) && substr($part,-1) !== $enclosure) - { - $part .= $delimiter.$parts[++$n]; - unset($parts[$n]); - } - $part = substr(str_replace($enclosure.$enclosure,$enclosure,$part),1,-1); - } - } - $parts_renum = array_values($parts); // renumber the parts (in case we had to concat them) - - if ($num > 0 && count($parts_renum) > $num) - { - $parts_renum[$num-1] = implode($delimiter,array_slice($parts_renum,$num-1,count($parts_renum)-$num+1)); - $parts_renum = array_slice($parts_renum,0,$num); - } - return $parts_renum; - } - - /** - * Registry of classes implementing widgets - * - * @var array - */ - static protected $widget_registry = array(); - - /** - * Register a given class for certain widgets - * - * Registration is only needed if widget (base-)name is not autoloadable, - * eg. class etemplate_widget_template does NOT need to be registered. - * - * @param string $class - * @param string|array $widgets - */ - public static function registerWidget($class, $widgets) - { - if (!is_subclass_of($class, __CLASS__)) - { - throw new egw_exception_wrong_parameter(__METHOD__."('$class', ".array2string($widgets).") $class is no subclass of ".__CLASS__.'!'); - } - foreach((array)$widgets as $widget) - { - self::$widget_registry[$widget] = $class; - } - } - - /** - * Factory method to construct all widgets - * - * @param string $type - * @param string|XMLReader $xml - * @param string $id =null - */ - public static function factory($type, $xml, $id=null) - { - $class_name =& self::$widget_registry[$type]; - - if (!isset($class_name)) - { - list($basetype) = explode('-',$type); - if (!class_exists($class_name = 'etemplate_widget_'.str_replace('-','_',$type)) && - !class_exists($class_name = 'etemplate_widget_'.$basetype) && - // widgets supplied by application in class ${app}_widget_etemplate or ${app}_${subtype}_widget_etemplate - !(isset($GLOBALS['egw_info']['apps'][$basetype]) && - (class_exists($class_name = str_replace('-','_',$type).'_etemplate_widget') || - class_exists($class_name = $basetype.'_etemplate_widget')))) - { - // Try for base type, it's probably better than the root - if(self::$widget_registry[$basetype] && self::$widget_registry[$basetype] != $class_name) - { - $class_name = self::$widget_registry[$basetype]; - } - // Look for old widgets that were adapted but not renamed - else if (class_exists($class_name = $basetype.'_widget') && in_array('etemplate_widget', class_parents($class_name))) - { - // Side-effects set $class_name - //error_log("Ported old widget: $class_name"); - } - else - { - // Fall back to widget class, we can not ignore it, as the widget may contain other widgets - $class_name = 'etemplate_widget'; - } - } - } - - if(!$xml) - { - if (empty($type)) $type = 'widget'; - $xml = "<$type id='$id'/>"; - } - //error_log(__METHOD__."('$type', ..., '$id') using $class_name"); - - // currently only overlays can contain templates, other widgets can only reference to templates via id - if ($type == 'template' && $id && ($template = etemplate_widget_template::instance($id))) - { - // references can set different attributes like: class, span, content (namespace) - return $template->set_attrs($xml, false); // false = need to clone template, if attributs are set! - } - return new $class_name($xml); - } - - /** - * Iterate over children to find the one with the given id and optional type - * - * @param string $id - * @param string $type =null - * @return etemplate_widget or NULL - */ - public function getElementById($id, $type=null) - { - foreach($this->children as $child) - { - if ($child->id === $id && (is_null($type) || $child->type === $type)) - { - return $child; - } - if (($element = $child->getElementById($id, $type))) - { - return $element; - } - } - return null; - } - - /** - * Iterate over children to find the one with the given type - * - * @param string $type - * @return array of etemplate_widget or empty array - */ - public function getElementsByType($type) - { - $elements = array(); - foreach($this->children as $child) - { - if ($child->type === $type) - { - $elements[] = $child; - } - $elements += $child->getElementsByType($type); - } - return $elements; - } - - /** - * Run a given method on all children - * - * Default implementation only calls method on itself and run on all children - * - * @param string $method_name - * @param array $params =array('') parameter(s) first parameter has to be the cname, second $expand! - * @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children - */ - public function run($method_name, $params=array(''), $respect_disabled=false) - { - // maintain $expand array name-expansion - $cname = $params[0]; - $expand =& $params[1]; - if ($expand['cname'] && $expand['cname'] !== $cname) - { - $expand['cont'] =& self::get_array(self::$request->content, $cname); - $expand['cname'] = $cname; - } - if ($respect_disabled && ($disabled = $this->attrs['disabled'] && self::check_disabled($this->attrs['disabled'], $expand))) - { - //error_log(__METHOD__."('$method_name', ".array2string($params).', '.array2string($respect_disabled).") $this disabled='{$this->attrs['disabled']}'=".array2string($disabled).": NOT running"); - return; - } - if (method_exists($this, $method_name)) - { - // Some parameter checking to avoid fatal errors - $call = true; - $method = new ReflectionMethod($this, $method_name); - foreach($method->getParameters() as $index => $param) - { - if(!$param->isOptional() && !array_key_exists($index,$params)) - { - error_log("Missing required parameter {$param->getPosition()}: {$param->getName()}"); - $call = false; - } - if($param->isArray() && !is_array($params[$index])) - { - error_log("$method_name expects an array for {$param->getPosition()}: {$param->getName()}"); - $params[$index] = (array)$params[$index]; - } - } - if($call) call_user_func_array(array($this, $method_name), $params); - } - foreach($this->children as $child) - { - // If type has something that can be expanded, we need to expand it so the correct method is run - $this->expand_widget($child, $expand); - $child->run($method_name, $params, $respect_disabled); - } - } - - /** - * If a widget's type is expandable, we need to expand it to make sure we have - * the right class before running the method on it - * - * @param etemplate_widget $child Widget to check & expand if needed - * @param Array $expand Expansion array - */ - protected function expand_widget(etemplate_widget &$child, Array &$expand) - { - if(strpos($child->attrs['type'], '@') !== false || strpos($child->attrs['type'], '$') !== false) - { - $type = self::expand_name($child->attrs['type'],$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); - $id = self::expand_name($child->id,$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); - $attrs = $child->attrs; - unset($attrs['type']); - $expanded_child = self::factory($type, false,$id); - $expanded_child->id = $id; - $expanded_child->type = $type; - $expanded_child->attrs = $attrs + array('type' => $type); - $child = $expanded_child; - } - } - - /** - * Checks if a grid row or column is disabled - * - * Expression: [!][@]val[=[@]check] - * Parts in square brackets are optional, a ! negates the expression, @val evaluates to $content['val'] - * if no =check is given all set non-empty and non-zero strings are true (standard php behavior) - * - * @param string $disabled expression to check, eg. "!@var" for !$content['var'] - * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' - * @return boolean true if the row/col is disabled or false if not - */ - protected static function check_disabled($disabled, array $expand) - { - if (($not = $disabled[0] == '!')) - { - $disabled = substr($disabled,1); - } - list($value,$check) = $vals = explode('=',$disabled); - - // use expand_name to be able to use @ or $ - $val = self::expand_name($value, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); - $check_val = self::expand_name($check, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); - $result = count($vals) == 1 ? $val != '' : ($check_val[0] == '/' ? preg_match($check_val,$val) : $val == $check_val); - if ($not) $result = !$result; - - //error_log(__METHOD__."('".($not?'!':'')."$disabled' = '$val' ".(count($vals) == 1 ? '' : ($not?'!':'=')."= '$check_val'")." = ".($result?'True':'False')); - return $result; - } - - /** - * Regular expression matching a PHP variable in a string, eg. - * - * "replies[$row][reply_message]" should only match $row - * "delete[$row_cont[path]]" should match $row_cont[path] - */ - const PHP_VAR_PREG = '\$[A-Za-z0-9_]+(\[[A-Za-z0-9_]+\])*'; - - /** - * allows a few variables (eg. row-number) to be used in field-names - * - * This is mainly used for autorepeat, but other use is possible. - * You need to be aware of the rules PHP uses to expand vars in strings, a name - * of "Row$row[length]" will expand to 'Row' as $row is scalar, you need to use - * "Row${row}[length]" instead. Only one indirection is allowd in a string by php !!! - * Out of that reason we have now the variable $row_cont, which is $cont[$row] too. - * Attention !!! - * Using only number as index in field-names causes a lot trouble, as depending - * on the variable type (which php determines itself) you used filling and later - * accessing the array it can by the index or the key of an array element. - * To make it short and clear, use "Row$row" or "$col$row" not "$row" or "$row$col" !!! - * - * @param string $name the name to expand - * @param int $c is the column index starting with 0 (if you have row-headers, data-cells start at 1) - * @param int $row is the row number starting with 0 (if you have col-headers, data-cells start at 1) - * @param int $c_ is the value of the previous template-inclusion, - * eg. the column-headers in the eTemplate-editor are templates itself, - * to show the column-name in the header you can not use $col as it will - * be constant as it is always the same col in the header-template, - * what you want is the value of the previous template-inclusion. - * @param int $row_ is the value of the previous template-inclusion, - * @param array $cont content of the template, you might use it to generate button-names with id values in it: - * "del[$cont[id]]" expands to "del[123]" if $cont = array('id' => 123) - * @return string the expanded name - */ - protected static function expand_name($name,$c,$row,$c_='',$row_='',$cont='') - { - $is_index_in_content = $name[0] == '@'; - if (($pos_var=strpos($name,'$')) !== false) - { - if (!$cont) - { - $cont = array(); - } - if (!is_numeric($c)) $c = boetemplate::chrs2num($c); - $col = self::num2chrs($c-1); // $c-1 to get: 0:'@', 1:'A', ... - $col_ = self::num2chrs($c_-1); - $row_cont = $cont[$row]; - $col_row_cont = $cont[$col.$row]; - - eval('$name = "'.str_replace('"','\\"',$name).'";'); - unset($col_, $row_, $row_cont, $col_row_cont); // quiten IDE warning about used vars, they might be used in above eval! - } - if ($is_index_in_content) - { - if ($name[1] == '@' && is_array(self::$request->content)) - { - $name = self::get_array(self::$request->content,substr($name,2)); - } - elseif(is_array($cont)) - { - $name = self::get_array($cont,substr($name,1)); - } - else - { - // Content not set expands to '' - $name = ''; - } - } - return $name; - } - - /** - * generates column-names from index: 'A', 'B', ..., 'AA', 'AB', ..., 'ZZ' (not more!) - * - * @param int $num numerical index to generate name from 1 => 'A' - * @return string the name - */ - static function num2chrs($num) - { - $min = ord('A'); - $max = ord('Z') - $min + 1; - if ($num >= $max) - { - $chrs = chr(($num / $max) + $min - 1); - } - $chrs .= chr(($num % $max) + $min); - - return $chrs; - } - - /** - * Convert object to string - * - * @return string - */ - public function __toString() - { - return '['.get_class($this).'] ' . - $this->type.($this->attrs['type'] && $this->attrs['type'] != $this->type ? '('.$this->attrs['type'].')' : '').'#'.$this->id; - } - - /** - * When cloning a widget, we also clone children - */ - public function __clone() - { - foreach($this->children as $child_num => $child) { - $this->children[$child_num] = clone $child; - } - } - - /** - * Convert widget (incl. children) to xml - * - * @param string $indent ='' - * @return string - */ - public function toXml($indent='') - { - echo "$indent<$this->type"; - if ($this->id) echo ' id="'.htmlspecialchars($this->id).'"'; - foreach($this->attrs as $name => $value) - { - if ($name == 'options' && $this->legacy_options && (!is_array($this->legacy_options) || - isset($this->legacy_options[$this->attrs['type'] ? $this->attrs['type'] : $this->type]))) - { - continue; // do NOT output already converted legacy options - } - echo ' '.$name.'="'.htmlspecialchars($value).'"'; - } - echo ' php-class="'.get_class($this).'"'; - - if ($this->children) - { - echo ">\n"; - foreach($this->children as $child) - { - $child->toXml($indent."\t"); - } - echo "$indenttype>\n"; - } - else - { - echo " />\n"; - } - } - - /** - * build the name of a form-element from a basename and name - * - * name and basename can contain sub-indices in square bracets, eg. basename="base[basesub1][basesub2]" - * and name = "name[sub]" gives "base[basesub1][basesub2][name][sub]" - * - * @param string $cname basename - * @param string $name name - * @param array $expand =null values for keys 'c', 'row', 'c_', 'row_', 'cont' - * @return string complete form-name - */ - static function form_name($cname,$name,array $expand=null) - { - if ($expand && !empty($name)) - { - $name = self::expand_name($name, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); - } - if (count($name_parts = explode('[', $name, 2)) > 1) - { - $name_parts = array_merge(array($name_parts[0]), explode('][', substr($name_parts[1],0,-1))); - } - if (!empty($cname)) - { - array_unshift($name_parts,$cname); - } - $form_name = array_shift($name_parts); - if (count($name_parts)) - { - // RB: not sure why this business with entity encoding for square brakets, it messes up validation - //$form_name .= '['.implode('][',$name_parts).']'; - $form_name .= '['.implode('][',$name_parts).']'; - } - return $form_name; - } - - /** - * return a reference to $arr[$idx] - * - * This works for non-trival indexes like 'a[b][c]' too: it returns &$arr[a][b][c] - * $sub = get_array($arr,'a[b]'); $sub = 'c'; is equivalent to $arr['a']['b'] = 'c'; - * - * @param array $arr the array to search, referenz as a referenz gets returned - * @param string $_idx the index, may contain sub-indices like a[b], see example below - * @param boolean $reference_into default False, if True none-existing sub-arrays/-indices get created to be returned as referenz, else False is returned - * @param bool $skip_empty returns false if $idx is not present in $arr - * @return mixed reference to $arr[$idx] or null if $idx is not set and not $reference_into - */ - static function &get_array(&$arr,$_idx,$reference_into=False,$skip_empty=False) - { - if (!is_array($arr)) - { - throw new egw_exception_assertion_failed(__METHOD__."(\$arr,'$_idx',$reference_into,$skip_empty) \$arr is no array!"); - } - if (is_object($_idx)) return false; // given an error in php5.2 - - // Make sure none of these are left - $idx = str_replace(array('[',']'), array('[',']'), $_idx); - - // Handle things expecting arrays - ends in [] - if(substr($idx,-2) == "[]") - { - $idx = substr($idx,0,-2); - } - - if (count($idxs = explode('[', $idx, 2)) > 1) - { - $idxs = array_merge(array($idxs[0]), explode('][', substr($idxs[1],0,-1))); - } - $pos = &$arr; - foreach($idxs as $idx) - { - if (!is_array($pos) && (!$reference_into || $reference_into && isset($pos))) - { - //if ($reference_into) error_log(__METHOD__."(".(strlen($s=array2string($arr))>512?substr($s,0,512).'...':$s).", '$idx', ".array2string($reference_into).", ".array2string($skip_empty).") ".function_backtrace()); - return null; - } - if($skip_empty && (!is_array($pos) || !isset($pos[$idx]))) return null; - $pos = &$pos[$idx]; - } - return $pos; - } - - /** - * return a reference to $arr[$idx] - * - * This works for non-trival indexes like 'a[b][c]' too: it returns &$arr[a][b][c] - * $sub = get_array($arr,'a[b]'); $sub = 'c'; is equivalent to $arr['a']['b'] = 'c'; - * - * @param array& $_arr the array to search, referenz as a referenz gets returned - * @param string $_idx the index, may contain sub-indices like a[b], see example below - * @param mixed $_value value to set - */ - static function set_array(&$_arr, $_idx, $_value) - { - $ref =& self::get_array($_arr, $_idx, true); - if (true) $ref = $_value; - } - - /** - * Checks if a widget is readonly: - * - readonly attribute set - * - $readonlys[__ALL__] set and $readonlys[$form_name] !== false - * - $readonlys[$form_name] evaluates to true - * - * @param string $cname ='' - * @param string $form_name =null form_name, to not calculate him again - * @return boolean - */ - public function is_readonly($cname='', $form_name=null) - { - if (!isset($form_name)) - { - $expand = array( - 'cont' => self::get_array(self::$request->content, $cname), - ); - $form_name = self::form_name($cname, $this->id, $expand); - } - $readonly = $this->attrs['readonly'] || self::$request->readonlys[$form_name] || - self::get_array(self::$request->readonlys,$form_name) === true || - isset(self::$request->readonlys['__ALL__']) && ( - // Exceptions to all - self::$request->readonlys[$form_name] !== false && - self::get_array(self::$request->readonlys,$form_name) !== false - ); - - //error_log(__METHOD__."('$cname') this->id='$this->id' --> form_name='$form_name': attrs[readonly]=".array2string($this->attrs['readonly']).", readonlys['$form_name']=".array2string(self::$request->readonlys[$form_name]).", readonlys[$form_name]=".array2string(self::get_array(self::$request->readonlys,$form_name)).", readonlys['__ALL__']=".array2string(self::$request->readonlys['__ALL__'])." returning ".array2string($readonly)); - return $readonly; - } - /** - * Validation errors from process_show and the extensions, should be set via etemplate::set_validation_error - * - * @public array form_name => message pairs - */ - static protected $validation_errors = array(); - - /** - * Sets a validation error, to be displayed in the next exec - * - * @param string $name (complete) name of the widget causing the error - * @param string|boolean $error error-message already translated or false to reset all existing error for given name - * @param string $cname =null set it to '', if the name is already a form-name, defaults to self::$name_vars - */ - public static function set_validation_error($name,$error,$cname=null) - { - // not yet used: if (is_null($cname)) $cname = self::$name_vars; - error_log(__METHOD__."('$name','$error','$cname') ".function_backtrace()); - - if ($cname) $name = self::form_name($cname,$name); - - if ($error === false) - { - unset(self::$validation_errors[$name]); - } - else - { - if (self::$validation_errors[$name]) - { - self::$validation_errors[$name] .= ', '; - } - self::$validation_errors[$name] .= $error; - } - } - - /** - * Check if we have not ignored validation errors - * - * @param string $ignore_validation ='' if not empty regular expression for validation-errors to ignore - * @param string $cname =null name-prefix, which need to be ignored, default self::$name_vars - * @return boolean true if there are not ignored validation errors, false otherwise - */ - public static function validation_errors($ignore_validation='',$cname='') - { - // not yet used: if (is_null($cname)) $cname = self::$name_vars; - //echo "

uietemplate::validation_errors('$ignore_validation','$cname') validation_error="; _debug_array(self::$validation_errors); - if (!$ignore_validation) return count(self::$validation_errors) > 0; - - foreach(array_values(self::$validation_errors) as $name) - { - if ($cname) $name = preg_replace('/^'.$cname.'\[([^\]]+)\](.*)$/','\\1\\2',$name); - - // treat $ignoare_validation only as regular expression, if it starts with a slash - if ($ignore_validation[0] == '/' && !preg_match($ignore_validation,$name) || - $ignore_validation[0] != '/' && $ignore_validation != $name) - { - //echo "

uietemplate::validation_errors('$ignore_validation','$cname') name='$name' ($error) not ignored!!!

\n"; - return true; - } - //echo "

uietemplate::validation_errors('$ignore_validation','$cname') name='$name' ($error) ignored

\n"; - } - return false; - } - - /** - * Returns reference to an attribute in a named cell - * - * Currently we always return a reference to an not set value, unless it was set before. - * We do not return a reference to the actual cell, as it get's contructed on client-side! - * - * @param string $name cell-name - * @param string $attr attribute-name - * @return mixed reference to attribute, usually NULL - */ - public function &getElementAttribute($name, $attr) - { - //error_log(__METHOD__."('$name', '$attr')"); - return self::$request->modifications[$name][$attr]; - } - - /** - * Set an attribute in a named cell if val is not NULL else return the attribute - * - * Can be called static, in which case it only sets modifications. - * - * @param string $name cell-name - * @param string $attr attribute-name - * @param mixed $val if not NULL sets attribute else returns it - * @return reference to attribute - */ - public static function &setElementAttribute($name,$attr,$val) - { - //error_log(__METHOD__."('$name', '$attr', ...) request=".get_class(self::$request).", response=".get_class(self::$response).function_backtrace()); - $ref =& self::$request->modifications[$name][$attr]; - if(self::$request && self::$response && (!isset($this) || $val != $this->attrs[$attr])) - { - // In an AJAX response - automatically add - self::$response->generic('assign',array( - 'etemplate_exec_id' => self::$request->id(), - 'id' => $name, - 'key' => $attr, - 'value' => $val - )); - // Don't delete it - self::$request->unset_to_process(''); - //error_log(__METHOD__."('$name', '$attr', ...) ".function_backtrace()); - } - if (isset($this)) $this->attrs[$attr] = $val; - if (!is_null($val)) $ref = $val; - - //error_log(__METHOD__."('$name', '$attr', ".array2string($val).')'); - return $ref; - } - - /** - * disables all cells with name == $name - * - * @param sting $name cell-name - * @param boolean $disabled =true disable or enable a cell, default true=disable - * @return reference to attribute - */ - public function disableElement($name,$disabled=True) - { - return self::setElementAttribute($name, 'disabled', $disabled); - } -} +class etemplate_widget extends Widget {} /** - * *box widgets having an own namespace - */ -class etemplate_widget_box extends etemplate_widget -{ - /** - * (Array of) comma-separated list of legacy options to automatically replace when parsing with set_attrs - * - * @var string|array - */ - protected $legacy_options = array( - 'box' => ',cellpadding,cellspacing,keep', - 'hbox' => 'cellpadding,cellspacing,keep', - 'vbox' => 'cellpadding,cellspacing,keep', - 'groupbox' => 'cellpadding,cellspacing,keep', - ); - - /** - * Run a given method on all children - * - * Reimplemented because grids and boxes can have an own namespace. - * GroupBox has no namespace! - * - * @param string $method_name - * @param array $params =array('') parameter(s) first parameter has to be cname! - * @param boolean $respect_disabled =false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children - */ - public function run($method_name, $params=array(''), $respect_disabled=false) - { - $cname =& $params[0]; - $expand =& $params[1]; - $old_cname = $params[0]; - $old_expand = $params[1]; - - if ($this->id && $this->type != 'groupbox') $cname = self::form_name($cname, $this->id, $params[1]); - if ($expand['cname'] !== $cname && $cname) - { - $expand['cont'] =& self::get_array(self::$request->content, $cname); - $expand['cname'] = $cname; - } - if ($respect_disabled && ($disabled = $this->attrs['disabled'] && self::check_disabled($this->attrs['disabled'], $expand))) - { - //error_log(__METHOD__."('$method_name', ".array2string($params).', '.array2string($respect_disabled).") $this disabled='{$this->attrs['disabled']}'=".array2string($disabled).": NOT running"); - return; - } - if (method_exists($this, $method_name)) - { - call_user_func_array(array($this, $method_name), $params); - } - - // Expand children - $columns_disabled = null; - for($n = 0; ; ++$n) - { - if (isset($this->children[$n])) - { - $child =& $this->children[$n]; - // If type has something that can be expanded, we need to expand it so the correct method is run - $this->expand_widget($child, $expand); - } - // check if we need to autorepeat last row ($child) - elseif (isset($child) && $child->type == 'box' && $this->need_autorepeat($child, $cname, $expand)) - { - // Set row for repeating - $expand['row'] = $n; - // not breaking repeats last row/column ($child) - } - else - { - break; - } - //error_log('Running ' . $method_name . ' on child ' . $n . '(' . $child . ') ['.$expand['row'] . ','.$expand['c'] . ']'); - $disabled = $child->run($method_name, $params, $respect_disabled, $columns_disabled) === false; - } - - $params[0] = $old_cname; - $params[1] = $old_expand; - - return true; - } - - /** - * Check if a box child needs autorepeating, because still content left - * - * We only check passed widget and direct children. - * - * @param string $cname - * @param array $expand - */ - private function need_autorepeat(etemplate_widget &$widget, $cname, array $expand) - { - foreach(array($widget) + $widget->children as $check_widget) - { - $pat = $check_widget->id; - while(($pattern = strstr($pat, '$'))) - { - $pat = substr($pattern,$pattern[1] == '{' ? 2 : 1); - - $Ok = $pat[0] == 'r' && !(substr($pat,0,2) == 'r_' || - substr($pat,0,4) == 'row_' && substr($pat,0,8) != 'row_cont'); - - if ($Ok && ($fname=self::form_name($cname, $check_widget->id, $expand)) && - // need to break if fname ends in [] as get_array() will ignore it and returns whole array - // for an id like "run[$row_cont[appname]]" - substr($fname, -2) != '[]' && - ($value = self::get_array(self::$request->content, $fname)) !== null) // null = not found (can be false!) - { - //error_log(__METHOD__."($widget,$cname) $this autorepeating row $expand[row] because of $check_widget->id = '$fname' is ".array2string($value)); - unset($value); - return true; - } - } - } - - return false; - } -} -// register class for layout widgets, which can have an own namespace -etemplate_widget::registerWidget('etemplate_widget_box', array('box', 'hbox', 'vbox', 'groupbox')); - -/** - * Description widget + * eTemplate Extension: Entry widget * - * Reimplemented to set legacy options + * This widget can be used to fetch fields of any entry specified by its ID. + * The entry is loaded once and shared amoung widget that need it. + * + * @deprecated use Api\Etemplate\Widget\Entry */ -class etemplate_widget_description extends etemplate_widget -{ - /** - * (Array of) comma-separated list of legacy options to automatically replace when parsing with set_attrs - * - * @var string|array - */ - protected $legacy_options = 'bold-italic,link,activate_links,label_for,link_target,link_popup_size,link_title'; -} +abstract class etemplate_widget_entry extends Widget\Entry {} + +/** + * eTemplate Tree widget + * + * @deprecated use Api\Etemplate\Widget\Tree + */ +class etemplate_widget_tree extends Widget\Tree {} + +/** + * eTemplate Select widget + * + * @deprecated use Api\Etemplate\Widget\Select + */ +class etemplate_widget_menupopup extends Widget\Select {} + +/** + * eTemplate Link widgets + * + * @deprecated use Api\Etemplate\Widget\Link + */ +class etemplate_widget_link extends Widget\Link {} + +/** + * eTemplate Nextmatch widgets + * + * @deprecated use Api\Etemplate\Widget\Nextmatch + */ +class etemplate_widget_nextmatch extends Widget\Nextmatch {} + +/** + * eTemplate Taglist widgets + * + * @deprecated use Api\Etemplate\Widget\Taglist + */ +class etemplate_widget_taglist extends Widget\Taglist {} + +/** + * eTemplate File widgets + * + * @deprecated use Api\Etemplate\Widget\File + */ +class etemplate_widget_file extends Widget\File {} + +/** + * eTemplate Vfs widgets + * + * @deprecated use Api\Etemplate\Widget\Vfs + */ +class etemplate_widget_vfs extends Widget\Vfs {} diff --git a/etemplate/inc/class.etemplate_widget_transformer.inc.php b/etemplate/inc/class.etemplate_widget_transformer.inc.php index 11306dfecf..e4d8b6a5f0 100644 --- a/etemplate/inc/class.etemplate_widget_transformer.inc.php +++ b/etemplate/inc/class.etemplate_widget_transformer.inc.php @@ -7,62 +7,19 @@ * @subpackage api * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-11 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +use EGroupware\Api\Etemplate\Widget\Transformer; + /** * eTemplate serverside base widget, to define new widgets using a transformation out of existing widgets + * + * @deprecated use Api\Etemplate\Widget\Transformer */ -abstract class etemplate_widget_transformer extends etemplate_widget +abstract class etemplate_widget_transformer extends Transformer { - /** - * Array with a transformation description, based on attributes to modify. - * - * Exampels: - * - * * 'type' => array('some' => 'other') - * if 'type' attribute equals 'some' replace it with 'other' - * - * * 'type' => array('some' => array('type' => 'other', 'options' => 'otheroption') - * same as above, but additonally set 'options' attr to 'otheroption' - * - * --> leaf element is the action, if previous filters are matched: - * - if leaf is scalar, it just replaces the previous filter value - * - if leaf is an array, it contains assignments for (multiple) attributes: attr => value pairs - * - * * 'type' => array( - * 'some' => array(...), - * 'other' => array(...), - * '__default__' => array(...), - * ) - * it's possible to have a list of filters with actions to run, plus a '__default__' which matches all not explicitly named values - * - * * 'value' => array('__callback__' => 'app.class.method' || 'class::method' || 'method') - * run value through a *serverside* callback, eg. reading an entry based on it's given id - * callback signature: mixed function(mixed $attr[, array $attrs]) - * - * * 'value' => array('__js__' => 'function(value) { return value+5; }') - * run value through a *clientside* callback running in the context of the widget - * - * * 'name' => '@name[@options]' - * replace value of 'name' attribute with itself (@name) plus value of options in square brackets - * * 'value' => '@value[@options]' - * replace value array with value for key taken from value of options attribute - * - * --> attribute name prefixed with @ sign means value of given attribute - * - * @var array - */ - protected static $transformation = array(); - - /** - * Switching debug messages to error_log on/off - * - * @var boolean - */ - const DEBUG = false; - /** * Rendering transformer widget serverside as an old etemplate extension * @@ -78,6 +35,8 @@ abstract class etemplate_widget_transformer extends etemplate_widget */ public function pre_process($name,&$value,&$cell,&$readonlys,&$extension_data,&$tmpl) { + unset($readonlys, $extension_data, $tmpl); // not used but required by function signature + $_value = $value; $_cell = $cell; $cell['value'] =& $value; @@ -93,167 +52,4 @@ abstract class etemplate_widget_transformer extends etemplate_widget error_log(__METHOD__."('$name', ".(is_array($_value)?$_value['id']:$_value).", ".array2string($_cell).", ...) transformed to ".array2string($cell)." and value=".array2string($value)); return true; } - - /** - * Fill type options in self::$request->sel_options to be used on the client - * - * @param string $cname - */ - public function beforeSendToClient($cname, array $expand=array()) - { - $attrs = $this->attrs; - $form_name = self::form_name($cname, $this->id); - if (empty($this->id)) - { - error_log(__METHOD__."() $this has no id!"); - return; - } - $attrs['value'] = $value =& self::get_array(self::$request->content, $form_name, false, true); - $attrs['type'] = $this->type; - $attrs['id'] = $this->id; - - $unmodified = $attrs; - - // run the transformation - foreach(static::$transformation as $filter => $data) - { - $this->action($filter, $data, $attrs); - } - - //echo $this; _debug_array($unmodified); _debug_array($attrs); _debug_array(array_diff_assoc($attrs, $unmodified)); - // compute the difference and send it to the client as modifications - $type_changed = false; - foreach(array_diff_assoc($attrs, $unmodified) as $attr => $val) - { - switch($attr) - { - case 'value': - if ($val != $value) - { - $value = $val; // $value is reference to self::$request->content - } - break; - case 'sel_options': - self::$request->sel_options[$form_name] = $val; - break; - case 'type': // not an attribute in etemplate2 - $type_changed = true; - if($val == 'template') - { - // If the widget has been transformed into a template, we - // also need to try and instanciate & parse the template too - $transformed_template = etemplate_widget_template::instance($attrs['template']); - if($transformed_template) - { - $this->expand_widget($transformed_template, $expand); - $transformed_template->run('beforeSendToClient',array($cname,$expand)); - } - $type_changed = false; - } - default: - self::setElementAttribute($form_name, $attr, $val); - break; - } - } - if($type_changed) - { - // Run the new widget type's beforeSendToClient - $expanded_child = self::factory($attrs['type'], false,$this->id); - $expanded_child->id = $this->id; - $expanded_child->type = $attrs['type']; - $expanded_child->attrs = $attrs; - $expanded_child->run('beforeSendToClient',array($cname,$expand)); - } - } - - /** - * Recursively run given action(s) on an attribute value - * - * @param string $attr attribute concerned - * @param int|string|array $action action to run - * @param array &$attrs attributes - * @throws egw_exception_wrong_parameter if $action is of wrong type - */ - protected function action($attr, $action, array &$attrs) - { - if (self::DEBUG) error_log(__METHOD__."('$attr', ".array2string($action).')'); - // action is an assignment - if (is_scalar($action) || is_null($action)) - { - // check if assignment contains placeholders --> replace them - if (strpos($action, '@') !== false) - { - $replace = array(); - foreach($attrs as $a => $v) - { - if (is_scalar($v) || is_null($v)) $replace['@'.$a] = $v; - } - $action = strtr($action, $replace); - // now replace with non-scalar value, eg. if values is an array: "@value", "@value[key] or "@value[@key]" - if (($a = strstr($action, '@'))) - { - $action = self::get_array($attrs, substr($a,1)); - } - } - $attrs[$attr] = $action; - if (self::DEBUG) error_log(__METHOD__."('$attr', ".array2string($action).") attrs['$attr'] = ".array2string($action).', attrs='.array2string($attrs)); - } - // action is a serverside callback - elseif(is_array($action) && isset($action['__callback__'])) - { - if (!is_string(($callback = $action['__callback__']))) - { - throw new egw_exception_wrong_parameter(__METHOD__."('$attr', ".array2string($action).', '.array2string($attrs).') wrong datatype for callback!'); - } - if (method_exists($this, $callback)) - { - $attrs[$attr] = $this->$callback($attrs[$attr], $attrs); - } - elseif(count(explode('.', $callback)) == 3) - { - $attrs[$attr] = ExecMethod($callback, $attrs[$attr], $attrs); - } - elseif (is_callable($callback, false)) - { - $attrs[$attr] = call_user_func($callback, $attrs[$attr], $attrs); - } - else - { - throw new egw_exception_wrong_parameter(__METHOD__."('$attr', ".array2string($action).', '.array2string($attrs).') wrong datatype for callback!'); - } - } - // action is a clientside callback - elseif(is_array($action) && isset($action['__js__'])) - { - // nothing to do here - } - // TODO: Might be a better way to handle when value to be set is an array - elseif(is_array($action) && $attr == 'sel_options') - { - $attrs[$attr] = $action; - } - // action is a switch --> check cases - elseif(is_array($action)) - { - // case matches --> run all actions - if (isset($action[$attrs[$attr]]) || !isset($action[$attrs[$attr]]) && isset($action['__default__'])) - { - $actions = isset($action[$attrs[$attr]]) ? $action[$attrs[$attr]] : $action['__default__']; - if(!is_array($actions)) - { - $attrs[$attr] = $actions; - $actions = array($attr => $actions); - } - if (self::DEBUG) error_log(__METHOD__."(attr='$attr', action=".array2string($action).") attrs['$attr']=='{$attrs[$attr]}' --> running actions"); - foreach($actions as $attr => $action) - { - $this->action($attr, $action, $attrs); - } - } - } - else - { - throw new egw_exception_wrong_parameter(__METHOD__."(attr='$attr', action=".array2string($action).', attrs='.array2string($attrs).') wrong datatype for action!'); - } - } } diff --git a/etemplate/inc/class.link_widget.inc.php b/etemplate/inc/class.link_widget.inc.php index 418ef42423..0a4536be5b 100644 --- a/etemplate/inc/class.link_widget.inc.php +++ b/etemplate/inc/class.link_widget.inc.php @@ -7,10 +7,12 @@ * @subpackage extensions * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2002-10 by RalfBecker@outdoor-training.de + * @copyright 2002-16 by RalfBecker@outdoor-training.de * @version $Id$ */ +use EGroupware\Api; + /** * eTemplate Extension: several widgets as user-interface for the link-class * @@ -763,7 +765,7 @@ class link_widget $search = $extra_array; } // open request - if ($etemplate_exec_id) $request = etemplate_request::read($etemplate_exec_id); + if ($etemplate_exec_id) $request = Api\Etemplate\Request::read($etemplate_exec_id); $response = new xajaxResponse(); $options = array(); @@ -844,7 +846,7 @@ class link_widget static function ajax_get_types($app,$id_res,$etemplate_exec_id) { // open request - if ($etemplate_exec_id) $request = etemplate_request::read($etemplate_exec_id); + if ($etemplate_exec_id) $request = Api\Etemplate\Request::read($etemplate_exec_id); $response = new xajaxResponse(); //$args = func_get_args(); $response->addAlert("link_widget::ajax_search('".implode("',\n'",$args)."')\n calling link->query( $app , $search )" ); diff --git a/etemplate/inc/class.soetemplate.inc.php b/etemplate/inc/class.soetemplate.inc.php index ffb30b4766..35bf962d0f 100644 --- a/etemplate/inc/class.soetemplate.inc.php +++ b/etemplate/inc/class.soetemplate.inc.php @@ -161,7 +161,7 @@ class soetemplate * * @param string $type type of the widget * @param string $name name of widget - * @param array $attributes=null array with further attributes + * @param array $attributes =null array with further attributes * @return array the cell */ static function empty_cell($type='label',$name='',$attributes=null) @@ -1102,9 +1102,9 @@ class soetemplate * * Please note: as call_user_func_array does not return references, methods ($func is an array) can not either!!! * - * @param string/array $func function to use or array($obj,'method') + * @param string|array $func function to use or array($obj,'method') * @param mixed &$extra extra parameter passed to function - * @param string $path='/' start-path + * @param string $path ='/' start-path * @return mixed return-value of func or null if nothing returned at all */ function &widget_tree_walk($func,&$extra,$path='/') @@ -1146,7 +1146,7 @@ class soetemplate * a further widget with children. * * @param array $widget the widget(-tree) the function should be applied too - * @param string/array $func function to use or array($obj,'method') + * @param string|array $func function to use or array($obj,'method') * @param mixed &$extra extra parameter passed to function * @param string $path path of widget in the widget-tree * @return mixed return-value of func or null if nothing returned at all