3043 lines
100 KiB
PHP
3043 lines
100 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 1999-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (LGPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
|
|
*
|
|
* @category Horde
|
|
* @copyright 1999-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Core
|
|
*/
|
|
|
|
/**
|
|
* The registry provides a set of methods for communication between Horde
|
|
* applications and keeping track of application configuration information.
|
|
*
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Jon Parise <jon@horde.org>
|
|
* @author Anil Madhavapeddy <anil@recoil.org>
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @category Horde
|
|
* @copyright 1999-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Core
|
|
*/
|
|
class Horde_Registry implements Horde_Shutdown_Task
|
|
{
|
|
/* Session flags. */
|
|
const SESSION_NONE = 1;
|
|
const SESSION_READONLY = 2;
|
|
|
|
/* Error codes for pushApp(). */
|
|
const AUTH_FAILURE = 1;
|
|
const NOT_ACTIVE = 2;
|
|
const PERMISSION_DENIED = 3;
|
|
const HOOK_FATAL = 4;
|
|
const INITCALLBACK_FATAL = 5;
|
|
|
|
/* View types. */
|
|
const VIEW_BASIC = 1;
|
|
const VIEW_DYNAMIC = 2;
|
|
const VIEW_MINIMAL = 3;
|
|
const VIEW_SMARTMOBILE = 4;
|
|
|
|
/* Session keys. */
|
|
const REGISTRY_CACHE = 'registry_cache';
|
|
|
|
/**
|
|
* Hash storing information on each registry-aware application.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $applications = array();
|
|
|
|
/**
|
|
* Original authentication exception. Set if 'fallback' auth is used, and
|
|
* authentication fails.
|
|
*
|
|
* @since 2.11.0
|
|
* @todo Fix this up for H6 (framework needs to do better job of
|
|
* supporting bootstrapping before authentication).
|
|
*
|
|
* @var Exception
|
|
*/
|
|
public $authException;
|
|
|
|
/**
|
|
* A flag that is set once the basic horde application has been
|
|
* minimally configured.
|
|
*
|
|
* @var boolean
|
|
*/
|
|
public $hordeInit = false;
|
|
|
|
/**
|
|
* The application that called appInit().
|
|
*
|
|
* @var string
|
|
*/
|
|
public $initialApp;
|
|
|
|
/**
|
|
* NLS configuration.
|
|
*
|
|
* @var Horde_Registry_Nlsconfig
|
|
*/
|
|
public $nlsconfig;
|
|
|
|
/**
|
|
* The current virtual host configuration file.
|
|
*
|
|
* @since 2.12.0
|
|
*
|
|
* @var string
|
|
*/
|
|
public $vhost = null;
|
|
|
|
/**
|
|
* The list of APIs.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_apiList = array();
|
|
|
|
/**
|
|
* Stack of in-use applications.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_appStack = array();
|
|
|
|
/**
|
|
* The list of applications initialized during this access.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_appsInit = array();
|
|
|
|
/**
|
|
* The arguments that have been passed when instantiating the registry.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_args = array();
|
|
|
|
/**
|
|
* Internal cached data.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_cache = array(
|
|
'auth' => null,
|
|
'cfile' => array(),
|
|
'conf' => array(),
|
|
'existing' => array(),
|
|
'ob' => array()
|
|
);
|
|
|
|
/**
|
|
* Interfaces list.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_interfaces = array();
|
|
|
|
/**
|
|
* The last modified time of the newest modified registry file.
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $_regmtime;
|
|
|
|
/**
|
|
* Application bootstrap initialization.
|
|
* Solves chicken-and-egg problem - need a way to init Horde environment
|
|
* from application without an active Horde_Registry object.
|
|
*
|
|
* Page compression will be started (if configured).
|
|
*
|
|
* Global variables defined:
|
|
* <pre>
|
|
* - $browser: Horde_Browser object
|
|
* - $cli: Horde_Cli object (if 'cli' is true)
|
|
* - $conf: Configuration array
|
|
* - $injector: Horde_Injector object
|
|
* - $language: Language
|
|
* - $notification: Horde_Notification object
|
|
* - $page_output: Horde_PageOutput object
|
|
* - $prefs: Horde_Prefs object
|
|
* - $registry: Horde_Registry object
|
|
* - $session: Horde_Session object
|
|
* </pre>
|
|
*
|
|
* @param string $app The application to initialize.
|
|
* @param array $args Optional arguments:
|
|
* <pre>
|
|
* - admin: (boolean) Require authenticated user to be an admin?
|
|
* DEFAULT: false
|
|
* - authentication: (string) The type of authentication to use:
|
|
* - none: Do not authenticate
|
|
* - fallback: Attempt to authenticate; if failure, then don't auth
|
|
* (@since 2.11.0).
|
|
* - [DEFAULT]: Authenticate; on no auth redirect to login screen
|
|
* - cli: (boolean) Initialize a CLI interface. Setting this to true
|
|
* implicitly sets 'authentication' to 'none' and 'admin' and
|
|
* 'nocompress' to true.
|
|
* DEFAULT: false
|
|
* - nocompress: (boolean) If set, the page will not be compressed.
|
|
* DEFAULT: false
|
|
* - nologintasks: (boolean) If set, don't perform logintasks (never
|
|
* performed if authentication is 'none').
|
|
* DEFAULT: false
|
|
* - nonotificationinit: (boolean) If set, don't initialize the
|
|
* application handlers for the notification
|
|
* system (@since 2.12.0).
|
|
* - permission: (array) The permission required by the user to access
|
|
* the page. The first element (REQUIRED) is the permission
|
|
* name. The second element (OPTION; defaults to SHOW) is
|
|
* the permission level.
|
|
* - session_cache_limiter: (string) Use this value for the session
|
|
* cache limiter.
|
|
* DEFAULT: Uses the value in the config.
|
|
* - session_control: (string) Special session control limitations:
|
|
* - netscape: TODO; start read/write session
|
|
* - none: Do not start a session
|
|
* - readonly: Start session readonly
|
|
* - [DEFAULT] - Start read/write session
|
|
* - test: (boolean) Is this the test script? If so, we relax several
|
|
* sanity checks and don't load things from the cache.
|
|
* DEFAULT: false
|
|
* - timezone: (boolean) Set the time zone?
|
|
* DEFAULT: false
|
|
* - user_admin: (boolean) Set authentication to an admin user?
|
|
* DEFAULT: false
|
|
* </pre>
|
|
*
|
|
* @return Horde_Registry_Application The application object.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public static function appInit($app, array $args = array())
|
|
{
|
|
if (isset($GLOBALS['registry'])) {
|
|
return $GLOBALS['registry']->getApiInstance($app, 'application');
|
|
}
|
|
|
|
$args = array_merge(array(
|
|
'admin' => false,
|
|
'authentication' => null,
|
|
'cli' => null,
|
|
'nocompress' => false,
|
|
'nologintasks' => false,
|
|
'nonotificationinit' => false,
|
|
'permission' => false,
|
|
'session_cache_limiter' => null,
|
|
'session_control' => null,
|
|
'timezone' => false,
|
|
'user_admin' => null,
|
|
), $args);
|
|
|
|
/* CLI initialization. */
|
|
if ($args['cli']) {
|
|
/* Make sure no one runs from the web. */
|
|
if (!Horde_Cli::runningFromCLI()) {
|
|
throw new Horde_Exception(Horde_Core_Translation::t("Script must be run from the command line"));
|
|
}
|
|
|
|
/* Load the CLI environment - make sure there's no time limit,
|
|
* init some variables, etc. */
|
|
$GLOBALS['cli'] = Horde_Cli::init();
|
|
|
|
$args['nocompress'] = true;
|
|
$args['authentication'] = 'none';
|
|
}
|
|
|
|
// For 'fallback' authentication, try authentication first.
|
|
if ($args['authentication'] === 'fallback') {
|
|
$fallback_auth = true;
|
|
$args['authentication'] = null;
|
|
} else {
|
|
$fallback_auth = false;
|
|
}
|
|
|
|
// Registry.
|
|
$s_ctrl = 0;
|
|
switch ($args['session_control']) {
|
|
case 'netscape':
|
|
// Chicken/egg: Browser object doesn't exist yet.
|
|
// Can't use Horde_Core_Browser since it depends on registry to be
|
|
// configured.
|
|
$browser = new Horde_Browser();
|
|
if ($browser->isBrowser('mozilla')) {
|
|
$args['session_cache_limiter'] = 'private, must-revalidate';
|
|
}
|
|
break;
|
|
|
|
case 'none':
|
|
$s_ctrl = self::SESSION_NONE;
|
|
break;
|
|
|
|
case 'readonly':
|
|
$s_ctrl = self::SESSION_READONLY;
|
|
break;
|
|
}
|
|
|
|
$classname = __CLASS__;
|
|
$registry = $GLOBALS['registry'] = new $classname($s_ctrl, $args);
|
|
$registry->initialApp = $app;
|
|
|
|
$appob = $registry->getApiInstance($app, 'application');
|
|
$appob->initParams = $args;
|
|
|
|
do {
|
|
try {
|
|
$registry->pushApp($app, array(
|
|
'check_perms' => ($args['authentication'] != 'none'),
|
|
'logintasks' => !$args['nologintasks'],
|
|
'notransparent' => !empty($args['notransparent'])
|
|
));
|
|
|
|
if ($args['admin'] && !$registry->isAdmin()) {
|
|
throw new Horde_Exception(Horde_Core_Translation::t("Not an admin"));
|
|
}
|
|
|
|
$e = null;
|
|
} catch (Horde_Exception_PushApp $e) {
|
|
if ($fallback_auth) {
|
|
$registry->authException = $e;
|
|
$registry->setAuthenticationSetting('none');
|
|
$args['authentication'] = 'none';
|
|
$fallback_auth = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break;
|
|
} while (true);
|
|
|
|
if (!is_null($e)) {
|
|
$appob->appInitFailure($e);
|
|
|
|
switch ($e->getCode()) {
|
|
case self::AUTH_FAILURE:
|
|
$failure = new Horde_Exception_AuthenticationFailure($e->getMessage());
|
|
$failure->application = $app;
|
|
throw $failure;
|
|
|
|
case self::NOT_ACTIVE:
|
|
/* Try redirect to Horde if an app is not active. */
|
|
if (!$args['cli'] && $app != 'horde') {
|
|
$GLOBALS['notification']->push($e, 'horde.error');
|
|
Horde::url($registry->getInitialPage('horde'))->redirect();
|
|
}
|
|
|
|
/* Shouldn't reach here, but fall back to permission denied
|
|
* error if we can't even access Horde. */
|
|
// Fall-through
|
|
|
|
case self::PERMISSION_DENIED:
|
|
$failure = new Horde_Exception_AuthenticationFailure($e->getMessage(), Horde_Auth::REASON_MESSAGE);
|
|
$failure->application = $app;
|
|
throw $failure;
|
|
}
|
|
|
|
throw $e;
|
|
}
|
|
|
|
if ($args['timezone']) {
|
|
$registry->setTimeZone();
|
|
}
|
|
|
|
if (!$args['nocompress']) {
|
|
$GLOBALS['page_output']->startCompression();
|
|
}
|
|
|
|
if ($args['user_admin']) {
|
|
if (empty($GLOBALS['conf']['auth']['admins'])) {
|
|
throw new Horde_Exception(Horde_Core_Translation::t("Admin authentication requested, but no admin users defined in configuration."));
|
|
}
|
|
$registry->setAuth(
|
|
reset($GLOBALS['conf']['auth']['admins']),
|
|
array(),
|
|
array('no_convert' => true)
|
|
);
|
|
}
|
|
|
|
if ($args['permission']) {
|
|
$admin_opts = array(
|
|
'permission' => $args['permission'][0],
|
|
'permlevel' => (isset($args['permission'][1]) ? $args['permission'][1] : Horde_Perms::SHOW)
|
|
);
|
|
if (!$registry->isAdmin($admin_opts)) {
|
|
throw new Horde_Exception_PermissionDenied(Horde_Core_Translation::t("Permission denied."));
|
|
}
|
|
}
|
|
|
|
return $appob;
|
|
}
|
|
|
|
/**
|
|
* Create a new Horde_Registry instance.
|
|
*
|
|
* @param integer $session_flags Any session flags.
|
|
* @param array $args See appInit().
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function __construct($session_flags = 0, array $args = array())
|
|
{
|
|
/* Set a valid timezone. */
|
|
date_default_timezone_set(
|
|
ini_get('date.timezone') ?: getenv('TZ') ?: 'UTC'
|
|
);
|
|
|
|
/* Save arguments. */
|
|
$this->_args = $args;
|
|
|
|
/* Define factories. By default, uses the 'create' method in the given
|
|
* classname (string). If other function needed, define as the second
|
|
* element in an array. */
|
|
$factories = array(
|
|
'Horde_ActiveSyncBackend' => 'Horde_Core_Factory_ActiveSyncBackend',
|
|
'Horde_ActiveSyncServer' => 'Horde_Core_Factory_ActiveSyncServer',
|
|
'Horde_ActiveSyncState' => 'Horde_Core_Factory_ActiveSyncState',
|
|
'Horde_Alarm' => 'Horde_Core_Factory_Alarm',
|
|
'Horde_Browser' => 'Horde_Core_Factory_Browser',
|
|
'Horde_Cache' => 'Horde_Core_Factory_Cache',
|
|
'Horde_Controller_Request' => 'Horde_Core_Factory_Request',
|
|
'Horde_Controller_RequestConfiguration' => array(
|
|
'Horde_Core_Controller_RequestMapper',
|
|
'getRequestConfiguration',
|
|
),
|
|
'Horde_Core_Auth_Signup' => 'Horde_Core_Factory_AuthSignup',
|
|
'Horde_Core_CssCache' => 'Horde_Core_Factory_CssCache',
|
|
'Horde_Core_JavascriptCache' => 'Horde_Core_Factory_JavascriptCache',
|
|
'Horde_Core_Perms' => 'Horde_Core_Factory_PermsCore',
|
|
'Horde_Dav_Server' => 'Horde_Core_Factory_DavServer',
|
|
'Horde_Dav_Storage' => 'Horde_Core_Factory_DavStorage',
|
|
'Horde_Db_Adapter' => 'Horde_Core_Factory_DbBase',
|
|
'Horde_Editor' => 'Horde_Core_Factory_Editor',
|
|
'Horde_ElasticSearch_Client' => 'Horde_Core_Factory_ElasticSearch',
|
|
'Horde_Group' => 'Horde_Core_Factory_Group',
|
|
'Horde_HashTable' => 'Horde_Core_Factory_HashTable',
|
|
'Horde_History' => 'Horde_Core_Factory_History',
|
|
'Horde_Kolab_Server_Composite' => 'Horde_Core_Factory_KolabServer',
|
|
'Horde_Kolab_Session' => 'Horde_Core_Factory_KolabSession',
|
|
'Horde_Kolab_Storage' => 'Horde_Core_Factory_KolabStorage',
|
|
'Horde_Lock' => 'Horde_Core_Factory_Lock',
|
|
'Horde_Log_Logger' => 'Horde_Core_Factory_Logger',
|
|
'Horde_Mail' => 'Horde_Core_Factory_MailBase',
|
|
'Horde_Memcache' => 'Horde_Core_Factory_Memcache',
|
|
'Horde_Nosql_Adapter' => 'Horde_Core_Factory_NosqlBase',
|
|
'Horde_Notification' => 'Horde_Core_Factory_Notification',
|
|
'Horde_Perms' => 'Horde_Core_Factory_Perms',
|
|
'Horde_Queue_Storage' => 'Horde_Core_Factory_QueueStorage',
|
|
'Horde_Routes_Mapper' => 'Horde_Core_Factory_Mapper',
|
|
'Horde_Routes_Matcher' => 'Horde_Core_Factory_Matcher',
|
|
'Horde_Secret' => 'Horde_Core_Factory_Secret',
|
|
'Horde_Secret_Cbc' => 'Horde_Core_Factory_Secret_Cbc',
|
|
'Horde_Service_Facebook' => 'Horde_Core_Factory_Facebook',
|
|
'Horde_Service_Twitter' => 'Horde_Core_Factory_Twitter',
|
|
'Horde_Service_UrlShortener' => 'Horde_Core_Factory_UrlShortener',
|
|
'Horde_SessionHandler' => 'Horde_Core_Factory_SessionHandler',
|
|
'Horde_Template' => 'Horde_Core_Factory_Template',
|
|
'Horde_Timezone' => 'Horde_Core_Factory_Timezone',
|
|
'Horde_Token' => 'Horde_Core_Factory_Token',
|
|
'Horde_Variables' => 'Horde_Core_Factory_Variables',
|
|
'Horde_View' => 'Horde_Core_Factory_View',
|
|
'Horde_View_Base' => 'Horde_Core_Factory_View',
|
|
'Horde_Weather' => 'Horde_Core_Factory_Weather',
|
|
'Net_DNS2_Resolver' => 'Horde_Core_Factory_Dns',
|
|
'Text_LanguageDetect' => 'Horde_Core_Factory_LanguageDetect',
|
|
);
|
|
|
|
/* Define implementations. */
|
|
$implementations = array(
|
|
'Horde_Controller_ResponseWriter' => 'Horde_Controller_ResponseWriter_Web'
|
|
);
|
|
|
|
/* Setup injector. */
|
|
$GLOBALS['injector'] = $injector = new Horde_Injector(new Horde_Injector_TopLevel());
|
|
|
|
foreach ($factories as $key => $val) {
|
|
if (is_string($val)) {
|
|
$val = array($val, 'create');
|
|
}
|
|
$injector->bindFactory($key, $val[0], $val[1]);
|
|
}
|
|
foreach ($implementations as $key => $val) {
|
|
$injector->bindImplementation($key, $val);
|
|
}
|
|
|
|
$GLOBALS['registry'] = $this;
|
|
$injector->setInstance(__CLASS__, $this);
|
|
|
|
/* Setup autoloader instance. */
|
|
$injector->setInstance('Horde_Autoloader', $GLOBALS['__autoloader']);
|
|
|
|
/* Import and global Horde's configuration values. */
|
|
$this->importConfig('horde');
|
|
$conf = $GLOBALS['conf'];
|
|
|
|
/* Set the umask according to config settings. */
|
|
if (isset($conf['umask'])) {
|
|
umask($conf['umask']);
|
|
}
|
|
|
|
/* Set the error reporting level in accordance with the config
|
|
* settings. */
|
|
error_reporting($conf['debug_level']);
|
|
|
|
/* Set the maximum execution time in accordance with the config
|
|
* settings, but only if not running from the CLI */
|
|
if (!isset($GLOBALS['cli'])) {
|
|
set_time_limit($conf['max_exec_time']);
|
|
}
|
|
|
|
/* The basic framework is up and loaded, so set the init flag. */
|
|
$this->hordeInit = true;
|
|
|
|
/* Initial Horde-wide settings. */
|
|
|
|
/* Initialize browser object. */
|
|
$GLOBALS['browser'] = $injector->getInstance('Horde_Browser');
|
|
|
|
/* Get modified time of registry files. */
|
|
$regfiles = array(
|
|
HORDE_BASE . '/config/registry.php',
|
|
HORDE_BASE . '/config/registry.d'
|
|
);
|
|
if (file_exists(HORDE_BASE . '/config/registry.local.php')) {
|
|
$regfiles[] = HORDE_BASE . '/config/registry.local.php';
|
|
}
|
|
if (!empty($conf['vhosts'])) {
|
|
$vhost = HORDE_BASE . '/config/registry-' . $conf['server']['name'] . '.php';
|
|
if (file_exists($vhost)) {
|
|
$regfiles[] = $this->vhost = $vhost;
|
|
}
|
|
}
|
|
$this->_regmtime = max(array_map('filemtime', $regfiles));
|
|
|
|
/* Start a session. */
|
|
if ($session_flags & self::SESSION_NONE) {
|
|
/* Never start a session if the session flags include
|
|
SESSION_NONE. */
|
|
$GLOBALS['session'] = $session = new Horde_Session_Null();
|
|
$session->setup(true, $args['session_cache_limiter']);
|
|
} elseif ((PHP_SAPI === 'cli') ||
|
|
(empty($_SERVER['SERVER_NAME']) &&
|
|
((PHP_SAPI === 'cgi') || (PHP_SAPI === 'cgi-fcgi')))) {
|
|
$GLOBALS['session'] = $session = new Horde_Session();
|
|
$session->setup(false, $args['session_cache_limiter']);
|
|
} else {
|
|
$GLOBALS['session'] = $session = new Horde_Session();
|
|
$session->setup(true, $args['session_cache_limiter']);
|
|
if ($session_flags & self::SESSION_READONLY) {
|
|
/* Close the session immediately so no changes can be made but
|
|
values are still available. */
|
|
$session->close();
|
|
}
|
|
}
|
|
$injector->setInstance('Horde_Session', $session);
|
|
|
|
/* Always need to load applications information. */
|
|
$this->_loadApplications();
|
|
|
|
/* Stop system if Horde is inactive. */
|
|
if ($this->applications['horde']['status'] == 'inactive') {
|
|
throw new Horde_Exception(Horde_Core_Translation::t("This system is currently deactivated."));
|
|
}
|
|
|
|
/* Initialize language configuration object. */
|
|
$this->nlsconfig = new Horde_Registry_Nlsconfig();
|
|
|
|
/* Initialize the localization routines and variables. */
|
|
$this->setLanguageEnvironment(null, 'horde');
|
|
|
|
/* Initialize global page output object. */
|
|
$GLOBALS['page_output'] = $injector->getInstance('Horde_PageOutput');
|
|
|
|
/* Initialize notification object. Always attach status listener by
|
|
* default. */
|
|
$nclass = null;
|
|
switch ($this->getView()) {
|
|
case self::VIEW_DYNAMIC:
|
|
$nclass = 'Horde_Core_Notification_Listener_DynamicStatus';
|
|
break;
|
|
|
|
case self::VIEW_SMARTMOBILE:
|
|
$nclass = 'Horde_Core_Notification_Listener_SmartmobileStatus';
|
|
break;
|
|
}
|
|
$GLOBALS['notification'] = $injector->getInstance('Horde_Notification');
|
|
if (empty($args['nonotificationinit'])) {
|
|
$GLOBALS['notification']->attachAllAppHandlers();
|
|
}
|
|
$GLOBALS['notification']->attach('status', null, $nclass);
|
|
|
|
Horde_Shutdown::add($this);
|
|
}
|
|
|
|
/**
|
|
* (Re)set the authentication parameter. Useful for requests, such as Rpc
|
|
* requests where we actually don't perform authentication until later in
|
|
* the request, but still need Horde bootstrapped early in the request. Also
|
|
* clears the local app/api cache since applications will probably already
|
|
* have been initialized during Notification polling.
|
|
*
|
|
* @see appInit()
|
|
*
|
|
* @param string $authentication The authentication setting.
|
|
*/
|
|
public function setAuthenticationSetting($authentication)
|
|
{
|
|
$this->_args['authentication'] = $authentication;
|
|
$this->_cache['cfile'] = $this->_cache['ob'] = array();
|
|
$this->_cache['isauth'] = array();
|
|
$this->_appsInit = array();
|
|
while ($this->popApp());
|
|
}
|
|
|
|
/**
|
|
* Events to do on shutdown.
|
|
*/
|
|
public function shutdown()
|
|
{
|
|
/* Register access key logger for translators. */
|
|
if (!empty($GLOBALS['conf']['log_accesskeys'])) {
|
|
Horde::getAccessKey(null, null, true);
|
|
}
|
|
|
|
/* Register memory tracker if logging in debug mode. */
|
|
Horde::log('Max memory usage: ' . memory_get_peak_usage(true) . ' bytes', 'DEBUG');
|
|
}
|
|
|
|
/**
|
|
* A property call to the registry object will return a Caller object.
|
|
*/
|
|
public function __get($api)
|
|
{
|
|
if (in_array($api, $this->listAPIs())) {
|
|
return new Horde_Registry_Caller($this, $api);
|
|
}
|
|
throw new Horde_Exception('The API "' . $api . '" is not defined in the Horde Registry.');
|
|
}
|
|
|
|
/**
|
|
* Clone should never be called on this object. If it is, die.
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function __clone()
|
|
{
|
|
throw new LogicException('Registry objects should never be cloned.');
|
|
}
|
|
|
|
/**
|
|
* serialize() should never be called on this object. If it is, die.
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function __sleep()
|
|
{
|
|
throw new LogicException('Registry objects should never be serialized.');
|
|
}
|
|
|
|
/**
|
|
* Rebuild the registry configuration.
|
|
*/
|
|
public function rebuild()
|
|
{
|
|
global $session;
|
|
|
|
$app = $this->getApp();
|
|
|
|
$this->applications = $this->_apiList = $this->_cache['conf'] = $this->_cache['ob'] = $this->_interfaces = array();
|
|
|
|
$session->remove('horde', 'nls/');
|
|
$session->remove('horde', 'registry/');
|
|
$session->remove('horde', self::REGISTRY_CACHE);
|
|
|
|
$this->_loadApplications();
|
|
|
|
$this->importConfig('horde');
|
|
$this->importConfig($app);
|
|
}
|
|
|
|
/**
|
|
* Load application information from registry config files.
|
|
*/
|
|
protected function _loadApplications()
|
|
{
|
|
global $cli, $injector;
|
|
|
|
if (!empty($this->_interfaces)) {
|
|
return;
|
|
}
|
|
|
|
/* First, try to load from cache. */
|
|
if (!isset($cli) && !$this->isTest()) {
|
|
if (Horde_Util::extensionExists('apc')) {
|
|
$cstorage = 'Horde_Cache_Storage_Apc';
|
|
} elseif (Horde_Util::extensionExists('xcache')) {
|
|
$cstorage = 'Horde_Cache_Storage_Xcache';
|
|
} else {
|
|
$cstorage = 'Horde_Cache_Storage_File';
|
|
}
|
|
|
|
$cache = new Horde_Cache(
|
|
new $cstorage(array(
|
|
'no_gc' => true,
|
|
'prefix' => 'horde_registry_cache_'
|
|
)),
|
|
array(
|
|
'lifetime' => 0,
|
|
'logger' => $injector->getInstance('Horde_Log_Logger')
|
|
)
|
|
);
|
|
|
|
if (($cid = $this->_cacheId()) &&
|
|
($cdata = $cache->get($cid, 0))) {
|
|
try {
|
|
list($this->applications, $this->_interfaces) =
|
|
$injector->getInstance('Horde_Pack')->unpack($cdata);
|
|
return;
|
|
} catch (Horde_Pack_Exception $e) {}
|
|
}
|
|
}
|
|
|
|
$config = new Horde_Registry_Registryconfig($this);
|
|
$this->applications = $config->applications;
|
|
$this->_interfaces = $config->interfaces;
|
|
|
|
if (!isset($cache)) {
|
|
return;
|
|
}
|
|
|
|
/* Need to determine hash of generated data, since it is possible that
|
|
* there is dynamic data in the config files. This only needs to
|
|
* be done once per session. */
|
|
$packed_data = $injector->getInstance('Horde_Pack')->pack(array(
|
|
$this->applications,
|
|
$this->_interfaces
|
|
));
|
|
$cid = $this->_cacheId($packed_data);
|
|
|
|
if (!$cache->exists($cid, 0)) {
|
|
$cache->set($cid, $packed_data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the cache ID for the registry information.
|
|
*
|
|
* @param string $hash If set, hash this value and use as the hash of the
|
|
* registry. If false, uses session stored value.
|
|
*/
|
|
protected function _cacheId($hash = null)
|
|
{
|
|
global $session;
|
|
|
|
if (!is_null($hash)) {
|
|
$hash = hash('md5', $hash);
|
|
$session->set('horde', self::REGISTRY_CACHE, $hash);
|
|
} elseif (!($hash = $session->get('horde', self::REGISTRY_CACHE))) {
|
|
return false;
|
|
}
|
|
|
|
/* Generate cache ID. */
|
|
return implode('|', array(
|
|
gethostname() ?: php_uname(),
|
|
__FILE__,
|
|
$this->_regmtime,
|
|
$hash
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Load an application's API object.
|
|
*
|
|
* @param string $app The application to load.
|
|
*
|
|
* @return Horde_Registry_Api The API object, or null if not available.
|
|
*/
|
|
protected function _loadApi($app)
|
|
{
|
|
if (isset($this->_cache['ob'][$app]['api'])) {
|
|
return $this->_cache['ob'][$app]['api'];
|
|
}
|
|
|
|
$api = null;
|
|
$status = array('active', 'notoolbar', 'hidden');
|
|
$status[] = $this->isAdmin()
|
|
? 'admin'
|
|
: 'noadmin';
|
|
|
|
if (in_array($this->applications[$app]['status'], $status)) {
|
|
try {
|
|
$api = $this->getApiInstance($app, 'api');
|
|
} catch (Horde_Exception $e) {
|
|
Horde::log($e, 'DEBUG');
|
|
}
|
|
}
|
|
|
|
$this->_cache['ob'][$app]['api'] = $api;
|
|
|
|
return $api;
|
|
}
|
|
|
|
/**
|
|
* Retrieve an API object.
|
|
*
|
|
* @param string $app The application to load.
|
|
* @param string $type Either 'application' or 'api'.
|
|
*
|
|
* @return Horde_Registry_Api|Horde_Registry_Application The API object.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function getApiInstance($app, $type)
|
|
{
|
|
if (isset($this->_cache['ob'][$app][$type])) {
|
|
return $this->_cache['ob'][$app][$type];
|
|
}
|
|
|
|
$path = $this->get('fileroot', $app) . '/lib';
|
|
|
|
/* Set up autoload paths for the current application. This needs to
|
|
* be done here because it is possible to try to load app-specific
|
|
* libraries from other applications. */
|
|
if (!isset($this->_cache['ob'][$app])) {
|
|
$autoloader = $GLOBALS['injector']->getInstance('Horde_Autoloader');
|
|
|
|
$app_mappers = array(
|
|
'Controller' => 'controllers',
|
|
'Helper' => 'helpers',
|
|
'SettingsExporter' => 'settings'
|
|
);
|
|
$applicationMapper = new Horde_Autoloader_ClassPathMapper_Application($this->get('fileroot', $app) . '/app');
|
|
foreach ($app_mappers as $key => $val) {
|
|
$applicationMapper->addMapping($key, $val);
|
|
}
|
|
$autoloader->addClassPathMapper($applicationMapper);
|
|
|
|
/* Skip horde, since this was already setup in core.php. */
|
|
if ($app != 'horde') {
|
|
$autoloader->addClassPathMapper(
|
|
new Horde_Autoloader_ClassPathMapper_PrefixString($app, $path)
|
|
);
|
|
}
|
|
}
|
|
|
|
$cname = Horde_String::ucfirst($type);
|
|
|
|
/* Can't autoload here, since the application may not have been
|
|
* initialized yet. */
|
|
$classname = Horde_String::ucfirst($app) . '_' . $cname;
|
|
$path = $path . '/' . $cname . '.php';
|
|
if (file_exists($path)) {
|
|
include_once $path;
|
|
} else {
|
|
$classname = __CLASS__ . '_' . $cname;
|
|
}
|
|
|
|
if (!class_exists($classname, false)) {
|
|
throw new Horde_Exception("$app does not have an API");
|
|
}
|
|
|
|
$this->_cache['ob'][$app][$type] = ($type == 'application')
|
|
? new $classname($app)
|
|
: new $classname();
|
|
|
|
return $this->_cache['ob'][$app][$type];
|
|
}
|
|
|
|
/**
|
|
* Return a list of the installed and registered applications.
|
|
*
|
|
* @param array $filter An array of the statuses that should be
|
|
* returned. Defaults to non-hidden.
|
|
* @param boolean $assoc Return hash with app names as keys and config
|
|
* parameters as values?
|
|
* @param integer $perms The permission level to check for in the list.
|
|
* If null, skips permission check.
|
|
*
|
|
* @return array List of apps registered with Horde. If no
|
|
* applications are defined returns an empty array.
|
|
*/
|
|
public function listApps($filter = null, $assoc = false,
|
|
$perms = Horde_Perms::SHOW)
|
|
{
|
|
if (is_null($filter)) {
|
|
$filter = array('notoolbar', 'active');
|
|
}
|
|
if (!$this->isAdmin() &&
|
|
in_array('active', $filter) &&
|
|
!in_array('noadmin', $filter)) {
|
|
$filter[] = 'noadmin';
|
|
}
|
|
|
|
$apps = array();
|
|
foreach ($this->applications as $app => $params) {
|
|
if (in_array($params['status'], $filter)) {
|
|
/* Topbar apps can only be displayed if the parent app is
|
|
* active. */
|
|
if (($params['status'] == 'topbar') &&
|
|
$this->isInactive($params['app'])) {
|
|
continue;
|
|
}
|
|
|
|
if ((is_null($perms) || $this->hasPermission($app, $perms))) {
|
|
$apps[$app] = $params;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $assoc ? $apps : array_keys($apps);
|
|
}
|
|
|
|
/**
|
|
* Return a list of all applications, ignoring permissions.
|
|
*
|
|
* @return array List of all apps registered with Horde.
|
|
*/
|
|
public function listAllApps()
|
|
{
|
|
// Default to all installed (but possibly not configured) applications.
|
|
return $this->listApps(array(
|
|
'active', 'admin', 'noadmin', 'hidden', 'inactive', 'notoolbar'
|
|
), false, null);
|
|
}
|
|
|
|
/**
|
|
* Is the given application inactive?
|
|
*
|
|
* @param string $app The application to check.
|
|
*
|
|
* @return boolean True if inactive.
|
|
*/
|
|
public function isInactive($app)
|
|
{
|
|
return (!isset($this->applications[$app]) ||
|
|
($this->applications[$app]['status'] == 'inactive') ||
|
|
(($this->applications[$app]['status'] == 'admin') &&
|
|
!$this->isAdmin()) ||
|
|
(($this->applications[$app]['status'] == 'noadmin') &&
|
|
$this->currentProcessAuth() &&
|
|
$this->isAdmin()));
|
|
}
|
|
|
|
/**
|
|
* Returns all available registry APIs.
|
|
*
|
|
* @return array The API list.
|
|
*/
|
|
public function listAPIs()
|
|
{
|
|
if (empty($this->_apiList) && !empty($this->_interfaces)) {
|
|
$apis = array();
|
|
|
|
foreach (array_keys($this->_interfaces) as $interface) {
|
|
list($api,) = explode('/', $interface, 2);
|
|
$apis[$api] = true;
|
|
}
|
|
|
|
$this->_apiList = array_keys($apis);
|
|
}
|
|
|
|
return $this->_apiList;
|
|
}
|
|
|
|
/**
|
|
* Returns all of the available registry methods, or alternately
|
|
* only those for a specified API.
|
|
*
|
|
* @param string $api Defines the API for which the methods shall be
|
|
* returned. If null, returns all methods.
|
|
*
|
|
* @return array The method list.
|
|
*/
|
|
public function listMethods($api = null)
|
|
{
|
|
$methods = array();
|
|
|
|
foreach (array_keys($this->applications) as $app) {
|
|
if (isset($this->applications[$app]['provides'])) {
|
|
$provides = $this->applications[$app]['provides'];
|
|
if (!is_array($provides)) {
|
|
$provides = array($provides);
|
|
}
|
|
|
|
foreach ($provides as $method) {
|
|
if (strpos($method, '/') !== false) {
|
|
if (is_null($api) ||
|
|
(substr($method, 0, strlen($api)) == $api)) {
|
|
$methods[$method] = true;
|
|
}
|
|
} elseif (($api_ob = $this->_loadApi($app)) &&
|
|
(is_null($api) || ($method == $api))) {
|
|
foreach ($api_ob->methods() as $service) {
|
|
$methods[$method . '/' . $service] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_keys($methods);
|
|
}
|
|
|
|
/**
|
|
* Determine if an interface is implemented by an active application.
|
|
*
|
|
* @param string $interface The interface to check for.
|
|
*
|
|
* @return mixed The application implementing $interface if we have it,
|
|
* false if the interface is not implemented.
|
|
*/
|
|
public function hasInterface($interface)
|
|
{
|
|
return !empty($this->_interfaces[$interface])
|
|
? $this->_interfaces[$interface]
|
|
: false;
|
|
}
|
|
|
|
/**
|
|
* Determine if a method has been registered with the registry.
|
|
*
|
|
* @param string $method The full name of the method to check for.
|
|
* @param string $app Only check this application.
|
|
*
|
|
* @return mixed The application implementing $method if we have it,
|
|
* false if the method doesn't exist.
|
|
*/
|
|
public function hasMethod($method, $app = null)
|
|
{
|
|
return $this->_doHasSearch($method, $app, 'methods');
|
|
}
|
|
|
|
/**
|
|
* Determine if a link has been registered with the registry.
|
|
*
|
|
* @since 2.12.0
|
|
*
|
|
* @param string $method The full name of the link method to check for.
|
|
* @param string $app Only check this application.
|
|
*
|
|
* @return mixed The application implementing $method if we have it,
|
|
* false if the link method doesn't exist.
|
|
*/
|
|
public function hasLink($method, $app = null)
|
|
{
|
|
return $this->_doHasSearch($method, $app, 'links');
|
|
}
|
|
|
|
/**
|
|
* Do the has*() search.
|
|
*
|
|
* @see hasMethod
|
|
* @see hasLink
|
|
*
|
|
* @param string $func The API function to call to get the list of
|
|
* elements to search. Either 'methods' or 'links'.
|
|
*
|
|
* @return mixed The application implementing $method, false if it
|
|
* doesn't exist;
|
|
*/
|
|
protected function _doHasSearch($method, $app, $func)
|
|
{
|
|
if (is_null($app)) {
|
|
if (($lookup = $this->_methodLookup($method)) === false) {
|
|
return false;
|
|
}
|
|
list($app, $call) = $lookup;
|
|
} else {
|
|
$call = $method;
|
|
}
|
|
|
|
if ($api_ob = $this->_loadApi($app)) {
|
|
switch ($func) {
|
|
case 'links':
|
|
$links = $api_ob->links();
|
|
return isset($links[$call]) ? $app : false;
|
|
|
|
case 'methods':
|
|
return in_array($call, $api_ob->methods()) ? $app : false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Return the hook corresponding to the default package that provides the
|
|
* functionality requested by the $method parameter.
|
|
* $method is a string consisting of "packagetype/methodname".
|
|
*
|
|
* @param string $method The method to call.
|
|
* @param array $args Arguments to the method.
|
|
*
|
|
* @return mixed Return from method call.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function call($method, $args = array())
|
|
{
|
|
if (($lookup = $this->_methodLookup($method)) === false) {
|
|
throw new Horde_Exception('The method "' . $method . '" is not defined in the Horde Registry.');
|
|
}
|
|
|
|
return $this->callByPackage($lookup[0], $lookup[1], $args);
|
|
}
|
|
|
|
/**
|
|
* Output the hook corresponding to the specific package named.
|
|
*
|
|
* @param string $app The application being called.
|
|
* @param string $call The method to call.
|
|
* @param array $args Arguments to the method.
|
|
* @param array $options Additional options:
|
|
* - noperms: (boolean) If true, don't check the perms.
|
|
*
|
|
* @return mixed Return from application call.
|
|
* @throws Horde_Exception_PushApp
|
|
*/
|
|
public function callByPackage($app, $call, array $args = array(),
|
|
array $options = array())
|
|
{
|
|
/* Note: calling hasMethod() makes sure that we've cached
|
|
* $app's services and included the API file, so we don't try
|
|
* to do it again explicitly in this method. */
|
|
if (!$this->hasMethod($call, $app)) {
|
|
throw new Horde_Exception(sprintf('The method "%s" is not defined in the API for %s.', $call, $app));
|
|
}
|
|
|
|
/* Load the API now. */
|
|
$methods = ($api_ob = $this->_loadApi($app))
|
|
? $api_ob->methods()
|
|
: array();
|
|
|
|
/* Make sure that the function actually exists. */
|
|
if (!in_array($call, $methods)) {
|
|
throw new Horde_Exception('The function implementing ' . $call . ' is not defined in ' . $app . '\'s API.');
|
|
}
|
|
|
|
/* Switch application contexts now, if necessary, before
|
|
* including any files which might do it for us. Return an
|
|
* error immediately if pushApp() fails. */
|
|
$pushed = $this->pushApp($app, array(
|
|
'check_perms' => !in_array($call, $api_ob->noPerms()) && empty($options['noperms']) && $this->currentProcessAuth()
|
|
));
|
|
|
|
try {
|
|
$result = call_user_func_array(array($api_ob, $call), $args);
|
|
if ($result instanceof PEAR_Error) {
|
|
$result = new Horde_Exception_Wrapped($result);
|
|
}
|
|
} catch (Horde_Exception $e) {
|
|
$result = $e;
|
|
}
|
|
|
|
/* If we changed application context in the course of this
|
|
* call, undo that change now. */
|
|
if ($pushed === true) {
|
|
$this->popApp();
|
|
}
|
|
|
|
if ($result instanceof Horde_Exception) {
|
|
throw $result;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Call a private Horde application method.
|
|
*
|
|
* @param string $app The application name.
|
|
* @param string $call The method to call.
|
|
* @param array $options Additional options:
|
|
* - args: (array) Additional parameters to pass to the method.
|
|
* - check_missing: (boolean) If true, throws an Exception if method
|
|
* does not exist. Otherwise, will return null.
|
|
* - noperms: (boolean) If true, don't check the perms.
|
|
*
|
|
* @return mixed Various.
|
|
*
|
|
* @throws Horde_Exception Application methods should throw this if there
|
|
* is a fatal error.
|
|
* @throws Horde_Exception_PushApp
|
|
*/
|
|
public function callAppMethod($app, $call, array $options = array())
|
|
{
|
|
/* Load the API now. */
|
|
try {
|
|
$api = $this->getApiInstance($app, 'application');
|
|
} catch (Horde_Exception $e) {
|
|
if (empty($options['check_missing'])) {
|
|
return null;
|
|
}
|
|
throw $e;
|
|
}
|
|
|
|
if (!method_exists($api, $call)) {
|
|
if (empty($options['check_missing'])) {
|
|
return null;
|
|
}
|
|
throw new Horde_Exception('Method does not exist.');
|
|
}
|
|
|
|
/* Switch application contexts now, if necessary, before
|
|
* including any files which might do it for us. Return an
|
|
* error immediately if pushApp() fails. */
|
|
$pushed = $this->pushApp($app, array(
|
|
'check_perms' => empty($options['noperms']) && $this->currentProcessAuth()
|
|
));
|
|
|
|
try {
|
|
$result = call_user_func_array(array($api, $call), empty($options['args']) ? array() : $options['args']);
|
|
} catch (Horde_Exception $e) {
|
|
$result = $e;
|
|
}
|
|
|
|
/* If we changed application context in the course of this
|
|
* call, undo that change now. */
|
|
if ($pushed === true) {
|
|
$this->popApp();
|
|
}
|
|
|
|
if ($result instanceof Exception) {
|
|
throw $e;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Returns the link corresponding to the default package that provides the
|
|
* functionality requested by the $method parameter.
|
|
*
|
|
* @param string $method The method to link to, consisting of
|
|
* "packagetype/methodname".
|
|
* @param array $args Arguments to the method.
|
|
* @param mixed $extra Extra, non-standard arguments to the method.
|
|
*
|
|
* @return string The link for that method.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function link($method, $args = array(), $extra = '')
|
|
{
|
|
if (($lookup = $this->_methodLookup($method)) === false) {
|
|
throw new Horde_Exception('The link "' . $method . '" is not defined in the Horde Registry.');
|
|
}
|
|
|
|
return $this->linkByPackage($lookup[0], $lookup[1], $args, $extra);
|
|
}
|
|
|
|
/**
|
|
* Returns the link corresponding to the specific package named.
|
|
*
|
|
* @param string $app The application being called.
|
|
* @param string $call The method to link to.
|
|
* @param array $args Arguments to the method.
|
|
* @param mixed $extra Extra, non-standard arguments to the method.
|
|
*
|
|
* @return string The link for that method.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function linkByPackage($app, $call, $args = array(), $extra = '')
|
|
{
|
|
$links = ($api_ob = $this->_loadApi($app))
|
|
? $api_ob->links()
|
|
: array();
|
|
|
|
/* Make sure the link is defined. */
|
|
if (!isset($links[$call])) {
|
|
throw new Horde_Exception('The link "' . $call . '" is not defined in ' . $app . '\'s API.');
|
|
}
|
|
|
|
/* Initial link value. */
|
|
$link = $links[$call];
|
|
|
|
/* Fill in html-encoded arguments. */
|
|
foreach ($args as $key => $val) {
|
|
$link = str_replace('%' . $key . '%', htmlentities($val), $link);
|
|
}
|
|
|
|
$link = $this->applicationWebPath($link, $app);
|
|
|
|
/* Replace htmlencoded arguments that haven't been specified with
|
|
an empty string (this is where the default would be substituted
|
|
in a stricter registry implementation). */
|
|
$link = preg_replace('|%.+%|U', '', $link);
|
|
|
|
/* Fill in urlencoded arguments. */
|
|
foreach ($args as $key => $val) {
|
|
$link = str_replace('|' . Horde_String::lower($key) . '|', urlencode($val), $link);
|
|
}
|
|
|
|
/* Append any extra, non-standard arguments. */
|
|
if (is_array($extra)) {
|
|
$extra_args = '';
|
|
foreach ($extra as $key => $val) {
|
|
$extra_args .= '&' . urlencode($key) . '=' . urlencode($val);
|
|
}
|
|
} else {
|
|
$extra_args = $extra;
|
|
}
|
|
$link = str_replace('|extra|', $extra_args, $link);
|
|
|
|
/* Replace html-encoded arguments that haven't been specified with
|
|
an empty string (this is where the default would be substituted
|
|
in a stricter registry implementation). */
|
|
$link = preg_replace('|\|.+\||U', '', $link);
|
|
|
|
return $link;
|
|
}
|
|
|
|
/**
|
|
* Do a lookup of method name -> app call.
|
|
*
|
|
* @param string $method The method name.
|
|
*
|
|
* @return mixed An array containing the app and method call, or false
|
|
* if not found.
|
|
*/
|
|
protected function _methodLookup($method)
|
|
{
|
|
list($interface, $call) = explode('/', $method, 2);
|
|
if (!empty($this->_interfaces[$method])) {
|
|
return array($this->_interfaces[$method], $call);
|
|
} elseif (!empty($this->_interfaces[$interface])) {
|
|
return array($this->_interfaces[$interface], $call);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Replace any %application% strings with the filesystem path to the
|
|
* application.
|
|
*
|
|
* @param string $path The application string.
|
|
* @param string $app The application being called.
|
|
*
|
|
* @return string The application file path.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function applicationFilePath($path, $app = null)
|
|
{
|
|
if (is_null($app)) {
|
|
$app = $this->getApp();
|
|
}
|
|
|
|
if (!isset($this->applications[$app])) {
|
|
throw new Horde_Exception(sprintf(Horde_Core_Translation::t("\"%s\" is not configured in the Horde Registry."), $app));
|
|
}
|
|
|
|
return str_replace('%application%', $this->applications[$app]['fileroot'], $path);
|
|
}
|
|
|
|
/**
|
|
* Replace any %application% strings with the web path to the application.
|
|
*
|
|
* @param string $path The application string.
|
|
* @param string $app The application being called.
|
|
*
|
|
* @return string The application web path.
|
|
*/
|
|
public function applicationWebPath($path, $app = null)
|
|
{
|
|
return str_replace('%application%', $this->get('webroot', $app), $path);
|
|
}
|
|
|
|
/**
|
|
* TODO
|
|
*
|
|
* @param string $type The type of link.
|
|
* <pre>
|
|
* The following must be defined in Horde's menu config, or else they
|
|
* won't be displayed in the menu:
|
|
* 'help', 'problem', 'logout', 'login', 'prefs'
|
|
* </pre>
|
|
*
|
|
* @return boolean True if the link is to be shown.
|
|
*/
|
|
public function showService($type)
|
|
{
|
|
global $conf;
|
|
|
|
if (!in_array($type, array('help', 'problem', 'logout', 'login', 'prefs'))) {
|
|
return true;
|
|
}
|
|
|
|
if (empty($conf['menu']['links'][$type])) {
|
|
return false;
|
|
}
|
|
|
|
switch ($conf['menu']['links'][$type]) {
|
|
case 'all':
|
|
return true;
|
|
|
|
case 'authenticated':
|
|
return (bool)$this->getAuth();
|
|
|
|
default:
|
|
case 'never':
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the URL to access a Horde service.
|
|
*
|
|
* @param string $type The service to display:
|
|
* - ajax: AJAX endpoint.
|
|
* - cache: Cached data output.
|
|
* - download: Download link.
|
|
* - emailconfirm: E-mail confirmation page.
|
|
* - go: URL redirection utility.
|
|
* - help: Help page.
|
|
* - imple: Imple endpoint.
|
|
* - login: Login page.
|
|
* - logintasks: Logintasks page.
|
|
* - logout: Logout page.
|
|
* - pixel: Pixel generation page.
|
|
* - portal: Main portal page.
|
|
* - prefs: Preferences UI.
|
|
* - problem: Problem reporting page.
|
|
* @param string $app The name of the current Horde application.
|
|
* @param boolean $full Return a full url? @since 2.4.0
|
|
*
|
|
* @return Horde_Url The link.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function getServiceLink($type, $app = null, $full = false)
|
|
{
|
|
$opts = array('app' => 'horde');
|
|
|
|
switch ($type) {
|
|
case 'ajax':
|
|
if (is_null($app)) {
|
|
$app = 'horde';
|
|
}
|
|
return Horde::url('services/ajax.php/' . $app . '/', $full, $opts)
|
|
->add('token', $GLOBALS['session']->getToken());
|
|
|
|
case 'cache':
|
|
$opts['append_session'] = -1;
|
|
return Horde::url('services/cache.php', $full, $opts);
|
|
|
|
case 'download':
|
|
return Horde::url('services/download/', $full, $opts)
|
|
->add('app', $app);
|
|
|
|
case 'emailconfirm':
|
|
return Horde::url('services/confirm.php', $full, $opts);
|
|
|
|
case 'go':
|
|
return Horde::url('services/go.php', $full, $opts);
|
|
|
|
case 'help':
|
|
return Horde::url('services/help/', $full, $opts)
|
|
->add('module', $app);
|
|
|
|
case 'imple':
|
|
return Horde::url('services/imple.php', $full, $opts);
|
|
|
|
case 'login':
|
|
return Horde::url('login.php', $full, $opts);
|
|
|
|
case 'logintasks':
|
|
return Horde::url('services/logintasks.php', $full, $opts)
|
|
->add('app', $app);
|
|
|
|
case 'logout':
|
|
return $this->getLogoutUrl(array(
|
|
'reason' => Horde_Auth::REASON_LOGOUT
|
|
));
|
|
|
|
case 'pixel':
|
|
return Horde::url('services/images/pixel.php', $full, $opts);
|
|
|
|
case 'prefs':
|
|
if (!in_array($GLOBALS['conf']['prefs']['driver'], array('', 'none'))) {
|
|
$url = Horde::url('services/prefs.php', $full, $opts);
|
|
if (!is_null($app)) {
|
|
$url->add('app', $app);
|
|
}
|
|
return $url;
|
|
}
|
|
break;
|
|
|
|
case 'portal':
|
|
return ($this->getView() == Horde_Registry::VIEW_SMARTMOBILE)
|
|
? Horde::url('services/portal/smartmobile.php', $full, $opts)
|
|
: Horde::url('services/portal/', $full, $opts);
|
|
break;
|
|
|
|
case 'problem':
|
|
return Horde::url('services/problem.php', $full, $opts)
|
|
->add(
|
|
'return_url',
|
|
Horde_Util::getFormData(
|
|
'location', Horde::signUrl(Horde::selfUrl(true, true, true))
|
|
)
|
|
);
|
|
|
|
case 'sidebar':
|
|
return Horde::url('services/sidebar.php', $full, $opts);
|
|
|
|
case 'twitter':
|
|
return Horde::url('services/twitter/', true);
|
|
}
|
|
|
|
throw new BadFunctionCallException('Invalid service requested: ' . print_r(debug_backtrace(false), true));
|
|
}
|
|
|
|
/**
|
|
* Set the current application, adding it to the top of the Horde
|
|
* application stack. If this is the first application to be
|
|
* pushed, retrieve session information as well.
|
|
*
|
|
* pushApp() also reads the application's configuration file and
|
|
* sets up its global $conf hash.
|
|
*
|
|
* @param string $app The name of the application to push.
|
|
* @param array $options Additional options:
|
|
* - check_perms: (boolean) Make sure that the current user has
|
|
* permissions to the application being loaded. Should
|
|
* ONLY be disabled by system scripts (cron jobs, etc.)
|
|
* and scripts that handle login.
|
|
* DEFAULT: true
|
|
* - logintasks: (boolean) Perform login tasks? Only performed if
|
|
* 'check_perms' is also true. System tasks are always
|
|
* peformed if the user is authorized.
|
|
* DEFAULT: false
|
|
* - notransparent: (boolean) Do not attempt transparent authentication.
|
|
* DEFAULT: false
|
|
*
|
|
* @return boolean Whether or not the _appStack was modified.
|
|
* @throws Horde_Exception_PushApp
|
|
*/
|
|
public function pushApp($app, array $options = array())
|
|
{
|
|
global $injector, $notification, $language, $session;
|
|
|
|
if ($app == $this->getApp()) {
|
|
return false;
|
|
}
|
|
|
|
/* Bail out if application is not present or inactive. */
|
|
if ($this->isInactive($app)) {
|
|
throw new Horde_Exception_PushApp(
|
|
sprintf(
|
|
Horde_Core_Translation::t("%s is not activated."),
|
|
$this->applications[$app]['name']
|
|
),
|
|
self::NOT_ACTIVE,
|
|
$app
|
|
);
|
|
}
|
|
|
|
$checkPerms = ((!isset($options['check_perms']) ||
|
|
!empty($options['check_perms'])) &&
|
|
$this->currentProcessAuth());
|
|
|
|
/* If permissions checking is requested, return an error if the
|
|
* current user does not have read perms to the application being
|
|
* loaded. We allow access:
|
|
* - To all admins.
|
|
* - To all authenticated users if no permission is set on $app.
|
|
* - To anyone who is allowed by an explicit ACL on $app. */
|
|
if ($checkPerms) {
|
|
$error = $error_log = null;
|
|
$error_app = $this->applications[$app]['name'];
|
|
$error_type = self::AUTH_FAILURE;
|
|
|
|
if (($auth = $this->getAuth()) && !$this->checkExistingAuth()) {
|
|
$error = '%s is not authorized %s(Remote host: %s)';
|
|
$error_app = '';
|
|
}
|
|
|
|
if (!$error &&
|
|
!$this->hasPermission($app, Horde_Perms::READ, array('notransparent' => !empty($options['notransparent'])))) {
|
|
$error = '%s is not authorized for %s (Host: %s).';
|
|
|
|
if ($this->isAuthenticated(array('app' => $app))) {
|
|
$error_log = '%s does not have READ permission for %s (Host: %s)';
|
|
$error_type = self::PERMISSION_DENIED;
|
|
}
|
|
}
|
|
|
|
if ($error) {
|
|
$auth = $auth ? 'User ' . $auth : 'Guest user';
|
|
$remote = $this->remoteHost();
|
|
|
|
if ($error_log) {
|
|
Horde::log(
|
|
sprintf($error_log, $auth, $error_app, $remote->host),
|
|
'DEBUG'
|
|
);
|
|
}
|
|
|
|
throw new Horde_Exception_PushApp(
|
|
sprintf($error, $auth, $error_app, $remote->host),
|
|
$error_type,
|
|
$app
|
|
);
|
|
}
|
|
}
|
|
|
|
/* Push application on the stack. */
|
|
$this->_appStack[] = $app;
|
|
|
|
/* Chicken and egg problem: the language environment has to be loaded
|
|
* before loading the configuration file, because it might contain
|
|
* gettext strings. Though the preferences can specify a different
|
|
* language for this app, they have to be loaded after the
|
|
* configuration, because they rely on configuration settings. So try
|
|
* with the current language, and reset the language later. */
|
|
$this->setLanguageEnvironment($language, $app);
|
|
|
|
/* Load config and prefs. */
|
|
$this->importConfig('horde');
|
|
$this->importConfig($app);
|
|
$this->loadPrefs($app);
|
|
|
|
/* Reset language, since now we can grab language from prefs. */
|
|
if (!$checkPerms && (count($this->_appStack) == 1)) {
|
|
$this->setLanguageEnvironment(null, $app);
|
|
}
|
|
|
|
/* Run authenticated hooks, if necessary. */
|
|
$hooks = $injector->getInstance('Horde_Core_Hooks');
|
|
if ($session->get('horde', 'auth_app_init/' . $app)) {
|
|
try {
|
|
$error = self::INITCALLBACK_FATAL;
|
|
$this->callAppMethod($app, 'authenticated');
|
|
|
|
$error = self::HOOK_FATAL;
|
|
$hooks->callHook('appauthenticated', $app);
|
|
} catch (Exception $e) {
|
|
$this->_pushAppError($e, $error);
|
|
}
|
|
|
|
$session->remove('horde', 'auth_app_init/' . $app);
|
|
unset($this->_appsInit[$app]);
|
|
}
|
|
|
|
/* Initialize application. */
|
|
if (!isset($this->_appsInit[$app])) {
|
|
$notification->addAppHandler($app);
|
|
|
|
try {
|
|
$error = self::INITCALLBACK_FATAL;
|
|
$this->callAppMethod($app, 'init');
|
|
|
|
$error = self::HOOK_FATAL;
|
|
$hooks->callHook('pushapp', $app);
|
|
} catch (Exception $e) {
|
|
$this->_pushAppError($e, $error);
|
|
}
|
|
|
|
$this->_appsInit[$app] = true;
|
|
}
|
|
|
|
/* Do login tasks. */
|
|
if ($checkPerms &&
|
|
!empty($options['logintasks']) &&
|
|
($tasks = $injector->getInstance('Horde_Core_Factory_LoginTasks')->create($app))) {
|
|
$tasks->runTasks();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Process Exceptions thrown when pushing app on stack.
|
|
*
|
|
* @param Exception $e The thrown Exception.
|
|
* @param integer $error The pushApp() error type.
|
|
*
|
|
* @throws Horde_Exception_PushApp
|
|
*/
|
|
protected function _pushAppError(Exception $e, $error)
|
|
{
|
|
if ($e instanceof Horde_Exception_HookNotSet) {
|
|
return;
|
|
}
|
|
|
|
/* Hook errors are already logged. */
|
|
if ($error == self::INITCALLBACK_FATAL) {
|
|
Horde::log($e);
|
|
}
|
|
|
|
$app = $this->getApp();
|
|
$this->applications[$app]['status'] = 'inactive';
|
|
$this->popApp();
|
|
|
|
throw new Horde_Exception_PushApp($e, $error, $app);
|
|
}
|
|
|
|
/**
|
|
* Remove the current app from the application stack, setting the current
|
|
* app to whichever app was current before this one took over.
|
|
*
|
|
* @return string The name of the application that was popped.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function popApp()
|
|
{
|
|
/* Pop the current application off of the stack. */
|
|
$previous = array_pop($this->_appStack);
|
|
|
|
/* Import the new active application's configuration values
|
|
* and set the gettext domain and the preferred language. */
|
|
$app = $this->getApp();
|
|
if ($app) {
|
|
/* Load config and prefs. */
|
|
$this->importConfig('horde');
|
|
$this->importConfig($app);
|
|
$this->loadPrefs($app);
|
|
$this->setTextdomain(
|
|
$app,
|
|
$this->get('fileroot', $app) . '/locale'
|
|
);
|
|
}
|
|
|
|
return $previous;
|
|
}
|
|
|
|
/**
|
|
* Return the current application - the app at the top of the application
|
|
* stack.
|
|
*
|
|
* @return string The current application.
|
|
*/
|
|
public function getApp()
|
|
{
|
|
return end($this->_appStack);
|
|
}
|
|
|
|
/**
|
|
* Check permissions on an application.
|
|
*
|
|
* @param string $app The name of the application
|
|
* @param integer $perms The permission level to check for.
|
|
* @param array $params Additional options:
|
|
* - notransparent: (boolean) Do not attempt transparent authentication.
|
|
* DEFAULT: false
|
|
*
|
|
* @return boolean Whether access is allowed.
|
|
*/
|
|
public function hasPermission($app, $perms = Horde_Perms::READ,
|
|
array $params = array())
|
|
{
|
|
/* Always do isAuthenticated() check first. You can be an admin, but
|
|
* application auth != Horde admin auth. And there can *never* be
|
|
* non-SHOW access to an application that requires authentication. */
|
|
if (!$this->isAuthenticated(array('app' => $app, 'notransparent' => !empty($params['notransparent']))) &&
|
|
$GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create($app)->requireAuth() &&
|
|
($perms != Horde_Perms::SHOW)) {
|
|
return false;
|
|
}
|
|
|
|
/* Otherwise, allow access for admins, for apps that do not have any
|
|
* explicit permissions, or for apps that allow the given permission. */
|
|
return $this->isAdmin() ||
|
|
($GLOBALS['injector']->getInstance('Horde_Perms')->exists($app)
|
|
? $GLOBALS['injector']->getInstance('Horde_Perms')->hasPermission($app, $this->getAuth(), $perms)
|
|
: (bool)$this->getAuth());
|
|
}
|
|
|
|
/**
|
|
* Reads the configuration values for the given application and imports
|
|
* them into the global $conf variable.
|
|
*
|
|
* @param string $app The application name.
|
|
*/
|
|
public function importConfig($app)
|
|
{
|
|
/* Make sure Horde is always loaded. */
|
|
if (!isset($this->_cache['conf']['horde'])) {
|
|
$this->_cache['conf']['horde'] = new Horde_Registry_Hordeconfig(array('app' => 'horde'));
|
|
}
|
|
|
|
if (!isset($this->_cache['conf'][$app])) {
|
|
$this->_cache['conf'][$app] = new Horde_Registry_Hordeconfig_Merged(array(
|
|
'aconfig' => new Horde_Registry_Hordeconfig(array('app' => $app)),
|
|
'hconfig' => $this->_cache['conf']['horde']
|
|
));
|
|
}
|
|
|
|
$GLOBALS['conf'] = $this->_cache['conf'][$app]->toArray();
|
|
}
|
|
|
|
/**
|
|
* Loads the preferences for the current user for the current application
|
|
* and imports them into the global $prefs variable.
|
|
* $app will be the active application after calling this function.
|
|
*
|
|
* @param string $app The name of the application.
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function loadPrefs($app = null)
|
|
{
|
|
global $injector, $prefs;
|
|
|
|
if (strlen($app)) {
|
|
$this->pushApp($app);
|
|
} elseif (($app = $this->getApp()) === false) {
|
|
$app = 'horde';
|
|
}
|
|
|
|
$user = $this->getAuth();
|
|
if ($user) {
|
|
if (isset($prefs) && ($prefs->getUser() == $user)) {
|
|
$prefs->changeScope($app);
|
|
return;
|
|
}
|
|
|
|
$opts = array(
|
|
'user' => $user
|
|
);
|
|
} else {
|
|
/* If there is no logged in user, return an empty Horde_Prefs
|
|
* object with just default preferences. */
|
|
$opts = array(
|
|
'driver' => 'Horde_Prefs_Storage_Null'
|
|
);
|
|
}
|
|
|
|
$prefs = $injector->getInstance('Horde_Core_Factory_Prefs')->create($app, $opts);
|
|
}
|
|
|
|
/**
|
|
* Load a configuration file from a Horde application's config directory.
|
|
* This call is cached (a config file is only loaded once, regardless of
|
|
* the $vars value).
|
|
*
|
|
* @since 2.12.0
|
|
*
|
|
* @param string $conf_file Configuration file name.
|
|
* @param mixed $vars List of config variables to load.
|
|
* @param string $app Application.
|
|
*
|
|
* @return Horde_Registry_Loadconfig The config object.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function loadConfigFile($conf_file, $vars = null, $app = null)
|
|
{
|
|
if (is_null($app)) {
|
|
$app = $this->getApp();
|
|
}
|
|
|
|
if (!isset($this->_cache['cfile'][$app][$conf_file])) {
|
|
$this->_cache['cfile'][$app][$conf_file] = new Horde_Registry_Loadconfig(
|
|
$app,
|
|
$conf_file,
|
|
$vars
|
|
);
|
|
}
|
|
|
|
return $this->_cache['cfile'][$app][$conf_file];
|
|
}
|
|
|
|
/**
|
|
* Return the requested configuration parameter for the specified
|
|
* application. If no application is specified, the value of
|
|
* the current application is used. However, if the parameter is not
|
|
* present for that application, the Horde-wide value is used instead.
|
|
* If that is not present, we return null.
|
|
*
|
|
* @param string $parameter The configuration value to retrieve.
|
|
* @param string $app The application to get the value for.
|
|
*
|
|
* @return string The requested parameter, or null if it is not set.
|
|
*/
|
|
public function get($parameter, $app = null)
|
|
{
|
|
if (is_null($app)) {
|
|
$app = $this->getApp();
|
|
}
|
|
|
|
if (isset($this->applications[$app][$parameter])) {
|
|
$pval = $this->applications[$app][$parameter];
|
|
} else {
|
|
switch ($parameter) {
|
|
case 'icon':
|
|
$pval = Horde_Themes::img($app . '.png', $app);
|
|
if ((string)$pval == '') {
|
|
$pval = Horde_Themes::img('app-unknown.png', 'horde');
|
|
}
|
|
break;
|
|
|
|
case 'initial_page':
|
|
$pval = null;
|
|
break;
|
|
|
|
default:
|
|
$pval = isset($this->applications['horde'][$parameter])
|
|
? $this->applications['horde'][$parameter]
|
|
: null;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ($parameter == 'name')
|
|
? (strlen($pval) ? _($pval) : '')
|
|
: $pval;
|
|
}
|
|
|
|
/**
|
|
* Return the version string for a given application.
|
|
*
|
|
* @param string $app The application to get the value for.
|
|
* @param boolean $number Return the raw version number, suitable for
|
|
* comparison purposes.
|
|
*
|
|
* @return string The version string for the application.
|
|
*/
|
|
public function getVersion($app = null, $number = false)
|
|
{
|
|
if (empty($app)) {
|
|
$app = $this->getApp();
|
|
}
|
|
|
|
try {
|
|
$api = $this->getApiInstance($app, 'application');
|
|
} catch (Horde_Exception $e) {
|
|
return 'unknown';
|
|
}
|
|
|
|
return $number
|
|
? preg_replace('/H\d \((.*)\)/', '$1', $api->version)
|
|
: $api->version;
|
|
}
|
|
|
|
/**
|
|
* Does the application have the queried feature?
|
|
*
|
|
* @param string $id Feature ID.
|
|
* @param string $app The application to check (defaults to current app).
|
|
*
|
|
* @return boolean True if the application has the feature.
|
|
*/
|
|
public function hasFeature($id, $app = null)
|
|
{
|
|
if (empty($app)) {
|
|
$app = $this->getApp();
|
|
}
|
|
|
|
try {
|
|
$api = $this->getApiInstance($app, 'application');
|
|
} catch (Horde_Exception $e) {
|
|
return false;
|
|
}
|
|
|
|
return !empty($api->features[$id]);
|
|
}
|
|
|
|
/**
|
|
* Does the given application have the queried view?
|
|
*
|
|
* @param integer $view The view type (VIEW_* constant).
|
|
* @param string $app The application to check (defaults to current
|
|
* app).
|
|
*
|
|
* @return boolean True if the view is available in the application.
|
|
*/
|
|
public function hasView($view, $app = null)
|
|
{
|
|
switch ($view) {
|
|
case self::VIEW_BASIC:
|
|
// For now, consider all apps to have BASIC view.
|
|
return true;
|
|
|
|
case self::VIEW_DYNAMIC:
|
|
return $this->hasFeature('dynamicView', $app);
|
|
|
|
case self::VIEW_MINIMAL:
|
|
return $this->hasFeature('minimalView', $app);
|
|
|
|
case self::VIEW_SMARTMOBILE:
|
|
return $this->hasFeature('smartmobileView', $app);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set current view.
|
|
*
|
|
* @param integer $view The view type.
|
|
*/
|
|
public function setView($view = self::VIEW_BASIC)
|
|
{
|
|
$GLOBALS['session']->set('horde', 'view', $view);
|
|
}
|
|
|
|
/**
|
|
* Get current view.
|
|
*
|
|
* @return integer The view type.
|
|
*/
|
|
public function getView()
|
|
{
|
|
global $session;
|
|
|
|
return $session->exists('horde', 'view')
|
|
? $session->get('horde', 'view')
|
|
: self::VIEW_BASIC;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of available drivers for a library that are available
|
|
* in an application.
|
|
*
|
|
* @param string $app The application name.
|
|
* @param string $prefix The library prefix.
|
|
*
|
|
* @return array The list of available class names.
|
|
*/
|
|
public function getAppDrivers($app, $prefix)
|
|
{
|
|
$classes = array();
|
|
$fileprefix = strtr($prefix, '_', '/');
|
|
$fileroot = $this->get('fileroot', $app);
|
|
|
|
if (!is_null($fileroot)) {
|
|
try {
|
|
$pushed = $this->pushApp($app);
|
|
} catch (Horde_Exception $e) {
|
|
if ($e->getCode() == Horde_Registry::AUTH_FAILURE) {
|
|
return array();
|
|
}
|
|
throw $e;
|
|
}
|
|
|
|
if (is_dir($fileroot . '/lib/' . $fileprefix)) {
|
|
try {
|
|
$di = new DirectoryIterator($fileroot . '/lib/' . $fileprefix);
|
|
|
|
foreach ($di as $val) {
|
|
if (!$val->isDir() && !$di->isDot()) {
|
|
$class = $app . '_' . $prefix . '_' . basename($val, '.php');
|
|
if (class_exists($class)) {
|
|
$classes[] = $class;
|
|
}
|
|
}
|
|
}
|
|
} catch (UnexpectedValueException $e) {}
|
|
}
|
|
|
|
if ($pushed) {
|
|
$this->popApp();
|
|
}
|
|
}
|
|
|
|
return $classes;
|
|
}
|
|
|
|
/**
|
|
* Query the initial page for an application - the webroot, if there is no
|
|
* initial_page set, and the initial_page, if it is set.
|
|
*
|
|
* @param string $app The name of the application.
|
|
*
|
|
* @return string URL pointing to the initial page of the application.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function getInitialPage($app = null)
|
|
{
|
|
try {
|
|
if (($url = $this->callAppMethod($app, 'getInitialPage')) !== null) {
|
|
return $url;
|
|
}
|
|
} catch (Horde_Exception $e) {}
|
|
|
|
if (($webroot = $this->get('webroot', $app)) !== null) {
|
|
return $webroot . '/' . strval($this->get('initial_page', $app));
|
|
}
|
|
|
|
throw new Horde_Exception(sprintf(
|
|
Horde_Core_Translation::t("\"%s\" is not configured in the Horde Registry."),
|
|
is_null($app) ? $this->getApp() : $app
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Clears any authentication tokens in the current session.
|
|
*
|
|
* @param boolean $destroy Destroy the session?
|
|
*/
|
|
public function clearAuth($destroy = true)
|
|
{
|
|
global $session;
|
|
|
|
/* Do application logout tasks. */
|
|
/* @todo: Replace with exclusively registered logout tasks. */
|
|
foreach ($this->getAuthApps() as $app) {
|
|
try {
|
|
$this->callAppMethod($app, 'logout');
|
|
} catch (Horde_Exception $e) {}
|
|
}
|
|
|
|
/* Do registered logout tasks. */
|
|
$logout = new Horde_Registry_Logout();
|
|
$logout->run();
|
|
|
|
// @suspicious shouldn't this be 'auth/'
|
|
$session->remove('horde', 'auth');
|
|
$session->remove('horde', 'auth_app/');
|
|
|
|
$this->_cache['auth'] = null;
|
|
$this->_cache['existing'] = $this->_cache['isauth'] = array();
|
|
|
|
if ($destroy) {
|
|
$session->destroy();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears authentication tokens for a given application in the current
|
|
* session.
|
|
*
|
|
* @return boolean If false, did not remove authentication token because
|
|
* the application is in control of Horde's auth.
|
|
*/
|
|
public function clearAuthApp($app)
|
|
{
|
|
global $session;
|
|
|
|
if ($session->get('horde', 'auth/credentials') == $app) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->isAuthenticated(array('app' => $app, 'notransparent' => true))) {
|
|
$this->callAppMethod($app, 'logout');
|
|
$session->remove($app);
|
|
$session->remove('horde', 'auth_app/' . $app);
|
|
$session->remove('horde', 'auth_app_init/' . $app);
|
|
}
|
|
|
|
unset(
|
|
$this->_cache['existing'][$app],
|
|
$this->_cache['isauth'][$app]
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Is a user an administrator?
|
|
*
|
|
* @param array $options Options:
|
|
* - permission: (string) Allow users with this permission admin access
|
|
* in the current context.
|
|
* - permlevel: (integer) The level of permissions to check for.
|
|
* Defaults to Horde_Perms::EDIT.
|
|
* - user: (string) The user to check.
|
|
* Defaults to self::getAuth().
|
|
*
|
|
* @return boolean Whether or not this is an admin user.
|
|
*/
|
|
public function isAdmin(array $options = array())
|
|
{
|
|
$user = isset($options['user'])
|
|
? $options['user']
|
|
: $this->getAuth();
|
|
|
|
if ($user &&
|
|
@is_array($GLOBALS['conf']['auth']['admins']) &&
|
|
in_array($user, $GLOBALS['conf']['auth']['admins'])) {
|
|
return true;
|
|
}
|
|
|
|
return isset($options['permission'])
|
|
? $GLOBALS['injector']->getInstance('Horde_Perms')->hasPermission($options['permission'], $user, isset($options['permlevel']) ? $options['permlevel'] : Horde_Perms::EDIT)
|
|
: false;
|
|
}
|
|
|
|
/**
|
|
* Checks if there is a session with valid auth information. If there
|
|
* isn't, but the configured Auth driver supports transparent
|
|
* authentication, then we try that.
|
|
*
|
|
* @param array $opts Additional options:
|
|
* - app: (string) Check authentication for this app.
|
|
* DEFAULT: Checks horde-wide authentication.
|
|
* - notransparent: (boolean) Do not attempt transparent authentication.
|
|
* DEFAULT: false
|
|
*
|
|
* @return boolean Whether or not the user is authenticated.
|
|
*/
|
|
public function isAuthenticated(array $opts = array())
|
|
{
|
|
global $injector, $session;
|
|
|
|
$app = empty($opts['app'])
|
|
? 'horde'
|
|
: $opts['app'];
|
|
$transparent = intval(empty($opts['notransparent']));
|
|
|
|
if (isset($this->_cache['isauth'][$app][$transparent])) {
|
|
return $this->_cache['isauth'][$app][$transparent];
|
|
}
|
|
|
|
/* Check for cached authentication results. */
|
|
if ($this->getAuth() &&
|
|
(($app == 'horde') ||
|
|
$session->exists('horde', 'auth_app/' . $app)) &&
|
|
$this->checkExistingAuth($app)) {
|
|
$res = true;
|
|
} elseif ($transparent) {
|
|
try {
|
|
$res = $injector->getInstance('Horde_Core_Factory_Auth')->create($app)->transparent();
|
|
} catch (Horde_Exception $e) {
|
|
Horde::log($e);
|
|
$res = false;
|
|
}
|
|
} else {
|
|
$res = false;
|
|
}
|
|
|
|
$this->_cache['isauth'][$app][$transparent] = $res;
|
|
|
|
return $res;
|
|
}
|
|
|
|
/**
|
|
* Checks whether this process required authentication.
|
|
*
|
|
* @since 2.11.0
|
|
*
|
|
* @return boolean True if the current process required authentication.
|
|
*/
|
|
public function currentProcessAuth()
|
|
{
|
|
return ($this->_args['authentication'] !== 'none');
|
|
}
|
|
|
|
/**
|
|
* Returns a URL to the login screen, adding the necessary logout
|
|
* parameters.
|
|
*
|
|
* If no reason/msg is passed in, uses the current global authentication
|
|
* error message.
|
|
*
|
|
* @param array $options Additional options:
|
|
* - app: (string) Authenticate to this application
|
|
* DEFAULT: Horde
|
|
* - msg: (string) If reason is Horde_Auth::REASON_MESSAGE, the message
|
|
* to display to the user.
|
|
* DEFAULT: None
|
|
* - params: (array) Additional params to add to the URL (not allowed:
|
|
* 'app', 'horde_logout_token', 'msg', 'reason', 'url').
|
|
* DEFAULT: None
|
|
* - reason: (integer) The reason for logout
|
|
* DEFAULT: None
|
|
*
|
|
* @return Horde_Url The formatted URL.
|
|
*/
|
|
public function getLogoutUrl(array $options = array())
|
|
{
|
|
if (!isset($options['reason'])) {
|
|
// TODO: This only returns the error for Horde-wide
|
|
// authentication, not for application auth.
|
|
$options['reason'] = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create()->getError();
|
|
}
|
|
|
|
$params = array();
|
|
if (!in_array($options['reason'], array(Horde_Auth::REASON_LOGOUT, Horde_Auth::REASON_MESSAGE))) {
|
|
$params['url'] = Horde::signUrl(Horde::selfUrl(true, true, true));
|
|
}
|
|
|
|
if (empty($options['app']) ||
|
|
($options['app'] == 'horde') ||
|
|
($options['reason'] == Horde_Auth::REASON_LOGOUT)) {
|
|
$params['horde_logout_token'] = $GLOBALS['session']->getToken();
|
|
}
|
|
|
|
if (isset($options['app'])) {
|
|
$params['app'] = $options['app'];
|
|
}
|
|
|
|
if ($options['reason']) {
|
|
$params['logout_reason'] = $options['reason'];
|
|
if ($options['reason'] == Horde_Auth::REASON_MESSAGE) {
|
|
$params['logout_msg'] = empty($options['msg'])
|
|
? $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create()->getError(true)
|
|
: $options['msg'];
|
|
}
|
|
}
|
|
|
|
return $this->getServiceLink('login', 'horde')->add($params)->setRaw(true);
|
|
}
|
|
|
|
/**
|
|
* Returns a URL to be used for downloading data.
|
|
*
|
|
* @param string $filename The filename of the download data.
|
|
* @param array $params Additional URL parameters needed.
|
|
*
|
|
* @return Horde_Url The download URL. This URL should be used as-is,
|
|
* since the filename MUST be the last parameter added
|
|
* to the URL.
|
|
*/
|
|
public function downloadUrl($filename, array $params = array())
|
|
{
|
|
$url = $this->getServiceLink('download', $this->getApp())
|
|
/* Add parameters. */
|
|
->add($params);
|
|
|
|
if (strlen($filename)) {
|
|
/* Add the filename to the end of the URL. Although not necessary
|
|
* for many browsers, this should allow every browser to download
|
|
* correctly. */
|
|
$url->add('fn', '/' . $filename);
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Converts an authentication username to a unique Horde username.
|
|
*
|
|
* @param string $userId The username to convert.
|
|
* @param boolean $toHorde If true, convert to a Horde username. If
|
|
* false, convert to the auth username.
|
|
*
|
|
* @return string The converted username.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function convertUsername($userId, $toHorde)
|
|
{
|
|
try {
|
|
return $GLOBALS['injector']->getInstance('Horde_Core_Hooks')->
|
|
callHook('authusername', 'horde', array($userId, $toHorde));
|
|
} catch (Horde_Exception_HookNotSet $e) {
|
|
return $userId;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the currently logged in user, if there is one.
|
|
*
|
|
* @param string $format The return format, defaults to the unique Horde
|
|
* ID. Alternative formats:
|
|
* - bare: (string) Horde ID without any domain information.
|
|
* EXAMPLE: foo@example.com would be returned as 'foo'.
|
|
* - domain: (string) Domain of the Horde ID.
|
|
* EXAMPLE: foo@example.com would be returned as 'example.com'.
|
|
* - original: (string) The username used to originally login to Horde.
|
|
*
|
|
* @return mixed The user ID or false if no user is logged in.
|
|
*/
|
|
public function getAuth($format = null)
|
|
{
|
|
global $session;
|
|
|
|
if (is_null($format) && !is_null($this->_cache['auth'])) {
|
|
return $this->_cache['auth'];
|
|
}
|
|
|
|
if (!isset($session)) {
|
|
return false;
|
|
}
|
|
|
|
if ($format == 'original') {
|
|
return $session->exists('horde', 'auth/authId')
|
|
? $session->get('horde', 'auth/authId')
|
|
: false;
|
|
}
|
|
|
|
$user = $session->get('horde', 'auth/userId');
|
|
if (is_null($user)) {
|
|
return false;
|
|
}
|
|
|
|
switch ($format) {
|
|
case 'bare':
|
|
return (($pos = strpos($user, '@')) === false)
|
|
? $user
|
|
: substr($user, 0, $pos);
|
|
|
|
case 'domain':
|
|
return (($pos = strpos($user, '@')) === false)
|
|
? false
|
|
: substr($user, $pos + 1);
|
|
|
|
default:
|
|
/* Specifically cache this result, since it generally is called
|
|
* many times in a page. */
|
|
$this->_cache['auth'] = $user;
|
|
return $user;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return whether the authentication backend requested a password change.
|
|
*
|
|
* @return boolean Whether the backend requested a password change.
|
|
*/
|
|
public function passwordChangeRequested()
|
|
{
|
|
return (bool)$GLOBALS['session']->get('horde', 'auth/change');
|
|
}
|
|
|
|
/**
|
|
* Returns the requested credential for the currently logged in user, if
|
|
* present.
|
|
*
|
|
* @param string $credential The credential to retrieve.
|
|
* @param string $app The app to query. Defaults to Horde.
|
|
*
|
|
* @return mixed The requested credential, all credentials if $credential
|
|
* is null, or false if no user is logged in.
|
|
*/
|
|
public function getAuthCredential($credential = null, $app = null)
|
|
{
|
|
if (!$this->getAuth()) {
|
|
return false;
|
|
}
|
|
|
|
$credentials = $this->_getAuthCredentials($app);
|
|
|
|
return is_null($credential)
|
|
? $credentials
|
|
: ((is_array($credentials) && isset($credentials[$credential]))
|
|
? $credentials[$credential]
|
|
: false);
|
|
}
|
|
|
|
/**
|
|
* Sets the requested credential for the currently logged in user.
|
|
*
|
|
* @param mixed $credential The credential to set. If an array,
|
|
* overwrites the current credentials array.
|
|
* @param string $value The value to set the credential to. If
|
|
* $credential is an array, this value is
|
|
* ignored.
|
|
* @param string $app The app to update. Defaults to Horde.
|
|
*/
|
|
public function setAuthCredential($credential, $value = null, $app = null)
|
|
{
|
|
global $session;
|
|
|
|
if (!$this->getAuth()) {
|
|
return;
|
|
}
|
|
|
|
if (is_array($credential)) {
|
|
$credentials = $credential;
|
|
} else {
|
|
if (($credentials = $this->_getAuthCredentials($app)) === false) {
|
|
return;
|
|
}
|
|
|
|
if (!is_array($credentials)) {
|
|
$credentials = array();
|
|
}
|
|
|
|
$credentials[$credential] = $value;
|
|
}
|
|
|
|
$entry = $credentials;
|
|
if (($base_app = $session->get('horde', 'auth/credentials')) &&
|
|
($session->get('horde', 'auth_app/' . $base_app) == $entry)) {
|
|
$entry = true;
|
|
}
|
|
|
|
if (is_null($app)) {
|
|
$app = $base_app;
|
|
}
|
|
|
|
/* The auth_app key contains application-specific authentication.
|
|
* Session subkeys are the app names, values are an array containing
|
|
* credentials. If the value is true, application does not require any
|
|
* specific credentials. */
|
|
$session->set('horde', 'auth_app/' . $app, $entry, $session::ENCRYPT);
|
|
$session->set('horde', 'auth_app_init/' . $app, true);
|
|
|
|
unset(
|
|
$this->_cache['existing'][$app],
|
|
$this->_cache['isauth'][$app]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get the list of credentials for a given app.
|
|
*
|
|
* @param string $app The application name.
|
|
*
|
|
* @return mixed True, false, or the credential list.
|
|
*/
|
|
protected function _getAuthCredentials($app)
|
|
{
|
|
global $session;
|
|
|
|
$base_app = $session->get('horde', 'auth/credentials');
|
|
if (is_null($base_app)) {
|
|
return false;
|
|
}
|
|
|
|
if (is_null($app)) {
|
|
$app = $base_app;
|
|
}
|
|
|
|
if (!$session->exists('horde', 'auth_app/' . $app)) {
|
|
return ($base_app != $app)
|
|
? $this->_getAuthCredentials($base_app)
|
|
: false;
|
|
}
|
|
|
|
return $session->get('horde', 'auth_app/' . $app);
|
|
}
|
|
|
|
/**
|
|
* Returns information about the remote host.
|
|
*
|
|
* @since 2.17.0
|
|
*
|
|
* @return object An object with the following properties:
|
|
* <pre>
|
|
* - addr: (string) Remote IP address.
|
|
* - host: (string) Remote hostname (if resolvable; otherwise, this value
|
|
* is identical to 'addr').
|
|
* - proxy: (boolean) True if this user is connecting through a proxy.
|
|
* </pre>
|
|
*/
|
|
public function remoteHost()
|
|
{
|
|
global $injector;
|
|
|
|
$out = new stdClass;
|
|
|
|
$dns = $injector->getInstance('Net_DNS2_Resolver');
|
|
$old_error = error_reporting(0);
|
|
|
|
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
|
$remote_path = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
|
|
$out->addr = $remote_path[0];
|
|
$out->proxy = true;
|
|
} else {
|
|
$out->addr = $_SERVER['REMOTE_ADDR'];
|
|
if (!empty($_SERVER['REMOTE_HOST'])) {
|
|
$out->host = $_SERVER['REMOTE_HOST'];
|
|
}
|
|
$out->proxy = false;
|
|
}
|
|
|
|
if ($dns && !isset($out->host)) {
|
|
$out->host = $out->addr;
|
|
try {
|
|
if ($response = $dns->query($out->addr, 'PTR')) {
|
|
foreach ($response->answer as $val) {
|
|
if (isset($val->ptrdname)) {
|
|
$out->host = $val->ptrdname;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} catch (Net_DNS2_Exception $e) {}
|
|
} elseif (!isset($out->host)) {
|
|
$out->host = gethostbyaddr($out->addr);
|
|
}
|
|
error_reporting($old_error);
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Sets data in the session saying that authorization has succeeded,
|
|
* note which userId was authorized, and note when the login took place.
|
|
*
|
|
* If a user name hook was defined in the configuration, it gets applied
|
|
* to the $userId at this point.
|
|
*
|
|
* @param string $authId The userId that has been authorized.
|
|
* @param array $credentials The credentials of the user.
|
|
* @param array $options Additional options:
|
|
* - app: (string) The app to set authentication credentials for.
|
|
* DEFAULT: 'horde'
|
|
* - change: (boolean) Whether to request that the user change their
|
|
* password.
|
|
* DEFAULT: No
|
|
* - language: (string) The preferred language.
|
|
* DEFAULT: null
|
|
* - no_convert: (boolean) Don't convert the user name with the
|
|
* authusername hook.
|
|
* DEFAULT: false
|
|
*/
|
|
public function setAuth($authId, $credentials, array $options = array())
|
|
{
|
|
global $browser, $injector, $session;
|
|
|
|
$app = empty($options['app'])
|
|
? 'horde'
|
|
: $options['app'];
|
|
|
|
if ($this->getAuth() == $authId) {
|
|
/* Store app credentials - base Horde session already exists. */
|
|
$this->setAuthCredential($credentials, null, $app);
|
|
return;
|
|
}
|
|
|
|
/* Initial authentication to Horde. */
|
|
$session->set('horde', 'auth/authId', $authId);
|
|
$session->set('horde', 'auth/browser', $browser->getAgentString());
|
|
if (!empty($options['change'])) {
|
|
$session->set('horde', 'auth/change', 1);
|
|
}
|
|
$session->set('horde', 'auth/credentials', $app);
|
|
|
|
$remote = $this->remoteHost();
|
|
$session->set('horde', 'auth/remoteAddr', $remote->addr);
|
|
|
|
$session->set('horde', 'auth/timestamp', time());
|
|
$username = trim($authId);
|
|
if (!empty($GLOBALS['conf']['auth']['lowercase'])) {
|
|
$username = Horde_String::lower($username);
|
|
}
|
|
if (empty($options['no_convert'])) {
|
|
$username = $this->convertUsername($username, true);
|
|
}
|
|
$session->set('horde', 'auth/userId', $username);
|
|
|
|
$this->_cache['auth'] = null;
|
|
$this->_cache['existing'] = $this->_cache['isauth'] = array();
|
|
|
|
$this->setAuthCredential($credentials, null, $app);
|
|
|
|
/* Reload preferences for the new user. */
|
|
unset($GLOBALS['prefs']);
|
|
$this->loadPrefs($this->getApp());
|
|
|
|
$this->setLanguageEnvironment(isset($options['language']) ? $this->preferredLang($options['language']) : null, $app);
|
|
}
|
|
|
|
/**
|
|
* Check existing auth for triggers that might invalidate it.
|
|
*
|
|
* @param string $app Check authentication for this app too.
|
|
*
|
|
* @return boolean Is existing auth valid?
|
|
*/
|
|
public function checkExistingAuth($app = 'horde')
|
|
{
|
|
global $browser, $conf, $injector, $session;
|
|
|
|
if (!empty($this->_cache['existing'][$app])) {
|
|
return true;
|
|
}
|
|
|
|
/* Tasks that only need to run once. */
|
|
if (empty($this->_cache['existing'])) {
|
|
if (!empty($conf['auth']['checkip']) &&
|
|
($remoteaddr = $session->get('horde', 'auth/remoteAddr')) &&
|
|
($remoteob = $this->remoteHost()) &&
|
|
($remoteaddr != $remoteob->addr)) {
|
|
$injector->getInstance('Horde_Core_Factory_Auth')->create()
|
|
->setError(Horde_Core_Auth_Application::REASON_SESSIONIP);
|
|
return false;
|
|
}
|
|
|
|
if (!empty($conf['auth']['checkbrowser']) &&
|
|
($session->get('horde', 'auth/browser') != $browser->getAgentString())) {
|
|
$injector->getInstance('Horde_Core_Factory_Auth')->create()
|
|
->setError(Horde_Core_Auth_Application::REASON_BROWSER);
|
|
return false;
|
|
}
|
|
|
|
if (!empty($conf['session']['max_time']) &&
|
|
(($conf['session']['max_time'] + $session->begin) < time())) {
|
|
$injector->getInstance('Horde_Core_Factory_Auth')->create()
|
|
->setError(Horde_Core_Auth_Application::REASON_SESSIONMAXTIME);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
foreach (array_unique(array('horde', $app)) as $val) {
|
|
if (!isset($this->_cache['existing'][$val])) {
|
|
$auth = $injector->getInstance('Horde_Core_Factory_Auth')->create($val);
|
|
if (!$auth->validateAuth()) {
|
|
if (!$auth->getError()) {
|
|
$auth->setError(Horde_Auth::REASON_SESSION);
|
|
}
|
|
return false;
|
|
}
|
|
$this->_cache['existing'][$val] = true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Removes a user from the authentication backend and calls all
|
|
* applications' removeUserData API methods.
|
|
*
|
|
* @param string $userId The userId to delete.
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function removeUser($userId)
|
|
{
|
|
$GLOBALS['injector']
|
|
->getInstance('Horde_Core_Factory_Auth')
|
|
->create()
|
|
->removeUser($userId);
|
|
$this->removeUserData($userId);
|
|
}
|
|
|
|
/**
|
|
* Removes user's application data.
|
|
*
|
|
* @param string $user The user ID to delete.
|
|
* @param string $app If set, only removes data from this application.
|
|
* By default, removes data from all apps.
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function removeUserData($user, $app = null)
|
|
{
|
|
if (!$this->isAdmin() && ($user != $this->getAuth())) {
|
|
throw new Horde_Exception(Horde_Core_Translation::t("You are not allowed to remove user data."));
|
|
}
|
|
|
|
$applist = empty($app)
|
|
? $this->listApps(
|
|
array('notoolbar', 'hidden', 'active', 'admin', 'noadmin')
|
|
)
|
|
: array($app);
|
|
$errApps = array();
|
|
if (!empty($applist)) {
|
|
$prefs_ob = $GLOBALS['injector']
|
|
->getInstance('Horde_Core_Factory_Prefs')
|
|
->create('horde', array('user' => $user));
|
|
|
|
// Remove all preference at once, if possible.
|
|
if (empty($app)) {
|
|
try {
|
|
$prefs_ob->removeAll();
|
|
} catch (Horde_Exception $e) {
|
|
Horde::log($e);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($applist as $item) {
|
|
try {
|
|
$this->callAppMethod($item, 'removeUserData', array(
|
|
'args' => array($user)
|
|
));
|
|
} catch (Exception $e) {
|
|
Horde::log($e);
|
|
$errApps[] = $item;
|
|
}
|
|
|
|
if (empty($app)) {
|
|
continue;
|
|
}
|
|
try {
|
|
$prefs_ob->retrieve($item);
|
|
$prefs_ob->remove();
|
|
} catch (Horde_Exception $e) {
|
|
Horde::log($e);
|
|
$errApps[] = $item;
|
|
}
|
|
}
|
|
|
|
if (count($errApps)) {
|
|
throw new Horde_Exception(sprintf(Horde_Core_Translation::t("The following applications encountered errors removing user data: %s"), implode(', ', array_unique($errApps))));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns authentication metadata information.
|
|
*
|
|
* @since 2.12.0
|
|
*
|
|
* @return array Authentication metadata:
|
|
* - authId: (string) The username used during the original auth.
|
|
* - browser: (string) The remote browser string.
|
|
* - change: (boolean) Is a password change requested?
|
|
* - credentials: (string) The 'auth_app' entry that contains the Horde
|
|
* credentials.
|
|
* - remoteAddr: (string) The remote IP address of the user.
|
|
* - timestamp: (integer) The login time.
|
|
* - userId: (string) The unique Horde username.
|
|
*/
|
|
public function getAuthInfo()
|
|
{
|
|
global $session;
|
|
|
|
return $session->get('horde', 'auth/', $session::TYPE_ARRAY);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of applications currently authenticated to.
|
|
*
|
|
* @since 2.12.0
|
|
*
|
|
* @return array List of authenticated applications.
|
|
*/
|
|
public function getAuthApps()
|
|
{
|
|
global $session;
|
|
|
|
return array_keys(
|
|
$session->get('horde', 'auth_app/', $session::TYPE_ARRAY)
|
|
);
|
|
}
|
|
|
|
/* NLS functions. */
|
|
|
|
/**
|
|
* Returns the charset for the current language.
|
|
*
|
|
* @return string The character set that should be used with the current
|
|
* locale settings.
|
|
*/
|
|
public function getLanguageCharset()
|
|
{
|
|
return ($charset = $this->nlsconfig->curr_charset)
|
|
? $charset
|
|
: 'ISO-8859-1';
|
|
}
|
|
|
|
/**
|
|
* Returns the charset to use for outgoing emails.
|
|
*
|
|
* @return string The preferred charset for outgoing mails based on
|
|
* the user's preferences and the current language.
|
|
*/
|
|
public function getEmailCharset()
|
|
{
|
|
if (isset($GLOBALS['prefs']) &&
|
|
($charset = $GLOBALS['prefs']->getValue('sending_charset'))) {
|
|
return $charset;
|
|
}
|
|
|
|
return ($charset = $this->nlsconfig->curr_emails)
|
|
? $charset
|
|
: $this->getLanguageCharset();
|
|
}
|
|
|
|
/**
|
|
* Selects the most preferred language for the current client session.
|
|
*
|
|
* @param string $lang Force to use this language.
|
|
*
|
|
* @return string The selected language abbreviation.
|
|
*/
|
|
public function preferredLang($lang = null)
|
|
{
|
|
/* Check if we have a language set in the session */
|
|
if ($GLOBALS['session']->exists('horde', 'language')) {
|
|
return basename($GLOBALS['session']->get('horde', 'language'));
|
|
}
|
|
|
|
/* If language pref exists, we should use that. */
|
|
if (isset($GLOBALS['prefs']) &&
|
|
($language = $GLOBALS['prefs']->getValue('language'))) {
|
|
return basename($language);
|
|
}
|
|
|
|
/* Check if the user selected a language from the login screen */
|
|
if (!empty($lang) && $this->nlsconfig->validLang($lang)) {
|
|
return basename($lang);
|
|
}
|
|
|
|
/* Try browser-accepted languages. */
|
|
if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
|
/* The browser supplies a list, so return the first valid one. */
|
|
$browser_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
|
|
foreach ($browser_langs as $lang) {
|
|
/* Strip quality value for language */
|
|
if (($pos = strpos($lang, ';')) !== false) {
|
|
$lang = substr($lang, 0, $pos);
|
|
}
|
|
|
|
$lang = $this->_mapLang(trim($lang));
|
|
if ($this->nlsconfig->validLang($lang)) {
|
|
return basename($lang);
|
|
}
|
|
|
|
/* In case there's no full match, save our best guess. Try
|
|
* ll_LL, followed by just ll. */
|
|
if (!isset($partial_lang)) {
|
|
$ll_LL = Horde_String::lower(substr($lang, 0, 2)) . '_' . Horde_String::upper(substr($lang, 0, 2));
|
|
if ($this->nlsconfig->validLang($ll_LL)) {
|
|
$partial_lang = $ll_LL;
|
|
} else {
|
|
$ll = $this->_mapLang(substr($lang, 0, 2));
|
|
if ($this->nlsconfig->validLang($ll)) {
|
|
$partial_lang = $ll;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($partial_lang)) {
|
|
return basename($partial_lang);
|
|
}
|
|
}
|
|
|
|
/* Use site-wide default, if one is defined */
|
|
return $this->nlsconfig->curr_default
|
|
? basename($this->nlsconfig->curr_default)
|
|
/* No dice auto-detecting, default to US English. */
|
|
: 'en_US';
|
|
}
|
|
|
|
/**
|
|
* Sets the language.
|
|
*
|
|
* @param string $lang The language abbreviation.
|
|
*
|
|
* @return string The current language (since 2.5.0).
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function setLanguage($lang = null)
|
|
{
|
|
if (empty($lang) || !$this->nlsconfig->validLang($lang)) {
|
|
$lang = $this->preferredLang();
|
|
}
|
|
|
|
$GLOBALS['session']->set('horde', 'language', $lang);
|
|
|
|
$changed = false;
|
|
if (isset($GLOBALS['language'])) {
|
|
if ($GLOBALS['language'] == $lang) {
|
|
return $lang;
|
|
}
|
|
$changed = true;
|
|
}
|
|
|
|
$GLOBALS['language'] = $lang;
|
|
|
|
$lang_charset = $lang . '.UTF-8';
|
|
if (setlocale(LC_ALL, $lang_charset)) {
|
|
putenv('LC_ALL=' . $lang_charset);
|
|
putenv('LANG=' . $lang_charset);
|
|
putenv('LANGUAGE=' . $lang_charset);
|
|
} else {
|
|
$changed = false;
|
|
}
|
|
|
|
if ($changed) {
|
|
$this->rebuild();
|
|
|
|
$this->_cache['cfile'] = array();
|
|
|
|
foreach ($this->listApps() as $app) {
|
|
if ($this->isAuthenticated(array('app' => $app, 'notransparent' => true))) {
|
|
$this->callAppMethod($app, 'changeLanguage');
|
|
}
|
|
}
|
|
}
|
|
|
|
return $lang;
|
|
}
|
|
|
|
/**
|
|
* Sets the language and reloads the whole NLS environment.
|
|
*
|
|
* When setting the language, the gettext catalogs have to be reloaded
|
|
* too, charsets have to be updated etc. This method takes care of all
|
|
* this.
|
|
*
|
|
* @param string $lang The new language.
|
|
* @param string $app The application for reloading the gettext catalog.
|
|
* Uses current application if null.
|
|
*/
|
|
public function setLanguageEnvironment($lang = null, $app = null)
|
|
{
|
|
if (empty($app)) {
|
|
$app = $this->getApp();
|
|
}
|
|
|
|
$old_lang = $this->setLanguage($lang);
|
|
|
|
$this->setTextdomain(
|
|
$app,
|
|
$this->get('fileroot', $app) . '/locale'
|
|
);
|
|
|
|
if ($old_lang == $GLOBALS['language']) {
|
|
return;
|
|
}
|
|
|
|
$GLOBALS['session']->remove('horde', 'nls/');
|
|
}
|
|
|
|
/**
|
|
* Sets the gettext domain.
|
|
*
|
|
* @param string $app The application name.
|
|
* @param string $directory The directory where the application's
|
|
* LC_MESSAGES directory resides.
|
|
*/
|
|
public function setTextdomain($app, $directory)
|
|
{
|
|
bindtextdomain($app, $directory);
|
|
textdomain($app);
|
|
|
|
/* The existence of this function depends on the platform. */
|
|
if (function_exists('bind_textdomain_codeset')) {
|
|
bind_textdomain_codeset($app, 'UTF-8');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the current timezone, if available.
|
|
*/
|
|
public function setTimeZone()
|
|
{
|
|
$tz = $GLOBALS['prefs']->getValue('timezone');
|
|
if (!empty($tz)) {
|
|
@date_default_timezone_set($tz);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maps languages with common two-letter codes (such as nl) to the full
|
|
* locale code (in this case, nl_NL). Returns the language unmodified if
|
|
* it isn't an alias.
|
|
*
|
|
* @param string $language The language code to map.
|
|
*
|
|
* @return string The mapped language code.
|
|
*/
|
|
protected function _mapLang($language)
|
|
{
|
|
// Translate the $language to get broader matches.
|
|
// (eg. de-DE should match de_DE)
|
|
$trans_lang = str_replace('-', '_', $language);
|
|
$lang_parts = explode('_', $trans_lang);
|
|
$trans_lang = Horde_String::lower($lang_parts[0]);
|
|
if (isset($lang_parts[1])) {
|
|
$trans_lang .= '_' . Horde_String::upper($lang_parts[1]);
|
|
}
|
|
|
|
return empty($this->nlsconfig->aliases[$trans_lang])
|
|
? $trans_lang
|
|
: $this->nlsconfig->aliases[$trans_lang];
|
|
}
|
|
|
|
/**
|
|
* Is the registry in 'test' mode?
|
|
*
|
|
* @since 2.12.0
|
|
*
|
|
* @return boolean True if in testing mode.
|
|
*/
|
|
public function isTest()
|
|
{
|
|
return !empty($this->_args['test']);
|
|
}
|
|
|
|
}
|