PageRenderTime 10ms CodeModel.GetById 5ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 1ms

/horde-3.3.13/lib/Horde.php

#
PHP | 2033 lines | 1229 code | 190 blank | 614 comment | 238 complexity | 93ded3be32a278182f511ce2f36c5b8f MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/**
   3 * @package Horde_Framework
   4 *
   5 * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
   6 *
   7 * See the enclosed file COPYING for license information (LGPL). If you
   8 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
   9 *
  10 * $Horde: framework/Horde/Horde.php,v 1.489.2.117 2010/03/04 11:51:15 wrobel Exp $
  11 */
  12
  13/** Log */
  14include_once 'Log.php';
  15
  16/** Util */
  17include_once 'Horde/Util.php';
  18
  19/**
  20 * The Horde:: class provides the functionality shared by all Horde
  21 * applications.
  22 *
  23 * @author  Chuck Hagenbuch <chuck@horde.org>
  24 * @author  Jon Parise <jon@horde.org>
  25 * @since   Horde 1.3
  26 * @package Horde_Framework
  27 */
  28class Horde {
  29
  30    /**
  31     * Logs a message to the global Horde log backend.
  32     *
  33     * @param mixed $message     Either a string or a PEAR_Error object.
  34     * @param string $file       What file was the log function called from
  35     *                           (e.g. __FILE__)?
  36     * @param integer $line      What line was the log function called from
  37     *                           (e.g. __LINE__)?
  38     * @param integer $priority  The priority of the message. One of:
  39     * <pre>
  40     * PEAR_LOG_EMERG
  41     * PEAR_LOG_ALERT
  42     * PEAR_LOG_CRIT
  43     * PEAR_LOG_ERR
  44     * PEAR_LOG_WARNING
  45     * PEAR_LOG_NOTICE
  46     * PEAR_LOG_INFO
  47     * PEAR_LOG_DEBUG
  48     * </pre>
  49     */
  50    function logMessage($message, $file, $line, $priority = PEAR_LOG_INFO)
  51    {
  52        $logger = &Horde::getLogger();
  53        if ($logger === false) {
  54            return;
  55        }
  56
  57        if ($priority > $GLOBALS['conf']['log']['priority']) {
  58            return;
  59        }
  60
  61        if (is_a($message, 'PEAR_Error')) {
  62            $userinfo = $message->getUserInfo();
  63            $message = $message->getMessage();
  64            if (!empty($userinfo)) {
  65                if (is_array($userinfo)) {
  66                    $old_error = error_reporting(0);
  67                    $userinfo = implode(', ', $userinfo);
  68                    error_reporting($old_error);
  69                }
  70                $message .= ': ' . $userinfo;
  71            }
  72        } elseif (is_object($message) &&
  73                  is_callable(array($message, 'getMessage'))) {
  74            $message = $message->getMessage();
  75        }
  76
  77        $app = isset($GLOBALS['registry']) ? $GLOBALS['registry']->getApp() : 'horde';
  78        $message = '[' . $app . '] ' . $message . ' [pid ' . getmypid() . ' on line ' . $line . ' of "' . $file . '"]';
  79
  80        /* Make sure to log in the system's locale and timezone. */
  81        $locale = setlocale(LC_TIME, 0);
  82        setlocale(LC_TIME, 'C');
  83        $tz = getenv('TZ');
  84        @putenv('TZ');
  85
  86        $logger->log($message, $priority);
  87
  88        /* Restore original locale and timezone. */
  89        setlocale(LC_TIME, $locale);
  90        if ($tz) {
  91            @putenv('TZ=' . $tz);
  92        }
  93
  94        return true;
  95    }
  96
  97    /**
  98     * Get an instantiated instance of the configured logger, if enabled.
  99     * New as of Horde 3.2: getLogger() will fatally exit if a Log object can
 100     * not be instantiated - there is no need to check the return for a
 101     * PEAR_Error anymore.
 102     *
 103     * @return mixed  Log object on success, false if disabled.
 104     */
 105    function &getLogger()
 106    {
 107        global $conf;
 108        static $logger;
 109
 110        if (empty($conf['log']['enabled'])) {
 111            $ret = false;
 112            return $ret;
 113        }
 114
 115        if (isset($logger)) {
 116            return $logger;
 117        }
 118
 119        // Try to make sure that we can log messages somehow.
 120        if (empty($conf['log']) ||
 121            empty($conf['log']['type']) ||
 122            empty($conf['log']['name']) ||
 123            empty($conf['log']['ident']) ||
 124            !isset($conf['log']['params'])) {
 125            Horde::fatal(PEAR::raiseError('Horde is not correctly configured to log error messages. You must configure at least a text file log in horde/config/conf.php.'), __FILE__, __LINE__, false);
 126        }
 127
 128        $logger = Log::singleton($conf['log']['type'],
 129                                 $conf['log']['name'],
 130                                 $conf['log']['ident'],
 131                                 $conf['log']['params']);
 132        if (!is_a($logger, 'Log')) {
 133            Horde::fatal(PEAR::raiseError('An error has occurred. Furthermore, Horde encountered an error attempting to log this error. Please check your Horde logging configuration in horde/config/conf.php.'), __FILE__, __LINE__, false);
 134        }
 135
 136        return $logger;
 137    }
 138
 139    /**
 140     * Destroys any existing session on login and make sure to use a new
 141     * session ID, to avoid session fixation issues. Should be called before
 142     * checking a login.
 143     */
 144    function getCleanSession()
 145    {
 146        // Make sure to force a completely new session ID and clear all
 147        // session data.
 148        if (version_compare(PHP_VERSION, '4.3.3') !== -1) {
 149            session_regenerate_id(true);
 150            session_unset();
 151        } else {
 152            $old_error = error_reporting(0);
 153            session_destroy();
 154            error_reporting($old_error);
 155
 156            if (Util::extensionExists('posix')) {
 157                $new_session_id = md5(microtime() . posix_getpid());
 158            } else {
 159                $new_session_id = md5(uniqid(mt_rand(), true));
 160            }
 161            session_id($new_session_id);
 162
 163            // Restart the session, including setting up the session handler.
 164            Horde::setupSessionHandler();
 165
 166            error_reporting(0);
 167            session_start();
 168            error_reporting($old_error);
 169        }
 170
 171        /* Reset cookie timeouts, if necessary. */
 172        if (!empty($GLOBALS['conf']['session']['timeout'])) {
 173            $app = $GLOBALS['registry']->getApp();
 174            if (Secret::clearKey($app)) {
 175                Secret::setKey($app);
 176            }
 177            Secret::setKey('auth');
 178        }
 179    }
 180
 181    /**
 182     * Aborts with a fatal error, displaying debug information to the user.
 183     *
 184     * @param mixed $error   A PEAR_Error object with debug information or an
 185     *                       error message.
 186     * @param integer $file  The file in which the error occured.
 187     * @param integer $line  The line on which the error occured.
 188     * @param boolean $log   Log this message via Horde::logMessage()?
 189     */
 190    function fatal($error, $file, $line, $log = true)
 191    {
 192        include_once 'Horde/Auth.php';
 193        include_once 'Horde/CLI.php';
 194
 195        $admin = class_exists('Auth') && Auth::isAdmin();
 196        $cli = class_exists('Horde_CLI') && Horde_CLI::runningFromCLI();
 197
 198        $errortext = '<h1>' . _("A fatal error has occurred") . '</h1>';
 199        if (is_a($error, 'PEAR_Error')) {
 200            $info = array_merge(array('file' => 'conf.php', 'variable' => '$conf'),
 201                                array($error->getUserInfo()));
 202
 203            switch ($error->getCode()) {
 204            case HORDE_ERROR_DRIVER_CONFIG_MISSING:
 205                $message = sprintf(_("No configuration information specified for %s."), $info['name']) . '<br />' .
 206                    sprintf(_("The file %s should contain some %s settings."),
 207                            $GLOBALS['registry']->get('fileroot') . '/config/' . $info['file'],
 208                            sprintf("%s['%s']['params']", $info['variable'], $info['driver']));
 209                break;
 210
 211            case HORDE_ERROR_DRIVER_CONFIG:
 212                $message = sprintf(_("Required \"%s\" not specified in %s configuration."), $info['field'], $info['name']) . '<br />' .
 213                    sprintf(_("The file %s should contain a %s setting."),
 214                            $GLOBALS['registry']->get('fileroot') . '/config/' . $info['file'],
 215                            sprintf("%s['%s']['params']['%s']", $info['variable'], $info['driver'], $info['field']));
 216                break;
 217
 218            default:
 219                $message = $error->getMessage();
 220                break;
 221            }
 222
 223            $errortext .= '<h3>' . htmlspecialchars($message) . '</h3>';
 224        } elseif (is_object($error) && method_exists($error, 'getMessage')) {
 225            $errortext .= '<h3>' . htmlspecialchars($error->getMessage()) . '</h3>';
 226        } elseif (is_string($error)) {
 227            $errortext .= '<h3>' . htmlspecialchars($error) . '</h3>';
 228        }
 229
 230        if ($admin) {
 231            $errortext .= '<p><code>' . sprintf(_("[line %d of %s]"), $line, $file) . '</code></p>';
 232            if (is_object($error)) {
 233                $errortext .= '<h3>' . _("Details:") . '</h3>';
 234                $errortext .= '<h4>' . _("The full error message is logged in Horde's log file, and is shown below only to administrators. Non-administrative users will not see error details.") . '</h4>';
 235                if (extension_loaded('xdebug')) {
 236                    $errortext .= '<br />' . print_r($error, true);
 237                } else {
 238                    $errortext .= '<p><pre>' . htmlspecialchars(print_r($error, true)) . '</pre></p>';
 239                }
 240            }
 241        } elseif ($log) {
 242            $errortext .= '<h3>' . _("Details have been logged for the administrator.") . '</h3>';
 243        }
 244
 245        // Log the error via Horde::logMessage() if requested.
 246        if ($log) {
 247            Horde::logMessage($error, $file, $line, PEAR_LOG_EMERG);
 248        }
 249
 250        if ($cli) {
 251            echo strip_tags(str_replace(array('<br />', '<p>', '</p>', '<h1>', '</h1>', '<h3>', '</h3>'), "\n", $errortext));
 252        } else {
 253            echo <<< HTML
 254<html>
 255<head><title>Horde :: Fatal Error</title></head>
 256<body style="background:#fff; color:#000">$errortext</body>
 257</html>
 258HTML;
 259        }
 260        exit(1);
 261    }
 262
 263    /**
 264     * Adds the javascript code to the output (if output has already started)
 265     * or to the list of script files to include via includeScriptFiles().
 266     *
 267     * @param string $file     The full javascript file name.
 268     * @param string $app      The application name. Defaults to the current
 269     *                         application.
 270     * @param boolean $direct  Include the file directly without passing it
 271     *                         through javascript.php
 272     * @param boolean $full    Output a full URL
 273     */
 274    function addScriptFile($file, $app = null, $direct = false, $full = false)
 275    {
 276        $hsf = &Horde_Script_Files::singleton();
 277        $hsf->add($file, $app, $direct, $full);
 278    }
 279
 280    /**
 281     * Includes javascript files that were needed before any headers were sent.
 282     */
 283    function includeScriptFiles()
 284    {
 285        $hsf = &Horde_Script_Files::singleton();
 286        $hsf->includeFiles();
 287    }
 288
 289    /**
 290     * Provide a list of script files to be included in the current page.
 291     *
 292     * @since Horde 3.2
 293     *
 294     * @var array
 295     */
 296    function listScriptFiles()
 297    {
 298        $hsf = &Horde_Script_Files::singleton();
 299        return $hsf->listFiles();
 300    }
 301
 302    /**
 303     * Disable auto-loading of the horde.js script.
 304     * Needs to auto-load by default for BC.
 305     *
 306     * @since Horde 3.2
 307     * @todo Remove for Horde 4
 308     */
 309    function disableAutoloadHordeJS()
 310    {
 311        $hsf = &Horde_Script_Files::singleton();
 312        $hsf->disableAutoloadHordeJS();
 313    }
 314
 315    /**
 316     * Get a token for protecting a form.
 317     *
 318     * @since Horde 3.2
 319     */
 320    function getRequestToken($slug)
 321    {
 322        require_once 'Horde/Token.php';
 323        $token = Horde_Token::generateId($slug);
 324        $_SESSION['horde_form_secrets'][$token] = time();
 325        return $token;
 326    }
 327
 328    /**
 329     * Check if a token for a form is valid.
 330     *
 331     * @since Horde 3.2
 332     */
 333    function checkRequestToken($slug, $token)
 334    {
 335        if (empty($_SESSION['horde_form_secrets'][$token])) {
 336            return PEAR::raiseError(_("We cannot verify that this request was really sent by you. It could be a malicious request. If you intended to perform this action, you can retry it now."));
 337        }
 338
 339        if (($_SESSION['horde_form_secrets'][$token] + $GLOBALS['conf']['urls']['token_lifetime'] * 60) < time()) {
 340            return PEAR::raiseError(sprintf(_("This request cannot be completed because the link you followed or the form you submitted was only valid for %s minutes. Please try again now."), $GLOBALS['conf']['urls']['token_lifetime']));
 341        }
 342
 343        return true;
 344    }
 345
 346    /**
 347     * Add a signature + timestamp to a query string and return the signed query
 348     * string.
 349     *
 350     * @since Horde 3.3
 351     *
 352     * @param string $queryString  The query string to sign.
 353     * @param integer $now         The timestamp at which to sign. Leave blank for
 354     *                             generating signatures; specify when testing.
 355     *
 356     * @return string  The signed query string.
 357     */
 358    function signQueryString($queryString, $now = null)
 359    {
 360        if (!isset($GLOBALS['conf']['secret_key'])) {
 361            return $queryString;
 362        }
 363
 364        if (is_null($now)) {
 365            $now = time();
 366        }
 367
 368        $queryString .= '&_t=' . $now . '&_h=';
 369        $hmac = Util::uriB64Encode(Util::hmac($queryString, $GLOBALS['conf']['secret_key'], true));
 370
 371        return $queryString . $hmac;
 372    }
 373
 374    /**
 375     * Verify a signature and timestamp on a query string.
 376     *
 377     * @since Horde 3.3
 378     *
 379     * @param string $data  The signed query string.
 380     * @param integer $now  The current time (can override for testing).
 381     *
 382     * @return boolean  Whether or not the string was valid.
 383     */
 384    function verifySignedQueryString($data, $now = null)
 385    {
 386        if (is_null($now)) {
 387            $now = time();
 388        }
 389
 390        $pos = strrpos($data, '&_h=');
 391        if ($pos === false) {
 392            return false;
 393        }
 394        $pos += 4;
 395
 396        $queryString = substr($data, 0, $pos);
 397        $hmac = substr($data, $pos);
 398
 399        if ($hmac != Util::uriB64Encode(Util::hmac($queryString, $GLOBALS['conf']['secret_key'], true))) {
 400            return false;
 401        }
 402
 403        // String was not tampered with; now validate timestamp
 404        parse_str($queryString, $values);
 405        if ($values['_t'] + $GLOBALS['conf']['urls']['hmac_lifetime'] * 60 < $now) {
 406            return false;
 407        }
 408
 409        return true;
 410    }
 411
 412    /**
 413     * Checks if link should be shown and return the necessary code.
 414     *
 415     * @param string  $type      Type of link to display
 416     * @param string  $app       The name of the current Horde application.
 417     * @param boolean $override  Override Horde settings?
 418     * @param boolean $referrer  Include the current page as the referrer (url=)?
 419     *
 420     * @return string  The HTML to create the link.
 421     */
 422    function getServiceLink($type, $app, $override = false, $referrer = true)
 423    {
 424        if (!Horde::showService($type, $override)) {
 425            return false;
 426        }
 427
 428        switch ($type) {
 429        case 'help':
 430            if ($GLOBALS['browser']->hasFeature('javascript')) {
 431                Horde::addScriptFile('popup.js', 'horde', true);
 432            }
 433            $url = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/help/', true);
 434            return Util::addParameter($url, 'module', $app);
 435
 436        case 'problem':
 437            return Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/problem.php?return_url=' . urlencode(Horde::selfUrl(true, true, true)));
 438
 439        case 'logout':
 440            return Horde::url(Auth::addLogoutParameters($GLOBALS['registry']->get('webroot', 'horde') . '/login.php', AUTH_REASON_LOGOUT));
 441
 442        case 'login':
 443            return Auth::getLoginScreen('', $referrer ? Horde::selfUrl(true) : null);
 444
 445        case 'options':
 446            global $conf;
 447            if (($conf['prefs']['driver'] != '') && ($conf['prefs']['driver'] != 'none')) {
 448                return Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/prefs.php?app=' . $app);
 449            }
 450            break;
 451        }
 452
 453        return false;
 454    }
 455
 456    /**
 457     * @param string $type  The type of link.
 458     * @param boolean $override  Override Horde settings?
 459     *
 460     * @return boolean  True if the link is to be shown.
 461     */
 462    function showService($type, $override = false)
 463    {
 464        global $conf;
 465
 466        if (empty($conf['menu']['links'][$type])) {
 467            return false;
 468        }
 469
 470        switch ($conf['menu']['links'][$type]) {
 471        case 'all':
 472            return true;
 473
 474        case 'never':
 475            return $override;
 476
 477        case 'authenticated':
 478            return $override || (bool)Auth::getAuth();
 479
 480        default:
 481            return $override;
 482        }
 483    }
 484
 485    /**
 486     * Loads global and vhost specific configuration files.
 487     *
 488     * @since Horde 3.2
 489     *
 490     * @param string $config_file      The name of the configuration file.
 491     * @param string|array $var_names  The name(s) of the variable(s) that
 492     *                                 is/are defined in the configuration
 493     *                                 file.
 494     * @param string $app              The application. Defaults to the current
 495     *                                 application.
 496     * @param boolean $show_output     If true, the contents of the requested
 497     *                                 config file are simply output instead of
 498     *                                 loaded into a variable.
 499     *
 500     * @return mixed  The value of $var_names, in a compact()'ed array if
 501     *                $var_names is an array.
 502     */
 503    function loadConfiguration($config_file, $var_names = null, $app = null,
 504                               $show_output = false)
 505    {
 506        global $registry;
 507
 508        if (is_null($app)) {
 509            $app = $registry->getApp();
 510        }
 511        $output = '';
 512
 513        // Load global configuration file.
 514        if ($app == 'horde' && defined('HORDE_BASE')) {
 515            $config_dir = HORDE_BASE . '/config/';
 516        } else {
 517            $config_dir = $registry->get('fileroot', $app) . '/config/';
 518        }
 519
 520        // Track if we've included some version (main or vhosted) of
 521        // the config file.
 522        $was_included = false;
 523
 524        if (file_exists($config_dir . $config_file)) {
 525            ob_start();
 526            // If we are not exporting variables located in the configuration
 527            // file, or we are not capturing the output, then there is no
 528            // need to load the configuration file more than once.
 529            if (is_null($var_names) && !$show_output) {
 530                $success = include_once $config_dir . $config_file;
 531            } else {
 532                $success = include $config_dir . $config_file;
 533            }
 534            $output = ob_get_clean();
 535            if (!empty($output) && !$show_output) {
 536                return PEAR::raiseError(sprintf('Failed to import configuration file "%s": ', $config_dir . $config_file) . strip_tags($output));
 537            }
 538            if (!$success) {
 539                return PEAR::raiseError(sprintf('Failed to import configuration file "%s".', $config_dir . $config_file));
 540            }
 541
 542            $was_included = true;
 543        }
 544
 545        // Load global configuration stanzas in .d directory
 546        $directory = preg_replace('/\.php$/', '.d', $config_dir . $config_file);
 547        if (file_exists($directory) && is_dir($directory)) {
 548            $sub_files = glob("$directory/*.php");
 549            if ($sub_files) {
 550                foreach ($sub_files as $sub_file) {
 551                    ob_start();
 552                    $success = (is_null($var_names) && !$show_output)
 553                        ? include_once $sub_file
 554                        : include $sub_file;
 555                    $output = ob_get_clean();
 556
 557                    if (!empty($output) && !$show_output) {
 558                        return PEAR::raiseError(sprintf('Failed to import configuration file "%s": ', $sub_file) . strip_tags($output));
 559                    }
 560
 561                    if (!$success) {
 562                        return PEAR::raiseError(sprintf('Failed to import configuration file "%s".', $sub_file));
 563                    }
 564                }
 565            }
 566        }
 567
 568        // Load vhost configuration file.
 569        if (!empty($conf['vhosts']) || !empty($GLOBALS['conf']['vhosts'])) {
 570            $server_name = isset($GLOBALS['conf']) ? $GLOBALS['conf']['server']['name'] : $conf['server']['name'];
 571            $config_file = substr($config_file, 0, -4) . '-' . $server_name . '.php';
 572            if (file_exists($config_dir . $config_file)) {
 573                ob_start();
 574                // See above.
 575                if (is_null($var_names) && !$show_output) {
 576                    $success = include_once $config_dir . $config_file;
 577                } else {
 578                    $success = include $config_dir . $config_file;
 579                }
 580                $output = ob_get_clean();
 581                if (!empty($output) && !$show_output) {
 582                    return PEAR::raiseError(sprintf('Failed to import configuration file "%s": ', $config_dir . $config_file) . strip_tags($output));
 583                }
 584                if (!$success) {
 585                    return PEAR::raiseError(sprintf('Failed to import configuration file "%s".', $config_dir . $config_file));
 586                }
 587
 588                $was_included = true;
 589            }
 590        }
 591
 592        // Return an error if neither main or vhosted versions of the
 593        // config file existed.
 594        if (!$was_included) {
 595            return PEAR::raiseError(sprintf('Failed to import configuration file "%s".', $config_dir . $config_file));
 596        }
 597
 598        if ($show_output) {
 599            echo $output;
 600        }
 601
 602        if (is_null($var_names)) {
 603            return;
 604        }
 605        if (is_array($var_names)) {
 606            return compact($var_names);
 607        } else if (isset($$var_names)) {
 608            return $$var_names;
 609        } else {
 610            return array();
 611        }
 612    }
 613
 614    /**
 615     * Returns the driver parameters for the specified backend.
 616     *
 617     * @param mixed $backend  The backend system (e.g. 'prefs', 'categories',
 618     *                        'contacts') being used.
 619     *                        The used configuration array will be
 620     *                        $conf[$backend]. If an array gets passed, it will
 621     *                        be $conf[$key1][$key2].
 622     * @param string $type    The type of driver.
 623     *
 624     * @return array  The connection parameters.
 625     */
 626    function getDriverConfig($backend, $type = 'sql')
 627    {
 628        global $conf;
 629
 630        $c = null;
 631        if (is_array($backend)) {
 632            require_once 'Horde/Array.php';
 633            $c = Horde_Array::getElement($conf, $backend);
 634        } elseif (isset($conf[$backend])) {
 635            $c = $conf[$backend];
 636        }
 637        if (!is_null($c) && isset($c['params'])) {
 638            $c['params']['umask'] = $conf['umask'];
 639            if (isset($conf[$type])) {
 640                return array_merge($conf[$type], $c['params']);
 641            } else {
 642                return $c['params'];
 643            }
 644        }
 645
 646        return isset($conf[$type]) ? $conf[$type] : array();
 647    }
 648
 649
 650    /**
 651     * Returns the VFS driver parameters for the specified backend.
 652     *
 653     * @param string $name  The VFS system name (e.g. 'images', 'documents')
 654     *                      being used.
 655     *
 656     * @return array  A hash with the VFS parameters; the VFS driver in 'type'
 657     *                and the connection parameters in 'params'.
 658     */
 659    function getVFSConfig($name)
 660    {
 661        global $conf;
 662
 663        if (!isset($conf[$name]['type'])) {
 664            return PEAR::raiseError(_("You must configure a VFS backend."));
 665        }
 666
 667        if ($conf[$name]['type'] == 'horde') {
 668            $vfs = $conf['vfs'];
 669        } else {
 670            $vfs = $conf[$name];
 671        }
 672
 673        if ($vfs['type'] == 'sql') {
 674            $vfs['params'] = Horde::getDriverConfig($name, 'sql');
 675        }
 676
 677        return $vfs;
 678    }
 679
 680    /**
 681     * Return the driver and parameters for the current mailer configuration.
 682     *
 683     * @since Horde 3.3.5
 684     *
 685     * @return array  Array with driver name and parameter hash.
 686     */
 687    function getMailerConfig()
 688    {
 689        $mail_driver = $GLOBALS['conf']['mailer']['type'];
 690        $mail_params = $GLOBALS['conf']['mailer']['params'];
 691        if ($mail_driver == 'smtp' && $mail_params['auth'] &&
 692            empty($mail_params['username'])) {
 693            $mail_params['username'] = Auth::getAuth();
 694            $mail_params['password'] = Auth::getCredential('password');
 695        }
 696
 697        return array($mail_driver, $mail_params);
 698    }
 699
 700    /**
 701     * Checks if all necessary parameters for a driver configuration
 702     * are set and throws a fatal error with a detailed explanation
 703     * how to fix this, if something is missing.
 704     *
 705     * @param array $params     The configuration array with all parameters.
 706     * @param string $driver    The key name (in the configuration array) of
 707     *                          the driver.
 708     * @param array $fields     An array with mandatory parameter names for
 709     *                          this driver.
 710     * @param string $name      The clear text name of the driver. If not
 711     *                          specified, the application name will be used.
 712     * @param string $file      The configuration file that should contain
 713     *                          these settings.
 714     * @param string $variable  The name of the configuration variable.
 715     */
 716    function assertDriverConfig($params, $driver, $fields, $name = null,
 717                                $file = 'conf.php', $variable = '$conf')
 718    {
 719        global $registry;
 720
 721        // Don't generate a fatal error if we fail during or before
 722        // Registry instantiation.
 723        if (is_null($name)) {
 724            $name = isset($registry) ? $registry->getApp() : '[unknown]';
 725        }
 726        $fileroot = isset($registry) ? $registry->get('fileroot') : '';
 727
 728        if (!is_array($params) || !count($params)) {
 729            Horde::fatal(PEAR::raiseError(
 730                sprintf(_("No configuration information specified for %s."), $name) . "\n\n" .
 731                sprintf(_("The file %s should contain some %s settings."),
 732                    $fileroot . '/config/' . $file,
 733                    sprintf("%s['%s']['params']", $variable, $driver))),
 734                __FILE__, __LINE__);
 735        }
 736
 737        foreach ($fields as $field) {
 738            if (!isset($params[$field])) {
 739                Horde::fatal(PEAR::raiseError(
 740                    sprintf(_("Required \"%s\" not specified in %s configuration."), $field, $name) . "\n\n" .
 741                    sprintf(_("The file %s should contain a %s setting."),
 742                        $fileroot . '/config/' . $file,
 743                        sprintf("%s['%s']['params']['%s']", $variable, $driver, $field))),
 744                    __FILE__, __LINE__);
 745            }
 746        }
 747    }
 748
 749    /**
 750     * Returns a session-id-ified version of $uri.
 751     * If a full URL is requested, all parameter separators get converted to
 752     * "&", otherwise to "&amp;".
 753     *
 754     * @param string $uri              The URI to be modified.
 755     * @param boolean $full            Generate a full (http://server/path/)
 756     *                                 URL.
 757     * @param integer $append_session  0 = only if needed, 1 = always, -1 =
 758     *                                 never.
 759     * @param boolean $force_ssl       Ignore $conf['use_ssl'] and force
 760     *                                 creation of a SSL URL?
 761     *
 762     * @return string  The URL with the session id appended (if needed).
 763     */
 764    function url($uri, $full = false, $append_session = 0, $force_ssl = false)
 765    {
 766        if ($force_ssl) {
 767            $full = true;
 768        }
 769
 770        if ($full) {
 771            global $conf, $registry, $browser;
 772
 773            /* Store connection parameters in local variables. */
 774            $server_name = $conf['server']['name'];
 775            $server_port = $conf['server']['port'];
 776
 777            $protocol = 'http';
 778            if ($conf['use_ssl'] == 1) {
 779                $protocol = 'https';
 780            } elseif ($conf['use_ssl'] == 2 &&
 781                      $browser->usingSSLConnection()) {
 782                $protocol = 'https';
 783            } elseif ($conf['use_ssl'] == 3) {
 784                $server_port = '';
 785                if ($force_ssl) {
 786                    $protocol = 'https';
 787                }
 788            }
 789
 790            /* If using non-standard ports, add the port to the URL. */
 791            if (!empty($server_port) &&
 792                ((($protocol == 'http') && ($server_port != 80)) ||
 793                 (($protocol == 'https') && ($server_port != 443)))) {
 794                $server_name .= ':' . $server_port;
 795            }
 796
 797            /* Store the webroot in a local variable. */
 798            $webroot = $registry->get('webroot');
 799
 800            $url = $protocol . '://' . $server_name;
 801            if (preg_match('|^([\w+-]{1,20})://|', $webroot)) {
 802                /* Don't prepend to webroot if it's already absolute. */
 803                $url = '';
 804            }
 805
 806            if (substr($uri, 0, 1) != '/') {
 807                /* Simple case for http:// absolute webroots. */
 808                if (preg_match('|^([\w+-]{1,20})://|', $uri)) {
 809                    $url = $uri;
 810                } elseif (substr($webroot, -1) == '/') {
 811                    $url .= $webroot . $uri;
 812                } else {
 813                    $url .= $webroot . '/' . $uri;
 814                }
 815            } else {
 816                $url .= $uri;
 817            }
 818        } else {
 819            $url = $uri;
 820
 821            if (!empty($_SERVER['HTTP_HOST'])) {
 822                // Don't generate absolute URLs if we don't have to.
 823                if (preg_match('|^([\w+-]{1,20}://' . preg_quote($_SERVER['HTTP_HOST'], '|') . ')/|', $url, $matches)) {
 824                    $url = substr($url, strlen($matches[1]));
 825                }
 826            }
 827        }
 828
 829        if (empty($GLOBALS['conf']['session']['use_only_cookies']) &&
 830            (($append_session == 1) ||
 831             (($append_session == 0) &&
 832              !isset($_COOKIE[session_name()])))) {
 833            $url = Util::addParameter($url, session_name(), session_id());
 834        }
 835
 836        if ($full) {
 837            /* We need to run the replace twice, because we only catch every
 838             * second match. */
 839            return preg_replace(array('/(=?.*?)&amp;(.*?=)/',
 840                                      '/(=?.*?)&amp;(.*?=)/'),
 841                                '$1&$2', $url);
 842        } elseif (preg_match('/=.*&amp;.*=/', $url)) {
 843            return $url;
 844        } else {
 845            return htmlentities($url);
 846        }
 847    }
 848
 849    /**
 850     * Returns a session-id-ified version of $uri, using the current
 851     * application's webroot setting.
 852     *
 853     * @param string $uri              The URI to be modified.
 854     * @param boolean $full            Generate a full (http://server/path/)
 855     *                                 URL.
 856     * @param integer $append_session  0 = only if needed, 1 = always, -1 =
 857     *                                 never.
 858     *
 859     * @return string  The url with the session id appended.
 860     */
 861    function applicationUrl($uri, $full = false, $append_session = 0)
 862    {
 863        if ($full) {
 864            return Horde::url($uri, $full, $append_session);
 865        }
 866
 867        if (substr($uri, 0, 1) != '/') {
 868            $webroot = $GLOBALS['registry']->get('webroot');
 869            if (substr($webroot, -1) != '/') {
 870                $webroot .= '/';
 871            }
 872            $uri = $webroot . $uri;
 873        }
 874
 875        return Horde::url($uri, $full, $append_session);
 876    }
 877
 878    /**
 879     * Returns an external link passed through the dereferrer to strip session
 880     * IDs from the referrer.
 881     *
 882     * @param string $url   The external URL to link to.
 883     * @param boolean $tag  If true, a complete <a> tag is returned, only the
 884     *                      url otherwise.
 885     *
 886     * @return string  The link to the dereferrer script.
 887     */
 888    function externalUrl($url, $tag = false)
 889    {
 890        if (!isset($_GET[session_name()]) ||
 891            String::substr($url, 0, 1) == '#' ||
 892            String::substr($url, 0, 7) == 'mailto:') {
 893            $ext = $url;
 894        } else {
 895            $ext = Horde::url($GLOBALS['registry']->get('webroot', 'horde') .
 896                              '/services/go.php', true, -1);
 897
 898            /* We must make sure there are no &amp's in the URL. */
 899            $url = preg_replace(array('/(=?.*?)&amp;(.*?=)/', '/(=?.*?)&amp;(.*?=)/'), '$1&$2', $url);
 900            $ext .= '?' . Horde::signQueryString('url=' . urlencode($url));
 901        }
 902
 903        if ($tag) {
 904            $ext = Horde::link($ext, $url, '', '_blank');
 905        }
 906
 907        return $ext;
 908    }
 909
 910    /**
 911     * Returns a URL to be used for downloading, that takes into account any
 912     * special browser quirks (i.e. IE's broken filename handling).
 913     *
 914     * @param string $filename  The filename of the download data.
 915     * @param array $params     Any additional parameters needed.
 916     * @param string $url       The URL to alter. If none passed in, will use
 917     *                          the file 'view.php' located in the current
 918     *                          module's base directory.
 919     *
 920     * @return string  The download URL.
 921     */
 922    function downloadUrl($filename, $params = array(), $url = null)
 923    {
 924        global $browser;
 925
 926        $horde_url = false;
 927
 928        if (is_null($url)) {
 929            global $registry;
 930            $url = Util::addParameter(Horde::url($registry->get('webroot', 'horde') . '/services/download/'), 'module', $registry->getApp());
 931            $horde_url = true;
 932        }
 933
 934        /* Add parameters. */
 935        if (!is_null($params)) {
 936            $url = Util::addParameter($url, $params);
 937        }
 938
 939        /* If we are using the default Horde download link, add the
 940         * filename to the end of the URL. Although not necessary for
 941         * many browsers, this should allow every browser to download
 942         * correctly. */
 943        if ($horde_url) {
 944            $url = Util::addParameter($url, 'fn', '/' . rawurlencode($filename));
 945        } elseif ($browser->hasQuirk('break_disposition_filename')) {
 946            /* Some browsers will only obtain the filename correctly
 947             * if the extension is the last argument in the query
 948             * string and rest of the filename appears in the
 949             * PATH_INFO element. */
 950            $filename = rawurlencode($filename);
 951
 952            /* Get the webserver ID. */
 953            $server = Horde::webServerID();
 954
 955            /* Get the name and extension of the file.  Apache 2 does
 956             * NOT support PATH_INFO information being passed to the
 957             * PHP module by default, so disable that
 958             * functionality. */
 959            if (($server != 'apache2')) {
 960                if (($pos = strrpos($filename, '.'))) {
 961                    $name = '/' . preg_replace('/\./', '%2E', substr($filename, 0, $pos));
 962                    $ext = substr($filename, $pos);
 963                } else {
 964                    $name = '/' . $filename;
 965                    $ext = '';
 966                }
 967
 968                /* Enter the PATH_INFO information. */
 969                if (($pos = strpos($url, '?'))) {
 970                    $url = substr($url, 0, $pos) . $name . substr($url, $pos);
 971                } else {
 972                    $url .= $name;
 973                }
 974            }
 975
 976            /* Append the extension, if it exists. */
 977            if (($server == 'apache2') || !empty($ext)) {
 978                $url = Util::addParameter($url, 'fn_ext', '/' . $filename);
 979            }
 980        }
 981
 982        return $url;
 983    }
 984
 985    /**
 986     * Returns an anchor tag with the relevant parameters
 987     *
 988     * @param string $url        The full URL to be linked to.
 989     * @param string $title      The link title/description.
 990     * @param string $class      The CSS class of the link.
 991     * @param string $target     The window target to point to.
 992     * @param string $onclick    JavaScript action for the 'onclick' event.
 993     * @param string $title2     The link title (tooltip) (deprecated - just
 994     *                           use $title).
 995     * @param string $accesskey  The access key to use.
 996     * @param array $attributes  Any other name/value pairs to add to the <a>
 997     *                           tag.
 998     * @param boolean $escape    Whether to escape special characters in the
 999     *                           title attribute.
1000     *
1001     * @return string  The full <a href> tag.
1002     */
1003    function link($url = '', $title = '', $class = '', $target = '',
1004                  $onclick = '', $title2 = '', $accesskey = '',
1005                  $attributes = array(), $escape = true)
1006    {
1007        static $charset;
1008        if (!isset($charset)) {
1009            $charset = NLS::getCharset();
1010        }
1011
1012        if (!empty($title2)) {
1013            $title = $title2;
1014        }
1015
1016        $ret = '<a';
1017        if (!empty($url)) {
1018            $ret .= " href=\"$url\"";
1019        }
1020        if (!empty($onclick)) {
1021            $ret .= " onclick=\"$onclick\"";
1022        }
1023        if (!empty($class)) {
1024            $ret .= " class=\"$class\"";
1025        }
1026        if (!empty($target)) {
1027            $ret .= " target=\"$target\"";
1028        }
1029        if (!empty($title)) {
1030            if ($escape) {
1031                $old_error = error_reporting(0);
1032                $title = str_replace(
1033                    array("\r", "\n"), '',
1034                    htmlspecialchars(
1035                        nl2br(htmlspecialchars($title, ENT_QUOTES, $charset)),
1036                        ENT_QUOTES, $charset));
1037                error_reporting($old_error);
1038            }
1039            $ret .= ' title="' . $title . '"';
1040        }
1041        if (!empty($accesskey)) {
1042            $ret .= ' accesskey="' . htmlspecialchars($accesskey) . '"';
1043        }
1044
1045        foreach ($attributes as $name => $value) {
1046            $ret .= ' ' . htmlspecialchars($name) . '="'
1047                . htmlspecialchars($value) . '"';
1048        }
1049
1050        return "$ret>";
1051    }
1052
1053    /**
1054     * Uses DOM Tooltips to display the 'title' attribute for
1055     * Horde::link() calls.
1056     *
1057     * @param string $url        The full URL to be linked to
1058     * @param string $status     The JavaScript mouse-over string
1059     * @param string $class      The CSS class of the link
1060     * @param string $target     The window target to point to.
1061     * @param string $onclick    JavaScript action for the 'onclick' event.
1062     * @param string $title      The link title (tooltip).
1063     * @param string $accesskey  The access key to use.
1064     * @param array  $attributes Any other name/value pairs to add to the <a>
1065     *                           tag.
1066     *
1067     * @return string  The full <a href> tag.
1068     */
1069    function linkTooltip($url, $status = '', $class = '', $target = '',
1070                         $onclick = '', $title = '', $accesskey = '',
1071                         $attributes = array())
1072    {
1073        static $charset;
1074        if (!isset($charset)) {
1075            $charset = NLS::getCharset();
1076        }
1077
1078        if (!empty($title)) {
1079            $old_error = error_reporting(0);
1080            $title = '&lt;pre&gt;' . preg_replace(array('/\n/', '/((?<!<br)\s{1,}(?<!\/>))/em', '/<br \/><br \/>/', '/<br \/>/'), array('', 'str_repeat("&nbsp;", strlen("$1"))', '&lt;br /&gt; &lt;br /&gt;', '&lt;br /&gt;'), nl2br(htmlspecialchars(htmlspecialchars($title, ENT_QUOTES, $charset), ENT_QUOTES, $charset))) . '&lt;/pre&gt;';
1081            error_reporting($old_error);
1082        }
1083        return Horde::link($url, $title, $class, $target, $onclick, null, $accesskey, $attributes, false);
1084    }
1085
1086    /**
1087     * Returns an anchor sequence with the relevant parameters for a widget
1088     * with accesskey and text.
1089     *
1090     * @access public
1091     *
1092     * @param string  $url      The full URL to be linked to.
1093     * @param string  $title    The link title/description.
1094     * @param string  $class    The CSS class of the link
1095     * @param string  $target   The window target to point to.
1096     * @param string  $onclick  JavaScript action for the 'onclick' event.
1097     * @param string  $title2   The link title (tooltip) (deprecated - just use
1098     *                          $title).
1099     * @param boolean $nocheck  Don't check if the access key already has been
1100     *                          used. Defaults to false (= check).
1101     *
1102     * @return string  The full <a href>Title</a> sequence.
1103     */
1104    function widget($url, $title = '', $class = 'widget', $target = '',
1105                    $onclick = '', $title2 = '', $nocheck = false)
1106    {
1107        if (!empty($title2)) {
1108            $title = $title2;
1109        }
1110
1111        $ak = Horde::getAccessKey($title, $nocheck);
1112
1113        return Horde::link($url, '', $class, $target, $onclick, '', $ak) . Horde::highlightAccessKey($title, $ak) . '</a>';
1114    }
1115
1116    /**
1117     * Returns a session-id-ified version of $SCRIPT_NAME resp. $PHP_SELF.
1118     *
1119     * @param boolean $script_params Include script parameters like
1120     *                               QUERY_STRING and PATH_INFO?
1121     * @param boolean $nocache       Include a nocache parameter in the URL?
1122     * @param boolean $full          Return a full URL?
1123     * @param boolean $force_ssl     Ignore $conf['use_ssl'] and force creation
1124     *                               of a SSL URL?
1125     *
1126     * @return string  The requested URI.
1127     */
1128    function selfUrl($script_params = false, $nocache = true, $full = false,
1129                     $force_ssl = false)
1130    {
1131        if (!strncmp(PHP_SAPI, 'cgi', 3)) {
1132            // When using CGI PHP, SCRIPT_NAME may contain the path to
1133            // the PHP binary instead of the script being run; use
1134            // PHP_SELF instead.
1135            $url = $_SERVER['PHP_SELF'];
1136        } else {
1137            $url = isset($_SERVER['SCRIPT_NAME']) ?
1138                $_SERVER['SCRIPT_NAME'] :
1139                $_SERVER['PHP_SELF'];
1140        }
1141
1142        if ($script_params) {
1143            if (!empty($_SERVER['PATH_INFO'])) {
1144                $url .= $_SERVER['PATH_INFO'];
1145            }
1146            if (!empty($_SERVER['QUERY_STRING'])) {
1147                $url .= '?' . $_SERVER['QUERY_STRING'];
1148            }
1149        }
1150
1151        $url = Horde::url($url, $full, 0, $force_ssl);
1152
1153        if ($nocache) {
1154            return Util::nocacheUrl($url, !$full);
1155        } else {
1156            return $url;
1157        }
1158    }
1159
1160    /**
1161     * Constructs a correctly-pathed link to an image.
1162     *
1163     * @param string $src   The image file.
1164     * @param string $alt   Text describing the image.
1165     * @param mixed  $attr  Any additional attributes for the image tag. Can be
1166     *                      a pre-built string or an array of key/value pairs
1167     *                      that will be assembled and html-encoded.
1168     * @param string $dir   The root graphics directory.
1169     *
1170     * @return string  The full image tag.
1171     */
1172    function img($src, $alt = '', $attr = '', $dir = null)
1173    {
1174        static $charset;
1175        if (!isset($charset)) {
1176            $charset = NLS::getCharset();
1177        }
1178
1179        /* If browser does not support images, simply return the ALT text. */
1180        if (!$GLOBALS['browser']->hasFeature('images')) {
1181            $old_error = error_reporting(0);
1182            $res = htmlspecialchars($alt, ENT_COMPAT, $charset);
1183            error_reporting($old_error);
1184            return $res;
1185        }
1186
1187        /* If no directory has been specified, get it from the registry. */
1188        if ($dir === null) {
1189            global $registry;
1190            $dir = $registry->getImageDir();
1191        }
1192
1193        /* If a directory has been provided, prepend it to the image source. */
1194        if (!empty($dir)) {
1195            $src = $dir . '/' . $src;
1196        }
1197
1198        /* Build all of the tag attributes. */
1199        $attributes = array('src' => $src,
1200                            'alt' => $alt);
1201        if (is_array($attr)) {
1202            $attributes = array_merge($attributes, $attr);
1203        }
1204        if (empty($attributes['title'])) {
1205            $attributes['title'] = '';
1206        }
1207
1208        $img = '<img';
1209        $old_error = error_reporting(0);
1210        foreach ($attributes as $attribute => $value) {
1211            $img .= ' ' . $attribute . '="' . ($attribute == 'src' ? $value : htmlspecialchars($value, ENT_COMPAT, $charset)) . '"';
1212        }
1213        error_reporting($old_error);
1214
1215        /* If the user supplied a pre-built string of attributes, add that. */
1216        if (is_string($attr) && !empty($attr)) {
1217            $img .= ' ' . $attr;
1218        }
1219
1220        /* Return the closed image tag. */
1221        return $img . ' />';
1222    }
1223
1224    /**
1225     * Determines the location of the system temporary directory. If a specific
1226     * setting cannot be found, it defaults to /tmp.
1227     *
1228     * @return string  A directory name that can be used for temp files.
1229     *                 Returns false if one could not be found.
1230     */
1231    function getTempDir()
1232    {
1233        global $conf;
1234
1235        /* If one has been specifically set, then use that */
1236        if (!empty($conf['tmpdir'])) {
1237            $tmp = $conf['tmpdir'];
1238        }
1239
1240        /* Next, try Util::getTempDir(). */
1241        if (empty($tmp)) {
1242            $tmp = Util::getTempDir();
1243        }
1244
1245        /* If it is still empty, we have failed, so return false;
1246         * otherwise return the directory determined. */
1247        return empty($tmp) ? false : $tmp;
1248    }
1249
1250    /**
1251     * Creates a temporary filename for the lifetime of the script, and
1252     * (optionally) registers it to be deleted at request shutdown.
1253     *
1254     * @param string $prefix   Prefix to make the temporary name more
1255     *                         recognizable.
1256     * @param boolean $delete  Delete the file at the end of the request?
1257     * @param string $dir      Directory to create the temporary file in.
1258     * @param boolean $secure  If deleting file, should we securely delete the
1259     *                         file?
1260     *
1261     * @return string   Returns the full path-name to the temporary file or
1262     *                  false if a temporary file could not be created.
1263     */
1264    function getTempFile($prefix = 'Horde', $delete = true, $dir = '',
1265                         $secure = false)
1266    {
1267        if (empty($dir) || !is_dir($dir)) {
1268            $dir = Horde::getTempDir();
1269        }
1270
1271        return Util::getTempFile($prefix, $delete, $dir, $secure);
1272    }
1273
1274    /**
1275     * Starts output compression, if requested.
1276     */
1277    function compressOutput()
1278    {
1279        static $started;
1280
1281        if (isset($started)) {
1282            return;
1283        }
1284
1285        /* Compress output if requested and possible. */
1286        if ($GLOBALS['conf']['compress_pages'] &&
1287            !$GLOBALS['browser']->hasQuirk('buggy_compression') &&
1288            !(bool)ini_get('zlib.output_compression') &&
1289            !(bool)ini_get('zend_accelerator.compress_all') &&
1290            ini_get('output_handler') != 'ob_gzhandler') {
1291            if (ob_get_level()) {
1292                ob_end_clean();
1293            }
1294            ob_start('ob_gzhandler');
1295        }
1296
1297        $started = true;
1298    }
1299
1300    /**
1301     * Determines if output compression can be used.
1302     *
1303     * @return boolean  True if output compression can be used, false if not.
1304     */
1305    function allowOutputCompression()
1306    {
1307        require_once 'Horde/Browser.php';
1308        $browser = &Browser::singleton();
1309
1310        /* Turn off compression for buggy browsers. */
1311        if ($browser->hasQuirk('buggy_compression')) {
1312            return false;
1313        }
1314
1315        return (ini_get('zlib.output_compression') == '' &&
1316                ini_get('zend_accelerator.compress_all') == '' &&
1317                ini_get('output_handler') != 'ob_gzhandler');
1318    }
1319
1320    /**
1321     * Returns the Web server being used.
1322     * PHP string list built from the PHP 'configure' script.
1323     *
1324     * @return string  A web server identification string.
1325     * <pre>
1326     * 'aolserver' = AOL Server
1327     * 'apache1'   = Apache 1.x
1328     * 'apache2'   = Apache 2.x
1329     * 'caudium'   = Caudium
1330     * 'cgi'       = Unknown server - PHP built as CGI program
1331     * 'cli'       = Command Line Interface build
1332     * 'embed'     = Embedded PHP
1333     * 'isapi'     = Zeus ISAPI
1334     * 'milter'    = Milter
1335     * 'nsapi'     = NSAPI
1336     * 'phttpd'    = PHTTPD
1337     * 'pi3web'    = Pi3Web
1338     * 'roxen'     = Roxen/Pike
1339     * 'servlet'   = Servlet
1340     * 'thttpd'    = thttpd
1341     * 'tux'       = Tux
1342     * 'webjames'  = Webjames
1343     * </pre>
1344     */
1345    function webServerID()
1346    {
1347        if (PHP_SAPI == 'apache') {
1348            return 'apache1';
1349        } elseif ((PHP_SAPI == 'apache2filter') ||
1350                  (PHP_SAPI == 'apache2handler')) {
1351            return 'apache2';
1352        } else {
1353            return PHP_SAPI;
1354        }
1355    }
1356
1357    /**
1358     * Returns the <link> tags for the CSS stylesheets.
1359     *
1360     * @param string|array $app  The Horde application(s).
1361     * @param mixed $theme       The theme to use; specify an empty value to
1362     *                           retrieve the theme from user preferences, and
1363     *                           false for no theme.
1364     * @param boolean $inherit   Inherit Horde-wide CSS?
1365     *
1366     * @return string  <link> tags for CSS stylesheets.
1367     */
1368    function stylesheetLink($apps = null, $theme = '', $inherit = true)
1369    {
1370        $css = Horde::getStylesheets($apps, $theme, $inherit);
1371
1372        $html = '';
1373        foreach ($css as $css_link) {
1374            $html .= '<link href="' . $css_link['u'] . '" rel="stylesheet" type="text/css" />' . "\n";
1375        }
1376
1377        return $html;
1378    }
1379
1380    /**
1381     * Return the list of base stylesheets to display.
1382     *
1383     * @since Horde 3.2
1384     *
1385     * @param string|array $app  The Horde application(s).
1386     * @param mixed $theme       The theme to use; specify an empty value to
1387     *…

Large files files are truncated, but you can click here to view the full file