1350 lines
45 KiB
PHP
1350 lines
45 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.
|
|
*
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Jon Parise <jon@horde.org>
|
|
* @category Horde
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL
|
|
* @package Core
|
|
*/
|
|
|
|
/**
|
|
* Provides the base functionality shared by all Horde applications.
|
|
*
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Jon Parise <jon@horde.org>
|
|
* @category Horde
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL
|
|
* @package Core
|
|
*/
|
|
class Horde
|
|
{
|
|
const SSL_NEVER = 0;
|
|
const SSL_ALWAYS = 1;
|
|
const SSL_AUTO = 2;
|
|
const SSL_ONLY_LOGIN = 3;
|
|
/**
|
|
* The current buffer level.
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected static $_bufferLevel = 0;
|
|
|
|
/**
|
|
* Has content been sent at the base buffer level?
|
|
*
|
|
* @var boolean
|
|
*/
|
|
protected static $_contentSent = false;
|
|
|
|
/**
|
|
* The labels already used in this page.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected static $_labels = array();
|
|
|
|
/**
|
|
* Are accesskeys supported on this system.
|
|
*
|
|
* @var boolean
|
|
*/
|
|
protected static $_noAccessKey;
|
|
|
|
/**
|
|
* The access keys already used in this page.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected static $_used = array();
|
|
|
|
/**
|
|
* Shortcut to logging method.
|
|
*
|
|
* @see Horde_Core_Log_Logger
|
|
*/
|
|
public static function log($event, $priority = null,
|
|
array $options = array())
|
|
{
|
|
$options['trace'] = isset($options['trace'])
|
|
? ($options['trace'] + 1)
|
|
: 1;
|
|
$log_ob = new Horde_Core_Log_Object($event, $priority, $options);
|
|
|
|
/* Chicken/egg: we must wait until we have basic framework setup
|
|
* before we can start logging. Otherwise, queue entries. */
|
|
if (isset($GLOBALS['injector']) &&
|
|
Horde_Core_Factory_Logger::available()) {
|
|
$GLOBALS['injector']->getInstance('Horde_Log_Logger')->logObject($log_ob);
|
|
} else {
|
|
Horde_Core_Factory_Logger::queue($log_ob);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shortcut to logging method.
|
|
*
|
|
* @deprecated Use log() instead
|
|
* @see log()
|
|
*/
|
|
public static function logMessage($event, $priority = null,
|
|
array $options = array())
|
|
{
|
|
$options['trace'] = isset($options['trace'])
|
|
? ($options['trace'] + 1)
|
|
: 1;
|
|
self::log($event, $priority, $options);
|
|
}
|
|
|
|
/**
|
|
* Debug method. Allows quick shortcut to produce debug output into a
|
|
* temporary file.
|
|
*
|
|
* @param mixed $event Item to log.
|
|
* @param string $fname Filename to log to. If empty, logs to
|
|
* 'horde_debug.txt' in the PHP temporary
|
|
* directory.
|
|
* @param boolean $backtrace Include backtrace information?
|
|
*/
|
|
public static function debug($event = null, $fname = null,
|
|
$backtrace = true)
|
|
{
|
|
if (is_null($fname)) {
|
|
$fname = self::getTempDir() . '/horde_debug.txt';
|
|
}
|
|
|
|
try {
|
|
$logger = new Horde_Log_Logger(new Horde_Log_Handler_Stream($fname));
|
|
} catch (Exception $e) {
|
|
return;
|
|
}
|
|
|
|
$html_ini = ini_set('html_errors', 'Off');
|
|
self::startBuffer();
|
|
if (!is_null($event)) {
|
|
echo "Variable information:\n";
|
|
var_dump($event);
|
|
echo "\n";
|
|
}
|
|
|
|
if (is_resource($event)) {
|
|
echo "Stream contents:\n";
|
|
rewind($event);
|
|
fpassthru($event);
|
|
echo "\n";
|
|
}
|
|
|
|
if ($backtrace) {
|
|
echo "Backtrace:\n";
|
|
echo strval(new Horde_Support_Backtrace());
|
|
}
|
|
|
|
$logger->log(self::endBuffer(), Horde_Log::DEBUG);
|
|
ini_set('html_errors', $html_ini);
|
|
}
|
|
|
|
/**
|
|
* Adds a signature + timestamp to a URL and returns the signed URL.
|
|
*
|
|
* @since Horde_Core 2.30.0
|
|
*
|
|
* @param string|Horde_Url $url The URL to sign.
|
|
* @param integer $now The timestamp at which to sign. Leave
|
|
* blank for generating signatures; specify
|
|
* when testing.
|
|
*
|
|
* @return string|Horde_Url The signed URL.
|
|
*/
|
|
public static function signUrl($url, $now = null)
|
|
{
|
|
global $conf;
|
|
|
|
if (!isset($conf['secret_key'])) {
|
|
return $url;
|
|
}
|
|
|
|
if (is_null($now)) {
|
|
$now = time();
|
|
}
|
|
|
|
if ($url instanceof Horde_Url) {
|
|
$url->setRaw(true)->add(array('_t' => $now, '_h' => ''));
|
|
$url->add(
|
|
'_h',
|
|
Horde_Url::uriB64Encode(
|
|
hash_hmac('sha1', $url . '=', $conf['secret_key'], true)
|
|
)
|
|
);
|
|
return $url;
|
|
}
|
|
|
|
if (!$url) {
|
|
return $url;
|
|
}
|
|
|
|
if (strpos($url, '?')) {
|
|
$url .= '&';
|
|
} else {
|
|
$url .= '?';
|
|
}
|
|
$url .= '_t=' . $now . '&_h=';
|
|
$url .= Horde_Url::uriB64Encode(
|
|
hash_hmac('sha1', $url, $conf['secret_key'], true)
|
|
);
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Verifies a signature and timestamp on a URL.
|
|
*
|
|
* @since Horde_Core 2.30.0
|
|
*
|
|
* @param string $data The signed URL.
|
|
* @param integer $now The current time (can override for testing).
|
|
*
|
|
* @return string|boolean The URL stripped off the signature, of false if
|
|
* not verified.
|
|
*/
|
|
public static function verifySignedUrl($data, $now = null)
|
|
{
|
|
global $conf;
|
|
|
|
if (is_null($now)) {
|
|
$now = time();
|
|
}
|
|
|
|
$pos = strrpos($data, '&_h=');
|
|
if ($pos === false) {
|
|
return false;
|
|
}
|
|
$pos += 4;
|
|
|
|
$url = substr($data, 0, $pos);
|
|
$hmac = substr($data, $pos);
|
|
|
|
if ($hmac != Horde_Url::uriB64Encode(hash_hmac('sha1', $url, $conf['secret_key'], true))) {
|
|
return false;
|
|
}
|
|
|
|
// String was not tampered with; now validate timestamp
|
|
parse_str(parse_url($url, PHP_URL_QUERY), $values);
|
|
if ($values['_t'] + $conf['urls']['hmac_lifetime'] * 60 < $now) {
|
|
return false;
|
|
}
|
|
|
|
$pos = strrpos($data, '&_t=');
|
|
if ($pos === false) {
|
|
$pos = strrpos($data, '?_t=');
|
|
}
|
|
if ($pos === false) {
|
|
return false;
|
|
}
|
|
|
|
return substr($data, 0, $pos);
|
|
}
|
|
|
|
/**
|
|
* Adds a signature + timestamp to a query string and returns the signed
|
|
* query string.
|
|
*
|
|
* @param mixed $queryString The query string (or Horde_Url object)
|
|
* to sign.
|
|
* @param integer $now The timestamp at which to sign. Leave blank
|
|
* for generating signatures; specify when
|
|
* testing.
|
|
*
|
|
* @return mixed The signed query string (or Horde_Url object).
|
|
*/
|
|
public static function signQueryString($queryString, $now = null)
|
|
{
|
|
if (!isset($GLOBALS['conf']['secret_key'])) {
|
|
return $queryString;
|
|
}
|
|
|
|
if (is_null($now)) {
|
|
$now = time();
|
|
}
|
|
|
|
if ($queryString instanceof Horde_Url) {
|
|
$queryString->setRaw(true)->add(array('_t' => $now, '_h' => ''));
|
|
$parse_url = parse_url($queryString);
|
|
$queryString->add('_h', Horde_Url::uriB64Encode(hash_hmac('sha1', $parse_url['query'] . '=', $GLOBALS['conf']['secret_key'], true)));
|
|
return $queryString;
|
|
}
|
|
|
|
$queryString .= '&_t=' . $now . '&_h=';
|
|
|
|
return $queryString . Horde_Url::uriB64Encode(hash_hmac('sha1', $queryString, $GLOBALS['conf']['secret_key'], true));
|
|
}
|
|
|
|
/**
|
|
* Verifies a signature and timestamp on a query string.
|
|
*
|
|
* @param string $data The signed query string.
|
|
* @param integer $now The current time (can override for testing).
|
|
*
|
|
* @return boolean Whether or not the string was valid.
|
|
*/
|
|
public static function verifySignedQueryString($data, $now = null)
|
|
{
|
|
if (is_null($now)) {
|
|
$now = time();
|
|
}
|
|
|
|
$pos = strrpos($data, '&_h=');
|
|
if ($pos === false) {
|
|
return false;
|
|
}
|
|
$pos += 4;
|
|
|
|
$queryString = substr($data, 0, $pos);
|
|
$hmac = substr($data, $pos);
|
|
|
|
if ($hmac != Horde_Url::uriB64Encode(hash_hmac('sha1', $queryString, $GLOBALS['conf']['secret_key'], true))) {
|
|
return false;
|
|
}
|
|
|
|
// String was not tampered with; now validate timestamp
|
|
parse_str($queryString, $values);
|
|
|
|
return !($values['_t'] + $GLOBALS['conf']['urls']['hmac_lifetime'] * 60 < $now);
|
|
}
|
|
|
|
/**
|
|
* Do necessary escaping to output JSON.
|
|
*
|
|
* @param mixed $data The data to JSON-ify.
|
|
* @param array $options Additional options:
|
|
* - nodelimit: (boolean) Don't add security delimiters?
|
|
* DEFAULT: false
|
|
* - urlencode: (boolean) URL encode the json string
|
|
* DEFAULT: false
|
|
*
|
|
* @return string The escaped string.
|
|
*/
|
|
public static function escapeJson($data, array $options = array())
|
|
{
|
|
$json = Horde_Serialize::serialize($data, Horde_Serialize::JSON);
|
|
if (empty($options['nodelimit'])) {
|
|
$json = '/*-secure-' . $json . '*/';
|
|
}
|
|
|
|
return empty($options['urlencode'])
|
|
? $json
|
|
: '\'' . rawurlencode($json) . '\'';
|
|
}
|
|
|
|
/**
|
|
* Is the current HTTP connection considered secure?
|
|
* @TODO Move this to the request classes!
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public static function isConnectionSecure()
|
|
{
|
|
global $browser, $conf, $registry;
|
|
|
|
if ($browser->usingSSLConnection()) {
|
|
return true;
|
|
}
|
|
|
|
if (!empty($conf['safe_ips'])) {
|
|
if (reset($conf['safe_ips']) == '*') {
|
|
return true;
|
|
}
|
|
|
|
/* $_SERVER['HTTP_X_FORWARDED_FOR'] is user data and not
|
|
* reliable. We don't consult it for safe IPs. We also have to
|
|
* assume that if it is present, the user is coming through a proxy
|
|
* server. If so, we don't count any non-SSL connection as safe, no
|
|
* matter the source IP. */
|
|
$remote = $registry->remoteHost();
|
|
if (!$remote->proxy) {
|
|
foreach ($conf['safe_ips'] as $safe_ip) {
|
|
$safe_ip = preg_replace('/(\.0)*$/', '', $safe_ip);
|
|
if (strpos($remote->addr, $safe_ip) === 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Throws an exception if not using a secure connection.
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public static function requireSecureConnection()
|
|
{
|
|
if (!self::isConnectionSecure()) {
|
|
throw new Horde_Exception(Horde_Core_Translation::t("The encryption features require a secure web connection."));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the driver parameters for the specified backend.
|
|
*
|
|
* @param mixed $backend The backend system (e.g. 'prefs', 'categories',
|
|
* 'contacts') being used.
|
|
* The used configuration array will be
|
|
* $conf[$backend]. If an array gets passed, it will
|
|
* be $conf[$key1][$key2].
|
|
* @param string $type The type of driver. If null, will not merge with
|
|
* base config.
|
|
*
|
|
* @return array The connection parameters.
|
|
*/
|
|
public static function getDriverConfig($backend, $type = 'sql')
|
|
{
|
|
global $conf;
|
|
|
|
if (!is_null($type)) {
|
|
$type = Horde_String::lower($type);
|
|
}
|
|
|
|
if (is_array($backend)) {
|
|
$c = Horde_Array::getElement($conf, $backend);
|
|
} elseif (isset($conf[$backend])) {
|
|
$c = $conf[$backend];
|
|
} else {
|
|
$c = null;
|
|
}
|
|
|
|
if (!is_null($c) && isset($c['params'])) {
|
|
$c['params']['umask'] = $conf['umask'];
|
|
|
|
$result = (!is_null($type) && isset($conf[$type]))
|
|
? array_merge($conf[$type], $c['params'])
|
|
: $c['params'];
|
|
|
|
// HOTFIX for Bug #14547. If using different protocols for the
|
|
// base SQL config and the explicit driver we are creating, we
|
|
// need to remove the not-used connection config since they use
|
|
// different keys.
|
|
if ((!isset($c['params']['driverconfig']) ||
|
|
$c['params']['driverconfig'] != 'horde') &&
|
|
!is_null($type) && $type == 'sql') {
|
|
if ($c['params']['protocol'] == 'unix') {
|
|
unset($result['hostspec'], $result['port']);
|
|
} else {
|
|
unset($result['socket']);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
return (!is_null($type) && isset($conf[$type]))
|
|
? $conf[$type]
|
|
: array();
|
|
}
|
|
|
|
/**
|
|
* Checks if all necessary parameters for a driver configuration
|
|
* are set and throws a fatal error with a detailed explanation
|
|
* how to fix this, if something is missing.
|
|
*
|
|
* @param array $params The configuration array with all parameters.
|
|
* @param string $driver The key name (in the configuration array) of
|
|
* the driver.
|
|
* @param array $fields An array with mandatory parameter names for
|
|
* this driver.
|
|
* @param string $name The clear text name of the driver. If not
|
|
* specified, the application name will be used.
|
|
* @param string $file The configuration file that should contain
|
|
* these settings.
|
|
* @param string $variable The name of the configuration variable.
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public static function assertDriverConfig($params, $driver, $fields,
|
|
$name = null,
|
|
$file = 'conf.php',
|
|
$variable = '$conf')
|
|
{
|
|
global $registry;
|
|
|
|
// Don't generate a fatal error if we fail during or before
|
|
// Registry instantiation.
|
|
if (is_null($name)) {
|
|
$name = isset($registry) ? $registry->getApp() : '[unknown]';
|
|
}
|
|
$fileroot = isset($registry) ? $registry->get('fileroot') : '';
|
|
|
|
if (!is_array($params) || !count($params)) {
|
|
throw new Horde_Exception(
|
|
sprintf(Horde_Core_Translation::t("No configuration information specified for %s."), $name) . "\n\n" .
|
|
sprintf(Horde_Core_Translation::t("The file %s should contain some %s settings."),
|
|
$fileroot . '/config/' . $file,
|
|
sprintf("%s['%s']['params']", $variable, $driver)));
|
|
}
|
|
|
|
foreach ($fields as $field) {
|
|
if (!isset($params[$field])) {
|
|
throw new Horde_Exception(
|
|
sprintf(Horde_Core_Translation::t("Required \"%s\" not specified in %s configuration."), $field, $name) . "\n\n" .
|
|
sprintf(Horde_Core_Translation::t("The file %s should contain a %s setting."),
|
|
$fileroot . '/config/' . $file,
|
|
sprintf("%s['%s']['params']['%s']", $variable, $driver, $field)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a session-id-ified version of $uri.
|
|
* If a full URL is requested, all parameter separators get converted to
|
|
* "&", otherwise to "&".
|
|
*
|
|
* @param mixed $uri The URI to be modified (either a string or any
|
|
* object with a __toString() function).
|
|
* @param boolean $full Generate a full (http://server/path/) URL.
|
|
* @param mixed $opts Additional options. If a string/integer, it is
|
|
* taken to be the 'append_session' option. If an
|
|
* array, one of the following:
|
|
* - app: (string) Use this app for the webroot.
|
|
* DEFAULT: current application
|
|
* - append_session: (integer) 0 = only if needed [DEFAULT], 1 = always,
|
|
* -1 = never.
|
|
* - force_ssl: (boolean) Ignore $conf['use_ssl'] and force creation of
|
|
* a SSL URL?
|
|
* DEFAULT: false
|
|
*
|
|
* @return Horde_Url The URL with the session id appended (if needed).
|
|
*/
|
|
public static function url($uri, $full = false, $opts = array())
|
|
{
|
|
if (is_array($opts)) {
|
|
$append_session = isset($opts['append_session'])
|
|
? $opts['append_session']
|
|
: 0;
|
|
if (!empty($opts['force_ssl'])) {
|
|
$full = true;
|
|
}
|
|
} else {
|
|
$append_session = $opts;
|
|
$opts = array();
|
|
}
|
|
|
|
$puri = parse_url($uri);
|
|
/* @todo Fix for PHP < 5.3.6 */
|
|
if (isset($puri['fragment']) && !isset($puri['path'])) {
|
|
$pos = strpos(
|
|
$uri,
|
|
'/',
|
|
strpos($uri, $puri['host']) + strlen($puri['host'])
|
|
);
|
|
$puri['path'] = substr($uri, $pos, strpos($uri, '#', $pos) - $pos);
|
|
}
|
|
/* End fix */
|
|
|
|
$url = '';
|
|
$schemeRegexp = '|^([a-zA-Z][a-zA-Z0-9+.-]{0,19})://|';
|
|
$webroot = ltrim(
|
|
$GLOBALS['registry']->get(
|
|
'webroot',
|
|
empty($opts['app']) ? null : $opts['app']
|
|
),
|
|
'/'
|
|
);
|
|
|
|
if ($full &&
|
|
!isset($puri['scheme']) &&
|
|
!preg_match($schemeRegexp, $webroot)) {
|
|
/* Store connection parameters in local variables. */
|
|
$server_name = $GLOBALS['conf']['server']['name'];
|
|
$server_port = isset($GLOBALS['conf']['server']['port'])
|
|
? $GLOBALS['conf']['server']['port']
|
|
: '';
|
|
|
|
$protocol = 'http';
|
|
switch ($GLOBALS['conf']['use_ssl']) {
|
|
case self::SSL_ALWAYS:
|
|
$protocol = 'https';
|
|
break;
|
|
|
|
case self::SSL_AUTO:
|
|
if ($GLOBALS['browser']->usingSSLConnection()) {
|
|
$protocol = 'https';
|
|
}
|
|
break;
|
|
|
|
case self::SSL_ONLY_LOGIN:
|
|
if (!empty($opts['force_ssl'])) {
|
|
$protocol = 'https';
|
|
$server_port = '';
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* If using a non-standard port, add to the URL. */
|
|
if (!empty($server_port) &&
|
|
((($protocol == 'http') && ($server_port != 80)) ||
|
|
(($protocol == 'https') && ($server_port != 443)))) {
|
|
$server_name .= ':' . $server_port;
|
|
}
|
|
|
|
$url = $protocol . '://' . $server_name;
|
|
} elseif (isset($puri['scheme'])) {
|
|
$url = $puri['scheme'] . '://' . $puri['host'];
|
|
|
|
/* If using a non-standard port, add to the URL. */
|
|
if (isset($puri['port']) &&
|
|
((($puri['scheme'] == 'http') && ($puri['port'] != 80)) ||
|
|
(($puri['scheme'] == 'https') && ($puri['port'] != 443)))) {
|
|
$url .= ':' . $puri['port'];
|
|
}
|
|
}
|
|
|
|
if (isset($puri['path']) &&
|
|
(substr($puri['path'], 0, 1) == '/') &&
|
|
(!preg_match($schemeRegexp, $webroot) ||
|
|
(preg_match($schemeRegexp, $webroot) && isset($puri['scheme'])))) {
|
|
$url .= $puri['path'];
|
|
} elseif (isset($puri['path']) && preg_match($schemeRegexp, $webroot)) {
|
|
if (substr($puri['path'], 0, 1) == '/') {
|
|
$pwebroot = parse_url($webroot);
|
|
$url = $pwebroot['scheme'] . '://' . $pwebroot['host']
|
|
. $puri['path'];
|
|
} else {
|
|
$url = $webroot . '/' . $puri['path'];
|
|
}
|
|
} else {
|
|
$url .= '/' . ($webroot ? $webroot . '/' : '') . (isset($puri['path']) ? $puri['path'] : '');
|
|
}
|
|
|
|
if (isset($puri['query'])) {
|
|
$url .= '?' . $puri['query'];
|
|
}
|
|
if (isset($puri['fragment'])) {
|
|
$url .= '#' . $puri['fragment'];
|
|
}
|
|
|
|
$ob = new Horde_Url($url, $full);
|
|
|
|
if (empty($GLOBALS['conf']['session']['use_only_cookies']) &&
|
|
(($append_session == 1) ||
|
|
(($append_session == 0) && !isset($_COOKIE[session_name()])))) {
|
|
$ob->add(session_name(), session_id());
|
|
}
|
|
|
|
return $ob;
|
|
}
|
|
|
|
/**
|
|
* Returns an external link passed through the dereferrer to strip session
|
|
* IDs from the referrer.
|
|
*
|
|
* @param string $url The external URL to link to.
|
|
* @param boolean $tag If true, a complete <a> tag is returned, only the
|
|
* url otherwise.
|
|
*
|
|
* @return string The link to the dereferrer script.
|
|
*/
|
|
public static function externalUrl($url, $tag = false)
|
|
{
|
|
if (!isset($_GET[session_name()]) ||
|
|
Horde_String::substr($url, 0, 1) == '#' ||
|
|
Horde_String::substr($url, 0, 7) == 'mailto:') {
|
|
$ext = $url;
|
|
} else {
|
|
$ext = self::signQueryString($GLOBALS['registry']->getServiceLink('go', 'horde')->add('url', $url));
|
|
}
|
|
|
|
if ($tag) {
|
|
$ext = self::link($ext, $url, '', '_blank');
|
|
}
|
|
|
|
return $ext;
|
|
}
|
|
|
|
/**
|
|
* Returns an anchor tag with the relevant parameters
|
|
*
|
|
* @param Horde_Url|string $url The full URL to be linked to.
|
|
* @param string $title The link title/description.
|
|
* @param string $class The CSS class of the link.
|
|
* @param string $target The window target to point to.
|
|
* @param string $onclick JavaScript action for the 'onclick' event.
|
|
* @param string $title2 The link title (tooltip) (deprecated - just
|
|
* use $title).
|
|
* @param string $accesskey The access key to use.
|
|
* @param array $attributes Any other name/value pairs to add to the
|
|
* <a> tag.
|
|
* @param boolean $escape Whether to escape special characters in the
|
|
* title attribute.
|
|
*
|
|
* @return string The full <a href> tag.
|
|
*/
|
|
public static function link($url = '', $title = '', $class = '',
|
|
$target = '', $onclick = '', $title2 = '',
|
|
$accesskey = '', $attributes = array(),
|
|
$escape = true)
|
|
{
|
|
if (!($url instanceof Horde_Url)) {
|
|
$url = new Horde_Url($url);
|
|
}
|
|
|
|
if (!empty($title2)) {
|
|
$title = $title2;
|
|
}
|
|
if (!empty($onclick)) {
|
|
$attributes['onclick'] = $onclick;
|
|
}
|
|
if (!empty($class)) {
|
|
$attributes['class'] = $class;
|
|
}
|
|
if (!empty($target)) {
|
|
$attributes['target'] = $target;
|
|
}
|
|
if (!empty($accesskey)) {
|
|
$attributes['accesskey'] = $accesskey;
|
|
}
|
|
if (!empty($title)) {
|
|
if ($escape) {
|
|
$title = str_replace(
|
|
array("\r", "\n"), '',
|
|
htmlspecialchars(nl2br(htmlspecialchars($title))));
|
|
/* Remove double encoded entities. */
|
|
$title = preg_replace('/&([a-z]+|(#\d+));/i', '&\\1;', $title);
|
|
}
|
|
$attributes['title.raw'] = $title;
|
|
}
|
|
|
|
return $url->link($attributes);
|
|
}
|
|
|
|
/**
|
|
* Uses DOM Tooltips to display the 'title' attribute for link() calls.
|
|
*
|
|
* @param string $url The full URL to be linked to
|
|
* @param string $status The JavaScript mouse-over string
|
|
* @param string $class The CSS class of the link
|
|
* @param string $target The window target to point to.
|
|
* @param string $onclick JavaScript action for the 'onclick' event.
|
|
* @param string $title The link title (tooltip). Most not contain
|
|
* HTML data other than <br>, which will
|
|
* be converted to a linebreak.
|
|
* @param string $accesskey The access key to use.
|
|
* @param array $attributes Any other name/value pairs to add to the
|
|
* <a> tag.
|
|
*
|
|
* @return string The full <a href> tag.
|
|
*/
|
|
public static function linkTooltip(
|
|
$url, $status = '', $class = '', $target = '', $onclick = '',
|
|
$title = '', $accesskey = '', $attributes = array()
|
|
)
|
|
{
|
|
if (strlen($title)) {
|
|
$attributes['nicetitle'] = Horde_Serialize::serialize(
|
|
preg_split(
|
|
'/\r?\n/',
|
|
preg_replace('/<br\s*\/?\s*>/', "\n", $title)
|
|
),
|
|
Horde_Serialize::JSON
|
|
);
|
|
$title = null;
|
|
$GLOBALS['injector']->getInstance('Horde_PageOutput')
|
|
->addScriptFile('tooltips.js', 'horde');
|
|
}
|
|
|
|
return self::link(
|
|
$url,
|
|
$title,
|
|
$class,
|
|
$target,
|
|
$onclick,
|
|
null,
|
|
$accesskey,
|
|
$attributes,
|
|
false
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns an anchor sequence with the relevant parameters for a widget
|
|
* with accesskey and text.
|
|
*
|
|
* @param array $params A hash with widget options (other options will be
|
|
* passed as attributes to the link tag):
|
|
* - url: (string) The full URL to be linked to.
|
|
* - title: (string) The link title/description.
|
|
* - nocheck: (boolean, optional) Don't check if the accesskey already
|
|
* already has been used.
|
|
* Defaults to false (= check).
|
|
*
|
|
* @return string The full <a href>Title</a> sequence.
|
|
*/
|
|
public static function widget($params)
|
|
{
|
|
$params = array_merge(
|
|
array(
|
|
'class' => '',
|
|
'target' => '',
|
|
'onclick' => '',
|
|
'nocheck' => false),
|
|
$params
|
|
);
|
|
|
|
$url = ($params['url'] instanceof Horde_Url)
|
|
? $params['url']
|
|
: new Horde_Url($params['url']);
|
|
$title = $params['title'];
|
|
$params['accesskey'] = self::getAccessKey($title, $params['nocheck']);
|
|
|
|
unset($params['url'], $params['title'], $params['nocheck']);
|
|
|
|
return $url->link($params)
|
|
. self::highlightAccessKey($title, $params['accesskey'])
|
|
. '</a>';
|
|
}
|
|
|
|
/**
|
|
* Returns a session-id-ified version of $SCRIPT_NAME resp. $PHP_SELF.
|
|
*
|
|
* @param boolean $script_params Include script parameters like
|
|
* QUERY_STRING and PATH_INFO?
|
|
* (Deprecated: use Horde::selfUrlParams()
|
|
* instead.)
|
|
* @param boolean $nocache Include a cache-buster parameter in the
|
|
* URL?
|
|
* @param boolean $full Return a full URL?
|
|
* @param boolean $force_ssl Ignore $conf['use_ssl'] and force creation
|
|
* of a SSL URL?
|
|
*
|
|
* @return Horde_Url The requested URL.
|
|
*/
|
|
public static function selfUrl($script_params = false, $nocache = true,
|
|
$full = false, $force_ssl = false)
|
|
{
|
|
if (!strncmp(PHP_SAPI, 'cgi', 3)) {
|
|
// When using CGI PHP, SCRIPT_NAME may contain the path to
|
|
// the PHP binary instead of the script being run; use
|
|
// PHP_SELF instead.
|
|
$url = $_SERVER['PHP_SELF'];
|
|
} else {
|
|
$url = isset($_SERVER['SCRIPT_NAME']) ?
|
|
$_SERVER['SCRIPT_NAME'] :
|
|
$_SERVER['PHP_SELF'];
|
|
}
|
|
if (isset($_SERVER['REQUEST_URI'])) {
|
|
$url = Horde_String::common($_SERVER['REQUEST_URI'], $url);
|
|
}
|
|
if (substr($url, -9) == 'index.php') {
|
|
$url = substr($url, 0, -9);
|
|
}
|
|
|
|
if ($script_params) {
|
|
$url = new Horde_Url($url);
|
|
if ($pathInfo = Horde_Util::getPathInfo()) {
|
|
$url->pathInfo = ltrim($pathInfo, '/');
|
|
}
|
|
if (!empty($_SERVER['QUERY_STRING'])) {
|
|
parse_str($_SERVER['QUERY_STRING'], $args);
|
|
$url->add($args);
|
|
}
|
|
}
|
|
|
|
$url = self::url($url, $full, array('force_ssl' => $force_ssl));
|
|
|
|
return ($nocache && $GLOBALS['browser']->hasQuirk('cache_same_url'))
|
|
? $url->unique()
|
|
: $url;
|
|
}
|
|
|
|
/**
|
|
* Create a self URL of the current page, building the parameter list from
|
|
* the current Horde_Variables object (or via another Variables object
|
|
* passed as an optional argument) rather than the original request data.
|
|
*
|
|
* @since 2.3.0
|
|
*
|
|
* @param array $opts Additional options:
|
|
* - force_ssl: (boolean) Force creation of an SSL URL?
|
|
* DEFAULT: false
|
|
* - full: (boolean) Return a full URL?
|
|
* DEFAULT: false
|
|
* - nocache: (boolean) Include a cache-buster parameter in the URL?
|
|
* DEFAULT: true
|
|
* - vars: (Horde_Variables) Use this Horde_Variables object instead of
|
|
* the Horde global object.
|
|
* DEFAULT: Use the Horde global object.
|
|
*
|
|
* @return Horde_Url The self URL.
|
|
*/
|
|
public static function selfUrlParams(array $opts = array())
|
|
{
|
|
$vars = isset($opts['vars'])
|
|
? $opts['vars']
|
|
: $GLOBALS['injector']->createInstance('Horde_Variables');
|
|
|
|
$url = self::selfUrl(
|
|
false,
|
|
(!array_key_exists('nocache', $opts) || empty($opts['nocache'])),
|
|
!empty($opts['full']),
|
|
!empty($opts['force_ssl'])
|
|
)->add(iterator_to_array($vars));
|
|
|
|
if (!isset($opts['vars'])) {
|
|
$url->remove(array_keys($_COOKIE));
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Determines the location of the system temporary directory. If a specific
|
|
* configuration cannot be found, it defaults to /tmp.
|
|
*
|
|
* @return string A directory name that can be used for temp files.
|
|
* Returns false if one could not be found.
|
|
*/
|
|
public static function getTempDir()
|
|
{
|
|
global $conf;
|
|
|
|
/* If one has been specifically set, then use that */
|
|
if (!empty($conf['tmpdir'])) {
|
|
$tmp = $conf['tmpdir'];
|
|
}
|
|
|
|
/* Next, try sys_get_temp_dir(). */
|
|
if (empty($tmp)) {
|
|
$tmp = sys_get_temp_dir();
|
|
}
|
|
|
|
/* If it is still empty, we have failed, so return false;
|
|
* otherwise return the directory determined. */
|
|
return empty($tmp)
|
|
? false
|
|
: $tmp;
|
|
}
|
|
|
|
/**
|
|
* Creates a temporary filename for the lifetime of the script, and
|
|
* (optionally) registers it to be deleted at request shutdown.
|
|
*
|
|
* @param string $prefix Prefix to make the temporary name more
|
|
* recognizable.
|
|
* @param boolean $delete Delete the file at the end of the
|
|
* request?
|
|
* @param string $dir Directory to create the temporary file
|
|
* in.
|
|
* @param boolean $secure If deleting file, should we securely
|
|
* delete the file?
|
|
* @param boolean $session_remove Delete this file when session is
|
|
* destroyed?
|
|
*
|
|
* @return string Returns the full path-name to the temporary file or
|
|
* false if a temporary file could not be created.
|
|
*/
|
|
public static function getTempFile($prefix = 'Horde', $delete = true,
|
|
$dir = '', $secure = false,
|
|
$session_remove = false)
|
|
{
|
|
if (empty($dir) || !is_dir($dir)) {
|
|
$dir = self::getTempDir();
|
|
}
|
|
$tmpfile = Horde_Util::getTempFile($prefix, $delete, $dir, $secure);
|
|
if ($session_remove) {
|
|
$gcfiles = $GLOBALS['session']->get('horde', 'gc_tempfiles', Horde_Session::TYPE_ARRAY);
|
|
$gcfiles[] = $tmpfile;
|
|
$GLOBALS['session']->set('horde', 'gc_tempfiles', $gcfiles);
|
|
}
|
|
|
|
return $tmpfile;
|
|
}
|
|
|
|
/**
|
|
* Returns the Web server being used.
|
|
* PHP string list built from the PHP 'configure' script.
|
|
*
|
|
* @return string A web server identification string.
|
|
* @see php_sapi_name()
|
|
*/
|
|
public static function webServerID()
|
|
{
|
|
switch (PHP_SAPI) {
|
|
case 'apache':
|
|
return 'apache1';
|
|
|
|
case 'apache2filter':
|
|
case 'apache2handler':
|
|
return 'apache2';
|
|
|
|
default:
|
|
return PHP_SAPI;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an un-used access key from the label given.
|
|
*
|
|
* @param string $label The label to choose an access key from.
|
|
* @param boolean $nocheck Don't check if the access key already has been
|
|
* used?
|
|
* @param boolean $shutdown Is this called as a shutdown function?
|
|
*
|
|
* @return string A single lower case character access key, or an empty
|
|
* string if no key can be found.
|
|
*/
|
|
public static function getAccessKey(
|
|
$label, $nocheck = false, $shutdown = false
|
|
)
|
|
{
|
|
/* Shutdown call for translators? */
|
|
if ($shutdown) {
|
|
if (!count(self::$_labels)) {
|
|
return;
|
|
}
|
|
$script = basename($_SERVER['PHP_SELF']);
|
|
$labels = array_keys(self::$_labels);
|
|
sort($labels);
|
|
$used = array_keys(self::$_used);
|
|
sort($used);
|
|
$remaining = str_replace($used, array(), 'abcdefghijklmnopqrstuvwxyz');
|
|
self::log('Access key information for ' . $script);
|
|
self::log('Used labels: ' . implode(',', $labels));
|
|
self::log('Used keys: ' . implode('', $used));
|
|
self::log('Free keys: ' . $remaining);
|
|
return;
|
|
}
|
|
|
|
/* Use access keys at all? */
|
|
if (!isset(self::$_noAccessKey)) {
|
|
self::$_noAccessKey = !$GLOBALS['browser']->hasFeature('accesskey') || !$GLOBALS['prefs']->getValue('widget_accesskey');
|
|
}
|
|
|
|
if (self::$_noAccessKey ||
|
|
!preg_match('/_(\w)/u', $label, $match)) {
|
|
return '';
|
|
}
|
|
$key = Horde_String_Transliterate::toAscii($match[1]);
|
|
|
|
/* Has this key already been used? */
|
|
if (isset(self::$_used[strtolower($key)]) &&
|
|
!($nocheck && isset(self::$_labels[$label]))) {
|
|
return '';
|
|
}
|
|
|
|
/* Save key and label. */
|
|
self::$_used[strtolower($key)] = true;
|
|
self::$_labels[$label] = true;
|
|
|
|
return $key;
|
|
}
|
|
|
|
/**
|
|
* Strips an access key from a label.
|
|
*
|
|
* For multibyte charset strings the access key gets removed completely,
|
|
* otherwise only the underscore gets removed.
|
|
*
|
|
* @param string $label The label containing an access key.
|
|
*
|
|
* @return string The label with the access key being stripped.
|
|
*/
|
|
public static function stripAccessKey($label)
|
|
{
|
|
$replace = $GLOBALS['registry']->nlsconfig->curr_multibyte &&
|
|
preg_match('/[\x80-\xff]/', $label)
|
|
? ''
|
|
: '$1';
|
|
return preg_replace('/_(\w)/u', $replace, $label);
|
|
}
|
|
|
|
/**
|
|
* Highlights an access key in a label.
|
|
*
|
|
* @param string $label The label to highlight the access key in.
|
|
* @param string $accessKey The access key to highlight.
|
|
*
|
|
* @return string The HTML version of the label with the access key
|
|
* highlighted.
|
|
*/
|
|
public static function highlightAccessKey($label, $accessKey)
|
|
{
|
|
$stripped_label = self::stripAccessKey($label);
|
|
|
|
if (empty($accessKey)) {
|
|
return $stripped_label;
|
|
}
|
|
|
|
if ($GLOBALS['registry']->nlsconfig->curr_multibyte) {
|
|
/* Prefix parenthesis with the UTF-8 representation of the LRO
|
|
* (Left-to-Right-Override) Unicode codepoint U+202D. */
|
|
return $stripped_label . "\xe2\x80\xad" .
|
|
'(<span class="accessKey">' . strtoupper($accessKey) .
|
|
'</span>' . ')';
|
|
}
|
|
|
|
return preg_replace(
|
|
'/_(\w)/u',
|
|
'<span class="accessKey">$1</span>',
|
|
$label
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the appropriate "accesskey" and "title" attributes for an HTML
|
|
* tag and the given label.
|
|
*
|
|
* @param string $label The title of an HTML element
|
|
* @param boolean $nocheck Don't check if the access key already has
|
|
* been used?
|
|
* @param boolean $return_array Return attributes as a hash?
|
|
*
|
|
* @return string The title, and if appropriate, the accesskey attributes
|
|
* for the element.
|
|
*/
|
|
public static function getAccessKeyAndTitle($label, $nocheck = false,
|
|
$return_array = false)
|
|
{
|
|
$ak = self::getAccessKey($label, $nocheck);
|
|
$attributes = array('title' => self::stripAccessKey($label));
|
|
if (!empty($ak)) {
|
|
$attributes['title'] .= sprintf(Horde_Core_Translation::t(" (Accesskey %s)"), strtoupper($ak));
|
|
$attributes['accesskey'] = $ak;
|
|
}
|
|
|
|
if ($return_array) {
|
|
return $attributes;
|
|
}
|
|
|
|
$html = '';
|
|
foreach ($attributes as $attribute => $value) {
|
|
$html .= sprintf(' %s="%s"', $attribute, $value);
|
|
}
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Returns a label element including an access key for usage in conjuction
|
|
* with a form field. User preferences regarding access keys are respected.
|
|
*
|
|
* @param string $for The form field's id attribute.
|
|
* @param string $label The label text.
|
|
* @param string $ak The access key to use. If null a new access key
|
|
* will be generated.
|
|
*
|
|
* @return string The html code for the label element.
|
|
*/
|
|
public static function label($for, $label, $ak = null)
|
|
{
|
|
if (is_null($ak)) {
|
|
$ak = self::getAccessKey($label, 1);
|
|
}
|
|
$label = self::highlightAccessKey($label, $ak);
|
|
|
|
return sprintf('<label for="%s"%s>%s</label>',
|
|
$for,
|
|
!empty($ak) ? ' accesskey="' . $ak . '"' : '',
|
|
$label);
|
|
}
|
|
|
|
/**
|
|
* Print inline javascript to output buffer after wrapping with necessary
|
|
* javascript tags.
|
|
*
|
|
* @param array $script The script to output.
|
|
*
|
|
* @return string The script with the necessary HTML javascript tags
|
|
* appended.
|
|
*/
|
|
public static function wrapInlineScript($script)
|
|
{
|
|
return '<script type="text/javascript">//<![CDATA[' . "\n" . implode('', $script) . "\n//]]></script>\n";
|
|
}
|
|
|
|
/**
|
|
* Creates a URL for cached data.
|
|
*
|
|
* @param string $type The cache type ('app', 'css', 'js').
|
|
* @param array $params Optional parameters:
|
|
* - app: REQUIRED for $type == 'app'. Identifies the application to
|
|
* call the 'cacheOutput' API call, which is passed in the
|
|
* value of the entire $params array (which may include parameters
|
|
* other than those listed here). The return from cacheOutput
|
|
* should be a 2-element array: 'data' (the cached data) and
|
|
* 'type' (the content-type of the data).
|
|
* - cid: REQUIRED for $type == 'css' || 'js'. The cacheid of the
|
|
* data (stored in Horde_Cache).
|
|
* - nocache: If true, sets the cache limiter to 'nocache' instead of
|
|
* the default 'public'.
|
|
*
|
|
* @return Horde_Url The URL to the cache page.
|
|
*/
|
|
public static function getCacheUrl($type, $params = array())
|
|
{
|
|
$url = $GLOBALS['registry']
|
|
->getserviceLink('cache', 'horde')
|
|
->add('cache', $type);
|
|
foreach ($params as $key => $val) {
|
|
$url .= '/' . $key . '=' . rawurlencode(strval($val));
|
|
}
|
|
|
|
return self::url($url, true, array('append_session' => -1));
|
|
}
|
|
|
|
/**
|
|
* Output the javascript needed to call the popup JS function.
|
|
*
|
|
* @param string|Horde_Url $url The page to load.
|
|
* @param array $options Additional options:
|
|
* - height: (integer) The height of the popup window.
|
|
* DEFAULT: 650px
|
|
* - menu: (boolean) Show the browser menu in the popup window?
|
|
* DEFAULT: false
|
|
* - onload: (string) A JS function to call after the popup window is
|
|
* fully loaded.
|
|
* DEFAULT: None
|
|
* - params: (array) Additional parameters to pass to the URL.
|
|
* DEFAULT: None
|
|
* - urlencode: (boolean) URL encode the json string?
|
|
* DEFAULT: false
|
|
* - width: (integer) The width of the popup window.
|
|
* DEFAULT: 700 px
|
|
*
|
|
* @return string The javascript needed to call the popup code.
|
|
*/
|
|
public static function popupJs($url, $options = array())
|
|
{
|
|
$GLOBALS['page_output']->addScriptPackage('Horde_Core_Script_Package_Popup');
|
|
|
|
$params = new stdClass;
|
|
|
|
if (!$url instanceof Horde_Url) {
|
|
$url = new Horde_Url($url);
|
|
}
|
|
$params->url = $url->url;
|
|
|
|
if (!empty($url->parameters)) {
|
|
if (!isset($options['params'])) {
|
|
$options['params'] = array();
|
|
}
|
|
foreach (array_merge($url->parameters, $options['params']) as $key => $val) {
|
|
$options['params'][$key] = addcslashes($val, '"');
|
|
}
|
|
}
|
|
|
|
if (!empty($options['menu'])) {
|
|
$params->menu = 1;
|
|
}
|
|
foreach (array('height', 'onload', 'params', 'width') as $key) {
|
|
if (!empty($options[$key])) {
|
|
$params->$key = $options[$key];
|
|
}
|
|
}
|
|
|
|
return 'void(HordePopup.popup(' . self::escapeJson($params, array('nodelimit' => true, 'urlencode' => !empty($options['urlencode']))) . '));';
|
|
}
|
|
|
|
/**
|
|
* Start buffering output.
|
|
*/
|
|
public static function startBuffer()
|
|
{
|
|
if (!self::$_bufferLevel) {
|
|
self::$_contentSent = self::contentSent();
|
|
}
|
|
|
|
++self::$_bufferLevel;
|
|
ob_start();
|
|
}
|
|
|
|
/**
|
|
* End buffering output.
|
|
*
|
|
* @return string The buffered output.
|
|
*/
|
|
public static function endBuffer()
|
|
{
|
|
if (self::$_bufferLevel) {
|
|
--self::$_bufferLevel;
|
|
return ob_get_clean();
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Has any content been sent to the browser?
|
|
*
|
|
* @return boolean True if content has been sent.
|
|
*/
|
|
public static function contentSent()
|
|
{
|
|
return ((self::$_bufferLevel && self::$_contentSent) ||
|
|
(!self::$_bufferLevel && (ob_get_length() || headers_sent())));
|
|
}
|
|
|
|
/**
|
|
* Returns the sidebar for the current application.
|
|
*
|
|
* @param string $app The application to generate the menu for. Defaults
|
|
* to the current app.
|
|
*
|
|
* @return Horve_View_Sidebar The sidebar.
|
|
*/
|
|
public static function sidebar($app = null)
|
|
{
|
|
global $registry;
|
|
|
|
if (empty($app)) {
|
|
$app = $registry->getApp();
|
|
}
|
|
|
|
$menu = new Horde_Menu();
|
|
$registry->callAppMethod($app, 'menu', array(
|
|
'args' => array($menu)
|
|
));
|
|
$sidebar = $menu->render();
|
|
$registry->callAppMethod($app, 'sidebar', array(
|
|
'args' => array($sidebar)
|
|
));
|
|
|
|
return $sidebar;
|
|
}
|
|
|
|
/**
|
|
* Process a permission denied error, running a user-defined hook if
|
|
* necessary.
|
|
*
|
|
* @param string $app Application name.
|
|
* @param string $perm Permission name.
|
|
* @param string $error An error message to output via the notification
|
|
* system.
|
|
*/
|
|
public static function permissionDeniedError($app, $perm, $error = null)
|
|
{
|
|
try {
|
|
$GLOBALS['injector']->getInstance('Horde_Core_Hooks')
|
|
->callHook('perms_denied', 'horde', array($app, $perm));
|
|
} catch (Horde_Exception_HookNotSet $e) {}
|
|
|
|
if (!is_null($error)) {
|
|
$GLOBALS['notification']->push($error, 'horde.warning');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle deprecated methods (located in Horde_Deprecated).
|
|
*/
|
|
public static function __callStatic($name, $arguments)
|
|
{
|
|
return call_user_func_array(
|
|
array('Horde_Deprecated', $name),
|
|
$arguments
|
|
);
|
|
}
|
|
|
|
}
|