eGroupWare PHP Cross Reference Groupware Applications

Source: /phpgwapi/inc/class.egw_framework.inc.php - 2451 lines - 84329 bytes - Summary - Text - Print

Description: EGroupware API - framework baseclass

   1  <?php
   2  /**
   3   * EGroupware API - framework baseclass
   4   *
   5   * @link http://www.egroupware.org
   6   * @author Ralf Becker <RalfBecker-AT-outdoor-training.de> rewrite in 12/2006
   7   * @author Pim Snel <pim@lingewoud.nl> author of the idots template set
   8   * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
   9   * @package api
  10   * @subpackage framework
  11   * @access public
  12   * @version $Id$
  13   */
  14  
  15  /**
  16   * eGW API - framework: virtual base class for all template sets
  17   *
  18   * This class creates / renders the eGW framework:
  19   *  a) html header
  20   *  b) navbar
  21   *  c) sidebox menu
  22   *  d) main application area
  23   *  e) footer
  24   * It replaces several methods in the common class and the diverse templates.
  25   *
  26   * Existing apps either set $GLOBALS['egw_info']['flags']['noheader'] and call common::egw_header() and
  27   * (if $GLOBALS['egw_info']['flags']['nonavbar'] is true) parse_navbar() or it's done by the header.inc.php include.
  28   * The app's hook_sidebox then calls the public function display_sidebox().
  29   * And the app calls common::egw_footer().
  30   *
  31   * This are the authors (and their copyrights) of the original egw_header, egw_footer methods of the common class:
  32   * This file written by Dan Kuykendall <seek3r@phpgroupware.org>
  33   * and Joseph Engo <jengo@phpgroupware.org>
  34   * and Mark Peters <skeeter@phpgroupware.org>
  35   * and Lars Kneschke <lkneschke@linux-at-work.de>
  36   * Copyright (C) 2000, 2001 Dan Kuykendall
  37   * Copyright (C) 2003 Lars Kneschke
  38   */
  39  abstract class egw_framework
  40  {
  41      /**
  42       * Name of the template set, eg. 'idots'
  43       *
  44       * @var string
  45       */
  46      var $template;
  47  
  48      /**
  49       * Path relative to EGW_SERVER_ROOT for the template directory
  50       *
  51       * @var string
  52       */
  53      var $template_dir;
  54  
  55      /**
  56      * true if $this->header() was called
  57      *
  58      * @var boolean
  59      */
  60      static $header_done = false;
  61      /**
  62      * true if $this->navbar() was called
  63      *
  64      * @var boolean
  65      */
  66      static $navbar_done = false;
  67  
  68      /**
  69       * Constructor
  70       *
  71       * The constructor instanciates the class in $GLOBALS['egw']->framework, from where it should be used
  72       *
  73       * @return egw_framework
  74       */
  75  	function __construct($template)
  76      {
  77          $this->template = $template;
  78  
  79          if (!isset($GLOBALS['egw']->framework))
  80          {
  81              $GLOBALS['egw']->framework = $this;
  82          }
  83          $this->template_dir = '/phpgwapi/templates/'.$template;
  84      }
  85  
  86      /**
  87       * Factory method to instanciate framework object
  88       *
  89       * @return egw_framwork
  90       */
  91  	public static function factory()
  92      {
  93          if ((html::$ua_mobile || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile') &&
  94              file_exists(EGW_SERVER_ROOT.'/pixelegg'))
  95          {
  96              $GLOBALS['egw_info']['server']['template_set'] = 'pixelegg';
  97          }
  98          // default to idots, if no template_set set, to eg. not stall installations if settings use egw::link
  99          if (empty($GLOBALS['egw_info']['server']['template_set'])) $GLOBALS['egw_info']['server']['template_set'] = 'idots';
 100          // setup the new eGW framework (template sets)
 101          $class = $GLOBALS['egw_info']['server']['template_set'].'_framework';
 102          if (!class_exists($class))    // first try to autoload the class
 103          {
 104              require_once($file=EGW_INCLUDE_ROOT.'/phpgwapi/templates/'.$GLOBALS['egw_info']['server']['template_set'].'/class.'.$class.'.inc.php');
 105              if (!in_array($file,(array)$_SESSION['egw_required_files']))
 106              {
 107                  $_SESSION['egw_required_files'][] = $file;    // automatic load the used framework class, when the object get's restored
 108              }
 109          }
 110          // fall back to idots if a template does NOT support current user-agent
 111          if ($class != 'idots_framework' && method_exists($class,'is_supported_user_agent') &&
 112              !call_user_func(array($class,'is_supported_user_agent')))
 113          {
 114              $GLOBALS['egw_info']['server']['template_set'] = 'idots';
 115              return self::factory();
 116          }
 117          return new $class($GLOBALS['egw_info']['server']['template_set']);
 118      }
 119  
 120      /**
 121       * Additional attributes or urls for CSP script-src 'self'
 122       *
 123       * 'unsafe-eval' is currently allways added, as it is used in a couple of places.
 124       *
 125       * @var array
 126       */
 127      private static $csp_script_src_attrs = array("'unsafe-eval'");
 128  
 129      /**
 130       * Set/get Content-Security-Policy attributes for script-src: 'unsafe-eval' and/or 'unsafe-inline'
 131       *
 132       * Using CK-Editor currently requires both to be set :(
 133       *
 134       * Old pre-et2 apps might need to call egw_framework::csp_script_src_attrs(array('unsafe-eval','unsafe-inline'))
 135       *
 136       * EGroupware itself currently still requires 'unsafe-eval'!
 137       *
 138       * @param string|array $set =array() 'unsafe-eval' and/or 'unsafe-inline' (without quotes!) or URL (incl. protocol!)
 139       * @return string with attributes eg. "'unsafe-eval' 'unsafe-inline'"
 140       */
 141  	public static function csp_script_src_attrs($set=null)
 142      {
 143          foreach((array)$set as $attr)
 144          {
 145              if (in_array($attr, array('none', 'self', 'unsafe-eval', 'unsafe-inline')))
 146              {
 147                  $attr = "'$attr'";    // automatic add quotes
 148              }
 149              if (!in_array($attr, self::$csp_script_src_attrs))
 150              {
 151                  self::$csp_script_src_attrs[] = $attr;
 152                  //error_log(__METHOD__."() setting CSP script-src $attr ".function_backtrace());
 153              }
 154          }
 155          //error_log(__METHOD__."(".array2string($set).") returned ".array2string(implode(' ', self::$csp_script_src_attrs)).' '.function_backtrace());
 156          return implode(' ', self::$csp_script_src_attrs);
 157      }
 158  
 159      /**
 160       * Additional attributes or urls for CSP style-src 'self'
 161       *
 162       * 'unsafe-inline' is currently allways added, as it is used in a couple of places.
 163       *
 164       * @var array
 165       */
 166      private static $csp_style_src_attrs = array("'unsafe-inline'");
 167  
 168      /**
 169       * Set/get Content-Security-Policy attributes for style-src: 'unsafe-inline'
 170       *
 171       * EGroupware itself currently still requires 'unsafe-inline'!
 172       *
 173       * @param string|array $set =array() 'unsafe-inline' (without quotes!) and/or URL (incl. protocol!)
 174       * @return string with attributes eg. "'unsafe-inline'"
 175       */
 176  	public static function csp_style_src_attrs($set=null)
 177      {
 178          foreach((array)$set as $attr)
 179          {
 180              if (in_array($attr, array('none', 'self', 'unsafe-inline')))
 181              {
 182                  $attr = "'$attr'";    // automatic add quotes
 183              }
 184              if (!in_array($attr, self::$csp_style_src_attrs))
 185              {
 186                  self::$csp_style_src_attrs[] = $attr;
 187                  //error_log(__METHOD__."() setting CSP script-src $attr ".function_backtrace());
 188              }
 189          }
 190          //error_log(__METHOD__."(".array2string($set).") returned ".array2string(implode(' ', self::$csp_script_src_attrs)).' '.function_backtrace());
 191          return implode(' ', self::$csp_style_src_attrs);
 192      }
 193  
 194      /**
 195       * Additional attributes or urls for CSP connect-src 'self'
 196       *
 197       * @var array
 198       */
 199      private static $csp_connect_src_attrs = array();
 200  
 201      /**
 202       * Set/get Content-Security-Policy attributes for connect-src:
 203       *
 204       * @param string|array $set =array() URL (incl. protocol!)
 205       * @return string with attributes eg. "'unsafe-inline'"
 206       */
 207  	public static function csp_connect_src_attrs($set=null)
 208      {
 209          foreach((array)$set as $attr)
 210          {
 211              if (!in_array($attr, self::$csp_connect_src_attrs))
 212              {
 213                  self::$csp_connect_src_attrs[] = $attr;
 214                  //error_log(__METHOD__."() setting CSP script-src $attr ".function_backtrace());
 215              }
 216          }
 217          //error_log(__METHOD__."(".array2string($set).") returned ".array2string(implode(' ', self::$csp_connect_src_attrs)).' '.function_backtrace());
 218          return implode(' ', self::$csp_connect_src_attrs);
 219      }
 220  
 221      /**
 222       * Additional attributes or urls for CSP frame-src 'self'
 223       *
 224       * @var array
 225       */
 226      private static $csp_frame_src_attrs;
 227  
 228      /**
 229       * Set/get Content-Security-Policy attributes for frame-src:
 230       *
 231       * Calling this method with an empty array sets no frame-src, but "'self'"!
 232       *
 233       * @param string|array $set =array() URL (incl. protocol!)
 234       * @return string with attributes eg. "'unsafe-inline'"
 235       */
 236  	public static function csp_frame_src_attrs($set=null)
 237      {
 238          // set frame-src attrs of API and apps via hook
 239          if (!isset(self::$csp_frame_src_attrs) && !isset($set))
 240          {
 241              $frame_src = array('manual.egroupware.org', 'www.egroupware.org');
 242              if (($app_additional = $GLOBALS['egw']->hooks->process('csp-frame-src')))
 243              {
 244                  foreach($app_additional as $addtional)
 245                  {
 246                      if ($addtional) $frame_src = array_unique(array_merge($frame_src, $addtional));
 247                  }
 248              }
 249              return self::csp_frame_src_attrs($frame_src);
 250          }
 251  
 252          if (!isset(self::$csp_frame_src_attrs)) self::$csp_frame_src_attrs = array();
 253  
 254          foreach((array)$set as $attr)
 255          {
 256              if (!in_array($attr, self::$csp_frame_src_attrs))
 257              {
 258                  self::$csp_frame_src_attrs[] = $attr;
 259                  //error_log(__METHOD__."() setting CSP script-src $attr ".function_backtrace());
 260              }
 261          }
 262          //error_log(__METHOD__."(".array2string($set).") returned ".array2string(implode(' ', self::$csp_frame_src_attrs)).' '.function_backtrace());
 263          return implode(' ', self::$csp_frame_src_attrs);
 264      }
 265  
 266      /**
 267       * Send HTTP headers: Content-Type and Content-Security-Policy
 268       */
 269  	public function send_headers()
 270      {
 271          // add a content-type header to overwrite an existing default charset in apache (AddDefaultCharset directiv)
 272          header('Content-type: text/html; charset='.translation::charset());
 273  
 274          // content-security-policy header:
 275          // - "script-src 'self' 'unsafe-eval'" allows only self and eval (eg. ckeditor), but forbids inline scripts, onchange, etc
 276          // - "connect-src 'self'" allows ajax requests only to self
 277          // - "style-src 'self' 'unsave-inline'" allows only self and inline style, which we need
 278          // - "frame-src 'self' manual.egroupware.org" allows frame and iframe content only for self or manual.egroupware.org
 279          $csp = "script-src 'self' ".self::csp_script_src_attrs().
 280              "; connect-src 'self' ".self::csp_connect_src_attrs().
 281              "; style-src 'self' ".self::csp_style_src_attrs().
 282              "; frame-src 'self' ".self::csp_frame_src_attrs();
 283  
 284          //$csp = "default-src * 'unsafe-eval' 'unsafe-inline'";    // allow everything
 285          header("Content-Security-Policy: $csp");
 286          header("X-Webkit-CSP: $csp");    // Chrome: <= 24, Safari incl. iOS
 287          header("X-Content-Security-Policy: $csp");    // FF <= 22
 288  
 289          // allow client-side to detect first load aka just logged in
 290          $reload_count =& egw_cache::getSession(__CLASS__, 'framework-reload');
 291          self::$extra['framework-reload'] = (int)(bool)$reload_count++;
 292      }
 293  
 294      /**
 295       * Constructor for static variables
 296       */
 297  	public static function init_static()
 298      {
 299          self::$js_include_mgr = new egw_include_mgr(array(
 300              // We need LABjs, but putting it through egw_include_mgr causes it to re-load itself
 301              //'/phpgwapi/js/labjs/LAB.src.js',
 302  
 303              // allways load jquery (not -ui) and egw_json first
 304              '/phpgwapi/js/jquery/jquery.js',
 305              // always include javascript helper functions
 306              '/phpgwapi/js/jsapi/jsapi.js',
 307              '/phpgwapi/js/jsapi/egw.js',
 308          ));
 309      }
 310  
 311      /**
 312       * PHP4-Constructor
 313       *
 314       * The constructor instanciates the class in $GLOBALS['egw']->framework, from where it should be used
 315       *
 316       * @deprecated use __construct()
 317       */
 318  	function egw_framework($template)
 319      {
 320          self::__construct($template);
 321      }
 322  
 323      /**
 324       * Link url generator
 325       *
 326       * @param string $url    The url the link is for
 327       * @param string|array    $extravars    Extra params to be passed to the url
 328       * @param string $link_app =null if appname or true, some templates generate a special link-handler url
 329       * @return string    The full url after processing
 330       */
 331  	static function link($url, $extravars = '', $link_app=null)
 332      {
 333          unset($link_app);    // not used by required by function signature
 334          return $GLOBALS['egw']->session->link($url, $extravars);
 335      }
 336  
 337      /**
 338       * Redirects direct to a generated link
 339       *
 340       * @param string $url    The url the link is for
 341       * @param string|array    $extravars    Extra params to be passed to the url
 342       * @param string $link_app =null if appname or true, some templates generate a special link-handler url
 343       * @return string    The full url after processing
 344       */
 345  	static function redirect_link($url, $extravars='', $link_app=null)
 346      {
 347          egw::redirect(self::link($url, $extravars), $link_app);
 348      }
 349  
 350      /**
 351       * Renders an applicaton page with the complete eGW framework (header, navigation and menu)
 352       *
 353       * This is the (new) prefered way to render a page in eGW!
 354       *
 355       * @param string $content html of the main application area
 356       * @param string $app_header =null application header, default what's set in $GLOBALS['egw_info']['flags']['app_header']
 357       * @param string $navbar =null show the navigation, default !$GLOBALS['egw_info']['flags']['nonavbar'], false gives a typical popu
 358       *
 359       */
 360  	function render($content,$app_header=null,$navbar=null)
 361      {
 362          if (!is_null($app_header)) $GLOBALS['egw_info']['flags']['app_header'] = $app_header;
 363          if (!is_null($navbar)) $GLOBALS['egw_info']['flags']['nonavbar'] = !$navbar;
 364  
 365          echo $this->header();
 366  
 367          if (!isset($GLOBALS['egw_info']['flags']['nonavbar']) || !$GLOBALS['egw_info']['flags']['nonavbar'])
 368          {
 369              echo $this->navbar();
 370          }
 371          echo $content;
 372  
 373          echo $this->footer();
 374      }
 375  
 376      /**
 377       * Extra values send as data attributes to script tag of egw.js
 378       *
 379       * @var array
 380       */
 381      protected static $extra = array();
 382  
 383      /**
 384       * Refresh given application $targetapp display of entry $app $id, incl. outputting $msg
 385       *
 386       * Calling egw_refresh and egw_message on opener in a content security save way
 387       *
 388       * To provide more information about necessary refresh an automatic 9th parameter is added
 389       * containing an object with application-name as attributes containing an array of linked ids
 390       * (adding happens in get_extras to give apps time to link new entries!).
 391       *
 392       * @param string $msg message (already translated) to show, eg. 'Entry deleted'
 393       * @param string $app application name
 394       * @param string|int $id =null id of entry to refresh
 395       * @param string $type =null either 'update', 'edit', 'delete', 'add' or null
 396       * - update: request just modified data from given rows.
 397       *    Sorting and filtering are not considered, so if the sort field is changed,
 398       *    the row will not be moved.  If the current filtering could include or exclude
 399       *    the record, use edit.
 400       * - edit: rows changed, but sorting or filtering may be affected.  Requires full reload.
 401       * - delete: just delete the given rows clientside (no server interaction neccessary)
 402       * - add: requires full reload for proper sorting
 403       * - null: full reload
 404       * @param string $targetapp =null which app's window should be refreshed, default current
 405       * @param string|RegExp $replace =null regular expression to replace in url
 406       * @param string $with =null
 407       * @param string $msg_type =null 'error', 'warning' or 'success' (default)
 408       */
 409  	public static function refresh_opener($msg, $app, $id=null, $type=null, $targetapp=null, $replace=null, $with=null, $msg_type=null)
 410      {
 411          //error_log(__METHOD__.'('.array2string(func_get_args()).')');
 412          self::$extra['refresh-opener'] = func_get_args();
 413  
 414          unset($msg, $app, $id, $type, $targetapp, $replace, $with, $msg_type);    // used only via func_get_args();
 415      }
 416  
 417      /**
 418       * Display an error or regular message
 419       *
 420       * Calls egw_message on client-side in a content security save way
 421       *
 422       * @param string $msg message to show
 423       * @param string $type ='success' 'error', 'warning' or 'success' (default)
 424       */
 425  	public static function message($msg, $type='success')
 426      {
 427          self::$extra['message'] = func_get_args();
 428  
 429          unset($msg, $type);    // used only via func_get_args();
 430      }
 431  
 432      /**
 433       * Open a popup independent if we run as json or regular request
 434       *
 435       * @param string $link
 436       * @param string $target
 437       * @param string $popup
 438       */
 439  	public static function popup($link, $target='_blank', $popup='640x480')
 440      {
 441          // default params are not returned by func_get_args!
 442          $args = func_get_args()+array(null, '_blank', '640x480');
 443  
 444          unset($link, $target, $popup);    // used only via func_get_args()
 445  
 446          if (egw_json_request::isJSONRequest())
 447          {
 448              egw_json_response::get()->apply('egw.open_link', $args);
 449          }
 450          else
 451          {
 452              self::$extra['popup'] = $args;
 453          }
 454      }
 455  
 456      /**
 457       * Close (popup) window, use to replace egw_framework::onload('window.close()') in a content security save way
 458       *
 459       * @param string $alert_msg ='' optional message to display as alert, before closing the window
 460       */
 461  	public static function window_close($alert_msg='')
 462      {
 463          //error_log(__METHOD__."()");
 464          self::$extra['window-close'] = $alert_msg ? $alert_msg : true;
 465  
 466          // are we in ajax_process_content -> just return extra data, with close instructions
 467          if (preg_match('/etemplate(_new)?(::|\.)ajax_process_content/', $_GET['menuaction']))
 468          {
 469              $response = egw_json_response::get();
 470              $response->generic('et2_load', egw_framework::get_extra());
 471          }
 472          else
 473          {
 474              $GLOBALS['egw']->framework->render('', false, false);
 475          }
 476          common::egw_exit();
 477      }
 478  
 479      /**
 480       * Close (popup) window, use to replace egw_framework::onload('window.close()') in a content security save way
 481       */
 482  	public static function window_focus()
 483      {
 484          //error_log(__METHOD__."()");
 485          self::$extra['window-focus'] = true;
 486      }
 487  
 488      /**
 489       * Allow app to store arbitray values in egw script tag
 490       *
 491       * Attribute name will be "data-$app-$name" and value will be json serialized, if not scalar.
 492       *
 493       * @param string $app
 494       * @param string $name
 495       * @param mixed $value
 496       */
 497  	public static function set_extra($app, $name, $value)
 498      {
 499          self::$extra[$app.'-'.$name] = $value;
 500      }
 501  
 502      /**
 503       * Clear all extra data
 504       */
 505  	public static function clear_extra()
 506      {
 507          self::$extra = array();
 508      }
 509  
 510      /**
 511       * Allow eg. ajax to query content set via refresh_opener or window_close
 512       *
 513       * @return array content of egw_framework::$extra
 514       */
 515  	public static function get_extra()
 516      {
 517          // adding links of refreshed entry, to give others apps more information about necessity to refresh
 518          if (isset(self::$extra['refresh-opener']) && count(self::$extra['refresh-opener']) <= 8 &&    // do not run twice
 519              !empty(self::$extra['refresh-opener'][1]) && !empty(self::$extra['refresh-opener'][2]))    // app/id given
 520          {
 521              $links = egw_link::get_links(self::$extra['refresh-opener'][1], self::$extra['refresh-opener'][2]);
 522              $apps = array();
 523              foreach($links as $link)
 524              {
 525                  $apps[$link['app']][] = $link['id'];
 526              }
 527              while (count(self::$extra['refresh-opener']) < 8)
 528              {
 529                  self::$extra['refresh-opener'][] = null;
 530              }
 531              self::$extra['refresh-opener'][] = $apps;
 532          }
 533          return self::$extra;
 534      }
 535  
 536      /**
 537       * Returns the html-header incl. the opening body tag
 538       *
 539       * @return string with html
 540       */
 541      abstract function header(array $extra=array());
 542  
 543      /**
 544       * Returns the html from the body-tag til the main application area (incl. opening div tag)
 545       *
 546       * If header has NOT been called, also return header content!
 547       * No need to manually call header, this allows to postpone header so navbar / sidebox can include JS or CSS.
 548       *
 549       * @return string with html
 550       */
 551      abstract function navbar();
 552  
 553      /**
 554       * Return true if we are rendering the top-level EGroupware window
 555       *
 556       * A top-level EGroupware window has a navbar: eg. no popup and for a framed template (jdots) only frameset itself
 557       *
 558       * @return boolean $consider_navbar_not_yet_called_as_true=true
 559       * @return boolean
 560       */
 561      abstract function isTop($consider_navbar_not_yet_called_as_true=true);
 562  
 563      /**
 564       * Returns the content of one sidebox
 565       *
 566       * @param string $appname
 567       * @param string $menu_title
 568       * @param array $file
 569       * @param string $type =null 'admin', 'preferences', 'favorites', ...
 570       */
 571      abstract function sidebox($appname,$menu_title,$file,$type=null);
 572  
 573      /**
 574       * Returns the html from the closing div of the main application area to the closing html-tag
 575       *
 576       * @return string
 577       */
 578      abstract function footer();
 579  
 580      /**
 581       * Displays the login screen
 582       *
 583       * @param string $extra_vars for login url
 584       * @param string $change_passwd =null string with message to render input fields for password change
 585      */
 586  	function login_screen($extra_vars, $change_passwd=null)
 587      {
 588          self::csp_frame_src_attrs(array());    // array() no external frame-sources
 589  
 590          //error_log(__METHOD__."() this->template=$this->template, this->template_dir=$this->template_dir, get_class(this)=".get_class($this));
 591          $tmpl = new Template(EGW_SERVER_ROOT.$this->template_dir);
 592  
 593          $tmpl->set_file(array('login_form' => 'login.tpl'));
 594  
 595          $tmpl->set_var('lang_message',$GLOBALS['loginscreenmessage']);
 596  
 597          // hide change-password fields, if not requested
 598          if (!$change_passwd)
 599          {
 600              $tmpl->set_block('login_form','change_password');
 601              $tmpl->set_var('change_password', '');
 602              $tmpl->set_var('lang_password',lang('password'));
 603              $tmpl->set_var('cd',check_logoutcode($_GET['cd']));
 604              $tmpl->set_var('cd_class', isset($_GET['cd']) && $_GET['cd'] != 1 ? 'error' : '');
 605              $last_loginid = $_COOKIE['last_loginid'];
 606              $last_domain  = $_COOKIE['last_domain'];
 607              $tmpl->set_var('passwd', '');
 608              $tmpl->set_var('autofocus_login', 'autofocus');
 609          }
 610          else
 611          {
 612              $tmpl->set_var('lang_password',lang('Old password'));
 613              $tmpl->set_var('lang_new_password',lang('New password'));
 614              $tmpl->set_var('lang_repeat_password',lang('Repeat password'));
 615              $tmpl->set_var('cd', $change_passwd);
 616              $tmpl->set_var('cd_class', 'error');
 617              $last_loginid = $_POST['login'];
 618              $last_domain  = $_POST['domain'];
 619              $tmpl->set_var('passwd', $_POST['passwd']);
 620              $tmpl->set_var('autofocus_login', '');
 621              $tmpl->set_var('autofocus_new_passwd', 'autofocus');
 622          }
 623          if($GLOBALS['egw_info']['server']['show_domain_selectbox'])
 624          {
 625              foreach(array_keys($GLOBALS['egw_domain']) as $domain)
 626              {
 627                  $domains[$domain] = $domain;
 628              }
 629              $tmpl->set_var(array(
 630                  'lang_domain'   => lang('domain'),
 631                  'select_domain' => html::select('logindomain',$last_domain,$domains,true,'tabindex="2"',0,false),
 632              ));
 633          }
 634          else
 635          {
 636              /* trick to make domain section disapear */
 637              $tmpl->set_block('login_form','domain_selection');
 638              $tmpl->set_var('domain_selection',$GLOBALS['egw_info']['user']['domain'] ?
 639              html::input_hidden('logindomain',$GLOBALS['egw_info']['user']['domain']) : '');
 640  
 641              if($last_loginid !== '')
 642              {
 643                  reset($GLOBALS['egw_domain']);
 644                  list($default_domain) = each($GLOBALS['egw_domain']);
 645  
 646                  if(!empty ($last_domain) && $last_domain != $default_domain)
 647                  {
 648                      $last_loginid .= '@' . $last_domain;
 649                  }
 650              }
 651          }
 652  
 653          $config_reg = config::read('registration');
 654  
 655          if($config_reg['enable_registration'])
 656          {
 657              if ($config_reg['register_link'])
 658              {
 659                  $reg_link='&nbsp;<a href="'. egw::link('/registration/index.php','lang_code='.$_GET['lang']). '">'.lang('Not a user yet? Register now').'</a><br/>';
 660              }
 661              if ($config_reg['lostpassword_link'])
 662              {
 663                  $lostpw_link='&nbsp;<a href="'. egw::link('/registration/index.php','menuaction=registration.registration_ui.lost_password&lang_code='.$_GET['lang']). '">'.lang('Lost password').'</a><br/>';
 664              }
 665              if ($config_reg['lostid_link'])
 666              {
 667                  $lostid_link='&nbsp;<a href="'. egw::link('/registration/index.php','menuaction=registration.registration_ui.lost_username&lang_code='.$_GET['lang']). '">'.lang('Lost Login Id').'</a><br/>';
 668              }
 669  
 670              /* if at least one option of "registration" is activated display the registration section */
 671              if($config_reg['register_link'] || $config_reg['lostpassword_link'] || $config_reg['lostid_link'] )
 672              {
 673                  $tmpl->set_var(array(
 674                  'register_link'     => $reg_link,
 675                  'lostpassword_link' => $lostpw_link,
 676                  'lostid_link'       => $lostid_link,
 677                  ));
 678              }
 679              else
 680              {
 681                  /* trick to make registration section disapear */
 682                  $tmpl->set_block('login_form','registration');
 683                  $tmpl->set_var('registration','');
 684              }
 685          }
 686  
 687          $tmpl->set_var('login_url', $GLOBALS['egw_info']['server']['webserver_url'] . '/login.php' . $extra_vars);
 688          $tmpl->set_var('version', $GLOBALS['egw_info']['server']['versions']['phpgwapi']);
 689          $tmpl->set_var('login', $last_loginid);
 690  
 691          $tmpl->set_var('lang_username',lang('username'));
 692          $tmpl->set_var('lang_login',lang('login'));
 693  
 694          $tmpl->set_var('website_title', $GLOBALS['egw_info']['server']['site_title']);
 695          $tmpl->set_var('template_set',$this->template);
 696  
 697          if (substr($GLOBALS['egw_info']['server']['login_logo_file'], 0, 4) == 'http' ||
 698              $GLOBALS['egw_info']['server']['login_logo_file'][0] == '/')
 699          {
 700              $var['logo_file'] = $GLOBALS['egw_info']['server']['login_logo_file'];
 701          }
 702          else
 703          {
 704              $var['logo_file'] = common::image('phpgwapi',$GLOBALS['egw_info']['server']['login_logo_file']?$GLOBALS['egw_info']['server']['login_logo_file']:'logo', '', null);    // null=explicit allow svg
 705          }
 706          $var['logo_url'] = $GLOBALS['egw_info']['server']['login_logo_url']?$GLOBALS['egw_info']['server']['login_logo_url']:'http://www.eGroupWare.org';
 707          if (substr($var['logo_url'],0,4) != 'http')
 708          {
 709              $var['logo_url'] = 'http://'.$var['logo_url'];
 710          }
 711          $var['logo_title'] = $GLOBALS['egw_info']['server']['login_logo_title']?$GLOBALS['egw_info']['server']['login_logo_title']:'www.eGroupWare.org';
 712          $tmpl->set_var($var);
 713  
 714          /* language section if activated in site config */
 715          if (@$GLOBALS['egw_info']['server']['login_show_language_selection'])
 716          {
 717              $tmpl->set_var(array(
 718                  'lang_language' => lang('Language'),
 719                  'select_language' => html::select('lang',$GLOBALS['egw_info']['user']['preferences']['common']['lang'],
 720                  translation::get_installed_langs(),true,'tabindex="1"',0,false),
 721              ));
 722          }
 723          else
 724          {
 725              $tmpl->set_block('login_form','language_select');
 726              $tmpl->set_var('language_select','');
 727          }
 728  
 729          /********************************************************\
 730          * Check if authentification via cookies is allowed       *
 731          * and place a time selectbox, how long cookie is valid   *
 732          \********************************************************/
 733  
 734          if($GLOBALS['egw_info']['server']['allow_cookie_auth'])
 735          {
 736              $tmpl->set_block('login_form','remember_me_selection');
 737              $tmpl->set_var('lang_remember_me',lang('Remember me'));
 738              $tmpl->set_var('select_remember_me',html::select('remember_me', '', array(
 739                  '' => lang('not'),
 740                  '1hour' => lang('1 Hour'),
 741                  '1day' => lang('1 Day'),
 742                  '1week'=> lang('1 Week'),
 743                  '1month' => lang('1 Month'),
 744                  'forever' => lang('Forever'),
 745              ),true,'tabindex="3"',0,false));
 746          }
 747          else
 748          {
 749              /* trick to make remember_me section disapear */
 750              $tmpl->set_block('login_form','remember_me_selection');
 751              $tmpl->set_var('remember_me_selection','');
 752          }
 753          $tmpl->set_var('autocomplete', ($GLOBALS['egw_info']['server']['autocomplete_login'] ? 'autocomplete="off"' : ''));
 754  
 755          // load jquery for login screen too
 756          self::validate_file('jquery', 'jquery');
 757  
 758          $this->render($tmpl->fp('loginout','login_form'),false,false);
 759      }
 760  
 761      /**
 762      * displays a login denied message
 763      */
 764  	function denylogin_screen()
 765      {
 766          $tmpl = new Template(EGW_SERVER_ROOT.$this->template_dir);
 767  
 768          $tmpl->set_file(array(
 769              'login_form' => 'login_denylogin.tpl'
 770          ));
 771  
 772          $tmpl->set_var(array(
 773              'template_set' => 'default',
 774              'deny_msg'     => lang('Oops! You caught us in the middle of system maintainance.').
 775              '<br />'.lang('Please, check back with us shortly.'),
 776          ));
 777  
 778          // load jquery for deny-login screen too
 779          self::validate_file('jquery', 'jquery');
 780  
 781          $this->render($tmpl->fp('loginout','login_form'),false,false);
 782      }
 783  
 784      /**
 785       * Get footer as array to eg. set as vars for a template (from idots' head.inc.php)
 786       *
 787       * @return array
 788       */
 789  	public function _get_footer()
 790      {
 791          $var = Array(
 792              'img_root'       => $GLOBALS['egw_info']['server']['webserver_url'] . $this->template_dir.'/images',
 793              'version'        => $GLOBALS['egw_info']['server']['versions']['phpgwapi']
 794          );
 795          $var['page_generation_time'] = '';
 796          if($GLOBALS['egw_info']['user']['preferences']['common']['show_generation_time'])
 797          {
 798              $totaltime = sprintf('%4.2lf',microtime(true) - $GLOBALS['egw_info']['flags']['page_start_time']);
 799  
 800              $var['page_generation_time'] = '<div class="pageGenTime" id="divGenTime_'.$GLOBALS['egw_info']['flags']['currentapp'].'"><span>'.lang('Page was generated in %1 seconds',$totaltime);
 801              if ($GLOBALS['egw_info']['flags']['session_restore_time'])
 802              {
 803                  $var['page_generation_time'] .= ' '.lang('(session restored in %1 seconds)',
 804                      sprintf('%4.2lf',$GLOBALS['egw_info']['flags']['session_restore_time']));
 805              }
 806              $var['page_generation_time'] .= '</span></div>';
 807          }
 808          $var['powered_by'] = '<a href="http://www.egroupware.org/" target="_blank">'.
 809              lang('Powered by').' Stylite\'s EGroupware '.
 810              $GLOBALS['egw_info']['server']['versions']['phpgwapi'].'</a>';
 811  
 812          return $var;
 813      }
 814  
 815      /**
 816       * Get the (depricated) application footer
 817       *
 818       * @return string html
 819       */
 820  	protected static function _get_app_footer()
 821      {
 822          ob_start();
 823          // Include the apps footer files if it exists
 824          if (EGW_APP_INC != EGW_API_INC &&    // this prevents an endless inclusion on the homepage
 825                                              // (some apps set currentapp in hook_home => it's not releyable)
 826              (file_exists (EGW_APP_INC . '/footer.inc.php') || isset($_GET['menuaction'])) &&
 827              $GLOBALS['egw_info']['flags']['currentapp'] != 'home' &&
 828              $GLOBALS['egw_info']['flags']['currentapp'] != 'login' &&
 829              $GLOBALS['egw_info']['flags']['currentapp'] != 'logout' &&
 830              !@$GLOBALS['egw_info']['flags']['noappfooter'])
 831          {
 832              list(, $class) = explode('.',(string)$_GET['menuaction']);
 833              if ($class && is_object($GLOBALS[$class]) && is_array($GLOBALS[$class]->public_functions) &&
 834                  isset($GLOBALS[$class]->public_functions['footer']))
 835              {
 836                  $GLOBALS[$class]->footer();
 837              }
 838              elseif(file_exists(EGW_APP_INC.'/footer.inc.php'))
 839              {
 840                  include(EGW_APP_INC . '/footer.inc.php');
 841              }
 842          }
 843          $content = ob_get_contents();
 844          ob_end_clean();
 845  
 846          return $content;
 847      }
 848  
 849      /**
 850       * Get header as array to eg. set as vars for a template (from idots' head.inc.php)
 851       *
 852       * @param array $extra =array() extra attributes passed as data-attribute to egw.js
 853       * @return array
 854       */
 855  	protected function _get_header(array $extra=array())
 856      {
 857          // display password expires in N days message once per session
 858          $message = null;
 859          if ($GLOBALS['egw_info']['flags']['currentapp'] != 'login' &&
 860              auth::check_password_change($message) !== true)
 861          {
 862              self::message($message, 'info');
 863          }
 864  
 865          // get used language code (with a little xss check, if someone tries to sneak something in)
 866          if (preg_match('/^[a-z]{2}(-[a-z]{2})?$/',$GLOBALS['egw_info']['user']['preferences']['common']['lang']))
 867          {
 868              $lang_code = $GLOBALS['egw_info']['user']['preferences']['common']['lang'];
 869          }
 870          // IE specific fixes
 871          if (html::$user_agent == 'msie')
 872          {
 873              // tell IE to use it's own mode, not old compatibility modes (set eg. via group policy for all intranet sites)
 874              // has to be before any other header tags, but meta and title!!!
 875              $pngfix = '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'."\n";
 876  
 877              // pngfix for IE6 defaults to yes
 878              if(!$GLOBALS['egw_info']['user']['preferences']['common']['disable_pngfix'] && html::$ua_version < 7)
 879              {
 880                  $pngfix_src = $GLOBALS['egw_info']['server']['webserver_url'] . '/phpgwapi/templates/idots/js/pngfix.js';
 881                  $pngfix .= '<!-- This solves the Internet Explorer PNG-transparency bug, but only for IE 5.5 - 6.0 and higher -->
 882                  <!--[if lt IE 7.0]>
 883                  <script src="'.$pngfix_src.'" type="text/javascript">
 884                  </script>
 885                  <![endif]-->';
 886              }
 887          }
 888  
 889          $app = $GLOBALS['egw_info']['flags']['currentapp'];
 890          $app_title = isset($GLOBALS['egw_info']['apps'][$app]) ? $GLOBALS['egw_info']['apps'][$app]['title'] : lang($app);
 891          $app_header = $GLOBALS['egw_info']['flags']['app_header'] ? $GLOBALS['egw_info']['flags']['app_header'] : $app_title;
 892          $site_title = strip_tags($GLOBALS['egw_info']['server']['site_title'].' ['.($app_header ? $app_header : $app_title).']');
 893  
 894          // send appheader to clientside
 895          $extra['app-header'] = $app_header;
 896  
 897          if($GLOBALS['egw_info']['flags']['currentapp'] != 'wiki') $robots ='<meta name="robots" content="none" />';
 898          if (substr($GLOBALS['egw_info']['server']['favicon_file'],0,4) == 'http')
 899          {
 900              $var['favicon_file'] = $GLOBALS['egw_info']['server']['favicon_file'];
 901          }
 902          else
 903          {
 904              $var['favicon_file'] = common::image('phpgwapi',$GLOBALS['egw_info']['server']['favicon_file']?$GLOBALS['egw_info']['server']['favicon_file']:'favicon.ico');
 905          }
 906  
 907          if ($GLOBALS['egw_info']['flags']['include_wz_tooltip'] &&
 908              file_exists(EGW_SERVER_ROOT.($wz_tooltip = '/phpgwapi/js/wz_tooltip/wz_tooltip.js')))
 909          {
 910              $include_wz_tooltip = '<script src="'.$GLOBALS['egw_info']['server']['webserver_url'].
 911                  $wz_tooltip.'?'.filemtime(EGW_SERVER_ROOT.$wz_tooltip).'" type="text/javascript"></script>';
 912          }
 913          return $this->_get_css()+array(
 914              'img_icon'            => $var['favicon_file'],
 915              'img_shortcut'        => $var['favicon_file'],
 916              'pngfix'            => $pngfix,
 917              'lang_code'            => $lang_code,
 918              'charset'           => translation::charset(),
 919              'website_title'     => $site_title,
 920              'body_tags'         => self::_get_body_attribs(),
 921              'java_script'       => self::_get_js($extra),
 922              'meta_robots'        => $robots,
 923              'dir_code'            => lang('language_direction_rtl') != 'rtl' ? '' : ' dir="rtl"',
 924              'include_wz_tooltip'=> $include_wz_tooltip,
 925              'webserver_url'     => $GLOBALS['egw_info']['server']['webserver_url'],
 926          );
 927      }
 928  
 929      /**
 930       * Get navbar as array to eg. set as vars for a template (from idots' navbar.inc.php)
 931       *
 932       * @param array $apps navbar apps from _get_navbar_apps
 933       * @return array
 934       */
 935  	protected function _get_navbar($apps)
 936      {
 937          $var['img_root'] = $GLOBALS['egw_info']['server']['webserver_url'] . '/phpgwapi/templates/'.$this->template.'/images';
 938  
 939          if(isset($GLOBALS['egw_info']['flags']['app_header']))
 940          {
 941              $var['current_app_title'] = $GLOBALS['egw_info']['flags']['app_header'];
 942          }
 943          else
 944          {
 945              $var['current_app_title']=$apps[$GLOBALS['egw_info']['flags']['currentapp']]['title'];
 946          }
 947          $var['currentapp'] = $GLOBALS['egw_info']['flags']['currentapp'];
 948  
 949          // current users for admins
 950          $var['current_users'] = $this->_current_users();
 951  
 952          // quick add selectbox
 953          $var['quick_add'] = $this->_get_quick_add();
 954  
 955          $var['user_info'] = $this->_user_time_info();
 956  
 957          if($GLOBALS['egw_info']['user']['account_lastpwd_change'] == 0)
 958          {
 959              $api_messages = lang('You are required to change your password during your first login').'<br />'.
 960                  lang('Click this image on the navbar: %1','<img src="'.common::image('preferences','navbar.gif').'">');
 961          }
 962          elseif($GLOBALS['egw_info']['server']['change_pwd_every_x_days'] && $GLOBALS['egw_info']['user']['account_lastpwd_change'] < time() - (86400*$GLOBALS['egw_info']['server']['change_pwd_every_x_days']))
 963          {
 964              $api_messages = lang('it has been more then %1 days since you changed your password',$GLOBALS['egw_info']['server']['change_pwd_every_x_days']);
 965          }
 966  
 967          if (substr($GLOBALS['egw_info']['server']['login_logo_file'],0,4) == 'http' ||
 968              $GLOBALS['egw_info']['server']['login_logo_file'][0] == '/')
 969          {
 970              $var['logo_file'] = $GLOBALS['egw_info']['server']['login_logo_file'];
 971          }
 972          else
 973          {
 974              $var['logo_file'] = common::image('phpgwapi',$GLOBALS['egw_info']['server']['login_logo_file']?$GLOBALS['egw_info']['server']['login_logo_file']:'logo', '', null);    // null=explicit allow svg
 975          }
 976          $var['logo_url'] = $GLOBALS['egw_info']['server']['login_logo_url']?$GLOBALS['egw_info']['server']['login_logo_url']:'http://www.eGroupWare.org';
 977  
 978          if (substr($var['logo_url'],0,4) != 'http')
 979          {
 980              $var['logo_url'] = 'http://'.$var['logo_url'];
 981          }
 982          $var['logo_title'] = $GLOBALS['egw_info']['server']['login_logo_title']?$GLOBALS['egw_info']['server']['login_logo_title']:'www.eGroupWare.org';
 983  
 984          return $var;
 985      }
 986  
 987      /**
 988       * Returns html with user and time
 989       *
 990       * @return void
 991       */
 992  	protected static function _user_time_info()
 993      {
 994          $now = new egw_time();
 995          $user_info = '<b>'.common::display_fullname() .'</b>'. ' - ' . lang($now->format('l')) . ' ' . $now->format(true);
 996  
 997          $user_tzs = egw_time::getUserTimezones();
 998          if (count($user_tzs) > 1)
 999          {
1000              $tz = $GLOBALS['egw_info']['user']['preferences']['common']['tz'];
1001              $user_info .= html::form(html::select('tz',$tz,$user_tzs,true),array(),
1002                  '/index.php','','tz_selection',' style="display: inline;"','GET');
1003          }
1004          return $user_info;
1005      }
1006  
1007      /**
1008       * Prepare the current users
1009       *
1010       * @return string
1011       */
1012  	protected static function _current_users()
1013      {
1014         if( $GLOBALS['egw_info']['user']['apps']['admin'] && $GLOBALS['egw_info']['user']['preferences']['common']['show_currentusers'])
1015         {
1016            $current_users = '<a href="' . egw::link('/index.php','menuaction=admin.admin_accesslog.sessions') . '">' .
1017                lang('Current users') . ': <span id="currentusers">' . $GLOBALS['egw']->session->session_count() . '</span></a>';
1018            return $current_users;
1019         }
1020      }
1021  
1022      /**
1023       * Prepare the quick add selectbox
1024       *
1025       * @return string
1026       */
1027  	protected static function _get_quick_add()
1028      {
1029          return '<span id="quick_add"></span>';
1030      }
1031  
1032      /**
1033       * Prepare notification signal (blinking bell)
1034       *
1035       * @return string
1036       */
1037  	protected static function _get_notification_bell()
1038      {
1039          return html::image('notifications', 'notificationbell', lang('notifications'),
1040              'id="notificationbell" style="display: none"');
1041      }
1042  
1043      /**
1044       * URL to check for security or maintenance updates
1045       */
1046      const CURRENT_VERSION_URL = 'http://www.egroupware.org/currentversion';
1047      /**
1048       * How long to cache (in secs) / often to check for updates
1049       */
1050      const VERSIONS_CACHE_TIMEOUT = 7200;
1051      /**
1052       * After how many days of not applied security updates, start warning non-admins too
1053       */
1054      const WARN_USERS_DAYS = 3;
1055  
1056      /**
1057       * Check update status
1058       *
1059       * @return string
1060       * @todo Check from client-side, if server-side check fails
1061       */
1062  	protected static function _get_update_notification()
1063      {
1064          $versions = egw_cache::getTree(__CLASS__, 'versions', function()
1065          {
1066              $versions = array();
1067              $security = null;
1068              if (($remote = file_get_contents(egw_framework::CURRENT_VERSION_URL, false, egw_framework::proxy_context())))
1069              {
1070                  list($current, $security) = explode("\n", $remote);
1071                  if (empty($security)) $security = $current;
1072                  $versions = array(
1073                      'current'  => $current,        // last maintenance update
1074                      'security' => $security,    // last security update
1075                  );
1076              }
1077              return $versions;
1078          }, array(), self::VERSIONS_CACHE_TIMEOUT);
1079  
1080          $api = self::api_version();
1081  
1082          if ($versions)
1083          {
1084              if (version_compare($api, $versions['security'], '<'))
1085              {
1086                  if (!$GLOBALS['egw_info']['user']['apps']['admin'] && !self::update_older($versions['security'], self::WARN_USERS_DAYS))
1087                  {
1088                      return null;
1089                  }
1090                  return html::a_href(html::image('phpgwapi', 'security-update', lang('EGroupware security update %1 needs to be installed!', $versions['security'])),
1091                      'http://www.egroupware.org/changelog', null, ' target="_blank"');
1092              }
1093              if ($GLOBALS['egw_info']['user']['apps']['admin'] && version_compare($api, $versions['current'], '<'))
1094              {
1095                  return html::a_href(html::image('phpgwapi', 'update', lang('EGroupware maintenance update %1 available', $versions['current'])),
1096                      'http://www.egroupware.org/changelog', null, ' target="_blank"');
1097              }
1098          }
1099          elseif ($GLOBALS['egw_info']['user']['apps']['admin'])
1100          {
1101              $error = lang('Automatic update check failed, you need to check manually!');
1102              if (!ini_get('allow_url_fopen'))
1103              {
1104                  $error .= "\n".lang('%1 setting "%2" = %3 disallows access via http!',
1105                      'php.ini', 'allow_url_fopen', array2string(ini_get('allow_url_fopen')));
1106              }
1107              return html::a_href(html::image('phpgwapi', 'update', $error),
1108                  'http://www.egroupware.org/changelog', null, ' target="_blank" data-api-version="'.$api.'"');
1109          }
1110          return null;
1111      }
1112  
1113      /**
1114       * Get context to use with file_get_context or fopen to use our proxy settings from setup
1115       *
1116       * @param string $username =null username for regular basic auth
1117       * @param string $password =null password --------- " ----------
1118       * @return resource|null context to use with file_get_context/fopen or null if no proxy configured
1119       */
1120  	public static function proxy_context($username=null, $password=null)
1121      {
1122          $opts = array(
1123              'method' => 'GET',
1124          );
1125          if (!empty($GLOBALS['egw_info']['server']['httpproxy_server']))
1126          {
1127              $opts += array (
1128                  'proxy'  => 'tcp://'.$GLOBALS['egw_info']['server']['httpproxy_server'].':'.
1129                      ($GLOBALS['egw_info']['server']['httpproxy_port'] ? $GLOBALS['egw_info']['server']['httpproxy_port'] : 8080),
1130                  'request_fulluri' => true,
1131              );
1132              // proxy authentication
1133              if (!empty($GLOBALS['egw_info']['server']['httpproxy_server_username']))
1134              {
1135                  $opts['header'][] = 'Proxy-Authorization: Basic '.base64_encode($GLOBALS['egw_info']['server']['httpproxy_server_username'].':'.
1136                      $GLOBALS['egw_info']['server']['httpproxy_server_password']);
1137              }
1138          }
1139          // optional authentication
1140          if (isset($username))
1141          {
1142              $opts['header'][] = 'Authorization: Basic '.base64_encode($username.':'.$password);
1143          }
1144          return stream_context_create(array(
1145              'http' => $opts,
1146              'https' => $opts,
1147          ));
1148      }
1149  
1150      /**
1151       * Check if version is older then $days days
1152       *
1153       * @param string $version eg. "14.1.20140715" last part is checked (only if > 20140000!)
1154       * @param int $days
1155       * @return boolean
1156       */
1157  	protected static function update_older($version, $days)
1158      {
1159          list(,,$date) = explode('.', $version);
1160          if ($date < 20140000) return false;
1161          $version_timestamp = mktime(0, 0, 0, (int)substr($date, 4, 2), (int)substr($date, -2), (int)substr($date, 0, 4));
1162  
1163          return (time() - $version_timestamp) / 86400 > $days;
1164      }
1165  
1166      /**
1167       * Get API version from changelog or database, whichever is bigger
1168       *
1169       * @param string &$changelog on return path to changelog
1170       * @return string
1171       */
1172  	public static function api_version(&$changelog=null)
1173      {
1174          $version = preg_replace('/[^0-9.]/', '', $GLOBALS['egw_info']['server']['versions']['phpgwapi']);
1175          // parse version from changelog
1176          $changelog = EGW_SERVER_ROOT.'/doc/rpm-build/debian.changes';
1177          $matches = null;
1178          if (($f = fopen($changelog, 'r')) && preg_match('/egroupware-epl \(([0-9.]+)/', fread($f, 80), $matches) &&
1179              version_compare($version, $matches[1], '<'))
1180          {
1181              $version = $matches[1];
1182              fclose($f);
1183          }
1184          return $version;
1185      }
1186  
1187      /**
1188       * Get the link to an application's index page
1189       *
1190       * @param string $app
1191       * @return string
1192       */
1193  	public static function index($app)
1194      {
1195          $data =& $GLOBALS['egw_info']['user']['apps'][$app];
1196          if (!isset($data))
1197          {
1198              throw new egw_exception_wrong_parameter("'$app' not a valid app for this user!");
1199          }
1200          $index = '/'.$app.'/index.php';
1201          if (isset($data['index']))
1202          {
1203              if ($data['index'][0] == '/')
1204              {
1205                  $index = $data['index'];
1206              }
1207              else
1208              {
1209                  $index = '/index.php?menuaction='.$data['index'];
1210              }
1211          }
1212          return egw::link($index,$GLOBALS['egw_info']['flags']['params'][$app]);
1213      }
1214  
1215      /**
1216       * Used internally to store unserialized value of $GLOBALS['egw_info']['user']['preferences']['common']['user_apporder']
1217       */
1218      private static $user_apporder = array();
1219  
1220      /**
1221       * Internal usort callback function used to sort an array according to the
1222       * user sort order
1223       */
1224  	private static function _sort_apparray($a, $b)
1225      {
1226          //Unserialize the user_apporder array
1227          $arr = self::$user_apporder;
1228  
1229          $ind_a = isset($arr[$a['name']]) ? $arr[$a['name']] : null;
1230          $ind_b = isset($arr[$b['name']]) ? $arr[$b['name']] : null;
1231  
1232          if ($ind_a == $ind_b)
1233              return 0;
1234  
1235          if ($ind_a == null)
1236              return -1;
1237  
1238          if ($ind_b == null)
1239              return 1;
1240  
1241          return $ind_a > $ind_b ? 1 : -1;
1242      }
1243  
1244      /**
1245       * Prepare an array with apps used to render the navbar
1246       *
1247       * This is similar to the former common::navbar() method - though it returns the vars and does not place them in global scope.
1248       *
1249       * @param boolean $svg =false should svg images be returned or not:
1250       *    true: always return svg, false: never return svg (current default), null: browser dependent, see svg_usable()
1251       * @return array
1252       */
1253  	protected static function _get_navbar_apps($svg=false)
1254      {
1255          list($first) = each($GLOBALS['egw_info']['user']['apps']);
1256          if(is_array($GLOBALS['egw_info']['user']['apps']['admin']) && $first != 'admin')
1257          {
1258              $newarray['admin'] = $GLOBALS['egw_info']['user']['apps']['admin'];
1259              foreach($GLOBALS['egw_info']['user']['apps'] as $index => $value)
1260              {
1261                  if($index != 'admin')
1262                  {
1263                      $newarray[$index] = $value;
1264                  }
1265              }
1266              $GLOBALS['egw_info']['user']['apps'] = $newarray;
1267              reset($GLOBALS['egw_info']['user']['apps']);
1268          }
1269          unset($index);
1270          unset($value);
1271          unset($newarray);
1272  
1273          $apps = array();
1274          foreach($GLOBALS['egw_info']['user']['apps'] as $app => $data)
1275          {
1276              if (is_long($app))
1277              {
1278                  continue;
1279              }
1280  
1281              if ($app == 'preferences' || $GLOBALS['egw_info']['apps'][$app]['status'] != 2 && $GLOBALS['egw_info']['apps'][$app]['status'] != 3)
1282              {
1283                  $apps[$app]['title'] = $GLOBALS['egw_info']['apps'][$app]['title'];
1284                  $apps[$app]['url']   = self::index($app);
1285                  $apps[$app]['name']  = $app;
1286  
1287                  // create popup target
1288                  if ($data['status'] == 4)
1289                  {
1290                      $apps[$app]['target'] = ' target="'.$app.'" onClick="'."if (this != '') { window.open(this+'".
1291                          (strpos($apps[$app]['url'],'?') !== false ? '&' : '?').
1292                          "referer='+encodeURIComponent(location),this.target,'width=800,height=600,scrollbars=yes,resizable=yes'); return false; } else { return true; }".'"';
1293                  }
1294                  elseif(isset($GLOBALS['egw_info']['flags']['navbar_target']) && $GLOBALS['egw_info']['flags']['navbar_target'])
1295                  {
1296                      $apps[$app]['target'] = 'target="' . $GLOBALS['egw_info']['flags']['navbar_target'] . '"';
1297                  }
1298                  else
1299                  {
1300                      $apps[$app]['target'] = '';
1301                  }
1302  
1303                  $icon = isset($data['icon']) ?  $data['icon'] : 'navbar';
1304                  $icon_app = isset($data['icon_app']) ? $data['icon_app'] : $app;
1305                  if ($app != $GLOBALS['egw_info']['flags']['currentapp'])
1306                  {
1307                      $apps[$app]['icon']  = common::image($icon_app,Array($icon,'nonav'),'',$svg);
1308                      $apps[$app]['icon_hover']  = common::image_on($icon_app,Array($icon,'nonav'),'-over',$svg);
1309                  }
1310                  else
1311                  {
1312                      $apps[$app]['icon']  = common::image_on($icon_app,Array($icon,'nonav'),'-over',$svg);
1313                      $apps[$app]['icon_hover']  = common::image($icon_app,Array($icon,'nonav'),'',$svg);
1314                  }
1315              }
1316          }
1317  
1318          //Sort the applications accordingly to their user sort setting
1319          if ($GLOBALS['egw_info']['user']['preferences']['common']['user_apporder'])
1320          {
1321              //Sort the application array using the user_apporder array as sort index
1322              self::$user_apporder =
1323                  unserialize($GLOBALS['egw_info']['user']['preferences']['common']['user_apporder']);
1324              uasort($apps, 'egw_framework::_sort_apparray');
1325          }
1326  
1327          if ($GLOBALS['egw_info']['flags']['currentapp'] == 'preferences' || $GLOBALS['egw_info']['flags']['currentapp'] == 'about')
1328          {
1329              $app = $app_title = 'EGroupware';
1330          }
1331          else
1332          {
1333              $app = $GLOBALS['egw_info']['flags']['currentapp'];
1334              $app_title = $GLOBALS['egw_info']['apps'][$app]['title'];
1335          }
1336  
1337          if ($GLOBALS['egw_info']['user']['apps']['preferences'])    // preferences last
1338          {
1339              $prefs = $apps['preferences'];
1340              unset($apps['preferences']);
1341              $apps['preferences'] = $prefs;
1342          }
1343  
1344          // We handle this here becuase its special
1345          $apps['about']['title'] = 'EGroupware';
1346  
1347          $apps['about']['url']   = egw::link('/about.php');
1348          $apps['about']['icon']  = common::image('phpgwapi',Array('about','nonav'));
1349          $apps['about']['icon_hover']  = common::image_on('phpgwapi',Array('about','nonav'),'-over');
1350          $apps['about']['name'] = 'about';
1351  
1352          $apps['logout']['title'] = lang('Logout');
1353          $apps['logout']['name'] = 'logout';
1354          $apps['logout']['url']   = egw::link('/logout.php');
1355          $apps['logout']['icon']  = common::image('phpgwapi',Array('logout','nonav'));
1356          $apps['logout']['icon_hover']  = common::image_on('phpgwapi',Array('logout','nonav'),'-over');
1357  
1358          return $apps;
1359      }
1360  
1361      /**
1362       * Used by template headers for including CSS in the header
1363       *
1364       * 'app_css'   - css styles from a) the menuaction's css-method and b) the $GLOBALS['egw_info']['flags']['css']
1365       * 'file_css'  - link tag of the app.css file of the current app
1366       * 'theme_css' - url of the theme css file
1367       * 'print_css' - url of the print css file
1368       *
1369       * @author Dave Hall (*based* on verdilak? css inclusion code)
1370       * @return array with keys 'app_css' from the css method of the menuaction-class and 'file_css' (app.css file of the application)
1371       */
1372  	public function _get_css()
1373      {
1374          $app_css = '';
1375          if (isset($GLOBALS['egw_info']['flags']['css']))
1376          {
1377              $app_css = $GLOBALS['egw_info']['flags']['css'];
1378          }
1379  
1380          if (self::$load_default_css)
1381          {
1382              // Load these first
1383              // Cascade should go:
1384              //  Libs < etemplate2 < framework/theme < app < print
1385              // Enhanced selectboxes (et1)
1386              self::include CSS('/phpgwapi/js/jquery/chosen/chosen.css');
1387  
1388              // eTemplate2 uses jQueryUI, so load it first so et2 can override if needed
1389              self::include CSS("/phpgwapi/js/jquery/jquery-ui/redmond/jquery-ui.css");
1390  
1391              // eTemplate2 - load in top so sidebox has styles too
1392              self::include CSS('/etemplate/templates/default/etemplate2.css');
1393  
1394              // Category styles
1395              categories::css(categories::GLOBAL_APPNAME);
1396  
1397              // For mobile user-agent we prefer mobile theme over selected one with a final fallback to theme named as template
1398              $themes_to_check = array();
1399              if (html::$ua_mobile) $themes_to_check[] = $this->template_dir.'/css/mobile.css';
1400              $themes_to_check[] = $this->template_dir.'/css/'.$GLOBALS['egw_info']['user']['preferences']['common']['theme'].'.css';
1401              $themes_to_check[] = $this->template_dir.'/css/'.$this->template.'.css';
1402              foreach($themes_to_check as $theme_css)
1403              {
1404                  if (file_exists(EGW_SERVER_ROOT.$theme_css)) break;
1405              }
1406              self::includeCSS($theme_css);
1407  
1408              // search for app specific css file, so it can customize the theme
1409              self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app-'.$GLOBALS['egw_info']['user']['preferences']['common']['theme']) ||
1410                  self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app');
1411  
1412              // sending print css last, so it can overwrite anything
1413              $print_css = $this->template_dir.'/print.css';
1414              if(!file_exists(EGW_SERVER_ROOT.$print_css))
1415              {
1416                  $print_css = '/phpgwapi/templates/idots/print.css';
1417              }
1418              self::includeCSS($print_css);
1419          }
1420          // add all css files from self::includeCSS
1421          $max_modified = 0;
1422          $debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True';
1423          $base_path = $GLOBALS['egw_info']['server']['webserver_url'];
1424          if ($base_path[0] != '/') $base_path = parse_url($base_path, PHP_URL_PATH);
1425          $css_files = '';
1426          foreach(self::$css_include_files as $path)
1427          {
1428              foreach(self::resolve_css_includes($path) as $path)
1429              {
1430                  list($file,$query) = explode('?',$path,2);
1431                  if (($mod = filemtime(EGW_SERVER_ROOT.$file)) > $max_modified) $max_modified = $mod;
1432  
1433                  // do NOT include app.css or categories.php, as it changes from app to app
1434                  if ($debug_minify || substr($path, -8) == '/app.css' || substr($file,-14) == 'categories.php')
1435                  {
1436                      $css_files .= '<link href="'.$GLOBALS['egw_info']['server']['webserver_url'].$path.($query ? '&' : '?').$mod.'" type="text/css" rel="StyleSheet" />'."\n";
1437                  }
1438                  else
1439                  {
1440                      $css_file .= ($css_file ? ',' : '').substr($path, 1);
1441                  }
1442              }
1443          }
1444          if (!$debug_minify)
1445          {
1446              $css = $GLOBALS['egw_info']['server']['webserver_url'].'/phpgwapi/inc/min/?';
1447              if ($base_path && $base_path != '/') $css .= 'b='.substr($base_path, 1).'&';
1448              $css .= 'f='.$css_file .
1449                  ($GLOBALS['egw_info']['server']['debug_minify'] === 'debug' ? '&debug' : '').
1450                  '&'.$max_modified;
1451              $css_files = '<link href="'.$css.'" type="text/css" rel="StyleSheet" />'."\n".$css_files;
1452          }
1453          return array(
1454              'app_css'   => $app_css,
1455              'css_file'  => $css_files,
1456          );
1457      }
1458  
1459      /**
1460       * Parse beginning of given CSS file for /*@import url("...") statements
1461       *
1462       * @param string $path EGroupware relative path eg. /phpgwapi/templates/default/some.css
1463       * @return array parsed pathes (EGroupware relative) including $path itself
1464       */
1465  	protected static function resolve_css_includes($path, &$pathes=array())
1466      {
1467          $matches = null;
1468  
1469          list($file,$query) = explode('?',$path,2);
1470          if (($to_check = file_get_contents (EGW_SERVER_ROOT.$file, false, null, -1, 1024)) &&
1471              stripos($to_check, '/*@import') !== false && preg_match_all('|/\*@import url\("([^"]+)"|i', $to_check, $matches))
1472          {
1473              foreach($matches[1] as $import_path)
1474              {
1475                  if ($import_path[0] != '/')
1476                  {
1477                      $dir = dirname($path);
1478                      while(substr($import_path,0,3) == '../')
1479                      {
1480                          $dir = dirname($dir);
1481                          $import_path = substr($import_path, 3);
1482                      }
1483                      $import_path = ($dir != '/' ? $dir : '').'/'.$import_path;
1484                  }
1485                  self::resolve_css_includes($import_path, $pathes);
1486              }
1487          }
1488          $pathes[] = $path;
1489  
1490          return $pathes;
1491      }
1492  
1493      /**
1494       * Used by the template headers for including javascript in the header
1495       *
1496       * The method is included here to make it easier to change the js support
1497       * in eGW.  One change then all templates will support it (as long as they
1498       * include a call to this method).
1499       *
1500       * @param array $extra =array() extra data to pass to egw.js as data-parameter
1501       * @return string the javascript to be included
1502       */
1503  	public static function _get_js(array $extra=array())
1504      {
1505          $java_script = '';
1506  
1507          /* this flag is for all javascript code that has to be put before other jscode.
1508          Think of conf vars etc...  (pim@lingewoud.nl) */
1509          if (isset($GLOBALS['egw_info']['flags']['java_script_thirst']))
1510          {
1511              $java_script .= $GLOBALS['egw_info']['flags']['java_script_thirst'] . "\n";
1512          }
1513          // add configuration, link-registry, images, user-data and -perferences for non-popup windows
1514          // specifying etag in url to force reload, as we send expires header
1515          if ($GLOBALS['egw_info']['flags']['js_link_registry'])
1516          {
1517              self::validate_file('/phpgwapi/config.php', array(
1518                  'etag' => md5(json_encode(config::clientConfigs()).egw_link::json_registry()),
1519              ));
1520              self::validate_file('/phpgwapi/images.php', array(
1521                  'template' => $GLOBALS['egw_info']['server']['template_set'],
1522                  'etag' => md5(json_encode(common::image_map($GLOBALS['egw_info']['server']['template_set']))),
1523                  'svg' => 0,    // always load non-svg image map
1524              ));
1525              self::validate_file('/phpgwapi/user.php', array(
1526                  'user' => $GLOBALS['egw_info']['user']['account_lid'],
1527                  'lang' => $GLOBALS['egw_info']['user']['preferences']['common']['lang'],
1528                  // add etag on url, so we can set an expires header
1529                  'etag' => md5(json_encode($GLOBALS['egw_info']['user']['preferences']['common']).
1530                      $GLOBALS['egw']->accounts->json($GLOBALS['egw_info']['user']['account_id'])),
1531              ));
1532          }
1533  
1534          $extra['url'] = $GLOBALS['egw_info']['server']['webserver_url'];
1535          $extra['include'] = array_map(function($str){return substr($str,1);}, self::get_script_links(true), array(1));
1536          $extra['app'] = $GLOBALS['egw_info']['flags']['currentapp'];
1537  
1538          // Load LABjs ONCE here
1539          $java_script .= '<script type="text/javascript" src="'.$GLOBALS['egw_info']['server']['webserver_url'].
1540                  '/phpgwapi/js/labjs/LAB.src.js?'.filemtime(EGW_SERVER_ROOT.'/phpgwapi/js/labjs/LAB.src.js')."\"></script>\n".
1541              '<script type="text/javascript" src="'.$GLOBALS['egw_info']['server']['webserver_url'].
1542                  '/phpgwapi/js/jsapi/egw.js?'.filemtime(EGW_SERVER_ROOT.'/phpgwapi/js/jsapi/egw.js').'" id="egw_script_id"';
1543  
1544          // add values of extra parameter and class var as data attributes to script tag of egw.js
1545          foreach($extra+self::$extra as $name => $value)
1546          {
1547              if (is_array($value)) $value = json_encode($value);
1548              // we need to double encode (html::htmlspecialchars( , TRUE)), as otherwise we get invalid json, eg. for quotes
1549              $java_script .= ' data-'.$name."=\"". html::htmlspecialchars($value, true)."\"";
1550          }
1551          $java_script .= "></script>\n";
1552  
1553          if(@isset($_GET['menuaction']))
1554          {
1555              list(, $class) = explode('.',$_GET['menuaction']);
1556              if(is_array($GLOBALS[$class]->public_functions) &&
1557                  $GLOBALS[$class]->public_functions['java_script'])
1558              {
1559                  $java_script .= $GLOBALS[$class]->java_script();
1560              }
1561          }
1562          if (isset($GLOBALS['egw_info']['flags']['java_script']))
1563          {
1564              // Strip out any script tags, this needs to be executed as anonymous function
1565              $GLOBALS['egw_info']['flags']['java_script'] = preg_replace(array('/(<script[^>]*>)([^<]*)/is','/<\/script>/'),array('$2',''),$GLOBALS['egw_info']['flags']['java_script']);
1566              if(trim($GLOBALS['egw_info']['flags']['java_script']) != '')
1567              {
1568                  $java_script .= '<script type="text/javascript">window.egw_LAB.wait(function() {'.$GLOBALS['egw_info']['flags']['java_script'] . "});</script>\n";
1569              }
1570          }
1571  
1572          return $java_script;
1573      }
1574  
1575      /**
1576       * List available themes
1577       *
1578       * Themes are css file in the template directory
1579       *
1580       * @param string $themes_dir ='css'
1581       */
1582  	function list_themes()
1583      {
1584          $list = array();
1585          if (($dh = @opendir(EGW_SERVER_ROOT.$this->template_dir . SEP . 'css')))
1586          {
1587              while (($file = readdir($dh)))
1588              {
1589                  if (preg_match('/'."\.css$".'/i', $file))
1590                  {
1591                      list($name) = explode('.',$file);
1592                      $list[$name] = $name;
1593                  }
1594              }
1595              closedir($dh);
1596          }
1597          return $list;
1598      }
1599  
1600      /**
1601       * List available templates
1602       *
1603       * @param boolean $full_data =false true: value is array with values for keys 'name', 'title', ...
1604       * @returns array alphabetically sorted list of templates
1605       */
1606  	static function list_templates($full_data=false)
1607      {
1608          $list = array('pixelegg'=>null,'jdots'=>null,'idots'=>null);
1609          // templates packaged in the api
1610          $d = dir(EGW_SERVER_ROOT . '/phpgwapi/templates');
1611          while (($entry=$d->read()))
1612          {
1613              if ($entry != '..' && file_exists(EGW_SERVER_ROOT . '/phpgwapi/templates/' . $entry .'/class.'.$entry.'_framework.inc.php'))
1614              {
1615                  if (file_exists ($f = EGW_SERVER_ROOT . '/phpgwapi/templates/' . $entry . '/setup/setup.inc.php'))
1616                  {
1617                      include($f);
1618                      $list[$entry] = $full_data ? $GLOBALS['egw_info']['template'][$entry] :
1619                          $GLOBALS['egw_info']['template'][$entry]['title'];
1620                  }
1621                  else
1622                  {
1623                      $list[$entry] = $full_data ? array(
1624                          'name'  => $entry,
1625                          'title' => $entry,
1626                      ) : $entry;
1627                  }
1628              }
1629          }
1630          $d->close();
1631          // templates packaged like apps in own directories (containing as setup/setup.inc.php file!)
1632          $dr = dir(EGW_SERVER_ROOT);
1633          while (($entry=$dr->read()))
1634          {
1635              if ($entry != '..' && !isset($GLOBALS['egw_info']['apps'][$entry]) && is_dir(EGW_SERVER_ROOT.'/'.$entry) &&
1636                  file_exists($f = EGW_SERVER_ROOT . '/' . $entry .'/setup/setup.inc.php'))
1637              {
1638                  include($f);
1639                  if (isset($GLOBALS['egw_info']['template'][$entry]))
1640                  {
1641                      $list[$entry] = $full_data ? $GLOBALS['egw_info']['template'][$entry] :
1642                          $GLOBALS['egw_info']['template'][$entry]['title'];
1643                  }
1644              }
1645          }
1646          $dr->close();
1647  
1648          return array_filter($list);
1649      }
1650  
1651      /**
1652      * Compile entries for topmenu:
1653      * - regular items: links
1654      * - info items
1655      *
1656      * @param array $vars
1657      * @param array $apps
1658      */
1659  	function topmenu(array $vars,array $apps)
1660      {
1661          if($GLOBALS['egw_info']['user']['apps']['home'] && isset($apps['home']))
1662          {
1663              $this->_add_topmenu_item($apps['home']);
1664          }
1665  
1666          if($GLOBALS['egw_info']['user']['apps']['preferences'])
1667          {
1668              $this->add_preferences_topmenu('prefs');
1669              $this->add_preferences_topmenu('acl');
1670              $this->add_preferences_topmenu('cats');
1671          }
1672  
1673          // allways display password in topmenu, if user has rights to change it
1674          if ($GLOBALS['egw_info']['user']['apps']['preferences'] &&
1675              !$GLOBALS['egw']->acl->check('nopasswordchange', 1, 'preferences'))
1676          {
1677              $this->_add_topmenu_item(array(
1678                  'id'    => 'password',
1679                  'name'  => 'preferences',
1680                  'title' => lang('Password'),
1681                  'url'   => "javascript:egw.open_link('".
1682                      egw::link('/index.php?menuaction=preferences.preferences_password.change')."','_blank','400x270')",
1683              ));
1684          }
1685          /* disable help until content is reworked
1686          if($GLOBALS['egw_info']['user']['apps']['manual'] && isset($apps['manual']))
1687          {
1688              $this->_add_topmenu_item(array_merge($apps['manual'],array('title' => lang('Help'))));
1689          }*/
1690  
1691          $GLOBALS['egw']->hooks->process('topmenu_info',array(),true);
1692          // Add extra items added by hooks
1693          foreach(self::$top_menu_extra as $extra_item) {
1694              $this->_add_topmenu_item($extra_item);
1695          }
1696  
1697          $this->_add_topmenu_item($apps['logout']);
1698  
1699          if (($update = self::_get_update_notification()))
1700          {
1701              $this->_add_topmenu_info_item($update, 'update');
1702          }
1703          if($GLOBALS['egw_info']['user']['apps']['notifications'])
1704          {
1705              $this->_add_topmenu_info_item(self::_get_notification_bell(), 'notifications');
1706          }
1707          $this->_add_topmenu_info_item($vars['user_info'], 'user_info');
1708          $this->_add_topmenu_info_item($vars['current_users'], 'current_users');
1709          $this->_add_topmenu_info_item($vars['quick_add'], 'quick_add');
1710      }
1711  
1712      /**
1713       * Add preferences link to topmenu using settings-hook to know if an app supports preferences
1714       */
1715  	protected function add_preferences_topmenu($type='prefs')
1716      {
1717          static $memberships=null;
1718          if (!isset($memberships)) $memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'], true);
1719          static $types = array(
1720              'prefs' => array(
1721                  'title' => 'Preferences',
1722                  'hook'  => 'settings',
1723              ),
1724              'acl' => array(
1725                  'title' => 'Access',
1726                  'hook'  => 'acl_rights',
1727              ),
1728              'cats' => array(
1729                  'title' => 'Categories',
1730                  'hook' => 'categories',
1731                  'run_hook' => true,    // acturally run hook, not just look it's implemented
1732              ),
1733          );
1734          if (!$GLOBALS['egw_info']['user']['apps']['preferences'] || $GLOBALS['egw_info']['server']['deny_'.$type] &&
1735              array_intersect($memberships, (array)$GLOBALS['egw_info']['server']['deny_'.$type]) &&
1736              !$GLOBALS['egw_info']['user']['apps']['admin'])
1737          {
1738              return;    // user has no access to preferences app
1739          }
1740          if (isset($types[$type]['run_hook']))
1741          {
1742              $apps = $GLOBALS['egw']->hooks->process($types[$type]['hook']);
1743              // as all apps answer, we need to remove none-true responses
1744              foreach($apps as $app => $val)
1745              {
1746                  if (!$val) unset($apps[$app]);
1747              }
1748          }
1749          else
1750          {
1751              $apps = $GLOBALS['egw']->hooks->hook_implemented($types[$type]['hook']);
1752          }
1753          $this->_add_topmenu_item(array(
1754              'id' => $type,
1755              'name' => 'preferences',
1756              'title' => lang($types[$type]['title']),
1757              'url' => "javascript:egw.show_preferences(\"$type\",".json_encode($apps).')',
1758          ));
1759      }
1760  
1761      /**
1762      * Add menu items to the topmenu template class to be displayed
1763      *
1764      * @param array $app application data
1765      * @param mixed $alt_label string with alternative menu item label default value = null
1766      * @param string $urlextra string with alternate additional code inside <a>-tag
1767      * @access protected
1768      * @return void
1769      */
1770      abstract function _add_topmenu_item(array $app_data,$alt_label=null);
1771  
1772      /**
1773      * Add info items to the topmenu template class to be displayed
1774      *
1775      * @param string $content html of item
1776      * @param string $id =null
1777      * @access protected
1778      * @return void
1779      */
1780      abstract function _add_topmenu_info_item($content, $id=null);
1781  
1782      static $top_menu_extra = array();
1783  
1784      /**
1785      * Called by hooks to add an entry in the topmenu location.
1786      * Extra entries will be added just before Logout.
1787      *
1788      * @param string $id unique element id
1789      * @param string $url Address for the entry to link to
1790      * @param string $title Text displayed for the entry
1791      * @param string $target Optional, so the entry can open in a new page or popup
1792      * @access public
1793      * @return void
1794      */
1795  	public static function add_topmenu_item($id,$url,$title,$target = '')
1796      {
1797          $entry['name'] = $id;
1798          $entry['url'] = $url;
1799          $entry['title'] = $title;
1800          $entry['target'] = $target;
1801  
1802          self::$top_menu_extra[$id] = $entry;
1803      }
1804  
1805      /**
1806      * called by hooks to add an icon in the topmenu info location
1807      *
1808      * @param string $id unique element id
1809      * @param string $icon_src src of the icon image. Make sure this nog height then 18pixels
1810      * @param string $iconlink where the icon links to
1811      * @param booleon $blink set true to make the icon blink
1812      * @param mixed $tooltip string containing the tooltip html, or null of no tooltip
1813      * @access public
1814      * @return void
1815      */
1816      abstract function topmenu_info_icon($id,$icon_src,$iconlink,$blink=false,$tooltip=null);
1817  
1818      /**
1819       * Call and return content of 'after_navbar' hook
1820       *
1821       * @return string
1822       */
1823  	protected function _get_after_navbar()
1824      {
1825          ob_start();
1826          $GLOBALS['egw']->hooks->process('after_navbar',null,true);
1827          $content = ob_get_contents();
1828          ob_end_clean();
1829  
1830          return $content;
1831      }
1832  
1833      /**
1834       * Return javascript (eg. for onClick) to open manual with given url
1835       *
1836       * @param string $url
1837       */
1838      abstract function open_manual_js($url);
1839  
1840      /**
1841       * Methods to add javascript to framework
1842       */
1843  
1844      /**
1845       * Body tags for onLoad, onUnload and onResize
1846       *
1847       * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
1848       * @var array
1849       */
1850      protected static $body_tags = array();
1851  
1852      /**
1853       * Sets an onLoad action for a page
1854       *
1855       * @param string $code ='' javascript to be used
1856       * @param boolean $replace =false false: append to existing, true: replace existing tag
1857       * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
1858       * @return string content of onXXX tag after adding code
1859       */
1860  	static function set_onload($code='',$replace=false)
1861      {
1862          if ($replace || empty(self::$body_tags['onLoad']))
1863          {
1864              self::$body_tags['onLoad'] = $code;
1865          }
1866          else
1867          {
1868              self::$body_tags['onLoad'] .= $code;
1869          }
1870          return self::$body_tags['onLoad'];
1871      }
1872  
1873      /**
1874       * Sets an onUnload action for a page
1875       *
1876       * @param string $code ='' javascript to be used
1877       * @param boolean $replace =false false: append to existing, true: replace existing tag
1878       * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
1879       * @return string content of onXXX tag after adding code
1880       */
1881  	static function set_onunload($code='',$replace=false)
1882      {
1883          if ($replace || empty(self::$body_tags['onUnload']))
1884          {
1885              self::$body_tags['onUnload'] = $code;
1886          }
1887          else
1888          {
1889              self::$body_tags['onUnload'] .= $code;
1890          }
1891          return self::$body_tags['onUnload'];
1892      }
1893  
1894      /**
1895       * Sets an onBeforeUnload action for a page
1896       *
1897       * @param string $code ='' javascript to be used
1898       * @param boolean $replace =false false: append to existing, true: replace existing tag
1899       * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
1900       * @return string content of onXXX tag after adding code
1901       */
1902  	static function set_onbeforeunload($code='',$replace=false)
1903      {
1904          if ($replace || empty(self::$body_tags['onBeforeUnload']))
1905          {
1906              self::$body_tags['onBeforeUnload'] = $code;
1907          }
1908          else
1909          {
1910              self::$body_tags['onBeforeUnload'] .= $code;
1911          }
1912          return self::$body_tags['onBeforeUnload'];
1913      }
1914  
1915      /**
1916       * Sets an onResize action for a page
1917       *
1918       * @param string $code ='' javascript to be used
1919       * @param boolean $replace =false false: append to existing, true: replace existing tag
1920       * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
1921       * @return string content of onXXX tag after adding code
1922       */
1923  	static function set_onresize($code='',$replace=false)
1924      {
1925          if ($replace || empty(self::$body_tags['onResize']))
1926          {
1927              self::$body_tags['onResize'] = $code;
1928          }
1929          else
1930          {
1931              self::$body_tags['onResize'] .= $code;
1932          }
1933          return self::$body_tags['onResize'];
1934      }
1935  
1936      /**
1937       * Adds on(Un)Load= attributes to the body tag of a page
1938       *
1939       * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!)
1940       * @returns string the attributes to be used
1941       */
1942  	static public function _get_body_attribs()
1943      {
1944          $js = '';
1945          foreach(self::$body_tags as $what => $data)
1946          {
1947              if (!empty($data))
1948              {
1949                  if($what == 'onLoad')
1950                  {
1951                      $js .= 'onLoad="egw_LAB.wait(function() {'. htmlspecialchars($data).'})"';
1952                      continue;
1953                  }
1954                  $js .= ' '.$what.'="' . htmlspecialchars($data) . '"';
1955              }
1956          }
1957          return $js;
1958      }
1959  
1960      /**
1961       * The include manager manages including js files and their dependencies
1962       */
1963      protected static $js_include_mgr;
1964  
1965      /**
1966      * Checks to make sure a valid package and file name is provided
1967      *
1968      * Example call syntax:
1969      * a) egw_framework::validate_file('jscalendar','calendar')
1970      *    --> /phpgwapi/js/jscalendar/calendar.js
1971      * b) egw_framework::validate_file('/phpgwapi/inc/calendar-setup.js',array('lang'=>'de'))
1972      *    --> /phpgwapi/inc/calendar-setup.js?lang=de
1973      *
1974      * @param string $package package or complete path (relative to EGW_SERVER_ROOT) to be included
1975      * @param string|array $file =null file to be included - no ".js" on the end or array with get params
1976      * @param string $app ='phpgwapi' application directory to search - default = phpgwapi
1977      * @param boolean $append =true should the file be added
1978      *
1979      * @discuss The browser specific option loads the file which is in the correct
1980      *          browser folder. Supported folder are those supported by class.browser.inc.php
1981      *
1982      * @returns bool was the file found?
1983      */
1984  	static function validate_file($package, $file=null, $app='phpgwapi')
1985      {
1986          self::$js_include_mgr->include_js_file($package, $file, $app);
1987      }
1988  
1989      /**
1990       * Set or return all javascript files set via validate_file, optionally clear all files
1991       *
1992       * @param array $files =null array with pathes relative to EGW_SERVER_ROOT, eg. /phpgwapi/js/jquery/jquery.js
1993       * @param boolean $clear_files =false true clear files after returning them
1994       * @return array with pathes relative to EGW_SERVER_ROOT
1995       */
1996  	static function js_files(array $files=null, $clear_files=false)
1997      {
1998          if (isset($files) && is_array($files))
1999          {
2000              self::$js_include_mgr->include_files($files);
2001          }
2002          return self::$js_include_mgr->get_included_files($clear_files);
2003      }
2004  
2005      /**
2006       * Used for generating the list of external js files to be included in the head of a page
2007       *
2008       * NOTE: This method should only be called by the template class.
2009       * The validation is done when the file is added so we don't have to worry now
2010       *
2011       * @param boolean $return_pathes =false false: return html script tags, true: return array of file pathes relative to webserver_url
2012       * @param boolean $clear_files =false true clear files after returning them
2013       * @return string|array see $return_pathes parameter
2014       */
2015  	static public function get_script_links($return_pathes=false, $clear_files=false)
2016      {
2017          $to_include = self::bundle_js_includes(self::$js_include_mgr->get_included_files($clear_files));
2018  
2019          if ($return_pathes)
2020          {
2021              return $to_include;
2022          }
2023          $start = '<script type="text/javascript" src="'. $GLOBALS['egw_info']['server']['webserver_url'];
2024          $end = '">'."</script>\n";
2025          return "\n".$start.implode($end.$start, $to_include).$end;
2026      }
2027  
2028      /**
2029       * Devide js-includes in bundles of javascript files to include eg. api or etemplate2, if minifying is enabled
2030       *
2031       * @param array $js_includes files to include with egw relative url
2032       * @return array egw relative urls to include incl. bundels/minify urls, if enabled
2033       */
2034  	public static function bundle_js_includes(array $js_includes)
2035      {
2036          $file2bundle = array();
2037          if ($GLOBALS['egw_info']['server']['debug_minify'] !== 'True')
2038          {
2039              // get used bundles and cache them on tree-level for 2h
2040              //$bundles = self::get_bundles(); egw_cache::setTree(__CLASS__, 'bundles', $bundles, 7200);
2041              $bundles = egw_cache::getTree(__CLASS__, 'bundles', array(__CLASS__, 'get_bundles'), array(), 7200);
2042              $bundles_ts = $bundles['.ts'];
2043              unset($bundles['.ts']);
2044              foreach($bundles as $name => $files)
2045              {
2046                  $file2bundle += array_combine($files, array_fill(0, count($files), $name));
2047              }
2048          }
2049          $to_include = $included_bundles = array();
2050          $query = null;
2051          foreach($js_includes as $file)
2052          {
2053              if (!isset($to_include[$file]))
2054              {
2055                  if (($bundle = $file2bundle[$file]))
2056                  {
2057                      //error_log(__METHOD__."() requiring boundle $bundle for $file");
2058                      if (!in_array($bundle, $included_bundles))
2059                      {
2060                          $max_modified = 0;
2061                          $to_include = array_merge($to_include, self::bundle_urls($bundles[$bundle], $max_modified));
2062                          $included_bundles[] = $bundle;
2063                          // check if bundle-config is more recent then
2064                          if ($max_modified > $bundles_ts)
2065                          {
2066                              // force new bundle config by deleting cached one and call ourself again
2067                              egw_cache::unsetTree(__CLASS__, 'bundles');
2068                              return self::bundle_js_includes($js_includes);
2069                          }
2070                      }
2071                  }
2072                  else
2073                  {
2074                      unset($query);
2075                      list($path, $query) = explode('?', $file, 2);
2076                      $mod = filemtime(EGW_SERVER_ROOT.$path);
2077  
2078                      $to_include[$file] = $path.'?'.$mod.($query ? '&'.$query : '');
2079                  }
2080              }
2081          }
2082          /*_debug_array($js_includes);
2083          _debug_array(array_values($to_include));
2084          die('STOP');*/
2085  
2086          return array_values($to_include);
2087      }
2088  
2089      /**
2090       * Generate bundle url(s) for given js files
2091       *
2092       * @param array $js_includes
2093       * @param int& $max_modified =null on return maximum modification time of bundle
2094       * @return array js-files (can be more then one, if one of given files can not be bundeled)
2095       */
2096  	protected static function bundle_urls(array $js_includes, &$max_modified=null)
2097      {
2098          $debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True';
2099          $to_include = $to_minify = array();
2100          $max_modified = 0;
2101          $query = null;
2102          foreach($js_includes as $path)
2103          {
2104              if ($path == '/phpgwapi/js/jsapi/egw.js') continue;    // loaded via own tag, and we must not load it twice!
2105  
2106              unset($query);
2107              list($path,$query) = explode('?',$path,2);
2108              $mod = filemtime(EGW_SERVER_ROOT.$path);
2109  
2110              // for now minify does NOT support query parameters, nor php files generating javascript
2111              if ($debug_minify || $query || substr($path, -3) != '.js' || strpos($path,'ckeditor') !== false ||
2112                  substr($path, -7) == '/app.js')    // do NOT include app.js, as it changes from app to app
2113              {
2114                  $path .= '?'. $mod.($query ? '&'.$query : '');
2115                  $to_include[] = $path;
2116              }
2117              else
2118              {
2119                  if ($mod > $max_modified) $max_modified = $mod;
2120                  $to_minify[] = substr($path,1);
2121              }
2122          }
2123          if (!$debug_minify && $to_minify)
2124          {
2125              $base_path = $GLOBALS['egw_info']['server']['webserver_url'];
2126              if ($base_path[0] != '/') $base_path = parse_url($base_path, PHP_URL_PATH);
2127              $path = '/phpgwapi/inc/min/?'.($base_path && $base_path != '/' ? 'b='.substr($base_path, 1).'&' : '').
2128                  'f='.implode(',', $to_minify) .
2129                  ($GLOBALS['egw_info']['server']['debug_minify'] === 'debug' ? '&debug' : '').
2130                  '&'.$max_modified;
2131              // need to include minified javascript before not minified stuff like jscalendar-setup, as it might depend on it
2132              array_unshift($to_include, $path);
2133          }
2134          //error_log(__METHOD__."(".array2string($js_includes).") returning ".array2string($to_include));
2135          return $to_include;
2136      }
2137  
2138      /**
2139       * Maximum number of files in a bundle
2140       *
2141       * We split bundles, if they contain more then these number of files,
2142       * because IE silently stops caching them, if Content-Length get's too big.
2143       *
2144       * IE11 cached 142kb compressed api bundle, but not 190kb et2 bundle.
2145       * Splitting et2 bundle in max 50 files chunks, got IE11 to cache both bundles.
2146       */
2147      const MAX_BUNDLE_FILES = 50;
2148  
2149      /**
2150       * Return typical bundes we use:
2151       * - api stuff phpgwapi/js/jsapi/* and it's dependencies incl. jquery
2152       * - etemplate2 stuff not including api bundle, but jquery-ui
2153       *
2154       * @return array bundle-url => array of contained files
2155       */
2156  	public static function get_bundles()
2157      {
2158          $inc_mgr = new egw_include_mgr();
2159          $bundles = array();
2160  
2161          $api_max_mod = $et2_max_mod = $jdots_max_mod = 0;
2162  
2163          // generate api bundle
2164          $inc_mgr->include_js_file('/phpgwapi/js/jquery/jquery.js');
2165          $inc_mgr->include_js_file('/phpgwapi/js/jquery/jquery-ui.js');
2166          $inc_mgr->include_js_file('/phpgwapi/js/jsapi/jsapi.js');
2167          $inc_mgr->include_js_file('/phpgwapi/js/egw_json.js');
2168          $inc_mgr->include_js_file('/phpgwapi/js/jsapi/egw.js');
2169          // dhtmlxTree (dhtmlxMenu get loaded via dependency in egw_menu_dhtmlx.js)
2170          $inc_mgr->include_js_file('/phpgwapi/js/dhtmlxtree/codebase/dhtmlxcommon.js');
2171          $inc_mgr->include_js_file('/phpgwapi/js/dhtmlxtree/sources/dhtmlxtree.js');
2172          $inc_mgr->include_js_file('/phpgwapi/js/dhtmlxtree/sources/ext/dhtmlxtree_json.js');
2173          // actions
2174          $inc_mgr->include_js_file('/phpgwapi/js/egw_action/egw_action.js');
2175          $inc_mgr->include_js_file('/phpgwapi/js/egw_action/egw_keymanager.js');
2176          $inc_mgr->include_js_file('/phpgwapi/js/egw_action/egw_action_popup.js');
2177          $inc_mgr->include_js_file('/phpgwapi/js/egw_action/egw_action_dragdrop.js');
2178          $inc_mgr->include_js_file('/phpgwapi/js/egw_action/egw_dragdrop_dhtmlx_tree.js');
2179          $inc_mgr->include_js_file('/phpgwapi/js/egw_action/egw_menu.js');
2180          $inc_mgr->include_js_file('/phpgwapi/js/egw_action/egw_menu_dhtmlx.js');
2181          // include choosen in api, as old eTemplate uses it and fail if it pulls in half of et2
2182          $inc_mgr->include_js_file('/phpgwapi/js/jquery/chosen/chosen.jquery.js');
2183          // include CKEditor in api, as old eTemplate uses it too
2184          $inc_mgr->include_js_file('/phpgwapi/js/ckeditor/ckeditor.js');
2185          $inc_mgr->include_js_file('/phpgwapi/js/ckeditor/config.js');
2186          $bundles['api'] = $inc_mgr->get_included_files();
2187          self::bundle_urls($bundles['api'], $api_max_mod);
2188  
2189          // generate et2 bundle (excluding files in api bundle)
2190          //$inc_mgr->include_js_file('/etemplate/js/lib/jsdifflib/difflib.js');    // it does not work with "use strict" therefore included in front
2191          $inc_mgr->include_js_file('/etemplate/js/etemplate2.js');
2192          $bundles['et2'] = array_diff($inc_mgr->get_included_files(), $bundles['api']);
2193          self::bundle_urls($bundles['et2'], $et2_max_mod);
2194  
2195          // generate jdots bundle, if installed
2196          /* switching jdots bundle off, as fw_pixelegg will cause whole jdots bundle incl. fw_jdots to include
2197          if (file_exists(EGW_SERVER_ROOT.'/jdots'))
2198          {
2199              $inc_mgr->include_js_file('/jdots/js/fw_jdots.js');
2200              $bundles['jdots'] = array_diff($inc_mgr->get_included_files(), call_user_func_array('array_merge', $bundles));
2201              self::bundle_urls($bundles['jdots'], $jdots_max_mod);
2202          }*/
2203  
2204          // automatic split bundles with more then MAX_BUNDLE_FILES (=50) files
2205          foreach($bundles as $name => $files)
2206          {
2207              $n = '';
2208              while (count($files) > self::MAX_BUNDLE_FILES*(int)$n)
2209              {
2210                  $files80 = array_slice($files, self::MAX_BUNDLE_FILES*(int)$n, self::MAX_BUNDLE_FILES, true);
2211                  $bundles[$name.$n++] = $files80;
2212              }
2213          }
2214  
2215          // store max modification time of all files in all bundles
2216          $bundles['.ts'] = max(array($api_max_mod, $et2_max_mod, $jdots_max_mod));
2217  
2218          //error_log(__METHOD__."() returning ".array2string($bundles));
2219          return $bundles;
2220      }
2221  
2222      /**
2223       * Content from includeCSS calls
2224       *
2225       * @var array
2226       */
2227      protected static $css_include_files = array();
2228  
2229      /**
2230       *
2231       * @var boolean
2232       */
2233      protected static $load_default_css = true;
2234  
2235      /**
2236       * Include a css file, either speicified by it's path (relative to EGW_SERVER_ROOT) or appname and css file name
2237       *
2238       * @param string $app path (relative to EGW_SERVER_ROOT) or appname (if !is_null($name))
2239       * @param string $name =null name of css file in $app/templates/{default|$this->template}/$name.css
2240       * @param boolean $append =true true append file, false prepend (add as first) file used eg. for template itself
2241       * @param boolean $no_default_css =false true do NOT load any default css, only what app explicitly includes
2242       * @return boolean false: css file not found, true: file found
2243       */
2244  	public static function includeCSS($app, $name=null, $append=true, $no_default_css=false)
2245      {
2246          if ($no_default_css)
2247          {
2248              self::$load_default_css = false;
2249              self::$css_include_files = array();
2250          }
2251  
2252          if (!is_null($name))
2253          {
2254              $path = '/'.$app.'/templates/'.$GLOBALS['egw_info']['server']['template_set'].'/'.$name.'.css';
2255              if (!file_exists(EGW_SERVER_ROOT.$path))
2256              {
2257                  $path = '/'.$app.'/templates/default/'.$name.'.css';
2258              }
2259          }
2260          else
2261          {
2262              $path = $app;
2263          }
2264          if (!file_exists(EGW_SERVER_ROOT.$path) && !file_exists(EGW_SERVER_ROOT . parse_url($path,PHP_URL_PATH)))
2265          {
2266              //error_log(__METHOD__."($app,$name) $path NOT found!");
2267              return false;
2268          }
2269          if (!in_array($path,self::$css_include_files))
2270          {
2271              if ($append)
2272              {
2273                  self::$css_include_files[] = $path;
2274              }
2275              else
2276              {
2277                  self::$css_include_files = array_merge(array($path), self::$css_include_files);
2278              }
2279          }
2280          return true;
2281      }
2282  
2283      /**
2284       * Add registered CSS and javascript to ajax response
2285       */
2286  	public static function include_css_js_response()
2287      {
2288          $response = egw_json_response::get();
2289          $app = $GLOBALS['egw_info']['flags']['currentapp'];
2290  
2291          // try to add app specific css file
2292          self::includeCSS($app, 'app-'.$GLOBALS['egw_info']['user']['preferences']['common']['theme']) ||
2293              self::includeCSS($app,'app');
2294  
2295          // add all css files from egw_framework::includeCSS()
2296          $query = null;
2297          foreach(self::$css_include_files as $path)
2298          {
2299              unset($query);
2300              list($path,$query) = explode('?',$path,2);
2301              $path .= '?'. ($query ? $query : filemtime(EGW_SERVER_ROOT.$path));
2302              $response->includeCSS($GLOBALS['egw_info']['server']['webserver_url'].$path);
2303          }
2304  
2305          // try to add app specific js file
2306          self::validate_file('.', 'app', $app);
2307  
2308          // add all js files from egw_framework::validate_file()
2309          $files = self::bundle_js_includes(self::$js_include_mgr->get_included_files());
2310          foreach($files as $path)
2311          {
2312              $response->includeScript($GLOBALS['egw_info']['server']['webserver_url'].$path);
2313          }
2314      }
2315  
2316      /**
2317       * Set a preference via ajax
2318       *
2319       * @param string $app
2320       * @param string $name
2321       * @param string $value
2322       */
2323  	public static function ajax_set_preference($app, $name, $value)
2324      {
2325          $GLOBALS['egw']->preferences->read_repository();
2326          if ((string)$value === '')
2327          {
2328              $GLOBALS['egw']->preferences->delete($app, $name);
2329          }
2330          else
2331          {
2332              $GLOBALS['egw']->preferences->add($app, $name, $value);
2333          }
2334          $GLOBALS['egw']->preferences->save_repository(True);
2335      }
2336  
2337      /**
2338       * Get preferences of a certain application via ajax
2339       *
2340       * @param string $app
2341       */
2342  	public static function ajax_get_preference($app)
2343      {
2344          if (preg_match('/^[a-z0-9_]+$/i', $app))
2345          {
2346              // send etag header, if we are directly called (not via jsonq!)
2347              if (strpos($_GET['menuaction'], __FUNCTION__) !== false)
2348              {
2349                  $etag = '"'.$app.'-'.md5(json_encode($GLOBALS['egw_info']['user']['preferences'][$app])).'"';
2350                  if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag)
2351                  {
2352                      header("HTTP/1.1 304 Not Modified");
2353                      common::egw_exit();
2354                  }
2355                  header('ETag: '.$etag);
2356              }
2357              $response = egw_json_response::get();
2358              $response->call('egw.set_preferences', (array)$GLOBALS['egw_info']['user']['preferences'][$app], $app);
2359          }
2360      }
2361  
2362      /**
2363       * Include favorites when generating the page server-side
2364       *
2365       * @param string $app application, needed to find preferences
2366       * @param string $default =null preference name for default favorite, default "nextmatch-$app.index.rows-favorite"
2367       * @deprecated use egw_favorites::favorite_list
2368       * @return array with a single sidebox menu item (array) containing html for favorites
2369       */
2370  	public static function favorite_list($app, $default=null)
2371      {
2372          return egw_favorites::list_favorites($app, $default);
2373      }
2374  
2375      /**
2376       * Create or delete a favorite for multiple users
2377       *
2378       * Need to be in egw_framework to be called with .template postfix from json.php!
2379       *
2380       * @param string $app Current application, needed to save preference
2381       * @param string $name Name of the favorite
2382       * @param string $action "add" or "delete"
2383       * @param boolean|int|string $group ID of the group to create the favorite for, or 'all' for all users
2384       * @param array $filters =array() key => value pairs for the filter
2385       * @return boolean Success
2386       */
2387  	public static function ajax_set_favorite($app, $name, $action, $group, $filters = array())
2388      {
2389          return egw_favorites::set_favorite($app, $name, $action, $group, $filters);
2390      }
2391  
2392      /**
2393       * Get a cachable list of users for the client
2394       *
2395       * The account source takes care of access and filtering according to preference
2396       */
2397  	public static function ajax_user_list()
2398      {
2399          $list = array('accounts' => array(),'groups' => array(), 'owngroups' => array());
2400          if($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'primary_group')
2401          {
2402              $list['accounts']['filter']['group'] = $GLOBALS['egw_info']['user']['account_primary_group'];
2403          }
2404          foreach($list as $type => &$accounts)
2405          {
2406              $options = array('account_type' => $type) + $accounts;
2407              $key_pair = accounts::link_query('',$options);
2408              $accounts = array();
2409              foreach($key_pair as $account_id => $name)
2410              {
2411                  $accounts[] = array('value' => $account_id, 'label' => $name);
2412              }
2413          }
2414  
2415          egw_json_response::get()->data($list);
2416          return $list;
2417      }
2418  }
2419  
2420  // Init all static variables
2421  egw_framework::init_static();
2422  
2423  /**
2424   * Public functions to be compatible with the exiting eGW framework
2425   */
2426  if (!function_exists('parse_navbar'))
2427  {
2428      /**
2429       * echo's out the navbar
2430       *
2431       * @deprecated use $GLOBALS['egw']->framework->navbar() or $GLOBALS['egw']->framework::render()
2432       */
2433  	function parse_navbar()
2434      {
2435          echo $GLOBALS['egw']->framework->navbar();
2436      }
2437  }
2438  
2439  if (!function_exists('display_sidebox'))
2440  {
2441      /**
2442       * echo's out a sidebox menu
2443       *
2444       * @deprecated use $GLOBALS['egw']->framework->sidebox()
2445       */
2446  	function display_sidebox($appname,$menu_title,$_file)
2447      {
2448          $file = str_replace('preferences.uisettings.index', 'preferences.preferences_settings.index', $_file);
2449          $GLOBALS['egw']->framework->sidebox($appname,$menu_title,$file);
2450      }
2451   }

title

Description

title

Description

title

Description

title

title

Body