eGroupWare PHP Cross Reference Groupware Applications

Source: /jdots/inc/class.jdots_framework.inc.php - 1117 lines - 35624 bytes - Summary - Text - Print

Description: Stylite: jdots template

   1  <?php
   2  /**
   3   * Stylite: jdots template
   4   *
   5   * @link http://www.stylite.de
   6   * @package jdots
   7   * @author Andreas Stöckel <as@stylite.de>
   8   * @author Ralf Becker <rb@stylite.de>
   9   * @author Nathan Gray <ng@stylite.de>
  10   * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
  11   * @version $Id$
  12   */
  13  
  14  /**
  15  * Stylite jdots template
  16  */
  17  class jdots_framework extends egw_framework
  18  {
  19      /**
  20       * Appname used to include javascript code
  21       */
  22      const JS_INCLUDE_APP = 'jdots';
  23      /**
  24       * Appname used for everything else
  25       */
  26      const APP = 'jdots';
  27  
  28      /**
  29       * Minimum width of sidebar eg. from German 2-letter daynames in Calendar
  30       *
  31       * Need to be changed in js/egw_fw.js around line 1536 too!
  32       */
  33      const MIN_SIDEBAR_WIDTH = 215;
  34      /**
  35       * Default width need to be tested with English 3-letter day-names and Pixelegg template in Calendar
  36       *
  37       * Need to be changed in js/egw_fw.js around line 1536 too!
  38       */
  39      const DEFAULT_SIDEBAR_WIDTH = 255;
  40      /**
  41       * Whether javascript:egw_link_handler calls (including given app) should be returned by the "link" function
  42       * or just the link
  43       *
  44       * @var string
  45       */
  46      private static $link_app;
  47  
  48      /**
  49       * Constructor
  50       *
  51       * @param string $template = 'idots' name of the template
  52       */
  53  	function __construct($template=self::APP)
  54      {
  55          parent::__construct($template);        // call the constructor of the extended class
  56  
  57          $this->template_dir = '/'.$template;        // we are packaged as an application
  58      }
  59  
  60      /**
  61       * Check if current user agent is supported
  62       *
  63       * Currently we do NOT support:
  64       * - iPhone, iPad, Android, SymbianOS due to iframe scrolling problems of Webkit
  65       * - IE < 7
  66       *
  67       * @return boolean
  68       */
  69  	public static function is_supported_user_agent()
  70      {
  71          if (html::$user_agent == 'msie' && html::$ua_version < 7)
  72          {
  73              return false;
  74          }
  75          return true;
  76      }
  77  
  78      /**
  79       * Reads an returns the width of the sidebox or false if the width is not set
  80       */
  81  	private static function get_sidebar_width($app)
  82      {
  83          $width = self::DEFAULT_SIDEBAR_WIDTH;
  84  
  85          //Check whether the width had been stored explicitly for the jdots template, use that value
  86          if ($GLOBALS['egw_info']['user']['preferences'][$app]['jdotssideboxwidth'])
  87          {
  88              $width = (int)$GLOBALS['egw_info']['user']['preferences'][$app]['jdotssideboxwidth'];
  89  //                error_log(__METHOD__.__LINE__."($app):$width --> reading jdotssideboxwidth");
  90          }
  91          //Otherwise use the legacy "idotssideboxwidth" value
  92          else if ($GLOBALS['egw_info']['user']['preferences'][$app]['idotssideboxwidth'])
  93          {
  94              $width = (int)$GLOBALS['egw_info']['user']['preferences'][$app]['idotssideboxwidth'];
  95  //                error_log(__METHOD__.__LINE__."($app):$width --> reading idotssideboxwidth");
  96          }
  97  
  98          //Width may not be smaller than MIN_SIDEBAR_WIDTH
  99          if ($width < self::MIN_SIDEBAR_WIDTH)
 100              $width = self::MIN_SIDEBAR_WIDTH;
 101  
 102          return $width;
 103      }
 104  
 105      /**
 106       * Returns the global width of the sidebox. If the app_specific_sidebar_width had been switched
 107       * on, the default width will be returned
 108       */
 109  	private static function get_global_sidebar_width()
 110      {
 111          return self::DEFAULT_SIDEBAR_WIDTH;
 112      }
 113  
 114  
 115      /**
 116       * Sets the sidebox width accoringly to the app_specific_sidebar_width setting, either
 117       * in the current application or globaly
 118       */
 119  	private static function set_sidebar_width($app, $val)
 120      {
 121          $GLOBALS['egw']->preferences->read_repository();
 122          $GLOBALS['egw']->preferences->change($app, 'jdotssideboxwidth', $val);
 123          $GLOBALS['egw']->preferences->save_repository(True);
 124      }
 125  
 126      /**
 127       * Extract applicaton name from given url (incl. GET parameters)
 128       *
 129       * @param string $url
 130       * @return string appname or NULL if it could not be detected (eg. constructing javascript urls)
 131       */
 132  	public static function app_from_url($url)
 133      {
 134          $matches = null;
 135          if (preg_match('/menuaction=([a-z0-9_-]+)\./i',$url,$matches))
 136          {
 137              return $matches[1];
 138          }
 139          if ($GLOBALS['egw_info']['server']['webserver_url'] &&
 140              ($webserver_path = parse_url($GLOBALS['egw_info']['server']['webserver_url'],PHP_URL_PATH)))
 141          {
 142              list(,$url) = explode($webserver_path, parse_url($url,PHP_URL_PATH),2);
 143          }
 144          if (preg_match('/\/([^\/]+)\/([^\/]+\.php)?(\?|\/|$)/',$url,$matches))
 145          {
 146              return $matches[1];
 147          }
 148          //error_log(__METHOD__."('$url') could NOT detect application!");
 149          return null;
 150      }
 151  
 152      /**
 153       * Link url generator
 154       *
 155       * @param string $url The url the link is for
 156       * @param string|array    $extravars    Extra params to be passed to the url
 157       * @param string $link_app = null if appname or true, some templates generate a special link-handler url
 158       * @return string    The full url after processing
 159       */
 160  	static function link($url = '', $extravars = '', $link_app=null)
 161      {
 162          if (is_null($link_app)) $link_app = self::$link_app;
 163          $link = parent::link($url, $extravars);
 164  
 165          // $link_app === true --> detect application, otherwise use given application
 166          if ($link_app && (is_string($link_app) || ($link_app = self::app_from_url($link))))
 167          {
 168              // Link gets handled in JS, so quotes need slashes as well as url-encoded
 169              // encoded ampersands in get parameters (%26) need to be encoded twise,
 170              // so they are still encoded when assigned to window.location
 171              $link_with_slashes = str_replace(array('%27','%26'), array('\%27','%2526'), $link);
 172  
 173              //$link = "javascript:window.egw_link_handler?egw_link_handler('$link','$link_app'):parent.egw_link_handler('$link','$link_app');";
 174              $link = "javascript:egw_link_handler('$link_with_slashes','$link_app')";
 175          }
 176          return $link;
 177      }
 178  
 179      /**
 180       * Overwrite to add our customizable colors
 181       *
 182       * @see egw_framework::_get_css()
 183       * @return array
 184       */
 185  	public function _get_css()
 186      {
 187          $ret = parent::_get_css();
 188  
 189          // color to use
 190          $color = str_replace('custom',$GLOBALS['egw_info']['user']['preferences']['common']['template_custom_color'],
 191              $GLOBALS['egw_info']['user']['preferences']['common']['template_color']);
 192          // use active tab or header, beside sidebox
 193          if (($use_active_tab = $color[0] == '@')) $color = substr($color,1);
 194  
 195          if (preg_match('/^(#[0-9A-F]+|[A-Z]+)$/i',$color))    // a little xss check
 196          {
 197              $ret['app_css'] .= "
 198  /**
 199   * theme changes to color jdots for color: $color
 200   */
 201  .egw_fw_ui_sidemenu_entry_header_active, .egw_fw_ui_sidemenu_entry_content, .egw_fw_ui_sidemenu_entry_header:hover {
 202      background-color: $color;
 203      border-color: $color;
 204  }
 205  .egw_fw_ui_sidemenu_entry_header_active, .egw_fw_ui_sidemenu_entry_header:hover {
 206      background-image: url(jdots/images/gradient30transparent.png);
 207  }
 208  .egw_fw_ui_sidemenu_entry_content {
 209      background-image: url(jdots/images/gradient10transparent.png);
 210  }
 211  div .egw_fw_ui_sidemenu_entry_content > div {
 212      background-color: #ffffff;
 213  }".($use_active_tab ? "
 214  .egw_fw_ui_tab_header_active {
 215      background-image: url(jdots/images/gradient30transparent.png);
 216      background-color: $color;
 217  }
 218  " : "
 219  .egw_fw_ui_tabs_header {
 220      background-image: url(jdots/images/gradient22transparent.png);
 221      background-color: $color;
 222  }");
 223          }
 224          return $ret;
 225      }
 226  
 227      /**
 228       * Query additional CSP frame-src from current app
 229       *
 230       * We have to query all apps, as we dont reload frameset!
 231       *
 232       * @return array
 233       */
 234  	protected function _get_csp_frame_src()
 235      {
 236          $srcs = array();
 237          foreach($GLOBALS['egw']->hooks->process('csp-frame-src') as $src)
 238          {
 239              if ($src) $srcs = array_merge($srcs, $src);
 240          }
 241          return $srcs;
 242      }
 243  
 244      /**
 245       * Returns the html-header incl. the opening body tag
 246       *
 247       * @param array $extra = array() extra attributes passed as data-attribute to egw.js
 248       * @return string with html
 249       */
 250  	function header(array $extra=array())
 251      {
 252          // make sure header is output only once
 253          if (self::$header_done) return '';
 254          self::$header_done = true;
 255  
 256          $this->send_headers();
 257  
 258          // catch error echo'ed before the header, ob_start'ed in the header.inc.php
 259          $content = ob_get_contents();
 260          ob_end_clean();
 261          //error_log(__METHOD__.'('.array2string($extra).') called from:'.function_backtrace());
 262  
 263          // the instanciation of the template has to be here and not in the constructor,
 264          // as the old Template class has problems if restored from the session (php-restore)
 265          // todo: check if this is still true
 266          $this->tpl = new Template(EGW_SERVER_ROOT.$this->template_dir);
 267          if (html::$ua_mobile || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile')
 268          {
 269              $this->tpl->set_file(array('_head' => 'head_mobile.tpl'));
 270          }
 271          else
 272          {
 273              $this->tpl->set_file(array('_head' => 'head.tpl'));
 274          }
 275          $this->tpl->set_block('_head','head');
 276          $this->tpl->set_block('_head','framework');
 277  
 278          // should we draw the framework, or just a header
 279          $do_framework = isset($_GET['cd']) && $_GET['cd'] === 'yes';
 280  
 281          // load clientside link registry to framework only
 282          if (!isset($GLOBALS['egw_info']['flags']['js_link_registry']))
 283          {
 284              $GLOBALS['egw_info']['flags']['js_link_registry'] = $do_framework;
 285          }
 286          // Loader
 287          $this->tpl->set_var('loader_text', lang('please wait...'));
 288  
 289          if ($do_framework)
 290          {
 291              //echo __METHOD__.__LINE__.' do framework ...'.'<br>';
 292              // framework javascript classes only need for framework
 293              self::validate_file('jquery','jquery-ui');
 294              self::validate_file('framework', 'fw', self::JS_INCLUDE_APP);
 295              self::validate_file('framework', 'fw_browser', self::JS_INCLUDE_APP);
 296              self::validate_file('framework', 'fw_ui', self::JS_INCLUDE_APP);
 297              if (html::$ua_mobile || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile')
 298              {
 299                  self::validate_file('.', 'fw_mobile', self::JS_INCLUDE_APP);
 300              }
 301              else
 302              {
 303                  self::validate_file('.', 'fw_'.static::APP, static::JS_INCLUDE_APP);
 304              }
 305              self::validate_file('.', 'egw_fw_classes', self::JS_INCLUDE_APP);
 306              self::validate_file('.','etemplate2','etemplate');
 307  
 308              // Need to load this here to get enhanced selectboxes working
 309              self::validate_file('/phpgwapi/js/jquery/chosen/chosen.jquery.js');
 310  
 311              egw_cache::unsetSession(__CLASS__,'sidebox_md5');    // sideboxes need to be send again
 312  
 313              // load jscalendar for calendar users
 314              if ($GLOBALS['egw_info']['user']['apps']['calendar'])
 315              {
 316                  $GLOBALS['egw']->jscalendar;
 317              }
 318              // load dhtmlxtree for pm or email users
 319              if ($GLOBALS['egw_info']['user']['apps']['projectmanager'] || $GLOBALS['egw_info']['user']['apps']['felamimail'])
 320              {
 321                  $GLOBALS['egw_info']['flags']['java_script'] .= html::tree(null,null);
 322              }
 323              $extra['navbar-apps'] = $this->get_navbar_apps($_SERVER['REQUEST_URI']);
 324          }
 325          // for an url WITHOUT cd=yes --> load framework if not yet loaded:
 326          // - if top has framework object, we are all right
 327          // - if not we need to check if we have an opener (are a popup window)
 328          // - as popups can open further popups, we need to decend all the way down until we find a framework
 329          // - only if we cant find a framework in all openers, we redirect to create a new framework
 330          if(!$do_framework)
 331          {
 332              // fetch sidebox from application and set it in extra data, if we are no popup
 333              if (!$GLOBALS['egw_info']['flags']['nonavbar'])
 334              {
 335                  $this->do_sidebox();
 336              }
 337              // for remote manual never check/create framework
 338              if (!in_array($GLOBALS['egw_info']['flags']['currentapp'], array('manual', 'login', 'logout', 'sitemgr')))
 339              {
 340                  if (empty($GLOBALS['egw_info']['flags']['java_script'])) $GLOBALS['egw_info']['flags']['java_script']='';
 341                  $extra['check-framework'] = $_GET['cd'] !== 'no';
 342              }
 343          }
 344          $this->tpl->set_var($this->_get_header($extra));
 345          $content = $this->tpl->fp('out','head').$content;
 346  
 347          if (!$do_framework)
 348          {
 349              return $content;
 350          }
 351  
 352          // topmenu
 353          $vars = $this->_get_navbar($apps = $this->_get_navbar_apps());
 354          $this->tpl->set_var($this->topmenu($vars,$apps));
 355  
 356          // hook after_navbar (eg. notifications)
 357          $this->tpl->set_var('hook_after_navbar',$this->_get_after_navbar());
 358  
 359          //Global sidebar width
 360          $this->tpl->set_var('sidebox_width', self::get_global_sidebar_width());
 361          $this->tpl->set_var('sidebox_min_width', self::MIN_SIDEBAR_WIDTH);
 362  
 363          if (!(html::$ua_mobile || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile'))
 364          {
 365              // logout button
 366              $this->tpl->set_var('title_logout', lang("Logout"));
 367              $this->tpl->set_var('link_logout', egw::link('/logout.php'));
 368              //Print button title
 369              $this->tpl->set_var('title_print', lang("Print current view"));
 370          }
 371  
 372          // add framework div's
 373          $this->tpl->set_var($this->_get_footer());
 374          $content .= $this->tpl->fp('out','framework');
 375          $content .= self::footer(false);
 376  
 377          echo $content;
 378          common::egw_exit();
 379      }
 380  
 381      private $topmenu_items;
 382      private $topmenu_info_items;
 383  
 384      /**
 385       * Compile entries for topmenu:
 386       * - regular items: links
 387       * - info items
 388       *
 389       * @param array $vars
 390       * @param array $apps
 391       * @return array
 392       */
 393  	function topmenu(array $vars,array $apps)
 394      {
 395          $this->topmenu_items = $this->topmenu_info_items = array();
 396  
 397          parent::topmenu($vars,$apps);
 398          $vars['topmenu_items'] = "<ul>\n<li>".implode("</li>\n<li>",$this->topmenu_items)."</li>\n</ul>";
 399          $vars['topmenu_info_items'] = '';
 400          foreach($this->topmenu_info_items as $id => $item)
 401          {
 402              $vars['topmenu_info_items'] .= '<div class="topmenu_info_item"'.
 403                  (is_numeric($id) ? '' : ' id="topmenu_info_'.$id.'"').'>'.$item."</div>\n";
 404          }
 405          $this->topmenu_items = $this->topmenu_info_items = null;
 406  
 407          return $vars;
 408      }
 409  
 410      /**
 411      * called by hooks to add an icon in the topmenu info location
 412      *
 413      * @param string $id unique element id
 414      * @param string $icon_src src of the icon image. Make sure this nog height then 18pixels
 415      * @param string $iconlink where the icon links to
 416      * @param booleon $blink set true to make the icon blink
 417      * @param mixed $tooltip string containing the tooltip html, or null of no tooltip
 418      * @todo implement in a reasonable way for jdots
 419      * @return void
 420      */
 421  	function topmenu_info_icon($id,$icon_src,$iconlink,$blink=false,$tooltip=null)
 422      {
 423          unset($id,$icon_src,$iconlink,$blink,$tooltip);    // not used
 424          // not yet implemented, only used in admin/inc/hook_topmenu_info.inc.php to notify about pending updates
 425      }
 426  
 427      /**
 428      * Add menu items to the topmenu template class to be displayed
 429      *
 430      * @param array $app application data
 431      * @param mixed $alt_label string with alternative menu item label default value = null
 432      * @param string $urlextra string with alternate additional code inside <a>-tag
 433      * @access protected
 434      * @return void
 435      */
 436  	function _add_topmenu_item(array $app_data,$alt_label=null)
 437      {
 438          switch($app_data['name'])
 439          {
 440              case 'logout':
 441                  if (html::$ua_mobile || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile')
 442                  {
 443  
 444                  }
 445                  else
 446                  {
 447                      return;    // no need for logout in topmenu on jdots
 448                  }
 449                  break;
 450  
 451              case 'manual':
 452                  $app_data['url'] = "javascript:callManual();";
 453                  break;
 454  
 455              default:
 456                  if (strpos($app_data['url'],'logout.php') === false && substr($app_data['url'], 0, 11) != 'javascript:')
 457                  {
 458                      $app_data['url'] = "javascript:egw_link_handler('".$app_data['url']."','".
 459                          (isset($GLOBALS['egw_info']['user']['apps'][$app_data['name']]) ?
 460                              $app_data['name'] : 'about')."')";
 461                  }
 462          }
 463          $id = $app_data['id'] ? $app_data['id'] : ($app_data['name'] ? $app_data['name'] : $app_data['title']);
 464          $title = html::$ua_mobile || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile'
 465              ? '' : htmlspecialchars($alt_label ? $alt_label : $app_data['title']);
 466          $this->topmenu_items[] = '<a id="topmenu_' . $id . '" href="'.htmlspecialchars($app_data['url']).'" title="'.$app_data['title'].'">'.$title.'</a>';
 467      }
 468  
 469      /**
 470       * Add info items to the topmenu template class to be displayed
 471       *
 472       * @param string $content html of item
 473       * @param string $id = null
 474       * @access protected
 475       * @return void
 476       */
 477  	function _add_topmenu_info_item($content, $id=null)
 478      {
 479          if(strpos($content,'menuaction=admin.admin_accesslog.sessions') !== false)
 480          {
 481              $content = preg_replace('/href="([^"]+)"/',"href=\"javascript:egw_link_handler('\\1','admin')\"",$content);
 482          }
 483          if ($id)
 484          {
 485              $this->topmenu_info_items[$id] = $content;
 486          }
 487          else
 488          {
 489              $this->topmenu_info_items[] = $content;
 490          }
 491      }
 492  
 493      /**
 494       * Change timezone
 495       *
 496       * @param string $tz
 497       */
 498  	function ajax_tz_selection($tz)
 499      {
 500          egw_time::setUserPrefs($tz);    // throws exception, if tz is invalid
 501  
 502          $GLOBALS['egw']->preferences->read_repository();
 503          $GLOBALS['egw']->preferences->add('common','tz',$tz);
 504          $GLOBALS['egw']->preferences->save_repository();
 505      }
 506  
 507      /**
 508       * Flag if do_sidebox() was called
 509       *
 510       * @var boolean
 511       */
 512      protected $sidebox_done = false;
 513  
 514      /**
 515       * Returns the html from the body-tag til the main application area (incl. opening div tag)
 516       *
 517       * jDots does NOT use a navbar, but it tells us that application might want a sidebox!
 518       *
 519       * @return string
 520       */
 521  	function navbar()
 522      {
 523          $header = '';
 524          if (!self::$header_done)
 525          {
 526              $header = $this->header();
 527          }
 528          $GLOBALS['egw_info']['flags']['nonavbar'] = false;
 529  
 530          if (!$this->sidebox_done && self::$header_done)
 531          {
 532              $this->do_sidebox();
 533              return $header.'<span id="late-sidebox" data-setSidebox="'.htmlspecialchars(json_encode(egw_framework::$extra['setSidebox'])).'"/>';
 534          }
 535  
 536          return $header;
 537      }
 538  
 539      /**
 540       * Set sidebox content in egw_framework::$data['setSidebox']
 541       *
 542       * We store in the session the md5 of each sidebox menu already send to client.
 543       * If the framework get reloaded, that list gets cleared in header();
 544       * Most apps never change sidebox, so we not even need to generate it more then once.
 545       */
 546  	function do_sidebox()
 547      {
 548          $this->sidebox_done = true;
 549  
 550          $app = $GLOBALS['egw_info']['flags']['currentapp'];
 551  
 552          // only send admin sidebox, for admin index url (when clicked on admin),
 553          // not for other admin pages, called eg. from sidebox menu of other apps
 554          // --> that way we always stay in the app, and NOT open admin sidebox for an app tab!!!
 555          if ($app == 'admin' && substr($_SERVER['PHP_SELF'],-16) != '/admin/index.php' &&
 556              $_GET['menuaction'] != 'admin.admin_ui.index')
 557          {
 558              //error_log(__METHOD__."() app=$app, menuaction=$_GET[menuaction], PHP_SELF=$_SERVER[PHP_SELF] --> sidebox request ignored");
 559              return;
 560          }
 561          $md5_session =& egw_cache::getSession(__CLASS__,'sidebox_md5');
 562  
 563          //Set the sidebox content
 564          $sidebox = $this->get_sidebox($app);
 565          $md5 = md5(json_encode($sidebox));
 566  
 567          if ($md5_session[$app] !== $md5)
 568          {
 569              //error_log(__METHOD__."() header changed md5_session[$app]!=='$md5' --> setting it on egw_framework::\$extra[setSidebox]");
 570              $md5_session[$app] = $md5;    // update md5 in session
 571              egw_framework::$extra['setSidebox'] = array($app, $sidebox, $md5);
 572          }
 573          //else error_log(__METHOD__."() md5_session[$app]==='$md5' --> nothing to do");
 574      }
 575  
 576      /**
 577       * Return true if we are rendering the top-level EGroupware window
 578       *
 579       * A top-level EGroupware window has a navbar: eg. no popup and for a framed template (jdots) only frameset itself
 580       *
 581       * @return boolean $consider_navbar_not_yet_called_as_true=true ignored by jdots, we only care for cd=yes GET param
 582       * @return boolean
 583       */
 584  	public function isTop($consider_navbar_not_yet_called_as_true=true)
 585      {
 586          unset($consider_navbar_not_yet_called_as_true);    // not used
 587          return isset($_GET['cd']) && $_GET['cd'] === 'yes';
 588      }
 589  
 590      /**
 591       * Array containing sidebox menus by applications and menu-name
 592       *
 593       * @var array
 594       */
 595      private $sideboxes;
 596  
 597      /**
 598       * Should calls the first call to self::sidebox create an opened menu
 599       *
 600       * @var boolean
 601       */
 602      private $sidebox_menu_opened = true;
 603  
 604      /**
 605       * Callback for sideboxes hooks, collects the data in a private var
 606       *
 607       * @param string $appname
 608       * @param string $menu_title
 609       * @param array $file
 610       * @param string $type = null 'admin', 'preferences', 'favorites', ...
 611       */
 612  	public function sidebox($appname,$menu_title,$file,$type=null)
 613      {
 614          if (!isset($file['menuOpened'])) $file['menuOpened'] = (boolean)$this->sidebox_menu_opened;
 615          //error_log(__METHOD__."('$appname', '$menu_title', file[menuOpened]=$file[menuOpened], ...) this->sidebox_menu_opened=$this->sidebox_menu_opened");
 616          $this->sidebox_menu_opened = false;
 617  
 618          // fix app admin menus to use admin.admin_ui.index loader
 619          if (($type == 'admin' || $menu_title == lang('Admin')) && $appname != 'admin')
 620          {
 621              $file = preg_replace("/^(javascript:egw_link_handler\(')(.*)menuaction=([^&]+)(.*)(','[^']+'\))$/",
 622                  '$1$2menuaction=admin.admin_ui.index&load=$3$4&ajax=true\',\'admin\')', $file_was=$file);
 623          }
 624  
 625          $this->sideboxes[$appname][$menu_title] = $file;
 626      }
 627  
 628      /**
 629       * Return sidebox data for an application
 630       *
 631       * @param $appname
 632       * @return array of array(
 633       *         'menu_name' => (string),    // menu name, currently md5(title)
 634       *         'title'     => (string),    // translated title to display
 635       *         'opened'    => (boolean),    // menu opend or closed
 636       *      'entries'   => array(
 637       *            array(
 638       *                'lang_item' => translated menu item or html, i item_link === false
 639       *                 'icon_or_star' => url of bullet images, or false for none
 640       *              'item_link' => url or false (lang_item contains complete html)
 641       *              'target' => target attribute fragment, ' target="..."'
 642       *            ),
 643       *            // more entries
 644       *        ),
 645       *     ),
 646       *    array (
 647       *        // next menu
 648       *    )
 649       */
 650  	public function get_sidebox($appname)
 651      {
 652          if (!isset($this->sideboxes[$appname]))
 653          {
 654              self::$link_app = $appname;
 655              // allow other apps to hook into sidebox menu of an app, hook-name: sidebox_$appname
 656              $this->sidebox_menu_opened = true;
 657              $GLOBALS['egw']->hooks->process('sidebox_'.$appname,array($appname),true);    // true = call independent of app-permissions
 658  
 659              // calling the old hook
 660              $this->sidebox_menu_opened = true;
 661              $GLOBALS['egw']->hooks->single('sidebox_menu',$appname);
 662              self::$link_app = null;
 663  
 664              // allow other apps to hook into sidebox menu of every app: sidebox_all
 665              $GLOBALS['egw']->hooks->process('sidebox_all',array($GLOBALS['egw_info']['flags']['currentapp']),true);
 666          }
 667          //If there still is no sidebox content, return null here
 668          if (!isset($this->sideboxes[$appname]))
 669          {
 670              return null;
 671          }
 672  
 673          $data = array();
 674          foreach($this->sideboxes[$appname] as $menu_name => &$file)
 675          {
 676              $current_menu = array(
 677                  'menu_name' => md5($menu_name),    // can contain html tags and javascript!
 678                  'title' => $menu_name,
 679                  'entries' => array(),
 680                  'opened' => (boolean)$file['menuOpened'],
 681              );
 682              foreach($file as $item_text => $item_link)
 683              {
 684                  if ($item_text === 'menuOpened' ||    // flag, not menu entry
 685                      $item_text === '_NewLine_' || $item_link === '_NewLine_')
 686                  {
 687                      continue;
 688                  }
 689                  if (strtolower($item_text) == 'grant access' && $GLOBALS['egw_info']['server']['deny_user_grants_access'])
 690                  {
 691                      continue;
 692                  }
 693  
 694                  $var = array();
 695                  $var['icon_or_star'] = $GLOBALS['egw_info']['server']['webserver_url'] . $this->template_dir.'/images/bullet.png';
 696                  $var['target'] = '';
 697                  if(is_array($item_link))
 698                  {
 699                      if(isset($item_link['icon']))
 700                      {
 701                          $app = isset($item_link['app']) ? $item_link['app'] : $appname;
 702                          $var['icon_or_star'] = $item_link['icon'] ? common::image($app,$item_link['icon']) : False;
 703                      }
 704                      $var['lang_item'] = isset($item_link['no_lang']) && $item_link['no_lang'] ? $item_link['text'] : lang($item_link['text']);
 705                      $var['item_link'] = $item_link['link'];
 706                      if ($item_link['target'])
 707                      {
 708                          // we only support real targets not html markup with target in it
 709                          if (strpos($item_link['target'], 'target=') === false &&
 710                              strpos($item_link['target'], '"') === false)
 711                          {
 712                              $var['target'] = $item_link['target'];
 713                          }
 714                      }
 715                  }
 716                  else
 717                  {
 718                      $var['lang_item'] = lang($item_text);
 719                      $var['item_link'] = $item_link;
 720                  }
 721                  $current_menu['entries'][] = $var;
 722              }
 723              $data[] = $current_menu;
 724          }
 725          return $data;
 726      }
 727  
 728      /**
 729       * Ajax callback which is called whenever a previously opened tab is closed or
 730       * opened.
 731       *
 732       * @param $tablist is an array which contains each tab as an associative array
 733       *   with the keys 'appName' and 'active'
 734       */
 735  	public function ajax_tab_changed_state($tablist)
 736      {
 737          $tabs = array();
 738          foreach($tablist as $data)
 739          {
 740              $tabs[] = $data['appName'];
 741              if ($data['active']) $active = $data['appName'];
 742          }
 743          // send app a notification, that it's tab got closed
 744          // used eg. in phpFreeChat to leave the chat
 745          if (($old_tabs = egw_cache::getSession(__CLASS__, 'open_tabs')))
 746          {
 747              foreach(array_diff(explode(',',$old_tabs),$tabs) as $app)
 748              {
 749                  //error_log("Tab '$app' closed, old_tabs=$old_tabs");
 750                  $GLOBALS['egw']->hooks->single(array(
 751                      'location' => 'tab_closed',
 752                      'app' => $app,
 753                  ), $app);
 754              }
 755          }
 756          $open = implode(',',$tabs);
 757  
 758          if ($open != $GLOBALS['egw_info']['user']['preferences']['common']['open_tabs'] ||
 759              $active != $GLOBALS['egw_info']['user']['preferences']['common']['active_tab'])
 760          {
 761              //error_log(__METHOD__.'('.array2string($tablist).") storing common prefs: open_tabs='$tabs', active_tab='$active'");
 762              egw_cache::setSession(__CLASS__, 'open_tabs', $open);
 763              $GLOBALS['egw']->preferences->read_repository();
 764              $GLOBALS['egw']->preferences->add('common', 'open_tabs', $open);
 765              $GLOBALS['egw']->preferences->add('common', 'active_tab', $active);
 766              $GLOBALS['egw']->preferences->save_repository(true);
 767          }
 768      }
 769  
 770      /**
 771       * Return sidebox data for an application
 772       *
 773       * Format see get_sidebox()
 774       *
 775       * @param $appname
 776       */
 777  	public function ajax_sidebox($appname, $md5)
 778      {
 779          $response = egw_json_response::get();
 780          $sidebox = $this->get_sidebox($appname);
 781          $encoded = json_encode($sidebox);
 782          $new_md5 = md5($encoded);
 783  
 784          $response_array = array();
 785          $response_array['md5'] = $new_md5;
 786  
 787          if ($new_md5 != $md5)
 788          {
 789              //TODO: Add some proper solution to be able to attach the already
 790              //JSON data to the response in order to gain some performace improvements.
 791              $response_array['data'] = $sidebox;
 792          }
 793  
 794          $response->data($response_array);
 795      }
 796  
 797      /**
 798       * Stores the width of the sidebox menu depending on the sidebox menu settings
 799       * @param $appname the name of the application
 800       * @param $width the width set
 801       */
 802  	public function ajax_sideboxwidth($appname, $width)
 803      {
 804          //error_log(__METHOD__."($appname, $width)");
 805          //Check whether the supplied parameters are valid
 806          if (is_int($width) && $GLOBALS['egw_info']['user']['apps'][$appname])
 807          {
 808              self::set_sidebar_width($appname, $width);
 809          }
 810      }
 811  
 812      /**
 813       * Stores the user defined sorting of the applications inside the preferences
 814       *
 815       * @param array $apps
 816       */
 817  	public function ajax_appsort(array $apps)
 818      {
 819          $order = array();
 820          $i = 0;
 821  
 822          //Parse the "$apps" array for valid content (security)
 823          foreach($apps as $app)
 824          {
 825              //Check whether the app really exists and add it to the $app_arr var
 826              if ($GLOBALS['egw_info']['user']['apps'][$app])
 827              {
 828                  $order[$app] = $i;
 829                  $i++;
 830              }
 831          }
 832  
 833          //Store the order array inside the common user preferences
 834          $GLOBALS['egw']->preferences->read_repository();
 835          $GLOBALS['egw']->preferences->change('common', 'user_apporder', serialize($order));
 836          $GLOBALS['egw']->preferences->save_repository(true);
 837      }
 838  
 839      /**
 840       * Reads all available remote applications (currently there may only be one)
 841       * and returns them as an array.
 842       */
 843  	public function jdots_remote_apps()
 844      {
 845          $result = array();
 846  
 847          /*if ($GLOBALS['egw_info']['user']['preferences']['common']['remote_application_enabled'])
 848          {
 849              $name = $GLOBALS['egw_info']['user']['preferences']['common']['remote_application_name'];
 850              $title = $GLOBALS['egw_info']['user']['preferences']['common']['remote_application_title'];
 851              $url = $GLOBALS['egw_info']['user']['preferences']['common']['remote_application_url'];
 852  
 853              $result[$name.'_remote'] = array(
 854                  'name' => $name.'_remote',
 855                  'sideboxwidth' => false,
 856                  'target' => false,
 857                  'title' => $title,
 858                  'icon' => "/egroupware/trunk/egroupware/".$name."/templates/default/images/navbar.png", //TODO: Check whether icon exists
 859                  'baseUrl' => $url,
 860                  'url' => $url.$name.'/index.php',
 861                  'internalName' => $name,
 862              );
 863          }*/
 864  
 865          return $result;
 866      }
 867  
 868      /**
 869       * Prepare an array with apps used to render the navbar
 870       *
 871       * @return array of array(
 872       *  'name'  => app / directory name
 873       *     'title' => translated application title
 874       *  'url'   => url to call for index
 875       *  'icon'  => icon name
 876       *  'icon_app' => application of icon
 877       *  'icon_hover' => hover-icon, if used by template
 878       *  'target'=> ' target="..."' attribute fragment to open url in target, popup or ''
 879       * )
 880       */
 881  	public function navbar_apps()
 882      {
 883          $apps = parent::_get_navbar_apps(common::svg_usable());    // use svg if usable in browser
 884          //$apps += $this->jdots_remote_apps();    currently not used/usable
 885  
 886          //Add its sidebox width to each app
 887          foreach ($apps as $app => &$data)
 888          {
 889              $data['sideboxwidth'] = self::get_sidebar_width($app);
 890              // overwrite icon with svg, if supported by browser
 891              unset($data['icon_hover']);    // not used in jdots
 892          }
 893  
 894          unset($apps['logout']);    // never display it
 895          if (isset($apps['about'])) $apps['about']['noNavbar'] = true;
 896          if (isset($apps['preferences'])) $apps['preferences']['noNavbar'] = true;
 897          if (isset($apps['manual'])) $apps['manual']['noNavbar'] = true;
 898          if (isset($apps['home'])) $apps['home']['noNavbar'] = true;
 899  
 900          // no need for website icon, if we have sitemgr
 901          if (isset($apps['sitemgr']) && isset($apps['sitemgr-link']))
 902          {
 903              unset($apps['sitemgr-link']);
 904          }
 905          return $apps;
 906      }
 907  
 908      /**
 909       * Prepare an array with apps used to render the navbar
 910       *
 911       * @param string $url contains the current url on the client side. It is used to
 912       *  determine whether the default app/home should be opened on the client
 913       *  or whether a specific application-url has been given.
 914       *
 915       * @return array of array(
 916       *  'name'  => app / directory name
 917       *     'title' => translated application title
 918       *  'url'   => url to call for index
 919       *  'icon'  => icon name
 920       *  'icon_app' => application of icon
 921       *  'icon_hover' => hover-icon, if used by template
 922       *  'target'=> ' target="..."' attribute fragment to open url in target, popup or ''
 923       *  'opened' => unset or false if the tab should not be opened, otherwise the numeric position in the tab list
 924       *  'active' => true if this tab should be the active one when it is restored, otherwise unset or false
 925       *  'openOnce' => unset or the url which will be opened when the tab is restored
 926       * )
 927       */
 928  	protected function get_navbar_apps($url)
 929      {
 930          $apps = $this->navbar_apps();
 931  
 932          // open tab for default app, if no other tab is set
 933          if (!($default_app = $GLOBALS['egw_info']['user']['preferences']['common']['default_app']))
 934          {
 935              $default_app = 'home';
 936          }
 937          if (isset($apps[$default_app]))
 938          {
 939              $apps[$default_app]['isDefault'] = true;
 940          }
 941  
 942          // check if user called a specific url --> open it as active tab
 943          $last_direct_url =& egw_cache::getSession(__CLASS__, 'last_direct_url');
 944          if ($url !== $last_direct_url)
 945          {
 946              $active_tab = $url_tab = self::app_from_url($url);
 947              $last_direct_url = $url;
 948          }
 949  
 950          //self::app_from_url might return an application the user has no rights
 951          //for or may return an application that simply does not exist. So check first
 952          //whether the $active_tab really exists in the $apps array.
 953          if ($active_tab && array_key_exists($active_tab, $apps))
 954          {
 955              // Do not remove cd=yes if it's an ajax=true app
 956              if (strpos( $apps[$active_tab]['url'],'ajax=true') !== False)
 957              {
 958                  $url = preg_replace('/[&?]cd=yes/','',$url);
 959              }
 960              $apps[$active_tab]['openOnce'] = $url;
 961              $store_prefs = true;
 962          }
 963          else
 964          {
 965              $active_tab = $GLOBALS['egw_info']['user']['preferences']['common']['active_tab'];
 966              if (!$active_tab) $active_tab = $default_app;
 967          }
 968          // if we have the open tabs in the session, use it instead the maybe forced common prefs open_tabs
 969          if (!($open_tabs = egw_cache::getSession(__CLASS__, 'open_tabs')))
 970          {
 971              $open_tabs = $GLOBALS['egw_info']['user']['preferences']['common']['open_tabs'];
 972          }
 973          $open_tabs = $open_tabs ? explode(',',$open_tabs) : array();
 974          if ($active_tab && !in_array($active_tab,$open_tabs))
 975          {
 976              $open_tabs[] = $active_tab;
 977              $store_prefs = true;
 978          }
 979          if ($store_prefs)
 980          {
 981              $GLOBALS['egw']->preferences->read_repository();
 982              $GLOBALS['egw']->preferences->change('common', 'open_tabs', implode(',',$open_tabs));
 983              $GLOBALS['egw']->preferences->change('common', 'active_tab', $active_tab);
 984              $GLOBALS['egw']->preferences->save_repository(true);
 985          }
 986  
 987          //error_log(__METHOD__."('$url') url_tab='$url_tab', active_tab=$active_tab, open_tabs=".array2string($open_tabs));
 988          // Restore Tabs
 989          foreach($open_tabs as $n => $app)
 990          {
 991              if (isset($apps[$app]))        // user might no longer have app rights
 992              {
 993                  $apps[$app]['opened'] = $n;
 994                  if ($app == $active_tab)
 995                  {
 996                      $apps[$app]['active'] = true;
 997                  }
 998              }
 999          }
1000          return array_values($apps);
1001      }
1002  
1003      /**
1004       * Have we output the footer
1005       *
1006       * @var boolean
1007       */
1008      static private $footer_done;
1009  
1010      /**
1011       * Returns the html from the closing div of the main application area to the closing html-tag
1012       *
1013       * @param boolean $no_framework = true
1014       * @return string
1015       */
1016  	function footer($no_framework=true)
1017      {
1018          //error_log(__METHOD__."($no_framework) footer_done=".array2string(self::$footer_done).' '.function_backtrace());
1019          if (self::$footer_done) return;    // prevent (multiple) footers
1020          self::$footer_done = true;
1021  
1022          if (!isset($GLOBALS['egw_info']['flags']['nofooter']) || !$GLOBALS['egw_info']['flags']['nofooter'])
1023          {
1024              if ($no_framework && $GLOBALS['egw_info']['user']['preferences']['common']['show_generation_time'])
1025              {
1026                  $vars = $this->_get_footer();
1027                  $footer = "\n".$vars['page_generation_time']."\n";
1028              }
1029          }
1030          return $footer.
1031              $GLOBALS['egw_info']['flags']['need_footer']."\n".    // eg. javascript, which need to be at the end of the page
1032              "</body>\n</html>\n";
1033      }
1034  
1035      /**
1036       * Return javascript (eg. for onClick) to open manual with given url
1037       *
1038       * @param string $url
1039       * @return string
1040       */
1041  	function open_manual_js($url)
1042      {
1043          return "callManual('$url')";
1044      }
1045  
1046      /**
1047       * JSON reponse object
1048       *
1049       * If set output is requested for an ajax response --> no header, navbar or footer
1050       *
1051       * @var egw_json_response
1052       */
1053      public $response;
1054  
1055      /**
1056       * Run a link via ajax, returning content via egw_json_response->data()
1057       *
1058       * This behavies like /index.php, but returns the content via json.
1059       *
1060       * @param string $link
1061       */
1062  	public static function ajax_exec($link)
1063      {
1064          $parts = parse_url($link);
1065          $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] = $parts['path'];
1066          if ($parts['query'])
1067          {
1068              $_SERVER['REQUEST_URI'] = '?'.$parts['query'];
1069              parse_str($parts['query'],$_GET);
1070          }
1071  
1072          if (!isset($_GET['menuaction']))
1073          {
1074              throw new egw_exception_wrong_parameter(__METHOD__."('$link') no menuaction set!");
1075          }
1076          list($app,$class,$method) = explode('.',$_GET['menuaction']);
1077  
1078          if (!isset($GLOBALS['egw_info']['user']['apps'][$app]))
1079          {
1080              throw new egw_exception_no_permission_app($app);
1081          }
1082          $GLOBALS['egw_info']['flags']['currentapp'] = $app;
1083  
1084          $GLOBALS['egw']->framework->response = egw_json_response::get();
1085  
1086          $GLOBALS[$class] = $obj = CreateObject($app.'.'.$class);
1087  
1088          if(!is_array($obj->public_functions) || !$obj->public_functions[$method])
1089          {
1090              throw new egw_exception_no_permission("Bad menuaction {$_GET['menuaction']}, not listed in public_functions!");
1091          }
1092          // dont send header and footer
1093          self::$header_done = self::$footer_done = true;
1094  
1095          // need to call do_sidebox, as header() with $header_done does NOT!
1096          $GLOBALS['egw']->framework->do_sidebox();
1097  
1098          // send preferences, so we dont need to request them in a second ajax request
1099          $GLOBALS['egw']->framework->response->call('egw.set_preferences',
1100              (array)$GLOBALS['egw_info']['user']['preferences'][$app], $app);
1101  
1102          // call application menuaction
1103          ob_start();
1104          $obj->$method();
1105          $output .= ob_get_contents();
1106          ob_end_clean();
1107  
1108          // add registered css and javascript to the response
1109          self::include_css_js_response();
1110  
1111          // add output if present
1112          if ($output)
1113          {
1114              $GLOBALS['egw']->framework->response->data($output);
1115          }
1116      }
1117  }

title

Description

title

Description

title

Description

title

title

Body