PageRenderTime 61ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/horde-3.3.13/lib/Horde/Registry.php

#
PHP | 1163 lines | 579 code | 136 blank | 448 comment | 138 complexity | f92367ccebc840a06911b715969e2f1f MD5 | raw file
Possible License(s): LGPL-2.0
  1. <?php
  2. /**
  3. * $Horde: framework/Horde/Horde/Registry.php,v 1.243.2.43 2011/01/25 21:55:42 slusarz Exp $
  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. * @package Horde_Framework
  11. */
  12. /** Do not start a session. */
  13. define('HORDE_SESSION_NONE', 1);
  14. /** Do not write changes to session. */
  15. define('HORDE_SESSION_READONLY', 2);
  16. /**
  17. * The Registry:: class provides a set of methods for communication
  18. * between Horde applications and keeping track of application
  19. * configuration information.
  20. *
  21. * @author Chuck Hagenbuch <chuck@horde.org>
  22. * @author Jon Parise <jon@horde.org>
  23. * @author Anil Madhavapeddy <anil@recoil.org>
  24. * @since Horde 1.3
  25. * @package Horde_Framework
  26. */
  27. class Registry {
  28. /**
  29. * Hash storing all of the known services and callbacks.
  30. *
  31. * @var array
  32. */
  33. var $_apiCache = array();
  34. /**
  35. * Hash storing all known data types.
  36. *
  37. * @var array
  38. */
  39. var $_typeCache = array();
  40. /**
  41. * Hash storing all of the registered interfaces that applications
  42. * provide.
  43. *
  44. * @var array
  45. */
  46. var $_interfaces = array();
  47. /**
  48. * Hash storing information on each registry-aware application.
  49. *
  50. * @var array
  51. */
  52. var $applications = array();
  53. /**
  54. * Stack of in-use applications.
  55. *
  56. * @var array
  57. */
  58. var $_appStack = array();
  59. /**
  60. * Quick pointer to the current application.
  61. *
  62. * @var string
  63. */
  64. var $_currentApp = null;
  65. /**
  66. * Cache of application configurations.
  67. *
  68. * @var array
  69. */
  70. var $_confCache = array();
  71. /**
  72. * Are we using registry caching?
  73. *
  74. * @param boolean
  75. */
  76. var $_usecache = false;
  77. /**
  78. * Update these cache entries on shutdown.
  79. *
  80. * @param array
  81. */
  82. var $_updatecache = array();
  83. /**
  84. * Don't update cache at end of request?
  85. *
  86. * @param boolean
  87. */
  88. var $_nocache = false;
  89. /**
  90. * The list of APIs.
  91. *
  92. * @param array
  93. */
  94. var $_apis = array();
  95. /**
  96. * Cached values of the image directories.
  97. *
  98. * @param array
  99. */
  100. var $_imgDir = array();
  101. /**
  102. * Cached values of theme information.
  103. *
  104. * @param array
  105. */
  106. var $_themeCache = array();
  107. /**
  108. * Returns a reference to the global Registry object, only
  109. * creating it if it doesn't already exist.
  110. *
  111. * This method must be invoked as: $registry = &Registry::singleton()
  112. *
  113. * @param integer $session_flags Any session flags.
  114. *
  115. * @return Registry The Horde Registry instance.
  116. */
  117. function &singleton($session_flags = 0)
  118. {
  119. static $registry;
  120. if (!isset($registry)) {
  121. $registry = new Registry($session_flags);
  122. }
  123. return $registry;
  124. }
  125. /**
  126. * Create a new registry instance. Should never be called except
  127. * by &Registry::singleton().
  128. *
  129. * @param integer $session_flags Any session flags.
  130. *
  131. * @access private
  132. */
  133. function Registry($session_flags = 0)
  134. {
  135. /* Import and global Horde's configuration values. */
  136. $this->importConfig('horde');
  137. /* Start a session. */
  138. if ($session_flags & HORDE_SESSION_NONE) {
  139. /* Never start a session if the session flags include
  140. HORDE_SESSION_NONE. */
  141. $_SESSION = array();
  142. } else {
  143. Horde::setupSessionHandler();
  144. $old_error = error_reporting(0);
  145. session_start();
  146. if ($session_flags & HORDE_SESSION_READONLY) {
  147. /* Close the session immediately so no changes can be
  148. made but values are still available. */
  149. session_write_close();
  150. } else {
  151. $this->_usecache = true;
  152. }
  153. error_reporting($old_error);
  154. }
  155. /* Initialize the localization routines and variables. */
  156. NLS::setLang();
  157. NLS::setTextdomain('horde', HORDE_BASE . '/locale', NLS::getCharset());
  158. String::setDefaultCharset(NLS::getCharset());
  159. $reg_mtime = filemtime(HORDE_BASE . '/config/registry.php');
  160. if (!empty($_SESSION['_registry']) &&
  161. isset($_SESSION['_registry']['registry_d'])) {
  162. $registry_d = $_SESSION['_registry']['registry_d'];
  163. } else {
  164. $registry_d = file_exists(HORDE_BASE . '/config/registry.d') &&
  165. is_dir(HORDE_BASE . '/config/registry.d');
  166. }
  167. if ($registry_d) {
  168. $reg_mtime = max($reg_mtime, filemtime(HORDE_BASE . '/config/registry.d'));
  169. }
  170. if (!empty($GLOBALS['conf']['vhosts'])) {
  171. $registry_vhost = HORDE_BASE . '/config/registry-' . $GLOBALS['conf']['server']['name'] . '.php';
  172. if (file_exists($registry_vhost)) {
  173. $reg_mtime = max($reg_mtime, filemtime($registry_vhost));
  174. }
  175. }
  176. /* Load registry information from the session, if available. */
  177. if (!empty($_SESSION['_registry']) &&
  178. !empty($_SESSION['_registry']['cache']) &&
  179. ($_SESSION['_registry']['mtime'] == $reg_mtime)) {
  180. foreach ($_SESSION['_registry']['cache'] as $key => $val) {
  181. $this->$key = $val;
  182. }
  183. } else {
  184. /* Read the registry configuration files. */
  185. require HORDE_BASE . '/config/registry.php';
  186. if ($registry_d) {
  187. $files = glob(HORDE_BASE . '/config/registry.d/*.php');
  188. if ($files) {
  189. foreach ($files as $r) {
  190. include $r;
  191. }
  192. }
  193. }
  194. if (!empty($GLOBALS['conf']['vhosts']) &&
  195. file_exists($registry_vhost)) {
  196. include $registry_vhost;
  197. }
  198. /* Stop system if Horde is inactive. */
  199. if ($this->applications['horde']['status'] == 'inactive') {
  200. Horde::fatal(_("This system is currently deactivated."), __FILE__, __LINE__);
  201. }
  202. /* Scan for all APIs provided by each app, and set other
  203. * common defaults like templates and graphics. */
  204. $appList = array_keys($this->applications);
  205. foreach ($appList as $appName) {
  206. $app = &$this->applications[$appName];
  207. if ($app['status'] == 'heading') {
  208. continue;
  209. }
  210. if (isset($app['fileroot']) && !file_exists($app['fileroot'])) {
  211. $app['status'] = 'inactive';
  212. Horde::logMessage('Setting ' . $appName . ' inactive because the fileroot does not exist.', __FILE__, __LINE__, PEAR_LOG_DEBUG);
  213. }
  214. if ($app['status'] != 'inactive' &&
  215. ($app['status'] != 'admin' || Auth::isAdmin()) &&
  216. isset($app['provides'])) {
  217. if (is_array($app['provides'])) {
  218. foreach ($app['provides'] as $interface) {
  219. $this->_interfaces[$interface] = $appName;
  220. }
  221. } else {
  222. $this->_interfaces[$app['provides']] = $appName;
  223. }
  224. }
  225. if (!isset($app['templates']) && isset($app['fileroot'])) {
  226. $app['templates'] = $app['fileroot'] . '/templates';
  227. }
  228. if (!isset($app['jsuri']) && isset($app['webroot'])) {
  229. $app['jsuri'] = $app['webroot'] . '/js';
  230. }
  231. if (!isset($app['jsfs']) && isset($app['fileroot'])) {
  232. $app['jsfs'] = $app['fileroot'] . '/js';
  233. }
  234. if (!isset($app['themesuri']) && isset($app['webroot'])) {
  235. $app['themesuri'] = $app['webroot'] . '/themes';
  236. }
  237. if (!isset($app['themesfs']) && isset($app['fileroot'])) {
  238. $app['themesfs'] = $app['fileroot'] . '/themes';
  239. }
  240. }
  241. /* Clear out the API & Type caches, if they exists. */
  242. $this->_apiCache = $this->_typeCache = array();
  243. $_SESSION['_registry'] = array('cache' => array(), 'registry_d' => $registry_d, 'mtime' => $reg_mtime);
  244. $this->_cacheVars(array('applications', '_interfaces', '_apiCache', '_typeCache'));
  245. }
  246. /* Create the global Perms object. */
  247. $GLOBALS['perms'] = &Perms::singleton();
  248. /* Attach javascript notification listener. */
  249. $notification = &Notification::singleton();
  250. $notification->attach('javascript');
  251. /* Register access key logger for translators. */
  252. if (!empty($GLOBALS['conf']['log_accesskeys'])) {
  253. register_shutdown_function(create_function('', 'Horde::getAccessKey(null, null, true);'));
  254. }
  255. /* Register memory tracker if logging in debug mode. */
  256. if ($GLOBALS['conf']['log']['enabled'] &&
  257. ($GLOBALS['conf']['log']['priority'] == PEAR_LOG_DEBUG) &&
  258. function_exists('memory_get_peak_usage')) {
  259. register_shutdown_function(array(&$this, '_memoryusageShutdown'));
  260. }
  261. }
  262. /**
  263. * Add a list of variable names to be cached.
  264. *
  265. * @access private
  266. *
  267. * @param array $vars A list of variable names to be cached.
  268. */
  269. function _cacheVars($vars = array())
  270. {
  271. if ($this->_usecache && !empty($vars)) {
  272. if (empty($this->_updatecache)) {
  273. register_shutdown_function(array(&$this, '_shutdowncache'));
  274. }
  275. $this->_updatecache = array_merge($this->_updatecache, $vars);
  276. }
  277. }
  278. /**
  279. * Stores cacheable member variables in the session at shutdown.
  280. *
  281. * @access private
  282. */
  283. function _shutdowncache()
  284. {
  285. if (isset($_SESSION) && !$this->_nocache && Auth::getAuth()) {
  286. if (!isset($_SESSION['_registry'])) {
  287. $_SESSION['_registry'] = array('cache' => array(), 'mtime' => false);
  288. }
  289. foreach (array_keys(array_flip($this->_updatecache)) as $val) {
  290. $_SESSION['_registry']['cache'][$val] = $this->$val;
  291. }
  292. }
  293. }
  294. /**
  295. * Print memory information on shutdown.
  296. *
  297. * @access private
  298. */
  299. function _memoryusageShutdown()
  300. {
  301. Horde::logMessage('Max memory usage: ' . memory_get_peak_usage(true) . ' bytes', __FILE__, __LINE__, PEAR_LOG_DEBUG);
  302. }
  303. /**
  304. * Clear the registry cache.
  305. *
  306. * @since Horde 3.1
  307. */
  308. function clearCache()
  309. {
  310. unset($_SESSION['_registry']);
  311. $this->_nocache = true;
  312. }
  313. /**
  314. * Fills the registry's API cache with the available services.
  315. *
  316. * @access private
  317. */
  318. function _fillApiCache()
  319. {
  320. if (!empty($this->_apiCache)) {
  321. return;
  322. }
  323. $status = array('active', 'notoolbar', 'hidden');
  324. if (Auth::isAdmin()) {
  325. $status[] = 'admin';
  326. }
  327. $apps = $this->listApps($status);
  328. foreach ($apps as $app) {
  329. $_services = $_types = null;
  330. $api = $this->get('fileroot', $app) . '/lib/api.php';
  331. if (is_readable($api)) {
  332. include_once $api;
  333. }
  334. $this->_apiCache[$app] = $_services;
  335. if (!is_null($_types)) {
  336. foreach ($_types as $type => $params) {
  337. /* Prefix non-Horde types with the application name. */
  338. $prefix = ($app == 'horde') ? '' : "${app}_";
  339. $this->_typeCache[$prefix . $type] = $params;
  340. }
  341. }
  342. }
  343. $this->_cacheVars(array('_apiCache', '_typeCache'));
  344. }
  345. /**
  346. * Return a list of the installed and registered applications.
  347. *
  348. * @param array $filter An array of the statuses that should be
  349. * returned. Defaults to non-hidden.
  350. * @param boolean $assoc Associative array with app names as keys.
  351. * @param integer $permission The permission level to check for in the
  352. * list. Defaults to PERMS_SHOW.
  353. *
  354. * @return array List of apps registered with Horde. If no
  355. * applications are defined returns an empty array.
  356. */
  357. function listApps($filter = null, $assoc = false, $permission = PERMS_SHOW)
  358. {
  359. $apps = array();
  360. if (is_null($filter)) {
  361. $filter = array('notoolbar', 'active');
  362. }
  363. foreach ($this->applications as $app => $params) {
  364. if (in_array($params['status'], $filter) &&
  365. (defined('AUTH_HANDLER') || $this->hasPermission($app, $permission))) {
  366. if ($assoc) {
  367. $apps[$app] = $app;
  368. } else {
  369. $apps[] = $app;
  370. }
  371. }
  372. }
  373. return $apps;
  374. }
  375. /**
  376. * Returns all available registry APIs.
  377. *
  378. * @return array The API list.
  379. */
  380. function listAPIs()
  381. {
  382. if (empty($this->_apis)) {
  383. foreach (array_keys($this->_interfaces) as $interface) {
  384. list($api,) = explode('/', $interface, 2);
  385. $this->_apis[$api] = true;
  386. }
  387. }
  388. return array_keys($this->_apis);
  389. }
  390. /**
  391. * Returns all of the available registry methods, or alternately
  392. * only those for a specified API.
  393. *
  394. * @param string $api Defines the API for which the methods shall be
  395. * returned.
  396. *
  397. * @return array The method list.
  398. */
  399. function listMethods($api = null)
  400. {
  401. $methods = array();
  402. $this->_fillApiCache();
  403. foreach (array_keys($this->applications) as $app) {
  404. if (isset($this->applications[$app]['provides'])) {
  405. $provides = $this->applications[$app]['provides'];
  406. if (!is_array($provides)) {
  407. $provides = array($provides);
  408. }
  409. } else {
  410. $provides = array();
  411. }
  412. foreach ($provides as $method) {
  413. if (strpos($method, '/') !== false) {
  414. if (is_null($api) ||
  415. (substr($method, 0, strlen($api)) == $api)) {
  416. $methods[$method] = true;
  417. }
  418. } elseif (is_null($api) || ($method == $api)) {
  419. if (isset($this->_apiCache[$app])) {
  420. foreach (array_keys($this->_apiCache[$app]) as $service) {
  421. $methods[$method . '/' . $service] = true;
  422. }
  423. }
  424. }
  425. }
  426. }
  427. return array_keys($methods);
  428. }
  429. /**
  430. * Returns all of the available registry data types.
  431. *
  432. * @return array The data type list.
  433. */
  434. function listTypes()
  435. {
  436. $this->_fillApiCache();
  437. return $this->_typeCache;
  438. }
  439. /**
  440. * Returns a method's signature.
  441. *
  442. * @param string $method The full name of the method to check for.
  443. *
  444. * @return array A two dimensional array. The first element contains an
  445. * array with the parameter names, the second one the return
  446. * type.
  447. */
  448. function getSignature($method)
  449. {
  450. if (!($app = $this->hasMethod($method))) {
  451. return;
  452. }
  453. $this->_fillApiCache();
  454. list(,$function) = explode('/', $method, 2);
  455. if (!empty($function) &&
  456. isset($this->_apiCache[$app][$function]['type']) &&
  457. isset($this->_apiCache[$app][$function]['args'])) {
  458. return array($this->_apiCache[$app][$function]['args'], $this->_apiCache[$app][$function]['type']);
  459. }
  460. }
  461. /**
  462. * Determine if an interface is implemented by an active application.
  463. *
  464. * @param string $interface The interface to check for.
  465. *
  466. * @return mixed The application implementing $interface if we have it,
  467. * false if the interface is not implemented.
  468. */
  469. function hasInterface($interface)
  470. {
  471. return !empty($this->_interfaces[$interface]) ?
  472. $this->_interfaces[$interface] :
  473. false;
  474. }
  475. /**
  476. * Determine if a method has been registered with the registry.
  477. *
  478. * @param string $method The full name of the method to check for.
  479. * @param string $app Only check this application.
  480. *
  481. * @return mixed The application implementing $method if we have it,
  482. * false if the method doesn't exist.
  483. */
  484. function hasMethod($method, $app = null)
  485. {
  486. if (is_null($app)) {
  487. list($interface, $call) = explode('/', $method, 2);
  488. if (!empty($this->_interfaces[$method])) {
  489. $app = $this->_interfaces[$method];
  490. } elseif (!empty($this->_interfaces[$interface])) {
  491. $app = $this->_interfaces[$interface];
  492. } else {
  493. return false;
  494. }
  495. } else {
  496. $call = $method;
  497. }
  498. $this->_fillApiCache();
  499. return !empty($this->_apiCache[$app][$call]) ? $app : false;
  500. }
  501. /**
  502. * Return the hook corresponding to the default package that
  503. * provides the functionality requested by the $method
  504. * parameter. $method is a string consisting of
  505. * "packagetype/methodname".
  506. *
  507. * @param string $method The method to call.
  508. * @param array $args Arguments to the method.
  509. *
  510. * @return TODO
  511. * Returns PEAR_Error on error.
  512. */
  513. function call($method, $args = array())
  514. {
  515. list($interface, $call) = explode('/', $method, 2);
  516. if (!empty($this->_interfaces[$method])) {
  517. $app = $this->_interfaces[$method];
  518. } elseif (!empty($this->_interfaces[$interface])) {
  519. $app = $this->_interfaces[$interface];
  520. } else {
  521. return PEAR::raiseError('The method "' . $method . '" is not defined in the Horde Registry.');
  522. }
  523. return $this->callByPackage($app, $call, $args);
  524. }
  525. /**
  526. * Output the hook corresponding to the specific package named.
  527. *
  528. * @param string $app The application being called.
  529. * @param string $call The method to call.
  530. * @param array $args Arguments to the method.
  531. *
  532. * @return TODO
  533. * Returns PEAR_Error on error.
  534. */
  535. function callByPackage($app, $call, $args = array())
  536. {
  537. /* Note: calling hasMethod() makes sure that we've cached
  538. * $app's services and included the API file, so we don't try
  539. * to do it again explicitly in this method. */
  540. if (!$this->hasMethod($call, $app)) {
  541. return PEAR::raiseError(sprintf('The method "%s" is not defined in the API for %s.', $call, $app));
  542. }
  543. /* Load the API now. */
  544. $api = $this->get('fileroot', $app) . '/lib/api.php';
  545. if (is_readable($api)) {
  546. include_once $api;
  547. }
  548. /* Make sure that the function actually exists. */
  549. $function = '_' . $app . '_' . str_replace('/', '_', $call);
  550. if (!function_exists($function)) {
  551. return PEAR::raiseError('The function implementing ' . $call . ' (' . $function . ') is not defined in ' . $app . '\'s API.');
  552. }
  553. $checkPerms = isset($this->_apiCache[$app][$call]['checkperms']) ?
  554. $this->_apiCache[$app][$call]['checkperms'] : true;
  555. /* Switch application contexts now, if necessary, before
  556. * including any files which might do it for us. Return an
  557. * error immediately if pushApp() fails. */
  558. $pushed = $this->pushApp($app, $checkPerms);
  559. if (is_a($pushed, 'PEAR_Error')) {
  560. return $pushed;
  561. }
  562. $res = call_user_func_array($function, $args);
  563. /* If we changed application context in the course of this
  564. * call, undo that change now. */
  565. if ($pushed === true) {
  566. $this->popApp();
  567. }
  568. return $res;
  569. }
  570. /**
  571. * Return the hook corresponding to the default package that
  572. * provides the functionality requested by the $method
  573. * parameter. $method is a string consisting of
  574. * "packagetype/methodname".
  575. *
  576. * @param string $method The method to link to.
  577. * @param array $args Arguments to the method.
  578. * @param mixed $extra Extra, non-standard arguments to the method.
  579. *
  580. * @return TODO
  581. * Returns PEAR_Error on error.
  582. */
  583. function link($method, $args = array(), $extra = '')
  584. {
  585. list($interface, $call) = explode('/', $method, 2);
  586. if (!empty($this->_interfaces[$method])) {
  587. $app = $this->_interfaces[$method];
  588. } elseif (!empty($this->_interfaces[$interface])) {
  589. $app = $this->_interfaces[$interface];
  590. } else {
  591. return PEAR::raiseError('The method "' . $method . '" is not defined in the Horde Registry.');
  592. }
  593. return $this->linkByPackage($app, $call, $args, $extra);
  594. }
  595. /**
  596. * Output the hook corresponding to the specific package named.
  597. *
  598. * @param string $app The application being called.
  599. * @param string $call The method to link to.
  600. * @param array $args Arguments to the method.
  601. * @param mixed $extra Extra, non-standard arguments to the method.
  602. *
  603. * @return TODO
  604. * Returns PEAR_Error on error.
  605. */
  606. function linkByPackage($app, $call, $args = array(), $extra = '')
  607. {
  608. /* Note: calling hasMethod makes sure that we've cached $app's
  609. * services and included the API file, so we don't try to do
  610. * it it again explicitly in this method. */
  611. if (!$this->hasMethod($call, $app)) {
  612. return PEAR::raiseError('The method "' . $call . '" is not defined in ' . $app . '\'s API.');
  613. }
  614. /* Make sure the link is defined. */
  615. if (empty($this->_apiCache[$app][$call]['link'])) {
  616. return PEAR::raiseError('The link ' . $call . ' is not defined in ' . $app . '\'s API.');
  617. }
  618. /* Initial link value. */
  619. $link = $this->_apiCache[$app][$call]['link'];
  620. /* Fill in html-encoded arguments. */
  621. foreach ($args as $key => $val) {
  622. $link = str_replace('%' . $key . '%', htmlentities($val), $link);
  623. }
  624. if (isset($this->applications[$app]['webroot'])) {
  625. $link = str_replace('%application%', $this->get('webroot', $app), $link);
  626. }
  627. /* Replace htmlencoded arguments that haven't been specified with
  628. an empty string (this is where the default would be substituted
  629. in a stricter registry implementation). */
  630. $link = preg_replace('|%.+%|U', '', $link);
  631. /* Fill in urlencoded arguments. */
  632. foreach ($args as $key => $val) {
  633. $link = str_replace('|' . String::lower($key) . '|', urlencode($val), $link);
  634. }
  635. /* Append any extra, non-standard arguments. */
  636. if (is_array($extra)) {
  637. $extra_args = '';
  638. foreach ($extra as $key => $val) {
  639. $extra_args .= '&' . urlencode($key) . '=' . urlencode($val);
  640. }
  641. } else {
  642. $extra_args = $extra;
  643. }
  644. $link = str_replace('|extra|', $extra_args, $link);
  645. /* Replace html-encoded arguments that haven't been specified with
  646. an empty string (this is where the default would be substituted
  647. in a stricter registry implementation). */
  648. $link = preg_replace('|\|.+\||U', '', $link);
  649. return $link;
  650. }
  651. /**
  652. * Replace any %application% strings with the filesystem path to the
  653. * application.
  654. *
  655. * @param string $path The application string.
  656. * @param string $app The application being called.
  657. *
  658. * @return TODO
  659. * Returns PEAR_Error on error.
  660. */
  661. function applicationFilePath($path, $app = null)
  662. {
  663. if (is_null($app)) {
  664. $app = $this->_currentApp;
  665. }
  666. if (!isset($this->applications[$app])) {
  667. return PEAR::raiseError(sprintf(_("\"%s\" is not configured in the Horde Registry."), $app));
  668. }
  669. return str_replace('%application%', $this->applications[$app]['fileroot'], $path);
  670. }
  671. /**
  672. * Replace any %application% strings with the web path to the application.
  673. *
  674. * @param string $path The application string.
  675. * @param string $app The application being called.
  676. *
  677. * @return TODO
  678. * Returns PEAR_Error on error.
  679. */
  680. function applicationWebPath($path, $app = null)
  681. {
  682. if (!isset($app)) {
  683. $app = $this->_currentApp;
  684. }
  685. return str_replace('%application%', $this->applications[$app]['webroot'], $path);
  686. }
  687. /**
  688. * Set the current application, adding it to the top of the Horde
  689. * application stack. If this is the first application to be
  690. * pushed, retrieve session information as well.
  691. *
  692. * pushApp() also reads the application's configuration file and
  693. * sets up its global $conf hash.
  694. *
  695. * @param string $app The name of the application to push.
  696. * @param boolean $checkPerms Make sure that the current user has
  697. * permissions to the application being loaded
  698. * Defaults to true. Should ONLY be disabled by
  699. * system scripts (cron jobs, etc.) and scripts
  700. * that handle login.
  701. *
  702. * @return boolean Whether or not the _appStack was modified.
  703. * Return PEAR_Error on error.
  704. */
  705. function pushApp($app, $checkPerms = true)
  706. {
  707. if ($app == $this->_currentApp) {
  708. return false;
  709. }
  710. /* Bail out if application is not present or inactive. */
  711. if (!isset($this->applications[$app]) ||
  712. $this->applications[$app]['status'] == 'inactive' ||
  713. ($this->applications[$app]['status'] == 'admin' && !Auth::isAdmin())) {
  714. Horde::fatal($app . ' is not activated', __FILE__, __LINE__);
  715. }
  716. /* If permissions checking is requested, return an error if the
  717. * current user does not have read perms to the application being
  718. * loaded. We allow access:
  719. *
  720. * - To all admins.
  721. * - To all authenticated users if no permission is set on $app.
  722. * - To anyone who is allowed by an explicit ACL on $app. */
  723. if ($checkPerms && !$this->hasPermission($app)) {
  724. Horde::logMessage(sprintf('%s does not have READ permission for %s', Auth::getAuth() ? 'User ' . Auth::getAuth() : 'Guest user', $app), __FILE__, __LINE__, PEAR_LOG_DEBUG);
  725. return PEAR::raiseError(sprintf(_('%s is not authorised for %s.'), Auth::getAuth() ? 'User ' . Auth::getAuth() : 'Guest user', $this->applications[$app]['name']), 'permission_denied');
  726. }
  727. /* Chicken and egg problem: the language environment has to be loaded
  728. * before loading the configuration file, because it might contain
  729. * gettext strings. Though the preferences can specify a different
  730. * language for this app, the have to be loaded after the
  731. * configuration, because they rely on configuration settings. So try
  732. * with the current language, and reset the language later. */
  733. NLS::setLanguageEnvironment($GLOBALS['language'], $app);
  734. /* Import this application's configuration values. */
  735. $success = $this->importConfig($app);
  736. if (is_a($success, 'PEAR_Error')) {
  737. return $success;
  738. }
  739. /* Load preferences after the configuration has been loaded to make
  740. * sure the prefs file has all the information it needs. */
  741. $this->loadPrefs($app);
  742. /* Reset the language in case there is a different one selected in the
  743. * preferences. */
  744. $language = '';
  745. if (isset($GLOBALS['prefs'])) {
  746. $language = $GLOBALS['prefs']->getValue('language');
  747. if ($language != $GLOBALS['language']) {
  748. NLS::setLanguageEnvironment($language, $app);
  749. }
  750. }
  751. /* Once we know everything succeeded and is in a consistent state
  752. * again, push the new application onto the stack. */
  753. $this->_appStack[] = $this->_currentApp = $app;
  754. /* Call post-push hook. */
  755. Horde::callHook('_horde_hook_post_pushapp', array($app), 'horde', null);
  756. return true;
  757. }
  758. /**
  759. * Remove the current app from the application stack, setting the current
  760. * app to whichever app was current before this one took over.
  761. *
  762. * @return string The name of the application that was popped.
  763. */
  764. function popApp()
  765. {
  766. /* Pop the current application off of the stack. */
  767. $previous = array_pop($this->_appStack);
  768. /* Import the new active application's configuration values
  769. * and set the gettext domain and the preferred language. */
  770. $this->_currentApp = count($this->_appStack) ? end($this->_appStack) : null;
  771. if ($this->_currentApp) {
  772. $this->importConfig($this->_currentApp);
  773. $this->loadPrefs($this->_currentApp);
  774. $language = $GLOBALS['prefs']->getValue('language');
  775. NLS::setLanguageEnvironment($language, $this->_currentApp);
  776. }
  777. return $previous;
  778. }
  779. /**
  780. * Return the current application - the app at the top of the application
  781. * stack.
  782. *
  783. * @return string The current application.
  784. */
  785. function getApp()
  786. {
  787. return $this->_currentApp;
  788. }
  789. /**
  790. * Check permissions on an application.
  791. *
  792. * @return boolean Whether or not access is allowed.
  793. */
  794. function hasPermission($app, $permission = PERMS_READ)
  795. {
  796. return Auth::isAdmin() ||
  797. ($GLOBALS['perms']->exists($app)
  798. ? $GLOBALS['perms']->hasPermission($app, Auth::getAuth(), $permission)
  799. : (bool)Auth::getAuth());
  800. }
  801. /**
  802. * Reads the configuration values for the given application and imports
  803. * them into the global $conf variable.
  804. *
  805. * @param string $app The name of the application.
  806. *
  807. * @return boolean True on success, PEAR_Error on error.
  808. */
  809. function importConfig($app)
  810. {
  811. /* Cache config values so that we don't re-read files on every
  812. * popApp() call. */
  813. if (!isset($this->_confCache[$app])) {
  814. if (!isset($this->_confCache['horde'])) {
  815. $success = Horde::loadConfiguration('conf.php', 'conf', 'horde');
  816. if (is_a($success, 'PEAR_Error')) {
  817. return $success;
  818. }
  819. $conf = $success;
  820. /* Initial Horde-wide settings. */
  821. /* Set the maximum execution time in accordance with
  822. * the config settings. */
  823. error_reporting(0);
  824. set_time_limit($conf['max_exec_time']);
  825. /* Set the error reporting level in accordance with
  826. * the config settings. */
  827. error_reporting($conf['debug_level']);
  828. /* Set the umask according to config settings. */
  829. if (isset($conf['umask'])) {
  830. umask($conf['umask']);
  831. }
  832. } else {
  833. $conf = $this->_confCache['horde'];
  834. }
  835. if ($app !== 'horde') {
  836. $success = Horde::loadConfiguration('conf.php', 'conf', $app);
  837. if (is_a($success, 'PEAR_Error')) {
  838. return $success;
  839. }
  840. require_once 'Horde/Array.php';
  841. $conf = Horde_Array::array_merge_recursive_overwrite($conf, $success);
  842. }
  843. $this->_cacheVars(array('_confCache'));
  844. $this->_confCache[$app] = &$conf;
  845. }
  846. $GLOBALS['conf'] = &$this->_confCache[$app];
  847. return true;
  848. }
  849. /**
  850. * Loads the preferences for the current user for the current application
  851. * and imports them into the global $prefs variable.
  852. *
  853. * @param string $app The name of the application.
  854. */
  855. function loadPrefs($app = null)
  856. {
  857. require_once 'Horde/Prefs.php';
  858. if (is_null($app)) {
  859. $app = $this->_currentApp;
  860. }
  861. /* If there is no logged in user, return an empty Prefs::
  862. * object with just default preferences. */
  863. if (!Auth::getAuth()) {
  864. $GLOBALS['prefs'] = &Prefs::factory('session', $app, '', '', null, false);
  865. } else {
  866. if (!isset($GLOBALS['prefs']) || $GLOBALS['prefs']->getUser() != Auth::getAuth()) {
  867. $GLOBALS['prefs'] = &Prefs::factory($GLOBALS['conf']['prefs']['driver'], $app,
  868. Auth::getAuth(), Auth::getCredential('password'));
  869. } else {
  870. $GLOBALS['prefs']->retrieve($app);
  871. }
  872. }
  873. }
  874. /**
  875. * Unload preferences from an application or (if no application is
  876. * specified) from ALL applications. Useful when a user has logged
  877. * out but you need to continue on the same page, etc.
  878. *
  879. * After unloading, if there is an application on the app stack to
  880. * load preferences from, then we reload a fresh set.
  881. *
  882. * @param string $app The application to unload prefrences for. If null,
  883. * ALL preferences are reset.
  884. */
  885. function unloadPrefs($app = null)
  886. {
  887. if ($this->_currentApp) {
  888. $this->loadPrefs();
  889. }
  890. }
  891. /**
  892. * Return the requested configuration parameter for the specified
  893. * application. If no application is specified, the value of
  894. * $this->_currentApp (the current application) is used. However,
  895. * if the parameter is not present for that application, the
  896. * Horde-wide value is used instead. If that is not present, we
  897. * return null.
  898. *
  899. * @param string $parameter The configuration value to retrieve.
  900. * @param string $app The application to get the value for.
  901. *
  902. * @return string The requested parameter, or null if it is not set.
  903. */
  904. function get($parameter, $app = null)
  905. {
  906. if (is_null($app)) {
  907. $app = $this->_currentApp;
  908. }
  909. if (isset($this->applications[$app][$parameter])) {
  910. $pval = $this->applications[$app][$parameter];
  911. } else {
  912. if ($parameter == 'icon') {
  913. $pval = $this->_getIcon($app);
  914. } else {
  915. $pval = isset($this->applications['horde'][$parameter]) ? $this->applications['horde'][$parameter] : null;
  916. }
  917. }
  918. if ($parameter == 'name') {
  919. return _($pval);
  920. } else {
  921. return $pval;
  922. }
  923. }
  924. /**
  925. * Function to work out an application's graphics URI, taking into account
  926. * any themes directories that may be set up.
  927. *
  928. * @param string $app The application for which to get the image
  929. * directory. If blank will default to current
  930. * application.
  931. *
  932. * @return string The image directory uri path.
  933. */
  934. function getImageDir($app = null)
  935. {
  936. if (empty($app)) {
  937. $app = $this->_currentApp;
  938. }
  939. if ($this->get('status', $app) == 'heading') {
  940. $app = 'horde';
  941. }
  942. if (isset($this->_imgDir[$app])) {
  943. return $this->_imgDir[$app];
  944. }
  945. /* This is the default location for the graphics. */
  946. $this->_imgDir[$app] = $this->get('themesuri', $app) . '/graphics';
  947. /* Figure out if this is going to be overridden by any theme
  948. * settings. */
  949. if (isset($GLOBALS['prefs']) &&
  950. ($theme = $GLOBALS['prefs']->getValue('theme'))) {
  951. if (!isset($this->_themeCache[$theme][$app])) {
  952. $this->_themeCache[$theme][$app] = file_exists($this->get('themesfs', $app) . '/' . $theme . '/themed_graphics');
  953. $this->_cacheVars(array('_themeCache'));
  954. }
  955. if ($this->_themeCache[$theme][$app]) {
  956. $this->_imgDir[$app] = $this->get('themesuri', $app) . '/' . $theme . '/graphics';
  957. }
  958. }
  959. return $this->_imgDir[$app];
  960. }
  961. /**
  962. * Returns the path to an application's icon, respecting whether the
  963. * current theme has its own icons.
  964. *
  965. * @access private
  966. *
  967. * @param string $app The application for which to get the icon.
  968. */
  969. function _getIcon($app)
  970. {
  971. return $this->getImageDir($app) . '/' . $app . '.png';
  972. }
  973. /**
  974. * Query the initial page for an application - the webroot, if there is no
  975. * initial_page set, and the initial_page, if it is set.
  976. *
  977. * @param string $app The name of the application.
  978. *
  979. * @return string URL pointing to the inital page of the application.
  980. * Returns PEAR_Error on error.
  981. */
  982. function getInitialPage($app = null)
  983. {
  984. if (is_null($app)) {
  985. $app = $this->_currentApp;
  986. }
  987. if (!isset($this->applications[$app])) {
  988. return PEAR::raiseError(sprintf(_("\"%s\" is not configured in the Horde Registry."), $app));
  989. }
  990. return $this->applications[$app]['webroot'] . '/' . (isset($this->applications[$app]['initial_page']) ? $this->applications[$app]['initial_page'] : '');
  991. }
  992. /**
  993. * @since Horde 3.1
  994. */
  995. function __get($api)
  996. {
  997. if (in_array($api, $this->listAPIs())) {
  998. return new RegistryCaller($this, $api);
  999. }
  1000. }
  1001. /**
  1002. * Clone should never be called on Registry objects. If it is, die.
  1003. *
  1004. * @since Horde 3.1
  1005. */
  1006. function __clone()
  1007. {
  1008. Horde::fatal('Registry objects should never be cloned.', __FILE__, __LINE__);
  1009. }
  1010. }
  1011. /**
  1012. * @package Horde_Framework
  1013. * @since Horde 3.1
  1014. */
  1015. class RegistryCaller {
  1016. var $registry;
  1017. var $api;
  1018. function __construct($registry, $api)
  1019. {
  1020. $this->registry = $registry;
  1021. $this->api = $api;
  1022. }
  1023. function __call($method, $args)
  1024. {
  1025. return $this->registry->call($this->api . '/' . $method, $args);
  1026. }
  1027. }