PageRenderTime 68ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/horde-3.3.13/lib/Horde.php

#
PHP | 2033 lines | 1229 code | 190 blank | 614 comment | 238 complexity | 93ded3be32a278182f511ce2f36c5b8f MD5 | raw file
Possible License(s): LGPL-2.0
  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. /** Log */
  13. include_once 'Log.php';
  14. /** Util */
  15. include_once 'Horde/Util.php';
  16. /**
  17. * The Horde:: class provides the functionality shared by all Horde
  18. * applications.
  19. *
  20. * @author Chuck Hagenbuch <chuck@horde.org>
  21. * @author Jon Parise <jon@horde.org>
  22. * @since Horde 1.3
  23. * @package Horde_Framework
  24. */
  25. class Horde {
  26. /**
  27. * Logs a message to the global Horde log backend.
  28. *
  29. * @param mixed $message Either a string or a PEAR_Error object.
  30. * @param string $file What file was the log function called from
  31. * (e.g. __FILE__)?
  32. * @param integer $line What line was the log function called from
  33. * (e.g. __LINE__)?
  34. * @param integer $priority The priority of the message. One of:
  35. * <pre>
  36. * PEAR_LOG_EMERG
  37. * PEAR_LOG_ALERT
  38. * PEAR_LOG_CRIT
  39. * PEAR_LOG_ERR
  40. * PEAR_LOG_WARNING
  41. * PEAR_LOG_NOTICE
  42. * PEAR_LOG_INFO
  43. * PEAR_LOG_DEBUG
  44. * </pre>
  45. */
  46. function logMessage($message, $file, $line, $priority = PEAR_LOG_INFO)
  47. {
  48. $logger = &Horde::getLogger();
  49. if ($logger === false) {
  50. return;
  51. }
  52. if ($priority > $GLOBALS['conf']['log']['priority']) {
  53. return;
  54. }
  55. if (is_a($message, 'PEAR_Error')) {
  56. $userinfo = $message->getUserInfo();
  57. $message = $message->getMessage();
  58. if (!empty($userinfo)) {
  59. if (is_array($userinfo)) {
  60. $old_error = error_reporting(0);
  61. $userinfo = implode(', ', $userinfo);
  62. error_reporting($old_error);
  63. }
  64. $message .= ': ' . $userinfo;
  65. }
  66. } elseif (is_object($message) &&
  67. is_callable(array($message, 'getMessage'))) {
  68. $message = $message->getMessage();
  69. }
  70. $app = isset($GLOBALS['registry']) ? $GLOBALS['registry']->getApp() : 'horde';
  71. $message = '[' . $app . '] ' . $message . ' [pid ' . getmypid() . ' on line ' . $line . ' of "' . $file . '"]';
  72. /* Make sure to log in the system's locale and timezone. */
  73. $locale = setlocale(LC_TIME, 0);
  74. setlocale(LC_TIME, 'C');
  75. $tz = getenv('TZ');
  76. @putenv('TZ');
  77. $logger->log($message, $priority);
  78. /* Restore original locale and timezone. */
  79. setlocale(LC_TIME, $locale);
  80. if ($tz) {
  81. @putenv('TZ=' . $tz);
  82. }
  83. return true;
  84. }
  85. /**
  86. * Get an instantiated instance of the configured logger, if enabled.
  87. * New as of Horde 3.2: getLogger() will fatally exit if a Log object can
  88. * not be instantiated - there is no need to check the return for a
  89. * PEAR_Error anymore.
  90. *
  91. * @return mixed Log object on success, false if disabled.
  92. */
  93. function &getLogger()
  94. {
  95. global $conf;
  96. static $logger;
  97. if (empty($conf['log']['enabled'])) {
  98. $ret = false;
  99. return $ret;
  100. }
  101. if (isset($logger)) {
  102. return $logger;
  103. }
  104. // Try to make sure that we can log messages somehow.
  105. if (empty($conf['log']) ||
  106. empty($conf['log']['type']) ||
  107. empty($conf['log']['name']) ||
  108. empty($conf['log']['ident']) ||
  109. !isset($conf['log']['params'])) {
  110. 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);
  111. }
  112. $logger = Log::singleton($conf['log']['type'],
  113. $conf['log']['name'],
  114. $conf['log']['ident'],
  115. $conf['log']['params']);
  116. if (!is_a($logger, 'Log')) {
  117. 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);
  118. }
  119. return $logger;
  120. }
  121. /**
  122. * Destroys any existing session on login and make sure to use a new
  123. * session ID, to avoid session fixation issues. Should be called before
  124. * checking a login.
  125. */
  126. function getCleanSession()
  127. {
  128. // Make sure to force a completely new session ID and clear all
  129. // session data.
  130. if (version_compare(PHP_VERSION, '4.3.3') !== -1) {
  131. session_regenerate_id(true);
  132. session_unset();
  133. } else {
  134. $old_error = error_reporting(0);
  135. session_destroy();
  136. error_reporting($old_error);
  137. if (Util::extensionExists('posix')) {
  138. $new_session_id = md5(microtime() . posix_getpid());
  139. } else {
  140. $new_session_id = md5(uniqid(mt_rand(), true));
  141. }
  142. session_id($new_session_id);
  143. // Restart the session, including setting up the session handler.
  144. Horde::setupSessionHandler();
  145. error_reporting(0);
  146. session_start();
  147. error_reporting($old_error);
  148. }
  149. /* Reset cookie timeouts, if necessary. */
  150. if (!empty($GLOBALS['conf']['session']['timeout'])) {
  151. $app = $GLOBALS['registry']->getApp();
  152. if (Secret::clearKey($app)) {
  153. Secret::setKey($app);
  154. }
  155. Secret::setKey('auth');
  156. }
  157. }
  158. /**
  159. * Aborts with a fatal error, displaying debug information to the user.
  160. *
  161. * @param mixed $error A PEAR_Error object with debug information or an
  162. * error message.
  163. * @param integer $file The file in which the error occured.
  164. * @param integer $line The line on which the error occured.
  165. * @param boolean $log Log this message via Horde::logMessage()?
  166. */
  167. function fatal($error, $file, $line, $log = true)
  168. {
  169. include_once 'Horde/Auth.php';
  170. include_once 'Horde/CLI.php';
  171. $admin = class_exists('Auth') && Auth::isAdmin();
  172. $cli = class_exists('Horde_CLI') && Horde_CLI::runningFromCLI();
  173. $errortext = '<h1>' . _("A fatal error has occurred") . '</h1>';
  174. if (is_a($error, 'PEAR_Error')) {
  175. $info = array_merge(array('file' => 'conf.php', 'variable' => '$conf'),
  176. array($error->getUserInfo()));
  177. switch ($error->getCode()) {
  178. case HORDE_ERROR_DRIVER_CONFIG_MISSING:
  179. $message = sprintf(_("No configuration information specified for %s."), $info['name']) . '<br />' .
  180. sprintf(_("The file %s should contain some %s settings."),
  181. $GLOBALS['registry']->get('fileroot') . '/config/' . $info['file'],
  182. sprintf("%s['%s']['params']", $info['variable'], $info['driver']));
  183. break;
  184. case HORDE_ERROR_DRIVER_CONFIG:
  185. $message = sprintf(_("Required \"%s\" not specified in %s configuration."), $info['field'], $info['name']) . '<br />' .
  186. sprintf(_("The file %s should contain a %s setting."),
  187. $GLOBALS['registry']->get('fileroot') . '/config/' . $info['file'],
  188. sprintf("%s['%s']['params']['%s']", $info['variable'], $info['driver'], $info['field']));
  189. break;
  190. default:
  191. $message = $error->getMessage();
  192. break;
  193. }
  194. $errortext .= '<h3>' . htmlspecialchars($message) . '</h3>';
  195. } elseif (is_object($error) && method_exists($error, 'getMessage')) {
  196. $errortext .= '<h3>' . htmlspecialchars($error->getMessage()) . '</h3>';
  197. } elseif (is_string($error)) {
  198. $errortext .= '<h3>' . htmlspecialchars($error) . '</h3>';
  199. }
  200. if ($admin) {
  201. $errortext .= '<p><code>' . sprintf(_("[line %d of %s]"), $line, $file) . '</code></p>';
  202. if (is_object($error)) {
  203. $errortext .= '<h3>' . _("Details:") . '</h3>';
  204. $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>';
  205. if (extension_loaded('xdebug')) {
  206. $errortext .= '<br />' . print_r($error, true);
  207. } else {
  208. $errortext .= '<p><pre>' . htmlspecialchars(print_r($error, true)) . '</pre></p>';
  209. }
  210. }
  211. } elseif ($log) {
  212. $errortext .= '<h3>' . _("Details have been logged for the administrator.") . '</h3>';
  213. }
  214. // Log the error via Horde::logMessage() if requested.
  215. if ($log) {
  216. Horde::logMessage($error, $file, $line, PEAR_LOG_EMERG);
  217. }
  218. if ($cli) {
  219. echo strip_tags(str_replace(array('<br />', '<p>', '</p>', '<h1>', '</h1>', '<h3>', '</h3>'), "\n", $errortext));
  220. } else {
  221. echo <<< HTML
  222. <html>
  223. <head><title>Horde :: Fatal Error</title></head>
  224. <body style="background:#fff; color:#000">$errortext</body>
  225. </html>
  226. HTML;
  227. }
  228. exit(1);
  229. }
  230. /**
  231. * Adds the javascript code to the output (if output has already started)
  232. * or to the list of script files to include via includeScriptFiles().
  233. *
  234. * @param string $file The full javascript file name.
  235. * @param string $app The application name. Defaults to the current
  236. * application.
  237. * @param boolean $direct Include the file directly without passing it
  238. * through javascript.php
  239. * @param boolean $full Output a full URL
  240. */
  241. function addScriptFile($file, $app = null, $direct = false, $full = false)
  242. {
  243. $hsf = &Horde_Script_Files::singleton();
  244. $hsf->add($file, $app, $direct, $full);
  245. }
  246. /**
  247. * Includes javascript files that were needed before any headers were sent.
  248. */
  249. function includeScriptFiles()
  250. {
  251. $hsf = &Horde_Script_Files::singleton();
  252. $hsf->includeFiles();
  253. }
  254. /**
  255. * Provide a list of script files to be included in the current page.
  256. *
  257. * @since Horde 3.2
  258. *
  259. * @var array
  260. */
  261. function listScriptFiles()
  262. {
  263. $hsf = &Horde_Script_Files::singleton();
  264. return $hsf->listFiles();
  265. }
  266. /**
  267. * Disable auto-loading of the horde.js script.
  268. * Needs to auto-load by default for BC.
  269. *
  270. * @since Horde 3.2
  271. * @todo Remove for Horde 4
  272. */
  273. function disableAutoloadHordeJS()
  274. {
  275. $hsf = &Horde_Script_Files::singleton();
  276. $hsf->disableAutoloadHordeJS();
  277. }
  278. /**
  279. * Get a token for protecting a form.
  280. *
  281. * @since Horde 3.2
  282. */
  283. function getRequestToken($slug)
  284. {
  285. require_once 'Horde/Token.php';
  286. $token = Horde_Token::generateId($slug);
  287. $_SESSION['horde_form_secrets'][$token] = time();
  288. return $token;
  289. }
  290. /**
  291. * Check if a token for a form is valid.
  292. *
  293. * @since Horde 3.2
  294. */
  295. function checkRequestToken($slug, $token)
  296. {
  297. if (empty($_SESSION['horde_form_secrets'][$token])) {
  298. 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."));
  299. }
  300. if (($_SESSION['horde_form_secrets'][$token] + $GLOBALS['conf']['urls']['token_lifetime'] * 60) < time()) {
  301. 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']));
  302. }
  303. return true;
  304. }
  305. /**
  306. * Add a signature + timestamp to a query string and return the signed query
  307. * string.
  308. *
  309. * @since Horde 3.3
  310. *
  311. * @param string $queryString The query string to sign.
  312. * @param integer $now The timestamp at which to sign. Leave blank for
  313. * generating signatures; specify when testing.
  314. *
  315. * @return string The signed query string.
  316. */
  317. function signQueryString($queryString, $now = null)
  318. {
  319. if (!isset($GLOBALS['conf']['secret_key'])) {
  320. return $queryString;
  321. }
  322. if (is_null($now)) {
  323. $now = time();
  324. }
  325. $queryString .= '&_t=' . $now . '&_h=';
  326. $hmac = Util::uriB64Encode(Util::hmac($queryString, $GLOBALS['conf']['secret_key'], true));
  327. return $queryString . $hmac;
  328. }
  329. /**
  330. * Verify a signature and timestamp on a query string.
  331. *
  332. * @since Horde 3.3
  333. *
  334. * @param string $data The signed query string.
  335. * @param integer $now The current time (can override for testing).
  336. *
  337. * @return boolean Whether or not the string was valid.
  338. */
  339. function verifySignedQueryString($data, $now = null)
  340. {
  341. if (is_null($now)) {
  342. $now = time();
  343. }
  344. $pos = strrpos($data, '&_h=');
  345. if ($pos === false) {
  346. return false;
  347. }
  348. $pos += 4;
  349. $queryString = substr($data, 0, $pos);
  350. $hmac = substr($data, $pos);
  351. if ($hmac != Util::uriB64Encode(Util::hmac($queryString, $GLOBALS['conf']['secret_key'], true))) {
  352. return false;
  353. }
  354. // String was not tampered with; now validate timestamp
  355. parse_str($queryString, $values);
  356. if ($values['_t'] + $GLOBALS['conf']['urls']['hmac_lifetime'] * 60 < $now) {
  357. return false;
  358. }
  359. return true;
  360. }
  361. /**
  362. * Checks if link should be shown and return the necessary code.
  363. *
  364. * @param string $type Type of link to display
  365. * @param string $app The name of the current Horde application.
  366. * @param boolean $override Override Horde settings?
  367. * @param boolean $referrer Include the current page as the referrer (url=)?
  368. *
  369. * @return string The HTML to create the link.
  370. */
  371. function getServiceLink($type, $app, $override = false, $referrer = true)
  372. {
  373. if (!Horde::showService($type, $override)) {
  374. return false;
  375. }
  376. switch ($type) {
  377. case 'help':
  378. if ($GLOBALS['browser']->hasFeature('javascript')) {
  379. Horde::addScriptFile('popup.js', 'horde', true);
  380. }
  381. $url = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/help/', true);
  382. return Util::addParameter($url, 'module', $app);
  383. case 'problem':
  384. return Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/problem.php?return_url=' . urlencode(Horde::selfUrl(true, true, true)));
  385. case 'logout':
  386. return Horde::url(Auth::addLogoutParameters($GLOBALS['registry']->get('webroot', 'horde') . '/login.php', AUTH_REASON_LOGOUT));
  387. case 'login':
  388. return Auth::getLoginScreen('', $referrer ? Horde::selfUrl(true) : null);
  389. case 'options':
  390. global $conf;
  391. if (($conf['prefs']['driver'] != '') && ($conf['prefs']['driver'] != 'none')) {
  392. return Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/prefs.php?app=' . $app);
  393. }
  394. break;
  395. }
  396. return false;
  397. }
  398. /**
  399. * @param string $type The type of link.
  400. * @param boolean $override Override Horde settings?
  401. *
  402. * @return boolean True if the link is to be shown.
  403. */
  404. function showService($type, $override = false)
  405. {
  406. global $conf;
  407. if (empty($conf['menu']['links'][$type])) {
  408. return false;
  409. }
  410. switch ($conf['menu']['links'][$type]) {
  411. case 'all':
  412. return true;
  413. case 'never':
  414. return $override;
  415. case 'authenticated':
  416. return $override || (bool)Auth::getAuth();
  417. default:
  418. return $override;
  419. }
  420. }
  421. /**
  422. * Loads global and vhost specific configuration files.
  423. *
  424. * @since Horde 3.2
  425. *
  426. * @param string $config_file The name of the configuration file.
  427. * @param string|array $var_names The name(s) of the variable(s) that
  428. * is/are defined in the configuration
  429. * file.
  430. * @param string $app The application. Defaults to the current
  431. * application.
  432. * @param boolean $show_output If true, the contents of the requested
  433. * config file are simply output instead of
  434. * loaded into a variable.
  435. *
  436. * @return mixed The value of $var_names, in a compact()'ed array if
  437. * $var_names is an array.
  438. */
  439. function loadConfiguration($config_file, $var_names = null, $app = null,
  440. $show_output = false)
  441. {
  442. global $registry;
  443. if (is_null($app)) {
  444. $app = $registry->getApp();
  445. }
  446. $output = '';
  447. // Load global configuration file.
  448. if ($app == 'horde' && defined('HORDE_BASE')) {
  449. $config_dir = HORDE_BASE . '/config/';
  450. } else {
  451. $config_dir = $registry->get('fileroot', $app) . '/config/';
  452. }
  453. // Track if we've included some version (main or vhosted) of
  454. // the config file.
  455. $was_included = false;
  456. if (file_exists($config_dir . $config_file)) {
  457. ob_start();
  458. // If we are not exporting variables located in the configuration
  459. // file, or we are not capturing the output, then there is no
  460. // need to load the configuration file more than once.
  461. if (is_null($var_names) && !$show_output) {
  462. $success = include_once $config_dir . $config_file;
  463. } else {
  464. $success = include $config_dir . $config_file;
  465. }
  466. $output = ob_get_clean();
  467. if (!empty($output) && !$show_output) {
  468. return PEAR::raiseError(sprintf('Failed to import configuration file "%s": ', $config_dir . $config_file) . strip_tags($output));
  469. }
  470. if (!$success) {
  471. return PEAR::raiseError(sprintf('Failed to import configuration file "%s".', $config_dir . $config_file));
  472. }
  473. $was_included = true;
  474. }
  475. // Load global configuration stanzas in .d directory
  476. $directory = preg_replace('/\.php$/', '.d', $config_dir . $config_file);
  477. if (file_exists($directory) && is_dir($directory)) {
  478. $sub_files = glob("$directory/*.php");
  479. if ($sub_files) {
  480. foreach ($sub_files as $sub_file) {
  481. ob_start();
  482. $success = (is_null($var_names) && !$show_output)
  483. ? include_once $sub_file
  484. : include $sub_file;
  485. $output = ob_get_clean();
  486. if (!empty($output) && !$show_output) {
  487. return PEAR::raiseError(sprintf('Failed to import configuration file "%s": ', $sub_file) . strip_tags($output));
  488. }
  489. if (!$success) {
  490. return PEAR::raiseError(sprintf('Failed to import configuration file "%s".', $sub_file));
  491. }
  492. }
  493. }
  494. }
  495. // Load vhost configuration file.
  496. if (!empty($conf['vhosts']) || !empty($GLOBALS['conf']['vhosts'])) {
  497. $server_name = isset($GLOBALS['conf']) ? $GLOBALS['conf']['server']['name'] : $conf['server']['name'];
  498. $config_file = substr($config_file, 0, -4) . '-' . $server_name . '.php';
  499. if (file_exists($config_dir . $config_file)) {
  500. ob_start();
  501. // See above.
  502. if (is_null($var_names) && !$show_output) {
  503. $success = include_once $config_dir . $config_file;
  504. } else {
  505. $success = include $config_dir . $config_file;
  506. }
  507. $output = ob_get_clean();
  508. if (!empty($output) && !$show_output) {
  509. return PEAR::raiseError(sprintf('Failed to import configuration file "%s": ', $config_dir . $config_file) . strip_tags($output));
  510. }
  511. if (!$success) {
  512. return PEAR::raiseError(sprintf('Failed to import configuration file "%s".', $config_dir . $config_file));
  513. }
  514. $was_included = true;
  515. }
  516. }
  517. // Return an error if neither main or vhosted versions of the
  518. // config file existed.
  519. if (!$was_included) {
  520. return PEAR::raiseError(sprintf('Failed to import configuration file "%s".', $config_dir . $config_file));
  521. }
  522. if ($show_output) {
  523. echo $output;
  524. }
  525. if (is_null($var_names)) {
  526. return;
  527. }
  528. if (is_array($var_names)) {
  529. return compact($var_names);
  530. } else if (isset($$var_names)) {
  531. return $$var_names;
  532. } else {
  533. return array();
  534. }
  535. }
  536. /**
  537. * Returns the driver parameters for the specified backend.
  538. *
  539. * @param mixed $backend The backend system (e.g. 'prefs', 'categories',
  540. * 'contacts') being used.
  541. * The used configuration array will be
  542. * $conf[$backend]. If an array gets passed, it will
  543. * be $conf[$key1][$key2].
  544. * @param string $type The type of driver.
  545. *
  546. * @return array The connection parameters.
  547. */
  548. function getDriverConfig($backend, $type = 'sql')
  549. {
  550. global $conf;
  551. $c = null;
  552. if (is_array($backend)) {
  553. require_once 'Horde/Array.php';
  554. $c = Horde_Array::getElement($conf, $backend);
  555. } elseif (isset($conf[$backend])) {
  556. $c = $conf[$backend];
  557. }
  558. if (!is_null($c) && isset($c['params'])) {
  559. $c['params']['umask'] = $conf['umask'];
  560. if (isset($conf[$type])) {
  561. return array_merge($conf[$type], $c['params']);
  562. } else {
  563. return $c['params'];
  564. }
  565. }
  566. return isset($conf[$type]) ? $conf[$type] : array();
  567. }
  568. /**
  569. * Returns the VFS driver parameters for the specified backend.
  570. *
  571. * @param string $name The VFS system name (e.g. 'images', 'documents')
  572. * being used.
  573. *
  574. * @return array A hash with the VFS parameters; the VFS driver in 'type'
  575. * and the connection parameters in 'params'.
  576. */
  577. function getVFSConfig($name)
  578. {
  579. global $conf;
  580. if (!isset($conf[$name]['type'])) {
  581. return PEAR::raiseError(_("You must configure a VFS backend."));
  582. }
  583. if ($conf[$name]['type'] == 'horde') {
  584. $vfs = $conf['vfs'];
  585. } else {
  586. $vfs = $conf[$name];
  587. }
  588. if ($vfs['type'] == 'sql') {
  589. $vfs['params'] = Horde::getDriverConfig($name, 'sql');
  590. }
  591. return $vfs;
  592. }
  593. /**
  594. * Return the driver and parameters for the current mailer configuration.
  595. *
  596. * @since Horde 3.3.5
  597. *
  598. * @return array Array with driver name and parameter hash.
  599. */
  600. function getMailerConfig()
  601. {
  602. $mail_driver = $GLOBALS['conf']['mailer']['type'];
  603. $mail_params = $GLOBALS['conf']['mailer']['params'];
  604. if ($mail_driver == 'smtp' && $mail_params['auth'] &&
  605. empty($mail_params['username'])) {
  606. $mail_params['username'] = Auth::getAuth();
  607. $mail_params['password'] = Auth::getCredential('password');
  608. }
  609. return array($mail_driver, $mail_params);
  610. }
  611. /**
  612. * Checks if all necessary parameters for a driver configuration
  613. * are set and throws a fatal error with a detailed explanation
  614. * how to fix this, if something is missing.
  615. *
  616. * @param array $params The configuration array with all parameters.
  617. * @param string $driver The key name (in the configuration array) of
  618. * the driver.
  619. * @param array $fields An array with mandatory parameter names for
  620. * this driver.
  621. * @param string $name The clear text name of the driver. If not
  622. * specified, the application name will be used.
  623. * @param string $file The configuration file that should contain
  624. * these settings.
  625. * @param string $variable The name of the configuration variable.
  626. */
  627. function assertDriverConfig($params, $driver, $fields, $name = null,
  628. $file = 'conf.php', $variable = '$conf')
  629. {
  630. global $registry;
  631. // Don't generate a fatal error if we fail during or before
  632. // Registry instantiation.
  633. if (is_null($name)) {
  634. $name = isset($registry) ? $registry->getApp() : '[unknown]';
  635. }
  636. $fileroot = isset($registry) ? $registry->get('fileroot') : '';
  637. if (!is_array($params) || !count($params)) {
  638. Horde::fatal(PEAR::raiseError(
  639. sprintf(_("No configuration information specified for %s."), $name) . "\n\n" .
  640. sprintf(_("The file %s should contain some %s settings."),
  641. $fileroot . '/config/' . $file,
  642. sprintf("%s['%s']['params']", $variable, $driver))),
  643. __FILE__, __LINE__);
  644. }
  645. foreach ($fields as $field) {
  646. if (!isset($params[$field])) {
  647. Horde::fatal(PEAR::raiseError(
  648. sprintf(_("Required \"%s\" not specified in %s configuration."), $field, $name) . "\n\n" .
  649. sprintf(_("The file %s should contain a %s setting."),
  650. $fileroot . '/config/' . $file,
  651. sprintf("%s['%s']['params']['%s']", $variable, $driver, $field))),
  652. __FILE__, __LINE__);
  653. }
  654. }
  655. }
  656. /**
  657. * Returns a session-id-ified version of $uri.
  658. * If a full URL is requested, all parameter separators get converted to
  659. * "&", otherwise to "&amp;".
  660. *
  661. * @param string $uri The URI to be modified.
  662. * @param boolean $full Generate a full (http://server/path/)
  663. * URL.
  664. * @param integer $append_session 0 = only if needed, 1 = always, -1 =
  665. * never.
  666. * @param boolean $force_ssl Ignore $conf['use_ssl'] and force
  667. * creation of a SSL URL?
  668. *
  669. * @return string The URL with the session id appended (if needed).
  670. */
  671. function url($uri, $full = false, $append_session = 0, $force_ssl = false)
  672. {
  673. if ($force_ssl) {
  674. $full = true;
  675. }
  676. if ($full) {
  677. global $conf, $registry, $browser;
  678. /* Store connection parameters in local variables. */
  679. $server_name = $conf['server']['name'];
  680. $server_port = $conf['server']['port'];
  681. $protocol = 'http';
  682. if ($conf['use_ssl'] == 1) {
  683. $protocol = 'https';
  684. } elseif ($conf['use_ssl'] == 2 &&
  685. $browser->usingSSLConnection()) {
  686. $protocol = 'https';
  687. } elseif ($conf['use_ssl'] == 3) {
  688. $server_port = '';
  689. if ($force_ssl) {
  690. $protocol = 'https';
  691. }
  692. }
  693. /* If using non-standard ports, add the port to the URL. */
  694. if (!empty($server_port) &&
  695. ((($protocol == 'http') && ($server_port != 80)) ||
  696. (($protocol == 'https') && ($server_port != 443)))) {
  697. $server_name .= ':' . $server_port;
  698. }
  699. /* Store the webroot in a local variable. */
  700. $webroot = $registry->get('webroot');
  701. $url = $protocol . '://' . $server_name;
  702. if (preg_match('|^([\w+-]{1,20})://|', $webroot)) {
  703. /* Don't prepend to webroot if it's already absolute. */
  704. $url = '';
  705. }
  706. if (substr($uri, 0, 1) != '/') {
  707. /* Simple case for http:// absolute webroots. */
  708. if (preg_match('|^([\w+-]{1,20})://|', $uri)) {
  709. $url = $uri;
  710. } elseif (substr($webroot, -1) == '/') {
  711. $url .= $webroot . $uri;
  712. } else {
  713. $url .= $webroot . '/' . $uri;
  714. }
  715. } else {
  716. $url .= $uri;
  717. }
  718. } else {
  719. $url = $uri;
  720. if (!empty($_SERVER['HTTP_HOST'])) {
  721. // Don't generate absolute URLs if we don't have to.
  722. if (preg_match('|^([\w+-]{1,20}://' . preg_quote($_SERVER['HTTP_HOST'], '|') . ')/|', $url, $matches)) {
  723. $url = substr($url, strlen($matches[1]));
  724. }
  725. }
  726. }
  727. if (empty($GLOBALS['conf']['session']['use_only_cookies']) &&
  728. (($append_session == 1) ||
  729. (($append_session == 0) &&
  730. !isset($_COOKIE[session_name()])))) {
  731. $url = Util::addParameter($url, session_name(), session_id());
  732. }
  733. if ($full) {
  734. /* We need to run the replace twice, because we only catch every
  735. * second match. */
  736. return preg_replace(array('/(=?.*?)&amp;(.*?=)/',
  737. '/(=?.*?)&amp;(.*?=)/'),
  738. '$1&$2', $url);
  739. } elseif (preg_match('/=.*&amp;.*=/', $url)) {
  740. return $url;
  741. } else {
  742. return htmlentities($url);
  743. }
  744. }
  745. /**
  746. * Returns a session-id-ified version of $uri, using the current
  747. * application's webroot setting.
  748. *
  749. * @param string $uri The URI to be modified.
  750. * @param boolean $full Generate a full (http://server/path/)
  751. * URL.
  752. * @param integer $append_session 0 = only if needed, 1 = always, -1 =
  753. * never.
  754. *
  755. * @return string The url with the session id appended.
  756. */
  757. function applicationUrl($uri, $full = false, $append_session = 0)
  758. {
  759. if ($full) {
  760. return Horde::url($uri, $full, $append_session);
  761. }
  762. if (substr($uri, 0, 1) != '/') {
  763. $webroot = $GLOBALS['registry']->get('webroot');
  764. if (substr($webroot, -1) != '/') {
  765. $webroot .= '/';
  766. }
  767. $uri = $webroot . $uri;
  768. }
  769. return Horde::url($uri, $full, $append_session);
  770. }
  771. /**
  772. * Returns an external link passed through the dereferrer to strip session
  773. * IDs from the referrer.
  774. *
  775. * @param string $url The external URL to link to.
  776. * @param boolean $tag If true, a complete <a> tag is returned, only the
  777. * url otherwise.
  778. *
  779. * @return string The link to the dereferrer script.
  780. */
  781. function externalUrl($url, $tag = false)
  782. {
  783. if (!isset($_GET[session_name()]) ||
  784. String::substr($url, 0, 1) == '#' ||
  785. String::substr($url, 0, 7) == 'mailto:') {
  786. $ext = $url;
  787. } else {
  788. $ext = Horde::url($GLOBALS['registry']->get('webroot', 'horde') .
  789. '/services/go.php', true, -1);
  790. /* We must make sure there are no &amp's in the URL. */
  791. $url = preg_replace(array('/(=?.*?)&amp;(.*?=)/', '/(=?.*?)&amp;(.*?=)/'), '$1&$2', $url);
  792. $ext .= '?' . Horde::signQueryString('url=' . urlencode($url));
  793. }
  794. if ($tag) {
  795. $ext = Horde::link($ext, $url, '', '_blank');
  796. }
  797. return $ext;
  798. }
  799. /**
  800. * Returns a URL to be used for downloading, that takes into account any
  801. * special browser quirks (i.e. IE's broken filename handling).
  802. *
  803. * @param string $filename The filename of the download data.
  804. * @param array $params Any additional parameters needed.
  805. * @param string $url The URL to alter. If none passed in, will use
  806. * the file 'view.php' located in the current
  807. * module's base directory.
  808. *
  809. * @return string The download URL.
  810. */
  811. function downloadUrl($filename, $params = array(), $url = null)
  812. {
  813. global $browser;
  814. $horde_url = false;
  815. if (is_null($url)) {
  816. global $registry;
  817. $url = Util::addParameter(Horde::url($registry->get('webroot', 'horde') . '/services/download/'), 'module', $registry->getApp());
  818. $horde_url = true;
  819. }
  820. /* Add parameters. */
  821. if (!is_null($params)) {
  822. $url = Util::addParameter($url, $params);
  823. }
  824. /* If we are using the default Horde download link, add the
  825. * filename to the end of the URL. Although not necessary for
  826. * many browsers, this should allow every browser to download
  827. * correctly. */
  828. if ($horde_url) {
  829. $url = Util::addParameter($url, 'fn', '/' . rawurlencode($filename));
  830. } elseif ($browser->hasQuirk('break_disposition_filename')) {
  831. /* Some browsers will only obtain the filename correctly
  832. * if the extension is the last argument in the query
  833. * string and rest of the filename appears in the
  834. * PATH_INFO element. */
  835. $filename = rawurlencode($filename);
  836. /* Get the webserver ID. */
  837. $server = Horde::webServerID();
  838. /* Get the name and extension of the file. Apache 2 does
  839. * NOT support PATH_INFO information being passed to the
  840. * PHP module by default, so disable that
  841. * functionality. */
  842. if (($server != 'apache2')) {
  843. if (($pos = strrpos($filename, '.'))) {
  844. $name = '/' . preg_replace('/\./', '%2E', substr($filename, 0, $pos));
  845. $ext = substr($filename, $pos);
  846. } else {
  847. $name = '/' . $filename;
  848. $ext = '';
  849. }
  850. /* Enter the PATH_INFO information. */
  851. if (($pos = strpos($url, '?'))) {
  852. $url = substr($url, 0, $pos) . $name . substr($url, $pos);
  853. } else {
  854. $url .= $name;
  855. }
  856. }
  857. /* Append the extension, if it exists. */
  858. if (($server == 'apache2') || !empty($ext)) {
  859. $url = Util::addParameter($url, 'fn_ext', '/' . $filename);
  860. }
  861. }
  862. return $url;
  863. }
  864. /**
  865. * Returns an anchor tag with the relevant parameters
  866. *
  867. * @param string $url The full URL to be linked to.
  868. * @param string $title The link title/description.
  869. * @param string $class The CSS class of the link.
  870. * @param string $target The window target to point to.
  871. * @param string $onclick JavaScript action for the 'onclick' event.
  872. * @param string $title2 The link title (tooltip) (deprecated - just
  873. * use $title).
  874. * @param string $accesskey The access key to use.
  875. * @param array $attributes Any other name/value pairs to add to the <a>
  876. * tag.
  877. * @param boolean $escape Whether to escape special characters in the
  878. * title attribute.
  879. *
  880. * @return string The full <a href> tag.
  881. */
  882. function link($url = '', $title = '', $class = '', $target = '',
  883. $onclick = '', $title2 = '', $accesskey = '',
  884. $attributes = array(), $escape = true)
  885. {
  886. static $charset;
  887. if (!isset($charset)) {
  888. $charset = NLS::getCharset();
  889. }
  890. if (!empty($title2)) {
  891. $title = $title2;
  892. }
  893. $ret = '<a';
  894. if (!empty($url)) {
  895. $ret .= " href=\"$url\"";
  896. }
  897. if (!empty($onclick)) {
  898. $ret .= " onclick=\"$onclick\"";
  899. }
  900. if (!empty($class)) {
  901. $ret .= " class=\"$class\"";
  902. }
  903. if (!empty($target)) {
  904. $ret .= " target=\"$target\"";
  905. }
  906. if (!empty($title)) {
  907. if ($escape) {
  908. $old_error = error_reporting(0);
  909. $title = str_replace(
  910. array("\r", "\n"), '',
  911. htmlspecialchars(
  912. nl2br(htmlspecialchars($title, ENT_QUOTES, $charset)),
  913. ENT_QUOTES, $charset));
  914. error_reporting($old_error);
  915. }
  916. $ret .= ' title="' . $title . '"';
  917. }
  918. if (!empty($accesskey)) {
  919. $ret .= ' accesskey="' . htmlspecialchars($accesskey) . '"';
  920. }
  921. foreach ($attributes as $name => $value) {
  922. $ret .= ' ' . htmlspecialchars($name) . '="'
  923. . htmlspecialchars($value) . '"';
  924. }
  925. return "$ret>";
  926. }
  927. /**
  928. * Uses DOM Tooltips to display the 'title' attribute for
  929. * Horde::link() calls.
  930. *
  931. * @param string $url The full URL to be linked to
  932. * @param string $status The JavaScript mouse-over string
  933. * @param string $class The CSS class of the link
  934. * @param string $target The window target to point to.
  935. * @param string $onclick JavaScript action for the 'onclick' event.
  936. * @param string $title The link title (tooltip).
  937. * @param string $accesskey The access key to use.
  938. * @param array $attributes Any other name/value pairs to add to the <a>
  939. * tag.
  940. *
  941. * @return string The full <a href> tag.
  942. */
  943. function linkTooltip($url, $status = '', $class = '', $target = '',
  944. $onclick = '', $title = '', $accesskey = '',
  945. $attributes = array())
  946. {
  947. static $charset;
  948. if (!isset($charset)) {
  949. $charset = NLS::getCharset();
  950. }
  951. if (!empty($title)) {
  952. $old_error = error_reporting(0);
  953. $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;';
  954. error_reporting($old_error);
  955. }
  956. return Horde::link($url, $title, $class, $target, $onclick, null, $accesskey, $attributes, false);
  957. }
  958. /**
  959. * Returns an anchor sequence with the relevant parameters for a widget
  960. * with accesskey and text.
  961. *
  962. * @access public
  963. *
  964. * @param string $url The full URL to be linked to.
  965. * @param string $title The link title/description.
  966. * @param string $class The CSS class of the link
  967. * @param string $target The window target to point to.
  968. * @param string $onclick JavaScript action for the 'onclick' event.
  969. * @param string $title2 The link title (tooltip) (deprecated - just use
  970. * $title).
  971. * @param boolean $nocheck Don't check if the access key already has been
  972. * used. Defaults to false (= check).
  973. *
  974. * @return string The full <a href>Title</a> sequence.
  975. */
  976. function widget($url, $title = '', $class = 'widget', $target = '',
  977. $onclick = '', $title2 = '', $nocheck = false)
  978. {
  979. if (!empty($title2)) {
  980. $title = $title2;
  981. }
  982. $ak = Horde::getAccessKey($title, $nocheck);
  983. return Horde::link($url, '', $class, $target, $onclick, '', $ak) . Horde::highlightAccessKey($title, $ak) . '</a>';
  984. }
  985. /**
  986. * Returns a session-id-ified version of $SCRIPT_NAME resp. $PHP_SELF.
  987. *
  988. * @param boolean $script_params Include script parameters like
  989. * QUERY_STRING and PATH_INFO?
  990. * @param boolean $nocache Include a nocache parameter in the URL?
  991. * @param boolean $full Return a full URL?
  992. * @param boolean $force_ssl Ignore $conf['use_ssl'] and force creation
  993. * of a SSL URL?
  994. *
  995. * @return string The requested URI.
  996. */
  997. function selfUrl($script_params = false, $nocache = true, $full = false,
  998. $force_ssl = false)
  999. {
  1000. if (!strncmp(PHP_SAPI, 'cgi', 3)) {
  1001. // When using CGI PHP, SCRIPT_NAME may contain the path to
  1002. // the PHP binary instead of the script being run; use
  1003. // PHP_SELF instead.
  1004. $url = $_SERVER['PHP_SELF'];
  1005. } else {
  1006. $url = isset($_SERVER['SCRIPT_NAME']) ?
  1007. $_SERVER['SCRIPT_NAME'] :
  1008. $_SERVER['PHP_SELF'];
  1009. }
  1010. if ($script_params) {
  1011. if (!empty($_SERVER['PATH_INFO'])) {
  1012. $url .= $_SERVER['PATH_INFO'];
  1013. }
  1014. if (!empty($_SERVER['QUERY_STRING'])) {
  1015. $url .= '?' . $_SERVER['QUERY_STRING'];
  1016. }
  1017. }
  1018. $url = Horde::url($url, $full, 0, $force_ssl);
  1019. if ($nocache) {
  1020. return Util::nocacheUrl($url, !$full);
  1021. } else {
  1022. return $url;
  1023. }
  1024. }
  1025. /**
  1026. * Constructs a correctly-pathed link to an image.
  1027. *
  1028. * @param string $src The image file.
  1029. * @param string $alt Text describing the image.
  1030. * @param mixed $attr Any additional attributes for the image tag. Can be
  1031. * a pre-built string or an array of key/value pairs
  1032. * that will be assembled and html-encoded.
  1033. * @param string $dir The root graphics directory.
  1034. *
  1035. * @return string The full image tag.
  1036. */
  1037. function img($src, $alt = '', $attr = '', $dir = null)
  1038. {
  1039. static $charset;
  1040. if (!isset($charset)) {
  1041. $charset = NLS::getCharset();
  1042. }
  1043. /* If browser does not support images, simply return the ALT text. */
  1044. if (!$GLOBALS['browser']->hasFeature('images')) {
  1045. $old_error = error_reporting(0);
  1046. $res = htmlspecialchars($alt, ENT_COMPAT, $charset);
  1047. error_reporting($old_error);
  1048. return $res;
  1049. }
  1050. /* If no directory has been specified, get it from the registry. */
  1051. if ($dir === null) {
  1052. global $registry;
  1053. $dir = $registry->getImageDir();
  1054. }
  1055. /* If a directory has been provided, prepend it to the image source. */
  1056. if (!empty($dir)) {
  1057. $src = $dir . '/' . $src;
  1058. }
  1059. /* Build all of the tag attributes. */
  1060. $attributes = array('src' => $src,
  1061. 'alt' => $alt);
  1062. if (is_array($attr)) {
  1063. $attributes = array_merge($attributes, $attr);
  1064. }
  1065. if (empty($attributes['title'])) {
  1066. $attributes['title'] = '';
  1067. }
  1068. $img = '<img';
  1069. $old_error = error_reporting(0);
  1070. foreach ($attributes as $attribute => $value) {
  1071. $img .= ' ' . $attribute . '="' . ($attribute == 'src' ? $value : htmlspecialchars($value, ENT_COMPAT, $charset)) . '"';
  1072. }
  1073. error_reporting($old_error);
  1074. /* If the user supplied a pre-built string of attributes, add that. */
  1075. if (is_string($attr) && !empty($attr)) {
  1076. $img .= ' ' . $attr;
  1077. }
  1078. /* Return the closed image tag. */
  1079. return $img . ' />';
  1080. }
  1081. /**
  1082. * Determines the location of the system temporary directory. If a specific
  1083. * setting cannot be found, it defaults to /tmp.
  1084. *
  1085. * @return string A directory name that can be used for temp files.
  1086. * Returns false if one could not be found.
  1087. */
  1088. function getTempDir()
  1089. {
  1090. global $conf;
  1091. /* If one has been specifically set, then use that */
  1092. if (!empty($conf['tmpdir'])) {
  1093. $tmp = $conf['tmpdir'];
  1094. }
  1095. /* Next, try Util::getTempDir(). */
  1096. if (empty($tmp)) {
  1097. $tmp = Util::getTempDir();
  1098. }
  1099. /* If it is still empty, we have failed, so return false;
  1100. * otherwise return the directory determined. */
  1101. return empty($tmp) ? false : $tmp;
  1102. }
  1103. /**
  1104. * Creates a temporary filename for the lifetime of the script, and
  1105. * (optionally) registers it to be deleted at request shutdown.
  1106. *
  1107. * @param string $prefix Prefix to make the temporary name more
  1108. * recognizable.
  1109. * @param boolean $delete Delete the file at the end of the request?
  1110. * @param string $dir Directory to create the temporary file in.
  1111. * @param boolean $secure If deleting file, should we securely delete the
  1112. * file?
  1113. *
  1114. * @return string Returns the full path-name to the temporary file or
  1115. * false if a temporary file could not be created.
  1116. */
  1117. function getTempFile($prefix = 'Horde', $delete = true, $dir = '',
  1118. $secure = false)
  1119. {
  1120. if (empty($dir) || !is_dir($dir)) {
  1121. $dir = Horde::getTempDir();
  1122. }
  1123. return Util::getTempFile($prefix, $delete, $dir, $secure);
  1124. }
  1125. /**
  1126. * Starts output compression, if requested.
  1127. */
  1128. function compressOutput()
  1129. {
  1130. static $started;
  1131. if (isset($started)) {
  1132. return;
  1133. }
  1134. /* Compress output if requested and possible. */
  1135. if ($GLOBALS['conf']['compress_pages'] &&
  1136. !$GLOBALS['browser']->hasQuirk('buggy_compression') &&
  1137. !(bool)ini_get('zlib.output_compression') &&
  1138. !(bool)ini_get('zend_accelerator.compress_all') &&
  1139. ini_get('output_handler') != 'ob_gzhandler') {
  1140. if (ob_get_level()) {
  1141. ob_end_clean();
  1142. }
  1143. ob_start('ob_gzhandler');
  1144. }
  1145. $started = true;
  1146. }
  1147. /**
  1148. * Determines if output compression can be used.
  1149. *
  1150. * @return boolean True if output compression can be used, false if not.
  1151. */
  1152. function allowOutputCompression()
  1153. {
  1154. require_once 'Horde/Browser.php';
  1155. $browser = &Browser::singleton();
  1156. /* Turn off compression for buggy browsers. */
  1157. if ($browser->hasQuirk('buggy_compression')) {
  1158. return false;
  1159. }
  1160. return (ini_get('zlib.output_compression') == '' &&
  1161. ini_get('zend_accelerator.compress_all') == '' &&
  1162. ini_get('output_handler') != 'ob_gzhandler');
  1163. }
  1164. /**
  1165. * Returns the Web server being used.
  1166. * PHP string list built from the PHP 'configure' script.
  1167. *
  1168. * @return string A web server identification string.
  1169. * <pre>
  1170. * 'aolserver' = AOL Server
  1171. * 'apache1' = Apache 1.x
  1172. * 'apache2' = Apache 2.x
  1173. * 'caudium' = Caudium
  1174. * 'cgi' = Unknown server - PHP built as CGI program
  1175. * 'cli' = Command Line Interface build
  1176. * 'embed' = Embedded PHP
  1177. * 'isapi' = Zeus ISAPI
  1178. * 'milter' = Milter
  1179. * 'nsapi' = NSAPI
  1180. * 'phttpd' = PHTTPD
  1181. * 'pi3web' = Pi3Web
  1182. * 'roxen' = Roxen/Pike
  1183. * 'servlet' = Servlet
  1184. * 'thttpd' = thttpd
  1185. * 'tux' = Tux
  1186. * 'webjames' = Webjames
  1187. * </pre>
  1188. */
  1189. function webServerID()
  1190. {
  1191. if (PHP_SAPI == 'apache') {
  1192. return 'apache1';
  1193. } elseif ((PHP_SAPI == 'apache2filter') ||
  1194. (PHP_SAPI == 'apache2handler')) {
  1195. return 'apache2';
  1196. } else {
  1197. return PHP_SAPI;
  1198. }
  1199. }
  1200. /**
  1201. * Returns the <link> tags for the CSS stylesheets.
  1202. *
  1203. * @param string|array $app The Horde application(s).
  1204. * @param mixed $theme The theme to use; specify an empty value to
  1205. * retrieve the theme from user preferences, and
  1206. * false for no theme.
  1207. * @param boolean $inherit Inherit Horde-wide CSS?
  1208. *
  1209. * @return string <link> tags for CSS stylesheets.
  1210. */
  1211. function stylesheetLink($apps = null, $theme = '', $inherit = true)
  1212. {
  1213. $css = Horde::getStylesheets($apps, $theme, $inherit);
  1214. $html = '';
  1215. foreach ($css as $css_link) {
  1216. $html .= '<link href="' . $css_link['u'] . '" rel="stylesheet" type="text/css" />' . "\n";
  1217. }
  1218. return $html;
  1219. }
  1220. /**
  1221. * Return the list of base stylesheets to display.
  1222. *
  1223. * @since Horde 3.2
  1224. *
  1225. * @param string|array $app The Horde application(s).
  1226. * @param mixed $theme The theme to use; specify an empty value to
  1227. * retrieve the theme from user preferences, and
  1228. * false for no theme.
  1229. * @param boolean $inherit Inherit Horde-wide CSS?
  1230. *
  1231. * @return array
  1232. */
  1233. function getStylesheets($apps = null, $theme = '', $inherit = true)
  1234. {
  1235. if ($theme === '' && isset($GLOBALS['prefs'])) {
  1236. $theme = $GLOBALS['prefs']->getValue('theme');
  1237. }
  1238. $css = array();
  1239. $rtl = isset($GLOBALS['nls']['rtl'][$GLOBALS['language']]);
  1240. if (!is_array($apps)) {
  1241. $apps = is_null($apps) ? array() : array($apps);
  1242. }
  1243. if ($inherit) {
  1244. $key = array_search('horde', $apps);
  1245. if ($key !== false) {
  1246. unset($apps[$key]);
  1247. }
  1248. array_unshift($apps, 'horde');
  1249. }
  1250. /* Collect browser specific stylesheets if needed. */
  1251. $browser_css = array();
  1252. if ($GLOBALS['browser']->isBrowser('msie')) {
  1253. $ie_major = $GLOBALS['browser']->getMajor();
  1254. if ($ie_major == 7) {
  1255. $browser_css[] = 'ie7.css';
  1256. } elseif ($ie_major < 7) {
  1257. $browser_css[] = 'ie6_or_less.css';
  1258. if ($GLOBALS['browser']->getPlatform() == 'mac') {
  1259. $browser_css[] = 'ie5mac.css';
  1260. }
  1261. }
  1262. }
  1263. if ($GLOBALS['browser']->isBrowser('opera')) {
  1264. $browser_css[] = 'opera.css';
  1265. }
  1266. if ($GLOBALS['browser']->isBrowser('mozilla') &&
  1267. $GLOBALS['browser']->getMajor() >= 5 &&
  1268. preg_match('/rv:(.*)\)/', $GLOBALS['browser']->getAgentString(), $revision) &&
  1269. $revision[1] <= 1.4) {
  1270. $browser_css[] = 'moz14.css';
  1271. }
  1272. if (strpos(strtolower($GLOBALS['browser']->getAgentString()), 'safari') !== false) {
  1273. $browser_css[] = 'safari.css';
  1274. }
  1275. foreach ($apps as $app) {
  1276. $themes_fs = $GLOBALS['registry']->get('themesfs', $app);
  1277. $themes_uri = Horde::url($GLOBALS['registry']->get('themesuri', $app), false, -1);
  1278. $css[] = array('u' => $themes_uri . '/screen.css', 'f' => $themes_fs . '/screen.css');
  1279. if (!empty($theme) &&
  1280. file_exists($themes_fs . '/' . $theme . '/screen.css')) {
  1281. $css[] = array('u' => $themes_uri . '/' . $theme . '/screen.css', 'f' => $themes_fs . '/' . $theme . '/screen.css');
  1282. }
  1283. if ($rtl) {
  1284. $css[] = array('u' => $themes_uri . '/rtl.css', 'f' => $themes_fs . '/rtl.css');
  1285. if (!empty($theme) &&
  1286. file_exists($themes_fs . '/' . $theme . '/rtl.css')) {
  1287. $css[] = array('u' => $themes_uri . '/' . $theme . '/rtl.css', 'f' => $themes_fs . '/' . $theme . '/rtl.css');
  1288. }
  1289. }
  1290. foreach ($browser_css as $browser) {
  1291. if (file_exists($themes_fs . '/' . $browser)) {
  1292. $css[] = array('u' => $themes_uri . '/' . $browser, 'f' => $themes_fs . '/' . $browser);
  1293. }
  1294. if (!empty($theme) &&
  1295. file_exists($themes_fs . '/' . $theme . '/' . $browser)) {
  1296. $css[] = array('u' => $themes_uri . '/' . $theme . '/' . $browser, 'f' => $themes_fs . '/' . $theme . '/' . $browser);
  1297. }
  1298. }
  1299. }
  1300. return $css;
  1301. }
  1302. /**
  1303. * Sets a custom session handler up, if there is one.
  1304. * If the global variable 'session_cache_limiter' is defined, its value
  1305. * will override the cache limiter setting found in the configuration
  1306. * file.
  1307. */
  1308. function setupSessionHandler()
  1309. {
  1310. global $conf;
  1311. ini_set('url_rewriter.tags', 0);
  1312. if (!empty($conf['session']['use_only_cookies'])) {
  1313. ini_set('session.use_only_cookies', 1);
  1314. if (!empty($conf['cookie']['domain']) &&
  1315. strpos($conf['server']['name'], '.') === false) {
  1316. Horde::fatal('Session cookies will not work without a FQDN and with a non-empty cookie domain. Either use a fully qualified domain name like "http://www.example.com" instead of "http://example" only, or set the cookie domain in the Horde configuration to an empty value, or enable non-cookie (url-based) sessions in the Horde configuration.', __FILE__, __LINE__);
  1317. }
  1318. }
  1319. session_set_cookie_params($conf['session']['timeout'],
  1320. $conf['cookie']['path'], $conf['cookie']['domain'], $conf['use_ssl'] == 1 ? 1 : 0);
  1321. session_cache_limiter(Util::nonInputVar('session_cache_limiter', $conf['session']['cache_limiter']));
  1322. session_name(urlencode($conf['session']['name']));
  1323. $type = !empty($conf['sessionhandler']['type']) ? $conf['sessionhandler']['type'] : 'none';
  1324. if ($type == 'external') {
  1325. $calls = $conf['sessionhandler']['params'];
  1326. session_set_save_handler($calls['open'],
  1327. $calls['close'],
  1328. $calls['read'],
  1329. $calls['write'],
  1330. $calls['destroy'],
  1331. $calls['gc']);
  1332. } elseif ($type != 'none') {
  1333. require_once 'Horde/SessionHandler.php';
  1334. $sh = &SessionHandler::singleton($conf['sessionhandler']['type'], array_merge(Horde::getDriverConfig('sessionhandler', $conf['sessionhandler']['type']), array('memcache' => !empty($conf['sessionhandler']['memcache']))));
  1335. if (is_a($sh, 'PEAR_Error')) {
  1336. Horde::fatal(PEAR::raiseError('Horde is unable to correctly start the custom session handler.'), __FILE__, __LINE__, false);
  1337. } else {
  1338. ini_set('session.save_handler', 'user');
  1339. session_set_save_handler(array(&$sh, 'open'),
  1340. array(&$sh, 'close'),
  1341. array(&$sh, 'read'),
  1342. array(&$sh, 'write'),
  1343. array(&$sh, 'destroy'),
  1344. array(&$sh, 'gc'));
  1345. }
  1346. }
  1347. }
  1348. /**
  1349. * Returns an un-used access key from the label given.
  1350. *
  1351. * @param string $label The label to choose an access key from.
  1352. * @param boolean $nocheck Don't check if the access key already has been
  1353. * used?
  1354. *
  1355. * @return string A single lower case character access key or empty
  1356. * string if none can be found
  1357. */
  1358. function getAccessKey($label, $nocheck = false, $shutdown = false)
  1359. {
  1360. /* The access keys already used in this page */
  1361. static $_used = array();
  1362. /* The labels already used in this page */
  1363. static $_labels = array();
  1364. /* Shutdown call for translators? */
  1365. if ($shutdown) {
  1366. if (!count($_labels)) {
  1367. return;
  1368. }
  1369. $script = basename($_SERVER['PHP_SELF']);
  1370. $labels = array_keys($_labels);
  1371. sort($labels);
  1372. $used = array_keys($_used);
  1373. sort($used);
  1374. $remaining = str_replace($used, array(), 'abcdefghijklmnopqrstuvwxyz');
  1375. Horde::logMessage('Access key information for ' . $script, __FILE__, __LINE__);
  1376. Horde::logMessage('Used labels: ' . implode(',', $labels), __FILE__, __LINE__);
  1377. Horde::logMessage('Used keys: ' . implode('', $used), __FILE__, __LINE__);
  1378. Horde::logMessage('Free keys: ' . $remaining, __FILE__, __LINE__);
  1379. return;
  1380. }
  1381. /* Use access keys at all? */
  1382. static $notsupported;
  1383. if (!isset($notsupported)) {
  1384. $notsupported = !$GLOBALS['browser']->hasFeature('accesskey') ||
  1385. !$GLOBALS['prefs']->getValue('widget_accesskey');
  1386. }
  1387. if ($notsupported || !preg_match('/_([A-Za-z])/', $label, $match)) {
  1388. return '';
  1389. }
  1390. $key = $match[1];
  1391. /* Has this key already been used? */
  1392. if (isset($_used[strtolower($key)]) &&
  1393. !($nocheck && isset($_labels[$label]))) {
  1394. return '';
  1395. }
  1396. /* Save key and label. */
  1397. $_used[strtolower($key)] = true;
  1398. $_labels[$label] = true;
  1399. return $key;
  1400. }
  1401. /**
  1402. * Strips an access key from a label.
  1403. * For multibyte charset strings the access key gets removed completely,
  1404. * otherwise only the underscore gets removed.
  1405. *
  1406. * @param string $label The label containing an access key.
  1407. *
  1408. * @return string The label with the access key being stripped.
  1409. */
  1410. function stripAccessKey($label)
  1411. {
  1412. if (!isset($GLOBALS['nls'])) {
  1413. Horde::loadConfiguration('nls.php', null, 'horde');
  1414. }
  1415. $multibyte = isset($GLOBALS['nls']['multibyte'][NLS::getCharset(true)]);
  1416. return preg_replace('/_([A-Za-z])/',
  1417. $multibyte && preg_match('/[\x80-\xff]/', $label) ? '' : '\1',
  1418. $label);
  1419. }
  1420. /**
  1421. * Highlights an access key in a label.
  1422. *
  1423. * @param string $label The label to highlight the access key in.
  1424. * @param string $accessKey The access key to highlight.
  1425. *
  1426. * @return string The HTML version of the label with the access key
  1427. * highlighted.
  1428. */
  1429. function highlightAccessKey($label, $accessKey)
  1430. {
  1431. $stripped_label = Horde::stripAccessKey($label);
  1432. if (empty($accessKey)) {
  1433. return $stripped_label;
  1434. }
  1435. if (isset($GLOBALS['nls']['multibyte'][NLS::getCharset(true)])) {
  1436. /* Prefix parenthesis with the UTF-8 representation of the LRO
  1437. * (Left-to-Right-Override) Unicode codepoint U+202D. */
  1438. $prefix = NLS::getCharset() == 'UTF-8' ? "\xe2\x80\xad" : '';
  1439. return $stripped_label . $prefix . '(<span class="accessKey">'
  1440. . strtoupper($accessKey) . '</span>' . ')';
  1441. } else {
  1442. return str_replace('_' . $accessKey, '<span class="accessKey">' . $accessKey . '</span>', $label);
  1443. }
  1444. }
  1445. /**
  1446. * Returns the appropriate "accesskey" and "title" attributes for an HTML
  1447. * tag and the given label.
  1448. *
  1449. * @param string $label The title of an HTML element
  1450. * @param boolean $nocheck Don't check if the access key already has been
  1451. * used?
  1452. *
  1453. * @return string The title, and if appropriate, the accesskey attributes
  1454. * for the element.
  1455. */
  1456. function getAccessKeyAndTitle($label, $nocheck = false)
  1457. {
  1458. $ak = Horde::getAccessKey($label, $nocheck);
  1459. $attributes = 'title="' . Horde::stripAccessKey($label);
  1460. if (!empty($ak)) {
  1461. $attributes .= sprintf(_(" (Accesskey %s)"), $ak);
  1462. $attributes .= '" accesskey="' . $ak;
  1463. }
  1464. return $attributes . '"';
  1465. }
  1466. /**
  1467. * Returns a label element including an access key for usage in conjuction
  1468. * with a form field. User preferences regarding access keys are respected.
  1469. *
  1470. * @param string $for The form field's id attribute.
  1471. * @param string $label The label text.
  1472. * @param string $ak The access key to use. If null a new access key
  1473. * will be generated.
  1474. *
  1475. * @return string The html code for the label element.
  1476. */
  1477. function label($for, $label, $ak = null)
  1478. {
  1479. if ($ak === null) {
  1480. $ak = Horde::getAccessKey($label, 1);
  1481. }
  1482. $label = Horde::highlightAccessKey($label, $ak);
  1483. return sprintf('<label for="%s"%s>%s</label>',
  1484. $for,
  1485. !empty($ak) ? ' accesskey="' . $ak . '"' : '',
  1486. $label);
  1487. }
  1488. /**
  1489. * Redirects to the main Horde login page on authentication failure.
  1490. */
  1491. function authenticationFailureRedirect()
  1492. {
  1493. require_once 'Horde/CLI.php';
  1494. if (Horde_CLI::runningFromCLI()) {
  1495. $cli = &Horde_CLI::singleton();
  1496. $cli->fatal(_("You are not authenticated."));
  1497. }
  1498. $url = $GLOBALS['registry']->get('webroot', 'horde') . '/login.php';
  1499. $url = Util::addParameter($url, array('url' => Horde::selfUrl(true), 'nosidebar' => 1), null, false);
  1500. $url = Auth::addLogoutParameters($url);
  1501. header('Location: ' . Horde::url($url, true));
  1502. exit;
  1503. }
  1504. /**
  1505. * Provides a standardised function to call a Horde hook, checking whether
  1506. * a hook config file exists and whether the function itself exists. If
  1507. * these two conditions are not satisfied it will return the specified
  1508. * value (by default a PEAR error).
  1509. *
  1510. * @param string $hook The function to call.
  1511. * @param array $args An array of any arguments to pass to the hook
  1512. * function.
  1513. * @param string $app If specified look for hooks in the config directory
  1514. * of this app.
  1515. * @param mixed $error What to return if $app/config/hooks.php or $hook
  1516. * does not exist. If this is the string 'PEAR_Error'
  1517. * a PEAR error object is returned instead, detailing
  1518. * the failure.
  1519. *
  1520. * @return mixed Either the results of the hook or PEAR error on failure.
  1521. */
  1522. function callHook($hook, $args = array(), $app = 'horde', $error = 'PEAR_Error')
  1523. {
  1524. global $registry;
  1525. static $hooks_loaded = array();
  1526. if (!isset($hooks_loaded[$app])) {
  1527. $success = Horde::loadConfiguration('hooks.php', null, $app);
  1528. if (is_a($success, 'PEAR_Error')) {
  1529. Horde::logMessage($success, __FILE__, __LINE__, PEAR_LOG_DEBUG);
  1530. $hooks_loaded[$app] = false;
  1531. } else {
  1532. $hooks_loaded[$app] = true;
  1533. }
  1534. }
  1535. if (function_exists($hook)) {
  1536. $result = call_user_func_array($hook, $args);
  1537. if (is_a($result, 'PEAR_Error')) {
  1538. Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR);
  1539. }
  1540. return $result;
  1541. }
  1542. if (is_string($error) && strcmp($error, 'PEAR_Error') == 0) {
  1543. $error = PEAR::raiseError(sprintf('Hook %s in application %s not called.', $hook, $app));
  1544. Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_DEBUG);
  1545. }
  1546. return $error;
  1547. }
  1548. /**
  1549. * Returns the specified permission for the current user.
  1550. *
  1551. * @since Horde 3.1
  1552. *
  1553. * @param string $permission A permission, currently only 'max_blocks'.
  1554. *
  1555. * @return mixed The value of the specified permission.
  1556. */
  1557. function hasPermission($permission)
  1558. {
  1559. global $perms;
  1560. if (!$perms->exists('horde:' . $permission)) {
  1561. return true;
  1562. }
  1563. $allowed = $perms->getPermissions('horde:' . $permission);
  1564. if (is_array($allowed)) {
  1565. switch ($permission) {
  1566. case 'max_blocks':
  1567. $allowed = max($allowed);
  1568. break;
  1569. }
  1570. }
  1571. return $allowed;
  1572. }
  1573. }
  1574. /**
  1575. * The Horde_Script_Files:: class provides a coherent way to manage script
  1576. * files for inclusion in Horde output. This class is meant to be used
  1577. * internally by Horde:: only.
  1578. *
  1579. * @author Michael Slusarz <slusarz@horde.org>
  1580. * @since Horde 3.2
  1581. * @package Horde_Framework
  1582. */
  1583. class Horde_Script_Files {
  1584. /**
  1585. * The list of script files to add.
  1586. *
  1587. * @var array
  1588. */
  1589. var $_files = array();
  1590. /**
  1591. * The list of files we have already included.
  1592. *
  1593. * @var array
  1594. */
  1595. var $_included = array();
  1596. /**
  1597. * The list of deprecated files.
  1598. *
  1599. * @var array
  1600. */
  1601. var $_ignored = array(
  1602. 'horde' => array('tooltip.js')
  1603. );
  1604. /**
  1605. * The list of javascript files to always load from Horde.
  1606. *
  1607. * @var array
  1608. */
  1609. var $_fromhorde = array('prototype.js', 'onDomReady.js');
  1610. /**
  1611. * The list of javscript files in Horde that have prototypejs'd versions.
  1612. *
  1613. * @var array
  1614. */
  1615. var $_ptversions = array('tables.js', 'stripe.js');
  1616. /**
  1617. * Auto load horde.js/horde-prototype.js?
  1618. *
  1619. * @var boolean
  1620. */
  1621. var $_loadhordejs = true;
  1622. /**
  1623. * Singleton.
  1624. */
  1625. function &singleton()
  1626. {
  1627. static $instance;
  1628. if (!isset($instance)) {
  1629. $instance = new Horde_Script_Files();
  1630. }
  1631. return $instance;
  1632. }
  1633. /**
  1634. * Adds the javascript code to the output (if output has already started)
  1635. * or to the list of script files to include.
  1636. *
  1637. * @param string $file The full javascript file name.
  1638. * @param string $app The application name. Defaults to the current
  1639. * application.
  1640. * @param boolean $direct Include the file directly without passing it
  1641. * through javascript.php?
  1642. * @param boolean $full Output a full url
  1643. */
  1644. function add($file, $app = null, $direct = false, $full = false)
  1645. {
  1646. $res = $this->_add($file, $app, $direct, $full);
  1647. if (empty($res) || (!ob_get_length() && !headers_sent())) {
  1648. return;
  1649. }
  1650. // If headers have already been sent, we need to output a <script>
  1651. // tag directly.
  1652. echo '<script type="text/javascript" src="' . $res['u'] . '"></script>' . "\n";
  1653. }
  1654. /**
  1655. * Helper function to determine if given file needs to be output.
  1656. */
  1657. function _add($file, $app, $direct, $full = false)
  1658. {
  1659. global $registry;
  1660. if (empty($app)) {
  1661. $app = $registry->getApp();
  1662. }
  1663. // Skip any js files that have since been deprecated.
  1664. if (!empty($this->_ignored[$app]) &&
  1665. in_array($file, $this->_ignored[$app])) {
  1666. return false;
  1667. }
  1668. // Several files will always be the same thing. Don't distinguish
  1669. // between loading them in different $app scopes; always load them
  1670. // from Horde scope.
  1671. if (in_array($file, $this->_fromhorde)) {
  1672. $app = 'horde';
  1673. }
  1674. // Don't include scripts multiple times.
  1675. if (!empty($this->_included[$app][$file])) {
  1676. return false;
  1677. }
  1678. $this->_included[$app][$file] = true;
  1679. // Explicitly check for a directly serve-able version of the script.
  1680. $path = $GLOBALS['registry']->get('fileroot', $app);
  1681. if (!$direct &&
  1682. file_exists($file[0] == '/'
  1683. ? $path . $file
  1684. : $registry->get('jsfs', $app) . '/' . $file)) {
  1685. $direct = true;
  1686. }
  1687. if ($direct) {
  1688. if ($file[0] == '/') {
  1689. echo $registry->get('webroot', $app);
  1690. $url = Horde::url($registry->get('webroot', $app) . $file,
  1691. $full, -1);
  1692. } else {
  1693. $url = Horde::url($registry->get('jsuri', $app) . '/' . $file,
  1694. $full, -1);
  1695. $path = $registry->get('jsfs', $app) . '/';
  1696. }
  1697. } else {
  1698. $path = $registry->get('templates', $app) . '/javascript/';
  1699. $url = Horde::url(
  1700. Util::addParameter(
  1701. $registry->get('webroot', 'horde') . '/services/javascript.php',
  1702. array('file' => $file, 'app' => $app)));
  1703. }
  1704. $out = $this->_files[$app][] = array('f' => $file, 'd' => $direct, 'u' => $url, 'p' => $path);
  1705. return $out;
  1706. }
  1707. /**
  1708. * Includes javascript files that are needed before any headers are sent.
  1709. */
  1710. function includeFiles()
  1711. {
  1712. foreach ($this->listFiles() as $app => $files) {
  1713. foreach ($files as $file) {
  1714. echo '<script type="text/javascript" src="' . $file['u'] . '"></script>' . "\n";
  1715. }
  1716. }
  1717. }
  1718. /**
  1719. * Prepares the list of javascript files to include.
  1720. *
  1721. * @return array
  1722. */
  1723. function listFiles()
  1724. {
  1725. /* If there is no javascript available, there's no point in including
  1726. * the rest of the files. */
  1727. if (!$GLOBALS['browser']->hasFeature('javascript')) {
  1728. return array();
  1729. }
  1730. $prototype = false;
  1731. $pt_list = array();
  1732. // Always include Horde-level scripts first.
  1733. if (!empty($this->_files['horde'])) {
  1734. foreach ($this->_files['horde'] as $file) {
  1735. if ($file['f'] == 'prototype.js') {
  1736. $prototype = true;
  1737. break;
  1738. }
  1739. }
  1740. if ($prototype) {
  1741. $keys = array_keys($this->_files['horde']);
  1742. foreach ($keys as $key) {
  1743. $file = $this->_files['horde'][$key];
  1744. if (in_array($file['f'], $this->_ptversions)) {
  1745. $pt_list[] = $file;
  1746. unset($this->_files['horde'][$key]);
  1747. }
  1748. }
  1749. }
  1750. }
  1751. /* Add general UI js library. If prototype is available, use the
  1752. * prototype-specific file. */
  1753. if ($this->_loadhordejs) {
  1754. if ($prototype) {
  1755. $this->_add('horde-prototype.js', 'horde', true);
  1756. } else {
  1757. $this->_add('horde.js', 'horde', true);
  1758. }
  1759. /* Fixes for IE that can't easily be done without browser
  1760. * detection. */
  1761. if ($GLOBALS['browser']->hasQuirk('windowed_controls')) {
  1762. $this->_add('horde.ie.js', 'horde', true);
  1763. }
  1764. }
  1765. /* Include other prototype specific files. */
  1766. foreach ($pt_list as $pt_file) {
  1767. $this->_add($pt_file['f'] . '-prototype.js', 'horde', $pt_file['d']);
  1768. }
  1769. /* Add accesskeys.js if access keys are enabled. */
  1770. if ($GLOBALS['prefs']->getValue('widget_accesskey')) {
  1771. $this->_add('prototype.js', 'horde', true);
  1772. $this->_add('accesskeys.js', 'horde', true);
  1773. }
  1774. /* Make sure 'horde' entries appear first. */
  1775. reset($this->_files);
  1776. if (key($this->_files) == 'horde') {
  1777. return $this->_files;
  1778. }
  1779. if (isset($this->_files['horde'])) {
  1780. $jslist = array('horde' => $this->_files['horde']);
  1781. } else {
  1782. $jslist = array();
  1783. }
  1784. foreach ($this->_files as $key => $val) {
  1785. if ($key != 'horde') {
  1786. $jslist[$key] = $val;
  1787. }
  1788. }
  1789. return $jslist;
  1790. }
  1791. /**
  1792. * Disable auto-loading of the horde.js script.
  1793. * Needs to auto-load by default for BC.
  1794. *
  1795. * @todo Remove for Horde 4
  1796. */
  1797. function disableAutoloadHordeJS()
  1798. {
  1799. $this->_loadhordejs = false;
  1800. }
  1801. }