diff --git a/etemplate/inc/class.boetemplate.inc.php b/etemplate/inc/class.boetemplate.inc.php index ab98c6579e..b7d82891ba 100644 --- a/etemplate/inc/class.boetemplate.inc.php +++ b/etemplate/inc/class.boetemplate.inc.php @@ -1,1003 +1,998 @@ - * @version $Id$ - */ +/** + * eGroupWare EditableTemplates - Business Objects + * + * @link http://www.egroupware.org + * @author Ralf Becker + * @copyright 2002-8 by RalfBecker@outdoor-training.de + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @version $Id$ + */ - include_once(EGW_INCLUDE_ROOT . '/etemplate/inc/class.soetemplate.inc.php'); +/** + * Business Object for eTemplates, extending the Storage Object + * + * Not so much so far, as the most logic is still in the UI-class + */ +class boetemplate extends soetemplate +{ + var $types = array( + 'label' => 'Label', // Label $cell['label'] is (to be translated) textual content + 'text' => 'Text', // Textfield 1 Line (size = [length][,maxlength]) + 'int' => 'Integer', // like text, but only numbers (size = [min][,max]) + 'float' => 'Floating Point', // --------------- " -------------------------- + 'textarea'=> 'Textarea', // Multiline Text Input (size = [rows][,cols]) + 'htmlarea' => 'Formatted Text (HTML)', + 'checkbox'=> 'Checkbox', + 'radio' => 'Radiobutton', // Radiobutton (size = value if checked) + 'button'=> 'Submitbutton', + 'buttononly' => 'Button', // input type="button" + 'hrule' => 'Horizontal Rule', + 'template' => 'Template', // $cell['name'] contains template-name, $cell['size'] index into $content,$cname,$readonlys + 'image' => 'Image', // label = url, name=link or method, help=alt or title + 'date' => '', // Datefield, size='' timestamp or size=format like 'm/d/Y' + 'select'=> 'Selectbox', // Selectbox ($sel_options[$name] or $content[options-$name] is array with options) + // if size > 1 then multiple selections, size lines showed + 'html' => 'Html', // Raw html in $content[$cell['name']] + 'file' => 'FileUpload', // show an input type='file', set the local name as ${name}_path + 'vbox' => 'VBox', // a (vertical) box to contain widgets in rows, size = # of rows + 'hbox' => 'HBox', // a (horizontal) box to contain widgets in cols, size = # of cols + 'groupbox' => 'GroupBox', // a box with a label containing other elements to group them (html: fieldset) + 'box' => 'Box', // just a container for widgets (html: div) + 'grid' => 'Grid', // tabular widget containing rows with columns of widgets + 'deck' => 'Deck', // a container of elements where only one is visible, size = # of elem. + 'passwd' => 'Password' // a text of type password + ); + private static $garbage_collection_done; /** - * Business Object for eTemplates, extending the Storage Object + * constructor of class * - * Not so much so far, as the most logic is still in the UI-class + * Calls the constructor of soetemplate * - * @package etemplate - * @subpackage api - * @author RalfBecker-AT-outdoor-training.de - * @license GPL + * @param string/array $name name of etemplate or array with name and other keys + * @param string/array $load_via name or array with keys of other etemplate to load in order to get $name */ - class boetemplate extends soetemplate + function boetemplate($name='',$load_via='') { - var $extensions = array(); + $this->soetemplate(); - var $types = array( - 'label' => 'Label', // Label $cell['label'] is (to be translated) textual content - 'text' => 'Text', // Textfield 1 Line (size = [length][,maxlength]) - 'int' => 'Integer', // like text, but only numbers (size = [min][,max]) - 'float' => 'Floating Point', // --------------- " -------------------------- - 'textarea'=> 'Textarea', // Multiline Text Input (size = [rows][,cols]) - 'htmlarea' => 'Formatted Text (HTML)', - 'checkbox'=> 'Checkbox', - 'radio' => 'Radiobutton', // Radiobutton (size = value if checked) - 'button'=> 'Submitbutton', - 'buttononly' => 'Button', // input type="button" - 'hrule' => 'Horizontal Rule', - 'template' => 'Template', // $cell['name'] contains template-name, $cell['size'] index into $content,$cname,$readonlys - 'image' => 'Image', // label = url, name=link or method, help=alt or title - 'date' => '', // Datefield, size='' timestamp or size=format like 'm/d/Y' - 'select'=> 'Selectbox', // Selectbox ($sel_options[$name] or $content[options-$name] is array with options) - // if size > 1 then multiple selections, size lines showed - 'html' => 'Html', // Raw html in $content[$cell['name']] - 'file' => 'FileUpload', // show an input type='file', set the local name as ${name}_path - 'vbox' => 'VBox', // a (vertical) box to contain widgets in rows, size = # of rows - 'hbox' => 'HBox', // a (horizontal) box to contain widgets in cols, size = # of cols - 'groupbox' => 'GroupBox', // a box with a label containing other elements to group them (html: fieldset) - 'box' => 'Box', // just a container for widgets (html: div) - 'grid' => 'Grid', // tabular widget containing rows with columns of widgets - 'deck' => 'Deck', // a container of elements where only one is visible, size = # of elem. - 'passwd' => 'Password' // a text of type password - ); - var $garbage_collection_done; - - /** - * constructor of class - * - * Calls the constructor of soetemplate - * - * @param string/array $name name of etemplate or array with name and other keys - * @param string/array $load_via name or array with keys of other etemplate to load in order to get $name - */ - function boetemplate($name='',$load_via='') + $tname = &$name; + if (is_array($name)) { - $this->soetemplate(); - - $tname = &$name; - if (is_array($name)) - { - $tname = &$name['name']; - } - $tname = (strpos($tname,'.') === false && !empty($tname) ? - (is_array($load_via) ? $load_via['name'] : $load_via).'.':'').$tname; - - if (empty($tname) || !$this->read($name,'','',0,'',$load_via)) - { - $this->init($name); - } - $this->garbage_collection_done =& $GLOBALS['egw_info']['etemplate']['garbage_collection_done']; + $tname = &$name['name']; } + $tname = (strpos($tname,'.') === false && !empty($tname) ? + (is_array($load_via) ? $load_via['name'] : $load_via).'.':'').$tname; - /** - * 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 $content the content-array in the context of the grid - * @return boolean true if the row/col is disabled or false if not - */ - function check_disabled($disabled,$content) + if (empty($tname) || !$this->read($name,'','',0,'',$load_via)) { - if ($this->onclick_handler && !$this->no_onclick) - { - return false; // we have an onclick handler - } - //return False; - if ($not = $disabled[0] == '!') - { - $disabled = substr($disabled,1); - } - list($val,$check_val) = $vals = explode('=',$disabled); - - if ($val[0] == '@') - { - $val = $this->get_array($content,substr($val,1)); - } - if ($check_val[0] == '@') - { - $check_val = $this->get_array($content,substr($check_val,1)); - } - $result = count($vals) == 1 ? $val != '' : ($check_val{0} == '/' ? preg_match($check_val,$val) : $val == $check_val); - if ($not) $result = !$result; - //echo "

check_disabled: '".($not?'!':'')."$disabled' = '$val' ".(count($vals) == 1 ? '' : ($not?'!':'=')."= '$check_val'")." = ".($result?'True':'False')."

\n"; - return $result; + $this->init($name); } + } - /** - * 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" !!! - * - * @static - * @param sring $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 - */ - function expand_name($name,$c,$row,$c_='',$row_='',$cont='') + /** + * 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 $content the content-array in the context of the grid + * @return boolean true if the row/col is disabled or false if not + */ + protected function check_disabled($disabled,$content) + { + if ($this->onclick_handler && !$this->no_onclick) { - $is_index_in_content = $name[0] == '@'; - if (strpos($name,'$') !== false) - { - if (!$cont) - { - $cont = array(); - } - if (!is_numeric($c)) $c = boetemplate::chrs2num($c); - $col = boetemplate::num2chrs($c-1); // $c-1 to get: 0:'@', 1:'A', ... - $col_ = boetemplate::num2chrs($c_-1); - $row_cont = $cont[$row]; - $col_row_cont = $cont[$col.$row]; - - eval('$name = "'.$name.'";'); - } - if ($is_index_in_content) - { - $name = boetemplate::get_array($cont,substr($name,1)); - } - return $name; + return false; // we have an onclick handler } - - /** - * Checks if we have an row- or column autorepeat and sets the indexes for $content, etc. - * - * Autorepeat is important to allow a variable numer of rows or cols, eg. for a list. - * The eTemplate has only one (have to be the last) row or column, which gets - * automaticaly repeated as long as content is availible. To check this the content - * has to be in an sub-array of content. The index / subscript into content is - * determined by the content of size for templates or name for regular fields. - * An autorepeat is defined by an index which contains variables to expand. - * (vor variable expansion in names see expand_names). Usually I use the keys - * $row: 0, 1, 2, 3, ... for only rows, $col: '@', 'A', 'B', 'C', ... for only cols or - * $col$row: '@0','A0',... '@1','A1','B1',... '@2','A2','B2',... for both rows and cells. - * In general everything expand_names can generate is ok - see there. - * As you usually have col- and row-headers, data-cells start with '1' or 'A' !!! - * - * @param array $cell with data of cell: name, type, size, ... - * @param int $c,$r col/row index starting from 0 - * @param string &$idx returns the index in $content and $readonlys (NOT $sel_options !!!) - * @param string &$idx_cname returns the basename for the form-name: is $idx if only one value - * (no ',') is given in size (name (not template-fields) are always only one value) - * @param boolean $check_col to check for col- or row-autorepeat - * @return boolean true if cell is autorepeat (has index with vars / '$') or false otherwise - */ - function autorepeat_idx($cell,$c,$r,&$idx,&$idx_cname,$check_col=False,$cont=null) + //return False; + if ($not = $disabled[0] == '!') { - $org_idx = $idx = $cell[ $cell['type'] == 'template' ? 'size' : 'name' ]; + $disabled = substr($disabled,1); + } + list($val,$check_val) = $vals = explode('=',$disabled); - $idx = $this->expand_name($idx,$c,$r,'','',$cont); - if (!($komma = strpos($idx,','))) + if ($val[0] == '@') + { + $val = $this->get_array($content,substr($val,1)); + } + if ($check_val[0] == '@') + { + $check_val = $this->get_array($content,substr($check_val,1)); + } + $result = count($vals) == 1 ? $val != '' : ($check_val{0} == '/' ? preg_match($check_val,$val) : $val == $check_val); + if ($not) $result = !$result; + //echo "

check_disabled: '".($not?'!':'')."$disabled' = '$val' ".(count($vals) == 1 ? '' : ($not?'!':'=')."= '$check_val'")." = ".($result?'True':'False')."

\n"; + return $result; + } + + /** + * 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 sring $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 + */ + static function expand_name($name,$c,$row,$c_='',$row_='',$cont='') + { + $is_index_in_content = $name[0] == '@'; + if (strpos($name,'$') !== false) + { + if (!$cont) { - $idx_cname = $idx; + $cont = array(); + } + if (!is_numeric($c)) $c = boetemplate::chrs2num($c); + $col = boetemplate::num2chrs($c-1); // $c-1 to get: 0:'@', 1:'A', ... + $col_ = boetemplate::num2chrs($c_-1); + $row_cont = $cont[$row]; + $col_row_cont = $cont[$col.$row]; + + eval('$name = "'.$name.'";'); + } + if ($is_index_in_content) + { + $name = boetemplate::get_array($cont,substr($name,1)); + } + return $name; + } + + /** + * Checks if we have an row- or column autorepeat and sets the indexes for $content, etc. + * + * Autorepeat is important to allow a variable numer of rows or cols, eg. for a list. + * The eTemplate has only one (have to be the last) row or column, which gets + * automaticaly repeated as long as content is availible. To check this the content + * has to be in an sub-array of content. The index / subscript into content is + * determined by the content of size for templates or name for regular fields. + * An autorepeat is defined by an index which contains variables to expand. + * (vor variable expansion in names see expand_names). Usually I use the keys + * $row: 0, 1, 2, 3, ... for only rows, $col: '@', 'A', 'B', 'C', ... for only cols or + * $col$row: '@0','A0',... '@1','A1','B1',... '@2','A2','B2',... for both rows and cells. + * In general everything expand_names can generate is ok - see there. + * As you usually have col- and row-headers, data-cells start with '1' or 'A' !!! + * + * @param array $cell with data of cell: name, type, size, ... + * @param int $c,$r col/row index starting from 0 + * @param string &$idx returns the index in $content and $readonlys (NOT $sel_options !!!) + * @param string &$idx_cname returns the basename for the form-name: is $idx if only one value + * (no ',') is given in size (name (not template-fields) are always only one value) + * @param boolean $check_col to check for col- or row-autorepeat + * @return boolean true if cell is autorepeat (has index with vars / '$') or false otherwise + */ + function autorepeat_idx($cell,$c,$r,&$idx,&$idx_cname,$check_col=False,$cont=null) + { + $org_idx = $idx = $cell[ $cell['type'] == 'template' ? 'size' : 'name' ]; + + $idx = $this->expand_name($idx,$c,$r,'','',$cont); + if (!($komma = strpos($idx,','))) + { + $idx_cname = $idx; + } + else + { + $idx_cname = substr($idx,1+$komma); + $idx = substr($idx,0,$komma); + } + $Ok = False; + $pat = $org_idx; + while (!$Ok && ($pat = @strstr($pat,'$'))) + { + $pat = substr($pat,$pat[1] == '{' ? 2 : 1); + + if ($check_col) + { + $Ok = $pat[0] == 'c' && !(substr($pat,0,4) == 'cont' || + substr($pat,0,2) == 'c_' || substr($pat,0,4) == 'col_'); } else { - $idx_cname = substr($idx,1+$komma); - $idx = substr($idx,0,$komma); + $Ok = $pat[0] == 'r' && !(substr($pat,0,2) == 'r_' || + substr($pat,0,4) == 'row_'); } - $Ok = False; - $pat = $org_idx; - while (!$Ok && ($pat = @strstr($pat,'$'))) - { - $pat = substr($pat,$pat[1] == '{' ? 2 : 1); + } + if ($this->name && $this->name == $this->debug) + { + echo "$this->name ".($check_col ? 'col' : 'row')."-check: c=$c, r=$r, idx='$org_idx'='$idx' idx_cname='$idx_cname' ==> ".($Ok?'True':'False')."

\n"; + } + return $Ok; + } - if ($check_col) + /** + * creates a new appsession-id via microtime() + * @return string + */ + protected static function appsession_id() + { + list($msec,$sec) = explode(' ',microtime()); + $time = 100 * $sec + (int)(100 * $msec); // gives precision of 1/100 sec + $id = $GLOBALS['egw_info']['flags']['currentapp'] .':'. $time; + //echo "

microtime()=".microtime().", sec=$sec, msec=$msec, id=$id

\n"; + return $id; + } + + /** + * saves content,readonlys,template-keys, ... via the appsession function + * + * As a user may open several windows with the same content/template wie generate a location-id from microtime + * which is used as location for appsession to descriminate between the different windows. This location-id + * is then saved as a hidden-var in the form. The above mentions session-id has nothing to do / is different + * from the session-id which is constant for all windows opened in one session. + * + * @param array $data the data to save + * @param string $id the id to use or '' to generate a new id + * @return string location-id + */ + protected static function save_appsession($data,$id='') + { + if (!$id) + { + $id = self::appsession_id(); + } + $GLOBALS['egw']->session->appsession($id,'etemplate',$data); + + if (substr($GLOBALS['egw_info']['server']['sessions_type'],0,4) == 'php4' && !self::$garbage_collection_done) + { + return self::php_session_garbage_collection(); + } + return $id; + } + + /** + * gets content,readonlys,template-keys, ... back from the appsession function + * + * @param string $id the location-id + * @return array with session-data + */ + protected function get_appsession($id) + { + $data = $GLOBALS['egw']->session->appsession($id,'etemplate'); + //echo "boetemplate::get_appsession('$id')"; _debug_array($data); + + if (substr($GLOBALS['egw_info']['server']['sessions_type'],0,4) == 'php4') + { + self::php_session_garbage_collection($id); + } + return $data; + } + + /** + * a little bit of garbage collection for php4 sessions (their size is limited by memory_limit) + * + * With constant eTemplate use it can grow quite big and lead to unusable sessions (php terminates + * before any output with "Allowed memory size of ... exhausted"). + * We delete now sessions once used after 10min and sessions never or multiple used after 60min. + * + * @param string $id_used id of session just read by get_appsession to increment the usage counter + */ + static private function php_session_garbage_collection($id_used='') + { + if (!defined('EGW_SESSION_VAR')) return; // for 1.0.0 compatibility + + // now we are on php4 sessions and do a bit of garbage collection + $app_sessions =& $_SESSION[EGW_SESSION_VAR]['app_sessions']['etemplate']; + $session_used =& $app_sessions['session_used']; + + if ($id_used) + { + //echo "session_used[$id_used]='".$session_used[$id_used]."'
\n"; + ++$session_used[$id_used]; // count the number of times a session got used + } + self::$garbage_collection_done = true; + + if (count($app_sessions) < 20) return $data; // we dont need to care + + list($msec,$sec) = explode(' ',microtime()); + $now = 100 * $sec + (int)(100 * $msec); // gives precision of 1/100 sec + + foreach(array_keys($app_sessions) as $id) + { + list($app,$time) = explode(':',$id); + + if (!$time) continue; // other data, no session + + //echo ++$n.') '.$id.': '.(($now-$time)/100.0)."secs old, used=".$session_used[$id].", size=".strlen($app_sessions[$id])."
\n"; + + if ($session_used[$id] == 1 && $time < $now - 10*6000 || // session used and older then 10min + $time < $now - 60*6000) // session not used and older then 1h + { + //echo "

boetemplate::php_session_garbage_collection('$id_used'): unsetting session '$id' (now=$now)

\n"; + unset($app_sessions[$id]); + unset($session_used[$id]); + } + } + } + + /** + * gets an attribute in a named cell + * + * @param string $name cell-name + * @param string $attr attribute-name + * @return mixed the attribute or False if named cell not found + */ + function &get_cell_attribute($name,$attr) + { + return $this->set_cell_attribute($name,$attr,NULL); + } + + /** + * 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 mixed number of changed cells or False, if none changed + */ + function &set_cell_attribute($name,$attr,$val) + { + //echo "

set_cell_attribute(tpl->name=$this->name, name='$name', attr='$attr',val='$val')

\n"; + + $extra = array(false,$name,$attr,$val); + $result =& $this->widget_tree_walk('set_cell_attribute_helper',$extra); + + if (is_null($val)) + { + return $result; + } + return $extra[0]; + } + + /** + * disables all cells with name == $name + * + * @param sting $name cell-name + * @param boolean $disabled=true disable or enable a cell, default true=disable + * @return mixed number of changed cells or False, if none changed + */ + function disable_cells($name,$disabled=True) + { + return $this->set_cell_attribute($name,'disabled',$disabled); + } + + /** + * set one or more attibutes for row $n + * + * @param int $n numerical row-number starting with 1 (!) + * @param string $height percent or pixel or '' for no height + * @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 + * @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') + { + $grid =& $this->get_widget_by_path($path); + if (is_null($grid) || $grid['type'] != 'grid') return false; + $grid_attr =& $grid['data'][0]; + + list($old_height,$old_disabled) = explode(',',$grid_attr["h$n"]); + $disabled = $disabled !== 0 ? $disabled : $old_disabled; + $grid_attr["h$n"] = ($height !== 0 ? $height : $old_height). + ($disabled ? ','.$disabled : ''); + list($old_class,$old_valign) = explode(',',$grid_attr["c$n"]); + $valign = $valign !== 0 ? $valign : $old_valign; + $grid_attr["c$n"] = ($class !== 0 ? $class : $old_class). + ($valign ? ','.$valign : ''); + + list($height,$disabled) = explode(',',$grid_attr["h$n"]); + list($class,$valign) = explode(',',$grid_attr["c$n"]); + return array($height,$class,$valign,$disabled); + } + + /** + * 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 + */ + function disable_row($n,$enable=False,$path='/0') + { + $this->set_row_attributes($n,0,0,0,!$enable,$path); + } + + /** + * set one or more attibutes for column $c + * + * @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 + * @return false if $path specifies no grid or array(width,disabled) otherwise + */ + function set_column_attributes($c,$width=0,$disabled=0,$path='/0') + { + if (is_numeric($c)) + { + $c = $this->num2chrs($c); + } + $grid =& $this->get_widget_by_path($path); + if (is_null($grid) || $grid['type'] != 'grid') return false; + $grid_attr =& $grid['data'][0]; + + list($old_width,$old_disabled) = explode(',',$grid_attr[$c]); + $disabled = $disabled !== 0 ? $disabled : $old_disabled; + $grid_attr[$c] = ($width !== 0 ? $width : $old_width). + ($disabled ? ','.$disabled : ''); + + //echo "set_column_attributes('$c',,'$path'): ".$grid_attr[$c]."

\n"; _debug_array($grid_attr); + return explode(',',$grid_attr[$c]); + } + + /** + * disables column $c + * + * @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 + */ + function disable_column($c,$enable=False,$path='/0') + { + $this->set_column_attributes($c,0,!$enable,$path); + } + + /** + * trys to load the Extension / Widget-class from the app or etemplate + * + * @param string $name name of the extension, the classname should be class.${name}_widget.inc.php + * the $name might be "$name.$app" to give a app-name (default is the current app,or template-name) + * @return string/boolean human readable name or false if not found/loadable + */ + protected function loadExtension($type) + { + list($class,$app) = explode('.',$type); + $class .= '_widget'; + + if (!$app) $app = $GLOBALS['egw_info']['flags']['current_app']; + + if (!file_exists(EGW_SERVER_ROOT."/$app/inc/class.$class.inc.php")) + { + list($app) = explode('_',$type); + } + if (!file_exists(EGW_SERVER_ROOT."/$app/inc/class.$class.inc.php")) + { + list($app) = explode('.',$this->name); + } + if (!file_exists(EGW_SERVER_ROOT."/$app/inc/class.$class.inc.php")) + { + $app = 'etemplate'; + } + if (!file_exists(EGW_SERVER_ROOT."/$app/inc/class.$class.inc.php")) + { + //echo "

boetemplate::loadExtension($type) extension not found

\n"; + return $GLOBALS['egw_info']['etemplate']['extension'][$type] = False; + } + $GLOBALS['egw_info']['etemplate']['extension'][$type] =& CreateObject($app.'.'.$class,$ui='html'); + + //echo "

boetemplate::loadExtension($type) extension found in App. $app

\n"; + return $GLOBALS['egw_info']['etemplate']['extension'][$type]->human_name; + } + + /** + * checks if extension is loaded (load it if it isnt) and optional checks if it has a given method + * + * @param string $name name of the extension, the classname should be class.${name}_widget.inc.php + * the $name might be "$name.$app" to give a app-name (default is the current app,or template-name) + * @param string $function 'pre_process', 'post_process' or 'render' + * @return boolean true if the extension (incl. method) exists, else false + */ + protected function haveExtension($type,$function='') + { + return ($GLOBALS['egw_info']['etemplate']['extension'][$type] || $this->loadExtension($type,$ui)) && + ($function == '' || $GLOBALS['egw_info']['etemplate']['extension'][$type]->public_functions[$function]); + } + + /** + * executes the pre_process-function of the extension $cell[type] + * + * @param string $type type of the extension + * @param string $name form-name of this widget/field (used as a unique index into extension_data) + * @param mixed &$value value of the extensions content(-array) + * @param array &$cell table-cell on which the extension operates + * @param array &$readonlys value of the extensions readonly-setting(-array) + * @return mixed the return-value of the extensions preprocess function + */ + protected function extensionPreProcess($type,$name,&$value,&$cell,&$readonlys) + { + if (!$this->haveExtension($type)) + { + return False; + } + return $GLOBALS['egw_info']['etemplate']['extension'][$type]->pre_process($name,$value,$cell,$readonlys, + $GLOBALS['egw_info']['etemplate']['extension_data'][$name],$this); + } + + /** + * executes the post_process-function of the extension $cell[type] + * + * @param string $type name of the extension + * @param string $name form-name of this widget/field (used as a unique index into extension_data) + * @param mixed &$value returns the value of the extensions content(-array) + * @param mixed $value_in unprocessed value, eg. as posted by the browser + * @return boolean True if a value should be returned (default for no postprocess fkt.), else False + */ + protected function extensionPostProcess($type,$name,&$value,$value_in) + { + if (!$this->haveExtension($type,'post_process')) + { + return True; + } + return $GLOBALS['egw_info']['etemplate']['extension'][$type]->post_process($name,$value, + $GLOBALS['egw_info']['etemplate']['extension_data'][$name], + $GLOBALS['egw_info']['etemplate']['loop'],$this,$value_in); + } + + /** + * executes the render-function of the extension $cell[type] + * + * @param string $type name of the extension + * @param string $name form-name of this widget/field (used as a unique index into extension_data) + * @param mixed &$value value of the extensions content(-array) + * @param array &$cell table-cell on which the extension operates + * @param array &$readonlys value of the extensions readonly-setting(-array) + * @return mixed return-value of the render function + */ + protected function extensionRender($type,$name,&$value,&$cell,$readonly) + { + if (!$this->haveExtension($type,'render')) + { + return False; + } + return $GLOBALS['egw_info']['etemplate']['extension'][$type]->render($cell,$name,$value,$readonly, + $GLOBALS['egw_info']['etemplate']['extension_data'][$name],$this); + } + + /** + * checks if $idx is set in array $arr + * + * for one level of subindes identical to isset($arr[$idx]) + * + * @param array $arr array to check + * @param string $idx may contain multiple subindex (eg.'x[y][z]') + * @return boolean true if set, else false + */ + static function isset_array($arr,$idx) + { + $idxs = explode('[',str_replace(']','',$idx)); + $last_idx = array_pop($idxs); + $pos = &$arr; + foreach($idxs as $idx) + { + if (!is_array($pos)) + { + return False; + } + $pos = &$pos[$idx]; + } + // was return isset($pos[$last_idx]); + // array_key_exists also returns true for keys with value null, which fixes some problems with autorepeating rows + return is_array($pos) && array_key_exists($last_idx,$pos); + } + + /** + * sets $arr[$idx] = $val + * + * This works for non-trival indexes like 'a[b][c]' too: $arr['a']['b']['c'] = $val; + * + * @param array &$arr the array to search + * @param string $idx the index, may contain sub-indices like a[b], see example below + * @param mixed $val value to set + */ + static function set_array(&$arr,$idx,$val) + { + if (!is_array($arr)) + { + die('set_array() $arr is no array
'.function_backtrace()); + } + $idxs = explode('[',str_replace(']','',$idx)); + $pos = &$arr; + foreach($idxs as $idx) + { + $pos = &$pos[$idx]; + } + $pos = $val; + } + + /** + * 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 false 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)) + { + die('set_array() $arr is no array
'.function_backtrace()); + } + if (is_object($idx)) return false; // given an error in php5.2 + + $idxs = explode('[',str_replace(']','',$idx)); + $pos = &$arr; + foreach($idxs as $idx) + { + if (!is_array($pos) && !$referenz_info) + { + return False; + } + if($skip_empty && !isset($pos[$idx])) return false; + $pos = &$pos[$idx]; + } + return $pos; + } + + /** + * unsets $arr[$idx] + * + * This works for non-trival indexes like 'a[b][c]' too + * unset_array($arr,'a[b]'); is equivalent to unset($arr['a']['b']); + * + * @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 + */ + static function unset_array(&$arr,$idx) + { + if (!is_array($arr)) + { + die('set_array() $arr is no array
'.function_backtrace()); + } + $idxs = explode('[',str_replace(']','',$idx)); + $last_idx = array_pop($idxs); + $pos = &$arr; + foreach($idxs as $idx) + { + $pos = &$pos[$idx]; + } + unset($pos[$last_idx]); + } + + /** + * 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 + */ + 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]) && isset($v[count($v)-1])) // or no associative array, eg. selecting multiple accounts { - $Ok = $pat[0] == 'c' && !(substr($pat,0,4) == 'cont' || - substr($pat,0,2) == 'c_' || substr($pat,0,4) == 'col_'); + $old[$k] = $v; } else { - $Ok = $pat[0] == 'r' && !(substr($pat,0,2) == 'r_' || - substr($pat,0,4) == 'row_'); - } - } - if ($this->name && $this->name == $this->debug) - { - echo "$this->name ".($check_col ? 'col' : 'row')."-check: c=$c, r=$r, idx='$org_idx'='$idx' idx_cname='$idx_cname' ==> ".($Ok?'True':'False')."

\n"; - } - return $Ok; - } - - /** - * creates a new appsession-id via microtime() - */ - function appsession_id() - { - list($msec,$sec) = explode(' ',microtime()); - $time = 100 * $sec + (int)(100 * $msec); // gives precision of 1/100 sec - $id = $GLOBALS['egw_info']['flags']['currentapp'] .':'. $time; - //echo "

microtime()=".microtime().", sec=$sec, msec=$msec, id=$id

\n"; - return $id; - } - - /** - * saves content,readonlys,template-keys, ... via the appsession function - * - * As a user may open several windows with the same content/template wie generate a location-id from microtime - * which is used as location for appsession to descriminate between the different windows. This location-id - * is then saved as a hidden-var in the form. The above mentions session-id has nothing to do / is different - * from the session-id which is constant for all windows opened in one session. - * - * @param array $data the data to save - * @param string $id the id to use or '' to generate a new id - * @return string location-id - */ - function save_appsession($data,$id='') - { - if (!$id) - { - $id = $this->appsession_id(); - } - $GLOBALS['egw']->session->appsession($id,'etemplate',$data); - - if (substr($GLOBALS['egw_info']['server']['sessions_type'],0,4) == 'php4' && !$this->garbage_collection_done) - { - return $this->php4_session_garbage_collection(); - } - return $id; - } - - /** - * gets content,readonlys,template-keys, ... back from the appsession function - * - * @param string $id the location-id - * @return array with session-data - */ - function get_appsession($id) - { - $data = $GLOBALS['egw']->session->appsession($id,'etemplate'); - //echo "boetemplate::get_appsession('$id')"; _debug_array($data); - - if (substr($GLOBALS['egw_info']['server']['sessions_type'],0,4) == 'php4') - { - $this->php4_session_garbage_collection($id); - } - return $data; - } - - /** - * a little bit of garbage collection for php4 sessions (their size is limited by memory_limit) - * - * With constant eTemplate use it can grow quite big and lead to unusable sessions (php terminates - * before any output with "Allowed memory size of ... exhausted"). - * We delete now sessions once used after 10min and sessions never or multiple used after 60min. - * - * @param string $id_used id of session just read by get_appsession to increment the usage counter - */ - function php4_session_garbage_collection($id_used='') - { - if (!defined('EGW_SESSION_VAR')) return; // for 1.0.0 compatibility - - // now we are on php4 sessions and do a bit of garbage collection - $app_sessions =& $_SESSION[EGW_SESSION_VAR]['app_sessions']['etemplate']; - $session_used =& $app_sessions['session_used']; - - if ($id_used) - { - //echo "session_used[$id_used]='".$session_used[$id_used]."'
\n"; - ++$session_used[$id_used]; // count the number of times a session got used - } - $this->garbage_collection_done = true; - - if (count($app_sessions) < 20) return $data; // we dont need to care - - list($msec,$sec) = explode(' ',microtime()); - $now = 100 * $sec + (int)(100 * $msec); // gives precision of 1/100 sec - - foreach(array_keys($app_sessions) as $id) - { - list($app,$time) = explode(':',$id); - - if (!$time) continue; // other data, no session - - //echo ++$n.') '.$id.': '.(($now-$time)/100.0)."secs old, used=".$session_used[$id].", size=".strlen($app_sessions[$id])."
\n"; - - if ($session_used[$id] == 1 && $time < $now - 10*6000 || // session used and older then 10min - $time < $now - 60*6000) // session not used and older then 1h - { - //echo "

boetemplate::php4_session_garbage_collection('$id_used'): unsetting session '$id' (now=$now)

\n"; - unset($app_sessions[$id]); - unset($session_used[$id]); + $old[$k] = self::complete_array_merge($old[$k],$v); } } } - - /** - * gets an attribute in a named cell - * - * @static - * @param string $name cell-name - * @param string $attr attribute-name - * @return mixed the attribute or False if named cell not found - */ - function &get_cell_attribute($name,$attr) + return $old; + } + + /** + * returns a reference to a widget in the widget's children tree spezified by a path + * + * The path get's generated by the widget_tree_walk() methode and consists of the keys of the children arrays. + * 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, ... + * @return array referenz to the widget spezified or null, if it's not found + */ + function &get_widget_by_path($path,$ancestor=0) + { + //echo "

boetemplate::get_widget_by_path('$path',$ancestor)

\n"; + $path_parts = explode('/',$path); + while($ancestor-- > 0) { - return $this->set_cell_attribute($name,$attr,NULL); + if (array_pop($path_parts) === '') return null; } + $path = implode('/',$path_parts); + if ($path == '/' || $path === '') return $this->children; + if (count($path_parts) == 2) return $this->children[$path_parts[1]]; - /** - * set an attribute in a named cell if val is not NULL else return the attribute - * - * @static - * @param string $name cell-name - * @param string $attr attribute-name - * @param mixed $val if not NULL sets attribute else returns it - * @return mixed number of changed cells or False, if none changed - */ - function &set_cell_attribute($name,$attr,$val) + return $this->widget_tree_walk('get_widget_by_path_helper',$path); + } + + /** + * returns a reference to a widget in the widget's children tree spezified it's name + * + * It returns the first match! + * + * @param string $name name of the widget + * @return array referenz to the widget spezified or null, if it's not found + */ + function &get_widget_by_name($name) + { + return $this->widget_tree_walk('get_widget_by_name_helper',$name); + } + + /** + * returns an array of references to widgets of the specified type + * + * @param type String + * @return array + */ + function &get_widgets_by_type($type) { + $extra = array( + 'type' => $type, + 'widgets' => array() + ); + $this->widget_tree_walk('get_widgets_by_type_helper', $extra); + return $extra['widgets']; + } + + /** + * generated a file-name from an eTemplates, name, template(-set) and lang + * + * @param string/array $name name of template or array('name'=>$name,'template'=>$template,'lang'=>$lang) + * @param string $template template-set + * @param string $lang language to use + * @return string + */ + private function cache_name($name='',$template='default',$lang='default') + { + if (empty($name)) { - //echo "

set_cell_attribute(tpl->name=$this->name, name='$name', attr='$attr',val='$val')

\n"; - - $extra = array(false,$name,$attr,$val); - $result =& $this->widget_tree_walk('set_cell_attribute_helper',$extra); - - if (is_null($val)) - { - return $result; - } - return $extra[0]; + $name = $this->name; + $template = $this->template; + $lang = $this->lang; } - - /** - * disables all cells with name == $name - * - * @param sting $name cell-name - * @param boolean $disabled=true disable or enable a cell, default true=disable - * @return mixed number of changed cells or False, if none changed - */ - function disable_cells($name,$disabled=True) + elseif (is_array($name)) { - return $this->set_cell_attribute($name,'disabled',$disabled); + $template = $name['template']; + $lang = $name['lang']; + $name = $name['name']; } - - /** - * set one or more attibutes for row $n - * - * @param int $n numerical row-number starting with 1 (!) - * @param string $height percent or pixel or '' for no height - * @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 - * @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') + if (empty($template)) { - $grid =& $this->get_widget_by_path($path); - if (is_null($grid) || $grid['type'] != 'grid') return false; - $grid_attr =& $grid['data'][0]; - - list($old_height,$old_disabled) = explode(',',$grid_attr["h$n"]); - $disabled = $disabled !== 0 ? $disabled : $old_disabled; - $grid_attr["h$n"] = ($height !== 0 ? $height : $old_height). - ($disabled ? ','.$disabled : ''); - list($old_class,$old_valign) = explode(',',$grid_attr["c$n"]); - $valign = $valign !== 0 ? $valign : $old_valign; - $grid_attr["c$n"] = ($class !== 0 ? $class : $old_class). - ($valign ? ','.$valign : ''); - - list($height,$disabled) = explode(',',$grid_attr["h$n"]); - list($class,$valign) = explode(',',$grid_attr["c$n"]); - return array($height,$class,$valign,$disabled); + $template = 'default'; } + $cname = $template . '/' . $name . (!empty($lang) && $lang != 'default' ? '.' . $lang : ''); + //echo "cache_name('$name','$template','$lang') = '$cname'"; - /** - * 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 - */ - function disable_row($n,$enable=False,$path='/0') + return $cname; + } + + /** + * stores the etemplate in the cache in phpgw_info + */ + private function store_in_cache() + { + //echo "

store_in_cache('$this->name','$this->template','$this->lang','$this->version')

\n"; + $GLOBALS['egw_info']['etemplate']['cache'][$this->cache_name()] = $this->as_array(1); + } + + /** + * deletes the etemplate in the cache in phpgw_info + */ + private function delete_in_cache() + { + //echo "

delete_in_cache('$this->name','$this->template','$this->lang','$this->version')

\n"; + unset($GLOBALS['egw_info']['etemplate']['cache'][$this->cache_name()]); + } + + /** + * returns true if a given eTemplate is in the cache + * + * @param string/array $name name of template or array('name'=>$name,'template'=>$template,'lang'=>$lang) + * @param string $template template-set + * @param string $lang language to use + * @param int $group to use the template for + * @param string $version of the template + * @return boolean + */ + private function in_cache($name,$template='default',$lang='default',$group=0,$version='') + { + $cname = $this->cache_name($name,$template,$lang); + if (is_array($name)) { - $this->set_row_attributes($n,0,0,0,!$enable,$path); + $version = $name['version']; + $name = $name['name']; } - - /** - * set one or more attibutes for column $c - * - * @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 - * @return false if $path specifies no grid or array(width,disabled) otherwise - */ - function set_column_attributes($c,$width=0,$disabled=0,$path='/0') + if (!isset($GLOBALS['egw_info']['etemplate']['cache'][$cname]) || + !empty($version) && $GLOBALS['egw_info']['etemplate']['cache'][$cname]['version'] != $version) { - if (is_numeric($c)) - { - $c = $this->num2chrs($c); - } - $grid =& $this->get_widget_by_path($path); - if (is_null($grid) || $grid['type'] != 'grid') return false; - $grid_attr =& $grid['data'][0]; - - list($old_width,$old_disabled) = explode(',',$grid_attr[$c]); - $disabled = $disabled !== 0 ? $disabled : $old_disabled; - $grid_attr[$c] = ($width !== 0 ? $width : $old_width). - ($disabled ? ','.$disabled : ''); - - //echo "set_column_attributes('$c',,'$path'): ".$grid_attr[$c]."

\n"; _debug_array($grid_attr); - return explode(',',$grid_attr[$c]); - } - - /** - * disables column $c - * - * @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 - */ - function disable_column($c,$enable=False,$path='/0') - { - $this->set_column_attributes($c,0,!$enable,$path); - } - - /** - * trys to load the Extension / Widget-class from the app or etemplate - * - * @param string $name name of the extension, the classname should be class.${name}_widget.inc.php - * the $name might be "$name.$app" to give a app-name (default is the current app,or template-name) - * @return string/boolean human readable name or false if not found/loadable - */ - function loadExtension($type) - { - list($class,$app) = explode('.',$type); - $class .= '_widget'; - - if (!$app) $app = $GLOBALS['egw_info']['flags']['current_app']; - - if (!file_exists(EGW_SERVER_ROOT."/$app/inc/class.$class.inc.php")) - { - list($app) = explode('_',$type); - } - if (!file_exists(EGW_SERVER_ROOT."/$app/inc/class.$class.inc.php")) - { - list($app) = explode('.',$this->name); - } - if (!file_exists(EGW_SERVER_ROOT."/$app/inc/class.$class.inc.php")) - { - $app = 'etemplate'; - } - if (!file_exists(EGW_SERVER_ROOT."/$app/inc/class.$class.inc.php")) - { - //echo "

boetemplate::loadExtension($type) extension not found

\n"; - return $GLOBALS['egw_info']['etemplate']['extension'][$type] = False; - } - $GLOBALS['egw_info']['etemplate']['extension'][$type] =& CreateObject($app.'.'.$class,$ui='html'); - - //echo "

boetemplate::loadExtension($type) extension found in App. $app

\n"; - return $GLOBALS['egw_info']['etemplate']['extension'][$type]->human_name; - } - - /** - * checks if extension is loaded (load it if it isnt) and optional checks if it has a given method - * - * @param string $name name of the extension, the classname should be class.${name}_widget.inc.php - * the $name might be "$name.$app" to give a app-name (default is the current app,or template-name) - * @param string $function 'pre_process', 'post_process' or 'render' - * @return boolean true if the extension (incl. method) exists, else false - */ - function haveExtension($type,$function='') - { - return ($GLOBALS['egw_info']['etemplate']['extension'][$type] || $this->loadExtension($type,$ui)) && - ($function == '' || $GLOBALS['egw_info']['etemplate']['extension'][$type]->public_functions[$function]); - } - - /** - * executes the pre_process-function of the extension $cell[type] - * - * @param string $type type of the extension - * @param string $name form-name of this widget/field (used as a unique index into extension_data) - * @param mixed &$value value of the extensions content(-array) - * @param array &$cell table-cell on which the extension operates - * @param array &$readonlys value of the extensions readonly-setting(-array) - * @return mixed the return-value of the extensions preprocess function - */ - function extensionPreProcess($type,$name,&$value,&$cell,&$readonlys) - { - if (!$this->haveExtension($type)) - { - return False; - } - return $GLOBALS['egw_info']['etemplate']['extension'][$type]->pre_process($name,$value,$cell,$readonlys, - $GLOBALS['egw_info']['etemplate']['extension_data'][$name],$this); - } - - /** - * executes the post_process-function of the extension $cell[type] - * - * @param string $type name of the extension - * @param string $name form-name of this widget/field (used as a unique index into extension_data) - * @param mixed &$value returns the value of the extensions content(-array) - * @param mixed $value_in unprocessed value, eg. as posted by the browser - * @return boolean True if a value should be returned (default for no postprocess fkt.), else False - */ - function extensionPostProcess($type,$name,&$value,$value_in) - { - if (!$this->haveExtension($type,'post_process')) - { - return True; - } - return $GLOBALS['egw_info']['etemplate']['extension'][$type]->post_process($name,$value, - $GLOBALS['egw_info']['etemplate']['extension_data'][$name], - $GLOBALS['egw_info']['etemplate']['loop'],$this,$value_in); - } - - /** - * executes the render-function of the extension $cell[type] - * - * @param string $type name of the extension - * @param string $name form-name of this widget/field (used as a unique index into extension_data) - * @param mixed &$value value of the extensions content(-array) - * @param array &$cell table-cell on which the extension operates - * @param array &$readonlys value of the extensions readonly-setting(-array) - * @return mixed return-value of the render function - */ - function extensionRender($type,$name,&$value,&$cell,$readonly) - { - if (!$this->haveExtension($type,'render')) - { - return False; - } - return $GLOBALS['egw_info']['etemplate']['extension'][$type]->render($cell,$name,$value,$readonly, - $GLOBALS['egw_info']['etemplate']['extension_data'][$name],$this); - } - - /** - * checks if $idx is set in array $arr - * - * for one level of subindes identical to isset($arr[$idx]) - * - * @static - * @param array $arr array to check - * @param string $idx may contain multiple subindex (eg.'x[y][z]') - * @return boolean true if set, else false - */ - function isset_array($arr,$idx) - { - $idxs = explode('[',str_replace(']','',$idx)); - $last_idx = array_pop($idxs); - $pos = &$arr; - foreach($idxs as $idx) - { - if (!is_array($pos)) - { - return False; - } - $pos = &$pos[$idx]; - } - // was return isset($pos[$last_idx]); - // array_key_exists also returns true for keys with value null, which fixes some problems with autorepeating rows - return is_array($pos) && array_key_exists($last_idx,$pos); - } - - /** - * sets $arr[$idx] = $val - * - * This works for non-trival indexes like 'a[b][c]' too: $arr['a']['b']['c'] = $val; - * - * @static - * @param array &$arr the array to search - * @param string $idx the index, may contain sub-indices like a[b], see example below - * @param mixed $val value to set - */ - function set_array(&$arr,$idx,$val) - { - if (!is_array($arr)) - { - die('set_array() $arr is no array
'.function_backtrace()); - } - $idxs = explode('[',str_replace(']','',$idx)); - $pos = &$arr; - foreach($idxs as $idx) - { - $pos = &$pos[$idx]; - } - $pos = $val; - } - - /** - * 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'; - * - * @static - * @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 false if $idx is not set and not $reference_into - */ - function &get_array(&$arr,$idx,$reference_into=False,$skip_empty=False) - { - if (!is_array($arr)) - { - die('set_array() $arr is no array
'.function_backtrace()); - } - if (is_object($idx)) return false; // given an error in php5.2 - - $idxs = explode('[',str_replace(']','',$idx)); - $pos = &$arr; - foreach($idxs as $idx) - { - if (!is_array($pos) && !$referenz_info) - { - return False; - } - if($skip_empty && !isset($pos[$idx])) return false; - $pos = &$pos[$idx]; - } - return $pos; - } - - /** - * unsets $arr[$idx] - * - * This works for non-trival indexes like 'a[b][c]' too - * unset_array($arr,'a[b]'); is equivalent to unset($arr['a']['b']); - * - * @static - * @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 - */ - function unset_array(&$arr,$idx) - { - if (!is_array($arr)) - { - die('set_array() $arr is no array
'.function_backtrace()); - } - $idxs = explode('[',str_replace(']','',$idx)); - $last_idx = array_pop($idxs); - $pos = &$arr; - foreach($idxs as $idx) - { - $pos = &$pos[$idx]; - } - unset($pos[$last_idx]); - } - - /** - * merges $old and $new, content of $new has precedence over $old - * - * THIS IS NOT THE SAME AS PHP4'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 - * - * @static - * @param array $old - * @param array $new - * @return array the merged array - */ - 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]) && isset($v[count($v)-1])) // or no associative array, eg. selecting multiple accounts - { - $old[$k] = $v; - } - else - { - $old[$k] = $this->complete_array_merge($old[$k],$v); - } - } - } - return $old; - } - - /** - * returns a reference to a widget in the widget's children tree spezified by a path - * - * The path get's generated by the widget_tree_walk() methode and consists of the keys of the children arrays. - * 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, ... - * @return array referenz to the widget spezified or null, if it's not found - */ - function &get_widget_by_path($path,$ancestor=0) - { - //echo "

boetemplate::get_widget_by_path('$path',$ancestor)

\n"; - $path_parts = explode('/',$path); - while($ancestor-- > 0) - { - if (array_pop($path_parts) === '') return null; - } - $path = implode('/',$path_parts); - if ($path == '/' || $path === '') return $this->children; - if (count($path_parts) == 2) return $this->children[$path_parts[1]]; - - return $this->widget_tree_walk('get_widget_by_path_helper',$path); - } - - /** - * returns a reference to a widget in the widget's children tree spezified it's name - * - * It returns the first match! - * - * @param string $name name of the widget - * @return array referenz to the widget spezified or null, if it's not found - */ - function &get_widget_by_name($name) - { - return $this->widget_tree_walk('get_widget_by_name_helper',$name); - } - - /** - * returns an array of references to widgets of the specified tipe - * @param type String - */ - function &get_widgets_by_type($type) { - $extra = array( - 'type' => $type, - 'widgets' => array() - ); - $this->widget_tree_walk('get_widgets_by_type_helper', $extra); - return $extra['widgets']; - } - - /** - * generated a file-name from an eTemplates, name, template(-set) and lang - * - * @param string/array $name name of template or array('name'=>$name,'template'=>$template,'lang'=>$lang) - * @param string $template template-set - * @param string $lang language to use - * @return string - */ - function cache_name($name='',$template='default',$lang='default') - { - if (empty($name)) - { - $name = $this->name; - $template = $this->template; - $lang = $this->lang; - } - elseif (is_array($name)) - { - $template = $name['template']; - $lang = $name['lang']; - $name = $name['name']; - } - if (empty($template)) - { - $template = 'default'; - } - $cname = $template . '/' . $name . (!empty($lang) && $lang != 'default' ? '.' . $lang : ''); - //echo "cache_name('$name','$template','$lang') = '$cname'"; - - return $cname; - } - - /** - * stores the etemplate in the cache in phpgw_info - */ - function store_in_cache() - { - //echo "

store_in_cache('$this->name','$this->template','$this->lang','$this->version')

\n"; - $GLOBALS['egw_info']['etemplate']['cache'][$this->cache_name()] = $this->as_array(1); - } - - /** - * deletes the etemplate in the cache in phpgw_info - */ - function delete_in_cache() - { - //echo "

delete_in_cache('$this->name','$this->template','$this->lang','$this->version')

\n"; - unset($GLOBALS['egw_info']['etemplate']['cache'][$this->cache_name()]); - } - - /* - * returns true if a given eTemplate is in the cache - * - * @param string/array $name name of template or array('name'=>$name,'template'=>$template,'lang'=>$lang) - * @param string $template template-set - * @param string $lang language to use - * @param int $group to use the template for - * @param string $version of the template - * @return boolean - */ - function in_cache($name,$template='default',$lang='default',$group=0,$version='') - { - $cname = $this->cache_name($name,$template,$lang); - if (is_array($name)) - { - $version = $name['version']; - $name = $name['name']; - } - if (!isset($GLOBALS['egw_info']['etemplate']['cache'][$cname]) || - !empty($version) && $GLOBALS['egw_info']['etemplate']['cache'][$cname]['version'] != $version) - { - //echo " NOT found in cache

\n"; - return False; - } - //echo " found in cache

\n"; - return $cname; - } - - /* - * reads the content of an eTemplate from the cache into the current object - * - * same as read but only via the cache - * - * @param string/array $name name of template or array('name'=>$name,'template'=>$template,'lang'=>$lang) - * @param string $template template-set - * @param string $lang language to use - * @param int $group to use the template for - * @param string $version of the template - * @return boolean true if the eTemplate was found in the cache - */ - function read_from_cache($name,$template='default',$lang='default',$group=0,$version='') - { - //if (is_array($name)) $version = $name['version']; echo "

read_from_cache(,,,version='$version'): "; - if ($cname = $this->in_cache($name,$template,$lang,$group)) - { - $this->init($GLOBALS['egw_info']['etemplate']['cache'][$cname]); - - return True; - } + //echo " NOT found in cache

\n"; return False; } + //echo " found in cache

\n"; + return $cname; + } - /** - * reads an eTemplate from the cache or database / filesystem (and updates the cache) - * - * reimplementation of soetemplate::read to use and/or update the cache - * - * @param string $name name of the eTemplate or array with the values for all keys - * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db - * @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 - */ - function read($name,$template='default',$lang='default',$group=0,$version='',$load_via='') + /** + * reads the content of an eTemplate from the cache into the current object + * + * same as read but only via the cache + * + * @param string/array $name name of template or array('name'=>$name,'template'=>$template,'lang'=>$lang) + * @param string $template template-set + * @param string $lang language to use + * @param int $group to use the template for + * @param string $version of the template + * @return boolean true if the eTemplate was found in the cache + */ + private function read_from_cache($name,$template='default',$lang='default',$group=0,$version='') + { + //if (is_array($name)) $version = $name['version']; echo "

read_from_cache(,,,version='$version'): "; + if ($cname = $this->in_cache($name,$template,$lang,$group)) { - if (is_array($name)) { - $pname = &$name['name']; - } - else - { - $pname = &$name; - } - if (empty($pname)) - { - return False; - } - $parent = is_array($load_via) ? $load_via['name'] : $load_via; + $this->init($GLOBALS['egw_info']['etemplate']['cache'][$cname]); - if (strstr($pname,'.') === False && !empty($parent)) - { - $pname = $parent . '.' . $pname; - } - if (!$this->read_from_cache($name,$template,$lang,$group,$version)) - { - if (!soetemplate::read($name,$template,$lang,$group,$version)) - { - if ($load_via && (is_string($load_via) || - !isset($load_via['tpls_in_file']) || $load_via['tpls_in_file'] > 1)) - { - soetemplate::read($load_via); - return $this->read_from_cache($name,$template,$lang,$group,$version); - } - return False; - } - $this->store_in_cache(); - } return True; } - - /** - * saves eTemplate-object to db and update the cache - * - * reimplementation of soetemplate::save to update the cache - * - * @param string $name name of the eTemplate or array with the values for all keys - * @param string $template template-set or '' for the default one - * @param string $lang language or '' for the default one - * @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 - * @return the number of affected rows, 1 should be ok, 0 somethings wrong - */ - function save($name='',$template='.',$lang='.',$group=0,$version='.') - { - if ($result = soetemplate::save($name,$template,$lang,$group,$version)) - { - $this->store_in_cache(); - } - return $result; - } - - /** - * Deletes the eTemplate from the db, object itself is unchanged - * - * reimplementation of soetemplate::delete to update the cache - * - * @return int number of affected rows, 1 should be ok, 0 somethings wrong - */ - function delete() - { - $this->delete_in_cache(); - - return soetemplate::delete(); - } - + return False; } - if (!function_exists('set_cell_attribute_helper')) + /** + * reads an eTemplate from the cache or database / filesystem (and updates the cache) + * + * reimplementation of soetemplate::read to use and/or update the cache + * + * @param string $name name of the eTemplate or array with the values for all keys + * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db + * @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 + */ + function read($name,$template='default',$lang='default',$group=0,$version='',$load_via='') { - function &set_cell_attribute_helper(&$widget,&$extra) - { - // extra = array(0=>n,1=>name,2=>attr,3=>value) - if ($widget['name'] == $extra[1]) - { - if (is_null($extra[3])) - { - $extra['__RETURN_NOW__'] = true; // wouldnt work otherwise, if attr is not yet set == null - return $widget[$extra[2]]; - } - $widget[$extra[2]] = $extra[3]; - ++$extra[0]; - } + if (is_array($name)) { + $pname = &$name['name']; } - - function &get_widget_by_name_helper(&$widget,$extra) + else { - if ($widget['name'] == $extra) return $widget; - } - - function &get_widget_by_path_helper(&$widget,$extra,$path) + $pname = &$name; + } + if (empty($pname)) { - //echo "

path_searched='$extra', widget-path($widget[type]:$widget[name])='$path'

\n"; - if ($path == $extra) return $widget; + return False; } + $parent = is_array($load_via) ? $load_via['name'] : $load_via; - function &get_widgets_by_type_helper(&$widget, &$extra) + if (strstr($pname,'.') === False && !empty($parent)) { - //echo '
get_widgets_by_type_helper(' . $widget['name'] . ',' . $extra['type'] . ')
'; - if($widget['type'] == $extra['type']) { - $extra['widgets'][] =& $widget; + $pname = $parent . '.' . $pname; + } + if (!$this->read_from_cache($name,$template,$lang,$group,$version)) + { + if (!soetemplate::read($name,$template,$lang,$group,$version)) + { + if ($load_via && (is_string($load_via) || + !isset($load_via['tpls_in_file']) || $load_via['tpls_in_file'] > 1)) + { + soetemplate::read($load_via); + return $this->read_from_cache($name,$template,$lang,$group,$version); + } + return False; } + $this->store_in_cache(); + } + return True; + } + + /** + * saves eTemplate-object to db and update the cache + * + * reimplementation of soetemplate::save to update the cache + * + * @param string $name name of the eTemplate or array with the values for all keys + * @param string $template template-set or '' for the default one + * @param string $lang language or '' for the default one + * @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 + * @return the number of affected rows, 1 should be ok, 0 somethings wrong + */ + function save($name='',$template='.',$lang='.',$group=0,$version='.') + { + if ($result = soetemplate::save($name,$template,$lang,$group,$version)) + { + $this->store_in_cache(); + } + return $result; + } + + /** + * Deletes the eTemplate from the db, object itself is unchanged + * + * reimplementation of soetemplate::delete to update the cache + * + * @return int number of affected rows, 1 should be ok, 0 somethings wrong + */ + function delete() + { + $this->delete_in_cache(); + + return soetemplate::delete(); + } + + /** + * initialise our static vars + */ + static function _init_static() + { + self::$garbage_collection_done =& $GLOBALS['egw_info']['etemplate']['garbage_collection_done']; + } +} +boetemplate::_init_static(); + +if (!function_exists('set_cell_attribute_helper')) +{ + function &set_cell_attribute_helper(&$widget,&$extra) + { + // extra = array(0=>n,1=>name,2=>attr,3=>value) + if ($widget['name'] == $extra[1]) + { + if (is_null($extra[3])) + { + $extra['__RETURN_NOW__'] = true; // wouldnt work otherwise, if attr is not yet set == null + return $widget[$extra[2]]; + } + $widget[$extra[2]] = $extra[3]; + ++$extra[0]; } } + + function &get_widget_by_name_helper(&$widget,$extra) + { + if ($widget['name'] == $extra) return $widget; + } + + function &get_widget_by_path_helper(&$widget,$extra,$path) + { + //echo "

path_searched='$extra', widget-path($widget[type]:$widget[name])='$path'

\n"; + if ($path == $extra) return $widget; + } + + function &get_widgets_by_type_helper(&$widget, &$extra) + { + //echo '
get_widgets_by_type_helper(' . $widget['name'] . ',' . $extra['type'] . ')
'; + if($widget['type'] == $extra['type']) { + $extra['widgets'][] =& $widget; + } + } +} diff --git a/etemplate/inc/class.customfields_widget.inc.php b/etemplate/inc/class.customfields_widget.inc.php index 2a8dc6d546..d63ed3b98e 100644 --- a/etemplate/inc/class.customfields_widget.inc.php +++ b/etemplate/inc/class.customfields_widget.inc.php @@ -363,7 +363,7 @@ } list($span,$class) = explode(',',$cell['span']); // msie (at least 5.5) shows nothing with div overflow=auto // we dont want to use up the full space for the table created, so we skip the line below - //$cell['size'] = '100%,100%,0,'.$class.','.(in_array($type,array('customfields-list','customfields-no-label'))?'0,0':',').($tmpl->html->user_agent != 'msie' ? ',auto' : ''); + //$cell['size'] = '100%,100%,0,'.$class.','.(in_array($type,array('customfields-list','customfields-no-label'))?'0,0':',').(html::$user_agent != 'msie' ? ',auto' : ''); return True; // extra Label is ok } diff --git a/etemplate/inc/class.link_widget.inc.php b/etemplate/inc/class.link_widget.inc.php index 39a438be24..b412d0cfc7 100644 --- a/etemplate/inc/class.link_widget.inc.php +++ b/etemplate/inc/class.link_widget.inc.php @@ -202,7 +202,7 @@ { foreach ($value as $link) { - $options .= " onMouseOver=\"self.status='".addslashes($tmpl->html->htmlspecialchars($help))."'; return true;\""; + $options .= " onMouseOver=\"self.status='".addslashes(html::htmlspecialchars($help))."'; return true;\""; $options .= " onMouseOut=\"self.status=''; return true;\""; if (($popup = egw_link::is_popup($link['app'],'view'))) { @@ -213,8 +213,8 @@ { $options = ' target="_blank"'; } - $str .= ($str !== '' ? ', ' : '') . $tmpl->html->a_href( - $tmpl->html->htmlspecialchars(egw_link::title($link['app'],$link['id'])), + $str .= ($str !== '' ? ', ' : '') . html::a_href( + html::htmlspecialchars(egw_link::title($link['app'],$link['id'])), '/index.php',egw_link::view($link['app'],$link['id'],$link),$options); } } @@ -335,7 +335,7 @@ $value[$row]['target'] = '_blank'; // we create a new window as the linked page is no popup } } - if ($link['app'] == egw_link::vfs_appname) + if ($link['app'] == egw_link::VFS_APPNAME) { $value[$row]['label'] = 'Delete'; $value[$row]['help'] = lang('Delete this file'); @@ -360,7 +360,7 @@ { if(in_array($GLOBALS['egw_info']['user']['preferences']['common']['link_list_format'], array('icons', 'icons_and_text') )) { // Hardcoded sizes to match the mimetype icons. Uses the navbar image and CSS to resize. - $value[$row]['mime_icon'] = $tmpl->html->image($value[$row]['app'], 'navbar', $value[$row]['app'], 'style="width: 16px; height: 16px;"'); + $value[$row]['mime_icon'] = html::image($value[$row]['app'], 'navbar', $value[$row]['app'], 'style="width: 16px; height: 16px;"'); } $value[$row]['label'] = 'Unlink'; $value[$row]['help'] = lang('Remove this link (not the entry itself)'); @@ -557,7 +557,7 @@ $value['file']['tmp_name'] .= '+'; } $link_id = egw_link::link($value['to_app'],$value['to_id'], - egw_link::vfs_appname,$value['file'],$value['remark']); + egw_link::VFS_APPNAME,$value['file'],$value['remark']); $value['remark'] = ''; if (isset($value['primary']) && !$value['anz_links'] ) diff --git a/etemplate/inc/class.soetemplate.inc.php b/etemplate/inc/class.soetemplate.inc.php index 4d19902c72..c3eec17c45 100644 --- a/etemplate/inc/class.soetemplate.inc.php +++ b/etemplate/inc/class.soetemplate.inc.php @@ -1,1350 +1,1341 @@ - * @version $Id$ - */ +/** + * eGroupWare EditableTemplates - Storage Objects + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @link http://www.egroupware.org + * @author Ralf Becker + * @version $Id$ + */ +/** + * Storage Objects: Everything to store and retrive and eTemplate. + * + * eTemplates are stored in the db in table 'phpgw_etemplate' and gets distributed + * through the file 'etemplates.inc.php' in the setup dir of each app. That file gets + * automatically imported in the db, whenever you show a eTemplate of the app. For + * performace reasons the timestamp of the file is stored in the db, so 'new' + * eTemplates need to have a newer file. The distribution-file is generated with the + * function dump, usually by pressing a button in the editor. + * writeLangFile writes an lang-file with all Labels, incorporating an existing one. + * Beside a name eTemplates use the following keys to find the most suitable template + * for an user (in order of precedence): + * 1) User-/Group-Id (not yet implemented) + * 2) preferd languages of the user (templates for all langs have $lang='') + * 3) selected template: verdilak, ... (the default is called '' in the db, not default) + * 4) a version-number of the form, eg: '0.9.13.001' (filled up with 0 same size) + * + * @package etemplate + * @subpackage api + * @author RalfBecker-AT-outdoor-training.de + * @license GPL + */ +class soetemplate +{ + var $debug; // =1 show some debug-messages, = 'app.name' show messages only for eTemplate 'app.name' + var $name; // name of the template, e.g. 'infolog.edit' + var $template; // '' = default (not 'default') + var $lang; // '' if general template else language short, e.g. 'de' + var $group; // 0 = not specific else groupId or if < 0 userId + var $version; // like 0.9.13.001 + var $style; // embeded CSS style-sheet + var $children; // array with children + var $data; // depricated: first grid of the children + var $size; // depricated: witdh,height,border of first grid /** - * Storage Objects: Everything to store and retrive and eTemplate. + * private reference to the global db-object * - * eTemplates are stored in the db in table 'phpgw_etemplate' and gets distributed - * through the file 'etemplates.inc.php' in the setup dir of each app. That file gets - * automatically imported in the db, whenever you show a eTemplate of the app. For - * performace reasons the timestamp of the file is stored in the db, so 'new' - * eTemplates need to have a newer file. The distribution-file is generated with the - * function dump, usually by pressing a button in the editor. - * writeLangFile writes an lang-file with all Labels, incorporating an existing one. - * Beside a name eTemplates use the following keys to find the most suitable template - * for an user (in order of precedence): - * 1) User-/Group-Id (not yet implemented) - * 2) preferd languages of the user (templates for all langs have $lang='') - * 3) selected template: verdilak, ... (the default is called '' in the db, not default) - * 4) a version-number of the form, eg: '0.9.13.001' (filled up with 0 same size) - * - * @package etemplate - * @subpackage api - * @author RalfBecker-AT-outdoor-training.de - * @license GPL + * @var egw_db */ - class soetemplate + private $db; + /** + * name of table + */ + const TABLE = 'egw_etemplate'; + static $db_key_cols = array( + 'et_name' => 'name', + 'et_template' => 'template', + 'et_lang' => 'lang', + 'et_group' => 'group', + 'et_version' => 'version' + ); + static $db_data_cols = array( + 'et_data' => 'data', + 'et_size' => 'size', + 'et_style' => 'style', + 'et_modified' => 'modified' + ); + static $db_cols; + /** + * widgets that contain other widgets, eg. for tree_walk method + * widget-type is the key, the value specifys how the children are stored. + * + * @var array + */ + static $widgets_with_children = array( + 'template' => 'template', + 'grid' => 'grid', + 'box' => 'box', + 'vbox' => 'box', + 'hbox' => 'box', + 'groupbox' => 'box', + 'deck' => 'box', + ); + + /** + * constructor of the class + * + * calls init or read depending on a name for the template is given + * + * @param string $name name of the eTemplate or array with the values for all keys + * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db + * @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 int $rows initial size of the template, default 1, only used if no name given !!! + * @param int $cols initial size of the template, default 1, only used if no name given !!! + * @return soetemplate + */ + function soetemplate($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1) { - var $debug; // =1 show some debug-messages, = 'app.name' show messages only for eTemplate 'app.name' - var $name; // name of the template, e.g. 'infolog.edit' - var $template; // '' = default (not 'default') - var $lang; // '' if general template else language short, e.g. 'de' - var $group; // 0 = not specific else groupId or if < 0 userId - var $version; // like 0.9.13.001 - var $style; // embeded CSS style-sheet - var $children; // array with children - var $data; // depricated: first grid of the children - var $size; // depricated: witdh,height,border of first grid - /** - * private instance of the db-object - * - * @var egw_db - */ - var $db; - /** - * name of table - * - * @var string - */ - var $table_name = 'egw_etemplate'; - var $db_key_cols = array( - 'et_name' => 'name', - 'et_template' => 'template', - 'et_lang' => 'lang', - 'et_group' => 'group', - 'et_version' => 'version' - ); - var $db_data_cols = array( - 'et_data' => 'data', - 'et_size' => 'size', - 'et_style' => 'style', - 'et_modified' => 'modified' - ); - var $db_cols; - /** - * widgets that contain other widgets, eg. for tree_walk method - * widget-type is the key, the value specifys how the children are stored. - * - * @var array - */ - var $widgets_with_children = array( - 'template' => 'template', - 'grid' => 'grid', - 'box' => 'box', - 'vbox' => 'box', - 'hbox' => 'box', - 'groupbox' => 'box', - 'deck' => 'box', - ); + if (is_object($GLOBALS['egw']->db)) + { + $this->db = $GLOBALS['egw']->db; + } + else + { + $GLOBALS['egw_info']['server']['eTemplate-source'] = 'files'; + } + if (empty($name)) + { + $this->init($name,$template,$lang,$group,$version,$rows,$cols); + } + else + { + $this->read($name,$template,$lang,$group,$version); + } + } + + /** + * 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; + } + + /** + * 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; - /** - * constructor of the class - * - * calls init or read depending on a name for the template is given - * - * @param string $name name of the eTemplate or array with the values for all keys - * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db - * @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 int $rows initial size of the template, default 1, only used if no name given !!! - * @param int $cols initial size of the template, default 1, only used if no name given !!! - * @return soetemplate - */ - function soetemplate($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1) + $num = 1+ord($chrs{0})-$min; + if (strlen($chrs) > 1) { - if (is_object($GLOBALS['egw']->db)) - { - $this->db = clone($GLOBALS['egw']->db); - $this->db->set_app('etemplate'); - } - else - { - $GLOBALS['egw_info']['server']['eTemplate-source'] = 'files'; - } - $this->db_cols = $this->db_key_cols + $this->db_data_cols; + $num *= 1 + $max - $min; + $num += 1+ord($chrs{1})-$min; + } + return $num; + } - if (empty($name)) + /** + * constructor for a new / empty cell/widget + * + * nothing fancy so far + * + * @param string $type type of the widget + * @param string $name name of widget + * @param array $attributes=null array with further attributes + * @return array the cell + */ + static function empty_cell($type='label',$name='',$attributes=null) + { + $cell = array( + 'type' => $type, + 'name' => $name, + ); + if ($attributes && is_array($attributes)) + { + return array_merge($attributes,$cell); + } + return $cell; + } + + /** + * constructs a new cell in a give row or the last row, not existing rows will be created + * + * @deprecated as it uses this->data + * @param int $row row-number starting with 1 (!) + * @param string $type type of the cell + * @param string $label label for the cell + * @param string $name name of the cell (index in the content-array) + * @param array $attributes other attributes for the cell + * @return array a reference to the new cell, use $new_cell = &$tpl->new_cell(); (!) + */ + function &new_cell($row=False,$type='label',$label='',$name='',$attributes=False) + { + $row = $row >= 0 ? intval($row) : 0; + if ($row && !isset($this->data[$row]) || !isset($this->data[1])) // new row ? + { + if (!$row) $row = 1; + + $this->data[$row] = array(); + } + if (!$row) // use last row + { + $row = count($this->data); + while (!isset($this->data[$row])) { - $this->init($name,$template,$lang,$group,$version,$rows,$cols); - } - else - { - $this->read($name,$template,$lang,$group,$version); + --$row; } } - - /** - * generates column-names from index: 'A', 'B', ..., 'AA', 'AB', ..., 'ZZ' (not more!) - * - * @static - * @param int $num numerical index to generate name from 1 => 'A' - * @return string the name - */ - function num2chrs($num) + $row = &$this->data[$row]; + $col = $this->num2chrs(count($row)); + $cell = &$row[$col]; + $cell = $this->empty_cell($type,$name); + if ($label !== '') { - $min = ord('A'); - $max = ord('Z') - $min + 1; - if ($num >= $max) - { - $chrs = chr(($num / $max) + $min - 1); - } - $chrs .= chr(($num % $max) + $min); - - return $chrs; + $attributes['label'] = $label; } - - /** - * generates column-names from index: 'A', 'B', ..., 'AA', 'AB', ..., 'ZZ' (not more!) - * - * @static - * @param string $chrs column letter to generate name from 'A' => 1 - * @return int the index - */ - function chrs2num($chrs) + if (is_array($attributes)) { - $min = ord('A'); - $max = ord('Z') - $min + 1; - - $num = 1+ord($chrs{0})-$min; - if (strlen($chrs) > 1) + foreach($attributes as $name => $value) { - $num *= 1 + $max - $min; - $num += 1+ord($chrs{1})-$min; + $cell[$name] = $value; } - return $num; } + return $cell; + } - /** - * constructor for a new / empty cell/widget - * - * nothing fancy so far - * - * @static - * @param string $type type of the widget - * @param string $name name of widget - * @param array $attributes=null array with further attributes - * @return array the cell - */ - function empty_cell($type='label',$name='',$attributes=null) + /** + * adds $cell to it's parent at the parent-type spezific location for childs + * + * @param array &$parent referenc to the parent + * @param array &$cell cell to add (need to be unset after the call to add_child, as it's a referenc !) + */ + static function add_child(&$parent,&$cell) + { + if (is_object($parent)) // parent is the template itself { - $cell = array( - 'type' => $type, - 'name' => $name, - ); - if ($attributes && is_array($attributes)) - { - return array_merge($attributes,$cell); - } - return $cell; + $parent->children[] = &$cell; + return; } - - /** - * constructs a new cell in a give row or the last row, not existing rows will be created - * - * @deprecated as it uses this->data - * @param int $row row-number starting with 1 (!) - * @param string $type type of the cell - * @param string $label label for the cell - * @param string $name name of the cell (index in the content-array) - * @param array $attributes other attributes for the cell - * @return array a reference to the new cell, use $new_cell = &$tpl->new_cell(); (!) - */ - function &new_cell($row=False,$type='label',$label='',$name='',$attributes=False) + switch($parent['type']) { - $row = $row >= 0 ? intval($row) : 0; - if ($row && !isset($this->data[$row]) || !isset($this->data[1])) // new row ? - { - if (!$row) $row = 1; + case 'vbox': + case 'hbox': + case 'groupbox': + case 'box': + case 'deck': + list($n,$options) = explode(',',$parent['size'],2); + $parent[++$n] = &$cell; + $parent['size'] = $n . ($options ? ','.$options : ''); + break; - $this->data[$row] = array(); - } - if (!$row) // use last row - { - $row = count($this->data); - while (!isset($this->data[$row])) + case 'grid': + $data = &$parent['data']; + $cols = &$parent['cols']; + $rows = &$parent['rows']; + $row = &$data[$rows]; + $col = count($row); + if (!$rows || !is_array($cell)) // create a new row { - --$row; + $row = &$data[++$rows]; + $row = array(); } - } - $row = &$this->data[$row]; - $col = $this->num2chrs(count($row)); - $cell = &$row[$col]; - $cell = $this->empty_cell($type,$name); - if ($label !== '') - { - $attributes['label'] = $label; - } - if (is_array($attributes)) - { - foreach($attributes as $name => $value) + if (is_array($cell)) // real cell to add { - $cell[$name] = $value; - } - } - return $cell; - } - - /** - * adds $cell to it's parent at the parent-type spezific location for childs - * - * @static - * @param array &$parent referenc to the parent - * @param array &$cell cell to add (need to be unset after the call to add_child, as it's a referenc !) - */ - function add_child(&$parent,&$cell) - { - if (is_object($parent)) // parent is the template itself - { - $parent->children[] = &$cell; - return; - } - switch($parent['type']) - { - case 'vbox': - case 'hbox': - case 'groupbox': - case 'box': - case 'deck': - list($n,$options) = explode(',',$parent['size'],2); - $parent[++$n] = &$cell; - $parent['size'] = $n . ($options ? ','.$options : ''); - break; - - case 'grid': - $data = &$parent['data']; - $cols = &$parent['cols']; - $rows = &$parent['rows']; - $row = &$data[$rows]; - $col = count($row); - if (!$rows || !is_array($cell)) // create a new row + $row[soetemplate::num2chrs($col++)] = &$cell; + list($spanned) = explode(',',$cell['span']); + $spanned = $spanned == 'all' ? 1 + $cols - $col : $spanned; + while (--$spanned > 0) { - $row = &$data[++$rows]; - $row = array(); + $row[soetemplate::num2chrs($col++)] = soetemplate::empty_cell(); } - if (is_array($cell)) // real cell to add - { - $row[soetemplate::num2chrs($col++)] = &$cell; - list($spanned) = explode(',',$cell['span']); - $spanned = $spanned == 'all' ? 1 + $cols - $col : $spanned; - while (--$spanned > 0) - { - $row[soetemplate::num2chrs($col++)] = soetemplate::empty_cell(); - } - if ($col > $cols) $cols = $col; - } - break; - } - } - - /** - * initialises internal vars rows & cols from the data of a grid - * - * @static - * @param array &$grid to calc rows and cols - */ - function set_grid_rows_cols(&$grid) - { - $grid['rows'] = count($grid['data']) - 1; - $grid['cols'] = 0; - for($r = 1; $r <= $grid['rows']; ++$r) - { - $cols = count($grid['data'][$r]); - if ($grid['cols'] < $cols) - { - $grid['cols'] = $cols; + if ($col > $cols) $cols = $col; } + break; + } + } + + /** + * initialises internal vars rows & cols from the data of a grid + * + * @param array &$grid to calc rows and cols + */ + static function set_grid_rows_cols(&$grid) + { + $grid['rows'] = count($grid['data']) - 1; + $grid['cols'] = 0; + for($r = 1; $r <= $grid['rows']; ++$r) + { + $cols = count($grid['data'][$r]); + if ($grid['cols'] < $cols) + { + $grid['cols'] = $cols; } } + } + + /** + * initialises internal vars rows & cols from the data of the first (!) grid + * + * @deprecated as it uses this->data + */ + function set_rows_cols() + { + if (is_null($this->data)) // tmpl contains no grid + { + $this->rows = $this->cols = 0; + } + else + { + $grid['data'] = &$this->data; + $grid['rows'] = &$this->rows; + $grid['cols'] = &$this->cols; + $this->set_grid_rows_cols($grid); + unset($grid); + } + } + + /** + * initialises all internal data-structures of the eTemplate and sets the keys + * + * @param string $name name of the eTemplate or array with the values for all keys and possibly data + * @param string $template template-set or '' for the default one + * @param string $lang language or '' for the default one + * @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 int $rows initial size of the template, default 1 + * @param int $cols initial size of the template, default 1 + */ + function init($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1) + { + // unset children and data as they are referenzes to each other + unset($this->children); unset($this->data); - /** - * initialises internal vars rows & cols from the data of the first (!) grid - * - * @deprecated as it uses this->data - */ - function set_rows_cols() + foreach(self::$db_cols as $db_col => $col) { - if (is_null($this->data)) // tmpl contains no grid - { - $this->rows = $this->cols = 0; - } - else - { - $grid['data'] = &$this->data; - $grid['rows'] = &$this->rows; - $grid['cols'] = &$this->cols; - $this->set_grid_rows_cols($grid); - unset($grid); - } + if ($col != 'data') $this->$col = is_array($name) ? (string) $name[$col] : $$col; } - - /** - * initialises all internal data-structures of the eTemplate and sets the keys - * - * @param string $name name of the eTemplate or array with the values for all keys and possibly data - * @param string $template template-set or '' for the default one - * @param string $lang language or '' for the default one - * @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 int $rows initial size of the template, default 1 - * @param int $cols initial size of the template, default 1 - */ - function init($name='',$template='',$lang='',$group=0,$version='',$rows=1,$cols=1) + if ($this->template == 'default') { - // unset children and data as they are referenzes to each other - unset($this->children); unset($this->data); - - foreach($this->db_cols as $db_col => $col) - { - if ($col != 'data') $this->$col = is_array($name) ? (string) $name[$col] : $$col; - } - if ($this->template == 'default') - { - $this->template = ''; - } - if ($this->lang == 'default') - { - $this->lang = ''; - } - $this->tpls_in_file = is_array($name) ? $name['tpls_in_file'] : 0; - - if (is_array($name) && $name['onclick_handler']) $this->onclick_handler = $name['onclick_handler']; - - if (is_array($name) && isset($name['data'])) - { - // data/children are in $name['data'] - $this->children = is_array($name['data']) ? $name['data'] : unserialize($name['data']); - - $this->fix_old_template_format(); - } - else - { - $this->size = $this->style = ''; - $this->data = array(0 => array()); - $this->rows = $rows < 0 ? 1 : $rows; - $this->cols = $cols < 0 ? 1 : $cols; - for ($row = 1; $row <= $rows; ++$row) - { - for ($col = 0; $col < $cols; ++$col) - { - $this->data[$row][$this->num2chrs($col)] = $this->empty_cell(); - } - } - $this->children[0]['type'] = 'grid'; - $this->children[0]['data'] = &$this->data; - $this->children[0]['rows'] = &$this->rows; - $this->children[0]['cols'] = &$this->cols; - $this->children[0]['size'] = &$this->size; - } + $this->template = ''; } - - /** - * reads an eTemplate from the database - * - * @param string $name name of the eTemplate or array with the values for all keys - * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db - * @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 - * @return boolean True if a fitting template is found, else False - */ - function read($name,$template='default',$lang='default',$group=0,$version='') + if ($this->lang == 'default') { - $this->init($name,$template,$lang,$group,$version); - if ($this->debug == 1 || $this->debug == $this->name) - { - echo "

soetemplate::read('$this->name','$this->template','$this->lang',$this->group,'$this->version')

\n"; - } - if (($GLOBALS['egw_info']['server']['eTemplate-source'] == 'files' || - $GLOBALS['egw_info']['server']['eTemplate-source'] == 'xslt') && $this->readfile()) - { - return True; - } - if ($this->name) - { - $this->test_import($this->name); // import updates in setup-dir - } - $pref_lang = $GLOBALS['egw_info']['user']['preferences']['common']['lang']; - $pref_templ = $GLOBALS['egw_info']['server']['template_set']; - - $where = array( - 'et_name' => $this->name, - ); - if (is_array($name)) - { - $template = $name['template']; - } - if ($template == 'default') - { - $where[] = '(et_template='.$this->db->quote($pref_templ)." OR et_template='')"; - } - else - { - $where['et_template'] = $this->template; - } - if (is_array($name)) - { - $lang = $name['lang']; - } - if ($lang == 'default' || $name['lang'] == 'default') - { - $where[] = '(et_lang='.$this->db->quote($pref_lang)." OR et_lang='')"; - } - else - { - $where['et_lang'] = $this->lang; - } - if ($this->version != '') - { - $where['et_version'] = $this->version; - } - $this->db->select($this->table_name,'*',$where,__LINE__,__FILE__,false,'ORDER BY et_lang DESC,et_template DESC,et_version DESC'); - if (!$this->db->next_record()) - { - $version = $this->version; - return $this->readfile() && (empty($version) || $version == $this->version); - } - $this->db2obj(); - - if ($this->debug == $this->name) - { - $this->echo_tmpl(); - } - return True; + $this->lang = ''; } + $this->tpls_in_file = is_array($name) ? $name['tpls_in_file'] : 0; + + if (is_array($name) && $name['onclick_handler']) $this->onclick_handler = $name['onclick_handler']; - /** - * Reads an eTemplate from the filesystem, the keys are already set by init in read - * - * @return boolean True if a template was found, else False - */ - function readfile() + if (is_array($name) && isset($name['data'])) { - list($app,$name) = explode('.',$this->name,2); - $template = $this->template == '' ? 'default' : $this->template; + // data/children are in $name['data'] + $this->children = is_array($name['data']) ? $name['data'] : unserialize($name['data']); - if ($this->lang) - { - $lang = '.' . $this->lang; - } - $first_try = $ext = $GLOBALS['egw_info']['server']['eTemplate-source'] == 'xslt' ? '.xsl' : '.xet'; - - while ((!$lang || !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/$template/$name$lang$ext") && - !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/default/$name$lang$ext")) && - !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/$template/$name$ext") && - !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/default/$name$ext")) - { - if ($ext == $first_try) - { - $ext = $ext == '.xet' ? '.xsl' : '.xet'; - - if ($this->debug == 1 || $this->name != '' && $this->debug == $this->name) - { - echo "

tried '$file' now trying it with extension '$ext' !!!

\n"; - } - } - else - { - break; - } - } - if ($this->name == '' || $app == '' || $name == '' || !@file_exists($file) || !($f = @fopen($file,'r'))) - { - if ($this->debug == 1 || $this->name != '' && $this->debug == $this->name) - { - echo "

Can't open template '$this->name' / '$file' !!!

\n"; - } - return False; - } - $xml = fread ($f, filesize ($file)); - fclose($f); - - if ($ext == '.xsl') - { - $cell = $this->empty_cell(); - $cell['type'] = 'xslt'; - $cell['size'] = $this->name; - //$cell['xslt'] = &$xml; xslttemplate class cant use it direct at the moment - $cell['name'] = ''; - $this->data = array(0 => array(),1 => array('A' => &$cell)); - $this->rows = $this->cols = 1; - } - else - { - if (!is_object($this->xul_io)) - { - $this->xul_io =& CreateObject('etemplate.xul_io'); - } - $loaded = $this->xul_io->import($this,$xml); - - if (!is_array($loaded)) - { - return False; - } - $this->name = $app . '.' . $name; // if template was copied or app was renamed - - $this->tpls_in_file = count($loaded); - } - return True; - } - - /** - * Convert the usual *,? wildcards to the sql ones and quote %,_ - * - * @param string $pattern - * @return string - */ - function sql_wildcards($pattern) - { - return str_replace(array('%','_','*','?'),array('\\%','\\_','%','_'),$pattern); - } - - /** - * Lists the eTemplates matching the given criteria, sql wildcards % and _ possible - * - * @param string $name name of the eTemplate or array with the values for all keys - * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db - * @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 - * @return array of arrays with the template-params - */ - function search($name,$template='default',$lang='default',$group=0,$version='') - { - if ($this->name) - { - $this->test_import($this->name); // import updates in setup-dir - } - if (is_array($name)) - { - $template = (string) $name['template']; - $lang = (string) $name['lang']; - $group = (int) $name['group']; - $version = (string) $name['version']; - $name = (string) $name['name']; - } - $where[] = 'et_name LIKE '.$this->db->quote($this->sql_wildcards($name).'%'); - if ($template != '' && $template != 'default') - { - $where[] = 'et_template LIKE '.$this->db->quote($this->sql_wildcards($template).'%'); - } - if ($lang != '' && $lang != 'default') - { - $where[] = 'et_lang LIKE '.$this->db->quote($this->sql_wildcards($lang).'%'); - } - if ($this->version != '') - { - $where[] = 'et_version LIKE '.$this->db->quote($this->sql_wildcards($version).'%'); - } - $this->db->select($this->table_name,'et_name,et_template,et_lang,et_group,et_version',$where,__LINE__,__FILE__,false,'ORDER BY et_name DESC,et_lang DESC,et_template DESC,et_version DESC'); - $result = array(); - while (($row = $this->db->row(true,'et_'))) - { - if ($row['lang'] != '##') // exclude or import-time-stamps - { - $result[] = $row; - } - } - if ($this->debug) - { - _debug_array($result); - } - return $result; - } - - /** - * copies all cols into the obj and unserializes the data-array - */ - function db2obj() - { - // unset children and data as they are referenzes to each other - unset($this->children); unset($this->data); - - foreach ($this->db_cols as $db_col => $name) - { - if ($name != 'data') - { - $this->$name = $this->db->f($db_col); - } - else - { - $this->children = unserialize($this->db->f($db_col)); - } - } $this->fix_old_template_format(); } - - /** - * test if we have an old/original template-format and fixes it to the new format - */ - function fix_old_template_format() + else { - if (!is_array($this->children)) $this->children = array(); - - if (!isset($this->children[0]['type'])) + $this->size = $this->style = ''; + $this->data = array(0 => array()); + $this->rows = $rows < 0 ? 1 : $rows; + $this->cols = $cols < 0 ? 1 : $cols; + for ($row = 1; $row <= $rows; ++$row) { - // old templates are treated as having one children of type grid (the original template) - $this->data = &$this->children; - unset($this->children); - - $this->children[0]['type'] = 'grid'; - $this->children[0]['data'] = &$this->data; - $this->children[0]['rows'] = &$this->rows; - $this->children[0]['cols'] = &$this->cols; - $this->children[0]['size'] = &$this->size; - - // that code fixes a bug in very old templates, not sure if it's still needed - if ($this->name[0] != '.' && is_array($this->data)) + for ($col = 0; $col < $cols; ++$col) { - reset($this->data); each($this->data); - while (list($row,$cols) = each($this->data)) - { - while (list($col,$cell) = each($cols)) - { - if (is_array($cell['type'])) - { - $this->data[$row][$col]['type'] = $cell['type'][0]; - //echo "corrected in $this->name cell $col$row attribute type
\n"; - } - if (is_array($cell['align'])) - { - $this->data[$row][$col]['align'] = $cell['align'][0]; - //echo "corrected in $this->name cell $col$row attribute align
\n"; - } - } - } + $this->data[$row][$this->num2chrs($col)] = $this->empty_cell(); + } + } + $this->children[0]['type'] = 'grid'; + $this->children[0]['data'] = &$this->data; + $this->children[0]['rows'] = &$this->rows; + $this->children[0]['cols'] = &$this->cols; + $this->children[0]['size'] = &$this->size; + } + } + + /** + * reads an eTemplate from the database + * + * @param string $name name of the eTemplate or array with the values for all keys + * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db + * @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 + * @return boolean True if a fitting template is found, else False + */ + function read($name,$template='default',$lang='default',$group=0,$version='') + { + $this->init($name,$template,$lang,$group,$version); + if ($this->debug == 1 || $this->debug == $this->name) + { + echo "

soetemplate::read('$this->name','$this->template','$this->lang',$this->group,'$this->version')

\n"; + } + if (($GLOBALS['egw_info']['server']['eTemplate-source'] == 'files' || + $GLOBALS['egw_info']['server']['eTemplate-source'] == 'xslt') && $this->readfile()) + { + return True; + } + if ($this->name) + { + $this->test_import($this->name); // import updates in setup-dir + } + $pref_lang = $GLOBALS['egw_info']['user']['preferences']['common']['lang']; + $pref_templ = $GLOBALS['egw_info']['server']['template_set']; + + $where = array( + 'et_name' => $this->name, + ); + if (is_array($name)) + { + $template = $name['template']; + } + if ($template == 'default') + { + $where[] = '(et_template='.$this->db->quote($pref_templ)." OR et_template='')"; + } + else + { + $where['et_template'] = $this->template; + } + if (is_array($name)) + { + $lang = $name['lang']; + } + if ($lang == 'default' || $name['lang'] == 'default') + { + $where[] = '(et_lang='.$this->db->quote($pref_lang)." OR et_lang='')"; + } + else + { + $where['et_lang'] = $this->lang; + } + if ($this->version != '') + { + $where['et_version'] = $this->version; + } + if (!($row = $this->db->select(self::TABLE,'*',$where,__LINE__,__FILE__,false,'ORDER BY et_lang DESC,et_template DESC,et_version DESC','etemplate')->fetch())) + { + $version = $this->version; + return $this->readfile() && (empty($version) || $version == $this->version); + } + $this->db2obj($row); + + if ($this->debug == $this->name) + { + $this->echo_tmpl(); + } + return True; + } + + /** + * Reads an eTemplate from the filesystem, the keys are already set by init in read + * + * @return boolean True if a template was found, else False + */ + function readfile() + { + list($app,$name) = explode('.',$this->name,2); + $template = $this->template == '' ? 'default' : $this->template; + + if ($this->lang) + { + $lang = '.' . $this->lang; + } + $first_try = $ext = $GLOBALS['egw_info']['server']['eTemplate-source'] == 'xslt' ? '.xsl' : '.xet'; + + while ((!$lang || !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/$template/$name$lang$ext") && + !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/default/$name$lang$ext")) && + !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/$template/$name$ext") && + !@file_exists($file = EGW_SERVER_ROOT . "/$app/templates/default/$name$ext")) + { + if ($ext == $first_try) + { + $ext = $ext == '.xet' ? '.xsl' : '.xet'; + + if ($this->debug == 1 || $this->name != '' && $this->debug == $this->name) + { + echo "

tried '$file' now trying it with extension '$ext' !!!

\n"; } } else { - unset($this->data); - // for the moment we make $this->data as a referenz to the first grid - foreach($this->children as $key => $child) - { - if ($child['type'] == 'grid') - { - $this->data = &$this->children[$key]['data']; - $this->rows = &$this->children[$key]['rows']; - $this->cols = &$this->children[$key]['cols']; - if (!isset($this->children[$key]['size']) && !empty($this->size)) - { - $this->children[$key]['size'] = &$this->size; - } - else - { - $this->size = &$this->children[$key]['size']; - } - break; - } - } + break; } - $this->set_rows_cols(); } - - /** - * all empty values and objects in the array got unset (to save space in the db ) - * - * The never empty type field ensures a cell does not disapear completely. - * Calls it self recursivly for arrays / the rows - * - * @param array $arr the array to compress - * @param boolean $remove_all_objs if true unset all objs, on false use as_array to save only the data of objs - * @return array - */ - function compress_array($arr,$remove_objs=false) + if ($this->name == '' || $app == '' || $name == '' || !@file_exists($file) || !($f = @fopen($file,'r'))) { - if (!is_array($arr)) + if ($this->debug == 1 || $this->name != '' && $this->debug == $this->name) { - return $arr; + echo "

Can't open template '$this->name' / '$file' !!!

\n"; } - foreach($arr as $key => $val) - { - if ($remove_objs && $key === 'obj') // it can be an array too - { - unset($arr[$key]); - } - elseif (is_array($val)) - { - $arr[$key] = $this->compress_array($val,$remove_objs); - } - elseif (!$remove_objs && $key == 'obj' && is_object($val) && method_exists($val,'as_array') && - // this test prevents an infinit recursion of templates calling itself, atm. etemplate.editor.new - $GLOBALS['egw_info']['etemplate']['as_array'][$this->name]++ < 2) - { - $arr['obj'] = $val->as_array(2); - } - elseif ($val == '' || is_object($val)) - { - unset($arr[$key]); - } - } - return $arr; + return False; } + $xml = fread ($f, filesize ($file)); + fclose($f); - /** - * returns obj-data/-vars as array - * - * the returned array ($data_too > 0) can be used with init to recreate the template - * - * @param int $data_too -1 = only keys, 0 = no data array, 1 = data array too, 2 = serialize data array, - * 3 = only data values and data serialized - * @param boolean $db_keys use db-column-names or internal names, default false=internal names - * @return array with template-data - */ - function as_array($data_too=0,$db_keys=false) + if ($ext == '.xsl') { - //echo "

soetemplate::as_array($data_too,$db_keys) name='$this->name', ver='$this->version'

\n"; - $arr = array(); - switch($data_too) - { - case -1: - $cols = $this->db_key_cols; - break; - case 3: - $cols = $this->db_data_cols; - break; - default: - $cols = $this->db_cols; - } - foreach($cols as $db_col => $col) - { - if ($col == 'data') - { - if ($data_too > 0) - { - $arr[$db_keys ? $db_col : $col] = $data_too < 2 ? $this->children : - serialize($this->compress_array($this->children,$db_keys)); - } - } - else - { - $arr[$db_keys ? $db_col : $col] = $this->$col; - } - } - if ($data_too != -1 && $this->tpls_in_file && !$db_keys) - { - $arr['tpls_in_file'] = $this->tpls_in_file; - } - if ($data_too != -1 && $this->onclick_handler && !$db_keys) - { - $arr['onclick_handler'] = $this->onclick_handler; - } - return $arr; + $cell = $this->empty_cell(); + $cell['type'] = 'xslt'; + $cell['size'] = $this->name; + //$cell['xslt'] = &$xml; xslttemplate class cant use it direct at the moment + $cell['name'] = ''; + $this->data = array(0 => array(),1 => array('A' => &$cell)); + $this->rows = $this->cols = 1; } - - /** - * saves eTemplate-object to db, can be used as saveAs by giving keys as params - * - * @param string $name name of the eTemplate or array with the values for all keys - * @param string $template template-set - * @param string $lang language or '' - * @param int $group id of the (primary) group, not used at the moment !!! - * @param string $version version of the eTemplate - * @return int number of affected rows, 1 should be ok, 0 somethings wrong - */ - function save($name='',$template='.',$lang='.',$group='',$version='.') + else { - if (is_array($name)) + if (!is_object($this->xul_io)) { - $template = $name['template']; - $lang = $name['lang']; - $group = $name['group']; - $version = $name['version']; - $name = $name['name']; + $this->xul_io =& CreateObject('etemplate.xul_io'); } - if ($name != '') - { - $this->name = $name; - } - if ($lang != '.') - { - $this->lang = $lang; - } - if ($template != '.') - { - $this->template = $template; - } - if ($group != '') - { - $this->group = $group; - } - if ($version != '.') - { - $this->version = $version; - } - if ($this->name == '') // name need to be set !!! + $loaded = $this->xul_io->import($this,$xml); + + if (!is_array($loaded)) { return False; } - if ($this->debug > 0 || $this->debug == $this->name) + $this->name = $app . '.' . $name; // if template was copied or app was renamed + + $this->tpls_in_file = count($loaded); + } + return True; + } + + /** + * Convert the usual *,? wildcards to the sql ones and quote %,_ + * + * @param string $pattern + * @return string + */ + static function sql_wildcards($pattern) + { + return str_replace(array('%','_','*','?'),array('\\%','\\_','%','_'),$pattern); + } + + /** + * Lists the eTemplates matching the given criteria, sql wildcards % and _ possible + * + * @param string $name name of the eTemplate or array with the values for all keys + * @param string $template template-set, '' loads the prefered template of the user, 'default' loads the default one '' in the db + * @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 + * @return array of arrays with the template-params + */ + function search($name,$template='default',$lang='default',$group=0,$version='') + { + if ($this->name) + { + $this->test_import($this->name); // import updates in setup-dir + } + if (is_array($name)) + { + $template = (string) $name['template']; + $lang = (string) $name['lang']; + $group = (int) $name['group']; + $version = (string) $name['version']; + $name = (string) $name['name']; + } + $where[] = 'et_name LIKE '.$this->db->quote($this->sql_wildcards($name).'%'); + if ($template != '' && $template != 'default') + { + $where[] = 'et_template LIKE '.$this->db->quote($this->sql_wildcards($template).'%'); + } + if ($lang != '' && $lang != 'default') + { + $where[] = 'et_lang LIKE '.$this->db->quote($this->sql_wildcards($lang).'%'); + } + if ($this->version != '') + { + $where[] = 'et_version LIKE '.$this->db->quote($this->sql_wildcards($version).'%'); + } + $result = array(); + foreach($this->db->select(self::TABLE,'et_name,et_template,et_lang,et_group,et_version',$where,__LINE__,__FILE__,false,'ORDER BY et_name DESC,et_lang DESC,et_template DESC,et_version DESC','etemplate') as $row) + { + if ($row['et_lang'] != '##') // exclude or import-time-stamps { - echo "

soetemplate::save('$this->name','$this->template','$this->lang',$this->group,'$this->version')

\n"; + $result[] = egw_db::strip_array_keys($row,'et_'); } - if ($this->name[0] != '.' && is_array($this->data)) // correct old messed up templates + } + if ($this->debug) + { + _debug_array($result); + } + return $result; + } + + /** + * copies all cols into the obj and unserializes the data-array + */ + function db2obj(array $row) + { + // unset children and data as they are referenzes to each other + unset($this->children); unset($this->data); + + foreach (self::$db_cols as $db_col => $name) + { + if ($name != 'data') + { + $this->$name = $row[$db_col]; + } + else + { + $this->children = unserialize($row[$db_col]); + } + } + $this->fix_old_template_format(); + } + + /** + * test if we have an old/original template-format and fixes it to the new format + */ + private function fix_old_template_format() + { + if (!is_array($this->children)) $this->children = array(); + + if (!isset($this->children[0]['type'])) + { + // old templates are treated as having one children of type grid (the original template) + $this->data = &$this->children; + unset($this->children); + + $this->children[0]['type'] = 'grid'; + $this->children[0]['data'] = &$this->data; + $this->children[0]['rows'] = &$this->rows; + $this->children[0]['cols'] = &$this->cols; + $this->children[0]['size'] = &$this->size; + + // that code fixes a bug in very old templates, not sure if it's still needed + if ($this->name[0] != '.' && is_array($this->data)) { reset($this->data); each($this->data); while (list($row,$cols) = each($this->data)) { while (list($col,$cell) = each($cols)) { - if (is_array($cell['type'])) { + if (is_array($cell['type'])) + { $this->data[$row][$col]['type'] = $cell['type'][0]; //echo "corrected in $this->name cell $col$row attribute type
\n"; } - if (is_array($cell['align'])) { + if (is_array($cell['align'])) + { $this->data[$row][$col]['align'] = $cell['align'][0]; //echo "corrected in $this->name cell $col$row attribute align
\n"; } } } } - if (!$this->modified) - { - $this->modified = time(); - } - if (is_null($this->group) && !is_int($this->group)) $this->group = 0; - - $this->db->insert($this->table_name,$this->as_array(3,true),$this->as_array(-1,true),__LINE__,__FILE__); - - $rows = $this->db->affected_rows(); - - if (!$rows) - { - echo "

soetemplate::save('$this->name','$this->template','$this->lang',$this->group,'$this->version') nothing written!!!

\n"; - function_backtrace(); - _debug_array($this->db); - } - return $rows; } - - /** - * Deletes the eTemplate from the db, object itself is unchanged - * - * @return int number of affected rows, 1 should be ok, 0 somethings wrong - */ - function delete() + else { - $this->db->delete($this->table_name,$this->as_array(-1,true),__LINE__,__FILE__); - - return $this->db->affected_rows(); - } - - /** - * dumps all eTemplates to /setup/etemplates.inc.php for distribution - * - * @param string $app app- or template-name contain app - * @return string translated message with number of dumped templates or error-message (webserver has no write access) - */ - function dump4setup($app) - { - list($app) = explode('.',$app); - - $this->db->query("SELECT * FROM $this->table_name WHERE et_name LIKE '$app%'"); - - $dir = EGW_SERVER_ROOT . "/$app/setup"; - if (!is_writeable($dir)) + unset($this->data); + // for the moment we make $this->data as a referenz to the first grid + foreach($this->children as $key => $child) { - return lang("Error: webserver is not allowed to write into '%1' !!!",$dir); - } - $file = "$dir/etemplates.inc.php"; - if (file_exists($file)) - { - $old_file = "$dir/etemplates.old.inc.php"; - if (file_exists($old_file)) + if ($child['type'] == 'grid') { - unlink($old_file); + $this->data = &$this->children[$key]['data']; + $this->rows = &$this->children[$key]['rows']; + $this->cols = &$this->children[$key]['cols']; + if (!isset($this->children[$key]['size']) && !empty($this->size)) + { + $this->children[$key]['size'] = &$this->size; + } + else + { + $this->size = &$this->children[$key]['size']; + } + break; } - rename($file,$old_file); } + } + $this->set_rows_cols(); + } - if (!($f = fopen($file,'w'))) - { - return 0; - } - fwrite($f,'db->next_record(); ++$n) + * The never empty type field ensures a cell does not disapear completely. + * Calls it self recursivly for arrays / the rows + * + * @param array $arr the array to compress + * @param boolean $remove_all_objs if true unset all objs, on false use as_array to save only the data of objs + * @return array + */ + static function compress_array($arr,$remove_objs=false) + { + if (!is_array($arr)) + { + return $arr; + } + foreach($arr as $key => $val) + { + if ($remove_objs && $key === 'obj') // it can be an array too { - $str = '$templ_data[] = array('; - foreach ($this->db_cols as $db_col => $name) + unset($arr[$key]); + } + elseif (is_array($val)) + { + $arr[$key] = self::compress_array($val,$remove_objs); + } + elseif (!$remove_objs && $key == 'obj' && is_object($val) && method_exists($val,'as_array') && + // this test prevents an infinit recursion of templates calling itself, atm. etemplate.editor.new + $GLOBALS['egw_info']['etemplate']['as_array'][$this->name]++ < 2) + { + $arr['obj'] = $val->as_array(2); + } + elseif ($val == '' || is_object($val)) + { + unset($arr[$key]); + } + } + return $arr; + } + + /** + * returns obj-data/-vars as array + * + * the returned array ($data_too > 0) can be used with init to recreate the template + * + * @param int $data_too -1 = only keys, 0 = no data array, 1 = data array too, 2 = serialize data array, + * 3 = only data values and data serialized + * @param boolean $db_keys use db-column-names or internal names, default false=internal names + * @return array with template-data + */ + function as_array($data_too=0,$db_keys=false) + { + //echo "

soetemplate::as_array($data_too,$db_keys) name='$this->name', ver='$this->version'

\n"; + $arr = array(); + switch($data_too) + { + case -1: + $cols = self::$db_key_cols; + break; + case 3: + $cols = self::$db_data_cols; + break; + default: + $cols = self::$db_cols; + } + foreach($cols as $db_col => $col) + { + if ($col == 'data') + { + if ($data_too > 0) { - // escape only backslashes and single quotes (in that order) - $str .= "'$name' => '".str_replace(array('\\','\'',"\r"),array('\\\\','\\\'',''),$this->db->f($db_col))."',"; + $arr[$db_keys ? $db_col : $col] = $data_too < 2 ? $this->children : + serialize($this->compress_array($this->children,$db_keys)); } - $str .= ");\n\n"; - fwrite($f,$str); - } - fclose($f); - - return lang("%1 eTemplates for Application '%2' dumped to '%3'",$n,$app,$file); - } - - /** - * extracts all texts: labels and helptexts from the cells of an eTemplate-object - * - * some extensions use a '|' to squezze multiple texts in a label or help field - * - * @return array with messages as key AND value - */ - function getToTranslate() - { - $to_trans = array(); - - $this->widget_tree_walk('getToTranslateCell',$to_trans); - - //echo ''.$this->name.''; _debug_array($to_trans); - return $to_trans; - } - - /** - * Read all eTemplates of an app an extracts the texts to an array - * - * @param string $app name of the app - * @return array with texts - */ - function getToTranslateApp($app) - { - $to_trans = array(); - - $tpls = $this->search($app); - - $tpl =& new soetemplate; // to not alter our own data - - while (list(,$keys) = each($tpls)) - { - if (($keys['name'] != $last['name'] || // write only newest version - $keys['template'] != $last['template']) && - strpos($keys['name'],'test') === false) - { - $tpl->read($keys); - $to_trans += $tpl->getToTranslate(); - $last = $keys; - } - } - return $to_trans; - } - - /** - * Write new lang-file using the existing one and all text from the eTemplates - * - * @param string $app app- or template-name - * @param string $lang language the messages in the template are, defaults to 'en' - * @param array $additional extra texts to translate, if you pass here an array with all messages and - * select-options they get writen too (form is => ) - * @return string translated message with number of messages written (total and new), or error-message - */ - function writeLangFile($app,$lang='en',$additional='') - { - if (!$additional) - { - $additional = array(); - } - list($app) = explode('.',$app); - - if (!file_exists(EGW_SERVER_ROOT.'/developer_tools/inc/class.solangfile.inc.php')) - { - $solangfile =& CreateObject('etemplate.solangfile'); } else { - $solangfile =& CreateObject('developer_tools.solangfile'); + $arr[$db_keys ? $db_col : $col] = $this->$col; } - $langarr = $solangfile->load_app($app,$lang); - if (!is_array($langarr)) - { - $langarr = array(); - } - $commonarr = $solangfile->load_app('phpgwapi',$lang) + $solangfile->load_app('etemplate',$lang); + } + if ($data_too != -1 && $this->tpls_in_file && !$db_keys) + { + $arr['tpls_in_file'] = $this->tpls_in_file; + } + if ($data_too != -1 && $this->onclick_handler && !$db_keys) + { + $arr['onclick_handler'] = $this->onclick_handler; + } + return $arr; + } - $to_trans = $this->getToTranslateApp($app); - if (is_array($additional)) + /** + * saves eTemplate-object to db, can be used as saveAs by giving keys as params + * + * @param string $name name of the eTemplate or array with the values for all keys + * @param string $template template-set + * @param string $lang language or '' + * @param int $group id of the (primary) group, not used at the moment !!! + * @param string $version version of the eTemplate + * @return int number of affected rows, 1 should be ok, 0 somethings wrong + */ + function save($name='',$template='.',$lang='.',$group='',$version='.') + { + if (is_array($name)) + { + $template = $name['template']; + $lang = $name['lang']; + $group = $name['group']; + $version = $name['version']; + $name = $name['name']; + } + if ($name != '') + { + $this->name = $name; + } + if ($lang != '.') + { + $this->lang = $lang; + } + if ($template != '.') + { + $this->template = $template; + } + if ($group != '') + { + $this->group = $group; + } + if ($version != '.') + { + $this->version = $version; + } + if ($this->name == '') // name need to be set !!! + { + return False; + } + if ($this->debug > 0 || $this->debug == $this->name) + { + echo "

soetemplate::save('$this->name','$this->template','$this->lang',$this->group,'$this->version')

\n"; + } + if ($this->name[0] != '.' && is_array($this->data)) // correct old messed up templates + { + reset($this->data); each($this->data); + while (list($row,$cols) = each($this->data)) { - //echo "writeLangFile: additional ="; _debug_array($additional); - foreach($additional as $msg) + while (list($col,$cell) = each($cols)) { - if (!is_array($msg)) $to_trans[trim(strtolower($msg))] = $msg; - } - } - unset($to_trans['']); - - for ($new = $n = 0; list($message_id,$content) = each($to_trans); ++$n) - { - if (!isset($langarr[$message_id]) && !isset($commonarr[$message_id])) - { - if (@isset($langarr[$content])) // caused by not lowercased-message_id's - { - unset($langarr[$content]); + if (is_array($cell['type'])) { + $this->data[$row][$col]['type'] = $cell['type'][0]; + //echo "corrected in $this->name cell $col$row attribute type
\n"; + } + if (is_array($cell['align'])) { + $this->data[$row][$col]['align'] = $cell['align'][0]; + //echo "corrected in $this->name cell $col$row attribute align
\n"; } - $langarr[$message_id] = array( - 'message_id' => $message_id, - 'app_name' => $app, - 'content' => $content - ); - ++$new; } } - ksort($langarr); - - $dir = EGW_SERVER_ROOT . "/$app/setup"; - if (!is_writeable($dir)) - { - return lang("Error: webserver is not allowed to write into '%1' !!!",$dir); - } - $file = "$dir/phpgw_$lang.lang"; - if (file_exists($file)) - { - $old_file = "$dir/phpgw_$lang.old.lang"; - if (file_exists($old_file)) - { - unlink($old_file); - } - rename($file,$old_file); - } - $solangfile->write_file($app,$langarr,$lang); - $solangfile->loaddb($app,$lang); - - return lang("%1 (%2 new) Messages writen for Application '%3' and Languages '%4'",$n,$new,$app,$lang); } - - /** - * Imports the dump-file /$app/setup/etempplates.inc.php unconditional (!) - * - * @param string $app app name - * @return string translated message with number of templates imported - */ - function import_dump($app) + if (!$this->modified) { - $templ_version=0; - - include($path = EGW_SERVER_ROOT."/$app/setup/etemplates.inc.php"); - $templ =& new etemplate($app); - - foreach($templ_data as $data) - { - if ((int)$templ_version < 1) // we need to stripslashes - { - $data['data'] = stripslashes($data['data']); - } - $templ->init($data); - - if (!$templ->modified) - { - $templ->modified = filemtime($path); - } - $templ->save(); - } - return lang("%1 new eTemplates imported for Application '%2'",$n,$app); + $this->modified = time(); } + if (is_null($this->group) && !is_int($this->group)) $this->group = 0; - /** - * test if new template-import necessary for app and does the import - * - * Get called on every read of a eTemplate, caches the result in phpgw_info. - * The timestamp of the last import for app gets written into the db. - * - * @param string $app app- or template-name - * @return string translated message with number of templates imported - */ - function test_import($app) // should be done from the setup-App + $this->db->insert(self::TABLE,$this->as_array(3,true),$this->as_array(-1,true),__LINE__,__FILE__,'etemplate'); + + if (!($rows = $this->db->affected_rows())) { - list($app) = explode('.',$app); - - if (!$app || $GLOBALS['egw_info']['etemplate']['import_tested'][$app]) - { - return ''; // ensure test is done only once per call and app - } - $GLOBALS['egw_info']['etemplate']['import_tested'][$app] = True; // need to be done before new ... - - $path = EGW_SERVER_ROOT."/$app/setup/etemplates.inc.php"; - - if ($time = @filemtime($path)) - { - $templ =& new soetemplate(".$app",'','##'); - if ($templ->lang != '##' || $templ->modified < $time) // need to import - { - $ret = $this->import_dump($app); - $templ->modified = $time; - $templ->save('.'.$app,'','##'); - } - } - return $ret; + echo "

soetemplate::save('$this->name','$this->template','$this->lang',$this->group,'$this->version') nothing written!!!

\n"; + function_backtrace(); + _debug_array($this->db); } + return $rows; + } + + /** + * Deletes the eTemplate from the db, object itself is unchanged + * + * @return int number of affected rows, 1 should be ok, 0 somethings wrong + */ + function delete() + { + $this->db->delete(self::TABLE,$this->as_array(-1,true),__LINE__,__FILE__,'etemplate'); + + return $this->db->affected_rows(); + } + + /** + * dumps all eTemplates to /setup/etemplates.inc.php for distribution + * + * @param string $app app- or template-name contain app + * @return string translated message with number of dumped templates or error-message (webserver has no write access) + */ + function dump4setup($app) + { + list($app) = explode('.',$app); + + $dir = EGW_SERVER_ROOT . "/$app/setup"; + if (!is_writeable($dir)) + { + return lang("Error: webserver is not allowed to write into '%1' !!!",$dir); + } + $file = "$dir/etemplates.inc.php"; + if (file_exists($file)) + { + $old_file = "$dir/etemplates.old.inc.php"; + if (file_exists($old_file)) + { + unlink($old_file); + } + rename($file,$old_file); + } + + if (!($f = fopen($file,'w'))) + { + return 0; + } + fwrite($f,'db->select(self::TABLE,'*','et_name LIKE '.$this->db->quote($app.'%'),__LINE__, __FILE__,false,'',egw_db::FETCH_ASSOC) as $row) + { + $str = '$templ_data[] = array('; + foreach (self::$db_cols as $db_col => $name) + { + // escape only backslashes and single quotes (in that order) + $str .= "'$name' => '".str_replace(array('\\','\'',"\r"),array('\\\\','\\\'',''),$row[$db_col])."',"; + } + $str .= ");\n\n"; + fwrite($f,$str); + ++$n; + } + fclose($f); + + return lang("%1 eTemplates for Application '%2' dumped to '%3'",$n,$app,$file); + } + + /** + * extracts all texts: labels and helptexts from the cells of an eTemplate-object + * + * some extensions use a '|' to squezze multiple texts in a label or help field + * + * @return array with messages as key AND value + */ + function getToTranslate() + { + $to_trans = array(); + + $this->widget_tree_walk(array('soetemplate','getToTranslateCell'),$to_trans); - /** - * prints/echos the template's content, eg. for debuging - * - * @param boolean $backtrace = true give a function backtrace - * @param boolean $no_other_objs = true dump other objs (db, html, ...) too - */ - function echo_tmpl($backtrace=true,$no_other_objs=true) - { - static $objs = array('db','html','xul_io'); - - if ($backtrace) echo "

".function_backtrace(1)."

\n"; + //echo ''.$this->name.''; _debug_array($to_trans); + return $to_trans; + } - if ($no_other_objs) + /** + * Read all eTemplates of an app an extracts the texts to an array + * + * @param string $app name of the app + * @return array with texts + */ + function getToTranslateApp($app) + { + $to_trans = array(); + + $tpls = $this->search($app); + + $tpl =& new soetemplate; // to not alter our own data + + while (list(,$keys) = each($tpls)) + { + if (($keys['name'] != $last['name'] || // write only newest version + $keys['template'] != $last['template']) && + strpos($keys['name'],'test') === false) { - foreach($objs as $obj) - { - $$obj = &$this->$obj; - unset($this->$obj); - } - } - _debug_array($this); - - if ($no_other_objs) - { - foreach($objs as $obj) - { - $this->$obj = &$$obj; - unset($$obj); - } + $tpl->read($keys); + $to_trans += $tpl->getToTranslate(); + $last = $keys; } } + return $to_trans; + } - /** - * applys a function to each widget in the children tree of the template - * - * The function should be defined as [&]func([&]$widget,[&]$extra[,$path]) - * If the function returns anything but null or sets $extra['__RETURN__NOW__'] (func has to reference $extra !!!), - * the walk stops imediatly and returns that result - * - * Only some widgets have a sub-tree of children: *box, grid, template, ... - * For them we call tree_walk($widget,$func,$extra) instead of func direct - * - * 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 mixed &$extra extra parameter passed to function - * @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='/') + /** + * Write new lang-file using the existing one and all text from the eTemplates + * + * @param string $app app- or template-name + * @param string $lang language the messages in the template are, defaults to 'en' + * @param array $additional extra texts to translate, if you pass here an array with all messages and + * select-options they get writen too (form is => ) + * @return string translated message with number of messages written (total and new), or error-message + */ + function writeLangFile($app,$lang='en',$additional='') + { + if (!$additional) { - if (!is_callable($func)) - { - echo "

boetemplate($this->name)::widget_tree_walk(".print_r($func,true).", ".print_r($extra,true).", ".print_r($opts,true).") func is not callable !!!
".function_backtrace()."

"; - return false; - } - foreach($this->children as $c => $nul) - { - $child = &$this->children[$c]; - if (isset($this->widgets_with_children[$child['type']])) - { - $result =& $this->tree_walk($child,$func,$extra,$path.$c); - } - elseif (is_array($func)) - { - $result =& call_user_func_array($func,array(&$child,&$extra,$path.$c)); - } - else - { - $result =& $func($child,$extra,$path.$c); - } - if (!is_null($result) || is_array($extra) && isset($extra['__RETURN_NOW__'])) break; - } - return $result; + $additional = array(); } - - /** - * applys a function to each child in the tree of a widget (incl. the widget itself) - * - * The function should be defined as [&]func([&]$widget,[&]$extra[,$path]) [] = optional - * If the function returns anything but null or sets $extra['__RETURN__NOW__'] (func has to reference $extra !!!), - * the walk stops imediatly and returns that result - * - * Only some widgets have a sub-tree of children: *box, grid, template, ... - * For performance reasons the function use recursion only if a widget with children contains - * 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 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 - */ - function &tree_walk(&$widget,$func,&$extra,$path='') + list($app) = explode('.',$app); + + if (!file_exists(EGW_SERVER_ROOT.'/developer_tools/inc/class.solangfile.inc.php')) { - if (!is_callable($func)) - { - echo "

boetemplate::tree_walk(, ".print_r($func,true).", ".print_r($extra,true).", ".print_r($opts,true).") func is not callable !!!
".function_backtrace()."

"; - return false; - } - if(is_array($func)) - { - $result =& call_user_func_array($func,array(&$widget,&$extra,$path)); - } - else - { - $result =& $func($widget,$extra,$path); - } - if (!is_null($result) || is_array($extra) && isset($extra['__RETURN__NOW__']) || - !isset($this->widgets_with_children[$widget['type']])) - { - return $result; - } - switch($widget['type']) - { - case 'box': - case 'vbox': - case 'hbox': - case 'groupbox': - case 'deck': - for($n = 1; is_array($widget[$n]); ++$n) - { - $child = &$widget[$n]; - if (isset($this->widgets_with_children[$child['type']])) - { - $result =& $this->tree_walk($child,$func,$extra,$path.'/'.$n); - } - elseif(is_array($func)) - { - $result =& call_user_func_array($func,array(&$child,&$extra,$path.'/'.$n)); - } - else - { - $result =& $func($child,$extra,$path.'/'.$n); - } - if (!is_null($result) || is_array($extra) && isset($extra['__RETURN__NOW__'])) return $result; - } - break; + $solangfile =& CreateObject('etemplate.solangfile'); + } + else + { + $solangfile =& CreateObject('developer_tools.solangfile'); + } + $langarr = $solangfile->load_app($app,$lang); + if (!is_array($langarr)) + { + $langarr = array(); + } + $commonarr = $solangfile->load_app('phpgwapi',$lang) + $solangfile->load_app('etemplate',$lang); - case 'grid': - $data = &$widget['data']; - if (!is_array($data)) break; // no children - - foreach($data as $r => $row) - { - if (!$r || !is_array($row)) continue; - - foreach($row as $c => $col) - { - $child = &$data[$r][$c]; - if (isset($this->widgets_with_children[$child['type']])) - { - $result =& $this->tree_walk($child,$func,$extra,$path.'/'.$r.$c); - } - elseif(is_array($func)) - { - $result =& call_user_func_array($func,array(&$child,&$extra,$path.'/'.$r.$c)); - } - else - { - $result =& $func($child,$extra,$path.'/'.$r.$c); - } - if (!is_null($result) || is_array($extra) && isset($extra['__RETURN__NOW__'])) return $result; - unset($child); - } - } - break; - - case 'template': - if (!isset($widget['obj']) && $widget['name'][0] != '@') - { - $widget['obj'] =& new etemplate; - if (!$widget['obj']->read($widget['name'])) $widget['obj'] = false; - } - if (!is_object($widget['obj'])) break; // cant descent into template - - $result =& $widget['obj']->widget_tree_walk($func,$extra,$path.'/'); - break; + $to_trans = $this->getToTranslateApp($app); + if (is_array($additional)) + { + //echo "writeLangFile: additional ="; _debug_array($additional); + foreach($additional as $msg) + { + if (!is_array($msg)) $to_trans[trim(strtolower($msg))] = $msg; + } + } + unset($to_trans['']); + + for ($new = $n = 0; list($message_id,$content) = each($to_trans); ++$n) + { + if (!isset($langarr[$message_id]) && !isset($commonarr[$message_id])) + { + if (@isset($langarr[$content])) // caused by not lowercased-message_id's + { + unset($langarr[$content]); + } + $langarr[$message_id] = array( + 'message_id' => $message_id, + 'app_name' => $app, + 'content' => $content + ); + ++$new; + } + } + ksort($langarr); + + $dir = EGW_SERVER_ROOT . "/$app/setup"; + if (!is_writeable($dir)) + { + return lang("Error: webserver is not allowed to write into '%1' !!!",$dir); + } + $file = "$dir/phpgw_$lang.lang"; + if (file_exists($file)) + { + $old_file = "$dir/phpgw_$lang.old.lang"; + if (file_exists($old_file)) + { + unlink($old_file); + } + rename($file,$old_file); + } + $solangfile->write_file($app,$langarr,$lang); + $solangfile->loaddb($app,$lang); + + return lang("%1 (%2 new) Messages writen for Application '%3' and Languages '%4'",$n,$new,$app,$lang); + } + + /** + * Imports the dump-file /$app/setup/etempplates.inc.php unconditional (!) + * + * @param string $app app name + * @return string translated message with number of templates imported + */ + static function import_dump($app) + { + $templ_version=0; + + include($path = EGW_SERVER_ROOT."/$app/setup/etemplates.inc.php"); + $templ =& new etemplate($app); + + foreach($templ_data as $data) + { + if ((int)$templ_version < 1) // we need to stripslashes + { + $data['data'] = stripslashes($data['data']); + } + $templ->init($data); + + if (!$templ->modified) + { + $templ->modified = filemtime($path); + } + $templ->save(); + } + return lang("%1 new eTemplates imported for Application '%2'",$n,$app); + } + + /** + * test if new template-import necessary for app and does the import + * + * Get called on every read of a eTemplate, caches the result in phpgw_info. + * The timestamp of the last import for app gets written into the db. + * + * @param string $app app- or template-name + * @return string translated message with number of templates imported + */ + static function test_import($app) // should be done from the setup-App + { + list($app) = explode('.',$app); + + if (!$app || $GLOBALS['egw_info']['etemplate']['import_tested'][$app]) + { + return ''; // ensure test is done only once per call and app + } + $GLOBALS['egw_info']['etemplate']['import_tested'][$app] = True; // need to be done before new ... + + $path = EGW_SERVER_ROOT."/$app/setup/etemplates.inc.php"; + + if ($time = @filemtime($path)) + { + $templ =& new soetemplate(".$app",'','##'); + if ($templ->lang != '##' || $templ->modified < $time) // need to import + { + $ret = $this->import_dump($app); + $templ->modified = $time; + $templ->save('.'.$app,'','##'); + } + } + return $ret; + } + + /** + * prints/echos the template's content, eg. for debuging + * + * @param boolean $backtrace = true give a function backtrace + * @param boolean $no_other_objs = true dump other objs (db, html, ...) too + */ + function echo_tmpl($backtrace=true,$no_other_objs=true) + { + static $objs = array('db','html','xul_io'); + + if ($backtrace) echo "

".function_backtrace(1)."

\n"; + + if ($no_other_objs) + { + foreach($objs as $obj) + { + $$obj = &$this->$obj; + unset($this->$obj); + } + } + _debug_array($this); + + if ($no_other_objs) + { + foreach($objs as $obj) + { + $this->$obj = &$$obj; + unset($$obj); } - return $result; } } - if (!function_exists('getToTranslateCell')) + /** + * applys a function to each widget in the children tree of the template + * + * The function should be defined as [&]func([&]$widget,[&]$extra[,$path]) + * If the function returns anything but null or sets $extra['__RETURN__NOW__'] (func has to reference $extra !!!), + * the walk stops imediatly and returns that result + * + * Only some widgets have a sub-tree of children: *box, grid, template, ... + * For them we call tree_walk($widget,$func,$extra) instead of func direct + * + * 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 mixed &$extra extra parameter passed to function + * @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='/') { - /** - * extracts all translatable labels from a widget - * - * @param array $cell the widget - * @param array &$to_trans array with (lowercased) label => translation pairs - */ - function getToTranslateCell($cell,&$to_trans) + if (!is_callable($func)) { - //echo $cell['name']; _debug_array($cell); - $strings = explode('|',$cell['help']); - - if ($cell['type'] != 'image') + echo "

boetemplate($this->name)::widget_tree_walk(".print_r($func,true).", ".print_r($extra,true).", ".print_r($opts,true).") func is not callable !!!
".function_backtrace()."

"; + return false; + } + foreach($this->children as $c => &$child) + { + $child = &$this->children[$c]; + if (isset(soetemplate::$widgets_with_children[$child['type']])) { - $strings = array_merge($strings,explode('|',$cell['label'])); + $result =& $this->tree_walk($child,$func,$extra,$path.$c); } - list($extra_row) = explode(',',$cell['size']); - if (substr($cell['type'],0,6) == 'select' && !empty($extra_row) && !intval($extra_row)) + elseif (is_array($func)) { - $strings[] = $extra_row; + $result =& call_user_func_array($func,array(&$child,&$extra,$path.$c)); } - if (!empty($cell['blur'])) + else { - $strings[] = $cell['blur']; + $result =& $func($child,$extra,$path.$c); } - foreach($strings as $str) - { - if (strlen($str) > 1 && $str{0} != '@' && $str{0} != '$' && - strpos($str,'$row') === false && strpos($str,'$cont') === false) + if (!is_null($result) || is_array($extra) && isset($extra['__RETURN_NOW__'])) break; + } + return $result; + } + + /** + * applys a function to each child in the tree of a widget (incl. the widget itself) + * + * The function should be defined as [&]func([&]$widget,[&]$extra[,$path]) [] = optional + * If the function returns anything but null or sets $extra['__RETURN__NOW__'] (func has to reference $extra !!!), + * the walk stops imediatly and returns that result + * + * Only some widgets have a sub-tree of children: *box, grid, template, ... + * For performance reasons the function use recursion only if a widget with children contains + * 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 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 + */ + function &tree_walk(&$widget,$func,&$extra,$path='') + { + if (!is_callable($func)) + { + echo "

boetemplate::tree_walk(, ".print_r($func,true).", ".print_r($extra,true).", ".print_r($opts,true).") func is not callable !!!
".function_backtrace()."

"; + return false; + } + if(is_array($func)) + { + $result =& call_user_func_array($func,array(&$widget,&$extra,$path)); + } + else + { + $result =& $func($widget,$extra,$path); + } + if (!is_null($result) || is_array($extra) && isset($extra['__RETURN__NOW__']) || + !isset(soetemplate::$widgets_with_children[$widget['type']])) + { + return $result; + } + switch($widget['type']) + { + case 'box': + case 'vbox': + case 'hbox': + case 'groupbox': + case 'deck': + for($n = 1; is_array($widget[$n]); ++$n) { - $to_trans[trim(strtolower($str))] = $str; + $child = &$widget[$n]; + if (isset(soetemplate::$widgets_with_children[$child['type']])) + { + $result =& $this->tree_walk($child,$func,$extra,$path.'/'.$n); + } + elseif(is_array($func)) + { + $result =& call_user_func_array($func,array(&$child,&$extra,$path.'/'.$n)); + } + else + { + $result =& $func($child,$extra,$path.'/'.$n); + } + if (!is_null($result) || is_array($extra) && isset($extra['__RETURN__NOW__'])) return $result; } + break; + + case 'grid': + $data = &$widget['data']; + if (!is_array($data)) break; // no children + + foreach($data as $r => $row) + { + if (!$r || !is_array($row)) continue; + + foreach($row as $c => $col) + { + $child = &$data[$r][$c]; + if (isset(soetemplate::$widgets_with_children[$child['type']])) + { + $result =& $this->tree_walk($child,$func,$extra,$path.'/'.$r.$c); + } + elseif(is_array($func)) + { + $result =& call_user_func_array($func,array(&$child,&$extra,$path.'/'.$r.$c)); + } + else + { + $result =& $func($child,$extra,$path.'/'.$r.$c); + } + if (!is_null($result) || is_array($extra) && isset($extra['__RETURN__NOW__'])) return $result; + unset($child); + } + } + break; + + case 'template': + if (!isset($widget['obj']) && $widget['name'][0] != '@') + { + $widget['obj'] =& new etemplate; + if (!$widget['obj']->read($widget['name'])) $widget['obj'] = false; + } + if (!is_object($widget['obj'])) break; // cant descent into template + + $result =& $widget['obj']->widget_tree_walk($func,$extra,$path.'/'); + break; + } + return $result; + } + + /** + * extracts all translatable labels from a widget + * + * @param array $cell the widget + * @param array &$to_trans array with (lowercased) label => translation pairs + */ + static function getToTranslateCell($cell,&$to_trans) + { + //echo $cell['name']; _debug_array($cell); + $strings = explode('|',$cell['help']); + + if ($cell['type'] != 'image') + { + $strings = array_merge($strings,explode('|',$cell['label'])); + } + list($extra_row) = explode(',',$cell['size']); + if (substr($cell['type'],0,6) == 'select' && !empty($extra_row) && !intval($extra_row)) + { + $strings[] = $extra_row; + } + if (!empty($cell['blur'])) + { + $strings[] = $cell['blur']; + } + foreach($strings as $str) + { + if (strlen($str) > 1 && $str{0} != '@' && $str{0} != '$' && + strpos($str,'$row') === false && strpos($str,'$cont') === false) + { + $to_trans[trim(strtolower($str))] = $str; } } } + /** + * init our static vars + */ + static function _init_static() + { + self::$db_cols = self::$db_key_cols + self::$db_data_cols; + } +} +soetemplate::_init_static(); \ No newline at end of file diff --git a/etemplate/inc/class.tree_widget.inc.php b/etemplate/inc/class.tree_widget.inc.php index c6976fbb08..303eaa922b 100644 --- a/etemplate/inc/class.tree_widget.inc.php +++ b/etemplate/inc/class.tree_widget.inc.php @@ -123,7 +123,7 @@ class tree_widget // creating a div-id and var-name for the tree-object by replacing brackets with underscores and removing exec or evtl. cont parts $tree_id = str_replace(array('exec[cont][','exec[','[',']'),array('','','_',''),$name); $onNodeSelect = 'onNodeSelect_'.$tree_id; - $script = $tmpl->html->input_hidden($name,$value,false)."\n"; list(,$class) = explode(',',$cell['span']); - $value = $script.$tmpl->html->tree($tmpl->_sel_options($cell,$name),$value,false,$onNodeSelect,$tree_id,$class,'',$onCheck); + $value = $script.html::tree($tmpl->_sel_options($cell,$name),$value,false,$onNodeSelect,$tree_id,$class,'',$onCheck); $cell = etemplate::empty_cell('html',$cell['name']); diff --git a/etemplate/inc/class.uietemplate.inc.php b/etemplate/inc/class.uietemplate.inc.php index 771d39e7c7..b99a796a42 100644 --- a/etemplate/inc/class.uietemplate.inc.php +++ b/etemplate/inc/class.uietemplate.inc.php @@ -1,2189 +1,2172 @@ - * @version $Id$ - */ +/** +* eGroupWare - EditableTemplates - HTML User Interface +* +* @link http://www.egroupware.org +* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License +* @author Ralf Becker +* @copyright 2002-8 by RalfBecker@outdoor-training.de +* @package etemplate +* @subpackage api +* @version $Id$ +*/ - include_once(EGW_INCLUDE_ROOT . '/etemplate/inc/class.boetemplate.inc.php'); +/** +* creates dialogs / HTML-forms from eTemplate descriptions +* +* Usage example: +* +* $tmpl = new etemplate('app.template.name'); +* $tmpl->exec('app.class.callback',$content_to_show); +* +* This creates a form from the eTemplate 'app.template.name' and takes care that +* the method / public function 'callback' in class 'class' of 'app' gets called +* if the user submits the form. For the complete param's see the description of exec. +* +* etemplate or uietemplate extends boetemplate, all vars and public functions are inherited +*/ +class etemplate extends boetemplate +{ + /** + * integer debug-level or template-name or cell-type or '' = off + * 1=calls to show and process_show, 2=content after process_show, + * 3=calls to show_cell and process_show_cell + * + * @var int/string + */ + var $debug; + var $xslt = false; /* do we run in the xslt framework (true) or the regular eGW one (false) */ + var $class_conf = array('nmh' => 'th','nmr0' => 'row_on','nmr1' => 'row_off'); + var $public_functions = array('process_exec' => True); + /** + * Inner width of browser window + * + * @var int + */ + var $innerWidth; + /** + * Reference to the content-param of the last call to show, for extensions to use + * + * @var array + */ + var $content; + /** + * Reference to the sel_options-param of the last call to show, for extensions to use + * + * @var array + */ + var $sel_options; + /** + * Name of the currently processed etemplate, reference to $GLOBALS['egw_info']['etemplate']['name_form'] + * + * @var string + */ + var $name_form; + /** + * Used form-names in this request, reference to $GLOBALS['egw_info']['etemplate']['name_forms'] + * + * @var array + */ + var $name_forms; + /** + * Basename of the variables (content) in $_POST and id's, usually 'exec', + * if there's not more then one eTemplate on the page (then it will be exec, exec2, exec3, ... + * + * @var string + */ + var $name_vars='exec'; + /** + * Are we running as sitemgr module or not + * + * @var boolean + */ + var $sitemgr=false; /** - * creates dialogs / HTML-forms from eTemplate descriptions - * - * Usage example: - * - * $tmpl =& CreateObject('etemplate.etemplate','app.template.name'); - * $tmpl->exec('app.class.callback',$content_to_show); - * - * This creates a form from the eTemplate 'app.template.name' and takes care that - * the method / public function 'callback' in class 'class' of 'app' gets called - * if the user submits the form. For the complete param's see the description of exec. - * - * etemplate or uietemplate extends boetemplate, all vars and public functions are inherited - * - * @package etemplate - * @subpackage api - * @author Ralf Becker - * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - */ - 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 + */ + function etemplate($name='',$load_via='') { - /** - * integer debug-level or template-name or cell-type or '' = off - * 1=calls to show and process_show, 2=content after process_show, - * 3=calls to show_cell and process_show_cell - * - * @var int/string - */ - var $debug; - /** - * Instance of the html class - * - * @var html - */ - var $html; - var $xslt = false; /* do we run in the xslt framework (true) or the regular eGW one (false) */ - var $class_conf = array('nmh' => 'th','nmr0' => 'row_on','nmr1' => 'row_off'); - var $public_functions = array('process_exec' => True); - /** - * Inner width of browser window - * - * @var int - */ - var $innerWidth; - /** - * Reference to the content-param of the last call to show, for extensions to use - * - * @var array - */ - var $content; - /** - * Reference to the sel_options-param of the last call to show, for extensions to use - * - * @var array - */ - var $sel_options; - /** - * Name of the currently processed etemplate, reference to $GLOBALS['egw_info']['etemplate']['name_form'] - * - * @var string - */ - var $name_form; - /** - * Used form-names in this request, reference to $GLOBALS['egw_info']['etemplate']['name_forms'] - * - * @var array - */ - var $name_forms; - /** - * Basename of the variables (content) in $_POST and id's, usually 'exec', - * if there's not more then one eTemplate on the page (then it will be exec, exec2, exec3, ... - * - * @var string - */ - var $name_vars='exec'; - /** - * Are we running as sitemgr module or not - * - * @var boolean - */ - var $sitemgr=false; - - /** - * 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 etemplate($name='',$load_via='') + if (!is_object($GLOBALS['egw']->template)) { - if (!is_object($GLOBALS['egw']->html)) - { - $GLOBALS['egw']->html =& CreateObject('phpgwapi.html'); - } - $this->html = &$GLOBALS['egw']->html; - - if (!is_object($GLOBALS['egw']->template)) - { - $GLOBALS['egw']->template =& CreateObject('phpgwapi.Template'); - } - $this->boetemplate($name,$load_via); - - $this->xslt = is_object($GLOBALS['egw']->xslttpl); - - $this->sitemgr = is_object($GLOBALS['Common_BO']); - - if (($this->innerWidth = (int) $_POST['innerWidth'])) - { - $GLOBALS['egw']->session->appsession('innerWidth','etemplate',$this->innerWidth); - } - elseif (!($this->innerWidth = (int) $GLOBALS['egw']->session->appsession('innerWidth','etemplate'))) - { - $this->innerWidth = 1018; // default width for an assumed screen-resolution of 1024x768 - } - //echo "

_POST[innerWidth]='$_POST[innerWidth]', innerWidth=$this->innerWidth

\n"; - $this->name_form =& $GLOBALS['egw_info']['etemplate']['name_form']; - $this->name_forms =& $GLOBALS['egw_info']['etemplate']['name_forms']; - if (!is_array($this->name_forms)) $this->name_forms = array(); + $GLOBALS['egw']->template =& CreateObject('phpgwapi.Template'); } + $this->boetemplate($name,$load_via); - /** - * 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 - */ - function location($params='') + $this->xslt = is_object($GLOBALS['egw']->xslttpl); + + $this->sitemgr = is_object($GLOBALS['Common_BO']); + + if (($this->innerWidth = (int) $_POST['innerWidth'])) { - $GLOBALS['egw']->redirect_link(is_array($params) ? '/index.php' : $params, - is_array($params) ? $params : ''); + $GLOBALS['egw']->session->appsession('innerWidth','etemplate',$this->innerWidth); } - - /** - * Generats 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, 2 = echo without navbar (eg. for popups) - * -1 = first time return html, after use 0 (echo html incl. navbar), eg. for home - * @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,$content,$sel_options='',$readonlys='',$preserv='',$output_mode=0,$ignore_validation='',$changes='') + elseif (!($this->innerWidth = (int) $GLOBALS['egw']->session->appsession('innerWidth','etemplate'))) { - //echo "
globals[java_script] = '".$GLOBALS['egw_info']['etemplate']['java_script']."', this->java_script() = '".$this->java_script()."'\n"; - if (!$sel_options) - { - $sel_options = array(); - } - if (!$readonlys) - { - $readonlys = array(); - } - if (!$preserv) - { - $preserv = array(); - } - if (!$changes) - { - $changes = array(); - } - if (isset($content['app_header'])) - { - $GLOBALS['egw_info']['flags']['app_header'] = $content['app_header']; - } - if ($GLOBALS['egw_info']['flags']['currentapp'] != 'etemplate') - { - $GLOBALS['egw']->translation->add_app('etemplate'); // some extensions have own texts - } - $id = $this->appsession_id(); -//echo "

unsetting existing egw_info[etemplate] which had keys=".implode(',',array_keys($GLOBALS['egw_info']['etemplate']))."

\n"; - // initialise $GLOBALS['egw_info']['etemplate'], in case there are multiple eTemplates on a page - $GLOBALS['egw_info']['etemplate'] = array( - 'name_forms' => $GLOBALS['egw_info']['etemplate']['name_forms'], - 'validation_errors' => $GLOBALS['egw_info']['etemplate']['validation_errors'], - 'hooked' => $GLOBALS['egw_info']['etemplate']['hooked'], - 'content' => $GLOBALS['egw_info']['etemplate']['content'], - 'hook_content' => $GLOBALS['egw_info']['etemplate']['hook_content'], - 'hook_app' => $GLOBALS['egw_info']['etemplate']['hook_app'], - 'extension_data' => $GLOBALS['egw_info']['etemplate']['extension_data'] - ); -//echo "

hooked=".(int)!!$GLOBALS['egw_info']['etemplate']['hooked'].", content=".(int)!!$GLOBALS['egw_info']['etemplate']['content'].", hook_content=".(int)!!$GLOBALS['egw_info']['etemplate']['hook_content'].", hook_app={$GLOBALS['egw_info']['etemplate']['hook_app']}

\n"; - $this->name_form =& $GLOBALS['egw_info']['etemplate']['name_form']; - $this->name_forms =& $GLOBALS['egw_info']['etemplate']['name_forms']; - if (!is_array($this->name_forms)) $this->name_forms = array(); + $this->innerWidth = 1018; // default width for an assumed screen-resolution of 1024x768 + } + //echo "

_POST[innerWidth]='$_POST[innerWidth]', innerWidth=$this->innerWidth

\n"; + $this->name_form =& $GLOBALS['egw_info']['etemplate']['name_form']; + $this->name_forms =& $GLOBALS['egw_info']['etemplate']['name_forms']; + if (!is_array($this->name_forms)) $this->name_forms = array(); + } - // use different form-names to allows multiple eTemplates in one page, eg. addressbook-view - $this->name_form = 'eTemplate'; - if (in_array($this->name_form,$this->name_forms)) - { - $this->name_form .= 1+count($this->name_forms); - $this->name_vars .= 1+count($this->name_forms); - } - $this->name_forms[] = $this->name_form; - - $GLOBALS['egw_info']['etemplate']['output_mode'] = $output_mode; // let extensions "know" they are run eg. in a popup - $GLOBALS['egw_info']['etemplate']['form_options'] = ''; // might be set in show - $GLOBALS['egw_info']['etemplate']['to_process'] = array(); - - $html = $this->html->form($this->include_java_script(1). - $this->html->input_hidden(array( - 'submit_button' => '', - 'innerWidth' => '', - ),'',False). - $this->show($this->complete_array_merge($content,$changes),$sel_options,$readonlys,$this->name_vars),array( - 'etemplate_exec_id' => $id - ),$this->sitemgr ? '' : '/etemplate/process_exec.php?menuaction='.$method, - '',$this->name_form,$GLOBALS['egw_info']['etemplate']['form_options']. - // dont set the width of popups! - ($output_mode != 0 ? '' : ' onsubmit="this.innerWidth.value=window.innerWidth ? window.innerWidth : document.body.clientWidth;"')); - //echo "to_process="; _debug_array($GLOBALS['egw_info']['etemplate']['to_process']); - - if ($this->sitemgr) + /** + * 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='') + { + $GLOBALS['egw']->redirect_link(is_array($params) ? '/index.php' : $params, + is_array($params) ? $params : ''); + } + + /** + * Generats 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, 2 = echo without navbar (eg. for popups) + * -1 = first time return html, after use 0 (echo html incl. navbar), eg. for home + * @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,$content,$sel_options='',$readonlys='',$preserv='',$output_mode=0,$ignore_validation='',$changes='') + { + //echo "
globals[java_script] = '".$GLOBALS['egw_info']['etemplate']['java_script']."', this->java_script() = '".$this->java_script()."'\n"; + if (!$sel_options) + { + $sel_options = array(); + } + if (!$readonlys) + { + $readonlys = array(); + } + if (!$preserv) + { + $preserv = array(); + } + if (!$changes) + { + $changes = array(); + } + if (isset($content['app_header'])) + { + $GLOBALS['egw_info']['flags']['app_header'] = $content['app_header']; + } + if ($GLOBALS['egw_info']['flags']['currentapp'] != 'etemplate') + { + $GLOBALS['egw']->translation->add_app('etemplate'); // some extensions have own texts + } + $id = $this->appsession_id(); + //echo "

unsetting existing egw_info[etemplate] which had keys=".implode(',',array_keys($GLOBALS['egw_info']['etemplate']))."

\n"; + // initialise $GLOBALS['egw_info']['etemplate'], in case there are multiple eTemplates on a page + $GLOBALS['egw_info']['etemplate'] = array( + 'name_forms' => $GLOBALS['egw_info']['etemplate']['name_forms'], + 'validation_errors' => $GLOBALS['egw_info']['etemplate']['validation_errors'], + 'hooked' => $GLOBALS['egw_info']['etemplate']['hooked'], + 'content' => $GLOBALS['egw_info']['etemplate']['content'], + 'hook_content' => $GLOBALS['egw_info']['etemplate']['hook_content'], + 'hook_app' => $GLOBALS['egw_info']['etemplate']['hook_app'], + 'extension_data' => $GLOBALS['egw_info']['etemplate']['extension_data'], + ); + //echo "

hooked=".(int)!!$GLOBALS['egw_info']['etemplate']['hooked'].", content=".(int)!!$GLOBALS['egw_info']['etemplate']['content'].", hook_content=".(int)!!$GLOBALS['egw_info']['etemplate']['hook_content'].", hook_app={$GLOBALS['egw_info']['etemplate']['hook_app']}

\n"; + $this->name_form =& $GLOBALS['egw_info']['etemplate']['name_form']; + $this->name_forms =& $GLOBALS['egw_info']['etemplate']['name_forms']; + if (!is_array($this->name_forms)) $this->name_forms = array(); + + // use different form-names to allows multiple eTemplates in one page, eg. addressbook-view + $this->name_form = 'eTemplate'; + if (in_array($this->name_form,$this->name_forms)) + { + $this->name_form .= 1+count($this->name_forms); + $this->name_vars .= 1+count($this->name_forms); + } + $this->name_forms[] = $this->name_form; + + $GLOBALS['egw_info']['etemplate']['output_mode'] = $output_mode; // let extensions "know" they are run eg. in a popup + $GLOBALS['egw_info']['etemplate']['form_options'] = ''; // might be set in show + $GLOBALS['egw_info']['etemplate']['to_process'] = array(); + + $html = html::form($this->include_java_script(1). + html::input_hidden(array( + 'submit_button' => '', + 'innerWidth' => '', + ),'',False). + $this->show($this->complete_array_merge($content,$changes),$sel_options,$readonlys,$this->name_vars),array( + 'etemplate_exec_id' => $id + ),$this->sitemgr ? '' : '/etemplate/process_exec.php?menuaction='.$method, + '',$this->name_form,$GLOBALS['egw_info']['etemplate']['form_options']. + // dont set the width of popups! + ($output_mode != 0 ? '' : ' onsubmit="this.innerWidth.value=window.innerWidth ? window.innerWidth : document.body.clientWidth;"')); + //echo "to_process="; _debug_array($GLOBALS['egw_info']['etemplate']['to_process']); + + if ($this->sitemgr) + { + $GLOBALS['egw_info']['flags']['java_script'] .= $this->include_java_script(2); + } + elseif (!$this->xslt) + { + $hooked = isset($GLOBALS['egw_info']['etemplate']['content']) ? $GLOBALS['egw_info']['etemplate']['content'] : + $GLOBALS['egw']->template->get_var('phpgw_body'); + + if (!@$GLOBALS['egw_info']['etemplate']['hooked'] && (int) $output_mode != 1 && (int) $output_mode != -1) // not just returning the html { $GLOBALS['egw_info']['flags']['java_script'] .= $this->include_java_script(2); - } - elseif (!$this->xslt) - { - $hooked = isset($GLOBALS['egw_info']['etemplate']['content']) ? $GLOBALS['egw_info']['etemplate']['content'] : - $GLOBALS['egw']->template->get_var('phpgw_body'); - if (!@$GLOBALS['egw_info']['etemplate']['hooked'] && (int) $output_mode != 1 && (int) $output_mode != -1) // not just returning the html + if ($GLOBALS['egw_info']['flags']['currentapp'] != 'etemplate') { - $GLOBALS['egw_info']['flags']['java_script'] .= $this->include_java_script(2); - - if ($GLOBALS['egw_info']['flags']['currentapp'] != 'etemplate') + $css_file = '/etemplate/templates/'.$GLOBALS['egw_info']['server']['template_set'].'/app.css'; + if (!file_exists(EGW_SERVER_ROOT.$css_file)) { - $css_file = '/etemplate/templates/'.$GLOBALS['egw_info']['server']['template_set'].'/app.css'; - if (!file_exists(EGW_SERVER_ROOT.$css_file)) - { - $css_file = '/etemplate/templates/default/app.css'; - } - $GLOBALS['egw_info']['flags']['css'] .= "\n\t\t\n\t\t".''."\n\t\t\n\t\t".''."\n\t\t'."\n"; + echo '
'.html::image('phpgwapi','ajax-loader') . '
'; + } + } + } + echo $GLOBALS['egw_info']['etemplate']['hook_content'].$html; + + if (!$GLOBALS['egw_info']['etemplate']['hooked'] && + (!isset($_GET['menuaction']) || + strpos($_SERVER['PHP_SELF'],'process_exec.php')!==false)) + { + if((int) $output_mode == 2) + { + echo "\n"; + } + $GLOBALS['egw']->common->egw_footer(); } - // saving the etemplate content for other hooked etemplate apps (atm. infolog hooked into addressbook) - $GLOBALS['egw_info']['etemplate']['content'] =& $html; } else { - $hooked = $GLOBALS['egw']->xslttpl->get_var('phpgw'); - $hooked = $hooked['body_data']; - $GLOBALS['egw']->xslttpl->set_var('phpgw',array('java_script' => $GLOBALS['egw_info']['flags']['java_script'].$this->include_java_script(2))); + // need to add some logic here to support popups (output_mode==2) for xslt, but who cares ... + $GLOBALS['egw']->xslttpl->set_var('phpgw',array('body_data' => $html)); } - //echo "

uietemplate::exec($method,...) after show: sitemgr=$this->sitemgr, xslt=$this->xslt, hooked=$hooked, output_mode=$output_mode

\n"; + } + $this->save_appsession($sess = $this->as_array(2) + array( + 'readonlys' => $readonlys, + 'content' => $content, + 'changes' => $changes, + 'sel_options' => $sel_options, + 'preserv' => $preserv, + 'extension_data' => $GLOBALS['egw_info']['etemplate']['extension_data'], + 'to_process' => $GLOBALS['egw_info']['etemplate']['to_process'], + 'java_script' => $GLOBALS['egw_info']['etemplate']['java_script'], + 'java_script_from_flags' => $GLOBALS['egw_info']['flags']['java_script'], + 'java_script_body_tags' => $GLOBALS['egw']->js->body, + 'java_script_files' => $GLOBALS['egw']->js->files, + 'include_xajax' => $GLOBALS['egw_info']['flags']['include_xajax'], + 'dom_enabled' => $GLOBALS['egw_info']['etemplate']['dom_enabled'], + 'hooked' => $hooked ? $hooked : $GLOBALS['egw_info']['etemplate']['hook_content'], + 'hook_app' => $hooked ? $GLOBALS['egw_info']['flags']['currentapp'] : $GLOBALS['egw_info']['etemplate']['hook_app'], + 'app_header' => $GLOBALS['egw_info']['flags']['app_header'], + 'output_mode' => $output_mode != -1 ? $output_mode : 0, + 'session_used' => 0, + 'ignore_validation' => $ignore_validation, + 'method' => $method, + 'name_vars' => $this->name_vars, + ),$id); + //echo "

hooked=".(int)!!$hooked.", content=".(int)!!$GLOBALS['egw_info']['etemplate']['content'].", hook_content=".(int)!!$GLOBALS['egw_info']['etemplate']['hook_content'].", hook_app={$GLOBALS['egw_info']['etemplate']['hook_app']}

\n"; + //echo "

session: "; foreach($sess as $key => $val) echo "$key=$val, "; echo "

\n"; + /* + echo "

total size session data = ".($total=strlen(serialize($sess)))."

\n"; + echo "

shares bigger then 1.0% percent of it:

\n"; + foreach($sess as $key => $val) + { + $len = strlen(is_array($val) ? serialize($val) : $val); + $len .= ' ('.sprintf('%2.1lf',($percent = 100.0 * $len / $total)).'%)'; + if ($percent < 1.0) continue; + echo "

$key: strlen(\$val)=$len

\n"; + if (is_array($val) && $len > 2000) + { + foreach($val as $k => $v) + { + $l = strlen(is_array($v) ? serialize($v) : $v); + $l .= ' ('.sprintf('%2.1lf',($p = 100.0 * $l / $total)).'%)'; + if ($p < 1.0) continue; + echo "

 - {$key}[$k]: strlen(\$v)=$l

\n"; + } + } + } + */ + if ($this->sitemgr || (int) $output_mode == 1 || (int) $output_mode == -1) // return html + { + return $html; + } + } - if (!$this->sitemgr && (int) $output_mode != 1 && (int) $output_mode != -1) // NOT returning html + /** + * 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 $this->name_vars + * @return boolean true if there are not ignored validation errors, false otherwise + */ + function validation_errors($ignore_validation='',$cname=null) + { + if (is_null($cname)) $cname = $this->name_vars; + //echo "

uietemplate::validation_errors('$ignore_validation','$cname') validation_error="; _debug_array($GLOBALS['egw_info']['etemplate']['validation_errors']); + if (!$ignore_validation) return count($GLOBALS['egw_info']['etemplate']['validation_errors']) > 0; + + foreach($GLOBALS['egw_info']['etemplate']['validation_errors'] as $name => $error) + { + 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; + } + + /** + * Makes the necessary adjustments to _POST before it calls the app's method + * + * This function is only to submit forms to, create with exec. + * All eTemplates / forms executed with exec are submited to this function + * via /etemplate/process_exec.php?menuaction=. We cant use the global index.php as + * it would set some constants to etemplate instead of the calling app. + * process_exec then calls process_show for the eTemplate (to adjust the content of the _POST) and + * ExecMethod's the given callback from the app with the content of the form as first argument. + * + * @return mixed false if no sessiondata and $this->sitemgr, else the returnvalue of exec of the method-calls + */ + function process_exec($etemplate_exec_id = null, $submit_button = null, $exec = null, $type = 'regular' ) + { + if(!$etemplate_exec_id) $etemplate_exec_id = $_POST['etemplate_exec_id']; + if(!$submit_button) $submit_button = $_POST['submit_button']; + if(!$exec) $exec = $_POST; + + //echo "process_exec: _POST ="; _debug_array($_POST); + $session_data = $this->get_appsession($etemplate_exec_id); + //echo "

process_exec: session_data ="; _debug_array($session_data); + + if (!$etemplate_exec_id || !is_array($session_data) || count($session_data) < 10) + { + if ($this->sitemgr) return false; + //echo "uitemplate::process_exec() id='$_POST[etemplate_exec_id]' invalid session-data !!!"; _debug_array($_SESSION); + // this prevents an empty screen, if the sessiondata gets lost somehow + $this->location(array('menuaction' => $_GET['menuaction'],'post_empty' => (int)!$_POST)); + } + $this->name_vars = $session_data['name_vars']; + if (isset($submit_button) && !empty($submit_button)) + { + $this->set_array($exec,$submit_button,'pressed'); + } + $content = $exec[$this->name_vars]; + if (!is_array($content)) + { + $content = array(); + } + $this->init($session_data); + $GLOBALS['egw_info']['etemplate']['extension_data'] = $session_data['extension_data']; + $GLOBALS['egw_info']['etemplate']['java_script'] = $session_data['java_script'] || $_POST['java_script']; + $GLOBALS['egw_info']['etemplate']['dom_enabled'] = $session_data['dom_enabled'] || $_POST['dom_enabled']; + //echo "globals[java_script] = '".$GLOBALS['egw_info']['etemplate']['java_script']."', session_data[java_script] = '".$session_data['java_script']."', _POST[java_script] = '".$_POST['java_script']."'\n"; + //echo "process_exec($this->name) content ="; _debug_array($content); + if ($GLOBALS['egw_info']['flags']['currentapp'] != 'etemplate') + { + $GLOBALS['egw']->translation->add_app('etemplate'); // some extensions have own texts + } + $this->process_show($content,$session_data['to_process'],$this->name_vars,$type); + + $GLOBALS['egw_info']['etemplate']['loop'] |= !$this->canceled && $this->button_pressed && + $this->validation_errors($session_data['ignore_validation']); // set by process_show + + // If a tab has an error on it, change to that tab + foreach($GLOBALS['egw_info']['etemplate']['validation_errors'] as $form_name => $msg) + { + $name = $this->template_name($form_name); + if (!$this->get_widget_by_name($name)) + { + foreach($this->get_widgets_by_type('tab') as $widget) + { + foreach(explode('|',$widget['name']) as $tab) + { + if (strpos('.',$tab) === false) $tab = $this->name.'.'.$tab; + $tab_tpl = new etemplate($tab); + if ($tab_tpl->get_widget_by_name($name)) + { + $content[$widget['name']] = $tab; + break 3; + } + } + } + } + } + + //echo "process_exec($this->name) process_show(content) ="; _debug_array($content); + //echo "process_exec($this->name) session_data[changes] ="; _debug_array($session_data['changes']); + $content = $this->complete_array_merge($session_data['changes'],$content); + //echo "process_exec($this->name) merge(changes,content) ="; _debug_array($content); + + if ($GLOBALS['egw_info']['etemplate']['loop']) + { + if ($session_data['hooked'] != '') // set previous phpgw_body if we are called as hook { if (!$this->xslt) { - if (!@$GLOBALS['egw_info']['etemplate']['hooked']) + $GLOBALS['egw_info']['etemplate']['hook_content'] = $session_data['hooked']; + $GLOBALS['egw_info']['flags']['currentapp'] = $GLOBALS['egw_info']['etemplate']['hook_app'] = $session_data['hook_app']; + } + else + { + $GLOBALS['egw']->xslttpl->set_var('phpgw',array('body_data' => $session_data['hooked'])); + } + } + if($session_data['include_xajax']) $GLOBALS['egw_info']['flags']['include_xajax'] = true; + + if (!empty($session_data['app_header'])) + { + $GLOBALS['egw_info']['flags']['app_header'] = $session_data['app_header']; + } + + $GLOBALS['egw_info']['flags']['java_script'] .= $session_data['java_script_from_flags']; + if (!empty($session_data['java_script_body_tags'])) + { + if( !is_object($GLOBALS['egw']->js)) + { + $GLOBALS['egw']->js =& CreateObject('phpgwapi.javascript'); + } + foreach ($session_data['java_script_body_tags'] as $tag => $code) + { + //error_log($GLOBALS['egw']->js->body[$tag]); + $GLOBALS['egw']->js->body[$tag] .= $code; + } + } + if (is_array($session_data['java_script_files'])) + { + if( !is_object($GLOBALS['egw']->js)) + { + $GLOBALS['egw']->js =& CreateObject('phpgwapi.javascript'); + } + $GLOBALS['egw']->js->files = !is_array($GLOBALS['egw']->js->files) ? $session_data['java_script_files'] : + $this->complete_array_merge($GLOBALS['egw']->js->files,$session_data['java_script_files']); + } + + //echo "

process_exec($this->name): loop is set, content=

\n"; _debug_array($content); + return $this->exec($session_data['method'],$session_data['content'],$session_data['sel_options'], + $session_data['readonlys'],$session_data['preserv'],$session_data['output_mode'], + $session_data['ignore_validation'],$content); + } + else + { + //echo "

process_exec($this->name): calling $session_data[method]

\n"; + return ExecMethod($session_data['method'],$this->complete_array_merge($session_data['preserv'],$content)); + } + } + + /** + * process the values transfered with the javascript function values2url + * + * The returned array contains the preserved values overwritten (only!) with the variables named in values2url + * + * @return array/boolean content array or false on error + */ + function process_values2url() + { + //echo "process_exec: _GET ="; _debug_array($_GET); + $session_data = $this->get_appsession($_GET['etemplate_exec_id']); + //echo "

process_exec: session_data ="; _debug_array($session_data); + + if (!$_GET['etemplate_exec_id'] || !is_array($session_data) || count($session_data) < 10) + { + return false; + } + $this->name_vars = $session_data['name_vars']; + $GLOBALS['egw_info']['etemplate']['extension_data'] = $session_data['extension_data']; + + $content = $_GET[$this->name_vars]; + if (!is_array($content)) + { + $content = array(); + } + $this->process_show($content,$session_data['to_process'],$this->name_vars); + + return $this->complete_array_merge($session_data['preserv'],$content); + } + + /** + * creates HTML from an eTemplate + * + * This is done by calling show_cell for each cell in the form. show_cell itself + * calls show recursivly for each included eTemplate. + * You could use it in the UI-layer of an app, just make shure to call process_show !!! + * This is intended as internal function and should NOT be called by new app's direct, + * as it deals with HTML and is so UI-dependent, use exec instead. + * + * @internal + * @param array $content with content for the cells, keys are the names given in the cells/form elements + * @param array $sel_options with options for the selectboxes, keys are the name of the selectbox + * @param array $readonlys with names of cells/form-elements to be not allowed to change + * This is to facilitate complex ACL's which denies access on field-level !!! + * @param string $cname basename of names for form-elements, means index in $_POST + * eg. $cname='cont', element-name = 'name' returned content in $_POST['cont']['name'] + * @param string $show_c name/index for name expansion + * @param string $show_row name/index for name expansion + * @return string the generated HTML + */ + function show($content,$sel_options='',$readonlys='',$cname='',$show_c=0,$show_row=0) + { + if (!$sel_options) + { + $sel_options = array(); + } + // make it globaly availible for show_cell and show_grid, or extensions + $this->sel_options =& $sel_options; + + if (!$readonlys) + { + $readonlys = array(); + } + if (++$this->already_showed > 1) return ''; // prefens infinit self-inclusion + + if (is_int($this->debug) && $this->debug >= 1 || $this->name && $this->debug == $this->name) + { + echo "

etemplate.show($this->name): $cname =\n"; _debug_array($content); + echo "readonlys="; _debug_array($readonlys); + } + if (!is_array($content)) + { + $content = array(); // happens if incl. template has no content + } + // make the content availible as class-var for extensions + $this->content =& $content; + + $html = "\n\n\n

name\">\n\n"; + if (!$GLOBALS['egw_info']['etemplate']['styles_included'][$this->name]) + { + $GLOBALS['egw_info']['etemplate']['styles_included'][$this->name] = True; + $html .= html::style($this->style)."\n\n"; + } + $path = '/'; + foreach ($this->children as $n => $child) + { + $h = $this->show_cell($child,$content,$readonlys,$cname,$show_c,$show_row,$nul,$class,$path.$n); + $html .= $class || $child['align'] ? html::div($h,html::formatOptions(array( + $class, + $child['align'], + ),'class,align')) : $h; + } + return $html."\n
\n\n\n"; + } + + /** + * Get the color of a category + * + * For multiple cats, the first with a color is used + * + * @param int/string $cats multiple comma-separated cat_id's + * @return string + */ + static function cats2color($cats) + { + static $cat2color; + + if (!$cats) return null; + + if (isset($cat2color[$cats])) + { + return $cat2color[$cats]; + } + + foreach(explode(',',$cats) as $cat) + { + if (isset($cat2color[$cat])) + { + return $cat2color[$cat]; + } + if (!is_object($GLOBALS['egw']->categories)) + { + $GLOBALS['egw']->categories = new categories(); + } + $data = unserialize($GLOBALS['egw']->categories->id2name($cat,'data')); + + if (($color = $data['color'])) + { + //echo "

cats2color($cats)=$color

\n"; + return $cat2color[$cats] = $cat2color[$cat] = $color; + } + } + return null; + } + + /** + * creates HTML from an eTemplate + * + * This is done by calling show_cell for each cell in the form. show_cell itself + * calls show recursivly for each included eTemplate. + * You can use it in the UI-layer of an app, just make shure to call process_show !!! + * This is intended as internal function and should NOT be called by new app's direct, + * as it deals with HTML and is so UI-dependent, use exec instead. + * + * @internal + * @param array $grid representing a grid + * @param array $content with content for the cells, keys are the names given in the cells/form elements + * @param array $readonlys with names of cells/form-elements to be not allowed to change + * This is to facilitate complex ACL's which denies access on field-level !!! + * @param string $cname basename of names for form-elements, means index in $_POST + * eg. $cname='cont', element-name = 'name' returned content in $_POST['cont']['name'] + * @param string $show_c name/index for name expansion + * @param string $show_row name/index for name expansion + * @param string $path path in the widget tree + * @return string the generated HTML + */ + private function show_grid(&$grid,$content,$readonlys='',$cname='',$show_c=0,$show_row=0,$path='') + { + if (!$readonlys) + { + $readonlys = array(); + } + if (is_int($this->debug) && $this->debug >= 2 || $grid['name'] && $this->debug == $grid['name'] || + $this->name && $this->debug == $this->name) + { + echo "

etemplate.show_grid($grid[name]): $cname =\n"; _debug_array($content); + } + if (!is_array($content)) + { + $content = array(); // happens if incl. template has no content + } + $content += array( // for var-expansion in names in show_cell + '.c' => $show_c, + '.col' => $this->num2chrs($show_c-1), + '.row' => $show_row + ); + $rows = array(); + + $data = &$grid['data']; + reset($data); + if (isset($data[0])) + { + list(,$opts) = each($data); + } + else + { + $opts = array(); + } + $max_cols = $grid['cols']; + for ($r = 0; $row = 1+$r /*list($row,$cols) = each($data)*/; ++$r) + { + if (!(list($r_key) = each($data))) // no further row + { + if (!(($this->autorepeat_idx($cols['A'],0,$r,$idx,$idx_cname,false,$content) && $idx_cname) || + (substr($cols['A']['type'],1) == 'box' && $this->autorepeat_idx($cols['A'][1],0,$r,$idx,$idx_cname,false,$content) && $idx_cname) || + ($this->autorepeat_idx($cols['B'],1,$r,$idx,$idx_cname,false,$content) && $idx_cname)) || + !$this->isset_array($content,$idx_cname)) + { + break; // no auto-row-repeat + } + } + else + { + $cols = &$data[$r_key]; + list($height,$disabled) = explode(',',$opts["h$row"]); + $class = /*TEST-RB$no_table_tr ? $tr_class :*/ $opts["c$row"]; + } + if ($disabled != '' && $this->check_disabled($disabled,$content)) + { + continue; // row is disabled + } + $rows[".$row"] .= html::formatOptions($height,'height'); + list($cl) = explode(',',$class); + if ($cl == '@' || $cl && strpos($cl,'$') !== false) + { + $cl = $this->expand_name($cl,0,$r,$content['.c'],$content['.row'],$content); + + if (!$cl || preg_match('/^[0-9,]*$/',$cl)) + { + if (($color = $this->cats2color($cl))) { - if((int) $output_mode != 2) + $rows[".$row"] .= ' style="background-color: '.$color.';"'; + } + $cl = 'row'; + } + } + if ($cl == 'nmr' || substr($cl,0,3) == 'row') // allow to have further classes behind row + { + $cl = 'row_'.($nmr_alternate++ & 1 ? 'off' : 'on').substr($cl,3); // alternate color + } + $cl = isset($this->class_conf[$cl]) ? $this->class_conf[$cl] : $cl; + $rows[".$row"] .= html::formatOptions($cl,'class'); + $rows[".$row"] .= html::formatOptions($class,',valign'); + reset ($cols); + $row_data = array(); + for ($c = 0; True /*list($col,$cell) = each($cols)*/; ++$c) + { + $col = $this->num2chrs($c); + if (!(list($c_key) = each($cols))) // no further cols + { + // only check if the max. column-number reached so far is exeeded + // otherwise the rows have a differen number of cells and it saved a lot checks + if ($c >= $max_cols) + { + if (!$this->autorepeat_idx($cell,$c,$r,$idx,$idx_cname,True,$content) || + !$this->isset_array($content,$idx)) { - echo parse_navbar(); + break; // no auto-col-repeat + } + $max_cols = $c+1; + } + } + else + { + $cell = $cols[$c_key]; + list($col_width,$col_disabled) = explode(',',$opts[$col]); + + if (!$cell['height']) // if not set, cell-height = height of row + { + $cell['height'] = $height; + } + if (!$cell['width']) // if not set, cell-width = width of column or table + { + list($col_span) = explode(',',$cell['span']); + if ($col_span == 'all' && !$c) + { + list($cell['width']) = explode(',',$this->size); } else { - echo '

'."\n"; - if ($GLOBALS['egw_info']['user']['apps']['manual']) // adding a manual icon to every popup - { - $manual =& new etemplate('etemplate.popup.manual'); - echo $manual->show(array()); - unset($manual); - echo ''."\n"; - echo '
'.$this->html->image('phpgwapi','ajax-loader') . '
'; - } + $cell['width'] = $col_width; } } - echo $GLOBALS['egw_info']['etemplate']['hook_content'].$html; + } + /*TEST-RB + if ($cell['type'] == 'template' && $cell['onchange']) + { + $cell['tr_class'] = $cl; + }*/ + if ($col_disabled != '' && $this->check_disabled($col_disabled,$content)) + { + continue; // col is disabled + } + $row_data[$col] = $this->show_cell($cell,$content,$readonlys,$cname,$c,$r,$span,$cl,$path.'/'.$r_key.$c_key); - if (!$GLOBALS['egw_info']['etemplate']['hooked'] && - (!isset($_GET['menuaction']) || - strpos($_SERVER['PHP_SELF'],'process_exec.php')!==false)) + if ($row_data[$col] == '' && $this->rows == 1) + { + unset($row_data[$col]); // omit empty/disabled cells if only one row + continue; + } + if (strlen($cell['onclick']) > 1) + { + $onclick = $cell['onclick']; + if (strpos($onclick,'$') !== false || $onclick{0} == '@') { - if((int) $output_mode == 2) - { - echo "
\n"; - } - $GLOBALS['egw']->common->egw_footer(); + $onclick = $this->expand_name($onclick,$c,$r,$content['.c'],$content['.row'],$content); + } + $row_data[".$col"] .= ' onclick="'.$this->js_pseudo_funcs($onclick,$cname).'"' . + ($cell['id'] ? ' id="'.$cell['id'].'"' : ''); + } + $colspan = $span == 'all' ? $this->cols-$c : 0+$span; + if ($colspan > 1) + { + $row_data[".$col"] .= " colspan=\"$colspan\""; + for ($i = 1; $i < $colspan; ++$i,++$c) + { + each($cols); // skip next cell(s) } } else { - // need to add some logic here to support popups (output_mode==2) for xslt, but who cares ... - $GLOBALS['egw']->xslttpl->set_var('phpgw',array('body_data' => $html)); + list($width,$disable) = explode(',',$opts[$col]); + if ($width) // width only once for a non colspan cell + { + $row_data[".$col"] .= " width=\"$width\""; + $opts[$col] = "0,$disable"; + } + } + $row_data[".$col"] .= html::formatOptions($cell['align']?$cell['align']:'left','align'); + // allow to set further attributes in the tablecell, beside the class + if (is_array($cl)) + { + foreach($cl as $attr => $val) + { + if ($attr != 'class' && $val) + { + $row_data['.'.$col] .= ' '.$attr.'="'.$val.'"'; + } + } + $cl = $cl['class']; + } + $cl = $this->expand_name(isset($this->class_conf[$cl]) ? $this->class_conf[$cl] : $cl, + $c,$r,$show_c,$show_row,$content); + // else the class is set twice, in the table and the table-cell, which is not good for borders + if ($cl && $cell['type'] != 'template' && $cell['type'] != 'grid') + { + $row_data[".$col"] .= html::formatOptions($cl,'class'); } } - $this->save_appsession($sess = $this->as_array(2) + array( - 'readonlys' => $readonlys, - 'content' => $content, - 'changes' => $changes, - 'sel_options' => $sel_options, - 'preserv' => $preserv, - 'extension_data' => $GLOBALS['egw_info']['etemplate']['extension_data'], - 'to_process' => $GLOBALS['egw_info']['etemplate']['to_process'], - 'java_script' => $GLOBALS['egw_info']['etemplate']['java_script'], - 'java_script_from_flags' => $GLOBALS['egw_info']['flags']['java_script'], - 'java_script_body_tags' => $GLOBALS['egw']->js->body, - 'java_script_files' => $GLOBALS['egw']->js->files, - 'include_xajax' => $GLOBALS['egw_info']['flags']['include_xajax'], - 'dom_enabled' => $GLOBALS['egw_info']['etemplate']['dom_enabled'], - 'hooked' => $hooked ? $hooked : $GLOBALS['egw_info']['etemplate']['hook_content'], - 'hook_app' => $hooked ? $GLOBALS['egw_info']['flags']['currentapp'] : $GLOBALS['egw_info']['etemplate']['hook_app'], - 'app_header' => $GLOBALS['egw_info']['flags']['app_header'], - 'output_mode' => $output_mode != -1 ? $output_mode : 0, - 'session_used' => 0, - 'ignore_validation' => $ignore_validation, - 'method' => $method, - 'name_vars' => $this->name_vars, - ),$id); -//echo "

hooked=".(int)!!$hooked.", content=".(int)!!$GLOBALS['egw_info']['etemplate']['content'].", hook_content=".(int)!!$GLOBALS['egw_info']['etemplate']['hook_content'].", hook_app={$GLOBALS['egw_info']['etemplate']['hook_app']}

\n"; -//echo "

session: "; foreach($sess as $key => $val) echo "$key=$val, "; echo "

\n"; -/* -echo "

total size session data = ".($total=strlen(serialize($sess)))."

\n"; -echo "

shares bigger then 1.0% percent of it:

\n"; -foreach($sess as $key => $val) -{ - $len = strlen(is_array($val) ? serialize($val) : $val); - $len .= ' ('.sprintf('%2.1lf',($percent = 100.0 * $len / $total)).'%)'; - if ($percent < 1.0) continue; - echo "

$key: strlen(\$val)=$len

\n"; - if (is_array($val) && $len > 2000) - { - foreach($val as $k => $v) - { - $l = strlen(is_array($v) ? serialize($v) : $v); - $l .= ' ('.sprintf('%2.1lf',($p = 100.0 * $l / $total)).'%)'; - if ($p < 1.0) continue; - echo "

 - {$key}[$k]: strlen(\$v)=$l

\n"; + $rows[$row] = $row_data; } + if (!$rows) return ''; + + list($width,$height,,,,,$overflow) = $options = explode(',',$grid['size']); + if ($overflow && $height) + { + $options[1] = ''; // set height in div only + } + $html = html::table($rows,html::formatOptions($options,'width,height,border,class,cellspacing,cellpadding'). + html::formatOptions($grid['span'],',class'). + html::formatOptions($grid['name']?$this->form_name($cname,$grid['name']):'','id')); + + if (!empty($overflow)) { + if (is_numeric($height)) $height .= 'px'; + if (is_numeric($width)) $width .= 'px'; + $div_style=' style="'.($width?"width: $width; ":'').($height ? "height: $height; ":'')."overflow: $overflow;\""; + $html = html::div($html,$div_style); + } + return "\n\n\n$html\n\n"; } -} -*/ - if ($this->sitemgr || (int) $output_mode == 1 || (int) $output_mode == -1) // return html - { - return $html; - } - } + + /** + * 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 + * @return string complete form-name + */ + static function form_name($cname,$name) + { + if(is_object($name)) return ''; - /** - * 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 $this->name_vars - * @return boolean true if there are not ignored validation errors, false otherwise - */ - function validation_errors($ignore_validation='',$cname=null) + $name_parts = explode('[',str_replace(']','',$name)); + if (!empty($cname)) { - if (is_null($cname)) $cname = $this->name_vars; - //echo "

uietemplate::validation_errors('$ignore_validation','$cname') validation_error="; _debug_array($GLOBALS['egw_info']['etemplate']['validation_errors']); - if (!$ignore_validation) return count($GLOBALS['egw_info']['etemplate']['validation_errors']) > 0; - - foreach($GLOBALS['egw_info']['etemplate']['validation_errors'] as $name => $error) - { - 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; + array_unshift($name_parts,$cname); } - - /** - * Makes the necessary adjustments to _POST before it calls the app's method - * - * This function is only to submit forms to, create with exec. - * All eTemplates / forms executed with exec are submited to this function - * via /etemplate/process_exec.php?menuaction=. We cant use the global index.php as - * it would set some constants to etemplate instead of the calling app. - * process_exec then calls process_show for the eTemplate (to adjust the content of the _POST) and - * ExecMethod's the given callback from the app with the content of the form as first argument. - * - * @return mixed false if no sessiondata and $this->sitemgr, else the returnvalue of exec of the method-calls - */ - function process_exec($etemplate_exec_id = null, $submit_button = null, $exec = null, $type = 'regular' ) + $form_name = array_shift($name_parts); + if (count($name_parts)) { - if(!$etemplate_exec_id) $etemplate_exec_id = $_POST['etemplate_exec_id']; - if(!$submit_button) $submit_button = $_POST['submit_button']; - if(!$exec) $exec = $_POST; - - //echo "process_exec: _POST ="; _debug_array($_POST); - $session_data = $this->get_appsession($etemplate_exec_id); - //echo "

process_exec: session_data ="; _debug_array($session_data); - - if (!$etemplate_exec_id || !is_array($session_data) || count($session_data) < 10) - { - if ($this->sitemgr) return false; - //echo "uitemplate::process_exec() id='$_POST[etemplate_exec_id]' invalid session-data !!!"; _debug_array($_SESSION); - // this prevents an empty screen, if the sessiondata gets lost somehow - $this->location(array('menuaction' => $_GET['menuaction'],'post_empty' => (int)!$_POST)); - } - $this->name_vars = $session_data['name_vars']; - if (isset($submit_button) && !empty($submit_button)) - { - $this->set_array($exec,$submit_button,'pressed'); - } - $content = $exec[$this->name_vars]; - if (!is_array($content)) - { - $content = array(); - } - $this->init($session_data); - $GLOBALS['egw_info']['etemplate']['extension_data'] = $session_data['extension_data']; - $GLOBALS['egw_info']['etemplate']['java_script'] = $session_data['java_script'] || $_POST['java_script']; - $GLOBALS['egw_info']['etemplate']['dom_enabled'] = $session_data['dom_enabled'] || $_POST['dom_enabled']; - //echo "globals[java_script] = '".$GLOBALS['egw_info']['etemplate']['java_script']."', session_data[java_script] = '".$session_data['java_script']."', _POST[java_script] = '".$_POST['java_script']."'\n"; - //echo "process_exec($this->name) content ="; _debug_array($content); - if ($GLOBALS['egw_info']['flags']['currentapp'] != 'etemplate') - { - $GLOBALS['egw']->translation->add_app('etemplate'); // some extensions have own texts - } - $this->process_show($content,$session_data['to_process'],$this->name_vars,$type); - - $GLOBALS['egw_info']['etemplate']['loop'] |= !$this->canceled && $this->button_pressed && - $this->validation_errors($session_data['ignore_validation']); // set by process_show - - // If a tab has an error on it, change to that tab - foreach($GLOBALS['egw_info']['etemplate']['validation_errors'] as $form_name => $msg) - { - $name = $this->template_name($form_name); - if (!$this->get_widget_by_name($name)) - { - foreach($this->get_widgets_by_type('tab') as $widget) - { - foreach(explode('|',$widget['name']) as $tab) - { - if (strpos('.',$tab) === false) $tab = $this->name.'.'.$tab; - $tab_tpl = new etemplate($tab); - if ($tab_tpl->get_widget_by_name($name)) - { - $content[$widget['name']] = $tab; - break 3; - } - } - } - } - } - - //echo "process_exec($this->name) process_show(content) ="; _debug_array($content); - //echo "process_exec($this->name) session_data[changes] ="; _debug_array($session_data['changes']); - $content = $this->complete_array_merge($session_data['changes'],$content); - //echo "process_exec($this->name) merge(changes,content) ="; _debug_array($content); - - if ($GLOBALS['egw_info']['etemplate']['loop']) - { - if ($session_data['hooked'] != '') // set previous phpgw_body if we are called as hook - { - if (!$this->xslt) - { - $GLOBALS['egw_info']['etemplate']['hook_content'] = $session_data['hooked']; - $GLOBALS['egw_info']['flags']['currentapp'] = $GLOBALS['egw_info']['etemplate']['hook_app'] = $session_data['hook_app']; - } - else - { - $GLOBALS['egw']->xslttpl->set_var('phpgw',array('body_data' => $session_data['hooked'])); - } - } - if($session_data['include_xajax']) $GLOBALS['egw_info']['flags']['include_xajax'] = true; - - if (!empty($session_data['app_header'])) - { - $GLOBALS['egw_info']['flags']['app_header'] = $session_data['app_header']; - } - - $GLOBALS['egw_info']['flags']['java_script'] .= $session_data['java_script_from_flags']; - if (!empty($session_data['java_script_body_tags'])) - { - if( !is_object($GLOBALS['egw']->js)) - { - $GLOBALS['egw']->js =& CreateObject('phpgwapi.javascript'); - } - foreach ($session_data['java_script_body_tags'] as $tag => $code) - { - //error_log($GLOBALS['egw']->js->body[$tag]); - $GLOBALS['egw']->js->body[$tag] .= $code; - } - } - if (is_array($session_data['java_script_files'])) - { - if( !is_object($GLOBALS['egw']->js)) - { - $GLOBALS['egw']->js =& CreateObject('phpgwapi.javascript'); - } - $GLOBALS['egw']->js->files = !is_array($GLOBALS['egw']->js->files) ? $session_data['java_script_files'] : - $this->complete_array_merge($GLOBALS['egw']->js->files,$session_data['java_script_files']); - } - - //echo "

process_exec($this->name): loop is set, content=

\n"; _debug_array($content); - return $this->exec($session_data['method'],$session_data['content'],$session_data['sel_options'], - $session_data['readonlys'],$session_data['preserv'],$session_data['output_mode'], - $session_data['ignore_validation'],$content); - } - else - { - //echo "

process_exec($this->name): calling $session_data[method]

\n"; - return ExecMethod($session_data['method'],$this->complete_array_merge($session_data['preserv'],$content)); - } + $form_name .= '['.implode('][',$name_parts).']'; } + return $form_name; + } - /** - * process the values transfered with the javascript function values2url - * - * The returned array contains the preserved values overwritten (only!) with the variables named in values2url - * - * @return array/boolean content array or false on error - */ - function process_values2url() + /** + * strip the prefix of a form-element from a form_name + * This function removes the prefix of form_name(). It takes a name like base[basesub1][basesub2][name][sub] + * and gives basesub1[basesub2][name][sub] + * + * @param string form_name + * @return string name without prefix + */ + static private function template_name($form_name) + { + $parts = explode('[',str_replace(']','',$form_name)); + + array_shift($parts); // remove exec + + $name = array_shift($parts); + + if ($parts) $name .= '['.implode('][',$parts).']'; + + return $name; + } + + /** + * generates HTML for one widget (input-field / cell) + * + * calls show to generate included eTemplates. Again only an INTERMAL function. + * + * @internal + * @param array $cell with data of the cell: name, type, ... + * @param array $content with content for the cells, keys are the names given in the cells/form elements + * @param array $readonlys with names of cells/form-elements to be not allowed to change + * This is to facilitate complex ACL's which denies access on field-level !!! + * @param string $cname basename of names for form-elements, means index in $_POST + * eg. $cname='cont', element-name = 'name' returned content in $_POST['cont']['name'] + * @param string $show_c name/index for name expansion + * @param string $show_row name/index for name expansion + * @param string &$span on return number of cells to span or 'all' for the rest (only used for grids) + * @param string &$class on return the css class of the cell, to be set in the tag + * @param string $path path in the widget tree + * @return string the generated HTML + */ + private function show_cell(&$cell,$content,$readonlys,$cname,$show_c,$show_row,&$span,&$class,$path='') + { + if ($this->debug && (is_int($this->debug) && $this->debug >= 3 || $this->debug == $cell['type'])) { - //echo "process_exec: _GET ="; _debug_array($_GET); - $session_data = $this->get_appsession($_GET['etemplate_exec_id']); - //echo "

process_exec: session_data ="; _debug_array($session_data); - - if (!$_GET['etemplate_exec_id'] || !is_array($session_data) || count($session_data) < 10) - { - return false; - } - $this->name_vars = $session_data['name_vars']; - $GLOBALS['egw_info']['etemplate']['extension_data'] = $session_data['extension_data']; - - $content = $_GET[$this->name_vars]; - if (!is_array($content)) - { - $content = array(); - } - $this->process_show($content,$session_data['to_process'],$this->name_vars); - - return $this->complete_array_merge($session_data['preserv'],$content); + echo "

etemplate.show_cell($this->name,name='${cell['name']}',type='${cell['type']}',cname='$cname')

\n"; } + list($span) = explode(',',$cell['span']); // evtl. overriten later for type template - /** - * creates HTML from an eTemplate - * - * This is done by calling show_cell for each cell in the form. show_cell itself - * calls show recursivly for each included eTemplate. - * You could use it in the UI-layer of an app, just make shure to call process_show !!! - * This is intended as internal function and should NOT be called by new app's direct, - * as it deals with HTML and is so UI-dependent, use exec instead. - * - * @internal - * @param array $content with content for the cells, keys are the names given in the cells/form elements - * @param array $sel_options with options for the selectboxes, keys are the name of the selectbox - * @param array $readonlys with names of cells/form-elements to be not allowed to change - * This is to facilitate complex ACL's which denies access on field-level !!! - * @param string $cname basename of names for form-elements, means index in $_POST - * eg. $cname='cont', element-name = 'name' returned content in $_POST['cont']['name'] - * @param string $show_c name/index for name expansion - * @param string $show_row name/index for name expansion - * @return string the generated HTML - */ - function show($content,$sel_options='',$readonlys='',$cname='',$show_c=0,$show_row=0) + if ($cell['name']{0} == '@' && $cell['type'] != 'template') { - if (!$sel_options) - { - $sel_options = array(); - } - // make it globaly availible for show_cell and show_grid, or extensions - $this->sel_options =& $sel_options; - - if (!$readonlys) - { - $readonlys = array(); - } - if (++$this->already_showed > 1) return ''; // prefens infinit self-inclusion - - if (is_int($this->debug) && $this->debug >= 1 || $this->name && $this->debug == $this->name) - { - echo "

etemplate.show($this->name): $cname =\n"; _debug_array($content); - echo "readonlys="; _debug_array($readonlys); - } - if (!is_array($content)) - { - $content = array(); // happens if incl. template has no content - } - // make the content availible as class-var for extensions - $this->content =& $content; - - $html = "\n\n\n

name\">\n\n"; - if (!$GLOBALS['egw_info']['etemplate']['styles_included'][$this->name]) - { - $GLOBALS['egw_info']['etemplate']['styles_included'][$this->name] = True; - $html .= $this->html->style($this->style)."\n\n"; - } - $path = '/'; - foreach ($this->children as $n => $child) - { - $h = $this->show_cell($child,$content,$readonlys,$cname,$show_c,$show_row,$nul,$class,$path.$n); - $html .= $class || $child['align'] ? $this->html->div($h,$this->html->formatOptions(array( - $class, - $child['align'], - ),'class,align')) : $h; - } - return $html."\n
\n\n\n"; + $cell['name'] = $this->get_array($content,$this->expand_name(substr($cell['name'],1), + $show_c,$show_row,$content['.c'],$content['.row'],$content)); } - - /** - * Get the color of a category - * - * For multiple cats, the first with a color is used - * - * @param int/string $cats multiple comma-separated cat_id's - * @return string - */ - function cats2color($cats) + $name = $this->expand_name($cell['name'],$show_c,$show_row,$content['.c'],$content['.row'],$content); + + $form_name = $this->form_name($cname,$name); + + $value = $this->get_array($content,$name); + + $options = ''; + if ($readonly = $cell['readonly'] || @$readonlys[$name] && !is_array($readonlys[$name]) || $readonlys['__ALL__']) { - static $cat2color; - - if (!$cats) return null; - - if (isset($cat2color[$cats])) - { - return $cat2color[$cats]; - } - - foreach(explode(',',$cats) as $cat) - { - if (isset($cat2color[$cat])) - { - return $cat2color[$cat]; - } - if (!is_object($GLOBALS['egw']->categories)) - { - $GLOBALS['egw']->categories = new categories(); - } - $data = unserialize($GLOBALS['egw']->categories->id2name($cat,'data')); - - if (($color = $data['color'])) - { - //echo "

cats2color($cats)=$color

\n"; - return $cat2color[$cats] = $cat2color[$cat] = $color; - } - } - return null; + $options .= ' readonly="readonly"'; } - - /** - * creates HTML from an eTemplate - * - * This is done by calling show_cell for each cell in the form. show_cell itself - * calls show recursivly for each included eTemplate. - * You can use it in the UI-layer of an app, just make shure to call process_show !!! - * This is intended as internal function and should NOT be called by new app's direct, - * as it deals with HTML and is so UI-dependent, use exec instead. - * - * @internal - * @param array $grid representing a grid - * @param array $content with content for the cells, keys are the names given in the cells/form elements - * @param array $readonlys with names of cells/form-elements to be not allowed to change - * This is to facilitate complex ACL's which denies access on field-level !!! - * @param string $cname basename of names for form-elements, means index in $_POST - * eg. $cname='cont', element-name = 'name' returned content in $_POST['cont']['name'] - * @param string $show_c name/index for name expansion - * @param string $show_row name/index for name expansion - * @param string $path path in the widget tree - * @return string the generated HTML - */ - function show_grid(&$grid,$content,$readonlys='',$cname='',$show_c=0,$show_row=0,$path='') + if ((int) $cell['tabindex']) $options .= ' tabindex="'.(int)$cell['tabindex'].'"'; + if ($cell['accesskey']) $options .= ' accesskey="'.html::htmlspecialchars($cell['accesskey']).'"'; + + if (strchr($cell['size'],'$') || $cell['size']{0} == '@') // expand cell['size'] for the button-disabled-check now { - if (!$readonlys) - { - $readonlys = array(); - } - if (is_int($this->debug) && $this->debug >= 2 || $grid['name'] && $this->debug == $grid['name'] || - $this->name && $this->debug == $this->name) - { - echo "

etemplate.show_grid($grid[name]): $cname =\n"; _debug_array($content); - } - if (!is_array($content)) - { - $content = array(); // happens if incl. template has no content - } - $content += array( // for var-expansion in names in show_cell - '.c' => $show_c, - '.col' => $this->num2chrs($show_c-1), - '.row' => $show_row - ); - $rows = array(); - - $data = &$grid['data']; - reset($data); - if (isset($data[0])) - { - list(,$opts) = each($data); - } - else - { - $opts = array(); - } - $max_cols = $grid['cols']; - for ($r = 0; $row = 1+$r /*list($row,$cols) = each($data)*/; ++$r) - { - if (!(list($r_key) = each($data))) // no further row - { - if (!(($this->autorepeat_idx($cols['A'],0,$r,$idx,$idx_cname,false,$content) && $idx_cname) || - (substr($cols['A']['type'],1) == 'box' && $this->autorepeat_idx($cols['A'][1],0,$r,$idx,$idx_cname,false,$content) && $idx_cname) || - ($this->autorepeat_idx($cols['B'],1,$r,$idx,$idx_cname,false,$content) && $idx_cname)) || - !$this->isset_array($content,$idx_cname)) - { - break; // no auto-row-repeat - } - } - else - { - $cols = &$data[$r_key]; - list($height,$disabled) = explode(',',$opts["h$row"]); - $class = /*TEST-RB$no_table_tr ? $tr_class :*/ $opts["c$row"]; - } - if ($disabled != '' && $this->check_disabled($disabled,$content)) - { - continue; // row is disabled - } - $rows[".$row"] .= $this->html->formatOptions($height,'height'); - list($cl) = explode(',',$class); - if ($cl == '@' || $cl && strpos($cl,'$') !== false) - { - $cl = $this->expand_name($cl,0,$r,$content['.c'],$content['.row'],$content); - - if (!$cl || preg_match('/^[0-9,]*$/',$cl)) - { - if (($color = $this->cats2color($cl))) - { - $rows[".$row"] .= ' style="background-color: '.$color.';"'; - } - $cl = 'row'; - } - } - if ($cl == 'nmr' || substr($cl,0,3) == 'row') // allow to have further classes behind row - { - $cl = 'row_'.($nmr_alternate++ & 1 ? 'off' : 'on').substr($cl,3); // alternate color - } - $cl = isset($this->class_conf[$cl]) ? $this->class_conf[$cl] : $cl; - $rows[".$row"] .= $this->html->formatOptions($cl,'class'); - $rows[".$row"] .= $this->html->formatOptions($class,',valign'); - reset ($cols); - $row_data = array(); - for ($c = 0; True /*list($col,$cell) = each($cols)*/; ++$c) - { - $col = $this->num2chrs($c); - if (!(list($c_key) = each($cols))) // no further cols - { - // only check if the max. column-number reached so far is exeeded - // otherwise the rows have a differen number of cells and it saved a lot checks - if ($c >= $max_cols) - { - if (!$this->autorepeat_idx($cell,$c,$r,$idx,$idx_cname,True,$content) || - !$this->isset_array($content,$idx)) - { - break; // no auto-col-repeat - } - $max_cols = $c+1; - } - } - else - { - $cell = $cols[$c_key]; - list($col_width,$col_disabled) = explode(',',$opts[$col]); - - if (!$cell['height']) // if not set, cell-height = height of row - { - $cell['height'] = $height; - } - if (!$cell['width']) // if not set, cell-width = width of column or table - { - list($col_span) = explode(',',$cell['span']); - if ($col_span == 'all' && !$c) - { - list($cell['width']) = explode(',',$this->size); - } - else - { - $cell['width'] = $col_width; - } - } - } - /*TEST-RB - if ($cell['type'] == 'template' && $cell['onchange']) - { - $cell['tr_class'] = $cl; - }*/ - if ($col_disabled != '' && $this->check_disabled($col_disabled,$content)) - { - continue; // col is disabled - } - $row_data[$col] = $this->show_cell($cell,$content,$readonlys,$cname,$c,$r,$span,$cl,$path.'/'.$r_key.$c_key); - - if ($row_data[$col] == '' && $this->rows == 1) - { - unset($row_data[$col]); // omit empty/disabled cells if only one row - continue; - } - if (strlen($cell['onclick']) > 1) - { - $onclick = $cell['onclick']; - if (strpos($onclick,'$') !== false || $onclick{0} == '@') - { - $onclick = $this->expand_name($onclick,$c,$r,$content['.c'],$content['.row'],$content); - } - $row_data[".$col"] .= ' onclick="'.$this->js_pseudo_funcs($onclick,$cname).'"' . - ($cell['id'] ? ' id="'.$cell['id'].'"' : ''); - } - $colspan = $span == 'all' ? $this->cols-$c : 0+$span; - if ($colspan > 1) - { - $row_data[".$col"] .= " colspan=\"$colspan\""; - for ($i = 1; $i < $colspan; ++$i,++$c) - { - each($cols); // skip next cell(s) - } - } - else - { - list($width,$disable) = explode(',',$opts[$col]); - if ($width) // width only once for a non colspan cell - { - $row_data[".$col"] .= " width=\"$width\""; - $opts[$col] = "0,$disable"; - } - } - $row_data[".$col"] .= $this->html->formatOptions($cell['align']?$cell['align']:'left','align'); - // allow to set further attributes in the tablecell, beside the class - if (is_array($cl)) - { - foreach($cl as $attr => $val) - { - if ($attr != 'class' && $val) - { - $row_data['.'.$col] .= ' '.$attr.'="'.$val.'"'; - } - } - $cl = $cl['class']; - } - $cl = $this->expand_name(isset($this->class_conf[$cl]) ? $this->class_conf[$cl] : $cl, - $c,$r,$show_c,$show_row,$content); - // else the class is set twice, in the table and the table-cell, which is not good for borders - if ($cl && $cell['type'] != 'template' && $cell['type'] != 'grid') - { - $row_data[".$col"] .= $this->html->formatOptions($cl,'class'); - } - } - $rows[$row] = $row_data; - } - if (!$rows) return ''; - - list($width,$height,,,,,$overflow) = $options = explode(',',$grid['size']); - if ($overflow && $height) - { - $options[1] = ''; // set height in div only - } - $html = $this->html->table($rows,$this->html->formatOptions($options,'width,height,border,class,cellspacing,cellpadding'). - $this->html->formatOptions($grid['span'],',class'). - $this->html->formatOptions($grid['name']?$this->form_name($cname,$grid['name']):'','id')); - - if (!empty($overflow)) { - if (is_numeric($height)) $height .= 'px'; - if (is_numeric($width)) $width .= 'px'; - $div_style=' style="'.($width?"width: $width; ":'').($height ? "height: $height; ":'')."overflow: $overflow;\""; - $html = $this->html->div($html,$div_style); - } - return "\n\n\n$html\n\n"; + $cell['size'] = $this->expand_name($cell['size'],$show_c,$show_row,$content['.c'],$content['.row'],$content); } - - /** - * 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 - * @return string complete form-name - */ - function form_name($cname,$name) + if ($cell['disabled'] && $readonlys[$name] !== false || $readonly && ($cell['type'] == 'button' || $cell['type'] == 'buttononly') && strpos($cell['size'],',')===false) { - if(is_object($name)) return ''; - - $name_parts = explode('[',str_replace(']','',$name)); - if (!empty($cname)) - { - array_unshift($name_parts,$cname); + if ($this->rows == 1) { + return ''; // if only one row omit cell } - $form_name = array_shift($name_parts); - if (count($name_parts)) - { - $form_name .= '['.implode('][',$name_parts).']'; - } - return $form_name; + $cell = $this->empty_cell('label','',array('span' => $cell['span'])); // show nothing (keep the css class!) + $value = ''; } + $extra_label = True; - /** - * strip the prefix of a form-element from a form_name - * This function removes the prefix of form_name(). It takes a name like base[basesub1][basesub2][name][sub] - * and gives basesub1[basesub2][name][sub] - * - * @param string form_name - * @return string name without prefix - */ - function template_name($form_name) + // the while loop allows to build extensions from other extensions + // please note: only the first extension's post_process function is called !!! + list($type,$sub_type) = explode('-',$cell['type']); + while ((!$this->types[$cell['type']] || !empty($sub_type)) && $this->haveExtension($type,'pre_process')) { - $parts = explode('[',str_replace(']','',$form_name)); - - array_shift($parts); // remove exec - - $name = array_shift($parts); - - if ($parts) $name .= '['.implode('][',$parts).']'; - - return $name; - } - - /** - * generates HTML for one widget (input-field / cell) - * - * calls show to generate included eTemplates. Again only an INTERMAL function. - * - * @internal - * @param array $cell with data of the cell: name, type, ... - * @param array $content with content for the cells, keys are the names given in the cells/form elements - * @param array $readonlys with names of cells/form-elements to be not allowed to change - * This is to facilitate complex ACL's which denies access on field-level !!! - * @param string $cname basename of names for form-elements, means index in $_POST - * eg. $cname='cont', element-name = 'name' returned content in $_POST['cont']['name'] - * @param string $show_c name/index for name expansion - * @param string $show_row name/index for name expansion - * @param string &$span on return number of cells to span or 'all' for the rest (only used for grids) - * @param string &$class on return the css class of the cell, to be set in the tag - * @param string $path path in the widget tree - * @return string the generated HTML - */ - function show_cell(&$cell,$content,$readonlys,$cname,$show_c,$show_row,&$span,&$class,$path='') - { - if ($this->debug && (is_int($this->debug) && $this->debug >= 3 || $this->debug == $cell['type'])) - { - echo "

etemplate.show_cell($this->name,name='${cell['name']}',type='${cell['type']}',cname='$cname')

\n"; - } - list($span) = explode(',',$cell['span']); // evtl. overriten later for type template - - if ($cell['name']{0} == '@' && $cell['type'] != 'template') - { - $cell['name'] = $this->get_array($content,$this->expand_name(substr($cell['name'],1), - $show_c,$show_row,$content['.c'],$content['.row'],$content)); - } - $name = $this->expand_name($cell['name'],$show_c,$show_row,$content['.c'],$content['.row'],$content); - - $form_name = $this->form_name($cname,$name); - - $value = $this->get_array($content,$name); - - $options = ''; - if ($readonly = $cell['readonly'] || @$readonlys[$name] && !is_array($readonlys[$name]) || $readonlys['__ALL__']) - { - $options .= ' readonly="readonly"'; - } - if ((int) $cell['tabindex']) $options .= ' tabindex="'.(int)$cell['tabindex'].'"'; - if ($cell['accesskey']) $options .= ' accesskey="'.$this->html->htmlspecialchars($cell['accesskey']).'"'; - - if (strchr($cell['size'],'$') || $cell['size']{0} == '@') // expand cell['size'] for the button-disabled-check now + //echo "

pre_process($cell[name]/$cell[type])

\n"; + if (strchr($cell['size'],'$') || $cell['size']{0} == '@') { $cell['size'] = $this->expand_name($cell['size'],$show_c,$show_row,$content['.c'],$content['.row'],$content); } - if ($cell['disabled'] && $readonlys[$name] !== false || $readonly && ($cell['type'] == 'button' || $cell['type'] == 'buttononly') && strpos($cell['size'],',')===false) - { - if ($this->rows == 1) { - return ''; // if only one row omit cell - } - $cell = $this->empty_cell('label','',array('span' => $cell['span'])); // show nothing (keep the css class!) - $value = ''; - } - $extra_label = True; + if (!$ext_type) $ext_type = $type; + $extra_label = $this->extensionPreProcess($type,$form_name,$value,$cell,$readonlys[$name]); - // the while loop allows to build extensions from other extensions - // please note: only the first extension's post_process function is called !!! - list($type,$sub_type) = explode('-',$cell['type']); - while ((!$this->types[$cell['type']] || !empty($sub_type)) && $this->haveExtension($type,'pre_process')) - { - //echo "

pre_process($cell[name]/$cell[type])

\n"; - if (strchr($cell['size'],'$') || $cell['size']{0} == '@') - { - $cell['size'] = $this->expand_name($cell['size'],$show_c,$show_row,$content['.c'],$content['.row'],$content); - } - if (!$ext_type) $ext_type = $type; - $extra_label = $this->extensionPreProcess($type,$form_name,$value,$cell,$readonlys[$name]); + $readonly = $readonly || $cell['readonly']; // might be set by extension + $this->set_array($content,$name,$value); - $readonly = $readonly || $cell['readonly']; // might be set by extension - $this->set_array($content,$name,$value); + if ($cell['type'] == $type.'-'.$sub_type) break; // stop if no further type-change - if ($cell['type'] == $type.'-'.$sub_type) break; // stop if no further type-change + list($type,$sub_type) = explode('-',$cell['type']); + } + list(,$class) = explode(',',$cell['span']); // might be set by extension + if (strchr($class,'$') || $class{0} == '@') + { + $class = $this->expand_name($class,$show_c,$show_row,$content['.c'],$content['.row'],$content); + } + if ($cell['needed'] && !in_array($cell['type'],array('button','buttononly'))) + { + $class .= ' inputRequired'; + } + $cell_options = $cell['size']; + if (strchr($cell_options,'$') || $cell_options{0} == '@') + { + $cell_options = $this->expand_name($cell_options,$show_c,$show_row,$content['.c'],$content['.row'],$content); + } + $label = $cell['label']; + if (strchr($label,'$') || $label{0} == '@') + { + $label = $this->expand_name($label,$show_c,$show_row,$content['.c'],$content['.row'],$content); + } + $help = $cell['help']; + if (strchr($help,'$') || $help{0} == '@') + { + $no_lang_on_help = true; + $help = $this->expand_name($help,$show_c,$show_row,$content['.c'],$content['.row'],$content); + } + $blur = $cell['blur']{0} == '@' ? $this->get_array($content,substr($cell['blur'],1)) : + (strlen($cell['blur']) <= 1 ? $cell['blur'] : lang($cell['blur'])); - list($type,$sub_type) = explode('-',$cell['type']); - } - list(,$class) = explode(',',$cell['span']); // might be set by extension - if (strchr($class,'$') || $class{0} == '@') + if ($this->java_script()) + { + if ($blur) { - $class = $this->expand_name($class,$show_c,$show_row,$content['.c'],$content['.row'],$content); + if (empty($value)) + { + $value = $blur; + } + $onFocus .= "if(this.value=='".addslashes(html::htmlspecialchars($blur))."') this.value='';"; + $onBlur .= "if(this.value=='') this.value='".addslashes(html::htmlspecialchars($blur))."';"; } - if ($cell['needed'] && !in_array($cell['type'],array('button','buttononly'))) + if ($help) { - $class .= ' inputRequired'; + if ((int)$cell['no_lang'] < 2 && !$no_lang_on_help) + { + $help = lang($help); + } + if (($use_tooltip_for_help = strpos($help,'<') !== false && strip_tags($help) != $help)) // helptext is html => use a tooltip + { + $options .= html::tooltip($help); + } + else // "regular" help-text in the statusline + { + $onFocus .= "self.status='".addslashes(html::htmlspecialchars($help))."'; return true;"; + $onBlur .= "self.status=''; return true;"; + if (in_array($cell['type'],array('button','buttononly','file'))) // for button additionally when mouse over button + { + $options .= " onMouseOver=\"self.status='".addslashes(html::htmlspecialchars($help))."'; return true;\""; + $options .= " onMouseOut=\"self.status=''; return true;\""; + } + } } - $cell_options = $cell['size']; - if (strchr($cell_options,'$') || $cell_options{0} == '@') + if ($onBlur) { - $cell_options = $this->expand_name($cell_options,$show_c,$show_row,$content['.c'],$content['.row'],$content); + $options .= " onFocus=\"$onFocus\" onBlur=\"$onBlur\""; } - $label = $cell['label']; - if (strchr($label,'$') || $label{0} == '@') + if ($cell['onchange'] && !($cell['type'] == 'button' || $cell['type'] == 'buttononly')) { - $label = $this->expand_name($label,$show_c,$show_row,$content['.c'],$content['.row'],$content); + if (strchr($cell['onchange'],'$') || $cell['onchange']{0} == '@') + { + $cell['onchange'] = $this->expand_name($cell['onchange'],$show_c,$show_row,$content['.c'],$content['.row'],$content); + } + $options .= ' onChange="'.($cell['onchange'] == '1' ? 'this.form.submit();' : $this->js_pseudo_funcs($cell['onchange'],$cname)).'"'; } - $help = $cell['help']; - if (strchr($help,'$') || $help{0} == '@') - { - $no_lang_on_help = true; - $help = $this->expand_name($help,$show_c,$show_row,$content['.c'],$content['.row'],$content); - } - $blur = $cell['blur']{0} == '@' ? $this->get_array($content,substr($cell['blur'],1)) : - (strlen($cell['blur']) <= 1 ? $cell['blur'] : lang($cell['blur'])); - - if ($this->java_script()) - { - if ($blur) - { - if (empty($value)) - { - $value = $blur; - } - $onFocus .= "if(this.value=='".addslashes($this->html->htmlspecialchars($blur))."') this.value='';"; - $onBlur .= "if(this.value=='') this.value='".addslashes($this->html->htmlspecialchars($blur))."';"; - } - if ($help) - { - if ((int)$cell['no_lang'] < 2 && !$no_lang_on_help) - { - $help = lang($help); - } - if (($use_tooltip_for_help = strpos($help,'<') !== false && strip_tags($help) != $help)) // helptext is html => use a tooltip - { - $options .= $this->html->tooltip($help); - } - else // "regular" help-text in the statusline - { - $onFocus .= "self.status='".addslashes($this->html->htmlspecialchars($help))."'; return true;"; - $onBlur .= "self.status=''; return true;"; - if (in_array($cell['type'],array('button','buttononly','file'))) // for button additionally when mouse over button - { - $options .= " onMouseOver=\"self.status='".addslashes($this->html->htmlspecialchars($help))."'; return true;\""; - $options .= " onMouseOut=\"self.status=''; return true;\""; - } - } - } - if ($onBlur) - { - $options .= " onFocus=\"$onFocus\" onBlur=\"$onBlur\""; - } - if ($cell['onchange'] && !($cell['type'] == 'button' || $cell['type'] == 'buttononly')) - { - if (strchr($cell['onchange'],'$') || $cell['onchange']{0} == '@') - { - $cell['onchange'] = $this->expand_name($cell['onchange'],$show_c,$show_row,$content['.c'],$content['.row'],$content); - } - $options .= ' onChange="'.($cell['onchange'] == '1' ? 'this.form.submit();' : $this->js_pseudo_funcs($cell['onchange'],$cname)).'"'; - } - } - if ($form_name != '') - { - $options = 'id="'.($cell['id'] ? $cell['id'] : $form_name).'" '.$options; - } - - switch ($type) - { - case 'label': // size: [b[old]][i[talic]],[link],[activate_links],[label_for],[link_target],[link_popup_size],[link_title] - if (is_array($value)) - break; - list($style,$extra_link,$activate_links,$label_for,$extra_link_target,$extra_link_popup,$extra_link_title) = explode(',',$cell_options,7); - $value = strlen($value) > 1 && !$cell['no_lang'] ? lang($value) : $value; - $value = nl2br($this->html->htmlspecialchars($value)); - if ($activate_links) $value = $this->html->activate_links($value); - if ($value != '' && $style && strpos($style,'b')!==false) $value = $this->html->bold($value); - if ($value != '' && $style && strpos($style,'i')!==false) $value = $this->html->italic($value); - // if the label has a name, use it as id in a span, to allow addressing it via javascript - $html .= ($name ? '' : '').$value.($name ? '' : ''); - if ($help) - { - $class = array( - 'class' => $class, - 'onmouseover' => "self.status='".addslashes($this->html->htmlspecialchars($help))."'; return true;", - 'onmouseout' => "self.status=''; return true;", - ); - } - break; - case 'html': // size: [link],[link_target],[link_popup_size],[link_title],[activate_links] - list($extra_link,$extra_link_target,$extra_link_popup,$extra_link_title,$activate_links) = explode(',',$cell_options); - if ($activate_links) $value = $this->html->activate_links($value); - $html .= $value; - break; - case 'int': // size: [min],[max],[len],[precission/sprint format] - case 'float': - list($min,$max,$cell_options,$pre) = explode(',',$cell_options); - if ($cell_options == '' && !$readonly) - { - $cell_options = $cell['type'] == 'int' ? 5 : 8; - } - if (($type == 'float' || !is_numeric($pre)) && $value && $pre) - { - $value = str_replace(array(' ',','),array('','.'),$value); - $value = is_numeric($pre) ? round($value,$pre) : sprintf($pre,$value); - } - $cell_options .= ',,'.($cell['type'] == 'int' ? '/^-?[0-9]*$/' : '/^-?[0-9]*[,.]?[0-9]*$/'); - // fall-through - case 'passwd' : - case 'text': // size: [length][,maxLength[,preg]] - $cell_opts = explode(',',$cell_options,3); - if ($readonly && (int)$cell_opts[0] >= 0) - { - $html .= strlen($value) ? $this->html->bold($this->html->htmlspecialchars($value)) : ''; - } - else - { - if ($cell_opts[0] < 0) $cell_opts[0] = abs($cell_opts[0]); - $html .= $this->html->input($form_name,$value,$type == 'passwd' ? 'password' : '', - $options.$this->html->formatOptions($cell_opts,'SIZE,MAXLENGTH')); - - if (!$readonly) - { - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( - 'type' => $cell['type'], - 'maxlength' => $cell_opts[1], - 'needed' => $cell['needed'], - 'preg' => $cell_opts[2], - 'min' => $min, // int and float only - 'max' => $max, - ); - } - } - unset($cell_opts); - break; - case 'textarea': // Multiline Text Input, size: [rows][,cols] - if ($readonly && !$cell_options) - { - $html .= '
'.nl2br($this->html->htmlspecialchars($value))."
\n"; - } - else - { - $html .= $this->html->textarea($form_name,$value, - $options.$this->html->formatOptions($cell_options,'ROWS,COLS')); - } - if (!$readonly) - { - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( - 'type' => $cell['type'], - 'needed' => $cell['needed'], - ); - } - break; - case 'htmlarea': // Multiline formatted Text Input, size: {simple|extended|advanced},height,width,toolbar-expanded,upload-path - list($mode,$height,$width,$toolbar,$baseref,$convertnl) = explode(',',$cell_options); - - if ($convertnl == 1) $value = nl2br($value); - - if (!$readonly) - { - $mode = $mode ? $mode : 'simple'; - $height = $height ? $height : '400px'; - $width = $width ? $width : '100%'; - $fckoptions = array( - 'toolbar_expanded' => $toolbar, - ); - $html .= $this->html->fckEditor($form_name,$value,$mode,$fckoptions,$height,$width,$baseref); - - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( - 'type' => $cell['type'], - 'needed' => $cell['needed'], - ); - } - else - { - $html .= $this->html->div($this->html->activate_links($value),'style="overflow: auto; width='. $width. '; height='. $height. '"'); - } - break; - case 'checkbox': - $set_val = 1; $unset_val = 0; - if (!empty($cell_options)) - { - list($set_val,$unset_val,$ro_true,$ro_false) = explode(',',$cell_options); - if (!$set_val && !$unset_val) $set_val = 1; - $value = $value == $set_val; - } - if ($readonly) - { - if (count(explode(',',$cell_options)) < 3) - { - $ro_true = 'x'; - $ro_false = ''; - } - if (!$value && $ro_false == 'disable') return ''; - - $html .= $value ? $this->html->bold($ro_true) : $ro_false; - } - else - { - if ($value) $options .= ' checked="checked"'; - - if (($multiple = substr($cell['name'],-2) == '[]')) - { - // add the set_val to the id to make it unique - $options = str_replace('id="'.$form_name,'id="'.substr($form_name,0,-2)."[$set_val]",$options); - } - $html .= $this->html->input($form_name,$set_val,'checkbox',$options); - - if ($multiple) $form_name = $this->form_name($cname,substr($cell['name'],0,-2)); - - if (!isset($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) - { - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( - 'type' => $cell['type'], - 'unset_value' => $unset_val, - 'multiple' => $multiple, - ); - } - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name]['values'][] = $set_val; - if (!$multiple) unset($set_val); // otherwise it will be added to the label - } - break; - case 'radio': // size: value if checked, readonly set, readonly unset - list($set_val,$ro_true,$ro_false) = explode(',',$cell_options); - $set_val = $this->expand_name($set_val,$show_c,$show_row,$content['.c'],$content['.row'],$content); - - if ($value == $set_val) - { - $options .= ' checked="checked"'; - } - // add the set_val to the id to make it unique - $options = str_replace('id="'.$form_name,'id="'.$form_name."[$set_val]",$options); - - if ($readonly) - { - if (!$ro_true && !$ro_false) $ro_true = 'x'; - $html .= $value == $set_val ? $this->html->bold($ro_true) : $ro_false; - } - else - { - $html .= $this->html->input($form_name,$set_val,'RADIO',$options); - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $cell['type']; - } - break; - case 'button': - case 'buttononly': - case 'cancel': // cancel button - list($app) = explode('.',$this->name); - list($img,$ro_img) = explode(',',$cell_options); - $title = strlen($label) <= 1 || $cell['no_lang'] ? $label : lang($label); - if ($cell['onclick'] && - ($onclick = $this->expand_name($cell['onclick'],$show_c,$show_row,$content['.c'],$content['.row'],$content))) - { - $onclick = $this->js_pseudo_funcs($onclick,$cname); - } - unset($cell['onclick']); // otherwise the grid will handle it - if ($this->java_script() && ($cell['onchange'] != '' || $img && !$readonly) && !$cell['needed']) // use a link instead of a button - { - $onclick = ($onclick ? preg_replace('/^return(.*);$/','if (\\1) ',$onclick) : ''). - (((string)$cell['onchange'] === '1' || $img) ? - "return submitit($this->name_form,'".addslashes($form_name)."');" : $cell['onchange']).'; return false;'; - - if (!$this->html->netscape4 && substr($img,-1) == '%' && is_numeric($percent = substr($img,0,-1))) - { - $html .= $this->html->progressbar($percent,$title,'onclick="'.$onclick.'" '.$options); - } - else - { - $html .= '' . - ($img ? $this->html->image($app,$img,$title,'border="0"') : $title) . ''; - } - } - else - { - if (!empty($img)) - { - $options .= ' title="'.$title.'"'; - } - if ($cell['onchange'] && $cell['onchange'] != 1) - { - $onclick = ($onclick ? preg_replace('/^return(.*);$/','if (\\1) ',$onclick) : '').$cell['onchange']; - } - $html .= !$readonly ? $this->html->submit_button($form_name,$label,$onclick, - strlen($label) <= 1 || $cell['no_lang'],$options,$img,$app,$type == 'buttononly' ? 'button' : 'submit') : - $this->html->image($app,$ro_img); - } - $extra_label = False; - if (!$readonly && $type != 'buttononly') // input button, are never submitted back! - { - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $cell['type']; - if (strlen($name>0)) { - if ($name == 'cancel' || stripos($name,'[cancel]') !== false) - { - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = 'cancel'; - } - } - } - break; - case 'hrule': - $html .= $this->html->hr($cell_options); - break; - case 'grid': - if ($readonly && !$readonlys['__ALL__']) - { - if (!is_array($readonlys)) $readonlys = array(); - $set_readonlys_all = $readonlys['__ALL__'] = True; - } - if ($name != '') - { - $cname .= $cname == '' ? $name : '['.str_replace('[','][',str_replace(']','',$name)).']'; - } - $html .= $this->show_grid($cell,$name ? $value : $content,$readonlys,$cname,$show_c,$show_row,$path); - if ($set_readonlys_all) unset($readonlys['__ALL__']); - break; - case 'template': // size: index in content-array (if not full content is past further on) - if (is_object($cell['name'])) - { - $cell['obj'] = &$cell['name']; - unset($cell['name']); - $cell['name'] = 'was Object'; - echo "

Object in Name in tpl '$this->name': "; _debug_array($grid); - } - $obj_read = 'already loaded'; - if (is_array($cell['obj'])) - { - $obj =& new etemplate(); - $obj->init($cell['obj']); - $cell['obj'] =& $obj; - unset($obj); - } - if (!is_object($cell['obj'])) - { - if ($cell['name']{0} == '@') - { - $cell['obj'] = $this->get_array($content,substr($cell['name'],1)); - $obj_read = is_object($cell['obj']) ? 'obj from content' : 'obj read, obj-name from content'; - if (!is_object($cell['obj'])) - { - $cell['obj'] =& new etemplate($cell['obj'],$this->as_array()); - } - } - else - { $obj_read = 'obj read'; - $cell['obj'] =& new etemplate($name,$this->as_array()); - } - } - if (is_int($this->debug) && $this->debug >= 3 || $this->debug == $cell['type']) - { - echo "

show_cell::template(tpl=$this->name,name=$cell[name]): $obj_read, readonly=$readonly

\n"; - } - if ($this->autorepeat_idx($cell,$show_c,$show_row,$idx,$idx_cname,false,$content) || $cell_options != '') - { - if ($span == '' && isset($content[$idx]['span'])) - { // this allows a colspan in autorepeated cells like the editor - list($span) = explode(',',$content[$idx]['span']); - if ($span == 'all') - { - $span = 1 + $content['cols'] - $show_c; - } - } - $readonlys = $this->get_array($readonlys,$idx); - $content = $this->get_array($content,$idx); - if ($idx_cname != '') - { - $cname .= $cname == '' ? $idx_cname : '['.str_replace('[','][',str_replace(']','',$idx_cname)).']'; - } - //echo "

show_cell-autorepeat($name,$show_c,$show_row,cname='$cname',idx='$idx',idx_cname='$idx_cname',span='$span'): content ="; _debug_array($content); - } - if ($readonly && !$readonlys['__ALL__']) - { - if (!is_array($readonlys)) $readonlys = array(); - $set_readonlys_all = $readonlys['__ALL__'] = True; - } - // propagate our onclick handler to embeded templates, if they dont have their own - if (!isset($cell['obj']->onclick_handler)) $cell['obj']->onclick_handler = $this->onclick_handler; - if ($cell['obj']->no_onclick) - { - $cell['obj']->onclick_proxy = $this->onclick_proxy ? $this->onclick_proxy : $this->name.':'.$this->version.':'.$path; - } - // propagate the CSS class to the template - if ($class) - { - $grid_size = array_pad(explode(',',$cell['obj']->size),4,''); - $grid_size[3] = ($grid_size[3] ? $grid_size[3].' ' : '') . $class; - $cell['obj']->size = implode(',',$grid_size); - } - $html = $cell['obj']->show($content,$this->sel_options,$readonlys,$cname,$show_c,$show_row); - - if ($set_readonlys_all) unset($readonlys['__ALL__']); - break; - case 'select': // size:[linesOnMultiselect|emptyLabel,extraStyleMulitselect] - $sels = array(); - list($multiple,$extraStyleMultiselect) = explode(',',$cell_options,2); - if (!empty($multiple) && 0+$multiple <= 0) - { - $sels[''] = $multiple < 0 ? 'all' : $multiple; - // extra-option: no_lang=0 gets translated later and no_lang=1 gets translated too (now), only no_lang>1 gets not translated - if ((int)$cell['no_lang'] == 1) - { - $sels[''] = substr($sels[''],-3) == '...' ? lang(substr($sels[''],0,-3)).'...' : lang($sels['']); - } - $multiple = 0; - } - $sels += $this->_sel_options($cell,$name,$content); - - if ($multiple && !is_array($value)) $value = explode(',',$value); - if ($readonly || $cell['noprint']) - { - foreach($multiple || is_array($value) ? $value : array($value) as $val) - { - if (is_array($sels[$val])) - { - $option_label = $sels[$val]['label']; - $option_title = $sels[$val]['title']; - } - else - { - $option_label = $sels[$val]; - $option_title = ''; - } - if (!$cell['no_lang']) $option_label = lang($option_label); - - if ($html) $html .= "
\n"; - - if ($option_title) - { - $html .= ''.$this->html->htmlspecialchars($option_label).''; - } - else - { - $html .= $this->html->htmlspecialchars($option_label); - } - } - } - if (!$readonly) - { - if ($cell['noprint']) - { - $html = ''.$html.''; - $options .= ' class="noPrint"'; - } - if ($multiple && is_numeric($multiple)) // eg. "3+" would give a regular multiselectbox - { - $html .= $this->html->checkbox_multiselect($form_name.($multiple > 1 ? '[]' : ''),$value,$sels, - $cell['no_lang'],$options,$multiple,$multiple{0}!=='0',$extraStyleMultiselect); - } - else - { - $html .= $this->html->select($form_name.($multiple > 1 ? '[]' : ''),$value,$sels, - $cell['no_lang'],$options,$multiple); - } - if (!isset($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) - { - // fix for optgroup's - $options=array(); - foreach($sels as $key => $val) - { - # we want the key anyway, even if this allowes more values than wanted (the name/key of the optgroup if there is one, - # the keys of the arrays in case you have key/value pair(s) as value for the value of your option ). - $options[$key]=$key; - if (is_array($val)) - { - foreach(array_keys($val) as $key2) - { - $options[$key2]=$key2; - } - } - } - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( - 'type' => $cell['type'], - 'needed' => $cell['needed'], - 'allowed' => array_keys($options), - 'multiple'=> $multiple, - ); - } - } - break; - case 'image': // size: [link],[link_target],[imagemap],[link_popup],[id] - $image = $value != '' ? $value : $name; - list($app,$img) = explode('/',$image,2); - if (!$app || !$img || !is_dir(EGW_SERVER_ROOT.'/'.$app) || strpos($img,'/')!==false) - { - $img = $image; - list($app) = explode('.',$this->name); - } - if (!$readonly) - { - list($extra_link,$extra_link_target,$imagemap,$extra_link_popup,$id) = explode(',',$cell['size']); - } - $html .= $this->html->image($app,$img,strlen($label) > 1 && !$cell['no_lang'] ? lang($label) : $label, - 'border="0"'.($imagemap?' usemap="'.$this->html->htmlspecialchars($imagemap).'"':''). - ($id || $value ? ' id="'.($id ? $id : $name).'"' : '')); - $extra_label = False; - break; - case 'file': // size: size of the filename field - if (!$readonly) - { - if ((int) $cell_options) $options .= ' size="'.(int)$cell_options.'"'; - $html .= $this->html->input_hidden($path_name = str_replace($name,$name.'_path',$form_name),'.'); - $html .= $this->html->input($form_name,'','file',$options); - $GLOBALS['egw_info']['etemplate']['form_options'] = - "enctype=\"multipart/form-data\" onsubmit=\"set_element2(this,'$path_name','$form_name')\""; - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $cell['type']; - } - break; - case 'vbox': - case 'hbox': - case 'groupbox': - case 'box': - $rows = array(); - $box_row = 1; - $box_col = 'A'; - $box_anz = 0; - list($num,$orient,,,$keep_empty) = explode(',',$cell_options); - if (!$orient) $orient = $type == 'hbox' ? 'horizontal' : ($type == 'box' ? false : 'vertical'); - for ($n = 1; $n <= (int) $num; ++$n) - { - $child = $cell[$n]; // first param is a var_param now! - $h = $this->show_cell($child,$content,$readonlys,$cname,$show_c,$show_row,$nul,$cl,$path.'/'.$n); - if ($h != '' && $h != ' ' || $keep_empty) - { - if ($orient != 'horizontal') - { - $box_row = $n; - } - else - { - $box_col = $this->num2chrs($n); - } - if (!$orient) - { - $html .= $cl ? $this->html->div($h," class=\"$cl\"") : $h; - } - else - { - $rows[$box_row][$box_col] = $html = $h; - } - $box_anz++; - if ($cell[$n]['align']) - { - $rows[$box_row]['.'.$box_col] = $this->html->formatOptions($child['align'],'align'); - $sub_cell_has_align = true; - } - if (strlen($child['onclick']) > 1) - { - $rows[$box_row]['.'.$box_col] .= ' onclick="'.$this->js_pseudo_funcs($child['onclick'],$cname).'"'. - ($child['id'] ? ' id="'.$child['id'].'"' : ''); - } - // allow to set further attributes in the tablecell, beside the class - if (is_array($cl)) - { - foreach($cl as $attr => $val) - { - if ($attr != 'class' && $val) - { - $rows[$box_row]['.'.$box_col] .= ' '.$attr.'="'.$val.'"'; - } - } - $cl = $cl['class']; - } - $box_item_class = $this->expand_name(isset($this->class_conf[$cl]) ? $this->class_conf[$cl] : $cl, - $show_c,$show_row,$content['.c'],$content['.row'],$content); - $rows[$box_row]['.'.$box_col] .= $this->html->formatOptions($box_item_class,'class'); - } - } - if ($box_anz > 1 && $orient) // a single cell is NOT placed into a table - { - $html = $this->html->table($rows,$this->html->formatOptions($cell_options,',,cellpadding,cellspacing'). - ($type != 'groupbox' ? $this->html->formatOptions($class,'class'). - ($cell['name'] ? ' id="'.$form_name.'"' : '') : ''). - ($cell['align'] && $orient != 'horizontal' || $sub_cell_has_align ? ' width="100%"' : '')); // alignment only works if table has full width - if ($type != 'groupbox') $class = ''; // otherwise we create an extra div - } - // put the class of the box-cell, into the the class of this cell - elseif ($box_item_class && $box_anz == 1) - { - $class = ($class ? $class . ' ' : '') . $box_item_class; - } - if ($type == 'groupbox') - { - if (strlen($label) > 1 && $cell['label'] == $label) - { - $label = lang($label); - } - $html = $this->html->fieldset($html,$label,($cell['name'] ? ' id="'.$form_name.'"' : ''). - ($class ? ' class="'.$class.'"' : '')); - $class = ''; // otherwise we create an extra div - } - elseif (!$orient) - { - $html = $this->html->div($html,$this->html->formatOptions(array( - $cell['height'], - $cell['width'], - $class, - $cell['name'] ? $form_name : '', - ),'height,width,class,id')). ($html ? '' : ''); - $class = ''; // otherwise we create an extra div - } - if ($box_anz > 1) // small docu in the html-source - { - $html = "\n\n\n\n".$html."\n\n\n\n"; - } - $extra_label = False; - break; - case 'deck': - for ($n = 1; $n <= $cell_options && (empty($value) || $value != $cell[$n]['name']); ++$n) ; - if ($n > $cell_options) - { - $value = $cell[1]['name']; - } - if ($s_width = $cell['width']) - { - $s_width = "width: $s_width".(substr($s_width,-1) != '%' ? 'px' : '').';'; - } - if ($s_height = $cell['height']) - { - $s_height = "height: $s_height".(substr($s_height,-1) != '%' ? 'px' : '').';'; - } - $html = $this->html->input_hidden($form_name,$value); - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $cell['type']; - - for ($n = 1; $n <= $cell_options; ++$n) - { - $child = $cell[$n]; // first param is a var_param now! - $html .= $this->html->div($this->show_cell($child,$content,$readonlys,$cname,$show_c, - $show_row,$nul,$cl,$path.'/'.$n),$this->html->formatOptions(array( - 'display: '.($value == $child['name'] ? 'inline' : 'none').';', - $child['name'] - ),'style,id')); - } - break; - default: - if ($ext_type && $this->haveExtension($ext_type,'render')) - { - $html .= $this->extensionRender($ext_type,$form_name,$value,$cell,$readonly); - } - else - { - $html .= "unknown type '$cell[type]'"; - } - break; - } - // extension-processing need to be after all other and only with diff. name - if ($ext_type && !$readonly && $this->haveExtension($ext_type,'post_process')) - { // unset it first, if it is already set, to be after the other widgets of the ext. - $to_process = 'ext-'.$ext_type; - if (is_array($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) - { - $to_process = $GLOBALS['egw_info']['etemplate']['to_process'][$form_name]; - $to_process['type'] = 'ext-'.$ext_type; - } - unset($GLOBALS['egw_info']['etemplate']['to_process'][$form_name]); - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $to_process; - } - // save blur-value to strip it in process_exec - if (!empty($blur) && isset($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) - { - if (!is_array($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) - { - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( - 'type' => $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] - ); - } - $GLOBALS['egw_info']['etemplate']['to_process'][$form_name]['blur'] = $blur; - } - if ($extra_label && ($label != '' || $html == '')) - { - if (strlen($label) > 1 && !($cell['no_lang'] && $cell['label'] != $label || (int)$cell['no_lang'] == 2)) - { - $label = lang($label); - } - $accesskey = false; - if (($accesskey = $label && strpos($label,'&')!==false) && $accesskey[1] != ' ' && $form_name != '' && - (($pos = strpos($accesskey,';')) === false || $pos > 5)) - { - $label = str_replace('&'.$accesskey[1],''.$accesskey[1].'',$label); - $accesskey = $accesskey[1]; - } - if ($label && !$readonly && ($accesskey || $label_for || $type != 'label' && $cell['name'])) - { - $label = $this->html->label($label,$label_for ? $this->form_name($cname,$label_for) : - $form_name.($set_val?"[$set_val]":''),$accesskey); - } - if ($type == 'radio' || $type == 'checkbox' || $label && strpos($label,'%s')!==false) // default for radio is label after the button - { - $html = strpos($label,'%s')!==false ? str_replace('%s',$html,$label) : $html.' '.$label; - } - elseif (($html = $label . ' ' . $html) == ' ') - { - $html = ' '; - } - } - if ($extra_link && (($extra_link = $this->expand_name($extra_link,$show_c,$show_row,$content['.c'],$content['.row'],$content)))) - { - $options = $help ? ' onmouseover="self.status=\''.addslashes($this->html->htmlspecialchars($help)).'\'; return true;"' . - ' onmouseout="self.status=\'\'; return true;"' : ''; - - if ($extra_link_target && (($extra_link_target = $this->expand_name($extra_link_target,$show_c,$show_row,$content['.c'],$content['.row'],$content)))) - { - $options .= ' target="'.addslashes($extra_link_target).'"'; - } - if ($extra_link_popup && (($extra_link_popup = $this->expand_name($extra_link_popup,$show_c,$show_row,$content['.c'],$content['.row'],$content)))) - { - list($w,$h) = explode('x',$extra_link_popup); - $options .= ' onclick="window.open(this,this.target,\'width='.(int)$w.',height='.(int)$h.',location=no,menubar=no,toolbar=no,scrollbars=yes,status=yes\'); return false;"'; - } - if ($extra_link_title) - { - $options .= ' title="'.addslashes($extra_link_title).'"'; - } - return $this->html->a_href($html,$extra_link,'',$options); - } - // if necessary show validation-error behind field - if (isset($GLOBALS['egw_info']['etemplate']['validation_errors'][$form_name])) - { - $html .= ' '.$GLOBALS['egw_info']['etemplate']['validation_errors'][$form_name].''; - } - // generate an extra div, if we have an onclick handler and NO children or it's an extension - //echo "

$this->name($this->onclick_handler:$this->no_onclick:$this->onclick_proxy): $cell[type]/$cell[name]

\n"; - if ($this->onclick_handler && !isset($this->widgets_with_children[$cell['type']])) - { - $handler = str_replace('%p',$this->no_onclick ? $this->onclick_proxy : $this->name.':'.$this->version.':'.$path, - $this->onclick_handler); - if ($type == 'button' || $type == 'buttononly' || !$label) // add something to click on - { - $html = (substr($html,-1) == "\n" ? substr($html,0,-1) : $html).' '; - } - return $this->html->div($html,' ondblclick="'.$handler.'"','clickWidgetToEdit'); - } - return $html; + } + if ($form_name != '') + { + $options = 'id="'.($cell['id'] ? $cell['id'] : $form_name).'" '.$options; } - /** - * Retrive options for selectboxes and similar widgets (eg. the tree) - * - * @param array $cell - * @param string $name - * @param array $content=array(); - * @return array - */ - function _sel_options($cell,$name,$content=array()) + switch ($type) { - $sels = array(); - - if (!empty($cell['sel_options'])) - { - if (!is_array($cell['sel_options'])) + case 'label': // size: [b[old]][i[talic]],[link],[activate_links],[label_for],[link_target],[link_popup_size],[link_title] + if (is_array($value)) + break; + list($style,$extra_link,$activate_links,$label_for,$extra_link_target,$extra_link_popup,$extra_link_title) = explode(',',$cell_options,7); + $value = strlen($value) > 1 && !$cell['no_lang'] ? lang($value) : $value; + $value = nl2br(html::htmlspecialchars($value)); + if ($activate_links) $value = html::activate_links($value); + if ($value != '' && $style && strpos($style,'b')!==false) $value = html::bold($value); + if ($value != '' && $style && strpos($style,'i')!==false) $value = html::italic($value); + // if the label has a name, use it as id in a span, to allow addressing it via javascript + $html .= ($name ? '' : '').$value.($name ? '' : ''); + if ($help) { - $opts = explode(',',$cell['sel_options']); - while (list(,$opt) = each($opts)) + $class = array( + 'class' => $class, + 'onmouseover' => "self.status='".addslashes(html::htmlspecialchars($help))."'; return true;", + 'onmouseout' => "self.status=''; return true;", + ); + } + break; + case 'html': // size: [link],[link_target],[link_popup_size],[link_title],[activate_links] + list($extra_link,$extra_link_target,$extra_link_popup,$extra_link_title,$activate_links) = explode(',',$cell_options); + if ($activate_links) $value = html::activate_links($value); + $html .= $value; + break; + case 'int': // size: [min],[max],[len],[precission/sprint format] + case 'float': + list($min,$max,$cell_options,$pre) = explode(',',$cell_options); + if ($cell_options == '' && !$readonly) + { + $cell_options = $cell['type'] == 'int' ? 5 : 8; + } + if (($type == 'float' || !is_numeric($pre)) && $value && $pre) + { + $value = str_replace(array(' ',','),array('','.'),$value); + $value = is_numeric($pre) ? round($value,$pre) : sprintf($pre,$value); + } + $cell_options .= ',,'.($cell['type'] == 'int' ? '/^-?[0-9]*$/' : '/^-?[0-9]*[,.]?[0-9]*$/'); + // fall-through + case 'passwd' : + case 'text': // size: [length][,maxLength[,preg]] + $cell_opts = explode(',',$cell_options,3); + if ($readonly && (int)$cell_opts[0] >= 0) + { + $html .= strlen($value) ? html::bold(html::htmlspecialchars($value)) : ''; + } + else + { + if ($cell_opts[0] < 0) $cell_opts[0] = abs($cell_opts[0]); + $html .= html::input($form_name,$value,$type == 'passwd' ? 'password' : '', + $options.html::formatOptions($cell_opts,'SIZE,MAXLENGTH')); + + if (!$readonly) { - list($k,$v) = explode('=',$opt); - $sels[$k] = $v; + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( + 'type' => $cell['type'], + 'maxlength' => $cell_opts[1], + 'needed' => $cell['needed'], + 'preg' => $cell_opts[2], + 'min' => $min, // int and float only + 'max' => $max, + ); + } + } + unset($cell_opts); + break; + case 'textarea': // Multiline Text Input, size: [rows][,cols] + if ($readonly && !$cell_options) + { + $html .= '
'.nl2br(html::htmlspecialchars($value))."
\n"; + } + else + { + $html .= html::textarea($form_name,$value, + $options.html::formatOptions($cell_options,'ROWS,COLS')); + } + if (!$readonly) + { + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( + 'type' => $cell['type'], + 'needed' => $cell['needed'], + ); + } + break; + case 'htmlarea': // Multiline formatted Text Input, size: {simple|extended|advanced},height,width,toolbar-expanded,upload-path + list($mode,$height,$width,$toolbar,$baseref,$convertnl) = explode(',',$cell_options); + + if ($convertnl == 1) $value = nl2br($value); + + if (!$readonly) + { + $mode = $mode ? $mode : 'simple'; + $height = $height ? $height : '400px'; + $width = $width ? $width : '100%'; + $fckoptions = array( + 'toolbar_expanded' => $toolbar, + ); + $html .= html::fckEditor($form_name,$value,$mode,$fckoptions,$height,$width,$baseref); + + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( + 'type' => $cell['type'], + 'needed' => $cell['needed'], + ); + } + else + { + $html .= html::div(html::activate_links($value),'style="overflow: auto; width='. $width. '; height='. $height. '"'); + } + break; + case 'checkbox': + $set_val = 1; $unset_val = 0; + if (!empty($cell_options)) + { + list($set_val,$unset_val,$ro_true,$ro_false) = explode(',',$cell_options); + if (!$set_val && !$unset_val) $set_val = 1; + $value = $value == $set_val; + } + if ($readonly) + { + if (count(explode(',',$cell_options)) < 3) + { + $ro_true = 'x'; + $ro_false = ''; + } + if (!$value && $ro_false == 'disable') return ''; + + $html .= $value ? html::bold($ro_true) : $ro_false; + } + else + { + if ($value) $options .= ' checked="checked"'; + + if (($multiple = substr($cell['name'],-2) == '[]')) + { + // add the set_val to the id to make it unique + $options = str_replace('id="'.$form_name,'id="'.substr($form_name,0,-2)."[$set_val]",$options); + } + $html .= html::input($form_name,$set_val,'checkbox',$options); + + if ($multiple) $form_name = $this->form_name($cname,substr($cell['name'],0,-2)); + + if (!isset($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) + { + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( + 'type' => $cell['type'], + 'unset_value' => $unset_val, + 'multiple' => $multiple, + ); + } + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name]['values'][] = $set_val; + if (!$multiple) unset($set_val); // otherwise it will be added to the label + } + break; + case 'radio': // size: value if checked, readonly set, readonly unset + list($set_val,$ro_true,$ro_false) = explode(',',$cell_options); + $set_val = $this->expand_name($set_val,$show_c,$show_row,$content['.c'],$content['.row'],$content); + + if ($value == $set_val) + { + $options .= ' checked="checked"'; + } + // add the set_val to the id to make it unique + $options = str_replace('id="'.$form_name,'id="'.$form_name."[$set_val]",$options); + + if ($readonly) + { + if (!$ro_true && !$ro_false) $ro_true = 'x'; + $html .= $value == $set_val ? html::bold($ro_true) : $ro_false; + } + else + { + $html .= html::input($form_name,$set_val,'RADIO',$options); + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $cell['type']; + } + break; + case 'button': + case 'buttononly': + case 'cancel': // cancel button + list($app) = explode('.',$this->name); + list($img,$ro_img) = explode(',',$cell_options); + $title = strlen($label) <= 1 || $cell['no_lang'] ? $label : lang($label); + if ($cell['onclick'] && + ($onclick = $this->expand_name($cell['onclick'],$show_c,$show_row,$content['.c'],$content['.row'],$content))) + { + $onclick = $this->js_pseudo_funcs($onclick,$cname); + } + unset($cell['onclick']); // otherwise the grid will handle it + if ($this->java_script() && ($cell['onchange'] != '' || $img && !$readonly) && !$cell['needed']) // use a link instead of a button + { + $onclick = ($onclick ? preg_replace('/^return(.*);$/','if (\\1) ',$onclick) : ''). + (((string)$cell['onchange'] === '1' || $img) ? + "return submitit($this->name_form,'".addslashes($form_name)."');" : $cell['onchange']).'; return false;'; + + if (!html::$netscape4 && substr($img,-1) == '%' && is_numeric($percent = substr($img,0,-1))) + { + $html .= html::progressbar($percent,$title,'onclick="'.$onclick.'" '.$options); + } + else + { + $html .= '' . + ($img ? html::image($app,$img,$title,'border="0"') : $title) . ''; } } else { - $sels += $cell['sel_options']; + if (!empty($img)) + { + $options .= ' title="'.$title.'"'; + } + if ($cell['onchange'] && $cell['onchange'] != 1) + { + $onclick = ($onclick ? preg_replace('/^return(.*);$/','if (\\1) ',$onclick) : '').$cell['onchange']; + } + $html .= !$readonly ? html::submit_button($form_name,$label,$onclick, + strlen($label) <= 1 || $cell['no_lang'],$options,$img,$app,$type == 'buttononly' ? 'button' : 'submit') : + html::image($app,$ro_img); } - } - if (isset($this->sel_options[$name]) && is_array($this->sel_options[$name])) + $extra_label = False; + if (!$readonly && $type != 'buttononly') // input button, are never submitted back! + { + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $cell['type']; + if (strlen($name>0)) { + if ($name == 'cancel' || stripos($name,'[cancel]') !== false) + { + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = 'cancel'; + } + } + } + break; + case 'hrule': + $html .= html::hr($cell_options); + break; + case 'grid': + if ($readonly && !$readonlys['__ALL__']) + { + if (!is_array($readonlys)) $readonlys = array(); + $set_readonlys_all = $readonlys['__ALL__'] = True; + } + if ($name != '') + { + $cname .= $cname == '' ? $name : '['.str_replace('[','][',str_replace(']','',$name)).']'; + } + $html .= $this->show_grid($cell,$name ? $value : $content,$readonlys,$cname,$show_c,$show_row,$path); + if ($set_readonlys_all) unset($readonlys['__ALL__']); + break; + case 'template': // size: index in content-array (if not full content is past further on) + if (is_object($cell['name'])) + { + $cell['obj'] = &$cell['name']; + unset($cell['name']); + $cell['name'] = 'was Object'; + echo "

Object in Name in tpl '$this->name': "; _debug_array($grid); + } + $obj_read = 'already loaded'; + if (is_array($cell['obj'])) + { + $obj =& new etemplate(); + $obj->init($cell['obj']); + $cell['obj'] =& $obj; + unset($obj); + } + if (!is_object($cell['obj'])) + { + if ($cell['name']{0} == '@') + { + $cell['obj'] = $this->get_array($content,substr($cell['name'],1)); + $obj_read = is_object($cell['obj']) ? 'obj from content' : 'obj read, obj-name from content'; + if (!is_object($cell['obj'])) + { + $cell['obj'] =& new etemplate($cell['obj'],$this->as_array()); + } + } + else + { $obj_read = 'obj read'; + $cell['obj'] =& new etemplate($name,$this->as_array()); + } + } + if (is_int($this->debug) && $this->debug >= 3 || $this->debug == $cell['type']) + { + echo "

show_cell::template(tpl=$this->name,name=$cell[name]): $obj_read, readonly=$readonly

\n"; + } + if ($this->autorepeat_idx($cell,$show_c,$show_row,$idx,$idx_cname,false,$content) || $cell_options != '') + { + if ($span == '' && isset($content[$idx]['span'])) + { // this allows a colspan in autorepeated cells like the editor + list($span) = explode(',',$content[$idx]['span']); + if ($span == 'all') + { + $span = 1 + $content['cols'] - $show_c; + } + } + $readonlys = $this->get_array($readonlys,$idx); + $content = $this->get_array($content,$idx); + if ($idx_cname != '') + { + $cname .= $cname == '' ? $idx_cname : '['.str_replace('[','][',str_replace(']','',$idx_cname)).']'; + } + //echo "

show_cell-autorepeat($name,$show_c,$show_row,cname='$cname',idx='$idx',idx_cname='$idx_cname',span='$span'): content ="; _debug_array($content); + } + if ($readonly && !$readonlys['__ALL__']) + { + if (!is_array($readonlys)) $readonlys = array(); + $set_readonlys_all = $readonlys['__ALL__'] = True; + } + // propagate our onclick handler to embeded templates, if they dont have their own + if (!isset($cell['obj']->onclick_handler)) $cell['obj']->onclick_handler = $this->onclick_handler; + if ($cell['obj']->no_onclick) + { + $cell['obj']->onclick_proxy = $this->onclick_proxy ? $this->onclick_proxy : $this->name.':'.$this->version.':'.$path; + } + // propagate the CSS class to the template + if ($class) + { + $grid_size = array_pad(explode(',',$cell['obj']->size),4,''); + $grid_size[3] = ($grid_size[3] ? $grid_size[3].' ' : '') . $class; + $cell['obj']->size = implode(',',$grid_size); + } + $html = $cell['obj']->show($content,$this->sel_options,$readonlys,$cname,$show_c,$show_row); + + if ($set_readonlys_all) unset($readonlys['__ALL__']); + break; + case 'select': // size:[linesOnMultiselect|emptyLabel,extraStyleMulitselect] + $sels = array(); + list($multiple,$extraStyleMultiselect) = explode(',',$cell_options,2); + if (!empty($multiple) && 0+$multiple <= 0) + { + $sels[''] = $multiple < 0 ? 'all' : $multiple; + // extra-option: no_lang=0 gets translated later and no_lang=1 gets translated too (now), only no_lang>1 gets not translated + if ((int)$cell['no_lang'] == 1) + { + $sels[''] = substr($sels[''],-3) == '...' ? lang(substr($sels[''],0,-3)).'...' : lang($sels['']); + } + $multiple = 0; + } + $sels += $this->_sel_options($cell,$name,$content); + + if ($multiple && !is_array($value)) $value = explode(',',$value); + if ($readonly || $cell['noprint']) + { + foreach($multiple || is_array($value) ? $value : array($value) as $val) + { + if (is_array($sels[$val])) + { + $option_label = $sels[$val]['label']; + $option_title = $sels[$val]['title']; + } + else + { + $option_label = $sels[$val]; + $option_title = ''; + } + if (!$cell['no_lang']) $option_label = lang($option_label); + + if ($html) $html .= "
\n"; + + if ($option_title) + { + $html .= ''.html::htmlspecialchars($option_label).''; + } + else + { + $html .= html::htmlspecialchars($option_label); + } + } + } + if (!$readonly) + { + if ($cell['noprint']) + { + $html = ''.$html.''; + $options .= ' class="noPrint"'; + } + if ($multiple && is_numeric($multiple)) // eg. "3+" would give a regular multiselectbox + { + $html .= html::checkbox_multiselect($form_name.($multiple > 1 ? '[]' : ''),$value,$sels, + $cell['no_lang'],$options,$multiple,$multiple{0}!=='0',$extraStyleMultiselect); + } + else + { + $html .= html::select($form_name.($multiple > 1 ? '[]' : ''),$value,$sels, + $cell['no_lang'],$options,$multiple); + } + if (!isset($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) + { + // fix for optgroup's + $options=array(); + foreach($sels as $key => $val) + { + # we want the key anyway, even if this allowes more values than wanted (the name/key of the optgroup if there is one, + # the keys of the arrays in case you have key/value pair(s) as value for the value of your option ). + $options[$key]=$key; + if (is_array($val)) + { + foreach(array_keys($val) as $key2) + { + $options[$key2]=$key2; + } + } + } + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( + 'type' => $cell['type'], + 'needed' => $cell['needed'], + 'allowed' => array_keys($options), + 'multiple'=> $multiple, + ); + } + } + break; + case 'image': // size: [link],[link_target],[imagemap],[link_popup],[id] + $image = $value != '' ? $value : $name; + list($app,$img) = explode('/',$image,2); + if (!$app || !$img || !is_dir(EGW_SERVER_ROOT.'/'.$app) || strpos($img,'/')!==false) + { + $img = $image; + list($app) = explode('.',$this->name); + } + if (!$readonly) + { + list($extra_link,$extra_link_target,$imagemap,$extra_link_popup,$id) = explode(',',$cell['size']); + } + $html .= html::image($app,$img,strlen($label) > 1 && !$cell['no_lang'] ? lang($label) : $label, + 'border="0"'.($imagemap?' usemap="'.html::htmlspecialchars($imagemap).'"':''). + ($id || $value ? ' id="'.($id ? $id : $name).'"' : '')); + $extra_label = False; + break; + case 'file': // size: size of the filename field + if (!$readonly) + { + if ((int) $cell_options) $options .= ' size="'.(int)$cell_options.'"'; + $html .= html::input_hidden($path_name = str_replace($name,$name.'_path',$form_name),'.'); + $html .= html::input($form_name,'','file',$options); + $GLOBALS['egw_info']['etemplate']['form_options'] = + "enctype=\"multipart/form-data\" onsubmit=\"set_element2(this,'$path_name','$form_name')\""; + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $cell['type']; + } + break; + case 'vbox': + case 'hbox': + case 'groupbox': + case 'box': + $rows = array(); + $box_row = 1; + $box_col = 'A'; + $box_anz = 0; + list($num,$orient,,,$keep_empty) = explode(',',$cell_options); + if (!$orient) $orient = $type == 'hbox' ? 'horizontal' : ($type == 'box' ? false : 'vertical'); + for ($n = 1; $n <= (int) $num; ++$n) + { + $child = $cell[$n]; // first param is a var_param now! + $h = $this->show_cell($child,$content,$readonlys,$cname,$show_c,$show_row,$nul,$cl,$path.'/'.$n); + if ($h != '' && $h != ' ' || $keep_empty) + { + if ($orient != 'horizontal') + { + $box_row = $n; + } + else + { + $box_col = $this->num2chrs($n); + } + if (!$orient) + { + $html .= $cl ? html::div($h," class=\"$cl\"") : $h; + } + else + { + $rows[$box_row][$box_col] = $html = $h; + } + $box_anz++; + if ($cell[$n]['align']) + { + $rows[$box_row]['.'.$box_col] = html::formatOptions($child['align'],'align'); + $sub_cell_has_align = true; + } + if (strlen($child['onclick']) > 1) + { + $rows[$box_row]['.'.$box_col] .= ' onclick="'.$this->js_pseudo_funcs($child['onclick'],$cname).'"'. + ($child['id'] ? ' id="'.$child['id'].'"' : ''); + } + // allow to set further attributes in the tablecell, beside the class + if (is_array($cl)) + { + foreach($cl as $attr => $val) + { + if ($attr != 'class' && $val) + { + $rows[$box_row]['.'.$box_col] .= ' '.$attr.'="'.$val.'"'; + } + } + $cl = $cl['class']; + } + $box_item_class = $this->expand_name(isset($this->class_conf[$cl]) ? $this->class_conf[$cl] : $cl, + $show_c,$show_row,$content['.c'],$content['.row'],$content); + $rows[$box_row]['.'.$box_col] .= html::formatOptions($box_item_class,'class'); + } + } + if ($box_anz > 1 && $orient) // a single cell is NOT placed into a table + { + $html = html::table($rows,html::formatOptions($cell_options,',,cellpadding,cellspacing'). + ($type != 'groupbox' ? html::formatOptions($class,'class'). + ($cell['name'] ? ' id="'.$form_name.'"' : '') : ''). + ($cell['align'] && $orient != 'horizontal' || $sub_cell_has_align ? ' width="100%"' : '')); // alignment only works if table has full width + if ($type != 'groupbox') $class = ''; // otherwise we create an extra div + } + // put the class of the box-cell, into the the class of this cell + elseif ($box_item_class && $box_anz == 1) + { + $class = ($class ? $class . ' ' : '') . $box_item_class; + } + if ($type == 'groupbox') + { + if (strlen($label) > 1 && $cell['label'] == $label) + { + $label = lang($label); + } + $html = html::fieldset($html,$label,($cell['name'] ? ' id="'.$form_name.'"' : ''). + ($class ? ' class="'.$class.'"' : '')); + $class = ''; // otherwise we create an extra div + } + elseif (!$orient) + { + $html = html::div($html,html::formatOptions(array( + $cell['height'], + $cell['width'], + $class, + $cell['name'] ? $form_name : '', + ),'height,width,class,id')). ($html ? '' : ''); + $class = ''; // otherwise we create an extra div + } + if ($box_anz > 1) // small docu in the html-source + { + $html = "\n\n\n\n".$html."\n\n\n\n"; + } + $extra_label = False; + break; + case 'deck': + for ($n = 1; $n <= $cell_options && (empty($value) || $value != $cell[$n]['name']); ++$n) ; + if ($n > $cell_options) + { + $value = $cell[1]['name']; + } + if ($s_width = $cell['width']) + { + $s_width = "width: $s_width".(substr($s_width,-1) != '%' ? 'px' : '').';'; + } + if ($s_height = $cell['height']) + { + $s_height = "height: $s_height".(substr($s_height,-1) != '%' ? 'px' : '').';'; + } + $html = html::input_hidden($form_name,$value); + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $cell['type']; + + for ($n = 1; $n <= $cell_options; ++$n) + { + $child = $cell[$n]; // first param is a var_param now! + $html .= html::div($this->show_cell($child,$content,$readonlys,$cname,$show_c, + $show_row,$nul,$cl,$path.'/'.$n),html::formatOptions(array( + 'display: '.($value == $child['name'] ? 'inline' : 'none').';', + $child['name'] + ),'style,id')); + } + break; + default: + if ($ext_type && $this->haveExtension($ext_type,'render')) + { + $html .= $this->extensionRender($ext_type,$form_name,$value,$cell,$readonly); + } + else + { + $html .= "unknown type '$cell[type]'"; + } + break; + } + // extension-processing need to be after all other and only with diff. name + if ($ext_type && !$readonly && $this->haveExtension($ext_type,'post_process')) + { // unset it first, if it is already set, to be after the other widgets of the ext. + $to_process = 'ext-'.$ext_type; + if (is_array($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) { - $sels += $this->sel_options[$name]; + $to_process = $GLOBALS['egw_info']['etemplate']['to_process'][$form_name]; + $to_process['type'] = 'ext-'.$ext_type; + } + unset($GLOBALS['egw_info']['etemplate']['to_process'][$form_name]); + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = $to_process; + } + // save blur-value to strip it in process_exec + if (!empty($blur) && isset($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) + { + if (!is_array($GLOBALS['egw_info']['etemplate']['to_process'][$form_name])) + { + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] = array( + 'type' => $GLOBALS['egw_info']['etemplate']['to_process'][$form_name] + ); + } + $GLOBALS['egw_info']['etemplate']['to_process'][$form_name]['blur'] = $blur; + } + if ($extra_label && ($label != '' || $html == '')) + { + if (strlen($label) > 1 && !($cell['no_lang'] && $cell['label'] != $label || (int)$cell['no_lang'] == 2)) + { + $label = lang($label); + } + $accesskey = false; + if (($accesskey = $label && strpos($label,'&')!==false) && $accesskey[1] != ' ' && $form_name != '' && + (($pos = strpos($accesskey,';')) === false || $pos > 5)) + { + $label = str_replace('&'.$accesskey[1],''.$accesskey[1].'',$label); + $accesskey = $accesskey[1]; + } + if ($label && !$readonly && ($accesskey || $label_for || $type != 'label' && $cell['name'])) + { + $label = html::label($label,$label_for ? $this->form_name($cname,$label_for) : + $form_name.($set_val?"[$set_val]":''),$accesskey); + } + if ($type == 'radio' || $type == 'checkbox' || $label && strpos($label,'%s')!==false) // default for radio is label after the button + { + $html = strpos($label,'%s')!==false ? str_replace('%s',$html,$label) : $html.' '.$label; + } + elseif (($html = $label . ' ' . $html) == ' ') + { + $html = ' '; + } + } + if ($extra_link && (($extra_link = $this->expand_name($extra_link,$show_c,$show_row,$content['.c'],$content['.row'],$content)))) + { + $options = $help ? ' onmouseover="self.status=\''.addslashes(html::htmlspecialchars($help)).'\'; return true;"' . + ' onmouseout="self.status=\'\'; return true;"' : ''; + + if ($extra_link_target && (($extra_link_target = $this->expand_name($extra_link_target,$show_c,$show_row,$content['.c'],$content['.row'],$content)))) + { + $options .= ' target="'.addslashes($extra_link_target).'"'; + } + if ($extra_link_popup && (($extra_link_popup = $this->expand_name($extra_link_popup,$show_c,$show_row,$content['.c'],$content['.row'],$content)))) + { + list($w,$h) = explode('x',$extra_link_popup); + $options .= ' onclick="window.open(this,this.target,\'width='.(int)$w.',height='.(int)$h.',location=no,menubar=no,toolbar=no,scrollbars=yes,status=yes\'); return false;"'; + } + if ($extra_link_title) + { + $options .= ' title="'.addslashes($extra_link_title).'"'; + } + return html::a_href($html,$extra_link,'',$options); + } + // if necessary show validation-error behind field + if (isset($GLOBALS['egw_info']['etemplate']['validation_errors'][$form_name])) + { + $html .= ' '.$GLOBALS['egw_info']['etemplate']['validation_errors'][$form_name].''; + } + // generate an extra div, if we have an onclick handler and NO children or it's an extension + //echo "

$this->name($this->onclick_handler:$this->no_onclick:$this->onclick_proxy): $cell[type]/$cell[name]

\n"; + if ($this->onclick_handler && !isset($this->widgets_with_children[$cell['type']])) + { + $handler = str_replace('%p',$this->no_onclick ? $this->onclick_proxy : $this->name.':'.$this->version.':'.$path, + $this->onclick_handler); + if ($type == 'button' || $type == 'buttononly' || !$label) // add something to click on + { + $html = (substr($html,-1) == "\n" ? substr($html,0,-1) : $html).' '; + } + return html::div($html,' ondblclick="'.$handler.'"','clickWidgetToEdit'); + } + return $html; + } + + /** + * Retrive options for selectboxes and similar widgets (eg. the tree) + * + * @param array $cell + * @param string $name + * @param array $content=array(); + * @return array + */ + private function _sel_options($cell,$name,$content=array()) + { + $sels = array(); + + if (!empty($cell['sel_options'])) + { + if (!is_array($cell['sel_options'])) + { + $opts = explode(',',$cell['sel_options']); + while (list(,$opt) = each($opts)) + { + list($k,$v) = explode('=',$opt); + $sels[$k] = $v; + } } else { - $name_parts = explode('[',str_replace(']','',$name)); - if (count($name_parts)) - { - $org_name = $name_parts[count($name_parts)-1]; - if (isset($this->sel_options[$org_name]) && is_array($this->sel_options[$org_name])) - { - $sels += $this->sel_options[$org_name]; - } - elseif (isset($this->sel_options[$name_parts[0]]) && is_array($this->sel_options[$name_parts[0]])) - { - $sels += $this->sel_options[$name_parts[0]]; - } - } + $sels += $cell['sel_options']; } - if (isset($content["options-$name"])) - { - $sels += $content["options-$name"]; - } - return $sels; } - - /** - * Resolve javascript pseudo functions in onclick or onchange: - * - egw::link('$l','$p') calls $egw->link($l,$p) - * - form::name('name') returns expanded name/id taking into account the name at that point of the template hierarchy - * - egw::lang('Message ...') translate the message - * - confirm('message') translates 'message' and adds a '?' if not present - * - window.open() replaces it with egw_openWindowCentered2() - * - xajax_doXMLHTTP('etemplate. replace ajax calls in widgets with special handler not requiring etemplate run rights - * - * @param string $on onclick, onchange, ... action - * @param string $cname name-prefix / name-space - * @return string - */ - function js_pseudo_funcs($on,$cname) + if (isset($this->sel_options[$name]) && is_array($this->sel_options[$name])) { - if (strpos($on,'::') !== false) // avoid the expensive regular expresions, for performance reasons + $sels += $this->sel_options[$name]; + } + else + { + $name_parts = explode('[',str_replace(']','',$name)); + if (count($name_parts)) { - if (preg_match("/egw::link\\('([^']+)','(.+?)'\\)/",$on,$matches)) // the ? alters the expression to shortest match - { // this way we can correctly parse ' in the 2. argument - $url = $GLOBALS['egw']->link($matches[1],$matches[2]); - $on = str_replace($matches[0],'\''.$url.'\'',$on); - } - - if (preg_match_all("/form::name\\('([^']+)'\\)/",$on,$matches)) { - foreach($matches[1] as $n => $matche_name) { - $matches[1][$n] = '\''.$this->form_name($cname,$matche_name).'\''; - } - $on = str_replace($matches[0],$matches[1],$on); - } - // we need to search ungready (shortest possible match), to avoid catching to much - if (preg_match('/egw::lang\(["\']{1}(.*)["\']{1}\)/U',$on,$matches)) { - $str = lang($matches[1]); - $on = str_replace($matches[0],'\''.addslashes($str).'\'',$on); - } - - // inserts the styles of a named template - if (preg_match('/template::styles\(["\']{1}(.*)["\']{1}\)/U',$on,$matches)) + $org_name = $name_parts[count($name_parts)-1]; + if (isset($this->sel_options[$org_name]) && is_array($this->sel_options[$org_name])) { - $tpl = $matches[1] == $this->name ? $this : new etemplate($matches[1]); - $on = str_replace($matches[0],"''",$on); + $sels += $this->sel_options[$org_name]; + } + elseif (isset($this->sel_options[$name_parts[0]]) && is_array($this->sel_options[$name_parts[0]])) + { + $sels += $this->sel_options[$name_parts[0]]; } } - if (strpos($on,'confirm(') !== false && preg_match('/confirm\(["\']{1}(.*)["\']{1}\)/',$on,$matches)) { - $question = lang($matches[1]).(substr($matches[1],-1) != '?' ? '?' : ''); // add ? if not there, saves extra phrase - $on = str_replace($matches[0],'confirm(\''.addslashes($question).'\')',$on); - //$on = preg_replace('/confirm\(["\']{1}(.*)["\']{1}\)/','confirm(\''.addslashes($question).'\')',$on); + } + if (isset($content["options-$name"])) + { + $sels += $content["options-$name"]; + } + return $sels; + } + + /** + * Resolve javascript pseudo functions in onclick or onchange: + * - egw::link('$l','$p') calls $egw->link($l,$p) + * - form::name('name') returns expanded name/id taking into account the name at that point of the template hierarchy + * - egw::lang('Message ...') translate the message + * - confirm('message') translates 'message' and adds a '?' if not present + * - window.open() replaces it with egw_openWindowCentered2() + * - xajax_doXMLHTTP('etemplate. replace ajax calls in widgets with special handler not requiring etemplate run rights + * + * @param string $on onclick, onchange, ... action + * @param string $cname name-prefix / name-space + * @return string + */ + function js_pseudo_funcs($on,$cname) + { + if (strpos($on,'::') !== false) // avoid the expensive regular expresions, for performance reasons + { + if (preg_match("/egw::link\\('([^']+)','(.+?)'\\)/",$on,$matches)) // the ? alters the expression to shortest match + { // this way we can correctly parse ' in the 2. argument + $url = $GLOBALS['egw']->link($matches[1],$matches[2]); + $on = str_replace($matches[0],'\''.$url.'\'',$on); } - if (strpos($on,'window.open(') !== false && preg_match("/window.open\('(.*)','(.*)','dependent=yes,width=(.*),height=(.*),scrollbars=yes,status=(.*)'\)/",$on,$matches)) { - $on = str_replace($matches[0], "egw_openWindowCentered2('{$matches[1]}', '{$matches[2]}', '{$matches[3]}', '{$matches[4]}', '{$matches[5]}')", $on); + if (preg_match_all("/form::name\\('([^']+)'\\)/",$on,$matches)) { + foreach($matches[1] as $n => $matche_name) { + $matches[1][$n] = '\''.$this->form_name($cname,$matche_name).'\''; + } + $on = str_replace($matches[0],$matches[1],$on); + } + // we need to search ungready (shortest possible match), to avoid catching to much + if (preg_match('/egw::lang\(["\']{1}(.*)["\']{1}\)/U',$on,$matches)) { + $str = lang($matches[1]); + $on = str_replace($matches[0],'\''.addslashes($str).'\'',$on); } - // replace xajax calls to code in widgets, with the "etemplate" handler, - // this allows to call widgets with the current app, otherwise everyone would need etemplate run rights - if (strpos($on,"xajax_doXMLHTTP('etemplate.") !== false) { - $on = preg_replace("/^xajax_doXMLHTTP\('etemplate\.([a-z]+_widget\.[a-zA-Z0-9_]+)\'/",'xajax_doXMLHTTP(\''.$GLOBALS['egw_info']['flags']['currentapp'].'.\\1.etemplate\'',$on); + // inserts the styles of a named template + if (preg_match('/template::styles\(["\']{1}(.*)["\']{1}\)/U',$on,$matches)) + { + $tpl = $matches[1] == $this->name ? $this : new etemplate($matches[1]); + $on = str_replace($matches[0],"''",$on); } - - return $on; } - - /** - * applies stripslashes recursivly on each element of an array - * - * @param array &$var - * @return array - */ - function array_stripslashes($var) - { - if (!is_array($var)) - { - return stripslashes($var); - } - foreach($var as $key => $val) - { - $var[$key] = is_array($val) ? etemplate::array_stripslashes($val) : stripslashes($val); - } - return $var; - } - - /** - * makes necessary adjustments on $_POST after a eTemplate / form gots submitted - * - * This is only an internal function, dont call it direct use only exec - * Process_show uses a list of input-fields/widgets generated by show. - * - * @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 type of request - * @return int number of validation errors (the adjusted content is returned by the var-param &$content !) - */ - function process_show(&$content,$to_process,$cname='', $type = 'regular') - { - if (!isset($content) || !is_array($content) || !is_array($to_process)) - { - return; - } - if (is_int($this->debug) && $this->debug >= 1 || $this->debug == $this->name && $this->name) - { - echo "

process_show($this->name) cname='$cname' start: content ="; _debug_array($content); - } - $content_in = $cname ? array($cname => $content) : $content; - $content = array(); - if (get_magic_quotes_gpc()) - { - $content_in = etemplate::array_stripslashes($content_in); - } - $GLOBALS['egw_info']['etemplate']['validation_errors'] = array(); - $this->canceled = $this->button_pressed = False; - - foreach($to_process as $form_name => $type) - { - if (is_array($type)) - { - $attr = $type; - $type = $attr['type']; - } - else - { - $attr = array(); - } - $value = etemplate::get_array($content_in,$form_name,True,$GLOBALS['egw_info']['flags']['currentapp'] == 'etemplate' ? false : true ); - // The comment below does only aplay to normal posts, not for xajax. Files are not supported anyway by xajax atm. - // not checked checboxes are not returned in HTML and file is in $_FILES and not in $content_in - if($value === false && $type == 'xajaxResponse' /*!in_array($type,array('checkbox','file'))*/) continue; - - if (isset($attr['blur']) && $attr['blur'] == $value) - { - $value = ''; // blur-values is equal to emtpy - } - //echo "

process_show($this->name) loop was {$GLOBALS['egw_info']['etemplate']['loop']}, $type: $form_name = '$value'

\n"; - list($type,$sub) = explode('-',$type); - switch ($type) - { - case 'ext': - $_cont = &etemplate::get_array($content,$form_name,True); - if (!$this->extensionPostProcess($sub,$form_name,$_cont,$value)) - { - //echo "\n

unsetting content[$form_name] !!!

\n"; - $this->unset_array($content,$form_name); - } - // this else should NOT be unnecessary as $_cont is a reference to the index - // $form_name of $content, but under some circumstances a set/changed $_cont - // does not result in a change in $content -- RalfBecker 2004/09/18 - // seems to depend on the number of (not existing) dimensions of the array -- -- RalfBecker 2005/04/06 - elseif (!etemplate::isset_array($content,$form_name)) - { - //echo "

setting content[$form_name]='$_cont' because is was unset !!!

\n"; - $this->set_array($content,$form_name,$_cont); - } - if ($_cont === '' && $attr['needed'] && !$attr['blur']) - { - $this->set_validation_error($form_name,lang('Field must not be empty !!!'),''); - } - break; - case 'htmlarea': - $this->set_array($content,$form_name,$value); - break; - case 'int': - case 'float': - case 'passwd': - case 'text': - case 'textarea': - if ($value === '' && $attr['needed'] && !$attr['blur']) - { - $this->set_validation_error($form_name,lang('Field must not be empty !!!'),''); - } - if ((int) $attr['maxlength'] > 0 && strlen($value) > (int) $attr['maxlength']) - { - $value = substr($value,0,(int) $attr['maxlength']); - } - if ($attr['preg'] && !preg_match($attr['preg'],$value)) - { - switch($type) - { - case 'int': - $this->set_validation_error($form_name,lang("'%1' is not a valid integer !!!",$value),''); - break; - case 'float': - $this->set_validation_error($form_name,lang("'%1' is not a valid floatingpoint number !!!",$value),''); - break; - default: - $this->set_validation_error($form_name,lang("'%1' has an invalid format !!!",$value),''); - break; - } - } - elseif ($type == 'int' || $type == 'float') // cast int and float and check range - { - if ($value !== '' || $attr['needed']) // empty values are Ok if needed is not set - { - $value = $type == 'int' ? (int) $value : (float) str_replace(',','.',$value); // allow for german (and maybe other) format - - if (!empty($attr['min']) && $value < $attr['min']) - { - $this->set_validation_error($form_name,lang("Value has to be at least '%1' !!!",$attr['min']),''); - $value = $type == 'int' ? (int) $attr['min'] : (float) $attr['min']; - } - if (!empty($attr['max']) && $value > $attr['max']) - { - $this->set_validation_error($form_name,lang("Value has to be at maximum '%1' !!!",$attr['max']),''); - $value = $type == 'int' ? (int) $attr['max'] : (float) $attr['max']; - } - } - } - $this->set_array($content,$form_name,$value); - break; - case 'cancel': // cancel button ==> dont care for validation errors - if ($value) - { - $this->canceled = True; - $this->set_array($content,$form_name,$value); - } - break; - case 'button': - if ($value) - { - $this->button_pressed = True; - $this->set_array($content,$form_name,$value); - } - break; - case 'select': - if ($attr['allowed']) // only check for $value is allowed, if allowed values are set - { - foreach(is_array($value) ? $value : array($value) as $val) - { - if (!($attr['multiple'] && !$val) && !in_array($val,$attr['allowed'])) - { - $this->set_validation_error($form_name,lang("'%1' is NOT allowed ('%2')!",$val,implode("','",$attr['allowed'])),''); - $value = ''; - break; - } - } - } - if (is_array($value)) $value = implode(',',$value); - if ($value === '' && $attr['needed']) - { - $this->set_validation_error($form_name,lang('Field must not be empty !!!',$value),''); - } - $this->set_array($content,$form_name,$value); - break; - case 'checkbox': - if ($value === false) // get_array() returns false for not set - { - $this->set_array($content,$form_name,$attr['multiple'] ? array() : $attr['unset_value']); // need to be reported too - } - else - { - $value = array_intersect(is_array($value) ? $value : array($value),$attr['values']); // return only allowed values - $this->set_array($content,$form_name,$attr['multiple'] ? $value : $value[0]); - } - break; - case 'file': - $parts = explode('[',str_replace(']','',$form_name)); - $name = array_shift($parts); - $index = count($parts) ? '['.implode('][',$parts).']' : ''; - $value = array(); - foreach(array('tmp_name','type','size','name') as $part) - { - $value[$part] = is_array($_FILES[$name]) ? $this->get_array($_FILES[$name],$part.$index) : False; - } - $value['path'] = $this->get_array($content_in,substr($form_name,0,-1).'_path]'); - $value['ip'] = get_var('REMOTE_ADDR',Array('SERVER')); - if (function_exists('is_uploaded_file') && !is_uploaded_file($value['tmp_name'])) - { - $value = array(); // to be on the save side - } - //_debug_array($value); - // fall-throught - default: - $this->set_array($content,$form_name,$value); - break; - } - } - if ($cname) - { - $content = $content[$cname]; - } - if (is_int($this->debug) && $this->debug >= 2 || $this->debug == $this->name && $this->name) - { - echo "

process_show($this->name) end: content ="; _debug_array($content); - if (count($GLOBALS['egw_info']['etemplate']['validation_errors'])) - { - echo "

validation_errors = "; _debug_array($GLOBALS['egw_info']['etemplate']['validation_errors']); - } - } - return count($GLOBALS['egw_info']['etemplate']['validation_errors']); + if (strpos($on,'confirm(') !== false && preg_match('/confirm\(["\']{1}(.*)["\']{1}\)/',$on,$matches)) { + $question = lang($matches[1]).(substr($matches[1],-1) != '?' ? '?' : ''); // add ? if not there, saves extra phrase + $on = str_replace($matches[0],'confirm(\''.addslashes($question).'\')',$on); + //$on = preg_replace('/confirm\(["\']{1}(.*)["\']{1}\)/','confirm(\''.addslashes($question).'\')',$on); } - /** - * Sets a validation error, to be displayed in the next exec - * - * @param string $name (complete) name of the widget causing the error - * @param string $error error-message already translated - * @param string $cname=null set it to '', if the name is already a form-name, defaults to $this->name_vars - */ - function set_validation_error($name,$error,$cname=null) + if (strpos($on,'window.open(') !== false && preg_match("/window.open\('(.*)','(.*)','dependent=yes,width=(.*),height=(.*),scrollbars=yes,status=(.*)'\)/",$on,$matches)) { + $on = str_replace($matches[0], "egw_openWindowCentered2('{$matches[1]}', '{$matches[2]}', '{$matches[3]}', '{$matches[4]}', '{$matches[5]}')", $on); + } + + // replace xajax calls to code in widgets, with the "etemplate" handler, + // this allows to call widgets with the current app, otherwise everyone would need etemplate run rights + if (strpos($on,"xajax_doXMLHTTP('etemplate.") !== false) { + $on = preg_replace("/^xajax_doXMLHTTP\('etemplate\.([a-z]+_widget\.[a-zA-Z0-9_]+)\'/",'xajax_doXMLHTTP(\''.$GLOBALS['egw_info']['flags']['currentapp'].'.\\1.etemplate\'',$on); + } + + return $on; + } + + /** + * applies stripslashes recursivly on each element of an array + * + * @param array &$var + * @return array + */ + static function array_stripslashes($var) + { + if (!is_array($var)) { - if (is_null($cname)) $cname = $this->name_vars; - //echo "

etemplate::set_validation_error('$name','$error','$cname');

\n"; - if ($cname) $name = $this->form_name($cname,$name); - - if ($GLOBALS['egw_info']['etemplate']['validation_errors'][$name]) + return stripslashes($var); + } + foreach($var as $key => $val) + { + $var[$key] = is_array($val) ? self::array_stripslashes($val) : stripslashes($val); + } + return $var; + } + + /** + * makes necessary adjustments on $_POST after a eTemplate / form gots submitted + * + * This is only an internal function, dont call it direct use only exec + * Process_show uses a list of input-fields/widgets generated by show. + * + * @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 type of request + * @return int number of validation errors (the adjusted content is returned by the var-param &$content !) + */ + private function process_show(&$content,$to_process,$cname='', $type = 'regular') + { + if (!isset($content) || !is_array($content) || !is_array($to_process)) + { + return; + } + if (is_int($this->debug) && $this->debug >= 1 || $this->debug == $this->name && $this->name) + { + echo "

process_show($this->name) cname='$cname' start: content ="; _debug_array($content); + } + $content_in = $cname ? array($cname => $content) : $content; + $content = array(); + if (get_magic_quotes_gpc()) + { + $content_in = etemplate::array_stripslashes($content_in); + } + $GLOBALS['egw_info']['etemplate']['validation_errors'] = array(); + $this->canceled = $this->button_pressed = False; + + foreach($to_process as $form_name => $type) + { + if (is_array($type)) { - $GLOBALS['egw_info']['etemplate']['validation_errors'][$name] .= ', '; + $attr = $type; + $type = $attr['type']; } - $GLOBALS['egw_info']['etemplate']['validation_errors'][$name] .= $error; - } - - /** - * is javascript enabled? - * - * this should be tested by the api at login - * - * @return boolean true if javascript is enabled or not yet tested and $consider_not_tested_as_enabled - */ - function java_script($consider_not_tested_as_enabled = True) - { - $ret = !!$GLOBALS['egw_info']['etemplate']['java_script'] || - $consider_not_tested_as_enabled && !isset($GLOBALS['egw_info']['etemplate']['java_script']); - //echo "

java_script($consider_not_tested_as_enabled)='$ret', java_script='".$GLOBALS['egw_info']['etemplate']['java_script']."', isset(java_script)=".isset($GLOBALS['egw_info']['etemplate']['java_script'])."

\n"; - - return $ret; - return !!$GLOBALS['egw_info']['etemplate']['java_script'] || - $consider_not_tested_as_enabled && - (!isset($GLOBALS['egw_info']['etemplate']['java_script']) || - $GLOBALS['egw_info']['etemplate']['java_script'].'' == ''); - } - - /** - * returns the javascript to be included by exec - * - * @param int $what &1 = returns the test, note: has to be included in the body, not the header, - * &2 = returns the common functions, best to be included in the header - * @return string javascript - */ - function include_java_script($what = 3) - { - // this is to test if javascript is enabled - if ($what & 1 && !isset($GLOBALS['egw_info']['etemplate']['java_script'])) + else { - $js = ' '; - } - // here are going all the necesarry functions if javascript is enabled - if ($what & 2 && $this->java_script(True)) - { - $lastmod = filectime(EGW_INCLUDE_ROOT. '/etemplate/js/etemplate.js'); - $js .= ''."\n"; - } - return $js; } - }; + // here are going all the necesarry functions if javascript is enabled + if ($what & 2 && $this->java_script(True)) + { + $lastmod = filectime(EGW_INCLUDE_ROOT. '/etemplate/js/etemplate.js'); + $js .= ''."\n"; + } + return $js; + } +}; diff --git a/etemplate/inc/thumbnail.inc.php b/etemplate/inc/thumbnail.inc.php index 7bc314d367..3760bc3724 100644 --- a/etemplate/inc/thumbnail.inc.php +++ b/etemplate/inc/thumbnail.inc.php @@ -1,4 +1,13 @@ 'infolog', 'noheader' => true, @@ -177,4 +186,3 @@ $gd_ver = $match[0]; return $match[0]; } -?> diff --git a/etemplate/templates/default/link_widget.to.xet b/etemplate/templates/default/link_widget.to.xet index a843ae9837..d8fb876bbc 100644 --- a/etemplate/templates/default/link_widget.to.xet +++ b/etemplate/templates/default/link_widget.to.xet @@ -7,7 +7,7 @@ -