eGroupWare PHP Cross Reference Groupware Applications

Source: /etemplate/inc/class.etemplate.inc.php - 2508 lines - 92779 bytes - Summary - Text - Print

Description: EGroupware - EditableTemplates - HTML User Interface

   1  <?php
   2  /**
   3  * EGroupware - EditableTemplates - HTML User Interface
   4  *
   5  * @link http://www.egroupware.org
   6  * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
   7  * @author Ralf Becker <RalfBecker@outdoor-training.de>
   8  * @copyright 2002-14 by RalfBecker@outdoor-training.de
   9  * @package etemplate
  10  * @subpackage api
  11  * @version $Id$
  12  */
  13  
  14  /**
  15  * creates dialogs / HTML-forms from eTemplate descriptions
  16  *
  17  * Usage example:
  18  *<code>
  19  * $tmpl = new etemplate('app.template.name');
  20  * $tmpl->exec('app.class.callback',$content_to_show);
  21  *</code>
  22  * This creates a form from the eTemplate 'app.template.name' and takes care that
  23  * the method / public function 'callback' in class 'class' of 'app' gets called
  24  * if the user submits the form. For the complete param's see the description of exec.
  25  *
  26  * etemplate or uietemplate extends boetemplate, all vars and public functions are inherited
  27  */
  28  class etemplate extends boetemplate
  29  {
  30      /**
  31      * integer debug-level or template-name or cell-type or '' = off
  32      * 1=calls to show and process_show, 2=content after process_show,
  33      * 3=calls to show_cell and process_show_cell
  34      *
  35      * @public int/string
  36      */
  37      public $debug;
  38      /**
  39      * Inner width of browser window
  40      *
  41      * @public int
  42      */
  43      public $innerWidth;
  44      /**
  45      * Reference to the content-param of the last call to show, for extensions to use
  46      *
  47      * @public array
  48      */
  49      public $content;
  50      /**
  51      * Reference to the sel_options-param of the last call to show, for extensions to use
  52      *
  53      * @public array
  54      */
  55      public $sel_options;
  56      /**
  57      * Name of the form of the currently processed etemplate
  58      *
  59      * @public string
  60      */
  61      static $name_form='eTemplate';
  62      /**
  63      * Used form-names in this request
  64      *
  65      * @public array
  66      */
  67      static $name_forms=array();
  68      /**
  69      * Basename of the variables (content) in $_POST and id's, usually 'exec',
  70      * if there's not more then one eTemplate on the page (then it will be exec, exec2, exec3, ...
  71      *
  72      * @public string
  73      */
  74      static $name_vars='exec';
  75      /**
  76      * Are we running as sitemgr module or not
  77      *
  78      * @public boolean
  79      */
  80      public $sitemgr=false;
  81      /**
  82       * Javascript to be called, when a widget get's double-clicked (used only by the editor)
  83       * A '%p' gets replace with the colon ':' separated template-name, -version and path of the clicked widget.
  84       *
  85       * @public string
  86       */
  87      public $onclick_handler;
  88      /**
  89       * Does template processes onclick itself, or forwards it to a proxy
  90       *
  91       * @var boolean
  92       */
  93      public $no_onclick = false;
  94      /**
  95       * handler to call for onclick
  96       *
  97       * @var string
  98       */
  99      public $onclick_proxy;
 100  
 101      /**
 102       * Extra options for forms, eg. enctype="multipart/form-data"
 103       *
 104       * @public string
 105       */
 106      static protected $form_options = '';
 107  
 108      /**
 109       * Validation errors from process_show and the extensions, should be set via self::set_validation_error
 110       *
 111       * @public array form_name => message pairs
 112       */
 113      static protected $validation_errors = array();
 114  
 115      /**
 116       * Flag if exec() is called as part of a hook, replaces the 1.6 and earlier $GLOBALS['egw_info']['etemplate']['hooked'] global variable
 117       *
 118       * @var boolean
 119       */
 120      static public $hooked;
 121  
 122      /**
 123       * The following 3 static vars are used to allow eTemplate apps to hook into each other
 124       *
 125       * @var mixed
 126       */
 127      static private $previous_content;
 128      static private $hook_content;
 129      static private $hook_app;
 130  
 131      /**
 132      * constructor of etemplate class, reads an eTemplate if $name is given
 133      *
 134      * @param string $name of etemplate or array with name and other keys
 135      * @param string/array $load_via with keys of other etemplate to load in order to get $name
 136      */
 137  	function __construct($name='',$load_via='')
 138      {
 139          // tell framework old eTemplate apps needs eval and inline javascript :(
 140          egw_framework::csp_script_src_attrs(array('unsafe-eval', 'unsafe-inline'));
 141  
 142          parent::__construct($name,$load_via);
 143  
 144          $this->sitemgr = isset($GLOBALS['Common_BO']) && is_object($GLOBALS['Common_BO']);
 145  
 146          if (($this->innerWidth = (int) $_POST['innerWidth']))
 147          {
 148              $GLOBALS['egw']->session->appsession('innerWidth','etemplate',$this->innerWidth);
 149          }
 150          elseif (!($this->innerWidth = (int) $GLOBALS['egw']->session->appsession('innerWidth','etemplate')))
 151          {
 152              $this->innerWidth = 1018;    // default width for an assumed screen-resolution of 1024x768
 153          }
 154          //echo "<p>_POST[innerWidth]='$_POST[innerWidth]', innerWidth=$this->innerWidth</p>\n";
 155      }
 156  
 157      /**
 158      * Abstracts a html-location-header call
 159      *
 160      * In other UI's than html this needs to call the methode, defined by menuaction or
 161      * open a browser-window for any other links.
 162      *
 163      * @param string/array $params url or array with get-params incl. menuaction
 164      */
 165  	static function location($params='')
 166      {
 167          egw::redirect_link(is_array($params) ? '/index.php' : $params,
 168              is_array($params) ? $params : '');
 169      }
 170  
 171      /**
 172      * Generats a Dialog from an eTemplate - abstract the UI-layer
 173      *
 174      * This is the only function an application should use, all other are INTERNAL and
 175      * do NOT abstract the UI-layer, because they return HTML.
 176      * Generates a webpage with a form from the template and puts process_exec in the
 177      * form as submit-url to call process_show for the template before it
 178      * ExecuteMethod's the given $method of the caller.
 179      *
 180      * @param string $method Methode (e.g. 'etemplate.editor.edit') to be called if form is submitted
 181      * @param array $content with content to fill the input-fields of template, eg. the text-field
 182      *         with name 'name' gets its content from $content['name']
 183      * @param $sel_options array or arrays with the options for each select-field, keys are the
 184      *         field-names, eg. array('name' => array(1 => 'one',2 => 'two')) set the
 185      *         options for field 'name'. ($content['options-name'] is possible too !!!)
 186      * @param array $readonlys with field-names as keys for fields with should be readonly
 187      *         (eg. to implement ACL grants on field-level or to remove buttons not applicable)
 188      * @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
 189      * @param int $output_mode
 190      *     0 = echo incl. navbar
 191      *     1 = return html
 192      *    -1 = first time return html, after use 0 (echo html incl. navbar), eg. for home
 193      *     2 = echo without navbar (eg. for popups)
 194      *     3 = return eGW independent html site
 195      * @param string $ignore_validation if not empty regular expression for validation-errors to ignore
 196      * @param array $changes change made in the last call if looping, only used internaly by process_exec
 197      * @return string html for $output_mode == 1, else nothing
 198      */
 199  	function exec($method,$content,$sel_options='',$readonlys='',$preserv='',$output_mode=0,$ignore_validation='',$changes='')
 200      {
 201          if (!$sel_options)
 202          {
 203              $sel_options = array();
 204          }
 205          if (!$readonlys)
 206          {
 207              $readonlys = array();
 208          }
 209          if (!$preserv)
 210          {
 211              $preserv = array();
 212          }
 213          if (!$changes)
 214          {
 215              $changes = array();
 216          }
 217          if (isset($content['app_header']))
 218          {
 219              $GLOBALS['egw_info']['flags']['app_header'] = $content['app_header'];
 220          }
 221          if ($GLOBALS['egw_info']['flags']['currentapp'] != 'etemplate')
 222          {
 223              translation::add_app('etemplate');    // some extensions have own texts
 224          }
 225          // use different form-names to allows multiple eTemplates in one page, eg. addressbook-view
 226          self::$name_form = 'eTemplate';
 227          if (in_array(self::$name_form,self::$name_forms))
 228          {
 229              self::$name_form .= 1+count(self::$name_forms);
 230              self::$name_vars .= 1+count(self::$name_forms);
 231          }
 232          self::$name_forms[] = self::$name_form;
 233  
 234          self::$request = etemplate_request::read();
 235          self::$request->output_mode = $output_mode;    // let extensions "know" they are run eg. in a popup
 236          self::$request->readonlys = $readonlys;
 237          self::$request->content = $content;
 238          self::$request->changes = $changes;
 239          self::$request->sel_options = $sel_options;
 240          self::$request->preserv = $preserv;
 241          self::$request->method = $method;
 242          self::$request->ignore_validation = $ignore_validation;
 243          self::$request->name_vars = self::$name_vars;
 244  
 245          // tell html5 form validation NOT to validate
 246          if ($ignore_validation) self::$form_options .= ' novalidate="novalidate"';
 247  
 248          if((int) $output_mode == 3)
 249          {
 250              self::$styles_included[$this->name] = True;
 251              return "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n"
 252                  ."<html>\n<head>\n".html::style($this->style)."\n</head>\n"
 253                  ."<body>\n".$this->show($content)."\n</body>\n</html>";
 254          }
 255  
 256          $html = $this->show(self::complete_array_merge($content,$changes),$sel_options,$readonlys,self::$name_vars);
 257  
 258          self::$request->java_script_from_flags = $GLOBALS['egw_info']['flags']['java_script'];
 259          self::$request->java_script_body_tags = array(
 260              'onload'   => egw_framework::set_onload(),
 261              'onunload' => egw_framework::set_onunload(),
 262              'onresize' => egw_framework::set_onresize(),
 263          );
 264          self::$request->java_script_files = egw_framework::js_files();
 265          self::$request->include_xajax = $GLOBALS['egw_info']['flags']['include_xajax'];
 266  
 267          // check if application of template has a app.js file --> load it
 268          list($app) = explode('.',$this->name);
 269          if (file_exists(EGW_SERVER_ROOT.'/'.$app.'/js/app.js'))
 270          {
 271              egw_framework::validate_file('.','app',$app,false);
 272          }
 273  
 274          if (!$this->sitemgr)
 275          {
 276              $hooked = isset(self::$previous_content) || !isset($GLOBALS['egw']->template) ?
 277                  self::$previous_content : $GLOBALS['egw']->template->get_var('phpgw_body');
 278          }
 279          self::$request->hooked = $hooked ? $hooked : self::$hook_content;
 280          self::$request->hook_app = $hooked ? $GLOBALS['egw_info']['flags']['currentapp'] : self::$hook_app;
 281          self::$request->app_header = $GLOBALS['egw_info']['flags']['app_header'];
 282          if (self::$request->output_mode == -1) self::$request->output_mode = 0;
 283          self::$request->template = $this->as_array(2);
 284  
 285          $html = html::form(html::input('etemplate_exec_id',self::$request->id(),'hidden',' id="etemplate_exec_id"').$html.
 286              html::input_hidden(array(
 287                  'submit_button' => '',
 288                  'innerWidth'    => '',
 289              ),'',false),array(),$this->sitemgr ? '' : '/etemplate/process_exec.php?menuaction='.$method,
 290              '',self::$name_form,self::$form_options.
 291              // dont set the width of popups!
 292              ($output_mode != 0 ? '' : ' onsubmit="this.innerWidth.value=window.innerWidth ? window.innerWidth : document.body.clientWidth;"'));
 293              //echo "to_process="; _debug_array(self::$request->to_process);
 294  
 295          egw_framework::validate_file('/etemplate/js/etemplate.js');
 296  
 297          //echo '<p>'.__METHOD__."($method,...) etemplate[hooked]=".(int)self::$hooked.", etemplate[hook_app]='".self::$hook_app."', isset(etemplate[content])=".(int)isset(self::$previous_content)."</p>\n";
 298          if (!$this->sitemgr)
 299          {
 300              // support the old global var, in case old apps like 1.6 infolog use it
 301              if (isset($GLOBALS['egw_info']['etemplate']['hooked'])) self::$hooked = $GLOBALS['egw_info']['etemplate']['hooked'];
 302  
 303              if (!@self::$hooked && (int) $output_mode != 1 && (int) $output_mode != -1)    // not just returning the html
 304              {
 305                  if ($GLOBALS['egw_info']['flags']['currentapp'] != 'etemplate')
 306                  {
 307                      egw_framework::includeCSS('etemplate', 'app');
 308                  }
 309              }
 310              // saving the etemplate content for other hooked etemplate apps (atm. infolog hooked into addressbook)
 311              self::$previous_content =& $html;
 312          }
 313          //echo '<p>'.__METHOD__."($method,...) after show: sitemgr=$this->sitemgr, hooked=".(int)$hooked.", output_mode=$output_mode</p>\n";
 314  
 315          if (!$this->sitemgr && (int) $output_mode != 1 && (int) $output_mode != -1)    // NOT returning html
 316          {
 317              if (!@self::$hooked)
 318              {
 319                  // let framework know, if we are a popup or not ('popup' not true, which is allways used by index.php!)
 320                  $GLOBALS['egw_info']['flags']['nonavbar'] = $output_mode == 2 ? 'popup' : false;
 321                  if((int) $output_mode != 2)
 322                  {
 323                      echo $GLOBALS['egw']->framework->navbar();
 324                  }
 325                  else
 326                  {
 327                      echo $GLOBALS['egw']->framework->header();
 328                      echo '<div id="popupMainDiv">'."\n";
 329                      if ($GLOBALS['egw_info']['user']['apps']['manual'])    // adding a manual icon to every popup
 330                      {
 331                          $manual = new etemplate('etemplate.popup.manual');
 332                          echo $manual->show(array());
 333                          unset($manual);
 334                          echo '<style type="text/css">.ajax-loader { position: absolute; right: 27px; top: 24px; display: none; }</style>'."\n";
 335                          echo '<div class="ajax-loader">'.html::image('phpgwapi','ajax-loader') . '</div>';
 336                      }
 337                  }
 338              }
 339              echo self::$hook_content.$html;
 340  
 341              if (!self::$hooked && (!isset($_GET['menuaction']) || strpos($_SERVER['PHP_SELF'],'process_exec.php') !== false))
 342              {
 343                  if((int) $output_mode == 2)
 344                  {
 345                      echo "</div>\n";
 346                  }
 347                  common::egw_footer();
 348              }
 349          }
 350          if ($this->sitemgr || (int) $output_mode == 1 || (int) $output_mode == -1)    // return html
 351          {
 352              return $html;
 353          }
 354      }
 355  
 356      /**
 357      * Check if we have not ignored validation errors
 358      *
 359      * @param string $ignore_validation='' if not empty regular expression for validation-errors to ignore
 360      * @param string $cname=null name-prefix, which need to be ignored, default self::$name_vars
 361      * @return boolean true if there are not ignored validation errors, false otherwise
 362      */
 363  	static function validation_errors($ignore_validation='',$cname=null)
 364      {
 365          if (is_null($cname)) $cname = self::$name_vars;
 366          //echo "<p>uiself::validation_errors('$ignore_validation','$cname') validation_error="; _debug_array(self::$validation_errors);
 367          if (!$ignore_validation) return count(self::$validation_errors) > 0;
 368  
 369          foreach(self::$validation_errors as $name => $error)
 370          {
 371              if (!self::ignore_validation_match($name, $ignore_validation))
 372              {
 373                  //echo "<p>uiself::validation_errors('$ignore_validation','$cname') name='$name' ($error) not ignored!!!</p>\n";
 374                  return true;
 375              }
 376              //echo "<p>uiself::validation_errors('$ignore_validation','$cname') name='$name' ($error) ignored</p>\n";
 377          }
 378          return false;
 379      }
 380  
 381      /**
 382       * Check if given form-name matches ai ignore-validation rule
 383       *
 384       * @param string $ignore_validation='' if not empty regular expression for validation-errors to ignore
 385       * @param string $cname=null name-prefix, which need to be ignored, default self::$name_vars
 386       * @param string $cname
 387       * @return boolean
 388       */
 389  	static function ignore_validation_match($form_name, $ignore_validation, $cname=null)
 390      {
 391          if (is_null($cname)) $cname = self::$name_vars;
 392          if ($cname) $form_name = preg_replace('/^'.$cname.'\[([^\]]+)\](.*)$/','\\1\\2', $form_name);
 393  
 394          return empty($ignore_validation) ||
 395              $ignore_validation[0] == '/' && preg_match($ignore_validation, $form_name) ||
 396              $ignore_validation[0] != '/' && $ignore_validation == $form_name;
 397      }
 398  
 399      /**
 400      * Makes the necessary adjustments to _POST before it calls the app's method
 401      *
 402      * This function is only to submit forms to, create with exec.
 403      * All eTemplates / forms executed with exec are submited to this function
 404      * via /etemplate/process_exec.php?menuaction=<callback>. We cant use the global index.php as
 405      * it would set some constants to etemplate instead of the calling app.
 406      * process_exec then calls process_show for the eTemplate (to adjust the content of the _POST) and
 407      * ExecMethod's the given callback from the app with the content of the form as first argument.
 408      *
 409      * @return mixed false if no sessiondata and $this->sitemgr, else the returnvalue of exec of the method-calls
 410      */
 411  	function process_exec($etemplate_exec_id = null, $submit_button = null, $exec = null, $type = 'regular' )
 412      {
 413          if(!$etemplate_exec_id) $etemplate_exec_id = $_POST['etemplate_exec_id'];
 414          if(!$submit_button) $submit_button = $_POST['submit_button'];
 415          if(!$exec) $exec = $_POST;
 416  
 417          //echo "process_exec: _POST ="; _debug_array($_POST);
 418          if (!$etemplate_exec_id || !(self::$request = etemplate_request::read($etemplate_exec_id)))
 419          {
 420              if ($this->sitemgr) return false;
 421              //echo "uitemplate::process_exec() id='$_POST[etemplate_exec_id]' invalid session-data !!!"; _debug_array($_SESSION);
 422              // this prevents an empty screen, if the sessiondata gets lost somehow
 423              $redirect = array(
 424                  'menuaction' => $_GET['menuaction'],
 425              );
 426              if (!$_POST && $_SERVER['REQUEST_METHOD'] == 'POST')
 427              {
 428                  $redirect['post_empty'] = 1;
 429                  // check if we have a failed upload, because user tried to uploaded a file
 430                  // bigger then php.ini setting post_max_size
 431                  // in that case the webserver calls PHP with $_POST === array()
 432                  if (substr($_SERVER['CONTENT_TYPE'],0,19) == 'multipart/form-data' &&
 433                      $_SERVER['CONTENT_LENGTH'] > self::km2int(ini_get('post_max_size')))
 434                  {
 435                      $redirect['failed_upload'] = 1;
 436                      $redirect['msg'] = lang('Error uploading file!')."\n".self::max_upload_size_message();
 437                  }
 438              } else if ($_SERVER['REQUEST_METHOD'] == 'GET') {
 439                  // Pass along any parameters
 440                  $redirect = $_GET;
 441              }
 442              $this->location($redirect);
 443          }
 444          self::$name_vars = self::$request->name_vars;
 445          if (isset($submit_button) && !empty($submit_button))
 446          {
 447              self::set_array($exec,$submit_button,'pressed');
 448          }
 449          $content = $exec[self::$name_vars];
 450          if (!is_array($content))
 451          {
 452              $content = array();
 453          }
 454          $this->init(self::$request->template);
 455          //echo "process_exec($this->name) content ="; _debug_array($content);
 456          if ($GLOBALS['egw_info']['flags']['currentapp'] != 'etemplate')
 457          {
 458              $GLOBALS['egw']->translation->add_app('etemplate');    // some extensions have own texts
 459          }
 460          $this->process_show($content,self::$request->to_process,self::$name_vars,$type);
 461  
 462          self::$loop |= !$this->canceled && $this->button_pressed &&
 463              $this->validation_errors(self::$request->ignore_validation);    // set by process_show
 464  
 465          // If a tab has an error on it, change to that tab
 466          foreach(self::$validation_errors as $form_name => $msg)
 467          {
 468              $name = $this->template_name($form_name);
 469              if (!$this->get_widget_by_name($name))
 470              {
 471                  foreach($this->get_widgets_by_type('tab') as $widget)
 472                  {
 473                      $tab_name = $tabs = $widget['name'];
 474                      if (strpos($tabs,'=') !== false) list($tab_name,$tabs) = explode('=',$tabs,2);
 475                      foreach(explode('|',$tabs) as $tab)
 476                      {
 477                          if (strpos('.',$tab) === false) $tab = $this->name.'.'.$tab;
 478                          $tab_tpl = new etemplate($tab);
 479                          if ($tab_tpl->get_widget_by_name($name))
 480                          {
 481                              $content[$tab_name] = $tab;
 482                              break 3;
 483                          }
 484                          elseif($name[0] == '#')
 485                          {
 486                              foreach($tab_tpl->get_widgets_by_type('customfields') as $cf_widget)
 487                              {
 488                                  if (empty($cf_widget['name']))
 489                                  {
 490                                      $content[$tab_name] = $tab;
 491                                      break 4;
 492                                  }
 493                              }
 494                          }
 495                      }
 496                  }
 497                  // widget NOT found --> as last resort add valdation message to $content['msg']
 498                  $content['msg'] .= ($content['msg'] ? "\n" : '').$msg;
 499              }
 500          }
 501  
 502          //echo "process_exec($this->name) process_show(content) ="; _debug_array($content);
 503          //echo "process_exec($this->name) session_data[changes] ="; _debug_array(self::$request->changes);
 504          $content = self::complete_array_merge(self::$request->changes,$content);
 505          //echo "process_exec($this->name) merge(changes,content) ="; _debug_array($content);
 506  
 507          if (self::$loop && $type == 'regular')    // only loop for regular (not ajax_submit) requests
 508          {
 509              if (self::$request->hooked != '')    // set previous phpgw_body if we are called as hook
 510              {
 511                  self::$hook_content = self::$request->hooked;
 512                  $GLOBALS['egw_info']['flags']['currentapp'] = self::$hook_app = self::$request->hook_app;
 513  
 514                  // Prevent previous content form names from being used again in exec()
 515                  preg_match_all('/<form .+name="('. self::$name_form . '[\d]*)"/',self::$request->hooked, $used);
 516                  if(is_array($used[1]) && count($used[1]))
 517                  {
 518                      self::$name_forms += $used[1];
 519                  }
 520              }
 521              if(self::$request->include_xajax) $GLOBALS['egw_info']['flags']['include_xajax'] = true;
 522  
 523              if (!empty(self::$request->app_header))
 524              {
 525                  $GLOBALS['egw_info']['flags']['app_header'] = self::$request->app_header;
 526              }
 527  
 528              $GLOBALS['egw_info']['flags']['java_script'] .= self::$request->java_script_from_flags;
 529              if (!empty(self::$request->java_script_body_tags))
 530              {
 531                  foreach (self::$request->java_script_body_tags as $tag => $code)
 532                  {
 533                      call_user_func('egw_framework::set_'.$tag,$code);
 534                  }
 535              }
 536              if (is_array(self::$request->java_script_files))
 537              {
 538                  $files = egw_framework::js_files();
 539                  if (is_array($files))
 540                  {
 541                      $files = array_unique(array_merge($files,self::$request->java_script_files));
 542                  }
 543                  else
 544                  {
 545                      $files = self::$request->java_script_files;
 546                  }
 547                  egw_framework::js_files($files);
 548              }
 549  
 550              //echo "<p>process_exec($this->name): <font color=red>loop is set</font>, content=</p>\n"; _debug_array(self::complete_array_merge(self::$request->content,$content));
 551              return $this->exec(self::$request->method,self::complete_array_merge(self::$request->content,$content),
 552                  self::$request->sel_options,self::$request->readonlys,self::$request->preserv,
 553                  self::$request->output_mode,self::$request->ignore_validation,$content);
 554          }
 555          else
 556          {
 557              //echo "<p>process_exec($this->name): calling ".($type == 'regular' ? self::$request->method : $_GET['menuaction'])."</p>\n";
 558              return ExecMethod($type == 'regular' ? self::$request->method : $_GET['menuaction'],
 559                  self::complete_array_merge(self::$request->preserv,$content));
 560          }
 561      }
 562  
 563      /**
 564       * Message containing the max Upload size from the current php.ini settings
 565       *
 566       * We have to take the smaler one of upload_max_filesize AND post_max_size-2800 into account.
 567       * memory_limit does NOT matter any more, because of the stream-interface of the vfs.
 568       *
 569       * @param int &$max_upload=null on return max. upload size in byte
 570       * @return string
 571       */
 572  	static function max_upload_size_message(&$max_upload=null)
 573      {
 574          $upload_max_filesize = ini_get('upload_max_filesize');
 575          $post_max_size = ini_get('post_max_size');
 576          $max_upload = min(self::km2int($upload_max_filesize),self::km2int($post_max_size)-2800);
 577  
 578          return lang('Maximum size for uploads').': '.egw_vfs::hsize($max_upload).
 579              " (php.ini: upload_max_filesize=$upload_max_filesize, post_max_size=$post_max_size)";
 580      }
 581  
 582      /**
 583       * Convert numbers like '32M' or '512k' to integers
 584       *
 585       * @param string $size
 586       * @return int
 587       */
 588  	private static function km2int($size)
 589      {
 590          if (!is_numeric($size))
 591          {
 592              switch(strtolower(substr($size,-1)))
 593              {
 594                  case 'm':
 595                      $size = 1024*1024*(int)$size;
 596                      break;
 597                  case 'k':
 598                      $size = 1024*(int)$size;
 599                      break;
 600              }
 601          }
 602          return (int)$size;
 603      }
 604  
 605      /**
 606      * process the values transfered with the javascript function values2url
 607      *
 608      * The returned array contains the preserved values overwritten (only!) with the variables named in values2url
 609      *
 610      * @return array/boolean content array or false on error
 611      */
 612  	function process_values2url()
 613      {
 614          //echo "process_exec: _GET ="; _debug_array($_GET);
 615          if (!$_GET['etemplate_exec_id'] || !($request = etemplate_request::read($_GET['etemplate_exec_id'])))
 616          {
 617              return false;
 618          }
 619          self::$name_vars = $request->name_vars;
 620  
 621          $content = $_GET[self::$name_vars];
 622          if (!is_array($content))
 623          {
 624              $content = array();
 625          }
 626          $this->process_show($content,$request->to_process,self::$name_vars);
 627  
 628          return self::complete_array_merge($request->preserv,$content);
 629      }
 630  
 631      /**
 632       * Flag if the styles of a certain template are already included
 633       *
 634       * @var array template-name => boolean
 635       */
 636      static private $styles_included = array();
 637  
 638      /**
 639      * creates HTML from an eTemplate
 640      *
 641      * This is done by calling show_cell for each cell in the form. show_cell itself
 642      * calls show recursivly for each included eTemplate.
 643      * You could use it in the UI-layer of an app, just make shure to call process_show !!!
 644      * This is intended as internal function and should NOT be called by new app's direct,
 645      * as it deals with HTML and is so UI-dependent, use exec instead.
 646      *
 647      * @internal
 648      * @param array $content with content for the cells, keys are the names given in the cells/form elements
 649      * @param array $sel_options with options for the selectboxes, keys are the name of the selectbox
 650      * @param array $readonlys with names of cells/form-elements to be not allowed to change
 651      *         This is to facilitate complex ACL's which denies access on field-level !!!
 652      * @param string $cname basename of names for form-elements, means index in $_POST
 653      *         eg. $cname='cont', element-name = 'name' returned content in $_POST['cont']['name']
 654      * @param string $show_c name/index for name expansion
 655      * @param string $show_row name/index for name expansion
 656      * @return string the generated HTML
 657      */
 658  	function show($content,$sel_options='',$readonlys='',$cname='',$show_c=0,$show_row=0)
 659      {
 660          if (!$sel_options)
 661          {
 662              $sel_options = array();
 663          }
 664          // make it globaly availible for show_cell and show_grid, or extensions
 665          $this->sel_options =& $sel_options;
 666  
 667          if (!$readonlys)
 668          {
 669              $readonlys = array();
 670          }
 671          if (++$this->already_showed > 1) return '';    // prefens infinit self-inclusion
 672  
 673          if (is_int($this->debug) && $this->debug >= 1 || $this->name && $this->debug == $this->name)
 674          {
 675              echo "<p>etemplate.show($this->name): $cname =\n"; _debug_array($content);
 676              echo "readonlys="; _debug_array($readonlys);
 677          }
 678          if (!is_array($content))
 679          {
 680              $content = array();    // happens if incl. template has no content
 681          }
 682          // make the content availible as class-public for extensions
 683          $this->content =& $content;
 684  
 685          $html = "\n\n<!-- BEGIN eTemplate $this->name -->\n<div id=\"".str_replace('"','&quot;',$this->name)."\">\n\n";
 686          if (!self::$styles_included[$this->name])
 687          {
 688              self::$styles_included[$this->name] = True;
 689              $html .= html::style($this->style)."\n\n";
 690          }
 691          $path = '/';
 692          foreach ($this->children as $n => $child)
 693          {
 694              $h = $this->show_cell($child,$content,$readonlys,$cname,$show_c,$show_row,$nul,$class,$path.$n);
 695              $html .= $class || $child['align'] ? html::div($h,html::formatOptions(array(
 696                  $class,
 697                  $child['align'],
 698              ),'class,align')) : $h;
 699          }
 700          return $html."\n</div>\n<!-- END eTemplate $this->name -->\n\n";
 701      }
 702  
 703      /**
 704      * Get the color of a category
 705      *
 706      * For multiple cats, the first with a color is used
 707      *
 708      * @param int/string $cats multiple comma-separated cat_id's
 709      * @return string
 710      */
 711  	static function cats2color($cats)
 712      {
 713          static $cat2color;
 714  
 715          // ACL check
 716          $cats = $GLOBALS['egw']->categories->check_list(EGW_ACL_READ,$cats);
 717  
 718          if (!$cats) return null;
 719  
 720          if (isset($cat2color[$cats]))
 721          {
 722              return $cat2color[$cats];
 723          }
 724  
 725          foreach(explode(',',$cats) as $cat)
 726          {
 727              if (isset($cat2color[$cat]))
 728              {
 729                  return $cat2color[$cat];
 730              }
 731              $data = categories::id2name($cat,'data');
 732  
 733              if (is_array($data) && ($color = $data['color']))
 734              {
 735                  //echo "<p>cats2color('$cats')=$color</p>\n";
 736                  return $cat2color[$cats] = $cat2color[$cat] = $color;
 737              }
 738          }
 739          return null;
 740      }
 741  
 742      /**
 743      * creates HTML from an eTemplate
 744      *
 745      * This is done by calling show_cell for each cell in the form. show_cell itself
 746      * calls show recursivly for each included eTemplate.
 747      * You can use it in the UI-layer of an app, just make shure to call process_show !!!
 748      * This is intended as internal function and should NOT be called by new app's direct,
 749      * as it deals with HTML and is so UI-dependent, use exec instead.
 750      *
 751      * @internal
 752      * @param array $grid representing a grid
 753      * @param array $content with content for the cells, keys are the names given in the cells/form elements
 754      * @param array $readonlys with names of cells/form-elements to be not allowed to change
 755      *         This is to facilitate complex ACL's which denies access on field-level !!!
 756      * @param string $cname basename of names for form-elements, means index in $_POST
 757      *        eg. $cname='cont', element-name = 'name' returned content in $_POST['cont']['name']
 758      * @param string $show_c name/index for name expansion
 759      * @param string $show_row name/index for name expansion
 760      * @param string $path path in the widget tree
 761      * @return string the generated HTML
 762      */
 763  	private function show_grid(&$grid,$content,$readonlys='',$cname='',$show_c=0,$show_row=0,$path='')
 764      {
 765          if (!$readonlys)
 766          {
 767              $readonlys = array();
 768          }
 769          if (is_int($this->debug) && $this->debug >= 2 || $grid['name'] && $this->debug == $grid['name'] ||
 770              $this->name && $this->debug == $this->name)
 771          {
 772              echo "<p>etemplate.show_grid($grid[name]): $cname =\n"; _debug_array($content);
 773          }
 774          if (!is_array($content))
 775          {
 776              $content = array();    // happens if incl. template has no content
 777          }
 778          $content += array(    // for var-expansion in names in show_cell
 779              '.c' => $show_c,
 780              '.col' => $this->num2chrs($show_c-1),
 781              '.row' => $show_row
 782          );
 783          $rows = array();
 784  
 785          $data = &$grid['data'];
 786          reset($data);
 787          if (isset($data[0]))
 788          {
 789              list(,$opts) = each($data);
 790          }
 791          else
 792          {
 793              $opts = array();
 794          }
 795          $row_id = $content['_row_id'];
 796  
 797          $max_cols = $grid['cols'];
 798          for ($r = 0; $row = 1+$r /*list($row,$cols) = each($data)*/; ++$r)
 799          {
 800              if (!(list($r_key) = each($data)))    // no further row
 801              {
 802                  if (!(($this->autorepeat_idx($cols['A'],0,$r,$idx,$idx_cname,false,$content) && $idx_cname) ||
 803                          (in_array($cols['A']['type'], array('vbox','hbox','box')) && $this->autorepeat_idx($cols['A'][1],0,$r,$idx,$idx_cname,false,$content) && $idx_cname) ||
 804                      ($this->autorepeat_idx($cols['B'],1,$r,$idx,$idx_cname,false,$content) && $idx_cname)) ||
 805                      !$this->isset_array($content,$idx_cname))
 806                  {
 807                      break;                         // no auto-row-repeat
 808                  }
 809              }
 810              else
 811              {
 812                  $cols = &$data[$r_key];
 813                  $part = '';    // '' = body-prefix
 814                  list($height,$disabled,$part) = explode(',',$opts["h$row"]);
 815                  $class = $opts["c$row"];
 816              }
 817              if ($disabled != '' && $this->check_disabled($disabled,$content,$r))
 818              {
 819                  continue;    // row is disabled
 820              }
 821              if ($part) $row = $part[0].$row;    // add part prefix
 822              $rows[".$row"] .= html::formatOptions($height,'height');
 823              list($cl) = explode(',',$class);
 824              if ($cl == '@' || $cl && strpos($cl,'$') !== false)
 825              {
 826                  $cl = $this->expand_name($cl,0,$r,$content['.c'],$content['.row'],$content);
 827                  if (!$cl || preg_match('/(^| )([0-9,]+)( |$)/',$cl,$matches))
 828                  {
 829                      if (($color = $this->cats2color($matches[2])))
 830                      {
 831                          $rows[".$row"] .= ' style="background-color: '.$color.';"';
 832                      }
 833                      $cl = str_replace($matches[2],'row',$cl);
 834                  }
 835              }
 836              if ($cl == 'nmr' || substr($cl,0,3) == 'row')    // allow to have further classes behind row
 837              {
 838                  $cl = 'row_'.($nmr_alternate++ & 1 ? 'off' : 'on').substr($cl,3); // alternate color
 839              }
 840              $cl = isset(self::$class_conf[$cl]) ? self::$class_conf[$cl] : $cl;
 841              $rows[".$row"] .= html::formatOptions($cl,'class');
 842              $rows[".$row"] .= html::formatOptions($class,',valign');
 843              // set row-id, if requested
 844              if ($row_id && isset($content[$r][$row_id]))
 845              {
 846                  $rows[".$row"] .= ' id="'.htmlspecialchars($content[$r][$row_id]).'"';
 847              }
 848              reset ($cols);
 849              $row_data = array();
 850              for ($c = 0; True /*list($col,$cell) = each($cols)*/; ++$c)
 851              {
 852                  $col = $this->num2chrs($c);
 853                  if (!(list($c_key) = each($cols)))        // no further cols
 854                  {
 855                      // only check if the max. column-number reached so far is exeeded
 856                      // otherwise the rows have a differen number of cells and it saved a lot checks
 857                      if ($c >= $max_cols)
 858                      {
 859                          if (!$this->autorepeat_idx($cell,$c,$r,$idx,$idx_cname,True,$content) ||
 860                              !$this->isset_array($content,$idx))
 861                          {
 862                              break;    // no auto-col-repeat
 863                          }
 864                          $max_cols = $c+1;
 865                      }
 866                  }
 867                  else
 868                  {
 869                      $cell = $cols[$c_key];
 870                      list($col_width,$col_disabled) = explode(',',$opts[$col]);
 871  
 872                      if (!$cell['height'])    // if not set, cell-height = height of row
 873                      {
 874                          $cell['height'] = $height;
 875                      }
 876                      if (!$cell['width'])    // if not set, cell-width = width of column or table
 877                      {
 878                          list($col_span) = explode(',',$cell['span']);
 879                          if ($col_span == 'all' && !$c)
 880                          {
 881                              list($cell['width']) = explode(',',$this->size);
 882                          }
 883                          else
 884                          {
 885                              $cell['width'] = $col_width;
 886                          }
 887                      }
 888                  }
 889                  if ($col_disabled != '' && $this->check_disabled($col_disabled,$content,$r,$c))
 890                  {
 891                      continue;    // col is disabled
 892                  }
 893                  $align_was = $cell['align'];
 894                  $row_data[$col] = $this->show_cell($cell,$content,$readonlys,$cname,$c,$r,$span,$cl,$path.'/'.$r_key.$c_key);
 895  
 896                  if ($row_data[$col] == '' && $this->rows == 1)
 897                  {
 898                      unset($row_data[$col]);    // omit empty/disabled cells if only one row
 899                      continue;
 900                  }
 901                  if (strlen($cell['onclick']) > 1)
 902                  {
 903                      $onclick = $cell['onclick'];
 904                      if (strpos($onclick,'$') !== false || $onclick[0] == '@')
 905                      {
 906                          $onclick = $this->expand_name($onclick,$c,$r,$content['.c'],$content['.row'],$content);
 907                      }
 908                      $row_data[".$col"] .= ' onclick="'.$this->js_pseudo_funcs($onclick,$cname).'"' .self::get_id('',$cell['name'],$cell['id']);
 909                  }
 910                  $colspan = $span == 'all' ? $grid['cols']-$c : 0+$span;
 911                  if ($colspan > 1)
 912                  {
 913                      $row_data[".$col"] .= " colspan=\"$colspan\"";
 914                      for ($i = 1; $i < $colspan; ++$i,++$c)
 915                      {
 916                          each($cols);    // skip next cell(s)
 917                      }
 918                  }
 919                  else
 920                  {
 921                      list($width,$disable) = explode(',',$opts[$col]);
 922                      if ($width)        // width only once for a non colspan cell
 923                      {
 924                          $row_data[".$col"] .= " width=\"$width\"";
 925                          $opts[$col] = "0,$disable";
 926                      }
 927                  }
 928                  $row_data[".$col"] .= html::formatOptions($cell['align']?$cell['align']:($align_was?$align_was:'left'),'align');
 929                  // allow to set further attributes in the tablecell, beside the class
 930                  if (is_array($cl))
 931                  {
 932                      foreach($cl as $attr => $val)
 933                      {
 934                          if ($attr != 'class' && $val)
 935                          {
 936                              $row_data['.'.$col] .= ' '.$attr.'="'.$val.'"';
 937                          }
 938                      }
 939                      $cl = $cl['class'];
 940                  }
 941                  $cl = $this->expand_name(isset(self::$class_conf[$cl]) ? self::$class_conf[$cl] : $cl,
 942                      $c,$r,$show_c,$show_row,$content);
 943                  // else the class is set twice, in the table and the table-cell, which is not good for borders
 944                  if ($cl && $cell['type'] != 'template' && $cell['type'] != 'grid')
 945                  {
 946                      $row_data[".$col"] .= html::formatOptions($cl,'class');
 947                  }
 948              }
 949              $rows[$row] = $row_data;
 950          }
 951          if (!$rows) return '';
 952  
 953          list($width,$height,,,,,$overflow) = $options = explode(',',$grid['size']);
 954          if ($overflow && $height)
 955          {
 956              $options[1] = '';    // set height in div only
 957          }
 958          $html = html::table($rows,html::formatOptions($options,'width,height,border,class,cellspacing,cellpadding').
 959              html::formatOptions($grid['span'],',class').
 960              html::formatOptions($grid['name']?self::form_name($cname,$grid['name']):'','id'));
 961  
 962          if (!empty($overflow))
 963          {
 964              if (is_numeric($height)) $height .= 'px';
 965              if (is_numeric($width)) $width .= 'px';
 966              if ($width == '100%') $overflow .= '; overflow-x: hidden';    // no horizontal scrollbar
 967              $div_style=' style="'.($width?"width: $width; ":'').($height ? "height: $height; ":'')."overflow: $overflow;\"";
 968              $html = html::div($html,$div_style);
 969          }
 970  
 971          // initialise egw_actions for nextmatch widget, if egwGridView_grid CSS class set
 972          if ($options[3] == 'egwGridView_grid')
 973          {
 974              $html .= nextmatch_widget::init_egw_actions($content['_actions'], $content['action_links'], $this->name);
 975          }
 976  
 977          return "\n\n<!-- BEGIN grid $grid[name] -->\n$html<!-- END grid $grid[name] -->\n\n";
 978      }
 979  
 980      /**
 981      * build the name of a form-element from a basename and name
 982      *
 983      * name and basename can contain sub-indices in square bracets, eg. basename="base[basesub1][basesub2]"
 984      * and name = "name[sub]" gives "base[basesub1][basesub2][name][sub]"
 985      *
 986      * @param string $cname basename
 987      * @param string $name name
 988      * @return string complete form-name
 989      */
 990  	static function form_name($cname,$name)
 991      {
 992          if(is_object($name)) return '';
 993  
 994          $name_parts = explode('[',str_replace(']','',$name));
 995          if (!empty($cname))
 996          {
 997              array_unshift($name_parts,$cname);
 998          }
 999          $form_name = array_shift($name_parts);
1000          if (count($name_parts))
1001          {
1002              $form_name .= '['.implode('][',$name_parts).']';
1003          }
1004          return $form_name;
1005      }
1006  
1007      /**
1008      * strip the prefix of a form-element from a form_name
1009      * This function removes the prefix of form_name().  It takes a name like base[basesub1][basesub2][name][sub]
1010      * and gives basesub1[basesub2][name][sub]
1011      *
1012      * @param string form_name
1013      * @return string name without prefix
1014      */
1015  	static private function template_name($form_name)
1016      {
1017          $parts = explode('[',str_replace(']','',$form_name));
1018  
1019          array_shift($parts);    // remove exec
1020  
1021          $name = array_shift($parts);
1022  
1023          if ($parts) $name .= '['.implode('][',$parts).']';
1024  
1025          return $name;
1026      }
1027  
1028      static private $class_conf = array('nmh' => 'th','nmr0' => 'row_on','nmr1' => 'row_off');
1029  
1030      /**
1031      * generates HTML for one widget (input-field / cell)
1032      *
1033      * calls show to generate included eTemplates. Again only an INTERMAL function.
1034      *
1035      * @internal
1036      * @param array $cell with data of the cell: name, type, ...
1037      * @param array $content with content for the cells, keys are the names given in the cells/form elements
1038      * @param array $readonlys with names of cells/form-elements to be not allowed to change
1039      *         This is to facilitate complex ACL's which denies access on field-level !!!
1040      * @param string $cname basename of names for form-elements, means index in $_POST
1041      *        eg. $cname='cont', element-name = 'name' returned content in $_POST['cont']['name']
1042      * @param string $show_c name/index for name expansion
1043      * @param string $show_row name/index for name expansion
1044      * @param string &$span on return number of cells to span or 'all' for the rest (only used for grids)
1045      * @param string &$class on return the css class of the cell, to be set in the <td> tag
1046      * @param string $path path in the widget tree
1047      * @return string the generated HTML
1048      */
1049  	private function show_cell(&$cell,$content,$readonlys,$cname,$show_c,$show_row,&$span,&$class,$path='')
1050      {
1051          if ($this->debug && (is_int($this->debug) && $this->debug >= 3 || $this->debug == $cell['type']))
1052          {
1053              echo "<p>etemplate.show_cell($this->name,name='$cell['name']}',type='$cell['type']}',cname='$cname',...,'$path')</p>\n";
1054          }
1055          list($span) = explode(',',$cell['span']);    // evtl. overriten later for type template
1056  
1057          if ($cell['name'][0] == '@' && $cell['type'] != 'template')
1058          {
1059              $cell['name'] = $this->get_array($content,$this->expand_name(substr($cell['name'],1),
1060                  $show_c,$show_row,$content['.c'],$content['.row'],$content));
1061          }
1062          $name = $this->expand_name($cell['name'],$show_c,$show_row,$content['.c'],$content['.row'],$content);
1063          // allow names like "tabs=one|two|three", which will be equal to just "tabs"
1064          // eg. for tabs to use a name independent of the tabs contained
1065          if (is_string($name) && strpos($name,'=') !== false)
1066          {
1067              list($name) = explode('=',$name);
1068          }
1069          $form_name = self::form_name($cname,$name);
1070  
1071          $value = $this->get_array($content,$name);
1072  
1073          $options = '';
1074          if ($readonly = $cell['readonly'] && $readonlys[$name] !== false ||     // allow to overwrite readonly settings of a cell
1075              @$readonlys[$name] && !is_array($readonlys[$name]) || $readonlys['__ALL__'] && (!is_string($name) || $readonlys[$name] !== false) ||
1076              !empty($name) && is_string($name) && ($p = strrpos($name,'[')) !== false && ($parent=substr($name,0,$p)) && $readonlys[$parent])    // allow also set parent readonly (instead each child)
1077          {
1078              $options .= ' readonly="readonly"';
1079          }
1080          if ((int) $cell['tabindex']) $options .= ' tabindex="'.(int)$cell['tabindex'].'"';
1081          if ($cell['accesskey']) $options .= ' accesskey="'.html::htmlspecialchars($cell['accesskey']).'"';
1082  
1083          if (is_string($cell['size']) && (strchr($cell['size'],'$') || $cell['size'][0] == '@'))    // expand cell['size'] for the button-disabled-check now
1084          {
1085              $cell['size'] = $this->expand_name($cell['size'],$show_c,$show_row,$content['.c'],$content['.row'],$content);
1086          }
1087          if ($cell['type'] == 'template' && !$this->check_disabled($cell['disabled'], $content))
1088          {
1089              // template is NOT disabled (eg. in editor)
1090          }
1091          elseif ($cell['disabled'] && $readonlys[$name] !== false || $readonly && in_array($cell['type'],array('button','buttononly','image','progress')) && strpos($cell['size'],',') === false)
1092          {
1093              if ($this->rows == 1)
1094              {
1095                  return '';    // if only one row omit cell
1096              }
1097              $cell = $this->empty_cell('label','',array('span' => $cell['span'])); // show nothing (keep the css class!)
1098              $name = $value = '';
1099          }
1100          $extra_label = True;
1101  
1102          if (strchr($cell['onchange'],'$') || $cell['onchange'][0] == '@')
1103          {
1104              $cell['onchange'] = $this->expand_name($cell['onchange'],$show_c,$show_row,$content['.c'],$content['.row'],$content);
1105          }
1106          if ($cell['type'][0] == '@')
1107          {
1108              $cell['type'] = $this->expand_name($t=$cell['type'],$show_c,$show_row,$content['.c'],$content['.row'],$content);
1109          }
1110          // the while loop allows to build extensions from other extensions
1111          // please note: only the first extension's post_process function is called !!!
1112          list($type,$sub_type) = explode('-',$cell['type']);
1113          while ((!self::$types[$cell['type']] || !empty($sub_type)) && $this->haveExtension($type,'pre_process'))
1114          {
1115              //echo "<p>pre_process($cell[name]/$cell[type])</p>\n";
1116              if (is_string($cell['size']) && (strchr($cell['size'],'$') || $cell['size'][0] == '@'))
1117              {
1118                  $cell['size'] = $this->expand_name($cell['size'],$show_c,$show_row,$content['.c'],$content['.row'],$content);
1119              }
1120              if (strchr($cell['onchange'],'$') || $cell['onchange'][0] == '@')
1121              {
1122                  $cell['onchange'] = $this->expand_name($cell['onchange'],$show_c,$show_row,$content['.c'],$content['.row'],$content);
1123              }
1124              if (!$ext_type) $ext_type = $type;
1125              // if readonlys[__ALL__] is set, also set readonlys[$name] (extensions can mark themselfs as 'noReadonlysALL', eg. tab-widget!)
1126              if ($readonlys['__ALL__'] && $readonlys[$name] !== false && !$this->haveExtension($type,'noReadonlysALL'))
1127              {
1128                  $readonlys[$name] = true;
1129              }
1130              $extra_label = $this->extensionPreProcess($type,$form_name,$value,$cell,$readonlys[$name]);
1131  
1132              $readonly = $cell['readonly'] !== false && ($readonly || $cell['readonly']);    // might be set or unset (===false) by extension
1133  
1134              self::set_array($content,$name,$value);
1135  
1136              if ($cell['type'] == $type.'-'.$sub_type) break;    // stop if no further type-change
1137  
1138              list($type,$sub_type) = explode('-',$cell['type']);
1139          }
1140          list(,$class) = explode(',',$cell['span']);    // might be set by extension
1141          if (strchr($class,'$') || $class[0] == '@')
1142          {
1143              $class = $this->expand_name($class,$show_c,$show_row,$content['.c'],$content['.row'],$content);
1144          }
1145          if ($cell['needed'] && !in_array($cell['type'],array('button','buttononly')))
1146          {
1147              $class .= ' inputRequired';
1148          }
1149          $cell_options = $cell['size'];
1150          if (strchr($cell_options,'$') || $cell_options[0] == '@')
1151          {
1152              $cell_options = $this->expand_name($cell_options,$show_c,$show_row,$content['.c'],$content['.row'],$content);
1153          }
1154          $label = $cell['label'];
1155          if (strchr($label,'$') || $label[0] == '@')
1156          {
1157              $label = $this->expand_name($label,$show_c,$show_row,$content['.c'],$content['.row'],$content);
1158          }
1159          $help = $cell['help'];
1160          if (strchr($help,'$') || $help[0] == '@')
1161          {
1162              $no_lang_on_help = true;
1163              $help = $this->expand_name($help,$show_c,$show_row,$content['.c'],$content['.row'],$content);
1164          }
1165          $blur = $cell['blur'][0] == '@' ? $this->get_array($content,substr($cell['blur'],1)) :
1166              (strlen($cell['blur']) <= 1 ? $cell['blur'] : lang($cell['blur']));
1167  
1168          if ($blur)
1169          {
1170              if ((string)$value === '')
1171              {
1172                  $value = $blur;
1173              }
1174              $onFocus .= "if(this.value=='".addslashes(html::htmlspecialchars($blur))."') this.value='';";
1175              $onBlur  .= "if(this.value=='') this.value='".addslashes(html::htmlspecialchars($blur))."';";
1176          }
1177          if ($help)
1178          {
1179              if ((int)$cell['no_lang'] < 2 && !$no_lang_on_help)
1180              {
1181                  if (($use_tooltip_for_help = $help[0] == '|')) $help = substr($help,1);
1182                  $help = lang($help);
1183              }
1184              if (substr($help,0,5) == 'call:')
1185              {
1186                  $options .= ' onMouseOver="'.html::htmlspecialchars(substr($help,5)).'"';
1187              }
1188              elseif (($use_tooltip_for_help = $use_tooltip_for_help || strpos($help,'<') !== false && strip_tags($help) != $help))    // helptext is html => use a tooltip
1189              {
1190                  $options .= html::tooltip($help);
1191              }
1192              else    // "regular" help-text in the statusline
1193              {
1194                  $onFocus .= "self.status='".addslashes(html::htmlspecialchars($help))."'; return true;";
1195                  $onBlur  .= "self.status=''; return true;";
1196                  if (in_array($cell['type'],array('button','buttononly','file')))    // for button additionally when mouse over button
1197                  {
1198                      $options .= " onMouseOver=\"self.status='".addslashes(html::htmlspecialchars($help))."'; return true;\"";
1199                      $options .= " onMouseOut=\"self.status=''; return true;\"";
1200                  }
1201              }
1202          }
1203          if ($onBlur)
1204          {
1205              $options .= " onFocus=\"$onFocus\" onBlur=\"$onBlur\"";
1206          }
1207          if ($cell['onchange'] && !($cell['type'] == 'button' || $cell['type'] == 'buttononly'))
1208          {
1209              $onchange = $cell['onchange'] == '1' ? 'this.form.submit();' : $this->js_pseudo_funcs($cell['onchange'],$cname);
1210              // rewriting onchange for checkboxes for IE to an onclick
1211              if ($cell['type'] == 'checkbox' && html::$user_agent == 'msie')
1212              {
1213                  $options .= ' onClick="'.$onchange.'; return true;"';
1214              }
1215              else
1216              {
1217                  $options .= ' onChange="'.$onchange.'"';
1218              }
1219          }
1220  
1221          if ($form_name != '')
1222          {
1223              $options = self::get_id($form_name,$cell['name'],$cell['id']).' '.$options;
1224          }
1225          switch ($type)
1226          {
1227              case 'label':    //  size: [b[old]][i[talic]],[link],[activate_links],[label_for],[link_target],[link_popup_size],[link_title]
1228                  if (is_array($value)) break;
1229                  if($cell_options)
1230                  {
1231                      list($style,$extra_link,$activate_links,$label_for,$extra_link_target,$extra_link_popup,$extra_link_title) = self::csv_split($cell_options,7);
1232                  }
1233                  else
1234                  {
1235                      $style = $cell['font_style'];
1236                      $extra_link = $cell['href'];
1237                      $activate_links = $cell['activate_links'];
1238                      $label_for = $cell['for'];
1239                      $extra_link_target = $cell['extra_link_target'];
1240                      $extra_link_popup = $cell['extra_link_popup'];
1241                      $extra_link_title = $cell['extra_link_title'];
1242                  }
1243                  $value = strlen($value) > 1 && !$cell['no_lang'] ? lang($value) : $value;
1244                  $value = nl2br(html::htmlspecialchars($value));
1245                  if ($activate_links) $value = html::activate_links($value);
1246                  if ($value != '' && $style && strpos($style,'b')!==false) $value = html::bold($value);
1247                  if ($value != '' && $style && strpos($style,'i')!==false) $value = html::italic($value);
1248                  // if the label has a name, use it as id in a span, to allow addressing it via javascript
1249                  $html .= ($name ? '<span id="'.($cell['id']?$cell['id']:$name).'">' : '').$value.($name ? '</span>' : '');
1250                  if ($help)
1251                  {
1252                      $class = array(
1253                          'class'       => $class,
1254                      );
1255                      list($class['onmouseover'],$class['onmouseout']) = html::tooltip($help, False, False,  true);
1256                  }
1257                  break;
1258              case 'html':    //  size: [link],[link_target],[link_popup_size],[link_title],[activate_links]
1259                  list($extra_link,$extra_link_target,$extra_link_popup,$extra_link_title,$activate_links) = explode(',',$cell_options);
1260                  if ($activate_links) $value = html::activate_links($value);
1261                  $html .= $value;
1262                  break;
1263              case 'int':        // size: [min],[max],[len],[precision/sprint format],[step]
1264              case 'integer':
1265              case 'float':
1266                  list($min,$max,$cell_options,$pre,$step) = explode(',',$cell_options);
1267                  // a few html5 options
1268                  if ((string)$min !== '') $options .= ' min="'.htmlspecialchars($min).'"';
1269                  if ((string)$max !== '') $options .= ' max="'.htmlspecialchars($max).'"';
1270                  //  default step="any" for float, as not setting it limits value to integer as step defaults to 1 in html5!
1271                  if (is_numeric($step) || $type == 'float')
1272                  {
1273                      $options .= ' step="'.(is_numeric($step)?$step:'any').'"';
1274                  }
1275                  if ($cell_options == '' && !$readonly)
1276                  {
1277                      $cell_options = $cell['type'] != 'float' ? 5 : 8;
1278                  }
1279                  // html5 input type=nummeric seems to ignore size, setting a width instead
1280                  $options .= ' style="width: '.(3+abs($cell_options)).'ex"';
1281                  if (($type == 'float' || !is_numeric($pre)) && $value && $pre)
1282                  {
1283                      $value = is_numeric($pre) ? self::number_format($value,$pre,$readonly) : sprintf($pre,$value);
1284                  }
1285                  $cell_options .= ',,'.($cell['type'] != 'float' ? '/^-?[0-9]*$/' : '"/^-?[0-9]*[,.]?[0-9]*$/"').',number';
1286                  // fall-through
1287              case 'hidden':
1288              case 'passwd':
1289              case 'text':        // size: [length][,maxLength[,preg[,html5type]]]
1290              case 'textbox':
1291                  $cell_opts = $c = self::csv_split($cell_options);    // allows to enclose preg in quote to allow comma
1292                  // fix preg, in case it contains a comma (html5type is only letters and always last option!)
1293                  if (count($cell_opts) > 3 && ($cell_opts2 = explode(',',$cell_options)) && $cell_opts2[2][0] != '"')
1294                  {
1295                      $html5type = array_pop($cell_opts);
1296                      $cell_opts = explode(',',$cell_options,3);
1297                      if (preg_match('/^[a-z]+$/i',$html5type))
1298                      {
1299                          $cell_opts[2] = substr($cell_opts[2],0,-strlen($html5type)-1);
1300                          if ($cell_opts[2][0] == ',') $cell_opts[2] = '';    // happens in link-entry under some condition
1301                          $cell_opts[] = $html5type;
1302                      }
1303                  }
1304                  if ($readonly && (int)$cell_opts[0] >= 0)
1305                  {
1306                      $html .= strlen($value) ? html::bold(html::htmlspecialchars($value)) : '';
1307                  }
1308                  else
1309                  {
1310                      if ($cell_opts[0] < 0)
1311                      {
1312                          $cell_opts[0] = abs($cell_opts[0]);
1313                          $options .= ' readonly="readonly"';
1314                      }
1315                      // only add html5 required attribute, if validation is NOT ignored
1316                      if ($cell['needed'] && self::ignore_validation_match($form_name, self::$request->ignore_validation, $cname))
1317                      {
1318                          $required = ' required';
1319                      }
1320                      $html .= html::input($form_name,$value,$type == 'passwd' ? 'password' : ($type == 'hidden' ? 'hidden' : $cell_opts[3]),
1321                          $options.html::formatOptions($cell_opts,'SIZE,MAXLENGTH').
1322                          $required.($type == 'passwd'?' autocomplete="off"':''));
1323  
1324                      if (!$readonly)
1325                      {
1326                          self::$request->set_to_process($form_name,$cell['type'],array(
1327                              'maxlength' => $cell_opts[1],
1328                              'needed'    => $cell['needed'],
1329                              'preg'      => $cell_opts[2],
1330                              'min'       => $min,    // int and float only
1331                              'max'       => $max,
1332                          ));
1333                      }
1334                  }
1335                  unset($cell_opts);
1336                  break;
1337              case 'textarea':    // Multiline Text Input, size: [rows][,cols]
1338                  if ($readonly && !$cell_options)
1339                  {
1340                      $html .= '<div>'.nl2br(html::htmlspecialchars($value))."</div>\n";
1341                  }
1342                  else
1343                  {
1344                      // if textarea is readonly, but form_name is already used by an other widget, dont use it
1345                      // browser would only send the content of the readonly (and therefore unchanged) field
1346                      if ($readonly && self::$request->isset_to_process($form_name)) $form_name = '';
1347                      $html .= html::textarea($form_name,$value,
1348                          $options.html::formatOptions($cell_options,'ROWS,COLS').($cell['needed']?' required="required"':''));
1349                  }
1350                  if (!$readonly)
1351                  {
1352                      self::$request->set_to_process($form_name,$cell['type'],array(
1353                          'needed'    => $cell['needed'],
1354                      ));
1355                  }
1356                  break;
1357              case 'htmlarea':    // Multiline formatted Text Input, size: {simple|extended|advanced},height,width,toolbar-expanded,upload-path
1358                  list($mode,$height,$width,$toolbar,$baseref,$convertnl) = explode(',',$cell_options);
1359  
1360                  if ($convertnl)
1361                  {
1362                      $value = nl2br(html::htmlspecialchars($value));
1363                  }
1364                  if (!$readonly)
1365                  {
1366                      $height = $height ? $height : '400px';
1367                      $width = $width ? $width : '100%';
1368                      $fckoptions = array(
1369                          'toolbar_expanded' => $toolbar,
1370                      );
1371                      // html::fckEditor runs everything through html::purify
1372                      $html .= html::fckEditor($form_name,$value,$mode,$fckoptions,$height,$width,$baseref);
1373  
1374                      self::$request->set_to_process($form_name,$cell['type'],array(
1375                          'needed'    => $cell['needed'],
1376                          'mode'      => $mode,    // need mode to not run purify for $mode=='ascii'
1377                      ));
1378                  }
1379                  else
1380                  {
1381                      $html .= html::div(html::purify(html::activate_links($value)),'style="overflow: auto; width='. $width. '; height='. $height. '"');
1382                  }
1383                  break;
1384              case 'checkbox':
1385                  $set_val = 1; $unset_val = 0;
1386                  if (!empty($cell_options))
1387                  {
1388                      list($set_val,$unset_val,$ro_true,$ro_false) = self::csv_split($cell_options);
1389                      if (!$set_val && !$unset_val) $set_val = 1;
1390                      $value = $value == $set_val;
1391                  }
1392                  if (($multiple = substr($cell['name'],-2) == '[]') && !$readonly)
1393                  {
1394                      $readonly = $readonlys[substr($cell['name'],0,-1).$set_val.']'];
1395                  }
1396                  if ($readonly)
1397                  {
1398                      if (count(explode(',',$cell_options)) < 3)
1399                      {
1400                          $ro_true = 'x';
1401                          $ro_false = '';
1402                      }
1403                      if (!$value && $ro_false == 'disable') return '';
1404  
1405                      $html .= $value ? html::bold($ro_true) : $ro_false;
1406                  }
1407                  else
1408                  {
1409                      if ($value) $options .= ' checked="checked"';
1410  
1411                      if ($multiple)
1412                      {
1413                          // add the set_val to the id to make it unique
1414                          $options = str_replace(self::get_id($form_name),
1415                              self::get_id(substr($form_name,0,-2)."[$set_val]"), $options);
1416                      }
1417                      $html .= html::input($form_name,$set_val,'checkbox',$options);
1418  
1419                      if ($multiple) $form_name = self::form_name($cname,substr($cell['name'],0,-2));
1420  
1421                      if (!self::$request->isset_to_process($form_name))
1422                      {
1423                          self::$request->set_to_process($form_name,$cell['type'],array(
1424                              'unset_value' => $unset_val,
1425                              'multiple'    => $multiple,
1426                              'needed'      => $cell['needed'],
1427                          ));
1428                      }
1429                      self::$request->set_to_process_attribute($form_name,'values',$set_val,true);
1430                      if (!$multiple) unset($set_val);    // otherwise it will be added to the label
1431                  }
1432                  break;
1433              case 'radio':        // size: value if checked, readonly set, readonly unset
1434                  list($set_val,$ro_true,$ro_false) = self::csv_split($cell_options);
1435                  $set_val = $this->expand_name($set_val,$show_c,$show_row,$content['.c'],$content['.row'],$content);
1436  
1437                  if ($value == $set_val)
1438                  {
1439                      $options .= ' checked="checked"';
1440                  }
1441                  // add the set_val to the id to make it unique
1442                  $options = str_replace(self::get_id($form_name),
1443                      self::get_id($form_name."[$set_val]"), $options);
1444  
1445                  if ($readonly)
1446                  {
1447                      if (!$ro_true && !$ro_false) $ro_true = 'x';
1448                      $html .= $value == $set_val ? html::bold($ro_true) : $ro_false;
1449                  }
1450                  else
1451                  {
1452                      $html .= html::input($form_name,$set_val,'RADIO',$options);
1453                      self::$request->set_to_process($form_name,$cell['type'],array(
1454                          'needed' => $cell['needed'],
1455                      ));
1456                  }
1457                  break;
1458              case 'button':
1459              case 'buttononly':
1460              case 'cancel':    // cancel button
1461                  if ($name == 'cancel' || stripos($name,'[cancel]') !== false) $type = 'cancel';
1462                  list($app) = explode('.',$this->name);
1463                  list($img,$ro_img) = explode(',',$cell_options);
1464                  if ($img[0] != '/' && strpos($img,'/') !== false && count($img_parts = explode('/',$img)) == 2)
1465                  {
1466                      list($app,$img) = $img_parts;    // allow to specify app in image name (eg. img='addressbook/navbar')
1467                  }
1468                  $title = strlen($label) <= 1 || $cell['no_lang'] ? $label : lang($label);
1469                  if ($cell['onclick'] &&
1470                      ($onclick = $this->expand_name($cell['onclick'],$show_c,$show_row,$content['.c'],$content['.row'],$content)))
1471                  {
1472                      $onclick = $this->js_pseudo_funcs($onclick,$cname);
1473                  }
1474                  unset($cell['onclick']);    // otherwise the grid will handle it
1475                  if (($cell['onchange'] != '' || $img && !$readonly) && !$cell['needed']) // use a link instead of a button
1476                  {
1477                      $onclick = ($onclick ? preg_replace('/^return(.*);$/','if (\\1) ',$onclick) : '').
1478                          (((string)$cell['onchange'] === '1' || $img) ?
1479                          'return submitit('.self::$name_form.",'".$form_name."');" : $cell['onchange']).'; return false;';
1480  
1481                      if (!html::$netscape4 && substr($img,-1) == '%' && is_numeric($percent = substr($img,0,-1)))
1482                      {
1483                          $html .= html::progressbar($percent,$title,'onclick="'.$onclick.'" '.$options);
1484                      }
1485                      else
1486                      {
1487                          $html .= '<a href="" onClick="'.$onclick.'" '.$options.'>' .
1488                              ($img ? html::image($app,$img,$title,'border="0"') : $title) . '</a>';
1489                      }
1490                  }
1491                  else
1492                  {
1493                      if (!empty($img))
1494                      {
1495                          $options .= ' title="'.html::htmlspecialchars($title).'"';
1496                      }
1497                      if ($cell['onchange'] && $cell['onchange'] != 1)
1498                      {
1499                          $onclick = ($onclick ? preg_replace('/^return(.*);$/','if (\\1) ',$onclick) : '').$cell['onchange'];
1500                      }
1501                      if ($type == 'cancel') $options .= ' novalidate="novalidate"';    // tell html5 form validation NOT to validate
1502                      $html .= !$readonly ? html::submit_button($form_name,$label,$onclick,
1503                          strlen($label) <= 1 || $cell['no_lang'],$options,$img,$app,$type == 'buttononly' ? 'button' : 'submit') :
1504                          html::image($app,$ro_img,'',$options);
1505                  }
1506                  $extra_label = False;
1507                  if (!$readonly && $type != 'buttononly')    // input button, are never submitted back!
1508                  {
1509                      self::$request->set_to_process($form_name,$type);
1510                  }
1511                  break;
1512              case 'hrule':
1513                  $html .= html::hr($cell_options);
1514                  break;
1515              case 'grid':
1516                  if ($readonly && !$readonlys['__ALL__'])
1517                  {
1518                      if (!is_array($readonlys)) $readonlys = array();
1519                      $set_readonlys_all = $readonlys['__ALL__'] = True;
1520                  }
1521                  if ($name != '')
1522                  {
1523                      $cname .= $cname == '' ? $name : '['.str_replace('[','][',str_replace(']','',$name)).']';
1524                  }
1525                  $html .= $this->show_grid($cell,$name ? $value : $content,$readonlys+(array)$readonlys[$name],$cname,$show_c,$show_row,$path);
1526                  if ($set_readonlys_all) unset($readonlys['__ALL__']);
1527                  break;
1528              case 'template':    // size: index in content-array (if not full content is past further on)
1529                  if (is_object($cell['name']))
1530                  {
1531                      $cell['obj'] = &$cell['name'];
1532                      unset($cell['name']);
1533                      $cell['name'] = 'was Object';
1534                      echo "<p>Object in Name in tpl '$this->name': "; _debug_array($grid);
1535                  }
1536                  $obj_read = 'already loaded';
1537                  if (is_array($cell['obj']))
1538                  {
1539                      $obj = new etemplate();
1540                      $obj->init($cell['obj']);
1541                      $cell['obj'] =& $obj;
1542                      unset($obj);
1543                  }
1544                  if (!is_object($cell['obj']))
1545                  {
1546                      if ($cell['name'][0] == '@')
1547                      {
1548                          $cell['obj'] = $this->get_array($content,substr($cell['name'],1));
1549                          $obj_read = is_object($cell['obj']) ? 'obj from content' : 'obj read, obj-name from content';
1550                          if (!is_object($cell['obj']))
1551                          {
1552                              $cell['obj'] = new etemplate($cell['obj'],$this->as_array());
1553                          }
1554                      }
1555                      else
1556                      {  $obj_read = 'obj read';
1557                          $cell['obj'] = new etemplate($name,$this->as_array());
1558                      }
1559                  }
1560                  if (is_int($this->debug) && $this->debug >= 3 || $this->debug == $cell['type'])
1561                  {
1562                      echo "<p>show_cell::template(tpl=$this->name,name=$cell[name]): $obj_read, readonly=$readonly</p>\n";
1563                  }
1564                  if ($this->autorepeat_idx($cell,$show_c,$show_row,$idx,$idx_cname,false,$content) || $cell_options != '')
1565                  {
1566                      if ($span == '' && isset($content[$idx]['span']))
1567                      {    // this allows a colspan in autorepeated cells like the editor
1568                          list($span) = explode(',',$content[$idx]['span']);
1569                          if ($span == 'all')
1570                          {
1571                              $span = 1 + $content['cols'] - $show_c;
1572                          }
1573                      }
1574                      $readonlys = $this->get_array($readonlys,$idx);
1575                      $content = $this->get_array($content,$idx);
1576                      if ($idx_cname != '')
1577                      {
1578                          $cname .= $cname == '' ? $idx_cname : '['.str_replace('[','][',str_replace(']','',$idx_cname)).']';
1579                      }
1580                      //echo "<p>show_cell-autorepeat($name,$show_c,$show_row,cname='$cname',idx='$idx',idx_cname='$idx_cname',span='$span'): content ="; _debug_array($content);
1581                  }
1582                  if ($readonly && !$readonlys['__ALL__'])
1583                  {
1584                      if (!is_array($readonlys)) $readonlys = array();
1585                      $set_readonlys_all = $readonlys['__ALL__'] = True;
1586                  }
1587                  // propagate our onclick handler to embeded templates, if they dont have their own
1588                  if (!isset($cell['obj']->onclick_handler)) $cell['obj']->onclick_handler = $this->onclick_handler;
1589                  if ($cell['obj']->no_onclick)
1590                  {
1591                      $cell['obj']->onclick_proxy = $this->onclick_proxy ? $this->onclick_proxy : $this->name.':'.$this->version.':'.$path;
1592                  }
1593                  // propagate the CSS class to the template
1594                  if ($class)
1595                  {
1596                      $grid_size = array_pad(explode(',',$cell['obj']->size),4,'');
1597                      $grid_size[3] = ($grid_size[3] ? $grid_size[3].' ' : '') . $class;
1598                      $cell['obj']->size = implode(',',$grid_size);
1599                  }
1600                  $html = $cell['obj']->show($content,$this->sel_options,$readonlys,$cname,$show_c,$show_row);
1601  
1602                  if ($set_readonlys_all) unset($readonlys['__ALL__']);
1603                  break;
1604              case 'select':    // size:[linesOnMultiselect|emptyLabel,extraStyleMulitselect, [<varies>,]{5} enhance]
1605                  $sels = array();
1606                  list($multiple,$extraStyleMultiselect) = explode(',',$cell_options,2);
1607  
1608                  // Allow widget to specify using enhanced select or not
1609                  $c_options = explode(',',$cell_options);
1610                  if(array_key_exists('enhance', $cell))
1611                  {
1612                      $enhance = $cell['enhance'];
1613                  }
1614                  else if (count($c_options) >= 8)
1615                  {
1616                      // 8 or more optionsu - #7 is enhance flag
1617                      $enhance = ($c_options[7] == '1' || $c_options[7] == 'true');
1618                  }
1619  
1620                  if (!empty($multiple) && 0+$multiple <= 0)
1621                  {
1622                      $sels[''] = $multiple < 0 ? 'all' : $multiple;
1623                      // extra-option: no_lang=0 gets translated later and no_lang=1 gets translated too (now), only no_lang>1 gets not translated
1624                      if ((int)$cell['no_lang'] == 1)
1625                      {
1626                          $sels[''] = substr($sels[''],-3) == '...' ? lang(substr($sels[''],0,-3)).'...' : lang($sels['']);
1627                      }
1628                      $multiple = 0;
1629                  }
1630                  $sels += $this->_sel_options($cell,$name,$content);
1631                  if ($multiple && !is_array($value)) $value = explode(',',$value);
1632                  if ($readonly || $cell['noprint'])
1633                  {
1634                      foreach($multiple || is_array($value) ? $value : array($value) as $val)
1635                      {
1636                          if (is_array($sels[$val]))
1637                          {
1638                              $option_label = $sels[$val]['label'];
1639                              $option_title = $sels[$val]['title'];
1640                          }
1641                          else
1642                          {
1643                              $option_label = ($sels[$val]?$sels[$val]:$val);
1644                              $option_title = '';
1645                          }
1646                          if (!$cell['no_lang']) $option_label = lang($option_label);
1647  
1648                          if ($html) $html .= "<br>\n";
1649  
1650                          if ($option_title)
1651                          {
1652                              $html .= '<span title="'.html::htmlspecialchars($option_title).'">'.html::htmlspecialchars($option_label).'</span>';
1653                          }
1654                          else
1655                          {
1656                              $html .= html::htmlspecialchars($option_label);
1657                          }
1658                      }
1659                  }
1660                  if (!$readonly)
1661                  {
1662                      if ($cell['noprint'])
1663                      {
1664                          $html = '<span class="onlyPrint">'.$html.'</span>';
1665                          $options .= ' class="noPrint"';
1666                      }
1667                      if ($multiple && is_numeric($multiple))    // eg. "3+" would give a regular multiselectbox
1668                      {
1669                          $html .= html::checkbox_multiselect($form_name.($multiple > 1 ? '[]' : ''),$value,$sels,
1670                              $cell['no_lang'],$options,$multiple,$multiple[0]!=='0',
1671                              $extraStyleMultiselect,$enhance);
1672                      }
1673                      else
1674                      {
1675                          $html .= html::select($form_name.($multiple > 1 ? '[]' : ''),$value,$sels,
1676                              $cell['no_lang'],$options,$multiple,$enhance);
1677                      }
1678                      if (!self::$request->isset_to_process($form_name))
1679                      {
1680                          // fix for optgroup's
1681                          $options=array();
1682                          foreach($sels as $key => $val)
1683                          {
1684                              # we want the key anyway, even if this allowes more values than wanted (the name/key of the optgroup if there is one,
1685                              # the keys of the arrays in case you have key/value pair(s) as value for the value of your option ).
1686                              $options[$key]=$key;
1687                              if (is_array($val))
1688                              {
1689                                  foreach(array_keys($val) as $key2)
1690                                  {
1691                                      $options[$key2]=$key2;
1692                                  }
1693                              }
1694                          }
1695                          self::$request->set_to_process($form_name,$cell['type'],array(
1696                              'needed'  => $cell['needed'],
1697                              'allowed' => array_keys($options),
1698                              'multiple'=> $multiple,
1699                          ));
1700                      }
1701                  }
1702                  break;
1703              case 'image':    // size: [link],[link_target],[imagemap],[link_popup],[id]
1704              case 'progress':
1705                  if (is_string($value) && $value !== '' &&
1706                      ($is_progress = substr($value,-1) == '%' && is_numeric(substr($value,0,-1))) !== ($type == 'progress'))
1707                  {
1708                      error_log("Please use correct widget-type '".($is_progress?'progress':'image')."' in eTemplate '$this->name'!");
1709                  }
1710                  $image = $value != '' ? $value : $name;
1711                  if (is_string($image)) list($app,$img) = explode('/',$image,2);
1712                  if (!$app || !$img || !is_dir(EGW_SERVER_ROOT.'/'.$app) || strpos($img,'/')!==false)
1713                  {
1714                      $img = $image;
1715                      list($app) = explode('.',$this->name);
1716                  }
1717                  if (!$readonly)
1718                  {
1719                      list($extra_link,$extra_link_target,$imagemap,$extra_link_popup,$id) = self::csv_split($cell['size']);
1720                  }
1721                  $html .= html::image($app,$img,strlen($label) > 1 && !$cell['no_lang'] ? lang($label) : $label,
1722                      'border="0"'.($imagemap?' usemap="#'.html::htmlspecialchars($imagemap).'"':'').
1723                      ($id || $value ? self::get_id($name,$cell['name'],$id) : ''));
1724                  $extra_label = False;
1725                  break;
1726              case 'file':    // size: size of the filename field
1727                  if (!$readonly)
1728                  {
1729                      if ((int) $cell_options) $options .= ' size="'.(int)$cell_options.'"';
1730                      if (substr($name,-2) == '[]')
1731                      {
1732                          self::$form_options .= ' enctype="multipart/form-data"';
1733                          if (strpos($options,'onChange="') !== false)
1734                          {
1735                              $options = preg_replace('/onChange="([^"]+)"/i','onChange="\\1; if (!this.multiple) add_upload(this);"',$options);
1736                          }
1737                          else
1738                          {
1739                              $options .= ' onChange="if (!this.multiple) add_upload(this);"';
1740                          }
1741                          $options .= ' multiple="multiple"';    // allow html5 browsers to select more then one file
1742                      }
1743                      else
1744                      {
1745                          $html .= html::input_hidden($path_name = str_replace($name,$name.'_path',$form_name),'.');
1746                          self::$form_options = " enctype=\"multipart/form-data\" onsubmit=\"set_element2(this,'$path_name','$form_name')\"";
1747                      }
1748                      $html .= html::input($form_name,'','file',$options);
1749                      self::$request->set_to_process($form_name,$cell['type'],array(
1750                          'needed' => $cell['needed'],
1751                      ));
1752                  }
1753                  break;
1754              case 'split':
1755                  // Render this et2 widget as a box
1756                  $orient = $cell['orientation'];
1757              case 'vbox':
1758              case 'hbox':
1759              case 'groupbox':
1760              case 'box':    // size: num,orient,cellpadding,cellspacing,keep
1761                  $rows = array();
1762                  $box_row = 1;
1763                  $box_col = 'A';
1764                  $box_anz = 0;
1765                  list($num,$orient,,,$keep_empty) = explode(',',$cell_options);
1766                  if (!$orient) $orient = $type == 'hbox' ? 'horizontal' : ($type == 'box' ? false : 'vertical');
1767                  for ($n = 1; $n <= (int) $num; ++$n)
1768                  {
1769                      $child = $cell[$n];    // first param is a var_param now!
1770                      $h = $this->show_cell($child,$content,$readonlys,$cname,$show_c,$show_row,$nul,$cl,$path.'/'.$n);
1771                      if ($h != '' && $h != '&nbsp;' || $keep_empty)
1772                      {
1773                          if ($orient != 'horizontal')
1774                          {
1775                              $box_row = $n;
1776                          }
1777                          else
1778                          {
1779                              $box_col = $this->num2chrs($n);
1780                          }
1781                          if (!$orient)
1782                          {
1783                              $html .= $cl ? html::div($h," class=\"$cl\"") : $h;
1784                          }
1785                          else
1786                          {
1787                              $rows[$box_row][$box_col] = $html = $h;
1788                          }
1789                          $box_anz++;
1790                          if ($cell[$n]['align'])
1791                          {
1792                              $rows[$box_row]['.'.$box_col] = html::formatOptions($child['align'],'align');
1793                              $sub_cell_has_align = true;
1794                          }
1795                          if (strlen($child['onclick']) > 1)
1796                          {
1797                              $rows[$box_row]['.'.$box_col] .= ' onclick="'.$this->js_pseudo_funcs($child['onclick'],$cname).'"'.
1798                                  self::get_id('',$child['name'],$child['id']);
1799                          }
1800                          // allow to set further attributes in the tablecell, beside the class
1801                          if (is_array($cl))
1802                          {
1803                              foreach($cl as $attr => $val)
1804                              {
1805                                  if ($attr != 'class' && $val)
1806                                  {
1807                                      $rows[$box_row]['.'.$box_col] .= ' '.$attr.'="'.$val.'"';
1808                                  }
1809                              }
1810                              $cl = $cl['class'];
1811                          }
1812                          $box_item_class = $this->expand_name(isset(self::$class_conf[$cl]) ? self::$class_conf[$cl] : $cl,
1813                              $show_c,$show_row,$content['.c'],$content['.row'],$content);
1814                          $rows[$box_row]['.'.$box_col] .= html::formatOptions($box_item_class,'class');
1815                      }
1816                  }
1817                  if ($box_anz > 1 && $orient)    // a single cell is NOT placed into a table
1818                  {
1819                      $html = html::table($rows,html::formatOptions($cell_options,',,cellpadding,cellspacing').
1820                          ($type != 'groupbox' ? html::formatOptions($class,'class').
1821                               ($cell['name'] ? self::get_id($form_name,$cell['name'],$cell['id']) : '') : '').
1822                          ($cell['align'] && $orient != 'horizontal' || $sub_cell_has_align ? ' width="100%"' : ''));    // alignment only works if table has full width
1823                      if ($type != 'groupbox') $class = '';    // otherwise we create an extra div
1824                  }
1825                  // put the class of the box-cell, into the the class of this cell
1826                  elseif ($box_item_class && $box_anz == 1)
1827                  {
1828                      $class = ($class ? $class . ' ' : '') . $box_item_class;
1829                      // if we have onclick or tooltip, add it to an extra div around single cell
1830                      if (!empty($rows[$box_row]['.'.$box_col])) $html = html::div($html, $rows[$box_row]['.'.$box_col]);
1831                  }
1832                  if ($type == 'groupbox')
1833                  {
1834                      if (strlen($label) > 1 && $cell['label'] == $label)
1835                      {
1836                          $label = lang($label);
1837                      }
1838                      $html = html::fieldset($html,$label,self::get_id($form_name,$cell['name'],$cell['id']).
1839                          ($class ? ' class="'.$class.'"' : ''));
1840                      $class = '';    // otherwise we create an extra div
1841                  }
1842                  elseif (!$orient)
1843                  {
1844                      if (strpos($html,'class="'.$class)) $class = '';    // dont add class a 2. time
1845                      $html = html::div($html,html::formatOptions(array(
1846                              $cell['height'],
1847                              $cell['width'],
1848                              $class,
1849                          ),'height,width,class').self::get_id($form_name,$cell['name'],$cell['id'])). ($html ? '' : '</div>');
1850                      $class = '';    // otherwise we create an extra div
1851                  }
1852                  if ($box_anz > 1)    // small docu in the html-source
1853                  {
1854                      $html = "\n\n<!-- BEGIN $cell[type] -->\n\n".$html."\n\n<!-- END $cell[type] -->\n\n";
1855                  }
1856                  // we need noPrint on td
1857                  if (strpos($cell['span'], 'noPrint')) $class .= ' noPrint';
1858                  $extra_label = False;
1859                  break;
1860              case 'deck':
1861                  for ($n = 1; $n <= $cell_options && (empty($value) || $value != $cell[$n]['name']); ++$n) ;
1862                  if ($n > $cell_options)
1863                  {
1864                      $value = $cell[1]['name'];
1865                  }
1866                  if ($s_width = $cell['width'])
1867                  {
1868                      $s_width = "width: $s_width".(substr($s_width,-1) != '%' ? 'px' : '').';';
1869                  }
1870                  if ($s_height = $cell['height'])
1871                  {
1872                      $s_height = "height: $s_height".(substr($s_height,-1) != '%' ? 'px' : '').';';
1873                  }
1874                  $html = html::input_hidden($form_name,$value);
1875                  self::$request->set_to_process($form_name,$cell['type']);
1876  
1877                  for ($n = 1; $n <= $cell_options; ++$n)
1878                  {
1879                      $child = $cell[$n];    // first param is a var_param now!
1880                      $html .= html::div($this->show_cell($child,$content,$readonlys,$cname,$show_c,
1881                          $show_row,$nul,$cl,$path.'/'.$n),html::formatOptions(array(
1882                          'display: '.($value == $child['name'] ? 'inline' : 'none').';',
1883                          $child['name']
1884                      ),'style,id'));
1885                  }
1886                  break;
1887              case 'colorpicker':
1888                  if ($readonly)
1889                  {
1890                      $html = $value;
1891                  }
1892                  else
1893                  {
1894                      $html = html::inputColor($form_name,$value,$cell['help']);
1895  
1896                      self::$request->set_to_process($form_name,$cell['type'],array(
1897                              'maxlength' => 7,
1898                              'needed'    => $cell['needed'],
1899                              'preg'      => '/^(#[0-9a-f]{6}|)$/i',
1900                          ));
1901                  }
1902                  break;
1903              default:
1904                  if ($ext_type && $this->haveExtension($ext_type,'render'))
1905                  {
1906                      $html .= $this->extensionRender($ext_type,$form_name,$value,$cell,$readonly);
1907                  }
1908                  else
1909                  {
1910                      $html .= "<i>unknown type '$cell[type]'</i>";
1911                  }
1912                  break;
1913          }
1914          // extension-processing need to be after all other and only with diff. name
1915          if ($ext_type && !$readonly && $this->haveExtension($ext_type,'post_process'))
1916          {    // unset it first, if it is already set, to be after the other widgets of the ext.
1917              $to_process = self::$request->get_to_process($form_name);
1918              self::$request->unset_to_process($form_name);
1919              self::$request->set_to_process($form_name,'ext-'.$ext_type,$to_process);
1920          }
1921          // save blur-value to strip it in process_exec
1922          if (!empty($blur) && self::$request->isset_to_process($form_name))
1923          {
1924              self::$request->set_to_process_attribute($form_name,'blur',$blur);
1925          }
1926          if ($extra_label && ($label != '' || $html == ''))
1927          {
1928              if (strlen($label) > 1 && !($cell['no_lang'] && $cell['label'] != $label || (int)$cell['no_lang'] == 2))
1929              {
1930                  $label = lang($label);
1931              }
1932              $accesskey = false;
1933              if (($accesskey = $label && strpos($label,'&')!==false) && $accesskey[1] != ' ' && $form_name != '' &&
1934                      (($pos = strpos($accesskey,';')) === false || $pos > 5))
1935              {
1936                  $label = str_replace('&'.$accesskey[1],'<u>'.$accesskey[1].'</u>',$label);
1937                  $accesskey = $accesskey[1];
1938              }
1939              if ($label && !$readonly && ($accesskey || $label_for || $type != 'label' && $cell['name']))
1940              {
1941                  if ($label_for)        // if label_for starts with a '#', it is already an id - no need to create default id from it
1942                  {
1943                      $label_for = $label_for[0] == '#' ? substr($label_for,1) : self::form_name($cname,$label_for);
1944                  }
1945                  else
1946                  {
1947                      $label_for = $form_name.($set_val?"[$set_val]":'');
1948                  }
1949                  $label = html::label($label,$label_for,$accesskey);
1950              }
1951              if ($type == 'radio' || $type == 'checkbox' || $label && strpos($label,'%s')!==false)    // default for radio is label after the button
1952              {
1953                  $html = strpos($label,'%s')!==false ? str_replace('%s',$html,$label) : $html.' '.$label;
1954              }
1955              elseif (($html = $label . ' ' . $html) == ' ')
1956              {
1957                  $html = '&nbsp;';
1958              }
1959          }
1960          if ($extra_link && (($extra_link = $this->expand_name($extra_link,$show_c,$show_row,$content['.c'],$content['.row'],$content))))
1961          {
1962              $options = $help ? ' onmouseover="self.status=\''.addslashes(html::htmlspecialchars($help)).'\'; return true;"' .
1963                  ' onmouseout="self.status=\'\'; return true;"' : '';
1964  
1965              if ($extra_link_target && (($extra_link_target = $this->expand_name($extra_link_target,$show_c,$show_row,$content['.c'],$content['.row'],$content))))
1966              {
1967                  $options .= ' target="'.addslashes($extra_link_target).'"';
1968              }
1969              if ($extra_link_popup && (($extra_link_popup = $this->expand_name($extra_link_popup,$show_c,$show_row,$content['.c'],$content['.row'],$content))))
1970              {
1971                  list($w,$h) = explode('x',$extra_link_popup);
1972                  $options .= ' onclick="egw(window).openPopup(this,'.(int)$w.','.(int)$h.',this.target); return false;"';
1973              }
1974              if ($extra_link_title)
1975              {
1976                  $options .= ' title="'.addslashes($extra_link_title).'"';
1977              }
1978              return html::a_href($html,$extra_link,'',$options);
1979          }
1980          // if necessary show validation-error behind field
1981          if (isset(self::$validation_errors[$form_name]))
1982          {
1983              $html .= ' <span style="color: red; white-space: nowrap;">'.htmlspecialchars(self::$validation_errors[$form_name]).'</span>';
1984          }
1985          // generate an extra div, if we have an onclick handler and NO children or it's an extension
1986          //echo "<p>$this->name($this->onclick_handler:$this->no_onclick:$this->onclick_proxy): $cell[type]/$cell[name]</p>\n";
1987          if ($this->onclick_handler && !isset(self::$widgets_with_children[$cell['type']]))
1988          {
1989              $handler = str_replace('%p',$this->no_onclick ? $this->onclick_proxy : $this->name.':'.$this->version.':'.$path,
1990                  $this->onclick_handler);
1991              if ($type == 'button' || $type == 'buttononly' || !$label)    // add something to click on
1992              {
1993                  $html = (substr($html,-1) == "\n" ? substr($html,0,-1) : $html).'&nbsp;';
1994              }
1995              return html::div($html,' ondblclick="'.$handler.'"','clickWidgetToEdit');
1996          }
1997          return $html;
1998      }
1999  
2000      /**
2001       * Return id="..." attribute, using the following order to determine the id:
2002       *    - $id if not empty
2003       *  - $name if starting with a hash (#), without the hash of cause
2004       *    - $form_name otherwise
2005       *
2006       * This is necessary to not break backward compatibility: if you want to specify
2007       * a certain id, you can use now "#something" as name to get id="something",
2008       * otherwise the $form_name "exec[something]" is used.
2009       * (If no id is directly supplied internally.)
2010       *
2011       * @param string $form_name
2012       * @param string $name=null
2013       * @param string $id=null
2014       * @return string ' id="..."' or '' if no id found
2015       */
2016  	static public function get_id($form_name,$name=null,$id=null)
2017      {
2018          if (empty($id))
2019          {
2020              if ($name[0] == '#')
2021              {
2022                  $id = substr($name,1);
2023              }
2024              else
2025              {
2026                  $id = $form_name;
2027              }
2028          }
2029          return !empty($id) ? ' id="'.htmlspecialchars($id).'"' : '';
2030      }
2031  
2032      /**
2033       * Format a number according to user prefs with decimal and thousands separator (later only for readonly)
2034       *
2035       * HTML5 input type=number requires a float value with a dot, not comma!
2036       * Chrome 22 and Safari 6 shows no value if a comma is used,
2037       * while FF 16, IE 9 and 10 have no support for input type=number :-(
2038       * --> use . as decimal separator for browser supporting html5 input type=number
2039       *
2040       * @param int|float|string $number
2041       * @param int $num_decimal_places=2
2042       * @param boolean $readonly=true
2043       * @return string
2044       */
2045  	static public function number_format($number,$num_decimal_places=2,$readonly=true)
2046      {
2047          static $dec_separator,$thousands_separator;
2048          if (is_null($dec_separator))
2049          {
2050              $dec_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][0];
2051              if (empty($dec_separator)) $dec_separator = '.';
2052              $thousands_separator = $GLOBALS['egw_info']['user']['preferences']['common']['number_format'][1];
2053          }
2054          if ((string)$number === '') return '';
2055  
2056          $ret = number_format(str_replace(' ','',$number), $num_decimal_places,
2057              // need to use '.' as decimal separator for all browser supporting html5 input type=number
2058              $dec_sep_used=$readonly || !in_array(html::$user_agent, array('chrome', 'safari', 'opera')) ?
2059                  $dec_separator : '.',
2060              $readonly ? $thousands_separator : '');
2061          //error_log(__METHOD__."($number, $num_decimal_places, $readonly) html::user_agent=".html::$user_agent.", dec_sep='$dec_separator' --> '$dec_sep_used', thousands_sep='$thousands_separator' returning '$ret'");
2062          return $ret;
2063      }
2064  
2065      /**
2066      * Retrive options for selectboxes and similar widgets (eg. the tree)
2067      *
2068      * @param array $cell
2069      * @param string $name
2070      * @param array $content=array();
2071      * @return array
2072      */
2073  	function _sel_options($cell,$name,$content=array())
2074      {
2075          $sels = array();
2076  
2077          if (!empty($cell['sel_options']))
2078          {
2079              if (!is_array($cell['sel_options']))
2080              {
2081                  $opts = explode(',',$cell['sel_options']);
2082                  while (list(,$opt) = each($opts))
2083                  {
2084                      list($k,$v) = explode('=',$opt);
2085                      $sels[$k] = $v;
2086                  }
2087              }
2088              else
2089              {
2090                  $sels += $cell['sel_options'];
2091              }
2092          }
2093          $explode_needed = true;
2094          if (($options = $this->sel_options[$name]) && is_array($options) ||
2095              ($options = self::get_array($this->sel_options, $name)) && is_array($options))
2096          {
2097              if(count($options) == 2 && $options['label'] && $options['title'])
2098              {
2099                  // In too far - need to back up
2100              }
2101              else
2102              {
2103                  $sels += $options;
2104                  $explode_needed = false;
2105              }
2106          }
2107          if($explode_needed)
2108          {
2109              $name_parts = explode('[',str_replace(']','',$name));
2110              if (count($name_parts))
2111              {
2112                  $org_name = $name_parts[count($name_parts)-1];
2113                  if (isset($this->sel_options[$org_name]) && is_array($this->sel_options[$org_name]))
2114                  {
2115                      $sels += $this->sel_options[$org_name];
2116                  }
2117                  elseif (isset($this->sel_options[$name_parts[0]]) && is_array($this->sel_options[$name_parts[0]]))
2118                  {
2119                      $sels += $this->sel_options[$name_parts[0]];
2120                  }
2121              }
2122          }
2123          if (isset($content["options-$name"]))
2124          {
2125              $sels += $content["options-$name"];
2126          }
2127          //error_log(__METHOD__."(, '$name') returning ".array2string($sels));
2128          return $sels;
2129      }
2130  
2131      /**
2132      * Resolve javascript pseudo functions in onclick or onchange:
2133      * - egw::link('$l','$p') calls $egw->link($l,$p)
2134      * - form::name('name') returns expanded name/id taking into account the name at that point of the template hierarchy
2135      * - egw::lang('Message ...') translate the message
2136      * - confirm('message') translates 'message' and adds a '?' if not present
2137      * - window.open() replaces it with egw(window).openPopup()
2138      * - xajax_doXMLHTTP('etemplate. replace ajax calls in widgets with special handler not requiring etemplate run rights
2139      *
2140      * @param string $on onclick, onchange, ... action
2141      * @param string $cname name-prefix / name-space
2142      * @return string
2143      */
2144  	function js_pseudo_funcs($on,$cname)
2145      {
2146          if (strpos($on,'::') !== false)    // avoid the expensive regular expresions, for performance reasons
2147          {
2148              if (preg_match_all("/egw::link\\('([^']+)','(.+?)'(?:,'(.+?)')?\\)/",$on,$matches))    // the ? alters the expression to shortest match
2149              {
2150                  foreach(array_keys($matches[1]) as $n)                             // this way we can correctly parse ' in the 2. argument
2151                  {
2152                      $url = $GLOBALS['egw']->link($matches[1][$n],$matches[2][$n],$matches[3][$n]);
2153                      $on = str_replace($matches[0][$n],'\''.addslashes($url).'\'',$on);
2154                  }
2155              }
2156  
2157              if (preg_match_all("/form::name\\('([^']+)'\\)/",$on,$matches))
2158              {
2159                  foreach($matches[1] as $n => $matche_name)
2160                  {
2161                      $matches[1][$n] = '\''.self::form_name($cname,$matche_name).'\'';
2162                  }
2163                  $on = str_replace($matches[0],$matches[1],$on);
2164              }
2165              // we need to search ungready (shortest possible match), to avoid catching to much
2166              if (preg_match_all('/egw::lang\(["\']{1}(.*)["\']{1}\)/U',$on,$matches)) {
2167                  foreach($matches[1] as $n => $string) {
2168                      $str = lang($string);
2169                      $on = str_replace($matches[0][$n],'\''.addslashes($str).'\'',$on);
2170                  }
2171              }
2172  
2173              // inserts the styles of a named template
2174              if (preg_match('/template::styles\(["\']{1}(.*)["\']{1}\)/U',$on,$matches))
2175              {
2176                  $tpl = $matches[1] == $this->name ? $this : new etemplate($matches[1]);
2177                  $on = str_replace($matches[0],"'<style>".str_replace(array("\n","\r"),'',$tpl->style)."</style>'",$on);
2178              }
2179          }
2180  
2181          // translate messages in confirm()
2182          if (strpos($on,'confirm(') !== false && preg_match('/confirm\(["\']{1}(.*)["\']{1}\)/U',$on,$matches))
2183          {
2184              $question = lang($matches[1]).(substr($matches[1],-1) != '?' ? '?' : '');    // add ? if not there, saves extra phrase
2185              $on = str_replace($matches[0],'confirm(\''.str_replace("'","\\'",$question).'\')',$on);
2186          }
2187  
2188          // replace window.open() with EGw's egw(window).openPopup()
2189          if (strpos($on,'window.open(') !== false && preg_match("/window.open\('(.*)','(.*)','dependent=yes,width=([^,]*),height=([^,]*),scrollbars=yes,status=(.*)'\)/",$on,$matches))
2190          {
2191              $on = str_replace($matches[0], "egw(window).openPopup('$matches[1]', $matches[3], $matches[4], '$matches[2]', false, false, '$matches[5]')", $on);
2192          }
2193  
2194          // replace window.close() with EGw's egw.close()
2195          if (strpos($on,'window.close(') !== false)
2196          {
2197              $on = str_replace('window.close(', 'egw(window).close(', $on);
2198          }
2199  
2200          // replace xajax calls to code in widgets, with the "etemplate" handler,
2201          // this allows to call widgets with the current app, otherwise everyone would need etemplate run rights
2202          if (strpos($on,"xajax_doXMLHTTP('etemplate.") !== false)
2203          {
2204              $on = preg_replace("/^xajax_doXMLHTTP\('etemplate\.([a-z]+_widget\.[a-zA-Z0-9_]+)\'/",'xajax_doXMLHTTP(\''.$GLOBALS['egw_info']['flags']['currentapp'].'.\\1.etemplate\'',$on);
2205          }
2206  
2207          return $on;
2208      }
2209  
2210      /**
2211      * applies stripslashes recursivly on each element of an array
2212      *
2213      * @param array &$var
2214      * @return array
2215      */
2216  	static function array_stripslashes($var)
2217      {
2218          if (!is_array($var))
2219          {
2220              return stripslashes($var);
2221          }
2222          foreach($var as $key => $val)
2223          {
2224              $var[$key] = is_array($val) ? self::array_stripslashes($val) : stripslashes($val);
2225          }
2226          return $var;
2227      }
2228  
2229      /**
2230      * makes necessary adjustments on $_POST after a eTemplate / form gots submitted
2231      *
2232      * This is only an internal function, dont call it direct use only exec
2233      * Process_show uses a list of input-fields/widgets generated by show.
2234      *
2235      * @internal
2236      * @param array $content $_POST[$cname], on return the adjusted content
2237      * @param array $to_process list of widgets/form-fields to process
2238      * @param string $cname='' basename of our returnt content (same as in call to show)
2239      * @param string $_type='regular' type of request
2240      * @return array with validation errors
2241      */
2242  	function process_show(&$content,$to_process,$cname='',$_type='regular')
2243      {
2244          if (!isset($content) || !is_array($content) || !is_array($to_process))
2245          {
2246              return;
2247          }
2248          if (is_int($this->debug) && $this->debug >= 1 || $this->debug == $this->name && $this->name)
2249          {
2250              echo "<p>process_show($this->name) cname='$cname' start: content ="; _debug_array($content);
2251          }
2252          $content_in = $cname ? array($cname => $content) : $content;
2253          $content = array();
2254          if (get_magic_quotes_gpc())
2255          {
2256              $content_in = self::array_stripslashes($content_in);
2257          }
2258          self::$validation_errors = array();
2259          $this->canceled = $this->button_pressed = False;
2260  
2261          foreach($to_process as $form_name => $type)
2262          {
2263              if (is_array($type))
2264              {
2265                  $attr = $type;
2266                  $type = $attr['type'];
2267              }
2268              else
2269              {
2270                  $attr = array();
2271              }
2272              $form_name = str_replace(array('&#x5B;','&#x5D;'), array('[',']'), $form_name);
2273              $value = self::get_array($content_in,$form_name,True,$GLOBALS['egw_info']['flags']['currentapp'] == 'etemplate' ? false : true );
2274              // The comment below does only aplay to normal posts, not for xajax. Files are not supported anyway by xajax atm.
2275              // not checked checboxes are not returned in HTML and file is in $_FILES and not in $content_in
2276              if($value === false && $_type == 'xajaxResponse' /*!in_array($type,array('checkbox','file'))*/) continue;
2277  
2278              if (isset($attr['blur']) && $attr['blur'] == $value)
2279              {
2280                  $value = '';    // blur-values is equal to emtpy
2281              }
2282              //echo "<p>process_show($this->name) loop was ".self::$loop.", $type: $form_name = ".array2string($value)."</p>\n";
2283              if (is_string($type)) list($type,$sub) = explode('-',$type);
2284              switch ($type)
2285              {
2286                  case 'ext':
2287                      $_cont = &self::get_array($content,$form_name,True);
2288                      if (!$this->extensionPostProcess($sub,$form_name,$_cont,$value))
2289                      {
2290                          //echo "\n<p><b>unsetting content[$form_name] !!!</b></p>\n";
2291                          $this->unset_array($content,$form_name);
2292                      }
2293                      // this else should NOT be unnecessary as $_cont is a reference to the index
2294                      // $form_name of $content, but under some circumstances a set/changed $_cont
2295                      // does not result in a change in $content -- RalfBecker 2004/09/18
2296                      // seems to depend on the number of (not existing) dimensions of the array -- -- RalfBecker 2005/04/06
2297                      elseif (!self::isset_array($content,$form_name))
2298                      {
2299                          //echo "<p>setting content[$form_name]='$_cont' because is was unset !!!</p>\n";
2300                          self::set_array($content,$form_name,$_cont);
2301                      }
2302                      if ($_cont === '' && $attr['needed'] && !$attr['blur'])
2303                      {
2304                          self::set_validation_error($form_name,lang('Field must not be empty !!!'),'');
2305                      }
2306                      break;
2307                  case 'htmlarea':
2308                      if ($attr['mode'] !== 'ascii')
2309                      {
2310                          self::set_array($content,$form_name,html::purify($value));
2311                          break;
2312                      }
2313                      // fall-throught for mode 'ascii', which is identical to textarea
2314                  case 'int':
2315                  case 'integer':
2316                  case 'float':
2317                  case 'passwd':
2318                  case 'text':
2319                  case 'textbox':
2320                  case 'hidden':
2321                  case 'textarea':
2322                  case 'colorpicker':
2323                      if ((string)$value === '' && $attr['needed'] && !$attr['blur'])
2324                      {
2325                          self::set_validation_error($form_name,lang('Field must not be empty !!!'),'');
2326                      }
2327                      if ((int) $attr['maxlength'] > 0 && mb_strlen($value) > (int) $attr['maxlength'])
2328                      {
2329                          $value = mb_substr($value,0,(int) $attr['maxlength']);
2330                      }
2331                      if ($attr['preg'] && !preg_match($attr['preg'],$value))
2332                      {
2333                          switch($type)
2334                          {
2335                              case 'int':
2336                              case 'integer':
2337                                  self::set_validation_error($form_name,lang("'%1' is not a valid integer !!!",$value),'');
2338                                  break;
2339                              case 'float':
2340                                  self::set_validation_error($form_name,lang("'%1' is not a valid floatingpoint number !!!",$value),'');
2341                                  break;
2342                              default:
2343                                  self::set_validation_error($form_name,lang("'%1' has an invalid format !!!",$value)/*." !preg_match('$attr[preg]', '$value')"*/,'');
2344                                  break;
2345                          }
2346                      }
2347                      elseif (in_array($type, array('int', 'integer', 'float')))    // cast int and float and check range
2348                      {
2349                          if ((string)$value !== '' || $attr['needed'])    // empty values are Ok if needed is not set
2350                          {
2351                              $value = $type != 'float' ? (int) $value : (float) str_replace(',','.',$value);    // allow for german (and maybe other) format
2352  
2353                              if (!empty($attr['min']) && $value < $attr['min'])
2354                              {
2355                                  self::set_validation_error($form_name,lang("Value has to be at least '%1' !!!",$attr['min']),'');
2356                                  $value = $type != 'float' ? (int) $attr['min'] : (float) $attr['min'];
2357                              }
2358                              if (!empty($attr['max']) && $value > $attr['max'])
2359                              {
2360                                  self::set_validation_error($form_name,lang("Value has to be at maximum '%1' !!!",$attr['max']),'');
2361                                  $value = $type != 'float' ? (int) $attr['max'] : (float) $attr['max'];
2362                              }
2363                          }
2364                      }
2365                      self::set_array($content,$form_name,$value);
2366                      break;
2367                  case 'cancel':    // cancel button ==> dont care for validation errors
2368                      if ($value)
2369                      {
2370                          $this->canceled = True;
2371                          self::set_array($content,$form_name,$value);
2372                      }
2373                      break;
2374                  case 'button':
2375                      if ($value)
2376                      {
2377                          $this->button_pressed = True;
2378                          self::set_array($content,$form_name,$value);
2379                      }
2380                      break;
2381                  case 'select':
2382                      if ($attr['allowed'])    // only check for $value is allowed, if allowed values are set
2383                      {
2384                          foreach(is_array($value) ? $value : array($value) as $val)
2385                          {
2386                              if (!($attr['multiple'] && !$val) && !in_array($val,$attr['allowed']))
2387                              {
2388                                  self::set_validation_error($form_name,lang("'%1' is NOT allowed ('%2')!",$val,implode("','",$attr['allowed'])),'');
2389                                  $value = '';
2390                                  break;
2391                              }
2392                          }
2393                      }
2394                      if (is_array($value)) $value = implode(',',$value);
2395                      if ($value === '' && $attr['needed'])
2396                      {
2397                          self::set_validation_error($form_name,lang('Field must not be empty !!!',$value),'');
2398                      }
2399                      self::set_array($content,$form_name,$value);
2400                      break;
2401                  case 'checkbox':
2402                      if (!$value && $attr['needed'])
2403                      {
2404                          self::set_validation_error($form_name,lang('Field must not be empty !!!',$value),'');
2405                      }
2406                      if ($value === false)
2407                      {
2408                          self::set_array($content,$form_name,$attr['multiple'] ? array() : $attr['unset_value']);    // need to be reported too
2409                      }
2410                      else
2411                      {
2412                          $value = array_intersect(is_array($value) ? $value : array($value),$attr['values']); // return only allowed values
2413                          self::set_array($content,$form_name,$attr['multiple'] ? $value : $value[0]);
2414                      }
2415                      break;
2416                  case 'file':
2417                      if (($multiple = substr($form_name,-2) == '[]'))
2418                      {
2419                          $form_name = substr($form_name,0,-2);
2420                      }
2421                      $parts = explode('[',str_replace(']','',$form_name));
2422                      $name = array_shift($parts);
2423                      $index  = count($parts) ? '['.implode('][',$parts).']' : '';
2424                      $value = array();
2425                      for($i=0; $i < 100; ++$i)
2426                      {
2427                          $file = array();
2428                          foreach(array('tmp_name','type','size','name','error') as $part)
2429                          {
2430                              if (!is_array($_FILES[$name])) break 2;    // happens eg. in forms set via xajax, which do not upload files
2431                              $file[$part] = $this->get_array($_FILES[$name],$part.$index.($multiple ? "[$i]" : ''));
2432                          }
2433                          if (!$multiple) $file['path'] = $this->get_array($content_in,substr($form_name,0,-1).'_path]');
2434                          $file['ip'] = $_SERVER['REMOTE_ADDR'];
2435                          // check if we have an upload error
2436                          if ($file['error'] && $file['name'] !== '' && !$file['size'])    // ignore empty upload boxes
2437                          {
2438                              self::set_validation_error($form_name.($multiple?'[]':''),
2439                                  lang('Error uploading file!')."\n".self::max_upload_size_message(),'');
2440                          }
2441                          if ((string)$file['name'] === '' || $file['tmp_name'] && function_exists('is_uploaded_file') && !is_uploaded_file($file['tmp_name']))
2442                          {
2443                              if ($multiple && ($file['name'] === '' || $file['error']))
2444                              {
2445                                  continue;    // ignore empty upload box
2446                              }
2447                              break;
2448                          }
2449                          if (!$multiple)
2450                          {
2451                              $value = $file;
2452                              break;
2453                          }
2454                          $value[] = $file;
2455                      }
2456                      //echo $form_name; _debug_array($value);
2457                      // fall-throught
2458                  default:
2459                      if ($attr['needed'] && !$value)
2460                      {
2461                          self::set_validation_error($form_name,lang('Field must not be empty !!!',$value),'');
2462                      }
2463                      self::set_array($content,$form_name,$value);
2464                      break;
2465              }
2466          }
2467          if ($cname)
2468          {
2469              $content = $content[$cname];
2470          }
2471          if (is_int($this->debug) && $this->debug >= 2 || $this->debug == $this->name && $this->name)
2472          {
2473              echo "<p>process_show($this->name) end: content ="; _debug_array($content);
2474              if (count(self::$validation_errors))
2475              {
2476                  echo "<p>validation_errors = "; _debug_array(self::$validation_errors);
2477              }
2478          }
2479          return self::$validation_errors;
2480      }
2481  
2482      /**
2483      * Sets a validation error, to be displayed in the next exec
2484      *
2485      * @param string $name (complete) name of the widget causing the error
2486      * @param string|boolean $error error-message already translated or false to reset all existing error for given name
2487      * @param string $cname=null set it to '', if the name is already a form-name, defaults to self::$name_vars
2488      */
2489  	static function set_validation_error($name,$error,$cname=null)
2490      {
2491          if (is_null($cname)) $cname = self::$name_vars;
2492          //echo "<p>self::set_validation_error('$name','$error','$cname');</p>\n";
2493          if ($cname) $name = self::form_name($cname,$name);
2494  
2495          if ($error === false)
2496          {
2497              unset(self::$validation_errors[$name]);
2498          }
2499          else
2500          {
2501              if (self::$validation_errors[$name])
2502              {
2503                  self::$validation_errors[$name] .= ', ';
2504              }
2505              self::$validation_errors[$name] .= $error;
2506          }
2507      }
2508  }

title

Description

title

Description

title

Description

title

title

Body