Overview

Packages

  • Auth
  • Core
  • Horde
    • Imsp
  • None
  • Notification

Classes

  • Horde
  • Horde_Config
  • Horde_Config_Form
  • Horde_Core_ActiveSync_Connector
  • Horde_Core_ActiveSync_Driver
  • Horde_Core_Ajax_Application
  • Horde_Core_Ajax_Imple
  • Horde_Core_Ajax_Imple_AutoCompleter
  • Horde_Core_Ajax_Imple_Geocoder_Geonames
  • Horde_Core_Ajax_Imple_SpellChecker
  • Horde_Core_Alarm_Handler_Notify
  • Horde_Core_Auth_Application
  • Horde_Core_Auth_Composite
  • Horde_Core_Auth_Ldap
  • Horde_Core_Auth_Msad
  • Horde_Core_Auth_Shibboleth
  • Horde_Core_Auth_Signup_Base
  • Horde_Core_Auth_Signup_Form
  • Horde_Core_Auth_Signup_Null
  • Horde_Core_Auth_Signup_Sql
  • Horde_Core_Auth_Signup_SqlObject
  • Horde_Core_Autoloader_Callback_Mime
  • Horde_Core_Autoloader_Callback_Nls
  • Horde_Core_Block
  • Horde_Core_Block_Collection
  • Horde_Core_Block_Layout
  • Horde_Core_Block_Layout_Manager
  • Horde_Core_Block_Layout_View
  • Horde_Core_Block_Upgrade
  • Horde_Core_Browser
  • Horde_Core_Bundle
  • Horde_Core_Cli
  • Horde_Core_Controller_NotFound
  • Horde_Core_Controller_RequestConfiguration
  • Horde_Core_Controller_RequestMapper
  • Horde_Core_Controller_SettingsFinder
  • Horde_Core_Db_Migration
  • Horde_Core_Factory_ActiveSyncBackend
  • Horde_Core_Factory_ActiveSyncServer
  • Horde_Core_Factory_ActiveSyncState
  • Horde_Core_Factory_Ajax
  • Horde_Core_Factory_Alarm
  • Horde_Core_Factory_Auth
  • Horde_Core_Factory_AuthSignup
  • Horde_Core_Factory_Base
  • Horde_Core_Factory_BlockCollection
  • Horde_Core_Factory_Browser
  • Horde_Core_Factory_Cache
  • Horde_Core_Factory_Crypt
  • Horde_Core_Factory_Data
  • Horde_Core_Factory_Db
  • Horde_Core_Factory_DbBase
  • Horde_Core_Factory_DbPear
  • Horde_Core_Factory_Dns
  • Horde_Core_Factory_Editor
  • Horde_Core_Factory_Facebook
  • Horde_Core_Factory_Group
  • Horde_Core_Factory_History
  • Horde_Core_Factory_HttpClient
  • Horde_Core_Factory_Identity
  • Horde_Core_Factory_Image
  • Horde_Core_Factory_Imple
  • Horde_Core_Factory_Imsp
  • Horde_Core_Factory_ImspAuth
  • Horde_Core_Factory_Injector
  • Horde_Core_Factory_KolabServer
  • Horde_Core_Factory_KolabSession
  • Horde_Core_Factory_KolabStorage
  • Horde_Core_Factory_Ldap
  • Horde_Core_Factory_Lock
  • Horde_Core_Factory_Logger
  • Horde_Core_Factory_LoginTasks
  • Horde_Core_Factory_Mail
  • Horde_Core_Factory_Mapper
  • Horde_Core_Factory_Matcher
  • Horde_Core_Factory_Memcache
  • Horde_Core_Factory_MimeViewer
  • Horde_Core_Factory_Notification
  • Horde_Core_Factory_Perms
  • Horde_Core_Factory_PermsCore
  • Horde_Core_Factory_Prefs
  • Horde_Core_Factory_Request
  • Horde_Core_Factory_Secret
  • Horde_Core_Factory_SessionHandler
  • Horde_Core_Factory_Share
  • Horde_Core_Factory_ShareBase
  • Horde_Core_Factory_Template
  • Horde_Core_Factory_TextFilter
  • Horde_Core_Factory_ThemesCache
  • Horde_Core_Factory_Token
  • Horde_Core_Factory_Tree
  • Horde_Core_Factory_Twitter
  • Horde_Core_Factory_UrlShortener
  • Horde_Core_Factory_Vfs
  • Horde_Core_Factory_View
  • Horde_Core_Factory_Weather
  • Horde_Core_Group_Ldap
  • Horde_Core_Log_Logger
  • Horde_Core_LoginTasks
  • Horde_Core_LoginTasks_Backend_Horde
  • Horde_Core_LoginTasks_SystemTask_Upgrade
  • Horde_Core_Mime_Viewer_Syntaxhighlighter
  • Horde_Core_Mime_Viewer_Vcard
  • Horde_Core_Notification_Event_Status
  • Horde_Core_Notification_Handler_Decorator_Hordelog
  • Horde_Core_Notification_Storage_Session
  • Horde_Core_Perms
  • Horde_Core_Perms_Ui
  • Horde_Core_Prefs_Cache_Session
  • Horde_Core_Prefs_Identity
  • Horde_Core_Prefs_Storage_Configuration
  • Horde_Core_Prefs_Storage_Hooks
  • Horde_Core_Prefs_Storage_Upgrade
  • Horde_Core_Prefs_Ui
  • Horde_Core_Prefs_Ui_Widgets
  • Horde_Core_Share_Driver
  • Horde_Core_Share_FactoryCallback
  • Horde_Core_Sidebar
  • Horde_Core_Text_Filter_Bbcode
  • Horde_Core_Text_Filter_Emails
  • Horde_Core_Text_Filter_Emoticons
  • Horde_Core_Text_Filter_Highlightquotes
  • Horde_Core_Translation
  • Horde_Core_Tree_Html
  • Horde_Core_Tree_Javascript
  • Horde_Core_Tree_Simplehtml
  • Horde_Core_Ui_FlagImage
  • Horde_Core_Ui_JsCalendar
  • Horde_Core_Ui_Language
  • Horde_Core_Ui_Layout
  • Horde_Core_Ui_ModalFormRenderer
  • Horde_Core_Ui_Pager
  • Horde_Core_Ui_Tabs
  • Horde_Core_Ui_TagCloud
  • Horde_Core_Ui_VarRenderer
  • Horde_Core_Ui_VarRenderer_Html
  • Horde_Core_Ui_VarRenderer_TablesetHtml
  • Horde_Core_Ui_Widget
  • Horde_ErrorHandler
  • Horde_Help
  • Horde_Menu
  • Horde_Registry
  • Horde_Registry_Api
  • Horde_Registry_Application
  • Horde_Registry_Caller
  • Horde_Registry_Nlsconfig
  • Horde_Script_Files
  • Horde_Session
  • Horde_Session_Null
  • Horde_Themes
  • Horde_Themes_Cache
  • Horde_Themes_Css
  • Horde_Themes_Element
  • Horde_Themes_Image
  • Horde_Themes_Sound

Exceptions

  • Horde_Exception_HookNotSet
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * The Horde:: class provides the functionality shared by all Horde
   4:  * applications.
   5:  *
   6:  * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
   7:  *
   8:  * See the enclosed file COPYING for license information (LGPL). If you
   9:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  10:  *
  11:  * @author   Chuck Hagenbuch <chuck@horde.org>
  12:  * @author   Jon Parise <jon@horde.org>
  13:  * @category Horde
  14:  * @package  Core
  15:  */
  16: class Horde
  17: {
  18:     /**
  19:      * Has compression been started?
  20:      *
  21:      * @var boolean
  22:      */
  23:     static protected $_compressStart = false;
  24: 
  25:     /**
  26:      * The access keys already used in this page.
  27:      *
  28:      * @var array
  29:      */
  30:     static protected $_used = array();
  31: 
  32:     /**
  33:      * The labels already used in this page.
  34:      *
  35:      * @var array
  36:      */
  37:     static protected $_labels = array();
  38: 
  39:     /**
  40:      * Are accesskeys supported on this system.
  41:      *
  42:      * @var boolean
  43:      */
  44:     static protected $_noAccessKey;
  45: 
  46:     /**
  47:      * Whether the hook has already been loaded.
  48:      *
  49:      * @var array
  50:      */
  51:     static protected $_hooksLoaded = array();
  52: 
  53:     /**
  54:      * Inline script cache.
  55:      *
  56:      * @var array
  57:      */
  58:     static protected $_inlineScript = array();
  59: 
  60:     /**
  61:      * The current buffer level.
  62:      *
  63:      * @var integer
  64:      */
  65:     static protected $_bufferLevel = 0;
  66: 
  67:     /**
  68:      * Has content been sent at the base buffer level?
  69:      *
  70:      * @var boolean
  71:      */
  72:     static protected $_contentSent = false;
  73: 
  74:     /**
  75:      * META tag cache.
  76:      *
  77:      * @var array
  78:      */
  79:     static protected $_metaTags = array();
  80: 
  81:     /**
  82:      * Shortcut to logging method.
  83:      *
  84:      * @see Horde_Core_Log_Logger
  85:      */
  86:     static public function logMessage($event, $priority = null,
  87:                                       array $options = array())
  88:     {
  89:         /* Chicken/egg: wait until we have basic framework setup before we
  90:          * start logging. */
  91:         if (isset($GLOBALS['conf']) && isset($GLOBALS['injector'])) {
  92:             $options['trace'] = 2;
  93:             $GLOBALS['injector']->getInstance('Horde_Log_Logger')->log($event, $priority, $options);
  94:         }
  95:     }
  96: 
  97:     /**
  98:      * Debug method.  Allows quick shortcut to produce debug output into a
  99:      * temporary file.
 100:      *
 101:      * @param mixed $event   Item to log.
 102:      * @param string $fname  Filename to log to. If empty, logs to
 103:      *                       'horde_debug.txt' in the temporary directory.
 104:      */
 105:     static public function debug($event = null, $fname = null)
 106:     {
 107:         if (is_null($fname)) {
 108:             $fname = self::getTempDir() . '/horde_debug.txt';
 109:         }
 110: 
 111:         try {
 112:             $logger = new Horde_Log_Logger(new Horde_Log_Handler_Stream($fname));
 113:         } catch (Exception $e) {
 114:             return;
 115:         }
 116: 
 117:         $html_ini = ini_set('html_errors', 'Off');
 118:         self::startBuffer();
 119:         if (!is_null($event)) {
 120:             echo "Variable information:\n";
 121:             var_dump($event);
 122:             echo "\n";
 123:         }
 124: 
 125:         if (is_resource($event)) {
 126:             echo "Stream contents:\n";
 127:             rewind($event);
 128:             fpassthru($event);
 129:             echo "\n";
 130:         }
 131: 
 132:         echo "Backtrace:\n";
 133:         echo strval(new Horde_Support_Backtrace());
 134: 
 135:         $logger->log(self::endBuffer(), Horde_Log::DEBUG);
 136:         ini_set('html_errors', $html_ini);
 137:     }
 138: 
 139:     /**
 140:      * Aborts with a fatal error, displaying debug information to the user.
 141:      *
 142:      * @param mixed $error   Either a string or an object with a getMessage()
 143:      *                       method (e.g. PEAR_Error, Exception).
 144:      * @param integer $file  The file in which the error occured.
 145:      * @param integer $line  The line on which the error occured.
 146:      * @param boolean $log   Log this message via logMessage()?
 147:      */
 148:     static public function fatal($error, $file = null, $line = null,
 149:                                  $log = true)
 150:     {
 151:         // Log the error via logMessage() if requested.
 152:         if ($log) {
 153:             try {
 154:                 self::logMessage($error, 'EMERG');
 155:             } catch (Exception $e) {}
 156:         }
 157: 
 158:         header('Content-type: text/html; charset=UTF-8');
 159:         try {
 160:             $admin = $GLOBALS['registry']->isAdmin();
 161:             $cli = Horde_Cli::runningFromCLI();
 162: 
 163:             $errortext = '<h1>' . Horde_Core_Translation::t("A fatal error has occurred") . '</h1>';
 164: 
 165:             if (($error instanceof PEAR_Error) ||
 166:                 (is_object($error) && method_exists($error, 'getMessage'))) {
 167:                 $errortext .= '<h3>' . htmlspecialchars($error->getMessage()) . '</h3>';
 168:             } elseif (is_string($error)) {
 169:                 $errortext .= '<h3>' . htmlspecialchars($error) . '</h3>';
 170:             }
 171: 
 172:             if ($admin || $cli) {
 173:                 $trace = ($error instanceof Exception)
 174:                     ? $error
 175:                     : debug_backtrace();
 176:                 $errortext .= '<div id="backtrace"><pre>' .
 177:                     strval(new Horde_Support_Backtrace($trace)) .
 178:                     '</pre></div>';
 179:                 if (is_object($error)) {
 180:                     $errortext .= '<h3>' . Horde_Core_Translation::t("Details") . '</h3>';
 181:                     $errortext .= '<h4>' . Horde_Core_Translation::t("The full error message is logged in Horde's log file, and is shown below only to administrators. Non-administrative users will not see error details.") . '</h4>';
 182:                     $errortext .= '<div id="details"><pre>' . htmlspecialchars(print_r($error, true)) . '</pre></div>';
 183:                 }
 184:             } elseif ($log) {
 185:                 $errortext .= '<h3>' . Horde_Core_Translation::t("Details have been logged for the administrator.") . '</h3>';
 186:             }
 187:         } catch (Exception $e) {
 188:             die($e);
 189:         }
 190: 
 191:         if ($cli) {
 192:             echo html_entity_decode(strip_tags(str_replace(array('<br />', '<p>', '</p>', '<h1>', '</h1>', '<h3>', '</h3>'), "\n", $errortext)));
 193:         } else {
 194:             echo <<< HTML
 195: <html>
 196: <head><title>Horde :: Fatal Error</title></head>
 197: <body style="background:#fff; color:#000">$errortext</body>
 198: </html>
 199: HTML;
 200:         }
 201:         exit(1);
 202:     }
 203: 
 204:     /**
 205:      * PHP legacy error handling (non-Exceptions).
 206:      *
 207:      * @param integer $errno     See set_error_handler().
 208:      * @param string $errstr     See set_error_handler().
 209:      * @param string $errfile    See set_error_handler().
 210:      * @param integer $errline   See set_error_handler().
 211:      * @param array $errcontext  See set_error_handler().
 212:      */
 213:     static public function errorHandler($errno, $errstr, $errfile, $errline,
 214:                                         $errcontext)
 215:     {
 216:         // Calls prefixed with '@'.
 217:         if (error_reporting() == 0) {
 218:             // Must return false to populate $php_errormsg (as of PHP 5.2).
 219:             return false;
 220:         }
 221: 
 222:         if (class_exists('Horde_Log')) {
 223:             try {
 224:                 switch ($errno) {
 225:                 case E_WARNING:
 226:                     $priority = Horde_Log::WARN;
 227:                     break;
 228: 
 229:                 case E_NOTICE:
 230:                     $priority = Horde_Log::NOTICE;
 231:                     break;
 232: 
 233:                 default:
 234:                     $priority = Horde_Log::DEBUG;
 235:                     break;
 236:                 }
 237: 
 238:                 self::logMessage(new ErrorException('PHP ERROR: ' . $errstr, 0, $errno, $errfile, $errline), $priority);
 239:                 if (class_exists('Horde_Support_Backtrace')) {
 240:                     self::logMessage(new Horde_Support_Backtrace(), Horde_Log::DEBUG);
 241:                 }
 242:             } catch (Exception $e) {}
 243:         }
 244:     }
 245: 
 246:     /**
 247:      * Adds the javascript code to the output (if output has already started)
 248:      * or to the list of script files to include via includeScriptFiles().
 249:      *
 250:      * As long as one script file is added, 'prototype.js' will be
 251:      * automatically added, if the prototypejs property of Horde_Script_Files
 252:      * is true (it is true by default).
 253:      *
 254:      * @param string $file    The full javascript file name.
 255:      * @param string $app     The application name. Defaults to the current
 256:      *                        application.
 257:      * @param array $options  Additional options:
 258:      * <pre>
 259:      * 'external' - (boolean) Treat $file as an external URL.
 260:      *              DEFAULT: $file is located in the app's js/ directory.
 261:      * 'full' - (boolean) Output a full URL
 262:      *          DEFAULT: false
 263:      * </pre>
 264:      *
 265:      * @throws Horde_Exception
 266:      */
 267:     static public function addScriptFile($file, $app = null,
 268:                                          $options = array())
 269:     {
 270:         $hsf = $GLOBALS['injector']->getInstance('Horde_Script_Files');
 271:         if (empty($options['external'])) {
 272:             $hsf->add($file, $app, !empty($options['full']));
 273:         } else {
 274:             $hsf->addExternal($file, $app);
 275:         }
 276:     }
 277: 
 278:     /**
 279:      * Outputs the necessary script tags, honoring configuration choices as
 280:      * to script caching.
 281:      *
 282:      * @throws Horde_Exception
 283:      */
 284:     static public function includeScriptFiles()
 285:     {
 286:         global $conf;
 287: 
 288:         $driver = empty($conf['cachejs'])
 289:             ? 'none'
 290:             : $conf['cachejsparams']['driver'];
 291:         $hsf = $GLOBALS['injector']->getInstance('Horde_Script_Files');
 292: 
 293:         if ($driver == 'none') {
 294:             $hsf->includeFiles();
 295:             return;
 296:         }
 297: 
 298:         $js = array(
 299:             'force' => array(),
 300:             'external' => array(),
 301:             'tocache' => array()
 302:         );
 303:         $mtime = array(
 304:             'force' => array(),
 305:             'tocache' => array()
 306:         );
 307: 
 308:         $s_list = $hsf->listFiles();
 309:         if (empty($s_list)) {
 310:             return;
 311:         }
 312: 
 313:         if ($driver == 'horde_cache') {
 314:             $cache = $GLOBALS['injector']->getInstance('Horde_Cache');
 315:             $cache_lifetime = empty($conf['cachejsparams']['lifetime'])
 316:                 ? 0
 317:                 : $conf['cachejsparams']['lifetime'];
 318:         }
 319: 
 320:         /* Output prototype.js separately from the other files. */
 321:         if ($s_list['horde'][0]['f'] == 'prototype.js') {
 322:             $js['force'][] = $s_list['horde'][0]['p'] . $s_list['horde'][0]['f'];
 323:             $mtime['force'][] = filemtime($s_list['horde'][0]['p'] . $s_list['horde'][0]['f']);
 324:             unset($s_list['horde'][0]);
 325:         }
 326: 
 327:         foreach ($s_list as $files) {
 328:             foreach ($files as $file) {
 329:                 if ($file['d'] && ($file['f'][0] != '/') && empty($file['e'])) {
 330:                     $js['tocache'][] = $file['p'] . $file['f'];
 331:                     $mtime['tocache'][] = filemtime($file['p'] . $file['f']);
 332:                 } elseif (!empty($file['e'])) {
 333:                     $js['external'][] = $file['u'];
 334:                 } else {
 335:                     $js['force'][] = $file['p'] . $file['f'];
 336:                     $mtime['force'][] = filemtime($file['p'] . $file['f']);
 337:                 }
 338:             }
 339:         }
 340: 
 341:         $jsmin_params = null;
 342:         foreach ($js as $key => $files) {
 343:             if (!count($files)) {
 344:                 continue;
 345:             }
 346: 
 347:             if ($key == 'external') {
 348:                 foreach ($files as $val) {
 349:                     $hsf->outputTag($val);
 350:                 }
 351:                 continue;
 352:             }
 353: 
 354:             $sig_files = $files;
 355:             sort($sig_files);
 356:             $sig = hash('md5', serialize($sig_files) . max($mtime[$key]));
 357: 
 358:             switch ($driver) {
 359:             case 'filesystem':
 360:                 $js_filename = '/static/' . $sig . '.js';
 361:                 $js_path = $GLOBALS['registry']->get('fileroot', 'horde') . $js_filename;
 362:                 $js_url = $GLOBALS['registry']->get('webroot', 'horde') . $js_filename;
 363:                 $exists = file_exists($js_path);
 364:                 break;
 365: 
 366:             case 'horde_cache':
 367:                 // Do lifetime checking here, not on cache display page.
 368:                 $exists = $cache->exists($sig, $cache_lifetime);
 369:                 $js_url = self::getCacheUrl('js', array('cid' => $sig));
 370:                 break;
 371:             }
 372: 
 373:             if (!$exists) {
 374:                 $out = '';
 375:                 foreach ($files as $val) {
 376:                     $js_text = file_get_contents($val);
 377: 
 378:                     if ($conf['cachejsparams']['compress'] == 'none') {
 379:                         $out .= $js_text . "\n";
 380:                     } else {
 381:                         if (is_null($jsmin_params)) {
 382:                             switch ($conf['cachejsparams']['compress']) {
 383:                             case 'closure':
 384:                                 $jsmin_params = array(
 385:                                     'closure' => $conf['cachejsparams']['closurepath'],
 386:                                     'java' => $conf['cachejsparams']['javapath']
 387:                                 );
 388:                             break;
 389: 
 390:                             case 'yui':
 391:                                 $jsmin_params = array(
 392:                                     'java' => $conf['cachejsparams']['javapath'],
 393:                                     'yui' => $conf['cachejsparams']['yuipath']
 394:                                 );
 395:                                 break;
 396:                             }
 397:                         }
 398: 
 399:                         /* Separate JS files with a newline since some
 400:                          * compressors may strip trailing terminators. */
 401:                         try {
 402:                             $out .= $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($js_text, 'JavascriptMinify', $jsmin_params) . "\n";
 403:                         } catch (Horde_Exception $e) {
 404:                             $out .= $js_text . "\n";
 405:                         }
 406:                     }
 407:                 }
 408: 
 409:                 switch ($driver) {
 410:                 case 'filesystem':
 411:                     if (!file_put_contents($js_path, $out)) {
 412:                         throw new Horde_Exception('Could not write cached JS file to disk.');
 413:                     }
 414:                     break;
 415: 
 416:                 case 'horde_cache':
 417:                     $cache->set($sig, $out);
 418:                     break;
 419:                 }
 420:             }
 421: 
 422:             $hsf->outputTag($js_url);
 423:         }
 424: 
 425:         $hsf->clear();
 426:     }
 427: 
 428:     /**
 429:      * Add a signature + timestamp to a query string and return the signed query
 430:      * string.
 431:      *
 432:      * @param string $queryString  The query string to sign.
 433:      * @param integer $now         The timestamp at which to sign. Leave blank
 434:      *                             for generating signatures; specify when
 435:      *                             testing.
 436:      *
 437:      * @return string  The signed query string.
 438:      */
 439:     static public function signQueryString($queryString, $now = null)
 440:     {
 441:         if (!isset($GLOBALS['conf']['secret_key'])) {
 442:             return $queryString;
 443:         }
 444: 
 445:         if (is_null($now)) {
 446:             $now = time();
 447:         }
 448: 
 449:         $queryString .= '&_t=' . $now . '&_h=';
 450: 
 451:         return $queryString . Horde_Url::uriB64Encode(hash_hmac('sha1', $queryString, $GLOBALS['conf']['secret_key'], true));
 452:     }
 453: 
 454:     /**
 455:      * Verify a signature and timestamp on a query string.
 456:      *
 457:      * @param string $data  The signed query string.
 458:      * @param integer $now  The current time (can override for testing).
 459:      *
 460:      * @return boolean  Whether or not the string was valid.
 461:      */
 462:     static public function verifySignedQueryString($data, $now = null)
 463:     {
 464:         if (is_null($now)) {
 465:             $now = time();
 466:         }
 467: 
 468:         $pos = strrpos($data, '&_h=');
 469:         if ($pos === false) {
 470:             return false;
 471:         }
 472:         $pos += 4;
 473: 
 474:         $queryString = substr($data, 0, $pos);
 475:         $hmac = substr($data, $pos);
 476: 
 477:         if ($hmac != Horde_Url::uriB64Encode(hash_hmac('sha1', $queryString, $GLOBALS['conf']['secret_key'], true))) {
 478:             return false;
 479:         }
 480: 
 481:         // String was not tampered with; now validate timestamp
 482:         parse_str($queryString, $values);
 483: 
 484:         return !($values['_t'] + $GLOBALS['conf']['urls']['hmac_lifetime'] * 60 < $now);
 485:     }
 486: 
 487:     /**
 488:      * Returns the URL to various Horde services.
 489:      *
 490:      * @param string $type       The service to display.
 491:      * <pre>
 492:      * 'ajax'
 493:      * 'cache'
 494:      * 'download'
 495:      * 'emailconfirm'
 496:      * 'go'
 497:      * 'help'
 498:      * 'imple'
 499:      * 'login'
 500:      * 'logintasks'
 501:      * 'logout'
 502:      * 'pixel'
 503:      * 'portal'
 504:      * 'problem'
 505:      * 'sidebar'
 506:      * 'prefs'
 507:      * </pre>
 508:      * @param string $app        The name of the current Horde application.
 509:      *
 510:      * @return Horde_Url|boolean  The HTML to create the link.
 511:      */
 512:     static public function getServiceLink($type, $app = null)
 513:     {
 514:         $opts = array('app' => 'horde');
 515: 
 516:         switch ($type) {
 517:         case 'ajax':
 518:             $opts['noajax'] = true;
 519:             return self::url('services/ajax.php/' . $app . '/', false, $opts);
 520: 
 521:         case 'cache':
 522:             $opts['append_session'] = -1;
 523:             return self::url('services/cache.php', false, $opts);
 524: 
 525:         case 'download':
 526:             return self::url('services/download/', false, $opts)
 527:                 ->add('module', $app);
 528: 
 529:         case 'emailconfirm':
 530:             $opts['noajax'] = true;
 531:             return self::url('services/confirm.php', false, $opts);
 532: 
 533:         case 'go':
 534:             $opts['noajax'] = true;
 535:             return self::url('services/go.php', false, $opts);
 536: 
 537:         case 'help':
 538:             return self::url('services/help/', false, $opts)
 539:                 ->add('module', $app);
 540: 
 541:         case 'imple':
 542:             $opts['noajax'] = true;
 543:             return self::url('services/imple.php', false, $opts);
 544: 
 545:         case 'login':
 546:             $opts['noajax'] = true;
 547:             return self::url('login.php', false, $opts);
 548: 
 549:         case 'logintasks':
 550:             return self::url('services/logintasks.php', false, $opts)
 551:                 ->add('app', $app);
 552: 
 553:         case 'logout':
 554:             return $GLOBALS['registry']->getLogoutUrl(array('reason' => Horde_Auth::REASON_LOGOUT));
 555: 
 556:         case 'pixel':
 557:             return self::url('services/images/pixel.php', false, $opts);
 558: 
 559:         case 'prefs':
 560:             if (!in_array($GLOBALS['conf']['prefs']['driver'], array('', 'none'))) {
 561:                 $url = self::url('services/prefs.php', false, $opts);
 562:                 if (!is_null($app)) {
 563:                     $url->add('app', $app);
 564:                 }
 565:                 return $url;
 566:             }
 567:             break;
 568: 
 569:         case 'portal':
 570:             if ($GLOBALS['session']->get('horde', 'mode') == 'smartmobile' && self::ajaxAvailable()) {
 571:                 return self::url('services/portal/mobile.php', false, $opts);
 572:             } else {
 573:                 return self::url('services/portal/', false, $opts);
 574:             }
 575:             break;
 576: 
 577:         case 'problem':
 578:             return self::url('services/problem.php', false, $opts)
 579:                 ->add('return_url', self::selfUrl(true, true, true));
 580: 
 581:         case 'sidebar':
 582:             return self::url('services/sidebar.php', false, $opts);
 583: 
 584:         case 'twitter':
 585:             return self::url('services/twitter/', true, $opts);
 586:         }
 587: 
 588:         return false;
 589:     }
 590: 
 591:     /**
 592:      * Returns a response object with added notification information.
 593:      *
 594:      * @param mixed $data      The 'response' data.
 595:      * @param boolean $notify  If true, adds notification info to object.
 596:      *
 597:      * @return object  The Horde JSON response.  It has the following
 598:      *                 properties:
 599:      *   - msgs: (array) [OPTIONAL] List of notification messages.
 600:      *   - response: (mixed) The response data for the request.
 601:      */
 602:     static public function prepareResponse($data = null, $notify = false)
 603:     {
 604:         $response = new stdClass();
 605:         $response->response = $data;
 606: 
 607:         if ($notify) {
 608:             $stack = $GLOBALS['notification']->notify(array('listeners' => 'status', 'raw' => true));
 609:             if (!empty($stack)) {
 610:                 $response->msgs = $stack;
 611:             }
 612:         }
 613: 
 614:         return $response;
 615:     }
 616: 
 617:     /**
 618:      * Send response data to browser.
 619:      *
 620:      * @param mixed $data  The data to serialize and send to the browser.
 621:      * @param string $ct   The content-type to send the data with.  Either
 622:      *                     'json', 'js-json', 'html', 'plain', and 'xml'.
 623:      */
 624:     static public function sendHTTPResponse($data, $ct)
 625:     {
 626:         // Output headers and encoded response.
 627:         switch ($ct) {
 628:         case 'json':
 629:         case 'js-json':
 630:             /* JSON responses are a structured object which always
 631:              * includes the response in a member named 'response', and an
 632:              * additional array of messages in 'msgs' which may be updates
 633:              * for the server or notification messages.
 634:              *
 635:              * Make sure no null bytes sneak into the JSON output stream.
 636:              * Null bytes cause IE to stop reading from the input stream,
 637:              * causing malformed JSON data and a failed request.  These
 638:              * bytes don't seem to break any other browser, but might as
 639:              * well remove them anyway.
 640:              *
 641:              * Finally, add prototypejs security delimiters to returned
 642:              * JSON. */
 643:             $s_data = str_replace("\00", '', self::escapeJson($data));
 644: 
 645:             if ($ct == 'json') {
 646:                 header('Content-Type: application/json');
 647:                 echo $s_data;
 648:             } else {
 649:                 header('Content-Type: text/html; charset=UTF-8');
 650:                 echo htmlspecialchars($s_data);
 651:             }
 652:             break;
 653: 
 654:         case 'html':
 655:         case 'plain':
 656:         case 'xml':
 657:             $s_data = is_string($data) ? $data : $data->response;
 658:             header('Content-Type: text/' . $ct . '; charset=UTF-8');
 659:             echo $s_data;
 660:             break;
 661: 
 662:         default:
 663:             echo $data;
 664:         }
 665: 
 666:         exit;
 667:     }
 668: 
 669:     /**
 670:      * Do necessary escaping to output JSON.
 671:      *
 672:      * @param mixed $data     The data to JSON-ify.
 673:      * @param array $options  Additional options:
 674:      * <pre>
 675:      * 'nodelimit' - (boolean) Don't add security delimiters?
 676:      *               DEFAULT: false
 677:      * 'urlencode' - (boolean) URL encode the json string
 678:      *               DEFAULT: false
 679:      * </pre>
 680:      *
 681:      * @return string  The escaped string.
 682:      */
 683:     static public function escapeJson($data, array $options = array())
 684:     {
 685:         $json = Horde_Serialize::serialize($data, Horde_Serialize::JSON);
 686:         if (empty($options['nodelimit'])) {
 687:             $json = '/*-secure-' . $json . '*/';
 688:         }
 689: 
 690:         return empty($options['urlencode'])
 691:             ? $json
 692:             : '\'' . rawurlencode($json) . '\'';
 693:     }
 694: 
 695:     /**
 696:      * Is the current HTTP connection considered secure?
 697:      * @TODO Move this to the request classes!
 698:      *
 699:      * @return boolean
 700:      */
 701:     static public function isConnectionSecure()
 702:     {
 703:         if ($GLOBALS['browser']->usingSSLConnection()) {
 704:             return true;
 705:         }
 706: 
 707:         if (!empty($GLOBALS['conf']['safe_ips'])) {
 708:             if (reset($GLOBALS['conf']['safe_ips']) == '*') {
 709:                 return true;
 710:             }
 711: 
 712:             /* $_SERVER['HTTP_X_FORWARDED_FOR'] is user data and not
 713:              * reliable. We don't consult it for safe IPs. We also have to
 714:              * assume that if it is present, the user is coming through a proxy
 715:              * server. If so, we don't count any non-SSL connection as safe, no
 716:              * matter the source IP. */
 717:             if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
 718:                 $remote_addr = $_SERVER['REMOTE_ADDR'];
 719:                 foreach ($GLOBALS['conf']['safe_ips'] as $safe_ip) {
 720:                     $safe_ip = preg_replace('/(\.0)*$/', '', $safe_ip);
 721:                     if (strpos($remote_addr, $safe_ip) === 0) {
 722:                         return true;
 723:                     }
 724:                 }
 725:             }
 726:         }
 727: 
 728:         return false;
 729:     }
 730: 
 731:     /**
 732:      * Throws an exception if not using a secure connection.
 733:      *
 734:      * @throws Horde_Exception
 735:      */
 736:     static public function requireSecureConnection()
 737:     {
 738:         if (!self::isConnectionSecure()) {
 739:             throw new Horde_Exception(Horde_Core_Translation::t("The encryption features require a secure web connection."));
 740:         }
 741:     }
 742: 
 743:     /**
 744:      * Loads global and vhost specific configuration files.
 745:      *
 746:      * @param string $config_file      The name of the configuration file.
 747:      * @param string|array $var_names  The name(s) of the variable(s) that
 748:      *                                 is/are defined in the configuration
 749:      *                                 file.
 750:      * @param string $app              The application. Defaults to the current
 751:      *                                 application.
 752:      * @param boolean $show_output     If true, the contents of the requested
 753:      *                                 config file are simply output instead of
 754:      *                                 loaded into a variable.
 755:      *
 756:      * @return mixed  The value of $var_names, in a compact()'ed array if
 757:      *                $var_names is an array.
 758:      * @throws Horde_Exception
 759:      */
 760:     static public function loadConfiguration($config_file, $var_names = null,
 761:                                              $app = null, $show_output = false)
 762:     {
 763:         global $registry;
 764: 
 765:         if (is_null($app)) {
 766:             $app = $registry->getApp();
 767:         }
 768: 
 769:         // Track if we've included some version (main or vhosted) of
 770:         // the config file.
 771:         $filelist = array();
 772:         $was_included = false;
 773: 
 774:         // Load global configuration file.
 775:         $config_dir = (($app == 'horde') && defined('HORDE_BASE'))
 776:             ? HORDE_BASE . '/config/'
 777:             : $registry->get('fileroot', $app) . '/config/';
 778:         $base_path = $file = $config_dir . $config_file;
 779:         if (file_exists($file)) {
 780:             $filelist[$file] = 1;
 781:         }
 782: 
 783:         // Load global configuration stanzas in .d directory
 784:         $directory = preg_replace('/\.php$/', '.d', $base_path);
 785:         if (file_exists($directory) &&
 786:             is_dir($directory) &&
 787:             ($sub_files = glob("$directory/*.php"))) {
 788:             foreach ($sub_files as $val) {
 789:                 $filelist[$val] = 0;
 790:             }
 791:         }
 792: 
 793:         // Load local version of configuration file
 794:         $file = substr($file, 0, strrpos($file, '.')) . '.local'
 795:             . substr($file, strrpos($file, '.'));
 796:         if (file_exists($file)) {
 797:             $filelist[$file] = 0;
 798:         }
 799: 
 800:         // Load vhost configuration file. The vhost conf.php for Horde is added
 801:         // later though, because the vhost configuration variable is not
 802:         // available at this point.
 803:         $vhost_added = false;
 804:         if (!empty($GLOBALS['conf']['vhosts'])) {
 805:             $file = $config_dir . substr($config_file, 0, -4) . '-' . $GLOBALS['conf']['server']['name'] . '.php';
 806: 
 807:             if (file_exists($file)) {
 808:                 $filelist[$file] = 0;
 809:             }
 810:             $vhost_added = true;
 811:         }
 812: 
 813:         /* We need to use a while-loop here because we modify $filelist inside
 814:          * the loop. */
 815:         while (list($file, $log_check) = each($filelist)) {
 816:             /* If we are not exporting variables located in the configuration
 817:              * file, or we are not capturing the output, then there is no
 818:              * need to load the configuration file more than once. */
 819:             self::startBuffer();
 820:             $success = (is_null($var_names) && !$show_output)
 821:                 ? include_once $file
 822:                 : include $file;
 823:             $output = self::endBuffer();
 824: 
 825:             if (!$success) {
 826:                 throw new Horde_Exception(sprintf('Failed to import configuration file "%s".', $file));
 827:             }
 828: 
 829:             if (!empty($output) && !$show_output) {
 830:                 /* Horde 3 -> 4 conversion checking. This is the only place
 831:                  * to catch PEAR_LOG errors. */
 832:                 if ($log_check &&
 833:                     isset($conf['log']['priority']) &&
 834:                     (strpos($conf['log']['priority'], 'PEAR_LOG_') !== false)) {
 835:                     $conf['log']['priority'] = 'INFO';
 836:                     self::logMessage('Logging priority is using the old PEAR_LOG constant', 'INFO');
 837:                 } else {
 838:                     throw new Horde_Exception(sprintf('Failed to import configuration file "%s": ', $file) . strip_tags($output));
 839:                 }
 840:             }
 841: 
 842:             // Load vhost conf.php for Horde now if necessary.
 843:             if (!$vhost_added &&
 844:                 $app == 'horde' &&
 845:                 $config_file == 'conf.php') {
 846:                 if (!empty($conf['vhosts'])) {
 847:                     $file = $config_dir . 'conf-' . $conf['server']['name'] . '.php';
 848:                     if (file_exists($file)) {
 849:                         $filelist[$file] = 0;
 850:                     }
 851:                 }
 852:                 $vhost_added = true;
 853:             }
 854: 
 855:             $was_included = true;
 856:         }
 857: 
 858:         // Return an error if neither main or vhosted versions of the config
 859:         // file exist.
 860:         if (!$was_included) {
 861:             throw new Horde_Exception(sprintf('Failed to import configuration file "%s".', $base_path));
 862:         }
 863: 
 864:         if (isset($output) && $show_output) {
 865:             echo $output;
 866:         }
 867: 
 868:         Horde::logMessage('Load config file (' . $config_file . '; app: ' . $app . ')', 'DEBUG');
 869: 
 870:         if (is_null($var_names)) {
 871:             return;
 872:         } elseif (is_array($var_names)) {
 873:             return compact($var_names);
 874:         } elseif (isset($$var_names)) {
 875:             return $$var_names;
 876:         }
 877: 
 878:         return array();
 879:     }
 880: 
 881:     /**
 882:      * Returns the driver parameters for the specified backend.
 883:      *
 884:      * @param mixed $backend  The backend system (e.g. 'prefs', 'categories',
 885:      *                        'contacts') being used.
 886:      *                        The used configuration array will be
 887:      *                        $conf[$backend]. If an array gets passed, it will
 888:      *                        be $conf[$key1][$key2].
 889:      * @param string $type    The type of driver. If null, will not merge with
 890:      *                        base config.
 891:      *
 892:      * @return array  The connection parameters.
 893:      */
 894:     static public function getDriverConfig($backend, $type = 'sql')
 895:     {
 896:         global $conf;
 897: 
 898:         if (!is_null($type)) {
 899:             $type = Horde_String::lower($type);
 900:         }
 901: 
 902:         if (is_array($backend)) {
 903:             $c = Horde_Array::getElement($conf, $backend);
 904:         } elseif (isset($conf[$backend])) {
 905:             $c = $conf[$backend];
 906:         } else {
 907:             $c = null;
 908:         }
 909: 
 910:         if (!is_null($c) && isset($c['params'])) {
 911:             $c['params']['umask'] = $conf['umask'];
 912:             return (!is_null($type) && isset($conf[$type]))
 913:                 ? array_merge($conf[$type], $c['params'])
 914:                 : $c['params'];
 915:         }
 916: 
 917:         return (!is_null($type) && isset($conf[$type]))
 918:             ? $conf[$type]
 919:             : array();
 920:     }
 921: 
 922:     /**
 923:      * Checks if all necessary parameters for a driver configuration
 924:      * are set and throws a fatal error with a detailed explanation
 925:      * how to fix this, if something is missing.
 926:      *
 927:      * @param array $params     The configuration array with all parameters.
 928:      * @param string $driver    The key name (in the configuration array) of
 929:      *                          the driver.
 930:      * @param array $fields     An array with mandatory parameter names for
 931:      *                          this driver.
 932:      * @param string $name      The clear text name of the driver. If not
 933:      *                          specified, the application name will be used.
 934:      * @param string $file      The configuration file that should contain
 935:      *                          these settings.
 936:      * @param string $variable  The name of the configuration variable.
 937:      *
 938:      * @throws Horde_Exception
 939:      */
 940:     static public function assertDriverConfig($params, $driver, $fields,
 941:                                               $name = null,
 942:                                               $file = 'conf.php',
 943:                                               $variable = '$conf')
 944:     {
 945:         global $registry;
 946: 
 947:         // Don't generate a fatal error if we fail during or before
 948:         // Registry instantiation.
 949:         if (is_null($name)) {
 950:             $name = isset($registry) ? $registry->getApp() : '[unknown]';
 951:         }
 952:         $fileroot = isset($registry) ? $registry->get('fileroot') : '';
 953: 
 954:         if (!is_array($params) || !count($params)) {
 955:             throw new Horde_Exception(
 956:                 sprintf(Horde_Core_Translation::t("No configuration information specified for %s."), $name) . "\n\n" .
 957:                 sprintf(Horde_Core_Translation::t("The file %s should contain some %s settings."),
 958:                     $fileroot . '/config/' . $file,
 959:                     sprintf("%s['%s']['params']", $variable, $driver)));
 960:         }
 961: 
 962:         foreach ($fields as $field) {
 963:             if (!isset($params[$field])) {
 964:                 throw new Horde_Exception(
 965:                     sprintf(Horde_Core_Translation::t("Required \"%s\" not specified in %s configuration."), $field, $name) . "\n\n" .
 966:                     sprintf(Horde_Core_Translation::t("The file %s should contain a %s setting."),
 967:                         $fileroot . '/config/' . $file,
 968:                         sprintf("%s['%s']['params']['%s']", $variable, $driver, $field)));
 969:             }
 970:         }
 971:     }
 972: 
 973:     /**
 974:      * Returns a session-id-ified version of $uri.
 975:      * If a full URL is requested, all parameter separators get converted to
 976:      * "&", otherwise to "&amp;".
 977:      *
 978:      * @param mixed $uri      The URI to be modified (either a string or any
 979:      *                        object with a __toString() function).
 980:      * @param boolean $full   Generate a full (http://server/path/) URL.
 981:      * @param mixed $opts     Additional options. If a string/integer, it is
 982:      *                        taken to be the 'append_session' option.  If an
 983:      *                        array, one of the following:
 984:      * <pre>
 985:      * 'app' - (string) Use this app for the webroot.
 986:      *         DEFAULT: current application
 987:      * 'append_session' - (integer) 0 = only if needed [DEFAULT], 1 = always,
 988:      *                    -1 = never.
 989:      * 'force_ssl' - (boolean) Ignore $conf['use_ssl'] and force creation of a
 990:      *               SSL URL?
 991:      *               DEFAULT: false
 992:      * 'noajax' - (boolean) Don't add AJAX UI parameter?
 993:      *            DEFAULT: false
 994:      * </pre>
 995:      *
 996:      * @return Horde_Url  The URL with the session id appended (if needed).
 997:      */
 998:     static public function url($uri, $full = false, $opts = array())
 999:     {
1000:         if (is_array($opts)) {
1001:             $append_session = isset($opts['append_session'])
1002:                 ? $opts['append_session']
1003:                 : 0;
1004:             if (!empty($opts['force_ssl'])) {
1005:                 $full = true;
1006:             }
1007:         } else {
1008:             $append_session = $opts;
1009:             $opts = array();
1010:         }
1011: 
1012:         $puri = parse_url($uri);
1013:         $url = '';
1014:         $schemeRegexp = '|^([a-zA-Z][a-zA-Z0-9+.-]{0,19})://|';
1015:         $webroot = ltrim($GLOBALS['registry']->get('webroot', empty($opts['app']) ? null : $opts['app']), '/');
1016: 
1017:         if ($full &&
1018:             !isset($puri['scheme']) &&
1019:             !preg_match($schemeRegexp, $webroot) ) {
1020:             /* Store connection parameters in local variables. */
1021:             $server_name = $GLOBALS['conf']['server']['name'];
1022:             $server_port = isset($GLOBALS['conf']['server']['port']) ? $GLOBALS['conf']['server']['port'] : '';
1023: 
1024:             $protocol = 'http';
1025:             switch ($GLOBALS['conf']['use_ssl']) {
1026:             case 1:
1027:                 $protocol = 'https';
1028:                 break;
1029: 
1030:             case 2:
1031:                 if ($GLOBALS['browser']->usingSSLConnection()) {
1032:                     $protocol = 'https';
1033:                 }
1034:                 break;
1035: 
1036:             case 3:
1037:                 $server_port = '';
1038:                 if (!empty($opts['force_ssl'])) {
1039:                     $protocol = 'https';
1040:                 }
1041:                 break;
1042:             }
1043: 
1044:             /* If using a non-standard port, add to the URL. */
1045:             if (!empty($server_port) &&
1046:                 ((($protocol == 'http') && ($server_port != 80)) ||
1047:                  (($protocol == 'https') && ($server_port != 443)))) {
1048:                 $server_name .= ':' . $server_port;
1049:             }
1050: 
1051:             $url = $protocol . '://' . $server_name;
1052:         } elseif (isset($puri['scheme'])) {
1053:             $url = $puri['scheme'] . '://' . $puri['host'];
1054: 
1055:             /* If using a non-standard port, add to the URL. */
1056:             if (isset($puri['port']) &&
1057:                 ((($puri['scheme'] == 'http') && ($puri['port'] != 80)) ||
1058:                  (($puri['scheme'] == 'https') && ($puri['port'] != 443)))) {
1059:                 $url .= ':' . $puri['port'];
1060:             }
1061:         }
1062: 
1063:         if (isset($puri['path']) &&
1064:             (substr($puri['path'], 0, 1) == '/') &&
1065:             (!preg_match($schemeRegexp, $webroot) ||
1066:              (preg_match($schemeRegexp, $webroot) && isset($puri['scheme'])))) {
1067:             $url .= $puri['path'];
1068:         } elseif (isset($puri['path']) && preg_match($schemeRegexp, $webroot)) {
1069:             if (substr($puri['path'], 0, 1) == '/') {
1070:                 $pwebroot = parse_url($webroot);
1071:                 $url = $pwebroot['scheme'] . '://' . $pwebroot['host']
1072:                     . $puri['path'];
1073:             } else {
1074:                 $url = $webroot . '/' . $puri['path'];
1075:             }
1076:         } else {
1077:             $url .= '/' . ($webroot ? $webroot . '/' : '') . (isset($puri['path']) ? $puri['path'] : '');
1078:         }
1079: 
1080:         if (isset($puri['query'])) {
1081:             $url .= '?' . $puri['query'];
1082:         }
1083: 
1084:         $ob = new Horde_Url($url, $full);
1085: 
1086:         if (empty($GLOBALS['conf']['session']['use_only_cookies']) &&
1087:             (($append_session == 1) ||
1088:              (($append_session == 0) && !isset($_COOKIE[session_name()])))) {
1089:             $ob->add(session_name(), session_id());
1090:         }
1091: 
1092:         if (empty($opts['noajax']) &&
1093:             ($append_session >= 0) &&
1094:             Horde_Util::getFormData('ajaxui')) {
1095:             $ob->add('ajaxui', 1);
1096:         }
1097: 
1098:         return $ob;
1099:     }
1100: 
1101:     /**
1102:      * Returns an external link passed through the dereferrer to strip session
1103:      * IDs from the referrer.
1104:      *
1105:      * @param string $url   The external URL to link to.
1106:      * @param boolean $tag  If true, a complete <a> tag is returned, only the
1107:      *                      url otherwise.
1108:      *
1109:      * @return string  The link to the dereferrer script.
1110:      */
1111:     static public function externalUrl($url, $tag = false)
1112:     {
1113:         if (!isset($_GET[session_name()]) ||
1114:             Horde_String::substr($url, 0, 1) == '#' ||
1115:             Horde_String::substr($url, 0, 7) == 'mailto:') {
1116:             $ext = $url;
1117:         } else {
1118:             $ext = self::getServiceLink('go', 'horde');
1119: 
1120:             /* We must make sure there are no &amp's in the URL. */
1121:             $url = preg_replace(array('/(=?.*?)&amp;(.*?=)/', '/(=?.*?)&amp;(.*?=)/'), '$1&$2', $url);
1122:             $ext .= '?' . self::signQueryString('url=' . urlencode($url));
1123:         }
1124: 
1125:         if ($tag) {
1126:             $ext = self::link($ext, $url, '', '_blank');
1127:         }
1128: 
1129:         return $ext;
1130:     }
1131: 
1132:     /**
1133:      * Returns a URL to be used for downloading, that takes into account any
1134:      * special browser quirks (i.e. IE's broken filename handling).
1135:      *
1136:      * @param string $filename  The filename of the download data.
1137:      * @param array $params     Any additional parameters needed.
1138:      * @param string $url       The URL to alter. If none passed in, will use
1139:      *                          the file 'view.php' located in the current
1140:      *                          app's base directory.
1141:      *
1142:      * @return Horde_Url  The download URL.
1143:      */
1144:     static public function downloadUrl($filename, $params = array(),
1145:                                        $url = null)
1146:     {
1147:         global $browser, $registry;
1148: 
1149:         $horde_url = false;
1150: 
1151:         if (is_null($url)) {
1152:             $url = self::getServiceLink('download', $registry->getApp());
1153:             $horde_url = true;
1154:         }
1155: 
1156:         /* Add parameters. */
1157:         if (!is_null($params)) {
1158:             $url->add($params);
1159:         }
1160: 
1161:         /* If we are using the default Horde download link, add the
1162:          * filename to the end of the URL. Although not necessary for
1163:          * many browsers, this should allow every browser to download
1164:          * correctly. */
1165:         if ($horde_url) {
1166:             $url->add('fn', '/' . rawurlencode($filename));
1167:         } elseif ($browser->hasQuirk('break_disposition_filename')) {
1168:             /* Some browsers will only obtain the filename correctly
1169:              * if the extension is the last argument in the query
1170:              * string and rest of the filename appears in the
1171:              * PATH_INFO element. */
1172:             $url = (string)$url;
1173:             $filename = rawurlencode($filename);
1174: 
1175:             /* Get the webserver ID. */
1176:             $server = self::webServerID();
1177: 
1178:             /* Get the name and extension of the file.  Apache 2 does
1179:              * NOT support PATH_INFO information being passed to the
1180:              * PHP module by default, so disable that
1181:              * functionality. */
1182:             if (($server != 'apache2')) {
1183:                 if (($pos = strrpos($filename, '.'))) {
1184:                     $name = '/' . preg_replace('/\./', '%2E', substr($filename, 0, $pos));
1185:                     $ext = substr($filename, $pos);
1186:                 } else {
1187:                     $name = '/' . $filename;
1188:                     $ext = '';
1189:                 }
1190: 
1191:                 /* Enter the PATH_INFO information. */
1192:                 if (($pos = strpos($url, '?'))) {
1193:                     $url = substr($url, 0, $pos) . $name . substr($url, $pos);
1194:                 } else {
1195:                     $url .= $name;
1196:                 }
1197:             }
1198:             $url = new Horde_Url($url);
1199: 
1200:             /* Append the extension, if it exists. */
1201:             if (($server == 'apache2') || !empty($ext)) {
1202:                 $url->add('fn_ext', '/' . $filename);
1203:             }
1204:         }
1205: 
1206:         return $url;
1207:     }
1208: 
1209:     /**
1210:      * Returns an anchor tag with the relevant parameters
1211:      *
1212:      * @param Horde_Url|string $url  The full URL to be linked to.
1213:      * @param string $title          The link title/description.
1214:      * @param string $class          The CSS class of the link.
1215:      * @param string $target         The window target to point to.
1216:      * @param string $onclick        JavaScript action for the 'onclick' event.
1217:      * @param string $title2         The link title (tooltip) (deprecated - just
1218:      *                               use $title).
1219:      * @param string $accesskey      The access key to use.
1220:      * @param array $attributes      Any other name/value pairs to add to the
1221:      *                               <a> tag.
1222:      * @param boolean $escape        Whether to escape special characters in the
1223:      *                               title attribute.
1224:      *
1225:      * @return string  The full <a href> tag.
1226:      */
1227:     static public function link($url = '', $title = '', $class = '',
1228:                                 $target = '', $onclick = '', $title2 = '',
1229:                                 $accesskey = '', $attributes = array(),
1230:                                 $escape = true)
1231:     {
1232:         if (!($url instanceof Horde_Url)) {
1233:             $url = new Horde_Url($url);
1234:         }
1235: 
1236:         if (!empty($title2)) {
1237:             $title = $title2;
1238:         }
1239:         if (!empty($onclick)) {
1240:             $attributes['onclick'] = $onclick;
1241:         }
1242:         if (!empty($class)) {
1243:             $attributes['class'] = $class;
1244:         }
1245:         if (!empty($target)) {
1246:             $attributes['target'] = $target;
1247:         }
1248:         if (!empty($accesskey)) {
1249:             $attributes['accesskey'] = $accesskey;
1250:         }
1251:         if (!empty($title)) {
1252:             if ($escape) {
1253:                 $title = str_replace(
1254:                     array("\r", "\n"), '',
1255:                     htmlspecialchars(nl2br(htmlspecialchars($title))));
1256:                 /* Remove double encoded entities. */
1257:                 $title = preg_replace('/&amp;([a-z]+|(#\d+));/i', '&\\1;', $title);
1258:             }
1259:             $attributes['title.raw'] = $title;
1260:         }
1261: 
1262:         return $url->link($attributes);
1263:     }
1264: 
1265:     /**
1266:      * Uses DOM Tooltips to display the 'title' attribute for link() calls.
1267:      *
1268:      * @param string $url        The full URL to be linked to
1269:      * @param string $status     The JavaScript mouse-over string
1270:      * @param string $class      The CSS class of the link
1271:      * @param string $target     The window target to point to.
1272:      * @param string $onclick    JavaScript action for the 'onclick' event.
1273:      * @param string $title      The link title (tooltip). Most not contain
1274:      *                           HTML data other than &lt;br&gt;, which will
1275:      *                           be converted to a linebreak.
1276:      * @param string $accesskey  The access key to use.
1277:      * @param array  $attributes Any other name/value pairs to add to the
1278:      *                           &lt;a&gt; tag.
1279:      *
1280:      * @return string  The full <a href> tag.
1281:      */
1282:     static public function linkTooltip($url, $status = '', $class = '',
1283:                                        $target = '', $onclick = '',
1284:                                        $title = '', $accesskey = '',
1285:                                        $attributes = array())
1286:     {
1287:         if (strlen($title)) {
1288:             $attributes['nicetitle'] = Horde_Serialize::serialize(explode("\n", preg_replace('/<br\s*\/?\s*>/', "\n", $title)), Horde_Serialize::JSON);
1289:             $title = null;
1290:             self::addScriptFile('tooltips.js', 'horde');
1291:         }
1292: 
1293:         return self::link($url, $title, $class, $target, $onclick, null, $accesskey, $attributes, false);
1294:     }
1295: 
1296:     /**
1297:      * Returns an anchor sequence with the relevant parameters for a widget
1298:      * with accesskey and text.
1299:      *
1300:      * @param string  $url      The full URL to be linked to.
1301:      * @param string  $title    The link title/description.
1302:      * @param string  $class    The CSS class of the link
1303:      * @param string  $target   The window target to point to.
1304:      * @param string  $onclick  JavaScript action for the 'onclick' event.
1305:      * @param string  $title2   The link title (tooltip) (deprecated - just use
1306:      *                          $title).
1307:      * @param boolean $nocheck  Don't check if the access key already has been
1308:      *                          used. Defaults to false (= check).
1309:      *
1310:      * @return string  The full <a href>Title</a> sequence.
1311:      */
1312:     static public function widget($url, $title = '', $class = 'widget',
1313:                                   $target = '', $onclick = '', $title2 = '',
1314:                                   $nocheck = false)
1315:     {
1316:         if (!empty($title2)) {
1317:             $title = $title2;
1318:         }
1319: 
1320:         $ak = self::getAccessKey($title, $nocheck);
1321: 
1322:         return self::link($url, '', $class, $target, $onclick, '', $ak) . self::highlightAccessKey($title, $ak) . '</a>';
1323:     }
1324: 
1325:     /**
1326:      * Returns a session-id-ified version of $SCRIPT_NAME resp. $PHP_SELF.
1327:      *
1328:      * @param boolean $script_params Include script parameters like
1329:      *                               QUERY_STRING and PATH_INFO?
1330:      * @param boolean $nocache       Include a cache-buster parameter in the
1331:      *                               URL?
1332:      * @param boolean $full          Return a full URL?
1333:      * @param boolean $force_ssl     Ignore $conf['use_ssl'] and force creation
1334:      *                               of a SSL URL?
1335:      *
1336:      * @return Horde_Url  The requested URL.
1337:      */
1338:     static public function selfUrl($script_params = false, $nocache = true,
1339:                                    $full = false, $force_ssl = false)
1340:     {
1341:         if (!strncmp(PHP_SAPI, 'cgi', 3)) {
1342:             // When using CGI PHP, SCRIPT_NAME may contain the path to
1343:             // the PHP binary instead of the script being run; use
1344:             // PHP_SELF instead.
1345:             $url = $_SERVER['PHP_SELF'];
1346:         } else {
1347:             $url = isset($_SERVER['SCRIPT_NAME']) ?
1348:                 $_SERVER['SCRIPT_NAME'] :
1349:                 $_SERVER['PHP_SELF'];
1350:         }
1351:         if (isset($_SERVER['REQUEST_URI'])) {
1352:             $url = Horde_String::common($_SERVER['REQUEST_URI'], $url);
1353:         }
1354:         if (substr($url, -9) == 'index.php') {
1355:             $url = substr($url, 0, -9);
1356:         }
1357: 
1358:         if ($script_params) {
1359:             if ($pathInfo = Horde_Util::getPathInfo()) {
1360:                 if (substr($url, -1) == '/') {
1361:                     $pathInfo = substr($pathInfo, 1);
1362:                 }
1363:                 $url .= $pathInfo;
1364:             }
1365:             if (!empty($_SERVER['QUERY_STRING'])) {
1366:                 $url .= '?' . $_SERVER['QUERY_STRING'];
1367:             }
1368:         }
1369: 
1370:         $url = self::url($url, $full, array('force_ssl' => $force_ssl));
1371: 
1372:         return ($nocache && $GLOBALS['browser']->hasQuirk('cache_same_url'))
1373:             ? $url->unique()
1374:             : $url;
1375:     }
1376: 
1377:     /**
1378:      * Constructs a correctly-pathed tag to an image.
1379:      *
1380:      * @param mixed $src   The image file (either a string or a
1381:      *                     Horde_Themes_Image object).
1382:      * @param string $alt  Text describing the image.
1383:      * @param mixed $attr  Any additional attributes for the image tag. Can
1384:      *                     be a pre-built string or an array of key/value
1385:      *                     pairs that will be assembled and html-encoded.
1386:      *
1387:      * @return string  The full image tag.
1388:      */
1389:     static public function img($src, $alt = '', $attr = '')
1390:     {
1391:         /* If browser does not support images, simply return the ALT text. */
1392:         if (!$GLOBALS['browser']->hasFeature('images')) {
1393:             return htmlspecialchars($alt);
1394:         }
1395: 
1396:         /* If no directory has been specified, get it from the registry. */
1397:         if (!($src instanceof Horde_Themes_Image) && (substr($src, 0, 1) != '/')) {
1398:             $src = Horde_Themes::img($src);
1399:         }
1400: 
1401:         /* Build all of the tag attributes. */
1402:         $attributes = array('alt' => $alt);
1403:         if (is_array($attr)) {
1404:             $attributes = array_merge($attributes, $attr);
1405:         }
1406: 
1407:         $img = '<img';
1408:         foreach ($attributes as $attribute => $value) {
1409:             $img .= ' ' . $attribute . '="' . @htmlspecialchars($value) . '"';
1410:         }
1411: 
1412:         /* If the user supplied a pre-built string of attributes, add that. */
1413:         if (is_string($attr) && !empty($attr)) {
1414:             $img .= ' ' . $attr;
1415:         }
1416: 
1417:         /* Return the closed image tag. */
1418:         return $img . ' src="' . (empty($GLOBALS['conf']['nobase64_img']) ? self::base64ImgData($src) : $src) . '" />';
1419:     }
1420: 
1421:     /**
1422:      * Same as img(), but returns a full source url for the image.
1423:      * Useful for when the image may be part of embedded Horde content on an
1424:      * external site.
1425:      *
1426:      * @see img()
1427:      */
1428:     static public function fullSrcImg($src, $options = array())
1429:     {
1430:         /* If browser does not support images, simply return the ALT text. */
1431:         if (!$GLOBALS['browser']->hasFeature('images')) {
1432:             return '';
1433:         }
1434: 
1435:         /* If no directory has been specified, get it from the registry. */
1436:         if (!($src instanceof Horde_Themes_Image) && (substr($src, 0, 1) != '/')) {
1437:             $src = Horde_Themes::img($src, $options);
1438:         }
1439: 
1440:         /* If we can send as data, no need to get the full path */
1441:         if (!empty($GLOBALS['conf']['nobase64_img'])) {
1442:             $src = self::base64ImgData($src);
1443:         }
1444:         if (substr($src, 0, 10) != 'data:image') {
1445:             $src = self::url($src, true, array('append_session' => -1));
1446:         }
1447: 
1448:         $img = '<img';
1449:         if (!empty($options['attr'])) {
1450:             /* Build all of the tag attributes. */
1451:             if (is_array($options['attr'])) {
1452:                 foreach ($options['attr'] as $attribute => $value) {
1453:                     $img .= ' ' . $attribute . '="' . htmlspecialchars($value) . '"';
1454:                 }
1455:             }
1456: 
1457:             /* If the user supplied a pre-built string of attributes, add
1458:              * that. */
1459:             if (is_string($options['attr'])) {
1460:                 $img .= ' ' . $options['attr'];
1461:             }
1462:         }
1463: 
1464:         /* Return the closed image tag. */
1465:         return $img . ' src="' . $src . '" />';
1466:     }
1467: 
1468:     /**
1469:      * Generate RFC 2397-compliant image data strings.
1470:      *
1471:      * @param mixed $in       URI or Horde_Themes_Image object containing
1472:      *                        image data.
1473:      * @param integer $limit  Sets a hard size limit for image data; if
1474:      *                        exceeded, will not string encode.
1475:      *
1476:      * @return string  The string to use in the image 'src' attribute; either
1477:      *                 the image data if the browser supports, or the URI
1478:      *                 if not.
1479:      */
1480:     static public function base64ImgData($in, $limit = null)
1481:     {
1482:         $dataurl = $GLOBALS['browser']->hasFeature('dataurl');
1483:         if (!$dataurl) {
1484:             return $in;
1485:         }
1486: 
1487:         if (!is_null($limit) &&
1488:             (is_bool($dataurl) || ($limit < $dataurl))) {
1489:             $dataurl = $limit;
1490:         }
1491: 
1492:         /* Only encode image files if they are below the dataurl limit. */
1493:         if (!($in instanceof Horde_Themes_Image)) {
1494:             $in = Horde_Themes_Image::fromUri($in);
1495:         }
1496:         if (!file_exists($in->fs)) {
1497:             return $in->uri;
1498:         }
1499: 
1500:         /* Delete approx. 50 chars from the limit to account for the various
1501:          * data/base64 header text.  Multiply by 0.75 to determine the
1502:          * base64 encoded size. */
1503:         return (($dataurl === true) ||
1504:                 (filesize($in->fs) <= (($dataurl * 0.75) - 50)))
1505:             ? 'data:' . Horde_Mime_Magic::extToMime(substr($in->uri, strrpos($in->uri, '.') + 1)) . ';base64,' . base64_encode(file_get_contents($in->fs))
1506:             : $in->uri;
1507:     }
1508: 
1509:     /**
1510:      * Generate the favicon tag for the current application.
1511:      */
1512:     static public function includeFavicon()
1513:     {
1514:         $img = strval(Horde_Themes::img('favicon.ico', array(
1515:             'nohorde' => true
1516:         )));
1517:         if (!$img) {
1518:             $img = strval(Horde_Themes::img('favicon.ico', array(
1519:                 'app' => 'horde'
1520:             )));
1521:         }
1522: 
1523:         echo '<link href="' . $img . '" rel="SHORTCUT ICON" />';
1524:     }
1525: 
1526:     /**
1527:      * Generate the stylesheet tags for the current application.
1528:      *
1529:      * @param array $opts  Options to pass to
1530:      *                     Horde_Themes_Css::getStylesheetUrls().
1531:      * @param array $full  Return a full URL?
1532:      */
1533:     static public function includeStylesheetFiles(array $opts = array(),
1534:                                                   $full = false)
1535:     {
1536:         foreach ($GLOBALS['injector']->getInstance('Horde_Themes_Css')->getStylesheetUrls($opts) as $val) {
1537:             echo '<link href="' . $val->toString(false, $full) . '" rel="stylesheet" type="text/css" />';
1538:         }
1539:     }
1540: 
1541:     /**
1542:      * Determines the location of the system temporary directory. If a specific
1543:      * configuration cannot be found, it defaults to /tmp.
1544:      *
1545:      * @return string  A directory name that can be used for temp files.
1546:      *                 Returns false if one could not be found.
1547:      */
1548:     static public function getTempDir()
1549:     {
1550:         global $conf;
1551: 
1552:         /* If one has been specifically set, then use that */
1553:         if (!empty($conf['tmpdir'])) {
1554:             $tmp = $conf['tmpdir'];
1555:         }
1556: 
1557:         /* Next, try Horde_Util::getTempDir(). */
1558:         if (empty($tmp)) {
1559:             $tmp = Horde_Util::getTempDir();
1560:         }
1561: 
1562:         /* If it is still empty, we have failed, so return false;
1563:          * otherwise return the directory determined. */
1564:         return empty($tmp)
1565:             ? false
1566:             : $tmp;
1567:     }
1568: 
1569:     /**
1570:      * Creates a temporary filename for the lifetime of the script, and
1571:      * (optionally) registers it to be deleted at request shutdown.
1572:      *
1573:      * @param string $prefix           Prefix to make the temporary name more
1574:      *                                 recognizable.
1575:      * @param boolean $delete          Delete the file at the end of the
1576:      *                                 request?
1577:      * @param string $dir              Directory to create the temporary file
1578:      *                                 in.
1579:      * @param boolean $secure          If deleting file, should we securely
1580:      *                                 delete the file?
1581:      * @param boolean $session_remove  Delete this file when session is
1582:      *                                 destroyed (since 1.7.0)?
1583:      *
1584:      * @return string   Returns the full path-name to the temporary file or
1585:      *                  false if a temporary file could not be created.
1586:      */
1587:     static public function getTempFile($prefix = 'Horde', $delete = true,
1588:                                        $dir = '', $secure = false,
1589:                                        $session_remove = false)
1590:     {
1591:         if (empty($dir) || !is_dir($dir)) {
1592:             $dir = self::getTempDir();
1593:         }
1594:         $tmpfile = Horde_Util::getTempFile($prefix, $delete, $dir, $secure);
1595:         if ($session_remove) {
1596:             $gcfiles = $GLOBALS['session']->get('horde', 'gc_tempfiles', Horde_Session::TYPE_ARRAY);
1597:             $gcfiles[] = $tmpfile;
1598:             $GLOBALS['session']->set('horde', 'gc_tempfiles', $gcfiles);
1599:         }
1600: 
1601:         return $tmpfile;
1602:     }
1603: 
1604:     /**
1605:      * Starts output compression, if requested.
1606:      */
1607:     static public function compressOutput()
1608:     {
1609:         if (self::$_compressStart) {
1610:             return;
1611:         }
1612: 
1613:         /* Compress output if requested and possible. */
1614:         if ($GLOBALS['conf']['compress_pages'] &&
1615:             !$GLOBALS['browser']->hasQuirk('buggy_compression') &&
1616:             !(bool)ini_get('zlib.output_compression') &&
1617:             !(bool)ini_get('zend_accelerator.compress_all') &&
1618:             ini_get('output_handler') != 'ob_gzhandler') {
1619:             if (ob_get_level()) {
1620:                 ob_end_clean();
1621:             }
1622:             ob_start('ob_gzhandler');
1623:         }
1624: 
1625:         self::$_compressStart = true;
1626:     }
1627: 
1628:     /**
1629:      * Determines if output compression can be used.
1630:      *
1631:      * @return boolean  True if output compression can be used, false if not.
1632:      */
1633:     static public function allowOutputCompression()
1634:     {
1635:         return !$GLOBALS['browser']->hasQuirk('buggy_compression') &&
1636:                (ini_get('zlib.output_compression') == '') &&
1637:                (ini_get('zend_accelerator.compress_all') == '') &&
1638:                (ini_get('output_handler') != 'ob_gzhandler');
1639:     }
1640: 
1641:     /**
1642:      * Returns the Web server being used.
1643:      * PHP string list built from the PHP 'configure' script.
1644:      *
1645:      * @return string  A web server identification string.
1646:      * @see php_sapi_name()
1647:      */
1648:     static public function webServerID()
1649:     {
1650:         switch (PHP_SAPI) {
1651:         case 'apache':
1652:             return 'apache1';
1653: 
1654:         case 'apache2filter':
1655:         case 'apache2handler':
1656:             return 'apache2';
1657: 
1658:         default:
1659:             return PHP_SAPI;
1660:         }
1661:     }
1662: 
1663:     /**
1664:      * Returns an un-used access key from the label given.
1665:      *
1666:      * @param string $label     The label to choose an access key from.
1667:      * @param boolean $nocheck  Don't check if the access key already has been
1668:      *                          used?
1669:      *
1670:      * @return string  A single lower case character access key or empty
1671:      *                 string if none can be found
1672:      */
1673:     static public function getAccessKey($label, $nocheck = false,
1674:                                         $shutdown = false)
1675:     {
1676:         /* Shutdown call for translators? */
1677:         if ($shutdown) {
1678:             if (!count(self::$_labels)) {
1679:                 return;
1680:             }
1681:             $script = basename($_SERVER['PHP_SELF']);
1682:             $labels = array_keys(self::$_labels);
1683:             sort($labels);
1684:             $used = array_keys(self::$_used);
1685:             sort($used);
1686:             $remaining = str_replace($used, array(), 'abcdefghijklmnopqrstuvwxyz');
1687:             self::logMessage('Access key information for ' . $script);
1688:             self::logMessage('Used labels: ' . implode(',', $labels));
1689:             self::logMessage('Used keys: ' . implode('', $used));
1690:             self::logMessage('Free keys: ' . $remaining);
1691:             return;
1692:         }
1693: 
1694:         /* Use access keys at all? */
1695:         if (!isset(self::$_noAccessKey)) {
1696:             self::$_noAccessKey = !$GLOBALS['browser']->hasFeature('accesskey') || !$GLOBALS['prefs']->getValue('widget_accesskey');
1697:         }
1698: 
1699:         if (self::$_noAccessKey || !preg_match('/_([A-Za-z])/', $label, $match)) {
1700:             return '';
1701:         }
1702:         $key = $match[1];
1703: 
1704:         /* Has this key already been used? */
1705:         if (isset(self::$_used[strtolower($key)]) &&
1706:             !($nocheck && isset(self::$_labels[$label]))) {
1707:             return '';
1708:         }
1709: 
1710:         /* Save key and label. */
1711:         self::$_used[strtolower($key)] = true;
1712:         self::$_labels[$label] = true;
1713: 
1714:         return $key;
1715:     }
1716: 
1717:     /**
1718:      * Strips an access key from a label.
1719:      * For multibyte charset strings the access key gets removed completely,
1720:      * otherwise only the underscore gets removed.
1721:      *
1722:      * @param string $label  The label containing an access key.
1723:      *
1724:      * @return string  The label with the access key being stripped.
1725:      */
1726:     static public function stripAccessKey($label)
1727:     {
1728:         return preg_replace('/_([A-Za-z])/', $GLOBALS['registry']->nlsconfig->curr_multibyte && preg_match('/[\x80-\xff]/', $label) ? '' : '\1', $label);
1729:     }
1730: 
1731:     /**
1732:      * Highlights an access key in a label.
1733:      *
1734:      * @param string $label      The label to highlight the access key in.
1735:      * @param string $accessKey  The access key to highlight.
1736:      *
1737:      * @return string  The HTML version of the label with the access key
1738:      *                 highlighted.
1739:      */
1740:     static public function highlightAccessKey($label, $accessKey)
1741:     {
1742:         $stripped_label = self::stripAccessKey($label);
1743: 
1744:         if (empty($accessKey)) {
1745:             return $stripped_label;
1746:         }
1747: 
1748:         if ($GLOBALS['registry']->nlsconfig->curr_multibyte) {
1749:             /* Prefix parenthesis with the UTF-8 representation of the LRO
1750:              * (Left-to-Right-Override) Unicode codepoint U+202D. */
1751:             return $stripped_label . "\xe2\x80\xad" .
1752:                 '(<span class="accessKey">' . strtoupper($accessKey) .
1753:                 '</span>' . ')';
1754:         }
1755: 
1756:         return str_replace('_' . $accessKey, '<span class="accessKey">' . $accessKey . '</span>', $label);
1757:     }
1758: 
1759:     /**
1760:      * Returns the appropriate "accesskey" and "title" attributes for an HTML
1761:      * tag and the given label.
1762:      *
1763:      * @param string $label     The title of an HTML element
1764:      * @param boolean $nocheck  Don't check if the access key already has been
1765:      *                          used?
1766:      *
1767:      * @return string  The title, and if appropriate, the accesskey attributes
1768:      *                 for the element.
1769:      */
1770:     static public function getAccessKeyAndTitle($label, $nocheck = false)
1771:     {
1772:         $ak = self::getAccessKey($label, $nocheck);
1773:         $attributes = 'title="' . self::stripAccessKey($label);
1774:         if (!empty($ak)) {
1775:             $attributes .= sprintf(Horde_Core_Translation::t(" (Accesskey %s)"), strtoupper($ak))
1776:               . '" accesskey="' . $ak;
1777:         }
1778: 
1779:         return $attributes . '"';
1780:     }
1781: 
1782:     /**
1783:      * Returns a label element including an access key for usage in conjuction
1784:      * with a form field. User preferences regarding access keys are respected.
1785:      *
1786:      * @param string $for    The form field's id attribute.
1787:      * @param string $label  The label text.
1788:      * @param string $ak     The access key to use. If null a new access key
1789:      *                       will be generated.
1790:      *
1791:      * @return string  The html code for the label element.
1792:      */
1793:     static public function label($for, $label, $ak = null)
1794:     {
1795:         if (is_null($ak)) {
1796:             $ak = self::getAccessKey($label, 1);
1797:         }
1798:         $label = self::highlightAccessKey($label, $ak);
1799: 
1800:         return sprintf('<label for="%s"%s>%s</label>',
1801:                        $for,
1802:                        !empty($ak) ? ' accesskey="' . $ak . '"' : '',
1803:                        $label);
1804:     }
1805: 
1806:     /**
1807:      * Call a Horde hook, handling all of the necessary lookups and parsing
1808:      * of the hook code.
1809:      *
1810:      * WARNING: Throwing exceptions is expensive, so use callHook() with care
1811:      * and cache the results if you going to use the results more than once.
1812:      *
1813:      * @param string $hook  The function to call.
1814:      * @param array  $args  An array of any arguments to pass to the hook
1815:      *                      function.
1816:      * @param string $app   The hook application.
1817:      *
1818:      * @return mixed  The results of the hook.
1819:      * @throws Horde_Exception  Thrown on error from hook code.
1820:      * @throws Horde_Exception_HookNotSet  Thrown if hook is not active.
1821:      */
1822:     static public function callHook($hook, $args = array(), $app = 'horde')
1823:     {
1824:         if (!self::hookExists($hook, $app)) {
1825:             throw new Horde_Exception_HookNotSet();
1826:         }
1827: 
1828:         $hook_class = $app . '_Hooks';
1829:         $hook_ob = new $hook_class;
1830:         try {
1831:             self::logMessage(sprintf('Hook %s in application %s called.', $hook, $app), 'DEBUG');
1832:             return call_user_func_array(array($hook_ob, $hook), $args);
1833:         } catch (Horde_Exception $e) {
1834:             self::logMessage($e, 'ERR');
1835:             throw $e;
1836:         }
1837:     }
1838: 
1839:     /**
1840:      * Returns whether a hook exists.
1841:      *
1842:      * Use this if you have to call a hook many times and expect the hook to
1843:      * not exist.
1844:      *
1845:      * @param string $hook  The function to call.
1846:      * @param string $app   The hook application.
1847:      *
1848:      * @return boolean  True if the hook exists.
1849:      */
1850:     static public function hookExists($hook, $app = 'horde')
1851:     {
1852:         $hook_class = $app . '_Hooks';
1853: 
1854:         if (!isset(self::$_hooksLoaded[$app])) {
1855:             self::$_hooksLoaded[$app] = false;
1856:             if (!class_exists($hook_class, false)) {
1857:                 try {
1858:                     self::loadConfiguration('hooks.php', null, $app);
1859:                     self::$_hooksLoaded[$app] = array();
1860:                 } catch (Horde_Exception $e) {}
1861:             }
1862:         }
1863: 
1864:         if (self::$_hooksLoaded[$app] === false) {
1865:             return false;
1866:         }
1867: 
1868:         if (!isset(self::$_hooksLoaded[$app][$hook])) {
1869:             self::$_hooksLoaded[$app][$hook] =
1870:                 class_exists($hook_class, false) &&
1871:                 ($hook_ob = new $hook_class) &&
1872:                 method_exists($hook_ob, $hook);
1873:         }
1874: 
1875:         return self::$_hooksLoaded[$app][$hook];
1876:     }
1877: 
1878:     /**
1879:      * Utility function to send redirect headers to browser, handling any
1880:      * browser quirks.
1881:      *
1882:      * @param string $url  The URL to redirect to.
1883:      */
1884:     static public function redirect($url)
1885:     {
1886:         if ($GLOBALS['browser']->isBrowser('msie') &&
1887:             ($GLOBALS['conf']['use_ssl'] == 3) &&
1888:             (strlen($url) < 160)) {
1889:             header('Refresh: 0; URL=' . $url);
1890:         } else {
1891:             header('Location: ' . $url);
1892:         }
1893:         exit;
1894:     }
1895: 
1896:     /**
1897:      * Add inline javascript to the output buffer.
1898:      *
1899:      * @param mixed $script    The script text to add (can be stored in an
1900:      *                         array also).
1901:      * @param string $onload   Load the script after the page has loaded?
1902:      *                         Either 'dom' (on dom:loaded), 'load'.
1903:      * @param boolean $top     Add script to top of stack?
1904:      */
1905:     static public function addInlineScript($script, $onload = null,
1906:                                            $top = false)
1907:     {
1908:         if (is_array($script)) {
1909:             $script = implode(';', $script);
1910:         }
1911: 
1912:         $script = trim($script);
1913:         if (empty($script)) {
1914:             return;
1915:         }
1916: 
1917:         if (is_null($onload)) {
1918:             $onload = 'none';
1919:         }
1920: 
1921:         $script = trim($script, ';') . ';';
1922: 
1923:         if ($top && isset(self::$_inlineScript[$onload])) {
1924:             array_unshift(self::$_inlineScript[$onload], $script);
1925:         } else {
1926:             self::$_inlineScript[$onload][] = $script;
1927:         }
1928: 
1929:         // If headers have already been sent, we need to output a
1930:         // <script> tag directly.
1931:         if (self::contentSent()) {
1932:             self::outputInlineScript();
1933:         }
1934:     }
1935: 
1936:     /**
1937:      * Add inline javascript variable definitions to the output buffer.
1938:      *
1939:      * @param array $data  Keys are the variable names, values are the data
1940:      *                     to JSON encode.  If the key begins with a '-',
1941:      *                     the data will be added to the output as-is.
1942:      * @param array $opts  Options:
1943:      * <pre>
1944:      * onload - (string) Wrap the definition in an onload handler? Either
1945:      *          'dom' (on dom:loaded), 'load'.
1946:      *          DEFAULT: false
1947:      * ret_vars - (boolean) If true, will return the list of variable
1948:      *            definitions instead of outputting to page.
1949:      *            DEFAULT: false
1950:      * top - (boolean) Add definitions to top of stack?
1951:      *       DEFAULT: false
1952:      * </pre>
1953:      *
1954:      * @return array  Returns the variable list of 'ret_vars' option is true.
1955:      */
1956:     static public function addInlineJsVars($data, array $opts = array())
1957:     {
1958:         $out = array();
1959:         $opts = array_merge(array(
1960:             'onload' => null,
1961:             'ret_vars' => false,
1962:             'top' => false
1963:         ), $opts);
1964: 
1965:         foreach ($data as $key => $val) {
1966:             if ($key[0] == '-') {
1967:                 $key = substr($key, 1);
1968:             } else {
1969:                 $val = Horde_Serialize::serialize($val, Horde_Serialize::JSON);
1970:             }
1971: 
1972:             $out[] = $key . '=' . $val;
1973:         }
1974: 
1975:         if ($opts['ret_vars']) {
1976:             return $out;
1977:         }
1978: 
1979:         self::addInlineScript($out, $opts['onload'], $opts['top']);
1980:     }
1981: 
1982:     /**
1983:      * Print pending inline javascript to the output buffer.
1984:      *
1985:      * @param boolean $raw  Return the raw script (not wrapped in CDATA tags
1986:      *                      or observe wrappers)?
1987:      */
1988:     static public function outputInlineScript($raw = false)
1989:     {
1990:         if (empty(self::$_inlineScript)) {
1991:             return;
1992:         }
1993: 
1994:         $script = array();
1995: 
1996:         foreach (self::$_inlineScript as $key => $val) {
1997:             $val = implode('', $val);
1998: 
1999:             if (!$raw) {
2000:                 switch ($key) {
2001:                 case 'dom':
2002:                     $val = 'document.observe("dom:loaded", function() {' . $val . '});';
2003:                     break;
2004: 
2005:                 case 'load':
2006:                     $val = 'Event.observe(window, "load", function() {' . $val . '});';
2007:                     break;
2008:                 }
2009:             }
2010: 
2011:             $script[] = $val;
2012:         }
2013: 
2014:         echo $raw
2015:             ? implode('', $script)
2016:             : self::wrapInlineScript($script);
2017: 
2018:         self::$_inlineScript = array();
2019:     }
2020: 
2021:     /**
2022:      * Print inline javascript to output buffer after wrapping with necessary
2023:      * javascript tags.
2024:      *
2025:      * @param array $script  The script to output.
2026:      *
2027:      * @return string  The script with the necessary HTML javascript tags
2028:      *                 appended.
2029:      */
2030:     static public function wrapInlineScript($script)
2031:     {
2032:         return '<script type="text/javascript">//<![CDATA[' . "\n" . implode('', $script) . "\n//]]></script>\n";
2033:     }
2034: 
2035:     /**
2036:      * Creates a URL for cached data.
2037:      *
2038:      * @param string $type   The cache type ('app', 'css', 'js').
2039:      * @param array $params  Optional parameters:
2040:      * <pre>
2041:      * RESERVED PARAMETERS:
2042:      * 'app' - REQUIRED for $type == 'app'. Identifies the application to
2043:      *         call the 'cacheOutput' API call, which is passed in the
2044:      *         value of the entire $params array (which may include parameters
2045:      *         other than those listed here). The return from cacheOutput
2046:      *         should be a 2-element array: 'data' (the cached data) and
2047:      *         'type' (the content-type of the data).
2048:      * 'cid' - REQUIRED for $type == 'css' || 'js'. The cacheid of the
2049:      *         data (stored in Horde_Cache).
2050:      * 'nocache' - If true, sets the cache limiter to 'nocache' instead of
2051:      *             the default 'public'.
2052:      * </pre>
2053:      *
2054:      * @return Horde_Url  The URL to the cache page.
2055:      */
2056:     static public function getCacheUrl($type, $params = array())
2057:     {
2058:         $url = self::getserviceLink('cache', 'horde')->add('cache', $type);
2059:         foreach ($params as $key => $val) {
2060:             $url .= '/' . $key . '=' . rawurlencode(strval($val));
2061:         }
2062: 
2063:         return self::url($url, true, array('append_session' => -1));
2064:     }
2065: 
2066:     /**
2067:      * Output the javascript needed to call the popup JS function.
2068:      *
2069:      * @param string|Horde_Url $url  The page to load.
2070:      * @param array $options         Additional options:
2071:      * <pre>
2072:      * 'height' - (integer) The height of the popup window.
2073:      *            DEFAULT: 650px
2074:      * 'menu' - (boolean) Show the browser menu in the popup window?
2075:      *          DEFAULT: false
2076:      * 'onload' - (string) A JS function to call after the popup window is
2077:      *            fully loaded.
2078:      *            DEFAULT: None
2079:      * 'params' - (array) Additional parameters to pass to the URL.
2080:      *            DEFAULT: None
2081:      * 'urlencode' - (boolean) URL encode the json string?
2082:      *               DEFAULT: No
2083:      * 'width' - (integer) The width of the popup window.
2084:      *           DEFAULT: 700 px
2085:      * </pre>
2086:      *
2087:      * @return string  The javascript needed to call the popup code.
2088:      */
2089:     static public function popupJs($url, $options = array())
2090:     {
2091:         self::addScriptFile('popup.js', 'horde');
2092: 
2093:         $params = new stdClass;
2094: 
2095:         if (!$url instanceof Horde_Url) {
2096:             $url = new Horde_Url($url);
2097:         }
2098:         $params->url = $url->url;
2099: 
2100:         if (!empty($url->parameters)) {
2101:             if (!isset($options['params'])) {
2102:                 $options['params'] = array();
2103:             }
2104:             $options['params'] = array_merge($url->parameters, $options['params']);
2105:         }
2106: 
2107:         if (!empty($options['height'])) {
2108:             $params->height = $options['height'];
2109:         }
2110:         if (!empty($options['menu'])) {
2111:             $params->menu = 1;
2112:         }
2113:         if (!empty($options['onload'])) {
2114:             $params->onload = $options['onload'];
2115:         }
2116:         if (!empty($options['params'])) {
2117:             // Bug #9903: 3rd parameter must explicitly be '&'
2118:             $params->params = http_build_query(array_map('rawurlencode', $options['params']), '', '&');
2119:         }
2120:         if (!empty($options['width'])) {
2121:             $params->width = $options['width'];
2122:         }
2123: 
2124:         return 'void(Horde.popup(' . self::escapeJson($params, array('nodelimit' => true, 'urlencode' => !empty($options['urlencode']))) . '));';
2125:     }
2126: 
2127:     /**
2128:      * Start buffering output.
2129:      */
2130:     static public function startBuffer()
2131:     {
2132:         if (!self::$_bufferLevel) {
2133:             self::$_contentSent = self::contentSent();
2134:         }
2135: 
2136:         ++self::$_bufferLevel;
2137:         ob_start();
2138:     }
2139: 
2140:     /**
2141:      * End buffering output.
2142:      *
2143:      * @return string  The buffered output.
2144:      */
2145:     static public function endBuffer()
2146:     {
2147:         if (self::$_bufferLevel) {
2148:             --self::$_bufferLevel;
2149:             return ob_get_clean();
2150:         }
2151: 
2152:         return '';
2153:     }
2154: 
2155:     /**
2156:      * Has any content been sent to the browser?
2157:      *
2158:      * @return boolean  True if content has been sent.
2159:      */
2160:     static public function contentSent()
2161:     {
2162:         return ((self::$_bufferLevel && self::$_contentSent) ||
2163:                 (!self::$_bufferLevel && (ob_get_length() || headers_sent())));
2164:     }
2165: 
2166:     /**
2167:      * Adds a META http-equiv tag to the page output.
2168:      *
2169:      * @param string $type     The http-equiv type value.
2170:      * @param string $content  The content of the META tag.
2171:      */
2172:     static public function addMetaTag($type, $content)
2173:     {
2174:         self::$_metaTags[$type] = $content;
2175:     }
2176: 
2177:     /**
2178:      * Adds a META refresh tag.
2179:      *
2180:      * @param integer $time  Refresh time.
2181:      * @param string $url    Refresh URL
2182:      */
2183:     static public function metaRefresh($time, $url)
2184:     {
2185:         if (!empty($time) && !empty($url)) {
2186:             self::addMetaTag('refresh', $time . ';url=' . $url);
2187:         }
2188:     }
2189: 
2190:     /**
2191:      * Adds a META tag to disable DNS prefetching.
2192:      * See Horde Bug #8836.
2193:      */
2194:     static public function noDnsPrefetch()
2195:     {
2196:         self::addMetaTag('x-dns-prefetch-control', 'off');
2197:     }
2198: 
2199:     /**
2200:      * Output META tags to page.
2201:      */
2202:     static public function outputMetaTags()
2203:     {
2204:         foreach (self::$_metaTags as $key => $val) {
2205:             echo '<meta http-equiv="' . $key . '" content="' . $val . "\" />\n";
2206:         }
2207: 
2208:         self::$_metaTags = array();
2209:     }
2210: 
2211:     /**
2212:      * Is an AJAX view supported/available on the current browser?
2213:      *
2214:      * @return boolean  True if the AJAX view can be displayed.
2215:      */
2216:     static public function ajaxAvailable()
2217:     {
2218:         global $browser;
2219: 
2220:         return $browser->hasFeature('xmlhttpreq') &&
2221:             (!$browser->isBrowser('msie') || $browser->getMajor() >= 7) &&
2222:             (!$browser->hasFeature('issafari') || $browser->getMajor() >= 2);
2223:     }
2224: 
2225:     /**
2226:      * Generates the menu output.
2227:      *
2228:      * @param array $opts  Additional options:
2229:      * <pre>
2230:      * 'app' - (string) The application to generate the menu for.
2231:      *         DEFAULT: current application
2232:      * 'mask' - (integer) The Horde_Menu mask to use.
2233:      *          DEFAULT: Horde_Menu::MASK_ALL
2234:      * 'menu_ob' - (boolean) If true, returns the menu object
2235:      *               DEFAULT: false (renders menu)
2236:      * </pre>
2237:      * @param string $app  The application to generate the menu for. Defaults
2238:      *                     to the current app.
2239:      *
2240:      * @return string|Horde_Menu  The menu output, or the menu object if
2241:      *                            'menu_ob' is true.
2242:      */
2243:     static public function menu(array $opts = array())
2244:     {
2245:         global $injector, $registry;
2246: 
2247:         if (empty($opts['app'])) {
2248:             $opts['app'] = $registry->getApp();
2249:         }
2250:         if (!isset($opts['mask'])) {
2251:             $opts['mask'] = Horde_Menu::MASK_ALL;
2252:         }
2253: 
2254:         $menu = new Horde_Menu(isset($opts['mask']) ? $opts['mask'] : Horde_Menu::MASK_ALL);
2255: 
2256:         $registry->callAppMethod($opts['app'], 'menu', array(
2257:             'args' => array($menu)
2258:         ));
2259: 
2260:         if (!empty($opts['menu_ob'])) {
2261:             return $menu;
2262:         }
2263: 
2264:         self::startBuffer();
2265:         require $registry->get('templates', 'horde') . '/menu/menu.inc';
2266:         return self::endBuffer();
2267:     }
2268: 
2269:     /**
2270:      * Process a permission denied error, running a user-defined hook if
2271:      * necessary.
2272:      *
2273:      * @param string $app    Application name.
2274:      * @param string $perm   Permission name.
2275:      * @param string $error  An error message to output via the notification
2276:      *                       system.
2277:      */
2278:     static public function permissionDeniedError($app, $perm, $error = null)
2279:     {
2280:         try {
2281:             self::callHook('perms_denied', array($app, $perm));
2282:         } catch (Horde_Exception_HookNotSet $e) {}
2283: 
2284:         if (!is_null($error)) {
2285:             $GLOBALS['notification']->push($error, 'horde.warning');
2286:         }
2287:     }
2288: }
2289: 
API documentation generated by ApiGen