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 */ 13 14namespace EGroupware\Api; 15 16use EGroupware\Api\Header\ContentSecurityPolicy; 17 18/** 19 * Framework: virtual base class for all template sets 20 * 21 * This class creates / renders the eGW framework: 22 * a) Html header 23 * b) navbar 24 * c) sidebox menu 25 * d) main application area 26 * e) footer 27 * It replaces several methods in the common class and the diverse templates. 28 * 29 * Existing apps either set $GLOBALS['egw_info']['flags']['noheader'] and call common::egw_header() and 30 * (if $GLOBALS['egw_info']['flags']['nonavbar'] is true) parse_navbar() or it's done by the header.inc.php include. 31 * The app's hook_sidebox then calls the public function display_sidebox(). 32 * And the app calls common::egw_footer(). 33 * 34 * This are the authors (and their copyrights) of the original egw_header, egw_footer methods of the common class: 35 * This file written by Dan Kuykendall <seek3r@phpgroupware.org> 36 * and Joseph Engo <jengo@phpgroupware.org> 37 * and Mark Peters <skeeter@phpgroupware.org> 38 * and Lars Kneschke <lkneschke@linux-at-work.de> 39 * Copyright (C) 2000, 2001 Dan Kuykendall 40 * Copyright (C) 2003 Lars Kneschke 41 */ 42abstract class Framework extends Framework\Extra 43{ 44 /** 45 * Name of the template set, eg. 'idots' 46 * 47 * @var string 48 */ 49 var $template; 50 51 /** 52 * Path relative to EGW_SERVER_ROOT for the template directory 53 * 54 * @var string 55 */ 56 var $template_dir; 57 58 /** 59 * Application specific template directories to try in given order for CSS 60 * 61 * @var string 62 */ 63 var $template_dirs = array(); 64 65 /** 66 * true if $this->header() was called 67 * 68 * @var boolean 69 */ 70 static $header_done = false; 71 /** 72 * true if $this->navbar() was called 73 * 74 * @var boolean 75 */ 76 static $navbar_done = false; 77 78 /** 79 * Constructor 80 * 81 * The constructor instanciates the class in $GLOBALS['egw']->framework, from where it should be used 82 */ 83 function __construct($template) 84 { 85 $this->template = $template; 86 87 if (!isset($GLOBALS['egw']->framework)) 88 { 89 $GLOBALS['egw']->framework = $this; 90 } 91 $this->template_dir = '/api/templates/'.$template; 92 93 $this->template_dirs[] = $template; 94 $this->template_dirs[] = 'default'; 95 } 96 97 /** 98 * Factory method to instanciate framework object 99 * 100 * @return self 101 */ 102 public static function factory() 103 { 104 // we prefer Pixelegg template, if it is available 105 if (file_exists(EGW_SERVER_ROOT.'/pixelegg') && 106 (empty($GLOBALS['egw_info']['flags']['deny_mobile']) && Header\UserAgent::mobile() || 107 $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'mobile' || 108 empty($GLOBALS['egw_info']['server']['template_set'])) || 109 // change old idots and jerryr to our standard template (pixelegg) 110 in_array($GLOBALS['egw_info']['server']['template_set'], array('idots', 'jerryr'))) 111 { 112 $GLOBALS['egw_info']['server']['template_set'] = 'pixelegg'; 113 } 114 // then jdots aka Stylite template 115 if (file_exists(EGW_SERVER_ROOT.'/jdots') && empty($GLOBALS['egw_info']['server']['template_set'])) 116 { 117 $GLOBALS['egw_info']['server']['template_set'] = 'jdots'; 118 } 119 // eg. "default" is only used for login at the moment 120 if (!class_exists($class=$GLOBALS['egw_info']['server']['template_set'].'_framework')) 121 { 122 $class = __CLASS__.'\\Minimal'; 123 } 124 return new $class($GLOBALS['egw_info']['server']['template_set']); 125 } 126 127 /** 128 * Check if we have a valid and installed EGroupware template 129 * 130 * Templates are installed in their own directory and contain a setup/setup.inc.php file 131 * 132 * @param string $template 133 * @return boolean 134 */ 135 public static function validTemplate($template) 136 { 137 return preg_match('/^[A-Z0-9_-]+$/i', $template) && 138 file_exists(EGW_SERVER_ROOT.'/'.$template) && 139 file_exists($file=EGW_SERVER_ROOT.'/'.$template.'/setup/setup.inc.php') && 140 include_once($file) && !empty($GLOBALS['egw_info']['template'][$template]); 141 } 142 143 /** 144 * Send HTTP headers: Content-Type and Content-Security-Policy 145 */ 146 public function send_headers() 147 { 148 // add a content-type header to overwrite an existing default charset in apache (AddDefaultCharset directiv) 149 header('Content-type: text/html; charset='.Translation::charset()); 150 151 Header\ContentSecurityPolicy::send(); 152 153 // allow client-side to detect first load aka just logged in 154 $reload_count =& Cache::getSession(__CLASS__, 'framework-reload'); 155 self::$extra['framework-reload'] = (int)(bool)$reload_count++; 156 } 157 158 /** 159 * Constructor for static variables 160 */ 161 public static function init_static() 162 { 163 self::$js_include_mgr = new Framework\IncludeMgr(array( 164 // We need LABjs, but putting it through Framework\IncludeMgr causes it to re-load itself 165 //'/api/js/labjs/LAB.src.js', 166 167 // allways load jquery (not -ui) first 168 '/vendor/bower-asset/jquery/dist/jquery.js', 169 '/api/js/jquery/jquery.noconflict.js', 170 // always include javascript helper functions 171 '/api/js/jsapi/jsapi.js', 172 '/api/js/jsapi/egw.js', 173 )); 174 } 175 176 /** 177 * Link url generator 178 * 179 * @param string $url The url the link is for 180 * @param string|array $extravars Extra params to be passed to the url 181 * @param string $link_app =null if appname or true, some templates generate a special link-handler url 182 * @return string The full url after processing 183 */ 184 static function link($url, $extravars = '', $link_app=null) 185 { 186 unset($link_app); // not used by required by function signature 187 return $GLOBALS['egw']->session->link($url, $extravars); 188 } 189 190 /** 191 * Get a full / externally usable URL from an EGroupware link 192 * 193 * @param string $link 194 */ 195 static function getUrl($link) 196 { 197 return Header\Http::fullUrl($link); 198 } 199 200 /** 201 * Handles redirects under iis and apache, it does NOT return (calls exit) 202 * 203 * This function handles redirects under iis and apache it assumes that $phpgw->link() has already been called 204 * 205 * @param string $url url to redirect to 206 * @param string $link_app =null appname to redirect for, default currentapp 207 */ 208 static function redirect($url, $link_app=null) 209 { 210 // Determines whether the current output buffer should be flushed 211 $do_flush = true; 212 213 if (Json\Response::isJSONResponse() || Json\Request::isJSONRequest()) 214 { 215 Json\Response::get()->redirect($url, false, $link_app); 216 217 // check if we have a message, in which case send it along too 218 $extra = self::get_extra(); 219 if ($extra['message']) 220 { 221 Json\Response::get()->apply('egw.message', $extra['message']); 222 } 223 224 // If we are in a json request, we should not flush the current output! 225 $do_flush = false; 226 } 227 else 228 { 229 $file = $line = null; 230 if (headers_sent($file,$line)) 231 { 232 throw new Exception\AssertionFailed(__METHOD__."('".htmlspecialchars($url)."') can NOT redirect, output already started at $file line $line!"); 233 } 234 if ($GLOBALS['egw']->framework instanceof Framework\Ajax && !empty($link_app)) 235 { 236 self::set_extra('egw', 'redirect', array($url, $link_app)); 237 $GLOBALS['egw']->framework->render(''); 238 } 239 else 240 { 241 Header("Location: $url"); 242 print("\n\n"); 243 } 244 } 245 246 if ($do_flush) 247 { 248 @ob_flush(); flush(); 249 } 250 251 // commit session (if existing), to fix timing problems sometimes preventing session creation ("Your session can not be verified") 252 if (isset($GLOBALS['egw']->session)) $GLOBALS['egw']->session->commit_session(); 253 254 // run egw destructor now explicit, in case a (notification) email is send via Egw::on_shutdown(), 255 // as stream-wrappers used by Horde Smtp fail when PHP is already in destruction 256 $GLOBALS['egw']->__destruct(); 257 exit; 258 } 259 260 /** 261 * Redirects direct to a generated link 262 * 263 * @param string $url The url the link is for 264 * @param string|array $extravars Extra params to be passed to the url 265 * @param string $link_app =null if appname or true, some templates generate a special link-handler url 266 * @return string The full url after processing 267 */ 268 static function redirect_link($url, $extravars='', $link_app=null) 269 { 270 self::redirect(self::link($url, $extravars), $link_app); 271 } 272 273 /** 274 * Renders an applicaton page with the complete eGW framework (header, navigation and menu) 275 * 276 * This is the (new) prefered way to render a page in eGW! 277 * 278 * @param string $content Html of the main application area 279 * @param string $app_header =null application header, default what's set in $GLOBALS['egw_info']['flags']['app_header'] 280 * @param string $navbar =null show the navigation, default !$GLOBALS['egw_info']['flags']['nonavbar'], false gives a typical popu 281 * 282 */ 283 function render($content,$app_header=null,$navbar=null) 284 { 285 if (!is_null($app_header)) $GLOBALS['egw_info']['flags']['app_header'] = $app_header; 286 if (!is_null($navbar)) $GLOBALS['egw_info']['flags']['nonavbar'] = !$navbar; 287 288 echo $this->header(); 289 290 if (!isset($GLOBALS['egw_info']['flags']['nonavbar']) || !$GLOBALS['egw_info']['flags']['nonavbar']) 291 { 292 echo $this->navbar(); 293 } 294 echo $content; 295 296 echo $this->footer(); 297 } 298 299 /** 300 * Returns the html-header incl. the opening body tag 301 * 302 * @return string with Html 303 */ 304 abstract function header(array $extra=array()); 305 306 /** 307 * Returns the Html from the body-tag til the main application area (incl. opening div tag) 308 * 309 * If header has NOT been called, also return header content! 310 * No need to manually call header, this allows to postpone header so navbar / sidebox can include JS or CSS. 311 * 312 * @return string with Html 313 */ 314 abstract function navbar(); 315 316 /** 317 * Return true if we are rendering the top-level EGroupware window 318 * 319 * A top-level EGroupware window has a navbar: eg. no popup and for a framed template (jdots) only frameset itself 320 * 321 * @return boolean $consider_navbar_not_yet_called_as_true=true 322 * @return boolean 323 */ 324 abstract function isTop($consider_navbar_not_yet_called_as_true=true); 325 326 /** 327 * Returns the content of one sidebox 328 * 329 * @param string $appname 330 * @param string $menu_title 331 * @param array $file 332 * @param string $type =null 'admin', 'preferences', 'favorites', ... 333 */ 334 abstract function sidebox($appname,$menu_title,$file,$type=null); 335 336 /** 337 * Returns the Html from the closing div of the main application area to the closing html-tag 338 * 339 * @return string 340 */ 341 abstract function footer(); 342 343 /** 344 * Displays the login screen 345 * 346 * @param string $extra_vars for login url 347 * @param string $change_passwd =null string with message to render input fields for password change 348 */ 349 function login_screen($extra_vars, $change_passwd=null) 350 { 351 (new Framework\Login($this))->screen($extra_vars, $change_passwd); 352 } 353 354 /** 355 * displays a login denied message 356 */ 357 function denylogin_screen() 358 { 359 (new Framework\Login($this))->deny_screen(); 360 } 361 362 /** 363 * Calculate page-generation- and session-restore times 364 * 365 * @return array values for keys 'page_generation_time' and 'session_restore_time', if display is an 366 */ 367 public static function get_page_generation_time() 368 { 369 $times = array( 370 'page_generation_time' => sprintf('%4.2lf', microtime(true) - $GLOBALS['egw_info']['flags']['page_start_time']), 371 ); 372 if ($GLOBALS['egw_info']['flags']['session_restore_time']) 373 { 374 $times['session_restore_time'] = sprintf('%4.2lf', $GLOBALS['egw_info']['flags']['session_restore_time']); 375 } 376 return $times; 377 } 378 379 /** 380 * Get footer as array to eg. set as vars for a template (from idots' head.inc.php) 381 * 382 * @return array 383 */ 384 public function _get_footer() 385 { 386 $var = Array( 387 'img_root' => $GLOBALS['egw_info']['server']['webserver_url'] . $this->template_dir.'/images', 388 'version' => $GLOBALS['egw_info']['server']['versions']['api'] 389 ); 390 $var['page_generation_time'] = ''; 391 if($GLOBALS['egw_info']['user']['preferences']['common']['show_generation_time']) 392 { 393 $times = self::get_page_generation_time(); 394 395 $var['page_generation_time'] = '<div class="pageGenTime" id="divGenTime_'.$GLOBALS['egw_info']['flags']['currentapp'].'"><span>'. 396 lang('Page was generated in %1 seconds', $times['page_generation_time']); 397 398 if (isset($times['session_restore_time'])) 399 { 400 $var['page_generation_time'] .= ' '.lang('(session restored in %1 seconds)', 401 $times['session_restore_time']); 402 } 403 $var['page_generation_time'] .= '</span></div>'; 404 } 405 if (empty($GLOBALS['egw_info']['server']['versions']['maintenance_release'])) 406 { 407 $GLOBALS['egw_info']['server']['versions']['maintenance_release'] = self::api_version(); 408 } 409 $var['powered_by'] = '<a href="http://www.egroupware.org/" class="powered_by" target="_blank">'. 410 lang('Powered by').' EGroupware '. 411 $GLOBALS['egw_info']['server']['versions']['maintenance_release'].'</a>'; 412 413 return $var; 414 } 415 416 /** 417 * Body tags for onLoad, onUnload and onResize 418 * 419 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!) 420 * @var array 421 */ 422 protected static $body_tags = array(); 423 424 /** 425 * Adds on(Un)Load= attributes to the body tag of a page 426 * 427 * Can only be set via egw_framework::set_on* methods. 428 * 429 * @deprecated since 14.1 use app.js et2_ready method instead to execute code or bind a handler (CSP will stop onXXX attributes!) 430 * @returns string the attributes to be used 431 */ 432 static public function _get_body_attribs() 433 { 434 $js = ''; 435 foreach(self::$body_tags as $what => $data) 436 { 437 if (!empty($data)) 438 { 439 if($what == 'onLoad') 440 { 441 $js .= 'onLoad="egw_LAB.wait(function() {'. htmlspecialchars($data).'})"'; 442 continue; 443 } 444 $js .= ' '.$what.'="' . htmlspecialchars($data) . '"'; 445 } 446 } 447 return $js; 448 } 449 450 protected static $body_classes = []; 451 452 /** 453 * Set a CSS class on the body tag 454 * 455 * @param string $class =null 456 * @return array with all currently set css classes 457 */ 458 public static function bodyClass($class=null) 459 { 460 if (!empty($class)) 461 { 462 self::$body_classes[] = $class; 463 } 464 return self::$body_classes; 465 } 466 467 /** 468 * Get class attribute for body tag 469 * 470 * @return string 471 */ 472 protected static function bodyClassAttribute() 473 { 474 return self::$body_classes ? ' class="'.htmlspecialchars(implode(' ', self::$body_classes)).'"' : ''; 475 } 476 477 /** 478 * Get header as array to eg. set as vars for a template (from idots' head.inc.php) 479 * 480 * @param array $extra =array() extra attributes passed as data-attribute to egw.js 481 * @return array 482 */ 483 protected function _get_header(array $extra=array()) 484 { 485 // display password expires in N days message once per session 486 $message = null; 487 if ($GLOBALS['egw_info']['flags']['currentapp'] != 'login' && 488 Auth::check_password_change($message) !== true) 489 { 490 self::message($message, 'info'); 491 } 492 493 // get used language code (with a little xss check, if someone tries to sneak something in) 494 if (preg_match('/^[a-z]{2}(-[a-z]{2})?$/',$GLOBALS['egw_info']['user']['preferences']['common']['lang'])) 495 { 496 $lang_code = $GLOBALS['egw_info']['user']['preferences']['common']['lang']; 497 } 498 // IE specific fixes 499 if (Header\UserAgent::type() == 'msie') 500 { 501 // tell IE to use it's own mode, not old compatibility modes (set eg. via group policy for all intranet sites) 502 // has to be before any other header tags, but meta and title!!! 503 $pngfix = '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'."\n"; 504 } 505 506 $app = $GLOBALS['egw_info']['flags']['currentapp']; 507 $app_title = isset($GLOBALS['egw_info']['apps'][$app]) ? $GLOBALS['egw_info']['apps'][$app]['title'] : lang($app); 508 $app_header = $GLOBALS['egw_info']['flags']['app_header'] ? $GLOBALS['egw_info']['flags']['app_header'] : $app_title; 509 $site_title = strip_tags($GLOBALS['egw_info']['server']['site_title'].' ['.($app_header ? $app_header : $app_title).']'); 510 511 // send appheader to clientside 512 $extra['app-header'] = $app_header; 513 514 if($GLOBALS['egw_info']['flags']['currentapp'] != 'wiki') $robots ='<meta name="robots" content="none" />'; 515 516 $var['favicon_file'] = self::get_login_logo_or_bg_url('favicon_file', 'favicon.ico'); 517 518 if ($GLOBALS['egw_info']['flags']['include_wz_tooltip'] && 519 file_exists(EGW_SERVER_ROOT.($wz_tooltip = '/phpgwapi/js/wz_tooltip/wz_tooltip.js'))) 520 { 521 $include_wz_tooltip = '<script src="'.$GLOBALS['egw_info']['server']['webserver_url']. 522 $wz_tooltip.'?'.filemtime(EGW_SERVER_ROOT.$wz_tooltip).'" type="text/javascript"></script>'; 523 } 524 return $this->_get_css()+array( 525 'img_icon' => $var['favicon_file'], 526 'img_shortcut' => $var['favicon_file'], 527 'pngfix' => $pngfix, 528 'lang_code' => $lang_code, 529 'charset' => Translation::charset(), 530 'website_title' => $site_title, 531 'body_tags' => self::_get_body_attribs().self::bodyClassAttribute(), 532 'java_script' => self::_get_js($extra), 533 'meta_robots' => $robots, 534 'dir_code' => lang('language_direction_rtl') != 'rtl' ? '' : ' dir="rtl"', 535 'include_wz_tooltip'=> $include_wz_tooltip, 536 'webserver_url' => $GLOBALS['egw_info']['server']['webserver_url'], 537 ); 538 } 539 540 /** 541 * Get navbar as array to eg. set as vars for a template (from idots' navbar.inc.php) 542 * 543 * @param array $apps navbar apps from _get_navbar_apps 544 * @return array 545 */ 546 protected function _get_navbar($apps) 547 { 548 $var['img_root'] = $GLOBALS['egw_info']['server']['webserver_url'] . '/phpgwapi/templates/'.$this->template.'/images'; 549 550 if(isset($GLOBALS['egw_info']['flags']['app_header'])) 551 { 552 $var['current_app_title'] = $GLOBALS['egw_info']['flags']['app_header']; 553 } 554 else 555 { 556 $var['current_app_title']=$apps[$GLOBALS['egw_info']['flags']['currentapp']]['title']; 557 } 558 $var['currentapp'] = $GLOBALS['egw_info']['flags']['currentapp']; 559 560 // quick add selectbox 561 $var['quick_add'] = $this->_get_quick_add(); 562 563 $var['user_info'] = $this->_user_time_info(); 564 565 if($GLOBALS['egw_info']['user']['account_lastpwd_change'] == 0) 566 { 567 $api_messages = lang('You are required to change your password during your first login').'<br />'. 568 lang('Click this image on the navbar: %1','<img src="'.Image::find('preferences','navbar.gif').'">'); 569 } 570 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'])) 571 { 572 $api_messages = lang('it has been more then %1 days since you changed your password',$GLOBALS['egw_info']['server']['change_pwd_every_x_days']); 573 } 574 575 $var['logo_header'] = $var['logo_file'] = self::get_login_logo_or_bg_url('login_logo_file', 'logo'); 576 577 if ($GLOBALS['egw_info']['server']['login_logo_header']) 578 { 579 $var['logo_header'] = self::get_login_logo_or_bg_url('login_logo_header', 'logo'); 580 } 581 582 $var['logo_url'] = $GLOBALS['egw_info']['server']['login_logo_url']?$GLOBALS['egw_info']['server']['login_logo_url']:'http://www.egroupware.org'; 583 584 if (substr($var['logo_url'],0,4) != 'http') 585 { 586 $var['logo_url'] = 'http://'.$var['logo_url']; 587 } 588 $var['logo_title'] = $GLOBALS['egw_info']['server']['login_logo_title']?$GLOBALS['egw_info']['server']['login_logo_title']:'www.egroupware.org'; 589 590 return $var; 591 } 592 593 /** 594 * Get login logo or background image base on requested config type 595 * 596 * @param type $type config type to fetch. e.g.: "login_logo_file" 597 * @param type $find_type type of image to search on as alternative option. e.g.: "logo" 598 * 599 * @return string returns full url of the image 600 */ 601 static function get_login_logo_or_bg_url ($type, $find_type) 602 { 603 $url = is_array($GLOBALS['egw_info']['server'][$type]) ? 604 $GLOBALS['egw_info']['server'][$type][0] : 605 $GLOBALS['egw_info']['server'][$type]; 606 607 if (substr($url, 0, 4) == 'http' || 608 $url[0] == '/') 609 { 610 return $url; 611 } 612 else 613 { 614 return Image::find('api',$url ? $url : $find_type, '', null); 615 } 616 } 617 618 /** 619 * Returns Html with user and time 620 * 621 * @return void 622 */ 623 protected static function _user_time_info() 624 { 625 $now = new DateTime(); 626 $user_info = '<span>'.lang($now->format('l')) . ' ' . $now->format(true).'</span>'; 627 628 $user_tzs = DateTime::getUserTimezones(); 629 if (count($user_tzs) > 1) 630 { 631 $tz = $GLOBALS['egw_info']['user']['preferences']['common']['tz']; 632 $user_info .= Html::form(Html::select('tz',$tz,$user_tzs,true),array(), 633 '/index.php','','tz_selection',' style="display: inline;"','GET'); 634 } 635 return $user_info; 636 } 637 638 /** 639 * Returns user avatar menu 640 * 641 * @return string 642 */ 643 protected static function _user_avatar_menu() 644 { 645 $stat = array_pop(Hooks::process('framework_avatar_stat')); 646 647 return '<span title="'.Accounts::format_username().'" class="avatar"><img src="'.Egw::link('/api/avatar.php', array( 648 'account_id' => $GLOBALS['egw_info']['user']['account_id'], 649 )).'"/>'.(!empty($stat) ? 650 '<span class="fw_avatar_stat '.$stat['class'].'" title="'.$stat['title'].'">'.$stat['body'].'</span>' : '').'</span>'; 651 } 652 653 /** 654 * Returns logout menu 655 * 656 * @return string 657 */ 658 protected static function _logout_menu() 659 { 660 return '<a href="'.Egw::link('/logout.php').'" title="'.lang("Logout").'" ></a>'; 661 } 662 663 /** 664 * Returns print menu 665 * 666 * @return string 667 */ 668 protected static function _print_menu() 669 { 670 return '<span title="'.lang("Print current view").'"</span>'; 671 } 672 673 674 /** 675 * Prepare the current users 676 * 677 * @return array 678 */ 679 protected static function _current_users() 680 { 681 if( $GLOBALS['egw_info']['user']['apps']['admin'] && $GLOBALS['egw_info']['user']['preferences']['common']['show_currentusers']) 682 { 683 return [ 684 'name' => 'current_user', 685 'title' => lang('Current users').':'.$GLOBALS['egw']->session->session_count(), 686 'url' => self::link('/index.php','menuaction=admin.admin_accesslog.sessions&ajax=true') 687 ]; 688 } 689 } 690 691 /** 692 * Prepare the quick add selectbox 693 * 694 * @return string 695 */ 696 protected static function _get_quick_add() 697 { 698 return '<span id="quick_add" title="'.lang('Quick add').'"></span>'; 699 } 700 701 /** 702 * Prepare notification signal (blinking bell) 703 * 704 * @return string 705 */ 706 protected static function _get_notification_bell() 707 { 708 return Html::image('notifications', 'notificationbell', lang('notifications'), 709 'id="notificationbell" style="display: none"'); 710 } 711 712 /** 713 * Get context to use with file_get_context or fopen to use our proxy settings from setup 714 * 715 * @param string $username =null username for regular basic Auth 716 * @param string $password =null password --------- " ---------- 717 * @param array $opts =array() further params for http(s) context, eg. array('timeout' => 123) 718 * @return resource|null context to use with file_get_context/fopen or null if no proxy configured 719 */ 720 public static function proxy_context($username=null, $password=null, array $opts = array()) 721 { 722 $opts += array( 723 'method' => 'GET', 724 ); 725 if (!empty($GLOBALS['egw_info']['server']['httpproxy_server'])) 726 { 727 $opts += array ( 728 'proxy' => 'tcp://'.$GLOBALS['egw_info']['server']['httpproxy_server'].':'. 729 ($GLOBALS['egw_info']['server']['httpproxy_port'] ? $GLOBALS['egw_info']['server']['httpproxy_port'] : 8080), 730 'request_fulluri' => true, 731 ); 732 // proxy authentication 733 if (!empty($GLOBALS['egw_info']['server']['httpproxy_server_username'])) 734 { 735 $opts['header'][] = 'Proxy-Authorization: Basic '.base64_encode($GLOBALS['egw_info']['server']['httpproxy_server_username'].':'. 736 $GLOBALS['egw_info']['server']['httpproxy_server_password']); 737 } 738 } 739 // optional authentication 740 if (isset($username)) 741 { 742 $opts['header'][] = 'Authorization: Basic '.base64_encode($username.':'.$password); 743 } 744 return stream_context_create(array( 745 'http' => $opts, 746 'https' => $opts, 747 )); 748 } 749 750 /** 751 * Get API version from changelog or database, whichever is bigger 752 * 753 * @param string &$changelog on return path to changelog 754 * @return string 755 */ 756 public static function api_version(&$changelog=null) 757 { 758 return Framework\Updates::api_version($changelog); 759 } 760 761 /** 762 * Get the link to an application's index page 763 * 764 * @param string $app 765 * @return string 766 */ 767 public static function index($app) 768 { 769 $data =& $GLOBALS['egw_info']['user']['apps'][$app]; 770 if (!isset($data)) 771 { 772 throw new Exception\WrongParameter("'$app' not a valid app for this user!"); 773 } 774 $index = '/'.$app.'/index.php'; 775 if (isset($data['index'])) 776 { 777 if (preg_match('|^https?://|', $data['index'])) 778 { 779 return $data['index']; 780 } 781 if ($data['index'][0] == '/') 782 { 783 $index = $data['index']; 784 } 785 else 786 { 787 $index = '/index.php?menuaction='.$data['index']; 788 } 789 } 790 return self::link($index,$GLOBALS['egw_info']['flags']['params'][$app]); 791 } 792 793 /** 794 * Used internally to store unserialized value of $GLOBALS['egw_info']['user']['preferences']['common']['user_apporder'] 795 */ 796 private static $user_apporder = array(); 797 798 /** 799 * Internal usort callback function used to sort an array according to the 800 * user sort order 801 */ 802 private static function _sort_apparray($a, $b) 803 { 804 //Unserialize the user_apporder array 805 $arr = self::$user_apporder; 806 807 $ind_a = isset($arr[$a['name']]) ? $arr[$a['name']] : null; 808 $ind_b = isset($arr[$b['name']]) ? $arr[$b['name']] : null; 809 810 if ($ind_a == $ind_b) 811 return 0; 812 813 if ($ind_a == null) 814 return -1; 815 816 if ($ind_b == null) 817 return 1; 818 819 return $ind_a > $ind_b ? 1 : -1; 820 } 821 822 /** 823 * Prepare an array with apps used to render the navbar 824 * 825 * This is similar to the former common::navbar() method - though it returns the vars and does not place them in global scope. 826 * 827 * @return array 828 */ 829 protected static function _get_navbar_apps() 830 { 831 $first = key($GLOBALS['egw_info']['user']['apps']); 832 if(is_array($GLOBALS['egw_info']['user']['apps']['admin']) && $first != 'admin') 833 { 834 $newarray['admin'] = $GLOBALS['egw_info']['user']['apps']['admin']; 835 foreach($GLOBALS['egw_info']['user']['apps'] as $index => $value) 836 { 837 if($index != 'admin') 838 { 839 $newarray[$index] = $value; 840 } 841 } 842 $GLOBALS['egw_info']['user']['apps'] = $newarray; 843 reset($GLOBALS['egw_info']['user']['apps']); 844 } 845 unset($index); 846 unset($value); 847 unset($newarray); 848 849 $apps = array(); 850 foreach($GLOBALS['egw_info']['user']['apps'] as $app => $data) 851 { 852 if (is_long($app)) 853 { 854 continue; 855 } 856 857 if ($app == 'preferences' || $GLOBALS['egw_info']['apps'][$app]['status'] != 2 && $GLOBALS['egw_info']['apps'][$app]['status'] != 3) 858 { 859 $apps[$app]['title'] = $GLOBALS['egw_info']['apps'][$app]['title']; 860 $apps[$app]['url'] = self::index($app); 861 $apps[$app]['name'] = $app; 862 863 // create popup target 864 if ($data['status'] == 4) 865 { 866 $apps[$app]['target'] = ' target="'.$app.'" onClick="'."if (this != '') { window.open(this+'". 867 (strpos($apps[$app]['url'],'?') !== false ? '&' : '?'). 868 "referer='+encodeURIComponent(location),this.target,'width=800,height=600,scrollbars=yes,resizable=yes'); return false; } else { return true; }".'"'; 869 } 870 elseif(isset($GLOBALS['egw_info']['flags']['navbar_target']) && $GLOBALS['egw_info']['flags']['navbar_target']) 871 { 872 $apps[$app]['target'] = 'target="' . $GLOBALS['egw_info']['flags']['navbar_target'] . '"'; 873 } 874 else 875 { 876 $apps[$app]['target'] = ''; 877 } 878 879 // take status flag into account as we might use it on client-side. 880 // for instance: applications with status 5 will run in background 881 $apps[$app]['status'] = $data['status']; 882 883 if (!empty($data['icon']) && preg_match('#^(https?://|/)#', $data['icon'])) 884 { 885 $icon_url = $data['icon']; 886 } 887 else 888 { 889 $icon = isset($data['icon']) ? $data['icon'] : 'navbar'; 890 $icon_app = isset($data['icon_app']) ? $data['icon_app'] : $app; 891 $icon_url = Image::find($icon_app,Array($icon,'nonav'),''); 892 } 893 $apps[$app]['icon'] = $apps[$app]['icon_hover'] = $icon_url; 894 } 895 } 896 897 //Sort the applications accordingly to their user sort setting 898 if ($GLOBALS['egw_info']['user']['preferences']['common']['user_apporder']) 899 { 900 //Sort the application array using the user_apporder array as sort index 901 self::$user_apporder = 902 unserialize($GLOBALS['egw_info']['user']['preferences']['common']['user_apporder']); 903 uasort($apps, __CLASS__.'::_sort_apparray'); 904 } 905 906 if ($GLOBALS['egw_info']['flags']['currentapp'] == 'preferences' || $GLOBALS['egw_info']['flags']['currentapp'] == 'about') 907 { 908 $app = $app_title = 'EGroupware'; 909 } 910 else 911 { 912 $app = $GLOBALS['egw_info']['flags']['currentapp']; 913 $app_title = $GLOBALS['egw_info']['apps'][$app]['title']; 914 } 915 916 if ($GLOBALS['egw_info']['user']['apps']['preferences']) // Preferences last 917 { 918 $prefs = $apps['preferences']; 919 unset($apps['preferences']); 920 $apps['preferences'] = $prefs; 921 } 922 923 // We handle this here because its special 924 $apps['about']['title'] = 'EGroupware'; 925 $apps['about']['url'] = self::link('/about.php'); 926 $apps['about']['icon'] = $apps['about']['icon_hover'] = Image::find('api',Array('about','nonav')); 927 $apps['about']['name'] = 'about'; 928 929 $apps['logout']['title'] = lang('Logout'); 930 $apps['logout']['name'] = 'logout'; 931 $apps['logout']['url'] = self::link('/logout.php'); 932 $apps['logout']['icon'] = $apps['logout']['icon_hover'] = Image::find('api',Array('logout','nonav')); 933 934 return $apps; 935 } 936 937 /** 938 * Used by template headers for including CSS in the header 939 * 940 * 'app_css' - css styles from a) the menuaction's css-method and b) the $GLOBALS['egw_info']['flags']['css'] 941 * 'file_css' - link tag of the app.css file of the current app 942 * 'theme_css' - url of the theme css file 943 * 'print_css' - url of the print css file 944 * 945 * @author Dave Hall (*based* on verdilak? css inclusion code) 946 * @return array with keys 'app_css' from the css method of the menuaction-class and 'file_css' (app.css file of the application) 947 */ 948 public function _get_css() 949 { 950 $app_css = ''; 951 if (isset($GLOBALS['egw_info']['flags']['css'])) 952 { 953 $app_css = $GLOBALS['egw_info']['flags']['css']; 954 } 955 956 if (self::$load_default_css) 957 { 958 // For mobile user-agent we prefer mobile theme over selected one with a final fallback to theme named as template 959 $themes_to_check = array(); 960 if (Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'fw_mobile') 961 { 962 $themes_to_check[] = $this->template_dir.'/mobile/'.$GLOBALS['egw_info']['user']['preferences']['common']['theme'].'.css'; 963 $themes_to_check[] = $this->template_dir.'/mobile/fw_mobile.css'; 964 } 965 $themes_to_check[] = $this->template_dir.'/css/'.$GLOBALS['egw_info']['user']['preferences']['common']['theme'].'.css'; 966 $themes_to_check[] = $this->template_dir.'/css/'.$this->template.'.css'; 967 foreach($themes_to_check as $theme_css) 968 { 969 if (file_exists(EGW_SERVER_ROOT.$theme_css)) break; 970 } 971 $debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True'; 972 if (!$debug_minify && file_exists(EGW_SERVER_ROOT.($theme_min_css = str_replace('.css', '.min.css', $theme_css)))) 973 { 974 //error_log(__METHOD__."() Framework\CssIncludes::get()=".array2string(Framework\CssIncludes::get())); 975 self::includeCSS($theme_min_css); 976 977 // Global category styles 978 if (basename($_SERVER['PHP_SELF']) !== 'login.php') 979 { 980 Categories::css(Categories::GLOBAL_APPNAME); 981 } 982 } 983 else 984 { 985 // Load these first 986 // Cascade should go: 987 // Libs < etemplate2 < framework/theme < app < print 988 // Enhanced selectboxes (et1) 989 self::includeCSS('/api/js/jquery/chosen/chosen.css'); 990 991 // eTemplate2 uses jQueryUI, so load it first so et2 can override if needed 992 self::includeCSS("/vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css"); 993 994 // eTemplate2 - load in top so sidebox has styles too 995 self::includeCSS('/api/templates/default/etemplate2.css'); 996 997 // Category styles 998 if (basename($_SERVER['PHP_SELF']) !== 'login.php') 999 { 1000 Categories::css(Categories::GLOBAL_APPNAME); 1001 } 1002 1003 self::includeCSS($theme_css); 1004 1005 // sending print css last, so it can overwrite anything 1006 $print_css = $this->template_dir.'/print.css'; 1007 if(!file_exists(EGW_SERVER_ROOT.$print_css)) 1008 { 1009 $print_css = '/api/templates/default/print.css'; 1010 } 1011 self::includeCSS($print_css); 1012 } 1013 // search for app specific css file, so it can customize the theme 1014 self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app-'.$GLOBALS['egw_info']['user']['preferences']['common']['theme']) || 1015 self::includeCSS($GLOBALS['egw_info']['flags']['currentapp'], 'app'); 1016 } 1017 return array( 1018 'app_css' => $app_css, 1019 'css_file' => Framework\CssIncludes::tags(), 1020 ); 1021 } 1022 1023 /** 1024 * Used by the template headers for including javascript in the header 1025 * 1026 * The method is included here to make it easier to change the js support 1027 * in eGW. One change then all templates will support it (as long as they 1028 * include a call to this method). 1029 * 1030 * @param array $extra =array() extra data to pass to egw.js as data-parameter 1031 * @return string the javascript to be included 1032 */ 1033 public static function _get_js(array $extra=array()) 1034 { 1035 $java_script = ''; 1036 1037 /* this flag is for all javascript code that has to be put before other jscode. 1038 Think of conf vars etc... (pim@lingewoud.nl) */ 1039 if (isset($GLOBALS['egw_info']['flags']['java_script_thirst'])) 1040 { 1041 $java_script .= $GLOBALS['egw_info']['flags']['java_script_thirst'] . "\n"; 1042 } 1043 // add configuration, link-registry, images, user-data and -perferences for non-popup windows 1044 // specifying etag in url to force reload, as we send expires header 1045 if ($GLOBALS['egw_info']['flags']['js_link_registry'] || isset($_GET['cd']) && $_GET['cd'] === 'popup') 1046 { 1047 self::includeJS('/api/config.php', array( 1048 'etag' => md5(json_encode(Config::clientConfigs()).Link::json_registry()), 1049 )); 1050 self::includeJS('/api/images.php', array( 1051 'template' => $GLOBALS['egw_info']['server']['template_set'], 1052 'etag' => md5(json_encode(Image::map($GLOBALS['egw_info']['server']['template_set']))) 1053 )); 1054 self::includeJS('/api/user.php', array( 1055 'user' => $GLOBALS['egw_info']['user']['account_lid'], 1056 'lang' => $GLOBALS['egw_info']['user']['preferences']['common']['lang'], 1057 // add etag on url, so we can set an expires header 1058 'etag' => md5(json_encode($GLOBALS['egw_info']['user']['preferences']['common']). 1059 $GLOBALS['egw']->accounts->json($GLOBALS['egw_info']['user']['account_id'])), 1060 )); 1061 } 1062 1063 $extra['url'] = $GLOBALS['egw_info']['server']['webserver_url']; 1064 $extra['include'] = array_map(function($str){return substr($str,1);}, self::get_script_links(true), array(1)); 1065 $extra['app'] = $GLOBALS['egw_info']['flags']['currentapp']; 1066 1067 // Load LABjs ONCE here 1068 $java_script .= '<script type="text/javascript" src="'.$GLOBALS['egw_info']['server']['webserver_url']. 1069 '/api/js/labjs/LAB.src.js?'.filemtime(EGW_SERVER_ROOT.'/api/js/labjs/LAB.src.js')."\"></script>\n". 1070 '<script type="text/javascript" src="'.$GLOBALS['egw_info']['server']['webserver_url']. 1071 '/api/js/jsapi/egw.js?'.filemtime(EGW_SERVER_ROOT.'/api/js/jsapi/egw.js').'" id="egw_script_id"'; 1072 1073 // add values of extra parameter and class var as data attributes to script tag of egw.js 1074 foreach($extra+self::$extra as $name => $value) 1075 { 1076 if (is_array($value)) $value = json_encode($value); 1077 // we need to double encode (Html::htmlspecialchars( , TRUE)), as otherwise we get invalid json, eg. for quotes 1078 $java_script .= ' data-'.$name."=\"". Html::htmlspecialchars($value, true)."\""; 1079 } 1080 $java_script .= "></script>\n"; 1081 1082 if(@isset($_GET['menuaction'])) 1083 { 1084 list(, $class) = explode('.',$_GET['menuaction']); 1085 if(is_array($GLOBALS[$class]->public_functions) && 1086 $GLOBALS[$class]->public_functions['java_script']) 1087 { 1088 $java_script .= $GLOBALS[$class]->java_script(); 1089 } 1090 } 1091 if (isset($GLOBALS['egw_info']['flags']['java_script'])) 1092 { 1093 // Strip out any script tags, this needs to be executed as anonymous function 1094 $GLOBALS['egw_info']['flags']['java_script'] = preg_replace(array('/(<script[^>]*>)([^<]*)/is','/<\/script>/'),array('$2',''),$GLOBALS['egw_info']['flags']['java_script']); 1095 if(trim($GLOBALS['egw_info']['flags']['java_script']) != '') 1096 { 1097 $java_script .= '<script type="text/javascript">window.egw_LAB.wait(function() {'.$GLOBALS['egw_info']['flags']['java_script'] . "});</script>\n"; 1098 } 1099 } 1100 1101 return $java_script; 1102 } 1103 1104 /** 1105 * List available themes 1106 * 1107 * Themes are css file in the template directory 1108 * 1109 * @param string $themes_dir ='css' 1110 */ 1111 function list_themes() 1112 { 1113 $list = array(); 1114 if (file_exists($file=EGW_SERVER_ROOT.$this->template_dir.'/setup/setup.inc.php') && 1115 (include $file) && isset($GLOBALS['egw_info']['template'][$this->template]['themes'])) 1116 { 1117 $list = $GLOBALS['egw_info']['template'][$this->template]['themes']; 1118 } 1119 if (($dh = @opendir(EGW_SERVER_ROOT.$this->template_dir.'/css'))) 1120 { 1121 while (($file = readdir($dh))) 1122 { 1123 if (preg_match('/'."\.css$".'/i', $file)) 1124 { 1125 list($name) = explode('.',$file); 1126 if (!isset($list[$name])) $list[$name] = ucfirst ($name); 1127 } 1128 } 1129 closedir($dh); 1130 } 1131 return $list; 1132 } 1133 1134 /** 1135 * List available templates 1136 * 1137 * @param boolean $full_data =false true: value is array with values for keys 'name', 'title', ... 1138 * @returns array alphabetically sorted list of templates 1139 */ 1140 static function list_templates($full_data=false) 1141 { 1142 $list = array('pixelegg'=>null); 1143 // templates packaged like apps in own directories (containing as setup/setup.inc.php file!) 1144 $dr = dir(EGW_SERVER_ROOT); 1145 while (($entry=$dr->read())) 1146 { 1147 if ($entry != '..' && !isset($GLOBALS['egw_info']['apps'][$entry]) && is_dir(EGW_SERVER_ROOT.'/'.$entry) && 1148 file_exists($f = EGW_SERVER_ROOT . '/' . $entry .'/setup/setup.inc.php')) 1149 { 1150 include($f); 1151 if (isset($GLOBALS['egw_info']['template'][$entry])) 1152 { 1153 $list[$entry] = $full_data ? $GLOBALS['egw_info']['template'][$entry] : 1154 $GLOBALS['egw_info']['template'][$entry]['title']; 1155 } 1156 } 1157 } 1158 $dr->close(); 1159 1160 return array_filter($list); 1161 } 1162 1163 /** 1164 * Compile entries for topmenu: 1165 * - regular items: links 1166 * - info items 1167 * 1168 * @param array $vars 1169 * @param array $apps 1170 */ 1171 function topmenu(array $vars,array $apps) 1172 { 1173 // array of topmenu info items (orders of the items matter) 1174 $topmenu_info_items = [ 1175 'user_avatar' => $this->_user_avatar_menu(), 1176 'logout' => (Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'fw_mobile') ? self::_logout_menu() : null, 1177 'update' => ($update = Framework\Updates::notification()) ? $update : null, 1178 'notifications' => ($GLOBALS['egw_info']['user']['apps']['notifications']) ? self::_get_notification_bell() : null, 1179 'quick_add' => $vars['quick_add'], 1180 'print_title' => $this->_print_menu() 1181 ]; 1182 1183 // array of topmenu items (orders of the items matter) 1184 $topmenu_items = [ 1185 0 => (is_array(($current_user = $this->_current_users()))) ? $current_user : null, 1186 ]; 1187 1188 // Home should be at the top before preferences 1189 if($GLOBALS['egw_info']['user']['apps']['home'] && isset($apps['home'])) 1190 { 1191 $this->_add_topmenu_item($apps['home']); 1192 } 1193 1194 // array of topmenu preferences items (orders of the items matter) 1195 $topmenu_preferences = ['prefs', 'acl', 'cats', 'security']; 1196 1197 // set topmenu preferences items 1198 if($GLOBALS['egw_info']['user']['apps']['preferences']) 1199 { 1200 foreach ($topmenu_preferences as $prefs) 1201 { 1202 $this->add_preferences_topmenu($prefs); 1203 } 1204 } 1205 1206 // call topmenu info items hooks 1207 Hooks::process('topmenu_info',array(),true); 1208 1209 // Add extra items added by hooks 1210 foreach(self::$top_menu_extra as $extra_item) { 1211 if ($extra_item['name'] == 'search') 1212 { 1213 $topmenu_info_items['search'] = '<a href="'.$extra_item['url'].'" title="'.$extra_item['title'].'"></a>'; 1214 } 1215 else 1216 { 1217 array_push($topmenu_items, $extra_item); 1218 } 1219 } 1220 // push logout as the last item in topmenu items list 1221 array_push($topmenu_items, $apps['logout']); 1222 1223 // set topmenu info items 1224 foreach ($topmenu_info_items as $id => $content) 1225 { 1226 if (!$content || (in_array($id, ['search', 'quick_add', 'update']) && (Header\UserAgent::mobile() || $GLOBALS['egw_info']['user']['preferences']['common']['theme'] == 'fw_mobile'))) 1227 { 1228 continue; 1229 } 1230 $this->_add_topmenu_info_item($content, $id); 1231 } 1232 // set topmenu items 1233 foreach ($topmenu_items as $item) 1234 { 1235 if ($item) $this->_add_topmenu_item($item); 1236 } 1237 } 1238 1239 /** 1240 * Add Preferences link to topmenu using settings-hook to know if an app supports Preferences 1241 */ 1242 protected function add_preferences_topmenu($type='prefs') 1243 { 1244 static $memberships=null; 1245 if (!isset($memberships)) $memberships = $GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'], true); 1246 static $types = array( 1247 'prefs' => array( 1248 'title' => 'Preferences', 1249 'hook' => 'settings', 1250 ), 1251 'acl' => array( 1252 'title' => 'Access', 1253 'hook' => 'acl_rights', 1254 ), 1255 'cats' => array( 1256 'title' => 'Categories', 1257 'hook' => 'categories', 1258 'run_hook' => true, // acturally run hook, not just look it's implemented 1259 ), 1260 'security' => array( 1261 'title' => 'Security & Password', 1262 'hook' => 'preferences_security', 1263 ), 1264 ); 1265 if (!$GLOBALS['egw_info']['user']['apps']['preferences'] || $GLOBALS['egw_info']['server']['deny_'.$type] && 1266 array_intersect($memberships, (array)$GLOBALS['egw_info']['server']['deny_'.$type]) && 1267 !$GLOBALS['egw_info']['user']['apps']['admin']) 1268 { 1269 return; // user has no access to Preferences app 1270 } 1271 if (isset($types[$type]['run_hook'])) 1272 { 1273 $apps = Hooks::process($types[$type]['hook']); 1274 // as all apps answer, we need to remove none-true responses 1275 foreach($apps as $app => $val) 1276 { 1277 if (!$val) unset($apps[$app]); 1278 } 1279 } 1280 else 1281 { 1282 $apps = Hooks::implemented($types[$type]['hook']); 1283 } 1284 // allways display password in topmenu, if user has rights to change it 1285 switch ($type) 1286 { 1287 case 'security': 1288 if ($apps || $GLOBALS['egw_info']['server']['2fa_required'] !== 'disabled' || 1289 !$GLOBALS['egw']->acl->check('nopasswordchange', 1)) 1290 { 1291 $this->_add_topmenu_item(array( 1292 'id' => 'password', 1293 'name' => 'preferences', 1294 'title' => lang($types[$type]['title']), 1295 'url' => "javascript:egw.open_link('". 1296 self::link('/index.php?menuaction=preferences.preferences_password.change')."','_blank','850x580')", 1297 )); 1298 } 1299 break; 1300 1301 default: 1302 $this->_add_topmenu_item(array( 1303 'id' => $type, 1304 'name' => 'preferences', 1305 'title' => lang($types[$type]['title']), 1306 'url' => "javascript:egw.show_preferences(\"$type\",".json_encode($apps).')', 1307 )); 1308 } 1309 } 1310 1311 /** 1312 * Add menu items to the topmenu template class to be displayed 1313 * 1314 * @param array $app application data 1315 * @param mixed $alt_label string with alternative menu item label default value = null 1316 * @param string $urlextra string with alternate additional code inside <a>-tag 1317 * @access protected 1318 * @return void 1319 */ 1320 abstract function _add_topmenu_item(array $app_data,$alt_label=null); 1321 1322 /** 1323 * Add info items to the topmenu template class to be displayed 1324 * 1325 * @param string $content Html of item 1326 * @param string $id =null 1327 * @access protected 1328 * @return void 1329 */ 1330 abstract function _add_topmenu_info_item($content, $id=null); 1331 1332 static $top_menu_extra = array(); 1333 1334 /** 1335 * Called by hooks to add an entry in the topmenu location. 1336 * Extra entries will be added just before Logout. 1337 * 1338 * @param string $id unique element id 1339 * @param string $url Address for the entry to link to 1340 * @param string $title Text displayed for the entry 1341 * @param string $target Optional, so the entry can open in a new page or popup 1342 * @access public 1343 * @return void 1344 */ 1345 public static function add_topmenu_item($id,$url,$title,$target = '') 1346 { 1347 $entry['name'] = $id; 1348 $entry['url'] = $url; 1349 $entry['title'] = $title; 1350 $entry['target'] = $target; 1351 1352 self::$top_menu_extra[$id] = $entry; 1353 } 1354 1355 /** 1356 * called by hooks to add an icon in the topmenu info location 1357 * 1358 * @param string $id unique element id 1359 * @param string $icon_src src of the icon image. Make sure this nog height then 18pixels 1360 * @param string $iconlink where the icon links to 1361 * @param booleon $blink set true to make the icon blink 1362 * @param mixed $tooltip string containing the tooltip html, or null of no tooltip 1363 * @access public 1364 * @return void 1365 */ 1366 abstract function topmenu_info_icon($id,$icon_src,$iconlink,$blink=false,$tooltip=null); 1367 1368 /** 1369 * Call and return content of 'after_navbar' hook 1370 * 1371 * @return string 1372 */ 1373 protected function _get_after_navbar() 1374 { 1375 ob_start(); 1376 Hooks::process('after_navbar',null,true); 1377 $content = ob_get_contents(); 1378 ob_end_clean(); 1379 1380 return $content; 1381 } 1382 1383 /** 1384 * Return javascript (eg. for onClick) to open manual with given url 1385 * 1386 * @param string $url 1387 */ 1388 abstract function open_manual_js($url); 1389 1390 /** 1391 * Methods to add javascript to framework 1392 */ 1393 1394 /** 1395 * The include manager manages including js files and their dependencies 1396 */ 1397 protected static $js_include_mgr; 1398 1399 /** 1400 * Checks to make sure a valid package and file name is provided 1401 * 1402 * Example call syntax: 1403 * a) Api\Framework::includeJS('jscalendar','calendar') 1404 * --> /phpgwapi/js/jscalendar/calendar.js 1405 * b) Api\Framework::includeJS('/phpgwapi/inc/calendar-setup.js',array('lang'=>'de')) 1406 * --> /phpgwapi/inc/calendar-setup.js?lang=de 1407 * 1408 * @param string $package package or complete path (relative to EGW_SERVER_ROOT) to be included 1409 * @param string|array $file =null file to be included - no ".js" on the end or array with get params 1410 * @param string $app ='phpgwapi' application directory to search - default = phpgwapi 1411 * @param boolean $append =true should the file be added 1412 */ 1413 static function includeJS($package, $file=null, $app='phpgwapi', $append=true) 1414 { 1415 self::$js_include_mgr->include_js_file($package, $file, $app, $append); 1416 } 1417 1418 /** 1419 * Set or return all javascript files set via validate_file, optionally clear all files 1420 * 1421 * @param array $files =null array with pathes relative to EGW_SERVER_ROOT, eg. /api/js/jquery/jquery.js 1422 * @param boolean $clear_files =false true clear files after returning them 1423 * @return array with pathes relative to EGW_SERVER_ROOT 1424 */ 1425 static function js_files(array $files=null, $clear_files=false) 1426 { 1427 if (isset($files) && is_array($files)) 1428 { 1429 self::$js_include_mgr->include_files($files); 1430 } 1431 return self::$js_include_mgr->get_included_files($clear_files); 1432 } 1433 1434 /** 1435 * Used for generating the list of external js files to be included in the head of a page 1436 * 1437 * NOTE: This method should only be called by the template class. 1438 * The validation is done when the file is added so we don't have to worry now 1439 * 1440 * @param boolean $return_pathes =false false: return Html script tags, true: return array of file pathes relative to webserver_url 1441 * @param boolean $clear_files =false true clear files after returning them 1442 * @return string|array see $return_pathes parameter 1443 */ 1444 static public function get_script_links($return_pathes=false, $clear_files=false) 1445 { 1446 $to_include = Framework\Bundle::js_includes(self::$js_include_mgr->get_included_files($clear_files)); 1447 1448 if ($return_pathes) 1449 { 1450 return $to_include; 1451 } 1452 $start = '<script type="text/javascript" src="'. $GLOBALS['egw_info']['server']['webserver_url']; 1453 $end = '">'."</script>\n"; 1454 return "\n".$start.implode($end.$start, $to_include).$end; 1455 } 1456 1457 /** 1458 * 1459 * @var boolean 1460 */ 1461 protected static $load_default_css = true; 1462 1463 /** 1464 * Include a css file, either speicified by it's path (relative to EGW_SERVER_ROOT) or appname and css file name 1465 * 1466 * @param string $app path (relative to EGW_SERVER_ROOT) or appname (if !is_null($name)) 1467 * @param string $name =null name of css file in $app/templates/{default|$this->template}/$name.css 1468 * @param boolean $append =true true append file, false prepend (add as first) file used eg. for template itself 1469 * @param boolean $no_default_css =false true do NOT load any default css, only what app explicitly includes 1470 * @return boolean false: css file not found, true: file found 1471 */ 1472 public static function includeCSS($app, $name=null, $append=true, $no_default_css=false) 1473 { 1474 if ($no_default_css) 1475 { 1476 self::$load_default_css = false; 1477 } 1478 //error_log(__METHOD__."('$app', '$name', append=$append, no_default=$no_default_css) ".function_backtrace()); 1479 return Framework\CssIncludes::add($app, $name, $append, $no_default_css); 1480 } 1481 1482 /** 1483 * Add registered CSS and javascript to ajax response 1484 */ 1485 public static function include_css_js_response() 1486 { 1487 $response = Json\Response::get(); 1488 $app = $GLOBALS['egw_info']['flags']['currentapp']; 1489 1490 // try to add app specific css file 1491 self::includeCSS($app, 'app-'.$GLOBALS['egw_info']['user']['preferences']['common']['theme']) || 1492 self::includeCSS($app,'app'); 1493 1494 // add all css files from Framework::includeCSS() 1495 $query = null; 1496 //error_log(__METHOD__."() Framework\CssIncludes::get()=".array2string(Framework\CssIncludes::get())); 1497 foreach(Framework\CssIncludes::get() as $path) 1498 { 1499 unset($query); 1500 list($path,$query) = explode('?',$path,2); 1501 $path .= '?'. ($query ? $query : filemtime(EGW_SERVER_ROOT.$path)); 1502 $response->includeCSS($GLOBALS['egw_info']['server']['webserver_url'].$path); 1503 } 1504 1505 // try to add app specific js file 1506 self::includeJS('.', 'app', $app); 1507 1508 // add all js files from Framework::includeJS() 1509 $files = Framework\Bundle::js_includes(self::$js_include_mgr->get_included_files()); 1510 foreach($files as $path) 1511 { 1512 $response->includeScript($GLOBALS['egw_info']['server']['webserver_url'].$path); 1513 } 1514 } 1515 1516 /** 1517 * Set a preference via ajax 1518 * 1519 * @param string $app 1520 * @param string $name 1521 * @param string $value 1522 */ 1523 public static function ajax_set_preference($app, $name, $value) 1524 { 1525 $GLOBALS['egw']->preferences->read_repository(); 1526 if ((string)$value === '') 1527 { 1528 $GLOBALS['egw']->preferences->delete($app, $name); 1529 } 1530 else 1531 { 1532 $GLOBALS['egw']->preferences->add($app, $name, $value); 1533 } 1534 $GLOBALS['egw']->preferences->save_repository(True); 1535 } 1536 1537 /** 1538 * Get Preferences of a certain application via ajax 1539 * 1540 * @param string $app 1541 */ 1542 public static function ajax_get_preference($app) 1543 { 1544 // dont block session, while we read preferences, they are not supposed to change something in the session 1545 $GLOBALS['egw']->session->commit_session(); 1546 1547 if (preg_match('/^[a-z0-9_]+$/i', $app)) 1548 { 1549 // send etag header, if we are directly called (not via jsonq!) 1550 if (strpos($_GET['menuaction'], __FUNCTION__) !== false) 1551 { 1552 $etag = '"'.$app.'-'.md5(json_encode($GLOBALS['egw_info']['user']['preferences'][$app])).'"'; 1553 if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) 1554 { 1555 header("HTTP/1.1 304 Not Modified"); 1556 exit(); 1557 } 1558 header('ETag: '.$etag); 1559 } 1560 $response = Json\Response::get(); 1561 $response->call('egw.set_preferences', (array)$GLOBALS['egw_info']['user']['preferences'][$app], $app); 1562 } 1563 } 1564 1565 /** 1566 * Create or delete a favorite for multiple users 1567 * 1568 * Need to be in egw_framework to be called with .template postfix from json.php! 1569 * 1570 * @param string $app Current application, needed to save preference 1571 * @param string $name Name of the favorite 1572 * @param string $action "add" or "delete" 1573 * @param boolean|int|string $group ID of the group to create the favorite for, or 'all' for all users 1574 * @param array $filters =array() key => value pairs for the filter 1575 * @return boolean Success 1576 */ 1577 public static function ajax_set_favorite($app, $name, $action, $group, $filters = array()) 1578 { 1579 return Framework\Favorites::set_favorite($app, $name, $action, $group, $filters); 1580 } 1581 1582 /** 1583 * Get a cachable list of users for the client 1584 * 1585 * The account source takes care of access and filtering according to preference 1586 */ 1587 public static function ajax_user_list() 1588 { 1589 $list = array('accounts' => array(),'groups' => array(), 'owngroups' => array()); 1590 if($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'primary_group') 1591 { 1592 $list['accounts']['filter']['group'] = $GLOBALS['egw_info']['user']['account_primary_group']; 1593 } 1594 $contact_obj = new Contacts(); 1595 foreach($list as $type => &$accounts) 1596 { 1597 $options = array('account_type' => $type) + $accounts; 1598 $key_pair = Accounts::link_query('',$options); 1599 $accounts = array(); 1600 foreach($key_pair as $account_id => $name) 1601 { 1602 $contact = $contact_obj->read('account:'.$account_id, true); 1603 $accounts[] = array('value' => $account_id, 'label' => $name, 'icon' => self::link('/api/avatar.php', array( 1604 'contact_id' => $contact['id'], 1605 'etag' => $contact['etag'] 1606 ))); 1607 } 1608 } 1609 1610 Json\Response::get()->data($list); 1611 return $list; 1612 } 1613 1614 /** 1615 * Get certain account-data of given account-id(s) 1616 * 1617 * @param string|array $_account_ids 1618 * @param string $_field ='account_email' 1619 * @param boolean $_resolve_groups =false true: return attribute for all members, false return attribute for group itself 1620 * @return array account_id => data pairs 1621 */ 1622 public static function ajax_account_data($_account_ids, $_field, $_resolve_groups=false) 1623 { 1624 $list = array(); 1625 foreach((array)$_account_ids as $account_id) 1626 { 1627 foreach($account_id < 0 && $_resolve_groups ? 1628 $GLOBALS['egw']->accounts->members($account_id, true) : array($account_id) as $account_id) 1629 { 1630 // Make sure name is formatted according to preference 1631 if($_field == 'account_fullname') 1632 { 1633 $list[$account_id] = Accounts::format_username( 1634 $GLOBALS['egw']->accounts->id2name($account_id, 'account_lid'), 1635 $GLOBALS['egw']->accounts->id2name($account_id, 'account_firstname'), 1636 $GLOBALS['egw']->accounts->id2name($account_id, 'account_lastname'), 1637 $account_id 1638 ); 1639 } 1640 else 1641 { 1642 $list[$account_id] = $GLOBALS['egw']->accounts->id2name($account_id, $_field); 1643 } 1644 } 1645 } 1646 1647 Json\Response::get()->data($list); 1648 return $list; 1649 } 1650} 1651// Init all static variables 1652Framework::init_static(); 1653