From 0d66dd98b7f8f7d1f9ec32a49a265476492e15ea Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Thu, 3 May 2012 14:17:47 +0000 Subject: [PATCH] fixed server-side valdation of autorepeated rows/columns had to change signature of validate function to get information for autorepeating through removed entity-encoding of square brackets, as they mess up validiation (havnt found any negative effects so far) --- etemplate/inc/class.etemplate.inc.php | 10 +- etemplate/inc/class.etemplate_widget.inc.php | 85 +++++++++---- .../inc/class.etemplate_widget_button.inc.php | 13 +- .../class.etemplate_widget_checkbox.inc.php | 7 +- ...lass.etemplate_widget_customfields.inc.php | 28 +++-- .../inc/class.etemplate_widget_date.inc.php | 9 +- .../inc/class.etemplate_widget_file.inc.php | 96 ++++++++------- .../inc/class.etemplate_widget_grid.inc.php | 112 +++++++++++++++++- .../class.etemplate_widget_htmlarea.inc.php | 7 +- .../inc/class.etemplate_widget_link.inc.php | 11 +- .../class.etemplate_widget_menupopup.inc.php | 9 +- .../class.etemplate_widget_nextmatch.inc.php | 15 ++- ...ss.etemplate_widget_projectmanager.inc.php | 3 +- .../class.etemplate_widget_template.inc.php | 9 +- .../class.etemplate_widget_textbox.inc.php | 9 +- .../inc/class.etemplate_widget_tree.inc.php | 11 +- .../inc/class.etemplate_widget_vfs.inc.php | 11 +- 17 files changed, 312 insertions(+), 133 deletions(-) diff --git a/etemplate/inc/class.etemplate.inc.php b/etemplate/inc/class.etemplate.inc.php index 1ec41ddc47..6b7e7aca85 100644 --- a/etemplate/inc/class.etemplate.inc.php +++ b/etemplate/inc/class.etemplate.inc.php @@ -215,7 +215,10 @@ class etemplate_new extends etemplate_widget_template throw new egw_exception_wrong_parameter('Can NOT read template '.array2string(self::$request->template)); } $validated = array(); - $template->run('validate', array('', $content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children + $expand = array( + 'cont' => &self::$request->content, + ); + $template->run('validate', array('', $expand, $content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children if (self::validation_errors(self::$request->ignore_validation)) { @@ -251,7 +254,10 @@ class etemplate_new extends etemplate_widget_template translation::add_app($GLOBALS['egw_info']['flags']['currentapp']); $validated = array(); - $template->run('validate', array('', $content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children + $expand = array( + 'cont' => &self::$request->content, + ); + $template->run('validate', array('', $expand, $content, &$validated), true); // $respect_disabled=true: do NOT validate disabled widgets and children if (self::validation_errors(self::$request->ignore_validation)) { error_log(__METHOD__."(,".array2string($content).') validation_errors='.array2string(self::$validation_errors)); diff --git a/etemplate/inc/class.etemplate_widget.inc.php b/etemplate/inc/class.etemplate_widget.inc.php index f1bf280b0f..ab22a8b1b2 100644 --- a/etemplate/inc/class.etemplate_widget.inc.php +++ b/etemplate/inc/class.etemplate_widget.inc.php @@ -155,7 +155,11 @@ class etemplate_widget { if ($reader->name != 'id' && $template->attr[$reader->name] != $reader->value) { - if (!$cloned) $template = clone($this); + if (!$cloned) + { + $template = clone($this); + $cloned = true; // only clone it once, otherwise we loose attributes! + } $template->attrs[$reader->name] = $reader->value; // split legacy options @@ -326,16 +330,23 @@ class etemplate_widget * Default implementation only calls method on itself and run on all children * * @param string $method_name - * @param array $params=array('') parameter(s) first parameter has to be the cname! + * @param array $params=array('') parameter(s) first parameter has to be the cname, second $expand! * @param boolean $respect_disabled=false false (default): ignore disabled, true: method is NOT run for disabled widgets AND their children */ public function run($method_name, $params=array(''), $respect_disabled=false) { + // maintain $expand array name-expansion + $cname = $params[0]; + $expand =& $params[1]; + if ($expand['cname'] !== $cname) + { + $expand['cont'] =& self::get_array(self::$request->content, $cname); + $expand['cname'] = $cname; + } if ($respect_disabled && ($disabled = $this->attrs['disabled'])) { // check if disabled contains @ or ! - $cname = $params[0]; - $disabled = self::check_disabled($disabled, $cname ? self::get_array(self::$request->content, $cname) : self::$request->content); + $disabled = self::check_disabled($disabled, $expand); if ($disabled) { error_log(__METHOD__."('$method_name', ".array2string($params).', '.array2string($respect_disabled).") $this disabled='{$this->attrs['disabled']}'='$disabled': NOT running"); @@ -360,12 +371,10 @@ class etemplate_widget * 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 - * @param int $row=null to be able to use $row or $row_content in value of checks - * @param int $c=null to be able to use $row or $row_content in value of checks + * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' * @return boolean true if the row/col is disabled or false if not */ - protected static function check_disabled($disabled,$content,$row=null,$c=null) + protected static function check_disabled($disabled, array $expland) { if ($not = $disabled[0] == '!') { @@ -374,8 +383,8 @@ class etemplate_widget list($val,$check_val) = $vals = explode('=',$disabled); // use expand_name to be able to use @ or $ - $val = self::expand_name($val,$c,$row,'','',$content); - $check_val = self::expand_name($check_val,$c,$row,'','',$content); + $val = self::expand_name($val,$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); + $check_val = self::expand_name($check_val,$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); $result = count($vals) == 1 ? $val != '' : ($check_val[0] == '/' ? preg_match($check_val,$val) : $val == $check_val); if ($not) $result = !$result; @@ -428,11 +437,13 @@ class etemplate_widget $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); + $col = self::num2chrs($c-1); // $c-1 to get: 0:'@', 1:'A', ... + $col_ = self::num2chrs($c_-1); $row_cont = $cont[$row]; $col_row_cont = $cont[$col.$row]; + /* RB: dont think any of this is needed in eTemplate2, as this escaping probably needs to be done on clientside anyway + // check if name is enclosed in single quotes as argument eg. to an event handler or // variable name is contained in quotes and curly brackets, eg. "'{$cont[nm][path]}'" or // used as name for a button like "delete[$row_cont[something]]" --> quote contained quotes (' or ") @@ -471,7 +482,7 @@ class etemplate_widget $value = str_replace('&',urlencode('&'),$value); $name = str_replace($matches[3],$value,$name); } - } + }*/ eval('$name = "'.str_replace('"','\\"',$name).'";'); } if ($is_index_in_content) @@ -485,10 +496,30 @@ class etemplate_widget $name = self::get_array($cont,substr($name,1)); } } - $name = str_replace(array('[',']'),array('[',']'),$name); + // RB: not sure why this business with entity encoding for square brakets, it messes up validation + //$name = str_replace(array('[',']'),array('[',']'),$name); return $name; } + /** + * generates column-names from index: 'A', 'B', ..., 'AA', 'AB', ..., 'ZZ' (not more!) + * + * @param int $num numerical index to generate name from 1 => 'A' + * @return string the name + */ + static function num2chrs($num) + { + $min = ord('A'); + $max = ord('Z') - $min + 1; + if ($num >= $max) + { + $chrs = chr(($num / $max) + $min - 1); + } + $chrs .= chr(($num % $max) + $min); + + return $chrs; + } + /** * Convert object to string * @@ -543,10 +574,15 @@ class etemplate_widget * * @param string $cname basename * @param string $name name + * @param array $expand=null values for keys 'c', 'row', 'c_', 'row_', 'cont' * @return string complete form-name */ - static function form_name($cname,$name) + static function form_name($cname,$name,array $expand=null) { + if ($expand && !empty($name)) + { + $name = self::expand_name($name, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']); + } if (count($name_parts = explode('[', $name, 2)) > 1) { $name_parts = array_merge(array($name_parts[0]), explode('][', substr($name_parts[1],0,-1))); @@ -558,7 +594,9 @@ class etemplate_widget $form_name = array_shift($name_parts); if (count($name_parts)) { - $form_name .= '['.implode('][',$name_parts).']'; + // RB: not sure why this business with entity encoding for square brakets, it messes up validation + //$form_name .= '['.implode('][',$name_parts).']'; + $form_name .= '['.implode('][',$name_parts).']'; } return $form_name; } @@ -615,12 +653,19 @@ class etemplate_widget * - $readonlys[__ALL__] set and $readonlys[$form_name] !== false * - $readonlys[$form_name] evaluates to true * + * @param string $cname='' + * @param string $form_name=null form_name, to not calculate him again * @return boolean */ - public function is_readonly($cname='') + public function is_readonly($cname='', $form_name=null) { - $form_name = self::form_name($cname, $this->id); - + if (!isset($form_name)) + { + $expand = array( + 'cont' => self::get_array(self::$request->content, $cname), + ); + $form_name = self::form_name($cname, $this->id, $expand); + } $readonly = $this->attrs['readonly'] || self::$request->readonlys[$form_name] || isset(self::$request->readonlys['__ALL__']) && self::$request->readonlys[$form_name] !== false; @@ -767,7 +812,7 @@ class etemplate_widget_box extends etemplate_widget { $cname =& $params[0]; $old_cname = $params[0]; - if ($this->id) $cname = self::form_name($cname, $this->id); + if ($this->id) $cname = self::form_name($cname, $this->id, $params[1]); parent::run($method_name, $params, $respect_disabled); $params[0] = $old_cname; diff --git a/etemplate/inc/class.etemplate_widget_button.inc.php b/etemplate/inc/class.etemplate_widget_button.inc.php index f1c3c0efcb..b238a5785a 100644 --- a/etemplate/inc/class.etemplate_widget_button.inc.php +++ b/etemplate/inc/class.etemplate_widget_button.inc.php @@ -17,20 +17,23 @@ class etemplate_widget_button extends etemplate_widget { /** - * Validate input + * Validate buttons * - * Readonly buttons can NOT be pressed + * Readonly buttons can NOT be pressed! * * @param string $cname current namespace + * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' * @param array $content * @param array &$validated=array() validated content * @return boolean true if no validation error, false otherwise */ - public function validate($cname, array $content, &$validated=array()) + public function validate($cname, array $expand, array $content, &$validated=array()) { - $form_name = self::form_name($cname, $this->id); + $form_name = self::form_name($cname, $this->id, $expand); + //error_log(__METHOD__."('$cname', ".array2string($expand).", ...) $this: get_array(\$content, '$form_name')=".array2string(self::get_array($content, $form_name))); - if (self::get_array($content, $form_name) && !$this->is_readonly($cname)) + // need to check === true, as get_array() ignores a "[]" postfix and returns array() eg. for a not existing $row_cont[id] in "delete[$row_cont[id]]" + if (!$this->is_readonly($cname, $form_name) && self::get_array($content, $form_name) === true) { $valid =& self::get_array($validated, $form_name, true); $valid = 'pressed'; // that's what it was in old etemplate diff --git a/etemplate/inc/class.etemplate_widget_checkbox.inc.php b/etemplate/inc/class.etemplate_widget_checkbox.inc.php index c43658e5ae..b81e55f7d0 100644 --- a/etemplate/inc/class.etemplate_widget_checkbox.inc.php +++ b/etemplate/inc/class.etemplate_widget_checkbox.inc.php @@ -35,20 +35,21 @@ class etemplate_widget_checkbox extends etemplate_widget * Same is true for the radio buttons of a radio-group sharing the same name. * * @param string $cname current namespace + * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' * @param array $content * @param array &$validated=array() validated content * @return boolean true if no validation error, false otherwise */ - public function validate($cname, array $content, &$validated=array()) + public function validate($cname, array $expand, array $content, &$validated=array()) { - $form_name = self::form_name($cname, $this->id); + $form_name = self::form_name($cname, $this->id, $expand); if (($multiple = substr($form_name, -2) == '[]') && $this->type == 'checkbox') { $form_name = substr($form_name, 0, -2); } - if (!$this->is_readonly($cname)) + if (!$this->is_readonly($cname, $form_name)) { $value = self::get_array($content, $form_name); $valid =& self::get_array($validated, $form_name, true); diff --git a/etemplate/inc/class.etemplate_widget_customfields.inc.php b/etemplate/inc/class.etemplate_widget_customfields.inc.php index 4be81643ff..b07ee924bb 100644 --- a/etemplate/inc/class.etemplate_widget_customfields.inc.php +++ b/etemplate/inc/class.etemplate_widget_customfields.inc.php @@ -50,7 +50,7 @@ class etemplate_widget_customfields extends etemplate_widget_transformer * @var $prefix string Prefix for every custiomfield name returned in $content (# for general (admin) customfields) */ protected static $prefix = '#'; - + // Make settings available globally const GLOBAL_VALS = '~custom_fields~'; @@ -210,24 +210,26 @@ class etemplate_widget_customfields extends etemplate_widget_transformer * - int and float get casted to their type * * @param string $cname current namespace + * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' * @param array $content * @param array &$validated=array() validated content */ - public function validate($cname, array $content, &$validated=array()) + public function validate($cname, array $expand, array $content, &$validated=array()) { - if (!$this->is_readonly($cname)) + if ($this->id) { - if($this->id) - { - $form_name = self::form_name($cname, $this->id); - } - else - { - $form_name = self::GLOBAL_ID; - } + $form_name = self::form_name($cname, $this->id, $expand); + } + else + { + $form_name = self::GLOBAL_ID; + } + if (!$this->is_readonly($cname, $form_name)) + { $value_in = self::get_array($content, $form_name); - if(is_array($value_in)) { + if(is_array($value_in)) + { foreach($value_in as $field => $value) { if ((string)$value === '' && $this->attrs['needed']) @@ -235,7 +237,7 @@ class etemplate_widget_customfields extends etemplate_widget_transformer self::set_validation_error($form_name,lang('Field must not be empty !!!'),''); } $valid =& self::get_array($validated, $this->id ? $form_name : $field, true); - + $valid = is_array($value) ? implode(',',$value) : $value; error_log(__METHOD__."() $form_name $field: ".array2string($value).' --> '.array2string($value)); } diff --git a/etemplate/inc/class.etemplate_widget_date.inc.php b/etemplate/inc/class.etemplate_widget_date.inc.php index ae456bd06b..e530f35c47 100644 --- a/etemplate/inc/class.etemplate_widget_date.inc.php +++ b/etemplate/inc/class.etemplate_widget_date.inc.php @@ -47,16 +47,17 @@ class etemplate_widget_date extends etemplate_widget_transformer * Validate input * * @param string $cname current namespace + * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' * @param array $content * @param array &$validated=array() validated content * @return boolean true if no validation error, false otherwise */ - public function validate($cname, array $content, &$validated=array()) + public function validate($cname, array $expand, array $content, &$validated=array()) { - if (!$this->is_readonly($cname) && $this->type != 'date-since') // date-since is always readonly - { - $form_name = self::form_name($cname, $this->id); + $form_name = self::form_name($cname, $this->id, $expand); + if (!$this->is_readonly($cname, $form_name) && $this->type != 'date-since') // date-since is always readonly + { $value = self::get_array($content, $form_name); $valid =& self::get_array($validated, $form_name, true); diff --git a/etemplate/inc/class.etemplate_widget_file.inc.php b/etemplate/inc/class.etemplate_widget_file.inc.php index 359d1ec00e..9d312a32c9 100644 --- a/etemplate/inc/class.etemplate_widget_file.inc.php +++ b/etemplate/inc/class.etemplate_widget_file.inc.php @@ -17,8 +17,13 @@ */ class etemplate_widget_file extends etemplate_widget { - - public function __construct($xml='') { + /** + * Constructor + * + * @param string $xml + */ + public function __construct($xml='') + { if($xml) parent::__construct($xml); // Legacy multiple - id ends in [] @@ -29,15 +34,15 @@ class etemplate_widget_file extends etemplate_widget } /** - * Ajax callback to receive an incoming file - * - * The incoming file is moved from its temporary location (otherwise server will delete it) and - * the file information is stored into the widget's value. When the form is submitted, the information for all - * files uploaded is available in the returned $content array. Because files are uploaded asynchronously, - * submission should be quick. - * - * @note Currently, no attempt is made to clean up files automatically. - */ + * Ajax callback to receive an incoming file + * + * The incoming file is moved from its temporary location (otherwise server will delete it) and + * the file information is stored into the widget's value. When the form is submitted, the information for all + * files uploaded is available in the returned $content array. Because files are uploaded asynchronously, + * submission should be quick. + * + * @note Currently, no attempt is made to clean up files automatically. + */ public static function ajax_upload() { $response = egw_json_response::get(); $request_id = str_replace(' ', '+', rawurldecode($_REQUEST['request_id'])); @@ -48,11 +53,11 @@ class etemplate_widget_file extends etemplate_widget } if (!($template = etemplate_widget_template::instance(self::$request->template['name'], self::$request->template['template_set'], - self::$request->template['version'], self::$request->template['load_via']))) - { + self::$request->template['version'], self::$request->template['load_via']))) + { // Can't use callback error_log("Could not get template for file upload, callback skipped"); - } + } $file_data = array(); @@ -146,42 +151,47 @@ class etemplate_widget_file extends etemplate_widget * Merge any already uploaded files into the content array * * @param string $cname current namespace + * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' * @param array $content * @param array &$validated=array() validated content */ - public function validate($cname, array $content, &$validated=array()) + public function validate($cname, array $expand, array $content, &$validated=array()) { - $form_name = self::form_name($cname, $this->id); - $value = $value_in = self::get_array($content, $form_name); - $valid =& self::get_array($validated, $form_name, true); + $form_name = self::form_name($cname, $this->id, $expand); - if(!is_array($value)) $value = array(); - - // Incoming values indexed by temp name - if($value[0]) $value = $value[0]; - - foreach($value as $tmp => $file) + if (!$this->is_readonly($cname, $form_name)) { - if (is_dir($GLOBALS['egw_info']['server']['temp_dir']) && is_writable($GLOBALS['egw_info']['server']['temp_dir'])) - { - $path = $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmp; - } - else - { - $path = $tmp.'+'; - } - $stat = stat($path); - $valid[] = array( - 'name' => $file['name'], - 'type' => $file['type'], - 'tmp_name' => $path, - 'error' => UPLOAD_ERR_OK, // Always OK if we get this far - 'size' => $stat['size'], - 'ip' => $_SERVER['REMOTE_ADDR'], // Assume it's the same as for when it was uploaded... - ); - } + $value = $value_in = self::get_array($content, $form_name); + $valid =& self::get_array($validated, $form_name, true); - if($valid && !$this->attrs['multiple']) $valid = $valid[0]; + if(!is_array($value)) $value = array(); + + // Incoming values indexed by temp name + if($value[0]) $value = $value[0]; + + foreach($value as $tmp => $file) + { + if (is_dir($GLOBALS['egw_info']['server']['temp_dir']) && is_writable($GLOBALS['egw_info']['server']['temp_dir'])) + { + $path = $GLOBALS['egw_info']['server']['temp_dir'].'/'.$tmp; + } + else + { + $path = $tmp.'+'; + } + $stat = stat($path); + $valid[] = array( + 'name' => $file['name'], + 'type' => $file['type'], + 'tmp_name' => $path, + 'error' => UPLOAD_ERR_OK, // Always OK if we get this far + 'size' => $stat['size'], + 'ip' => $_SERVER['REMOTE_ADDR'], // Assume it's the same as for when it was uploaded... + ); + } + + if($valid && !$this->attrs['multiple']) $valid = $valid[0]; + } } } etemplate_widget::registerWidget('etemplate_widget_file', array('file')); diff --git a/etemplate/inc/class.etemplate_widget_grid.inc.php b/etemplate/inc/class.etemplate_widget_grid.inc.php index d930742b78..896b96b0f3 100644 --- a/etemplate/inc/class.etemplate_widget_grid.inc.php +++ b/etemplate/inc/class.etemplate_widget_grid.inc.php @@ -13,6 +13,28 @@ /** * eTemplate grid widget incl. row(s) and column(s) + * + * Example of a grid containing 3 columns and 2 rows (last one autorepeated) + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *