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_Registry:: class provides a set of methods for communication
   4:  * between Horde applications and keeping track of application
   5:  * configuration information.
   6:  *
   7:  * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
   8:  *
   9:  * See the enclosed file COPYING for license information (LGPL). If you
  10:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  11:  *
  12:  * @author   Chuck Hagenbuch <chuck@horde.org>
  13:  * @author   Jon Parise <jon@horde.org>
  14:  * @author   Anil Madhavapeddy <anil@recoil.org>
  15:  * @author   Michael Slusarz <slusarz@horde.org>
  16:  * @category Horde
  17:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
  18:  * @package  Core
  19:  */
  20: class Horde_Registry
  21: {
  22:     /* Session flags. */
  23:     const SESSION_NONE = 1;
  24:     const SESSION_READONLY = 2;
  25: 
  26:     /* Error codes for pushApp(). */
  27:     const AUTH_FAILURE = 1;
  28:     const NOT_ACTIVE = 2;
  29:     const PERMISSION_DENIED = 3;
  30:     const HOOK_FATAL = 4;
  31: 
  32:     /**
  33:      * Hash storing information on each registry-aware application.
  34:      *
  35:      * @var array
  36:      */
  37:     public $applications = array();
  38: 
  39:     /**
  40:      * The application that called appInit().
  41:      *
  42:      * @var string
  43:      */
  44:     public $initialApp;
  45: 
  46:     /**
  47:      * NLS configuration.
  48:      *
  49:      * @var Horde_Registry_Nlsconfig
  50:      */
  51:     public $nlsconfig;
  52: 
  53:     /**
  54:      * Stack of in-use applications.
  55:      *
  56:      * @var array
  57:      */
  58:     protected $_appStack = array();
  59: 
  60:     /**
  61:      * The list of external services.
  62:      *
  63:      * @var array
  64:      */
  65:     protected $_apis;
  66: 
  67:     /**
  68:      * The list of APIs.
  69:      *
  70:      * @var array
  71:      */
  72:     protected $_apiList = array();
  73: 
  74:     /**
  75:      * The list of applications initialized during this access.
  76:      *
  77:      * @var array
  78:      */
  79:     protected $_appsinit = array();
  80: 
  81:     /**
  82:      * The arguments that have been passed when instantiating the registry.
  83:      *
  84:      * @var array
  85:      */
  86:     protected $_args = array();
  87: 
  88:     /**
  89:      * Cached configuration information.
  90:      *
  91:      * @var array
  92:      */
  93:     protected $_confCache = array();
  94: 
  95:     /**
  96:      * Interfaces list.
  97:      *
  98:      * @var array
  99:      */
 100:     protected $_interfaces = array();
 101: 
 102:     /**
 103:      * Object (Application/Api) cache.
 104:      *
 105:      * @var array
 106:      */
 107:     protected $_obCache = array();
 108: 
 109:     /**
 110:      * The last modified time of the newest modified registry file.
 111:      *
 112:      * @var integer
 113:      */
 114:     protected $_regmtime;
 115: 
 116:     /**
 117:      * The current vhost configuration file.
 118:      *
 119:      * @var string
 120:      */
 121:     protected $_vhost = null;
 122: 
 123:     /**
 124:      * Application bootstrap initialization.
 125:      * Solves chicken-and-egg problem - need a way to init Horde environment
 126:      * from application without an active Horde_Registry object.
 127:      *
 128:      * Page compression will be started (if configured).
 129:      * init() will be called after the initialization is completed.
 130:      *
 131:      * Global variables defined:
 132:      *   $browser - Horde_Browser object
 133:      *   $cli - Horde_Cli object (if 'cli' is true)
 134:      *   $conf - Configuration array
 135:      *   $injector - Horde_Injector object
 136:      *   $language - Language
 137:      *   $notification - Horde_Notification object
 138:      *   $prefs - Horde_Prefs object
 139:      *   $registry - Horde_Registry object
 140:      *   $session - Horde_Session object
 141:      *
 142:      * @param string $app  The application to initialize.
 143:      * @param array $args  Optional arguments:
 144:      * <pre>
 145:      * 'admin' - (boolean) Require authenticated user to be an admin?
 146:      *           DEFAULT: false
 147:      * 'authentication' - (string) The type of authentication to use:
 148:      *   'none'  - Do not authenticate
 149:      *   'throw' - Authenticate; on no auth, throw a Horde_Exception
 150:      *   [DEFAULT] - Authenticate; on no auth redirect to login screen
 151:      * 'cli' - (boolean) Initialize a CLI interface. Setting this to true
 152:      *         implicits setting 'authentication' to 'none' and 'admin' and
 153:      *         'nocompress' to true.
 154:      *         DEFAULT: false
 155:      * 'nocompress' - (boolean) If set, the page will not be compressed.
 156:      *                DEFAULT: false
 157:      * 'nologintasks' - (boolean) If set, don't perform logintasks (never
 158:      *                  performed if authentication is 'none').
 159:      *                  DEFAULT: false
 160:      * 'session_cache_limiter' - (string) Use this value for the session cache
 161:      *                           limiter.
 162:      *                           DEFAULT: Uses the value in the configuration.
 163:      * 'session_control' - (string) Sets special session control limitations:
 164:      *   'netscape' - TODO; start read/write session
 165:      *   'none' - Do not start a session
 166:      *   'readonly' - Start session readonly
 167:      *   [DEFAULT] - Start read/write session
 168:      * 'test' - (boolean) Is this the test script? If so, we relax several
 169:      *          sanity checks and don't load things from the cache.
 170:      *          DEFAULT: false
 171:      * 'timezone' - (boolean) Set the time zone?
 172:      *              DEFAULT: false
 173:      * 'user_admin' - (boolean) Set authentication to an admin user?
 174:      *                DEFAULT: false
 175:      * </pre>
 176:      *
 177:      * @return Horde_Registry_Application  The application object.
 178:      * @throws Horde_Exception
 179:      */
 180:     static public function appInit($app, $args = array())
 181:     {
 182:         if (isset($GLOBALS['registry'])) {
 183:             $appOb = $GLOBALS['registry']->getApiInstance($app, 'application');
 184:             $appOb->init();
 185:             return $appOb;
 186:         }
 187: 
 188:         $args = array_merge(array(
 189:             'admin' => false,
 190:             'authentication' => null,
 191:             'cli' => null,
 192:             'nocompress' => false,
 193:             'nologintasks' => false,
 194:             'session_cache_limiter' => null,
 195:             'session_control' => null,
 196:             'timezone' => false,
 197:             'user_admin' => null
 198:         ), $args);
 199: 
 200:         /* CLI initialization. */
 201:         if ($args['cli']) {
 202:             /* Make sure no one runs from the web. */
 203:             if (!Horde_Cli::runningFromCLI()) {
 204:                 throw new Horde_Exception('Script must be run from the command line');
 205:             }
 206: 
 207:             /* Load the CLI environment - make sure there's no time limit,
 208:              * init some variables, etc. */
 209:             $GLOBALS['cli'] = Horde_Cli::init();
 210: 
 211:             $args['nocompress'] = true;
 212:             $args['authentication'] = 'none';
 213:         }
 214: 
 215:         // Registry.
 216:         $s_ctrl = 0;
 217:         switch ($args['session_control']) {
 218:         case 'netscape':
 219:             // Chicken/egg: Browser object doesn't exist yet.
 220:             // Can't use Horde_Core_Browser since it depends on registry to be
 221:             // configured.
 222:             $browser = new Horde_Browser();
 223:             if ($browser->isBrowser('mozilla')) {
 224:                 $args['session_cache_limiter'] = 'private, must-revalidate';
 225:             }
 226:             break;
 227: 
 228:         case 'none':
 229:             $s_ctrl = self::SESSION_NONE;
 230:             break;
 231: 
 232:         case 'readonly':
 233:             $s_ctrl = self::SESSION_READONLY;
 234:             break;
 235:         }
 236: 
 237:         $classname = __CLASS__;
 238:         $registry = $GLOBALS['registry'] = new $classname($s_ctrl, $args);
 239:         $registry->initialApp = $app;
 240: 
 241:         $appob = $registry->getApiInstance($app, 'application');
 242:         $appob->initParams = $args;
 243: 
 244:         try {
 245:             $registry->pushApp($app, array('check_perms' => ($args['authentication'] != 'none'), 'logintasks' => !$args['nologintasks'], 'notransparent' => !empty($args['notransparent'])));
 246: 
 247:             if ($args['admin'] && !$registry->isAdmin()) {
 248:                 throw new Horde_Exception('Not an admin');
 249:             }
 250:         } catch (Horde_Exception $e) {
 251:             $appob->appInitFailure($e);
 252: 
 253:             if ($args['authentication'] == 'throw') {
 254:                 throw $e;
 255:             }
 256: 
 257:             $registry->authenticateFailure($app, $e);
 258:         }
 259: 
 260:         if ($args['timezone']) {
 261:             $registry->setTimeZone();
 262:         }
 263: 
 264:         if (!$args['nocompress']) {
 265:             Horde::compressOutput();
 266:         }
 267: 
 268:         if ($args['user_admin']) {
 269:             if (empty($GLOBALS['conf']['auth']['admins'])) {
 270:                 throw new Horde_Exception('No admin users defined in configuration.');
 271:             }
 272:             $registry->setAuth(reset($GLOBALS['conf']['auth']['admins']), array());
 273:         }
 274: 
 275:         $appob->init();
 276: 
 277:         return $appob;
 278:     }
 279: 
 280:     /**
 281:      * Create a new Horde_Registry instance.
 282:      *
 283:      * @param integer $session_flags  Any session flags.
 284:      * @param array $args             See appInit().
 285:      *
 286:      * @throws Horde_Exception
 287:      */
 288:     public function __construct($session_flags = 0, array $args = array())
 289:     {
 290:         /* Save arguments. */
 291:         $this->_args = $args;
 292: 
 293:         /* Define autoloader callbacks. */
 294:         $callbacks = array(
 295:             'Horde_Mime' => 'Horde_Core_Autoloader_Callback_Mime',
 296:             'Horde_Nls' => 'Horde_Core_Autoloader_Callback_Nls',
 297:         );
 298: 
 299:         /* Define factories. By default, uses the 'create' method in the given
 300:          * classname (string). If other function needed, define as the second
 301:          * element in an array. */
 302:         $factories = array(
 303:             'Horde_ActiveSyncBackend' => 'Horde_Core_Factory_ActiveSyncBackend',
 304:             'Horde_ActiveSyncServer' => 'Horde_Core_Factory_ActiveSyncServer',
 305:             'Horde_ActiveSyncState' => 'Horde_Core_Factory_ActiveSyncState',
 306:             'Horde_Alarm' => 'Horde_Core_Factory_Alarm',
 307:             'Horde_Browser' => 'Horde_Core_Factory_Browser',
 308:             'Horde_Cache' => 'Horde_Core_Factory_Cache',
 309:             'Horde_Controller_Request' => 'Horde_Core_Factory_Request',
 310:             'Horde_Controller_RequestConfiguration' => array(
 311:                 'Horde_Core_Controller_RequestMapper',
 312:                 'getRequestConfiguration',
 313:             ),
 314:             'Horde_Core_Auth_Signup' => 'Horde_Core_Factory_AuthSignup',
 315:             'Horde_Core_Perms' => 'Horde_Core_Factory_PermsCore',
 316:             'Horde_Db_Adapter' => 'Horde_Core_Factory_DbBase',
 317:             'Horde_Editor' => 'Horde_Core_Factory_Editor',
 318:             'Horde_Group' => 'Horde_Core_Factory_Group',
 319:             'Horde_History' => 'Horde_Core_Factory_History',
 320:             'Horde_Log_Logger' => 'Horde_Core_Factory_Logger',
 321:             'Horde_Service_Facebook' => 'Horde_Core_Factory_Facebook',
 322:             'Horde_Kolab_Server_Composite' => 'Horde_Core_Factory_KolabServer',
 323:             'Horde_Kolab_Session' => 'Horde_Core_Factory_KolabSession',
 324:             'Horde_Kolab_Storage' => 'Horde_Core_Factory_KolabStorage',
 325:             'Horde_Lock' => 'Horde_Core_Factory_Lock',
 326:             'Horde_Mail' => 'Horde_Core_Factory_Mail',
 327:             'Horde_Memcache' => 'Horde_Core_Factory_Memcache',
 328:             'Horde_Notification' => 'Horde_Core_Factory_Notification',
 329:             'Horde_Perms' => 'Horde_Core_Factory_Perms',
 330:             'Horde_Routes_Mapper' => 'Horde_Core_Factory_Mapper',
 331:             'Horde_Routes_Matcher' => 'Horde_Core_Factory_Matcher',
 332:             'Horde_Secret' => 'Horde_Core_Factory_Secret',
 333:             'Horde_Service_Facebook' => 'Horde_Core_Factory_Facebook',
 334:             'Horde_Service_Twitter' => 'Horde_Core_Factory_Twitter',
 335:             'Horde_SessionHandler' => 'Horde_Core_Factory_SessionHandler',
 336:             'Horde_Template' => 'Horde_Core_Factory_Template',
 337:             'Horde_Token' => 'Horde_Core_Factory_Token',
 338:             'Horde_Service_UrlShortener' => 'Horde_Core_Factory_UrlShortener',
 339:             'Horde_View' => 'Horde_Core_Factory_View',
 340:             'Horde_View_Base' => 'Horde_Core_Factory_View',
 341:             'Horde_Weather' => 'Horde_Core_Factory_Weather',
 342:             'Net_DNS2_Resolver' => 'Horde_Core_Factory_Dns',
 343:         );
 344: 
 345:         /* Define implementations. */
 346:         $implementations = array(
 347:             'Horde_Controller_ResponseWriter' => 'Horde_Controller_ResponseWriter_Web',
 348:         );
 349: 
 350:         /* Setup injector. */
 351:         $GLOBALS['injector'] = $injector = new Horde_Injector(new Horde_Injector_TopLevel());
 352: 
 353:         foreach ($factories as $key => $val) {
 354:             if (is_string($val)) {
 355:                 $val = array($val, 'create');
 356:             }
 357:             $injector->bindFactory($key, $val[0], $val[1]);
 358:         }
 359:         foreach ($implementations as $key => $val) {
 360:             $injector->bindImplementation($key, $val);
 361:         }
 362: 
 363:         $GLOBALS['registry'] = $this;
 364:         $injector->setInstance(__CLASS__, $this);
 365: 
 366:         /* Setup autoloader instance and callbacks.
 367:          * $__autoloader is defined in horde/lib/core.php */
 368:         $injector->setInstance('Horde_Autoloader', $GLOBALS['__autoloader']);
 369:         foreach ($callbacks as $key => $val) {
 370:             $GLOBALS['__autoloader']->addCallback($key, array($val, 'callback'));
 371:         }
 372: 
 373:         /* Import and global Horde's configuration values. Almost a chicken
 374:          * and egg issue - since loadConfiguration() uses registry in certain
 375:          * instances. However, if HORDE_BASE is defined, and app is
 376:          * 'horde', registry is not used in the method so we are free to
 377:          * call it here (prevents us from duplicating a bunch of code). */
 378:         $this->importConfig('horde');
 379:         $conf = $GLOBALS['conf'];
 380: 
 381:         /* Initialize browser object. */
 382:         $GLOBALS['browser'] = $injector->getInstance('Horde_Browser');
 383: 
 384:         /* Initial Horde-wide settings. */
 385: 
 386:         /* Set the maximum execution time in accordance with the config
 387:          * settings, but only if not running from the CLI */
 388:         if (!Horde_Cli::runningFromCLI()) {
 389:             set_time_limit($conf['max_exec_time']);
 390:         }
 391: 
 392:         /* Set the error reporting level in accordance with the config
 393:          * settings. */
 394:         error_reporting($conf['debug_level']);
 395: 
 396:         /* Set the umask according to config settings. */
 397:         if (isset($conf['umask'])) {
 398:             umask($conf['umask']);
 399:         }
 400: 
 401:         /* Get modified time of registry files. */
 402:         $this->_regmtime = max(filemtime(HORDE_BASE . '/config/registry.php'),
 403:                                filemtime(HORDE_BASE . '/config/registry.d'));
 404:         if (file_exists(HORDE_BASE . '/config/registry.local.php')) {
 405:             $this->_regmtime = max($this->_regmtime, filemtime(HORDE_BASE . '/config/registry.local.php'));
 406:         }
 407:         if (!empty($conf['vhosts'])) {
 408:             $this->_vhost = HORDE_BASE . '/config/registry-' . $conf['server']['name'] . '.php';
 409:             if (file_exists($this->_vhost)) {
 410:                 $this->_regmtime = max($this->_regmtime, filemtime($this->_vhost));
 411:             } else {
 412:                 $this->_vhost = null;
 413:             }
 414:         }
 415: 
 416:         /* Start a session. */
 417:         if ($session_flags & self::SESSION_NONE) {
 418:             /* Never start a session if the session flags include
 419:                SESSION_NONE. */
 420:             $GLOBALS['session'] = $session = new Horde_Session_Null();
 421:         } elseif (PHP_SAPI == 'cli' ||
 422:                   ((PHP_SAPI == 'cgi' || PHP_SAPI == 'cgi-fcgi') &&
 423:                     empty($_SERVER['SERVER_NAME']))) {
 424:             $GLOBALS['session'] = $session = new Horde_Session();
 425:             $session->setup(false, $args['session_cache_limiter']);
 426:         } else {
 427:             $GLOBALS['session'] = $session = new Horde_Session();
 428:             $session->setup(true, $args['session_cache_limiter']);
 429:             if ($session_flags & self::SESSION_READONLY) {
 430:                 /* Close the session immediately so no changes can be made but
 431:                    values are still available. */
 432:                 $session->close();
 433:             }
 434:         }
 435:         $injector->setInstance('Horde_Session', $session);
 436: 
 437:         /* Always need to load applications information. */
 438:         $this->_loadApplications();
 439: 
 440:         /* Initialize language configuration object. */
 441:         $this->nlsconfig = new Horde_Registry_Nlsconfig();
 442: 
 443:         /* Initialize the localization routines and variables. */
 444:         $this->setLanguageEnvironment(null, 'horde');
 445: 
 446:         /* Stop system if Horde is inactive. */
 447:         if ($this->applications['horde']['status'] == 'inactive') {
 448:             throw new Horde_Exception(Horde_Core_Translation::t("This system is currently deactivated."));
 449:         }
 450: 
 451:         /* Initialize notification object. Always attach status listener by
 452:          * default. Default status listener can be overriden through the
 453:          * 'notification_override' session variable. */
 454:         $GLOBALS['notification'] = $injector->getInstance('Horde_Notification');
 455:         if (Horde_Util::getFormData('ajaxui') &&
 456:             ($override = $session->get('horde', 'notification_override'))) {
 457:             require_once $override[0];
 458:             $GLOBALS['notification']->attach('status', null, $override[1]);
 459:         } else {
 460:             $GLOBALS['notification']->attach('status');
 461:         }
 462: 
 463:         register_shutdown_function(array($this, 'shutdown'));
 464:     }
 465: 
 466:     /**
 467:      * Events to do on shutdown.
 468:      */
 469:     public function shutdown()
 470:     {
 471:         /* Register access key logger for translators. */
 472:         if (!empty($GLOBALS['conf']['log_accesskeys'])) {
 473:             Horde::getAccessKey(null, null, true);
 474:         }
 475: 
 476:         /* Register memory tracker if logging in debug mode. */
 477:         if (function_exists('memory_get_peak_usage')) {
 478:             Horde::logMessage('Max memory usage: ' . memory_get_peak_usage(true) . ' bytes', 'DEBUG');
 479:         }
 480:     }
 481: 
 482:     /**
 483:      * TODO
 484:      */
 485:     public function __get($api)
 486:     {
 487:         if (in_array($api, $this->listAPIs())) {
 488:             return new Horde_Registry_Caller($this, $api);
 489:         }
 490:     }
 491: 
 492:     /**
 493:      * Clone should never be called on this object. If it is, die.
 494:      *
 495:      * @throws Horde_Exception
 496:      */
 497:     public function __clone()
 498:     {
 499:         throw new Horde_Exception('Registry objects should never be cloned.');
 500:     }
 501: 
 502:     /**
 503:      * serialize() should never be called on this object. If it is, die.
 504:      *
 505:      * @throws Horde_Exception
 506:      */
 507:     public function __sleep()
 508:     {
 509:         throw new Horde_Exception('Registry objects should never be serialized.');
 510:     }
 511: 
 512:     /**
 513:      * Rebuild the registry configuration.
 514:      */
 515:     public function rebuild()
 516:     {
 517:         $app = $this->getApp();
 518: 
 519:         $this->applications = $this->_apiList = $this->_confCache = $this->_interfaces = array();
 520:         unset($this->_apis);
 521: 
 522:         $GLOBALS['session']->remove('horde', 'nls/');
 523:         $GLOBALS['session']->remove('horde', 'registry/');
 524:         $this->_saveCache('apis');
 525:         $this->_saveCache('app');
 526: 
 527:         $this->_loadApplications();
 528: 
 529:         $this->importConfig('horde');
 530:         $this->importConfig($app);
 531:     }
 532: 
 533:     /**
 534:      * Load application configuration information.
 535:      */
 536:     protected function _loadApplications()
 537:     {
 538:         /* First, try to load from cache. */
 539:         if ($ret = $this->_loadCache('app')) {
 540:             $this->applications = $ret[0];
 541:             $this->_interfaces = $ret[1];
 542:             return;
 543:         }
 544: 
 545:         /* Read the registry configuration files. */
 546:         if (!file_exists(HORDE_BASE . '/config/registry.php')) {
 547:             throw new Horde_Exception('Missing registry.php configuration file');
 548:         }
 549: 
 550:         /* Set textdomain to Horde, so that we really only load translations
 551:          * from Horde. */
 552:         if ($this->getApp() != 'horde') {
 553:             textdomain('horde');
 554:         }
 555: 
 556:         require HORDE_BASE . '/config/registry.php';
 557:         if ($files = glob(HORDE_BASE . '/config/registry.d/*.php')) {
 558:             foreach ($files as $r) {
 559:                 include $r;
 560:             }
 561:         }
 562:         if (file_exists(HORDE_BASE . '/config/registry.local.php')) {
 563:             include HORDE_BASE . '/config/registry.local.php';
 564:         }
 565:         if ($this->_vhost) {
 566:             include $this->_vhost;
 567:         }
 568: 
 569:         /* Reset textdomain. */
 570:         if ($this->getApp() != 'horde') {
 571:             textdomain($this->getApp());
 572:         }
 573: 
 574:         if (!isset($this->applications['horde']['fileroot'])) {
 575:             $this->applications['horde']['fileroot'] = isset($app_fileroot)
 576:                 ? $app_fileroot
 577:                 : HORDE_BASE;
 578:         }
 579:         if (!isset($app_fileroot)) {
 580:             $app_fileroot = $this->applications['horde']['fileroot'];
 581:         }
 582: 
 583:         /* Make sure the fileroot of Horde has a trailing slash to not trigger
 584:          * open_basedir restrictions that have that trailing slash too. */
 585:         $app_fileroot = rtrim($app_fileroot, '/') . '/';
 586: 
 587:         if (!isset($this->applications['horde']['webroot'])) {
 588:             $this->applications['horde']['webroot'] = isset($app_webroot)
 589:                 ? $app_webroot
 590:                 : $this->_detectWebroot();
 591:         }
 592:         if (!isset($app_webroot)) {
 593:             $app_webroot = $this->applications['horde']['webroot'];
 594:         }
 595: 
 596:         /* Scan for all APIs provided by each app, and set other common
 597:          * defaults like templates and graphics. */
 598:         foreach ($this->applications as $appName => &$app) {
 599:             if (!isset($app['status'])) {
 600:                 $app['status'] = 'active';
 601:             } elseif ($app['status'] == 'heading' ||
 602:                       $app['status'] == 'sidebar') {
 603:                 continue;
 604:             }
 605: 
 606:             $app['fileroot'] = isset($app['fileroot'])
 607:                 ? rtrim($app['fileroot'], ' /')
 608:                 : $app_fileroot . $appName;
 609: 
 610:             if (!isset($app['name'])) {
 611:                 $app['name'] = '';
 612:             }
 613: 
 614:             if (!file_exists($app['fileroot']) ||
 615:                 (empty($this->_args['test']) &&
 616:                  file_exists($app['fileroot'] . '/config/conf.xml') &&
 617:                  !file_exists($app['fileroot'] . '/config/conf.php'))) {
 618:                 $app['status'] = 'inactive';
 619:                 Horde::logMessage('Setting ' . $appName . ' inactive because the fileroot does not exist or the application is not configured yet.', 'DEBUG');
 620:             }
 621: 
 622:             $app['webroot'] = isset($app['webroot'])
 623:                 ? rtrim($app['webroot'], ' /')
 624:                 : $app_webroot . '/' . $appName;
 625: 
 626:             if (($app['status'] != 'inactive') &&
 627:                 isset($app['provides']) &&
 628:                 (($app['status'] != 'admin') || $this->isAdmin())) {
 629:                 if (is_array($app['provides'])) {
 630:                     foreach ($app['provides'] as $interface) {
 631:                         $this->_interfaces[$interface] = $appName;
 632:                     }
 633:                 } else {
 634:                     $this->_interfaces[$app['provides']] = $appName;
 635:                 }
 636:             }
 637: 
 638:             if (!isset($app['templates']) && isset($app['fileroot'])) {
 639:                 $app['templates'] = $app['fileroot'] . '/templates';
 640:             }
 641:             if (!isset($app['jsuri']) && isset($app['webroot'])) {
 642:                 $app['jsuri'] = $app['webroot'] . '/js';
 643:             }
 644:             if (!isset($app['jsfs']) && isset($app['fileroot'])) {
 645:                 $app['jsfs'] = $app['fileroot'] . '/js';
 646:             }
 647:             if (!isset($app['themesuri']) && isset($app['webroot'])) {
 648:                 $app['themesuri'] = $app['webroot'] . '/themes';
 649:             }
 650:             if (!isset($app['themesfs']) && isset($app['fileroot'])) {
 651:                 $app['themesfs'] = $app['fileroot'] . '/themes';
 652:             }
 653:         }
 654: 
 655:         $this->_saveCache('app', array(
 656:             $this->applications,
 657:             $this->_interfaces
 658:         ));
 659:     }
 660: 
 661:     /**
 662:      * Attempt to auto-detect the Horde webroot.
 663:      *
 664:      * @return string  The webroot.
 665:      */
 666:     protected function _detectWebroot()
 667:     {
 668:         // Note for Windows: the below assumes the PHP_SELF variable uses
 669:         // forward slashes.
 670:         if (isset($_SERVER['SCRIPT_URL']) || isset($_SERVER['SCRIPT_NAME'])) {
 671:             $path = empty($_SERVER['SCRIPT_URL'])
 672:                 ? $_SERVER['SCRIPT_NAME']
 673:                 : $_SERVER['SCRIPT_URL'];
 674:             $hordedir = basename(str_replace(DIRECTORY_SEPARATOR, '/', realpath(HORDE_BASE)));
 675:             return (preg_match(';/' . $hordedir . ';', $path))
 676:                 ? preg_replace(';/' . $hordedir . '.*;', '/' . $hordedir, $path)
 677:                 : '';
 678:         }
 679: 
 680:         if (!isset($_SERVER['PHP_SELF'])) {
 681:             return '/horde';
 682:         }
 683: 
 684:         $webroot = preg_split(';/;', $_SERVER['PHP_SELF'], 2, PREG_SPLIT_NO_EMPTY);
 685:         $webroot = strstr(realpath(HORDE_BASE), DIRECTORY_SEPARATOR . array_shift($webroot));
 686:         if ($webroot !== false) {
 687:             return preg_replace(array('/\\\\/', ';/config$;'), array('/', ''), $webroot);
 688:         }
 689: 
 690:         return ($webroot === false)
 691:             ? ''
 692:             : '/horde';
 693:     }
 694: 
 695:     /**
 696:      * Load the list of available external services.
 697:      *
 698:      * @throws Horde_Exception
 699:      */
 700:     protected function _loadApis()
 701:     {
 702:         if (isset($this->_apis) ||
 703:             ($this->_apis = $this->_loadCache('apis'))) {
 704:             return;
 705:         }
 706: 
 707:         /* Generate api/type cache. */
 708:         $status = array('active', 'notoolbar', 'hidden');
 709:         if ($this->isAdmin()) {
 710:             $status[] = 'admin';
 711:         } else {
 712:             $status[] = 'noadmin';
 713:         }
 714: 
 715:         $this->_apis = array();
 716: 
 717:         foreach (array_keys($this->applications) as $app) {
 718:             if (in_array($this->applications[$app]['status'], $status)) {
 719:                 try {
 720:                     $api = $this->getApiInstance($app, 'api');
 721:                     $this->_apis[$app] = array(
 722:                         'api' => array_diff(get_class_methods($api), array('__construct')),
 723:                         'links' => $api->links,
 724:                         'noperms' => $api->noPerms
 725:                     );
 726:                 } catch (Horde_Exception $e) {
 727:                     Horde::logMessage($e, 'DEBUG');
 728:                 }
 729:             }
 730:         }
 731: 
 732:         $this->_saveCache('apis', $this->_api);
 733:     }
 734: 
 735:     /**
 736:      * Retrieve an API object.
 737:      *
 738:      * @param string $app   The application to load.
 739:      * @param string $type  Either 'application' or 'api'.
 740:      *
 741:      * @return Horde_Registry_Api|Horde_Registry_Application  The API object.
 742:      * @throws Horde_Exception
 743:      */
 744:     public function getApiInstance($app, $type)
 745:     {
 746:         if (isset($this->_obCache[$app][$type])) {
 747:             return $this->_obCache[$app][$type];
 748:         }
 749: 
 750:         $cname = Horde_String::ucfirst($type);
 751: 
 752:         /* Can't autoload here, since the application may not have been
 753:          * initialized yet. */
 754:         $classname = Horde_String::ucfirst($app) . '_' . $cname;
 755:         $path = $this->get('fileroot', $app) . '/lib/' . $cname . '.php';
 756:         if (file_exists($path)) {
 757:             include_once $path;
 758:         } else {
 759:             $classname = __CLASS__ . '_' . $cname;
 760:         }
 761: 
 762:         if (!class_exists($classname, false)) {
 763:             throw new Horde_Exception("$app does not have an API");
 764:         }
 765: 
 766:         $this->_obCache[$app][$type] = new $classname();
 767: 
 768:         return $this->_obCache[$app][$type];
 769:     }
 770: 
 771:     /**
 772:      * Return a list of the installed and registered applications.
 773:      *
 774:      * @param array $filter   An array of the statuses that should be
 775:      *                        returned. Defaults to non-hidden.
 776:      * @param boolean $assoc  Return hash with app names as keys and config
 777:      *                        parameters as values?
 778:      * @param integer $perms  The permission level to check for in the list.
 779:      *                        If null, skips permission check.
 780:      *
 781:      * @return array  List of apps registered with Horde. If no
 782:      *                applications are defined returns an empty array.
 783:      */
 784:     public function listApps($filter = null, $assoc = false,
 785:                              $perms = Horde_Perms::SHOW)
 786:     {
 787:         if (is_null($filter)) {
 788:             $filter = array('notoolbar', 'active');
 789:         }
 790:         if (!$this->isAdmin() &&
 791:             in_array('active', $filter) &&
 792:             !in_array('noadmin', $filter)) {
 793:             $filter[] = 'noadmin';
 794:         }
 795: 
 796:         $apps = array();
 797:         foreach ($this->applications as $app => $params) {
 798:             if (in_array($params['status'], $filter)) {
 799:                 /* Sidebar apps can only be displayed if the parent app is
 800:                  * active. */
 801:                 if (($params['status'] == 'sidebar') &&
 802:                     $this->isInactive($params['app'])) {
 803:                         continue;
 804:                 }
 805: 
 806:                 if ((is_null($perms) || $this->hasPermission($app, $perms))) {
 807:                     $apps[$app] = $params;
 808:                 }
 809:             }
 810:         }
 811: 
 812:         return $assoc ? $apps : array_keys($apps);
 813:     }
 814: 
 815:     /**
 816:      * Return a list of all applications, ignoring permissions.
 817:      *
 818:      * @return array  List of all apps registered with Horde.
 819:      */
 820:     public function listAllApps($filter = null)
 821:     {
 822:         // Default to all installed (but possibly not configured) applications.
 823:         if (is_null($filter)) {
 824:             $filter = array(
 825:                 'active', 'admin', 'noadmin', 'hidden', 'inactive', 'notoolbar'
 826:             );
 827:         }
 828: 
 829:         return $this->listApps($filter, false, null);
 830:     }
 831: 
 832:     /**
 833:      * Is the given application inactive?
 834:      *
 835:      * @param string $app  The application to check.
 836:      *
 837:      * @return boolean  True if inactive.
 838:      */
 839:     public function isInactive($app)
 840:     {
 841:         return (!isset($this->applications[$app]) ||
 842:                 ($this->applications[$app]['status'] == 'inactive') ||
 843:                 (($this->applications[$app]['status'] == 'admin') &&
 844:                  !$this->isAdmin()) ||
 845:                 (($this->applications[$app]['status'] == 'noadmin') &&
 846:                  $this->_args['authentication'] != 'none' &&
 847:                  $this->isAdmin()));
 848:     }
 849: 
 850:     /**
 851:      * Returns all available registry APIs.
 852:      *
 853:      * @return array  The API list.
 854:      */
 855:     public function listAPIs()
 856:     {
 857:         if (empty($this->_apiList) && !empty($this->_interfaces)) {
 858:             $apis = array();
 859: 
 860:             foreach (array_keys($this->_interfaces) as $interface) {
 861:                 list($api,) = explode('/', $interface, 2);
 862:                 $apis[$api] = true;
 863:             }
 864: 
 865:             $this->_apiList = array_keys($apis);
 866:         }
 867: 
 868:         return $this->_apiList;
 869:     }
 870: 
 871:     /**
 872:      * Returns all of the available registry methods, or alternately
 873:      * only those for a specified API.
 874:      *
 875:      * @param string $api  Defines the API for which the methods shall be
 876:      *                     returned.
 877:      *
 878:      * @return array  The method list.
 879:      */
 880:     public function listMethods($api = null)
 881:     {
 882:         $methods = array();
 883: 
 884:         $this->_loadApis();
 885: 
 886:         foreach (array_keys($this->applications) as $app) {
 887:             if (isset($this->applications[$app]['provides'])) {
 888:                 $provides = $this->applications[$app]['provides'];
 889:                 if (!is_array($provides)) {
 890:                     $provides = array($provides);
 891:                 }
 892: 
 893:                 foreach ($provides as $method) {
 894:                     if (strpos($method, '/') !== false) {
 895:                         if (is_null($api) ||
 896:                             (substr($method, 0, strlen($api)) == $api)) {
 897:                             $methods[$method] = true;
 898:                         }
 899:                     } elseif (isset($this->_apis[$app]) &&
 900:                               (is_null($api) || ($method == $api))) {
 901:                         foreach ($this->_apis[$app]['api'] as $service) {
 902:                             $methods[$method . '/' . $service] = true;
 903:                         }
 904:                     }
 905:                 }
 906:             }
 907:         }
 908: 
 909:         return array_keys($methods);
 910:     }
 911: 
 912:     /**
 913:      * Determine if an interface is implemented by an active application.
 914:      *
 915:      * @param string $interface  The interface to check for.
 916:      *
 917:      * @return mixed  The application implementing $interface if we have it,
 918:      *                false if the interface is not implemented.
 919:      */
 920:     public function hasInterface($interface)
 921:     {
 922:         return !empty($this->_interfaces[$interface])
 923:             ? $this->_interfaces[$interface]
 924:             : false;
 925:     }
 926: 
 927:     /**
 928:      * Determine if a method has been registered with the registry.
 929:      *
 930:      * @param string $method  The full name of the method to check for.
 931:      * @param string $app     Only check this application.
 932:      *
 933:      * @return mixed  The application implementing $method if we have it,
 934:      *                false if the method doesn't exist.
 935:      */
 936:     public function hasMethod($method, $app = null)
 937:     {
 938:         if (is_null($app)) {
 939:             list($interface, $call) = explode('/', $method, 2);
 940:             if (!empty($this->_interfaces[$method])) {
 941:                 $app = $this->_interfaces[$method];
 942:             } elseif (!empty($this->_interfaces[$interface])) {
 943:                 $app = $this->_interfaces[$interface];
 944:             } else {
 945:                 return false;
 946:             }
 947:         } else {
 948:             $call = $method;
 949:         }
 950: 
 951:         $this->_loadApis();
 952: 
 953:         return (isset($this->_apis[$app]) && in_array($call, $this->_apis[$app]['api']))
 954:             ? $app
 955:             : false;
 956:     }
 957: 
 958:     /**
 959:      * Return the hook corresponding to the default package that provides the
 960:      * functionality requested by the $method parameter.
 961:      * $method is a string consisting of "packagetype/methodname".
 962:      *
 963:      * @param string $method  The method to call.
 964:      * @param array $args     Arguments to the method.
 965:      *
 966:      * @return mixed  Return from method call.
 967:      * @throws Horde_Exception
 968:      */
 969:     public function call($method, $args = array())
 970:     {
 971:         list($interface, $call) = explode('/', $method, 2);
 972: 
 973:         if (!empty($this->_interfaces[$method])) {
 974:             $app = $this->_interfaces[$method];
 975:         } elseif (!empty($this->_interfaces[$interface])) {
 976:             $app = $this->_interfaces[$interface];
 977:         } else {
 978:             throw new Horde_Exception('The method "' . $method . '" is not defined in the Horde Registry.');
 979:         }
 980: 
 981:         return $this->callByPackage($app, $call, $args);
 982:     }
 983: 
 984:     /**
 985:      * Output the hook corresponding to the specific package named.
 986:      *
 987:      * @param string $app     The application being called.
 988:      * @param string $call    The method to call.
 989:      * @param array $args     Arguments to the method.
 990:      * @param array $options  Additional options:
 991:      * <pre>
 992:      * 'noperms' - (boolean) If true, don't check the perms.
 993:      * </pre>
 994:      *
 995:      * @return mixed  Return from application call.
 996:      * @throws Horde_Exception
 997:      */
 998:     public function callByPackage($app, $call, $args = array(),
 999:                                   $options = array())
1000:     {
1001:         /* Note: calling hasMethod() makes sure that we've cached
1002:          * $app's services and included the API file, so we don't try
1003:          * to do it again explicitly in this method. */
1004:         if (!$this->hasMethod($call, $app)) {
1005:             throw new Horde_Exception(sprintf('The method "%s" is not defined in the API for %s.', $call, $app));
1006:         }
1007: 
1008:         /* Load the API now. */
1009:         $api = $this->getApiInstance($app, 'api');
1010: 
1011:         /* Make sure that the function actually exists. */
1012:         if (!method_exists($api, $call)) {
1013:             throw new Horde_Exception('The function implementing ' . $call . ' is not defined in ' . $app . '\'s API.');
1014:         }
1015: 
1016:         /* Switch application contexts now, if necessary, before
1017:          * including any files which might do it for us. Return an
1018:          * error immediately if pushApp() fails. */
1019:         $pushed = $this->pushApp($app, array('check_perms' => !in_array($call, $this->_apis[$app]['noperms']) && empty($options['noperms']) && $this->_args['authentication'] != 'none'));
1020: 
1021:         try {
1022:             $result = call_user_func_array(array($api, $call), $args);
1023:             if ($result instanceof PEAR_Error) {
1024:                 throw new Horde_Exception_Wrapped($result);
1025:             }
1026:         } catch (Horde_Exception $e) {
1027:             $result = $e;
1028:         }
1029: 
1030:         /* If we changed application context in the course of this
1031:          * call, undo that change now. */
1032:         if ($pushed === true) {
1033:             $this->popApp();
1034:         }
1035: 
1036:         if ($result instanceof Horde_Exception) {
1037:             throw $e;
1038:         }
1039: 
1040:         return $result;
1041:     }
1042: 
1043:     /**
1044:      * Call a private Horde application method.
1045:      *
1046:      * @param string $app     The application name.
1047:      * @param string $call    The method to call.
1048:      * @param array $options  Additional options:
1049:      * <pre>
1050:      * args - (array) Additional parameters to pass to the method.
1051:      * check_missing - (boolean) If true, throws an Exception if method does
1052:      *                 not exist. Otherwise, will return null.
1053:      * noperms - (boolean) If true, don't check the perms.
1054:      * </pre>
1055:      *
1056:      * @return mixed  Various.
1057:      * @throws Horde_Exception  Application methods should throw this if there
1058:      *                          is a fatal error.
1059:      */
1060:     public function callAppMethod($app, $call, array $options = array())
1061:     {
1062:         /* Load the API now. */
1063:         try {
1064:             $api = $this->getApiInstance($app, 'application');
1065:         } catch (Horde_Exception $e) {
1066:             if (empty($options['check_missing'])) {
1067:                 return null;
1068:             }
1069:             throw $e;
1070:         }
1071: 
1072:         if (!method_exists($api, $call)) {
1073:             if (empty($options['check_missing'])) {
1074:                 return null;
1075:             }
1076:             throw new Horde_Exception('Method does not exist.');
1077:         }
1078: 
1079:         /* Switch application contexts now, if necessary, before
1080:          * including any files which might do it for us. Return an
1081:          * error immediately if pushApp() fails. */
1082:         $pushed = $this->pushApp($app, array('check_perms' => empty($options['noperms']) && $this->_args['authentication'] != 'none'));
1083: 
1084:         try {
1085:             $result = call_user_func_array(array($api, $call), empty($options['args']) ? array() : $options['args']);
1086:         } catch (Horde_Exception $e) {
1087:             $result = $e;
1088:         }
1089: 
1090:         /* If we changed application context in the course of this
1091:          * call, undo that change now. */
1092:         if ($pushed === true) {
1093:             $this->popApp();
1094:         }
1095: 
1096:         if ($result instanceof Exception) {
1097:             throw $e;
1098:         }
1099: 
1100:         return $result;
1101:     }
1102: 
1103:     /**
1104:      * Returns the link corresponding to the default package that provides the
1105:      * functionality requested by the $method parameter.
1106:      *
1107:      * @param string $method  The method to link to, consisting of
1108:      *                        "packagetype/methodname".
1109:      * @param array $args     Arguments to the method.
1110:      * @param mixed $extra    Extra, non-standard arguments to the method.
1111:      *
1112:      * @return string  The link for that method.
1113:      * @throws Horde_Exception
1114:      */
1115:     public function link($method, $args = array(), $extra = '')
1116:     {
1117:         list($interface, $call) = explode('/', $method, 2);
1118: 
1119:         if (!empty($this->_interfaces[$method])) {
1120:             $app = $this->_interfaces[$method];
1121:         } elseif (!empty($this->_interfaces[$interface])) {
1122:             $app = $this->_interfaces[$interface];
1123:         } else {
1124:             throw new Horde_Exception('The method "' . $method . '" is not defined in the Horde Registry.');
1125:         }
1126: 
1127:         return $this->linkByPackage($app, $call, $args, $extra);
1128:     }
1129: 
1130:     /**
1131:      * Returns the link corresponding to the specific package named.
1132:      *
1133:      * @param string $app   The application being called.
1134:      * @param string $call  The method to link to.
1135:      * @param array $args   Arguments to the method.
1136:      * @param mixed $extra  Extra, non-standard arguments to the method.
1137:      *
1138:      * @return string  The link for that method.
1139:      * @throws Horde_Exception
1140:      */
1141:     public function linkByPackage($app, $call, $args = array(), $extra = '')
1142:     {
1143:         /* Make sure the link is defined. */
1144:         $this->_loadApis();
1145:         if (empty($this->_apis[$app]['links'][$call])) {
1146:             throw new Horde_Exception('The link ' . $call . ' is not defined in ' . $app . '\'s API.');
1147:         }
1148: 
1149:         /* Initial link value. */
1150:         $link = $this->_apis[$app]['links'][$call];
1151: 
1152:         /* Fill in html-encoded arguments. */
1153:         foreach ($args as $key => $val) {
1154:             $link = str_replace('%' . $key . '%', htmlentities($val), $link);
1155:         }
1156:         if (isset($this->applications[$app]['webroot'])) {
1157:             $link = str_replace('%application%', $this->get('webroot', $app), $link);
1158:         }
1159: 
1160:         /* Replace htmlencoded arguments that haven't been specified with
1161:            an empty string (this is where the default would be substituted
1162:            in a stricter registry implementation). */
1163:         $link = preg_replace('|%.+%|U', '', $link);
1164: 
1165:         /* Fill in urlencoded arguments. */
1166:         foreach ($args as $key => $val) {
1167:             $link = str_replace('|' . Horde_String::lower($key) . '|', urlencode($val), $link);
1168:         }
1169: 
1170:         /* Append any extra, non-standard arguments. */
1171:         if (is_array($extra)) {
1172:             $extra_args = '';
1173:             foreach ($extra as $key => $val) {
1174:                 $extra_args .= '&' . urlencode($key) . '=' . urlencode($val);
1175:             }
1176:         } else {
1177:             $extra_args = $extra;
1178:         }
1179:         $link = str_replace('|extra|', $extra_args, $link);
1180: 
1181:         /* Replace html-encoded arguments that haven't been specified with
1182:            an empty string (this is where the default would be substituted
1183:            in a stricter registry implementation). */
1184:         $link = preg_replace('|\|.+\||U', '', $link);
1185: 
1186:         return $link;
1187:     }
1188: 
1189:     /**
1190:      * Replace any %application% strings with the filesystem path to the
1191:      * application.
1192:      *
1193:      * @param string $path  The application string.
1194:      * @param string $app   The application being called.
1195:      *
1196:      * @return string  The application file path.
1197:      * @throws Horde_Exception
1198:      */
1199:     public function applicationFilePath($path, $app = null)
1200:     {
1201:         if (is_null($app)) {
1202:             $app = $this->getApp();
1203:         }
1204: 
1205:         if (!isset($this->applications[$app])) {
1206:             throw new Horde_Exception(sprintf(Horde_Core_Translation::t("\"%s\" is not configured in the Horde Registry."), $app));
1207:         }
1208: 
1209:         return str_replace('%application%', $this->applications[$app]['fileroot'], $path);
1210:     }
1211: 
1212:     /**
1213:      * Replace any %application% strings with the web path to the application.
1214:      *
1215:      * @param string $path  The application string.
1216:      * @param string $app   The application being called.
1217:      *
1218:      * @return string  The application web path.
1219:      */
1220:     public function applicationWebPath($path, $app = null)
1221:     {
1222:         if (!isset($app)) {
1223:             $app = $this->getApp();
1224:         }
1225: 
1226:         return str_replace('%application%', $this->applications[$app]['webroot'], $path);
1227:     }
1228: 
1229:     /**
1230:      * Set the current application, adding it to the top of the Horde
1231:      * application stack. If this is the first application to be
1232:      * pushed, retrieve session information as well.
1233:      *
1234:      * pushApp() also reads the application's configuration file and
1235:      * sets up its global $conf hash.
1236:      *
1237:      * @param string $app          The name of the application to push.
1238:      * @param array $options       Additional options:
1239:      * <pre>
1240:      * 'check_perms' - (boolean) Make sure that the current user has
1241:      *                 permissions to the application being loaded. Should
1242:      *                 ONLY be disabled by system scripts (cron jobs, etc.)
1243:      *                 and scripts that handle login.
1244:      *                 DEFAULT: true
1245:      * 'noinit' - (boolean) Do not init the application.
1246:      *            DEFAULT: false
1247:      * 'logintasks' - (boolean) Perform login tasks? Only performed if
1248:      *                'check_perms' is also true. System tasks are always
1249:      *                peformed if the user is authorized.
1250:      *                DEFAULT: false
1251:      * </pre>
1252:      *
1253:      * @return boolean  Whether or not the _appStack was modified.
1254:      * @throws Horde_Exception
1255:      *         Code can be one of the following:
1256:      *         Horde_Registry::AUTH_FAILURE
1257:      *         Horde_Registry::NOT_ACTIVE
1258:      *         Horde_Registry::PERMISSION_DENIED
1259:      *         Horde_Registry::HOOK_FATAL
1260:      */
1261:     public function pushApp($app, $options = array())
1262:     {
1263:         if ($app == $this->getApp()) {
1264:             return false;
1265:         }
1266: 
1267:         /* Bail out if application is not present or inactive. */
1268:         if (!isset($this->applications[$app]) || $this->isInactive($app)) {
1269:             throw new Horde_Exception($app . ' is not activated.', self::NOT_ACTIVE);
1270:         }
1271: 
1272:         $app_mappers = array(
1273:             'Controller' =>  'controllers',
1274:             'Helper' => 'helpers',
1275:             'SettingsExporter' => 'settings'
1276:         );
1277: 
1278:         /* Set up autoload paths for the current application. This needs to
1279:          * be done here because it is possible to try to load app-specific
1280:          * libraries from other applications. */
1281:         $autoloader = $GLOBALS['injector']->getInstance('Horde_Autoloader');
1282:         $autoloader->addClassPathMapper(new Horde_Autoloader_ClassPathMapper_Prefix('/^' . $app . '(?:$|_)/i', $this->get('fileroot', $app) . '/lib'));
1283: 
1284:         $applicationMapper = new Horde_Autoloader_ClassPathMapper_Application($this->get('fileroot', $app) . '/app');
1285:         foreach ($app_mappers as $key => $val) {
1286:             $applicationMapper->addMapping($key, $val);
1287:         }
1288:         $autoloader->addClassPathMapper($applicationMapper);
1289: 
1290:         $checkPerms = (!isset($options['check_perms']) ||
1291:                        !empty($options['check_perms'])) &&
1292:             $this->_args['authentication'] != 'none';
1293: 
1294:         /* If permissions checking is requested, return an error if the
1295:          * current user does not have read perms to the application being
1296:          * loaded. We allow access:
1297:          *  - To all admins.
1298:          *  - To all authenticated users if no permission is set on $app.
1299:          *  - To anyone who is allowed by an explicit ACL on $app. */
1300:         if ($checkPerms) {
1301:             if ($this->getAuth() && !$this->checkExistingAuth()) {
1302:                 throw new Horde_Exception('User is not authorized', self::AUTH_FAILURE);
1303:             }
1304: 
1305:             if (!$this->hasPermission($app, Horde_Perms::READ, array('notransparent' => !empty($options['notransparent'])))) {
1306:                 if (!$this->isAuthenticated(array('app' => $app))) {
1307:                     throw new Horde_Exception('User is not authorized for ' . $app, self::AUTH_FAILURE);
1308:                 }
1309: 
1310:                 Horde::logMessage(sprintf('%s does not have READ permission for %s', $this->getAuth() ? 'User ' . $this->getAuth() : 'Guest user', $app), 'DEBUG');
1311:                 throw new Horde_Exception(sprintf('%s is not authorized for %s.', $this->getAuth() ? 'User ' . $this->getAuth() : 'Guest user', $this->applications[$app]['name']), self::PERMISSION_DENIED);
1312:             }
1313:         }
1314: 
1315:         /* Push application on the stack. */
1316:         $this->_appStack[] = $app;
1317: 
1318:         /* Chicken and egg problem: the language environment has to be loaded
1319:          * before loading the configuration file, because it might contain
1320:          * gettext strings. Though the preferences can specify a different
1321:          * language for this app, they have to be loaded after the
1322:          * configuration, because they rely on configuration settings. So try
1323:          * with the current language, and reset the language later. */
1324:         $this->setLanguageEnvironment($GLOBALS['language'], $app);
1325: 
1326:         /* Load config and prefs. */
1327:         $this->importConfig($app);
1328:         $this->loadPrefs($app);
1329: 
1330:         /* Reset language, since now we can grab language from prefs. */
1331:         if (!$checkPerms && (count($this->_appStack) == 1)) {
1332:             $this->setLanguageEnvironment(null, $app);
1333:         }
1334: 
1335:         /* Call first initialization hook. */
1336:         if (isset($this->_appsinit[$app])) {
1337:             unset($this->_appsinit[$app]);
1338:             try {
1339:                 Horde::callHook('appinitialized', array(), $app);
1340:             } catch (Horde_Exception_HookNotSet $e) {}
1341:         }
1342: 
1343:         /* Call pre-push hook. */
1344:         if (Horde::hookExists('pushapp', $app)) {
1345:             try {
1346:                 Horde::callHook('pushapp', array(), $app);
1347:             } catch (Horde_Exception $e) {
1348:                 $e->setCode(self::HOOK_FATAL);
1349:                 $this->popApp();
1350:                 throw $e;
1351:             }
1352:         }
1353: 
1354:         /* Initialize application. */
1355:         if ($checkPerms || empty($options['noinit'])) {
1356:             try {
1357:                 $this->callAppMethod($app, 'init');
1358:             } catch (Horde_Exception $e) {
1359:                 $this->popApp();
1360:                 $this->applications[$app]['status'] = 'inactive';
1361:                 Horde::logMessage($e);
1362:                 throw $e;
1363:             }
1364:         }
1365: 
1366:         /* Call post-push hook. */
1367:         if (Horde::hookExists('pushapp_post', $app)) {
1368:             try {
1369:                 Horde::callHook('pushapp_post', array(), $app);
1370:             } catch (Exception $e) {}
1371:         }
1372: 
1373:         /* Do login tasks. */
1374:         if ($checkPerms &&
1375:             ($tasks = $GLOBALS['injector']->getInstance('Horde_Core_Factory_LoginTasks')->create($app)) &&
1376:             !empty($options['logintasks'])) {
1377:             $tasks->runTasks();
1378:         }
1379: 
1380:         return true;
1381:     }
1382: 
1383:     /**
1384:      * Remove the current app from the application stack, setting the current
1385:      * app to whichever app was current before this one took over.
1386:      *
1387:      * @return string  The name of the application that was popped.
1388:      * @throws Horde_Exception
1389:      */
1390:     public function popApp()
1391:     {
1392:         /* Pop the current application off of the stack. */
1393:         $previous = array_pop($this->_appStack);
1394: 
1395:         /* Import the new active application's configuration values
1396:          * and set the gettext domain and the preferred language. */
1397:         $app = $this->getApp();
1398:         if ($app) {
1399:             /* Load config and prefs. */
1400:             $this->importConfig($app);
1401:             $this->loadPrefs($app);
1402:             $this->setTextdomain(
1403:                 $app,
1404:                 $this->get('fileroot', $app) . '/locale'
1405:             );
1406:         }
1407: 
1408:         return $previous;
1409:     }
1410: 
1411:     /**
1412:      * Return the current application - the app at the top of the application
1413:      * stack.
1414:      *
1415:      * @return string  The current application.
1416:      */
1417:     public function getApp()
1418:     {
1419:         return end($this->_appStack);
1420:     }
1421: 
1422:     /**
1423:      * Check permissions on an application.
1424:      *
1425:      * @param string $app     The name of the application
1426:      * @param integer $perms  The permission level to check for.
1427:      * @param array $options  Additional options:
1428:      * <pre>
1429:      * 'notransparent' - (boolean) Do not attempt transparent authentication.
1430:      *                   DEFAULT: false
1431:      * </pre>
1432:      *
1433:      * @return boolean  Whether access is allowed.
1434:      */
1435:     public function hasPermission($app, $perms = Horde_Perms::READ,
1436:                                   array $params = array())
1437:     {
1438:         /* Always do isAuthenticated() check first. You can be an admin, but
1439:          * application auth != Horde admin auth. And there can *never* be
1440:          * non-SHOW access to an application that requires authentication. */
1441:         if (!$this->isAuthenticated(array('app' => $app, 'notransparent' => !empty($params['notransparent']))) &&
1442:             $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create($app)->requireAuth() &&
1443:             ($perms != Horde_Perms::SHOW)) {
1444:             return false;
1445:         }
1446: 
1447:         /* Otherwise, allow access for admins, for apps that do not have any
1448:          * explicit permissions, or for apps that allow the given permission. */
1449:         return $this->isAdmin() ||
1450:             ($GLOBALS['injector']->getInstance('Horde_Perms')->exists($app)
1451:              ? $GLOBALS['injector']->getInstance('Horde_Perms')->hasPermission($app, $this->getAuth(), $perms)
1452:              : (bool)$this->getAuth());
1453:     }
1454: 
1455:     /**
1456:      * Reads the configuration values for the given application and imports
1457:      * them into the global $conf variable.
1458:      *
1459:      * @param string $app  The application name.
1460:      */
1461:     public function importConfig($app)
1462:     {
1463:         if (!isset($this->_confCache[$app])) {
1464:             try {
1465:                 $config = Horde::loadConfiguration('conf.php', 'conf', $app);
1466:             } catch (Horde_Exception $e) {
1467:                 $config = null;
1468:             }
1469: 
1470:             $this->_confCache[$app] = empty($config)
1471:                 ? array()
1472:                 : $config;
1473:         }
1474: 
1475:         $GLOBALS['conf'] = ($app == 'horde')
1476:             ? $this->_confCache['horde']
1477:             : $this->_mergeConfig($this->_confCache['horde'], $this->_confCache[$app]);
1478:     }
1479: 
1480:     /**
1481:      * Merge configurations between two applications.
1482:      * See Bug #10381 for more information.
1483:      *
1484:      * @param array $a1  Horde configuration.
1485:      * @param array $a2  App configuration.
1486:      *
1487:      * @return array  Merged configuration.
1488:      */
1489:     protected function _mergeConfig(array $a1, array $a2)
1490:     {
1491:         foreach ($a2 as $key => $val) {
1492:             if (isset($a1[$key]) &&
1493:                 is_array($a1[$key])) {
1494:                 reset($a1[$key]);
1495:                 $a1[$key] = is_int(key($a1[$key]))
1496:                     ? $val
1497:                     : $this->_mergeConfig($a1[$key], $val);
1498:             } else {
1499:                 $a1[$key] = $val;
1500:             }
1501:         }
1502: 
1503:         return $a1;
1504:     }
1505: 
1506:     /**
1507:      * Loads the preferences for the current user for the current application
1508:      * and imports them into the global $prefs variable.
1509:      * $app will be the active application after calling this function.
1510:      *
1511:      * @param string $app  The name of the application.
1512:      *
1513:      * @throws Horde_Exception
1514:      */
1515:     public function loadPrefs($app = null)
1516:     {
1517:         global $injector, $prefs;
1518: 
1519:         if (strlen($app)) {
1520:             $this->pushApp($app);
1521:         } elseif (($app = $this->getApp()) === false) {
1522:             $app = 'horde';
1523:         }
1524: 
1525:         $user = $this->getAuth();
1526:         if ($user) {
1527:             if (isset($prefs) && ($prefs->getUser() == $user)) {
1528:                 $prefs->retrieve($app);
1529:                 return;
1530:             }
1531: 
1532:             $opts = array(
1533:                 'password' => $this->getAuthCredential('password'),
1534:                 'user' => $user,
1535:             );
1536:         } else {
1537:             /* If there is no logged in user, return an empty Horde_Prefs
1538:              * object with just default preferences. */
1539:             $opts = array(
1540:                 'driver' => 'Horde_Prefs_Storage_Null'
1541:             );
1542:         }
1543: 
1544:         $prefs = $injector->getInstance('Horde_Core_Factory_Prefs')->create($app, $opts);
1545:     }
1546: 
1547:     /**
1548:      * Return the requested configuration parameter for the specified
1549:      * application. If no application is specified, the value of
1550:      * the current application is used. However, if the parameter is not
1551:      * present for that application, the Horde-wide value is used instead.
1552:      * If that is not present, we return null.
1553:      *
1554:      * @param string $parameter  The configuration value to retrieve.
1555:      * @param string $app        The application to get the value for.
1556:      *
1557:      * @return string  The requested parameter, or null if it is not set.
1558:      */
1559:     public function get($parameter, $app = null)
1560:     {
1561:         if (is_null($app)) {
1562:             $app = $this->getApp();
1563:         }
1564: 
1565:         if (isset($this->applications[$app][$parameter])) {
1566:             $pval = $this->applications[$app][$parameter];
1567:         } elseif ($parameter == 'icon') {
1568:             $pval = Horde_Themes::img($app . '.png', $app);
1569:             if ((string)$pval == '') {
1570:                 $pval = Horde_Themes::img('app-unknown.png', 'horde');
1571:             }
1572:         } else {
1573:             $pval = isset($this->applications['horde'][$parameter]) ? $this->applications['horde'][$parameter] : null;
1574:         }
1575: 
1576:         return ($parameter == 'name')
1577:             ? (strlen($pval) ? _($pval) : '')
1578:             : $pval;
1579:     }
1580: 
1581:     /**
1582:      * Return the version string for a given application.
1583:      *
1584:      * @param string $app      The application to get the value for.
1585:      * @param boolean $number  Return the raw version number, suitable for
1586:      *                         comparison purposes.
1587:      *
1588:      * @return string  The version string for the application.
1589:      */
1590:     public function getVersion($app = null, $number = false)
1591:     {
1592:         if (empty($app)) {
1593:             $app = $this->getApp();
1594:         }
1595: 
1596:         try {
1597:             $api = $this->getApiInstance($app, 'application');
1598:         } catch (Horde_Exception $e) {
1599:             return 'unknown';
1600:         }
1601: 
1602:         return $number
1603:             ? preg_replace('/H\d \((.*)\)/', '$1', $api->version)
1604:             : $api->version;
1605:     }
1606: 
1607:     /**
1608:      * Does the given application have a mobile view?
1609:      *
1610:      * @param string $app  The application to check.
1611:      *
1612:      * @return boolean  Whether app has mobile view.
1613:      */
1614:     public function hasMobileView($app = null)
1615:     {
1616:         if (empty($app)) {
1617:             $app = $this->getApp();
1618:         }
1619: 
1620:         try {
1621:             $api = $this->getApiInstance($app, 'application');
1622:             return !empty($api->mobileView);
1623:         } catch (Horde_Exception $e) {
1624:             return false;
1625:         }
1626:     }
1627: 
1628:     /**
1629:      * Does the given application have an ajax view?
1630:      *
1631:      * @param string $app  The application to check.
1632:      *
1633:      * @return boolean  Whether app has an ajax view.
1634:      */
1635:     public function hasAjaxView($app = null)
1636:     {
1637:         if (empty($app)) {
1638:             $app = $this->getApp();
1639:         }
1640: 
1641:         try {
1642:             $api = $this->getApiInstance($app, 'application');
1643:             return !empty($api->ajaxView);
1644:         } catch (Horde_Exception $e) {
1645:             return false;
1646:         }
1647:     }
1648: 
1649:     /**
1650:      * Returns a list of available drivers for a library that are available
1651:      * in an application.
1652:      *
1653:      * @param string $app     The application name.
1654:      * @param string $prefix  The library prefix.
1655:      *
1656:      * @return array  The list of available class names.
1657:      */
1658:     public function getAppDrivers($app, $prefix)
1659:     {
1660:         $classes = array();
1661:         $fileprefix = strtr($prefix, '_', '/');
1662:         $fileroot = $this->get('fileroot', $app);
1663: 
1664:         if (!is_null($fileroot)) {
1665:             try {
1666:                 $pushed = $this->pushApp($app);
1667:             } catch (Horde_Exception $e) {
1668:                 if ($e->getCode() == Horde_Registry::AUTH_FAILURE) {
1669:                     return array();
1670:                 }
1671:                 throw $e;
1672:             }
1673: 
1674:             if (is_dir($fileroot . '/lib/' . $fileprefix)) {
1675:                 try {
1676:                     $di = new DirectoryIterator($fileroot . '/lib/' . $fileprefix);
1677: 
1678:                     foreach ($di as $val) {
1679:                         if (!$val->isDir() && !$di->isDot()) {
1680:                             $class = $app . '_' . $prefix . '_' . basename($val, '.php');
1681:                             if (class_exists($class)) {
1682:                                 $classes[] = $class;
1683:                             }
1684:                         }
1685:                     }
1686:                 } catch (UnexpectedValueException $e) {}
1687:             }
1688: 
1689:             if ($pushed) {
1690:                 $this->popApp();
1691:             }
1692:         }
1693: 
1694:         return $classes;
1695:     }
1696: 
1697:     /**
1698:      * Query the initial page for an application - the webroot, if there is no
1699:      * initial_page set, and the initial_page, if it is set.
1700:      *
1701:      * @param string $app  The name of the application.
1702:      *
1703:      * @return string  URL pointing to the initial page of the application.
1704:      * @throws Horde_Exception
1705:      */
1706:     public function getInitialPage($app = null)
1707:     {
1708:         if (is_null($app)) {
1709:             $app = $this->getApp();
1710:         }
1711: 
1712:         if (isset($this->applications[$app])) {
1713:             return $this->applications[$app]['webroot'] . '/' . (isset($this->applications[$app]['initial_page']) ? $this->applications[$app]['initial_page'] : '');
1714:         }
1715: 
1716:         throw new Horde_Exception(sprintf(Horde_Core_Translation::t("\"%s\" is not configured in the Horde Registry."), $app));
1717:     }
1718: 
1719:     /**
1720:      * Retrieves a cache variable.
1721:      *
1722:      * @param string $name  Cache variable name.
1723:      *
1724:      * @return mixed  The cached data or false if no data retrieved.
1725:      */
1726:     protected function _loadCache($name)
1727:     {
1728:         if (empty($this->_args['test']) &&
1729:             ($id = $this->_getCacheId($name))) {
1730:             $result = $GLOBALS['injector']->getInstance('Horde_Cache')->get($id, 86400);
1731:             if ($result !== false) {
1732:                 Horde::logMessage(__CLASS__ . ': retrieved ' . $name . ' with cache ID ' . $id, 'DEBUG');
1733:                 return unserialize($result);
1734:             }
1735:         }
1736: 
1737:         return false;
1738:     }
1739: 
1740:     /**
1741:      * Get the cache storage ID for a particular cache name.
1742:      *
1743:      * @param string $name  Cache variable name.
1744:      * @param string $md5   Use this MD5 value instead of session MD5 value.
1745:      *
1746:      * @return mixed  The cache ID or false if cache entry doesn't exist in
1747:      *                the session.
1748:      */
1749:     protected function _getCacheId($name, $md5 = null)
1750:     {
1751:         $id = 'horde_registry|' . $name . '|' . $this->_regmtime;
1752: 
1753:         if (!is_null($md5) ||
1754:             ($md5 = $GLOBALS['session']->get('horde', 'registry/' . $name))) {
1755:             return $id . '|' . $md5;
1756:         }
1757: 
1758:         return false;
1759:     }
1760: 
1761:     /**
1762:      * Save a registry cache entry.
1763:      *
1764:      * @param string $key  The cache key.
1765:      * @param mixed $data  The cache data. If null, deletes the item.
1766:      */
1767:     protected function _saveCache($key, $data = null)
1768:     {
1769:         if (!empty($this->_args['test'])) {
1770:             return;
1771:         }
1772: 
1773:         $ob = $GLOBALS['injector']->getInstance('Horde_Cache');
1774: 
1775:         if (is_null($data)) {
1776:             if ($id = $this->_getCacheId($key)) {
1777:                 // Entry has been deleted.
1778:                 $ob->expire($id);
1779:             }
1780:         } else {
1781:             // Entry has been updated.
1782:             $data = serialize($data);
1783:             $md5sum = hash('md5', $data);
1784:             $GLOBALS['session']->set('horde', 'registry/' . $key, $md5sum);
1785:             $id = $this->_getCacheId($key, $md5sum);
1786:             if ($ob->set($id, $data, 86400)) {
1787:                 Horde::logMessage(__CLASS__ . ': stored ' . $key . ' with cache ID ' . $id, 'DEBUG');
1788:             }
1789:         }
1790:     }
1791: 
1792:     /**
1793:      * Destroys any existing session on login and make sure to use a new
1794:      * session ID, to avoid session fixation issues. Should be called before
1795:      * checking a login.
1796:      */
1797:     public function getCleanSession()
1798:     {
1799:         if ($GLOBALS['session']->clean() &&
1800:             !empty($GLOBALS['conf']['session']['timeout'])) {
1801:             /* Reset cookie timeouts, if necessary. */
1802:             $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
1803:             $secret->setKey('auth');
1804:         }
1805:     }
1806: 
1807:     /**
1808:      * Clears any authentication tokens in the current session.
1809:      *
1810:      * @param boolean $destroy  Destroy the session?
1811:      */
1812:     public function clearAuth($destroy = true)
1813:     {
1814:         global $session;
1815: 
1816:         /* Do logout tasks. */
1817:         foreach (array_keys($session->get('horde', 'auth_app/', Horde_Session::TYPE_ARRAY)) as $app) {
1818:             try {
1819:                 $this->callAppMethod($app, 'logout');
1820:             } catch (Horde_Exception $e) {}
1821:         }
1822: 
1823:         $session->remove('horde', 'auth');
1824:         $session->remove('horde', 'auth_app/');
1825: 
1826:         /* Remove the user's cached preferences if they are present. */
1827:         $GLOBALS['injector']->getInstance('Horde_Core_Factory_Prefs')->clearCache();
1828: 
1829:         if ($destroy) {
1830:             $session->destroy();
1831:         }
1832:     }
1833: 
1834:     /**
1835:      * Is a user an administrator?
1836:      *
1837:      * @param array $options  Options:
1838:      * <pre>
1839:      * 'permission' - (string) Allow users with this permission admin access
1840:      *                in the current context.
1841:      * 'permlevel' - (integer) The level of permissions to check for.
1842:      *               Defaults to Horde_Perms::EDIT.
1843:      * 'user' - (string) The user to check.
1844:      *          Defaults to self::getAuth().
1845:      * </pre>
1846:      *
1847:      * @return boolean  Whether or not this is an admin user.
1848:      */
1849:     public function isAdmin(array $options = array())
1850:     {
1851:         $user = isset($options['user'])
1852:             ? $options['user']
1853:             : $this->getAuth();
1854: 
1855:         if ($user &&
1856:             @is_array($GLOBALS['conf']['auth']['admins']) &&
1857:             in_array($user, $GLOBALS['conf']['auth']['admins'])) {
1858:             return true;
1859:         }
1860: 
1861:         return isset($options['permission'])
1862:             ? $GLOBALS['injector']->getInstance('Horde_Perms')->hasPermission($options['permission'], $user, isset($options['permlevel']) ? $options['permlevel'] : Horde_Perms::EDIT)
1863:             : false;
1864:     }
1865: 
1866:     /**
1867:      * Checks if there is a session with valid auth information. If there
1868:      * isn't, but the configured Auth driver supports transparent
1869:      * authentication, then we try that.
1870:      *
1871:      * @param array $options  Additional options:
1872:      * <pre>
1873:      * 'app' - (string) Check authentication for this app.
1874:      *         DEFAULT: Checks horde-wide authentication.
1875:      * 'notransparent' - (boolean) Do not attempt transparent authentication.
1876:      *                   DEFAULT: false
1877:      * </pre>
1878:      *
1879:      * @return boolean  Whether or not the user is authenticated.
1880:      */
1881:     public function isAuthenticated(array $options = array())
1882:     {
1883:         $app = empty($options['app'])
1884:             ? 'horde'
1885:             : $options['app'];
1886: 
1887:         /* Check for cached authentication results. */
1888:         if ($this->getAuth() &&
1889:             (($app == 'horde') ||
1890:              $GLOBALS['session']->exists('horde', 'auth_app/' . $app))) {
1891:             if ($this->checkExistingAuth($app)) {
1892:                 return true;
1893:             }
1894:         }
1895: 
1896:         /* Try transparent authentication. */
1897:         if (!empty($options['notransparent'])) {
1898:             return false;
1899:         }
1900:         try {
1901:             return $GLOBALS['injector']
1902:                 ->getInstance('Horde_Core_Factory_Auth')
1903:                 ->create($app)
1904:                 ->transparent();
1905:         } catch (Horde_Exception $e) {
1906:             Horde::logMessage($e);
1907:             return false;
1908:         }
1909:     }
1910: 
1911:     /**
1912:      * Handle authentication failures, redirecting to the login page
1913:      * when appropriate.
1914:      *
1915:      * @param string $app         The app which failed authentication.
1916:      * @param Horde_Exception $e  An exception thrown by pushApp().
1917:      *
1918:      * @throws Horde_Exception
1919:      */
1920:     public function authenticateFailure($app = 'horde', $e = null)
1921:     {
1922:         if (Horde_Cli::runningFromCLI()) {
1923:             $cli = new Horde_Cli();
1924:             $cli->fatal($e ? $e : Horde_Core_Translation::t("You are not authenticated."));
1925:         }
1926: 
1927:         if (is_null($e)) {
1928:             $params = array();
1929:         } else {
1930:             switch ($e->getCode()) {
1931:             case self::PERMISSION_DENIED:
1932:                 $params = array('app' => $app, 'reason' => Horde_Auth::REASON_MESSAGE, 'msg' => $e->getMessage());
1933:                 break;
1934: 
1935:             case self::AUTH_FAILURE:
1936:                 $params = array('app' => $app);
1937:                 break;
1938: 
1939:             default:
1940:                 throw $e;
1941:             }
1942:         }
1943: 
1944:         header('Location: ' . $this->getLogoutUrl($params));
1945:         exit;
1946:     }
1947: 
1948:     /**
1949:      * Returns a URL to the login screen, adding the necessary logout
1950:      * parameters.
1951:      *
1952:      * If no reason/msg is passed in, uses the current global authentication
1953:      * error message.
1954:      *
1955:      * @param array $options  Additional options:
1956:      *     - app: (string) Authenticate to this application
1957:      *            DEFAULT: Horde
1958:      *     - msg: (string) If reason is Horde_Auth::REASON_MESSAGE, the message
1959:      *            to display to the user.
1960:      *            DEFAULT: None
1961:      *     - params: (array) Additional params to add to the URL (not allowed:
1962:      *               'app', 'horde_logout_token', 'msg', 'reason', 'url').
1963:      *               DEFAULT: None
1964:      *     - reason: (integer) The reason for logout
1965:      *               DEFAULT: None
1966:      *
1967:      * @return Horde_Url  The formatted URL.
1968:      */
1969:     public function getLogoutUrl(array $options = array())
1970:     {
1971:         if (!isset($options['reason'])) {
1972:             // TODO: This only returns the error for Horde-wide
1973:             // authentication, not for application auth.
1974:             $options['reason'] = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create()->getError();
1975:         }
1976: 
1977:         $params = array();
1978:         if ($options['reason'] != Horde_Auth::REASON_LOGOUT) {
1979:             $params['url'] = Horde::selfUrl(true, true, true);
1980:         }
1981: 
1982:         if (empty($options['app']) ||
1983:             ($options['app'] == 'horde') ||
1984:             ($options['reason'] == Horde_Auth::REASON_LOGOUT)) {
1985:             $params['horde_logout_token'] = $GLOBALS['injector']->getInstance('Horde_Token')->get('horde.logout');
1986:        }
1987: 
1988:         if (isset($options['app'])) {
1989:             $params['app'] = $options['app'];
1990:         }
1991: 
1992:         if ($options['reason']) {
1993:             $params['logout_reason'] = $options['reason'];
1994:             if ($options['reason'] == Horde_Auth::REASON_MESSAGE) {
1995:                 $params['logout_msg'] = empty($options['msg'])
1996:                     ? $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create()->getError(true)
1997:                     : $options['msg'];
1998:             }
1999:         }
2000: 
2001:         return Horde::getServiceLink('login', 'horde')->add($params)->setRaw(true);
2002:     }
2003: 
2004:     /**
2005:      * Converts an authentication username to a unique Horde username.
2006:      *
2007:      * @param string $username  The username to convert.
2008:      * @param boolean $toHorde  If true, convert to a Horde username. If
2009:      *                          false, convert to the auth username.
2010:      *
2011:      * @return string  The converted username.
2012:      * @throws Horde_Exception
2013:      */
2014:     public function convertUsername($userId, $toHorde)
2015:     {
2016:         try {
2017:             return Horde::callHook('authusername', array($userId, $toHorde));
2018:         } catch (Horde_Exception_HookNotSet $e) {
2019:             return $userId;
2020:         }
2021:     }
2022: 
2023:     /**
2024:      * Returns the currently logged in user, if there is one.
2025:      *
2026:      * @param string $format  The return format, defaults to the unique Horde
2027:      *                        ID. Alternative formats:
2028:      * <pre>
2029:      * bare - Horde ID without any domain information.
2030:      *        EXAMPLE: foo@example.com would be returned as 'foo'.
2031:      * domain: Domain of the Horde ID.
2032:      *         EXAMPLE: foo@example.com would be returned as 'example.com'.
2033:      * original: The username used to originally login to Horde.
2034:      * </pre>
2035:      *
2036:      * @return mixed  The user ID or false if no user is logged in.
2037:      */
2038:     public function getAuth($format = null)
2039:     {
2040:         global $session;
2041: 
2042:         if (!isset($session)) {
2043:             return false;
2044:         }
2045: 
2046:         if ($format == 'original') {
2047:             return $session->exists('horde', 'auth/authId')
2048:                 ? $session->get('horde', 'auth/authId')
2049:                 : false;
2050:         }
2051: 
2052:         $user = $session->get('horde', 'auth/userId');
2053:         if (is_null($user)) {
2054:             return false;
2055:         }
2056: 
2057:         switch ($format) {
2058:         case 'bare':
2059:             return (($pos = strpos($user, '@')) === false)
2060:                 ? $user
2061:                 : substr($user, 0, $pos);
2062: 
2063:         case 'domain':
2064:             return (($pos = strpos($user, '@')) === false)
2065:                 ? false
2066:                 : substr($user, $pos + 1);
2067: 
2068:         default:
2069:             return $user;
2070:         }
2071:     }
2072: 
2073:     /**
2074:      * Return whether the authentication backend requested a password change.
2075:      *
2076:      * @return boolean  Whether the backend requested a password change.
2077:      */
2078:     public function passwordChangeRequested()
2079:     {
2080:         return (bool)$GLOBALS['session']->get('horde', 'auth/change');
2081:     }
2082: 
2083:     /**
2084:      * Returns the requested credential for the currently logged in user, if
2085:      * present.
2086:      *
2087:      * @param string $credential  The credential to retrieve.
2088:      * @param string $app         The app to query. Defaults to Horde.
2089:      *
2090:      * @return mixed  The requested credential, all credentials if $credential
2091:      *                is null, or false if no user is logged in.
2092:      */
2093:     public function getAuthCredential($credential = null, $app = null)
2094:     {
2095:         if (!$this->getAuth()) {
2096:             return false;
2097:         }
2098: 
2099:         $credentials = $this->_getAuthCredentials($app);
2100: 
2101:         return is_null($credential)
2102:             ? $credentials
2103:             : ((is_array($credentials) && isset($credentials[$credential]))
2104:                    ? $credentials[$credential]
2105:                    : false);
2106:     }
2107: 
2108:     /**
2109:      * Sets the requested credential for the currently logged in user.
2110:      *
2111:      * @param mixed $credential  The credential to set.  If an array,
2112:      *                           overwrites the current credentials array.
2113:      * @param string $value      The value to set the credential to. If
2114:      *                           $credential is an array, this value is
2115:      *                           ignored.
2116:      * @param string $app        The app to update. Defaults to Horde.
2117:      */
2118:     public function setAuthCredential($credential, $value = null, $app = null)
2119:     {
2120:         global $session;
2121: 
2122:         if (!$this->getAuth()) {
2123:             return;
2124:         }
2125: 
2126:         if (is_array($credential)) {
2127:             $credentials = $credential;
2128:         } else {
2129:             if (($credentials = $this->_getAuthCredentials($app)) === false) {
2130:                 return;
2131:             }
2132: 
2133:             if (!is_array($credentials)) {
2134:                 $credentials = array();
2135:             }
2136: 
2137:             $credentials[$credential] = $value;
2138:         }
2139: 
2140:         $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
2141:         $entry = $secret->write($secret->getKey('auth'), serialize($credentials));
2142: 
2143:         if (($base_app = $session->get('horde', 'auth/credentials')) &&
2144:             ($session->get('horde', 'auth_app/' . $base_app) == $entry)) {
2145:             $entry = true;
2146:         }
2147: 
2148:         if (is_null($app)) {
2149:             $app = $base_app;
2150:         }
2151: 
2152:         $session->set('horde', 'auth_app/' . $app, $entry);
2153:     }
2154: 
2155:     /**
2156:      * Get the list of credentials for a given app.
2157:      *
2158:      * @param string $app  The application name.
2159:      *
2160:      * @return mixed  True, false, or the credential list.
2161:      */
2162:     protected function _getAuthCredentials($app)
2163:     {
2164:         global $session;
2165: 
2166:         $base_app = $session->get('horde', 'auth/credentials');
2167:         if (is_null($base_app)) {
2168:             return false;
2169:         }
2170: 
2171:         if (is_null($app)) {
2172:             $app = $base_app;
2173:         }
2174: 
2175:         if (!$session->exists('horde', 'auth_app/' . $app)) {
2176:             return ($base_app != $app)
2177:                 ? $this->_getAuthCredentials($base_app)
2178:                 : false;
2179:         }
2180: 
2181:         $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
2182:         $data = $secret->read($secret->getKey('auth'),
2183:                               $session->get('horde', 'auth_app/' . $app));
2184:         return @unserialize($data);
2185:     }
2186: 
2187:     /**
2188:      * Sets data in the session saying that authorization has succeeded,
2189:      * note which userId was authorized, and note when the login took place.
2190:      *
2191:      * If a user name hook was defined in the configuration, it gets applied
2192:      * to the $userId at this point.
2193:      *
2194:      * Horde authentication data is stored in the session in the 'auth' and
2195:      * 'auth_app' array keys.  The 'auth' key has the following members:
2196:      * <pre>
2197:      * 'authId' - (string) The username used during the original auth.
2198:      * 'browser' - (string) The remote browser string.
2199:      * 'change' - (boolean) Is a password change requested?
2200:      * 'credentials' - (string) The 'auth_app' entry that contains the Horde
2201:      *                 credentials.
2202:      * 'remoteAddr' - (string) The remote IP address of the user.
2203:      * 'timestamp' - (integer) The login time.
2204:      * 'userId' - (string) The unique Horde username.
2205:      * </pre>
2206:      *
2207:      * The auth_app key contains application-specific authentication.
2208:      * Session subkeys are the app names, values are an array containing
2209:      * credentials. If the value is true, application does not require any
2210:      * specific credentials.
2211:      *
2212:      * @param string $authId      The userId that has been authorized.
2213:      * @param array $credentials  The credentials of the user.
2214:      * @param array $options      Additional options:
2215:      * <pre>
2216:      * 'app' - (string) The app to set authentication credentials for.
2217:      *         DEFAULT: 'horde'
2218:      * 'change' - (boolean) Whether to request that the user change their
2219:      *            password.
2220:      *            DEFAULT: No
2221:      * 'language' - (string) The preferred language.
2222:      *              DEFAULT: null
2223:      * </pre>
2224:      */
2225:     public function setAuth($authId, $credentials, array $options = array())
2226:     {
2227:         global $browser, $injector, $session;
2228: 
2229:         $app = empty($options['app'])
2230:             ? 'horde'
2231:             : $options['app'];
2232: 
2233:         $this->_appsinit[$app] = true;
2234: 
2235:         if ($this->getAuth() == $authId) {
2236:             /* Store app credentials - base Horde session already exists. */
2237:             $this->setAuthCredential($credentials, null, $app);
2238:             return;
2239:         }
2240: 
2241:         /* Initial authentication to Horde. */
2242:         $session->set('horde', 'auth/authId', $authId);
2243:         $session->set('horde', 'auth/browser', $browser->getAgentString());
2244:         if (!empty($options['change'])) {
2245:             $session->set('horde', 'auth/change', 1);
2246:         }
2247:         $session->set('horde', 'auth/credentials', $app);
2248:         if (isset($_SERVER['REMOTE_ADDR'])) {
2249:             $session->set('horde', 'auth/remoteAddr', $_SERVER['REMOTE_ADDR']);
2250:         }
2251:         $session->set('horde', 'auth/timestamp', time());
2252:         $session->set('horde', 'auth/userId', $this->convertUsername(trim($authId), true));
2253: 
2254:         $this->setAuthCredential($credentials, null, $app);
2255: 
2256:         /* Reload preferences for the new user. */
2257:         unset($GLOBALS['prefs']);
2258:         $injector->getInstance('Horde_Core_Factory_Prefs')->clearCache();
2259:         $this->loadPrefs($this->getApp());
2260: 
2261:         unset($this->_appsinit['horde']);
2262:         try {
2263:             Horde::callHook('appinitialized', array(), 'horde');
2264:         } catch (Horde_Exception_HookNotSet $e) {}
2265: 
2266:         $this->setLanguageEnvironment(isset($options['language']) ? $this->preferredLang($options['language']) : null, $app);
2267:     }
2268: 
2269:     /**
2270:      * Check existing auth for triggers that might invalidate it.
2271:      *
2272:      * @param string $app  Check authentication for this app too.
2273:      *                     @since Horde_Core 1.4.0.
2274:      *
2275:      * @return boolean  Is existing auth valid?
2276:      */
2277:     public function checkExistingAuth($app = 'horde')
2278:     {
2279:         global $session;
2280: 
2281:         $auth = $GLOBALS['injector']
2282:             ->getInstance('Horde_Core_Factory_Auth')
2283:             ->create();
2284: 
2285:         if (!empty($GLOBALS['conf']['auth']['checkip']) &&
2286:             ($remoteaddr = $session->get('horde', 'auth/remoteAddr')) &&
2287:             ($remoteaddr != $_SERVER['REMOTE_ADDR'])) {
2288:             $auth->setError(Horde_Core_Auth_Application::REASON_SESSIONIP);
2289:             return false;
2290:         }
2291: 
2292:         if (!empty($GLOBALS['conf']['auth']['checkbrowser']) &&
2293:             ($session->get('horde', 'auth/browser') != $GLOBALS['browser']->getAgentString())) {
2294:             $auth->setError(Horde_Core_Auth_Application::REASON_BROWSER);
2295:             return false;
2296:         }
2297: 
2298:         if ($auth->validateAuth()) {
2299:             if ($app != 'horde') {
2300:                 $auth = $GLOBALS['injector']
2301:                     ->getInstance('Horde_Core_Factory_Auth')
2302:                     ->create($app);
2303:                 if (!$auth->validateAuth()) {
2304:                     return false;
2305:                 }
2306:             }
2307:             return true;
2308:         }
2309: 
2310:         /* Make sure there is always a logout reason set. */
2311:         if (!$auth->getError()) {
2312:             $auth->setError(Horde_Auth::REASON_SESSION);
2313:         }
2314: 
2315:         return false;
2316:     }
2317: 
2318:     /**
2319:      * Removes a user from the authentication backend and calls all
2320:      * applications' removeUserData API methods.
2321:      *
2322:      * @param string $userId  The userId to delete.
2323:      *
2324:      * @throws Horde_Exception
2325:      */
2326:     public function removeUser($userId)
2327:     {
2328:         $GLOBALS['injector']
2329:             ->getInstance('Horde_Core_Factory_Auth')
2330:             ->create()
2331:             ->removeUser($userId);
2332:         $this->removeUserData($userId);
2333:     }
2334: 
2335:     /**
2336:      * Removes user's application data.
2337:      *
2338:      * @param string $user  The user ID to delete.
2339:      * @param string $app   If set, only removes data from this application.
2340:      *                      By default, removes data from all apps.
2341:      *
2342:      * @throws Horde_Exception
2343:      */
2344:     public function removeUserData($user, $app = null)
2345:     {
2346:         if (!$this->isAdmin() && ($user != $this->getAuth())) {
2347:             throw new Horde_Exception(Horde_Core_Translation::t("You are not allowed to remove user data."));
2348:         }
2349: 
2350:         $applist = empty($app)
2351:             ? $this->listApps(array('notoolbar', 'hidden', 'active', 'admin', 'noadmin'))
2352:             : array($app);
2353:         $errApps = array();
2354:         if (!empty($applist)) {
2355:             $prefs_ob = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Prefs')->create('horde', array(
2356:                 'user' => $user
2357:             ));
2358:         }
2359: 
2360:         foreach ($applist as $app) {
2361:             try {
2362:                 $this->callAppMethod($app, 'removeUserData', array(
2363:                     'args' => array($user)
2364:                 ));
2365:             } catch (Exception $e) {
2366:                 Horde::logMessage($e);
2367:                 $errApps[] = $app;
2368:             }
2369: 
2370:             try {
2371:                 $prefs_ob->retrieve($app);
2372:                 $prefs_ob->remove();
2373:             } catch (Horde_Exception $e) {
2374:                 Horde::logMessage($e);
2375:                 $errApps[] = $app;
2376:             }
2377:         }
2378: 
2379:         if (count($errApps)) {
2380:             throw new Horde_Exception(sprintf(Horde_Core_Translation::t("The following applications encountered errors removing user data: %s"), implode(', ', array_unique($errApps))));
2381:         }
2382:     }
2383: 
2384:     /* NLS functions. */
2385: 
2386:     /**
2387:      * Returns the charset for the current language.
2388:      *
2389:      * @return string  The character set that should be used with the current
2390:      *                 locale settings.
2391:      */
2392:     public function getLanguageCharset()
2393:     {
2394:         return ($charset = $this->nlsconfig->curr_charset)
2395:             ? $charset
2396:             : 'ISO-8859-1';
2397:     }
2398: 
2399:     /**
2400:      * Returns the charset to use for outgoing emails.
2401:      *
2402:      * @return string  The preferred charset for outgoing mails based on
2403:      *                 the user's preferences and the current language.
2404:      */
2405:     public function getEmailCharset()
2406:     {
2407:         if (isset($GLOBALS['prefs']) && ($charset = $GLOBALS['prefs']->getValue('sending_charset'))) {
2408:             return $charset;
2409:         }
2410: 
2411:         return ($charset = $this->nlsconfig->curr_emails)
2412:             ? $charset
2413:             : $this->getLanguageCharset();
2414:     }
2415: 
2416:     /**
2417:      * Selects the most preferred language for the current client session.
2418:      *
2419:      * @param string $lang  Force to use this language.
2420:      *
2421:      * @return string  The selected language abbreviation.
2422:      */
2423:     public function preferredLang($lang = null)
2424:     {
2425:         /* If language pref exists, we should use that. */
2426:         if (isset($GLOBALS['prefs']) &&
2427:             ($language = $GLOBALS['prefs']->getValue('language'))) {
2428:             return basename($language);
2429:         }
2430: 
2431:         /* Check if the user selected a language from the login screen */
2432:         if (!empty($lang) && $this->nlsconfig->validLang($lang)) {
2433:             return basename($lang);
2434:         }
2435: 
2436:         /* Check if we have a language set in the session */
2437:         if ($GLOBALS['session']->exists('horde', 'language')) {
2438:             return basename($GLOBALS['session']->get('horde', 'language'));
2439:         }
2440: 
2441:         /* Try browser-accepted languages. */
2442:         if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
2443:             /* The browser supplies a list, so return the first valid one. */
2444:             $browser_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
2445:             foreach ($browser_langs as $lang) {
2446:                 /* Strip quality value for language */
2447:                 if (($pos = strpos($lang, ';')) !== false) {
2448:                     $lang = substr($lang, 0, $pos);
2449:                 }
2450: 
2451:                 $lang = $this->_mapLang(trim($lang));
2452:                 if ($this->nlsconfig->validLang($lang)) {
2453:                     return basename($lang);
2454:                 }
2455: 
2456:                 /* In case there's no full match, save our best guess. Try
2457:                  * ll_LL, followed by just ll. */
2458:                 if (!isset($partial_lang)) {
2459:                     $ll_LL = Horde_String::lower(substr($lang, 0, 2)) . '_' . Horde_String::upper(substr($lang, 0, 2));
2460:                     if ($this->nlsconfig->validLang($ll_LL)) {
2461:                         $partial_lang = $ll_LL;
2462:                     } else {
2463:                         $ll = $this->_mapLang(substr($lang, 0, 2));
2464:                         if ($this->nlsconfig->validLang($ll))  {
2465:                             $partial_lang = $ll;
2466:                         }
2467:                     }
2468:                 }
2469:             }
2470: 
2471:             if (isset($partial_lang)) {
2472:                 return basename($partial_lang);
2473:             }
2474:         }
2475: 
2476:         /* Use site-wide default, if one is defined */
2477:         return $this->nlsconfig->curr_default
2478:             ? basename($this->nlsconfig->curr_default)
2479:             /* No dice auto-detecting, default to US English. */
2480:             : 'en_US';
2481:     }
2482: 
2483:     /**
2484:      * Sets the language.
2485:      *
2486:      * @param string $lang  The language abbreviation.
2487:      *
2488:      * @throws Horde_Exception
2489:      */
2490:     public function setLanguage($lang = null)
2491:     {
2492:         if (empty($lang) || !$this->nlsconfig->validLang($lang)) {
2493:             $lang = $this->preferredLang();
2494:         }
2495: 
2496:         $GLOBALS['session']->set('horde', 'language', $lang);
2497: 
2498:         $changed = false;
2499:         if (isset($GLOBALS['language'])) {
2500:             if ($GLOBALS['language'] == $lang) {
2501:                 return;
2502:             }
2503:             $changed = true;
2504:         }
2505:         $GLOBALS['language'] = $lang;
2506: 
2507:         $lang_charset = $lang . '.UTF-8';
2508:         if (setlocale(LC_ALL, $lang_charset)) {
2509:             putenv('LC_ALL=' . $lang_charset);
2510:             putenv('LANG=' . $lang_charset);
2511:             putenv('LANGUAGE=' . $lang_charset);
2512:         } else {
2513:             $changed = false;
2514:         }
2515: 
2516:         if ($changed) {
2517:             $this->rebuild();
2518:         }
2519:     }
2520: 
2521:     /**
2522:      * Sets the language and reloads the whole NLS environment.
2523:      *
2524:      * When setting the language, the gettext catalogs have to be reloaded
2525:      * too, charsets have to be updated etc. This method takes care of all
2526:      * this.
2527:      *
2528:      * @param string $language  The new language.
2529:      * @param string $app       The application for reloading the gettext
2530:      *                          catalog. The current application if empty.
2531:      */
2532:     public function setLanguageEnvironment($lang = null, $app = null)
2533:     {
2534:         if (empty($app)) {
2535:             $app = $this->getApp();
2536:         }
2537: 
2538:         $this->setLanguage($lang);
2539:         $this->setTextdomain(
2540:             $app,
2541:             $this->get('fileroot', $app) . '/locale'
2542:         );
2543:     }
2544: 
2545:     /**
2546:      * Sets the gettext domain.
2547:      *
2548:      * @param string $app        The application name.
2549:      * @param string $directory  The directory where the application's
2550:      *                           LC_MESSAGES directory resides.
2551:      */
2552:     public function setTextdomain($app, $directory)
2553:     {
2554:         bindtextdomain($app, $directory);
2555:         textdomain($app);
2556: 
2557:         /* The existence of this function depends on the platform. */
2558:         if (function_exists('bind_textdomain_codeset')) {
2559:             bind_textdomain_codeset($app, 'UTF-8');
2560:         }
2561:     }
2562: 
2563:     /**
2564:      * Sets the current timezone, if available.
2565:      */
2566:     public function setTimeZone()
2567:     {
2568:         $tz = $GLOBALS['prefs']->getValue('timezone');
2569:         if (!empty($tz)) {
2570:             @date_default_timezone_set($tz);
2571:         }
2572:     }
2573: 
2574:     /**
2575:      * Maps languages with common two-letter codes (such as nl) to the full
2576:      * locale code (in this case, nl_NL). Returns the language unmodified if
2577:      * it isn't an alias.
2578:      *
2579:      * @param string $language  The language code to map.
2580:      *
2581:      * @return string  The mapped language code.
2582:      */
2583:     protected function _mapLang($language)
2584:     {
2585:         // Translate the $language to get broader matches.
2586:         // (eg. de-DE should match de_DE)
2587:         $trans_lang = str_replace('-', '_', $language);
2588:         $lang_parts = explode('_', $trans_lang);
2589:         $trans_lang = Horde_String::lower($lang_parts[0]);
2590:         if (isset($lang_parts[1])) {
2591:             $trans_lang .= '_' . Horde_String::upper($lang_parts[1]);
2592:         }
2593: 
2594:         return empty($this->nlsconfig->aliases[$trans_lang])
2595:             ? $trans_lang
2596:             : $this->nlsconfig->aliases[$trans_lang];
2597:     }
2598: }
2599: 
API documentation generated by ApiGen