* @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:
*
* - $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
*
*
* @param string $app The application to initialize.
* @param array $args Optional arguments:
*
* - 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
*
*
* @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.
*
* 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'
*
*
* @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:
*
* - 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.
*
*/
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']);
}
}