statusnet.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <?php
  2. /**
  3. * StatusNet - the distributed open-source microblogging tool
  4. * Copyright (C) 2009-2010 StatusNet, Inc.
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. if (!defined('STATUSNET') && !defined('LACONICA')) {
  21. exit(1);
  22. }
  23. global $config, $_server, $_path;
  24. /**
  25. * Global configuration setup and management.
  26. */
  27. class StatusNet
  28. {
  29. protected static $have_config;
  30. protected static $is_api;
  31. protected static $is_ajax;
  32. protected static $plugins = array();
  33. /**
  34. * Configure and instantiate a plugin into the current configuration.
  35. * Class definitions will be loaded from standard paths if necessary.
  36. * Note that initialization events won't be fired until later.
  37. *
  38. * @param string $name class name & plugin file/subdir name
  39. * @param array $attrs key/value pairs of public attributes to set on plugin instance
  40. *
  41. * @throws ServerException if plugin can't be found
  42. */
  43. public static function addPlugin($name, array $attrs=array())
  44. {
  45. $name = ucfirst($name);
  46. if (isset(self::$plugins[$name])) {
  47. // We have already loaded this plugin. Don't try to
  48. // do it again with (possibly) different values.
  49. // Försten till kvarn får mala.
  50. return true;
  51. }
  52. $pluginclass = "{$name}Plugin";
  53. if (!class_exists($pluginclass)) {
  54. $files = array("local/plugins/{$pluginclass}.php",
  55. "local/plugins/{$name}/{$pluginclass}.php",
  56. "local/{$pluginclass}.php",
  57. "local/{$name}/{$pluginclass}.php",
  58. "plugins/{$pluginclass}.php",
  59. "plugins/{$name}/{$pluginclass}.php");
  60. foreach ($files as $file) {
  61. $fullpath = INSTALLDIR.'/'.$file;
  62. if (@file_exists($fullpath)) {
  63. include_once($fullpath);
  64. break;
  65. }
  66. }
  67. if (!class_exists($pluginclass)) {
  68. throw new ServerException("Plugin $name not found.", 500);
  69. }
  70. }
  71. // Doesn't this $inst risk being garbage collected or something?
  72. // TODO: put into a static array that makes sure $inst isn't lost.
  73. $inst = new $pluginclass();
  74. foreach ($attrs as $aname => $avalue) {
  75. $inst->$aname = $avalue;
  76. }
  77. // Record activated plugins for later display/config dump
  78. self::$plugins[$name] = $attrs;
  79. return true;
  80. }
  81. public static function delPlugin($name)
  82. {
  83. // Remove our plugin if it was previously loaded
  84. $name = ucfirst($name);
  85. if (isset(self::$plugins[$name])) {
  86. unset(self::$plugins[$name]);
  87. }
  88. // make sure initPlugins will avoid this
  89. common_config_set('plugins', 'disable-'.$name, true);
  90. return true;
  91. }
  92. /**
  93. * Get a list of activated plugins in this process.
  94. * @return array of (string $name, array $args) pairs
  95. */
  96. public static function getActivePlugins()
  97. {
  98. return self::$plugins;
  99. }
  100. /**
  101. * Initialize, or re-initialize, StatusNet global configuration
  102. * and plugins.
  103. *
  104. * If switching site configurations during script execution, be
  105. * careful when working with leftover objects -- global settings
  106. * affect many things and they may not behave as you expected.
  107. *
  108. * @param $server optional web server hostname for picking config
  109. * @param $path optional URL path for picking config
  110. * @param $conffile optional configuration file path
  111. *
  112. * @throws NoConfigException if config file can't be found
  113. */
  114. public static function init($server=null, $path=null, $conffile=null)
  115. {
  116. Router::clear();
  117. self::initDefaults($server, $path);
  118. self::loadConfigFile($conffile);
  119. $sprofile = common_config('site', 'profile');
  120. if (!empty($sprofile)) {
  121. self::loadSiteProfile($sprofile);
  122. }
  123. // Load settings from database; note we need autoload for this
  124. Config::loadSettings();
  125. self::initPlugins();
  126. }
  127. /**
  128. * Get identifier of the currently active site configuration
  129. * @return string
  130. */
  131. public static function currentSite()
  132. {
  133. return common_config('site', 'nickname');
  134. }
  135. /**
  136. * Change site configuration to site specified by nickname,
  137. * if set up via Status_network. If not, sites other than
  138. * the current will fail horribly.
  139. *
  140. * May throw exception or trigger a fatal error if the given
  141. * site is missing or configured incorrectly.
  142. *
  143. * @param string $nickname
  144. */
  145. public static function switchSite($nickname)
  146. {
  147. if ($nickname == StatusNet::currentSite()) {
  148. return true;
  149. }
  150. $sn = Status_network::getKV('nickname', $nickname);
  151. if (empty($sn)) {
  152. return false;
  153. throw new Exception("No such site nickname '$nickname'");
  154. }
  155. $server = $sn->getServerName();
  156. StatusNet::init($server);
  157. }
  158. /**
  159. * Pull all local sites from status_network table.
  160. *
  161. * Behavior undefined if site is not configured via Status_network.
  162. *
  163. * @return array of nicknames
  164. */
  165. public static function findAllSites()
  166. {
  167. $sites = array();
  168. $sn = new Status_network();
  169. $sn->find();
  170. while ($sn->fetch()) {
  171. $sites[] = $sn->nickname;
  172. }
  173. return $sites;
  174. }
  175. /**
  176. * Fire initialization events for all instantiated plugins.
  177. */
  178. protected static function initPlugins()
  179. {
  180. // User config may have already added some of these plugins, with
  181. // maybe configured parameters. The self::addPlugin function will
  182. // ignore the new call if it has already been instantiated.
  183. // Load core plugins
  184. foreach (common_config('plugins', 'core') as $name => $params) {
  185. call_user_func('self::addPlugin', $name, $params);
  186. }
  187. // Load default plugins
  188. foreach (common_config('plugins', 'default') as $name => $params) {
  189. $key = 'disable-' . $name;
  190. if (common_config('plugins', $key)) {
  191. continue;
  192. }
  193. // TODO: We should be able to avoid this is_null and assume $params
  194. // is an array, since that's how it is typed in addPlugin
  195. if (is_null($params)) {
  196. self::addPlugin($name);
  197. } else if (is_array($params)) {
  198. if (count($params) == 0) {
  199. self::addPlugin($name);
  200. } else {
  201. $keys = array_keys($params);
  202. if (is_string($keys[0])) {
  203. self::addPlugin($name, $params);
  204. } else {
  205. foreach ($params as $paramset) {
  206. self::addPlugin($name, $paramset);
  207. }
  208. }
  209. }
  210. }
  211. }
  212. // XXX: if plugins should check the schema at runtime, do that here.
  213. if (common_config('db', 'schemacheck') == 'runtime') {
  214. Event::handle('CheckSchema');
  215. }
  216. // Give plugins a chance to initialize in a fully-prepared environment
  217. Event::handle('InitializePlugin');
  218. }
  219. /**
  220. * Quick-check if configuration has been established.
  221. * Useful for functions which may get used partway through
  222. * initialization to back off from fancier things.
  223. *
  224. * @return bool
  225. */
  226. public static function haveConfig()
  227. {
  228. return self::$have_config;
  229. }
  230. public static function isApi()
  231. {
  232. return self::$is_api;
  233. }
  234. public static function setApi($mode)
  235. {
  236. self::$is_api = $mode;
  237. }
  238. public static function isAjax()
  239. {
  240. return self::$is_ajax;
  241. }
  242. public static function setAjax($mode)
  243. {
  244. self::$is_ajax = $mode;
  245. }
  246. /**
  247. * Build default configuration array
  248. * @return array
  249. */
  250. protected static function defaultConfig()
  251. {
  252. global $_server, $_path;
  253. require(INSTALLDIR.'/lib/default.php');
  254. return $default;
  255. }
  256. /**
  257. * Establish default configuration based on given or default server and path
  258. * Sets global $_server, $_path, and $config
  259. */
  260. public static function initDefaults($server, $path)
  261. {
  262. global $_server, $_path, $config, $_PEAR;
  263. Event::clearHandlers();
  264. self::$plugins = array();
  265. // try to figure out where we are. $server and $path
  266. // can be set by including module, else we guess based
  267. // on HTTP info.
  268. if (isset($server)) {
  269. $_server = $server;
  270. } else {
  271. $_server = array_key_exists('SERVER_NAME', $_SERVER) ?
  272. strtolower($_SERVER['SERVER_NAME']) :
  273. null;
  274. }
  275. if (isset($path)) {
  276. $_path = $path;
  277. } else {
  278. $_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ?
  279. self::_sn_to_path($_SERVER['SCRIPT_NAME']) :
  280. null;
  281. }
  282. // Set config values initially to default values
  283. $default = self::defaultConfig();
  284. $config = $default;
  285. // default configuration, overwritten in config.php
  286. // Keep DB_DataObject's db config synced to ours...
  287. $config['db'] = &$_PEAR->getStaticProperty('DB_DataObject','options');
  288. $config['db'] = $default['db'];
  289. if (function_exists('date_default_timezone_set')) {
  290. /* Work internally in UTC */
  291. date_default_timezone_set('UTC');
  292. }
  293. }
  294. public static function loadSiteProfile($name)
  295. {
  296. global $config;
  297. $settings = SiteProfile::getSettings($name);
  298. $config = array_replace_recursive($config, $settings);
  299. }
  300. protected static function _sn_to_path($sn)
  301. {
  302. $past_root = substr($sn, 1);
  303. $last_slash = strrpos($past_root, '/');
  304. if ($last_slash > 0) {
  305. $p = substr($past_root, 0, $last_slash);
  306. } else {
  307. $p = '';
  308. }
  309. return $p;
  310. }
  311. /**
  312. * Load the default or specified configuration file.
  313. * Modifies global $config and may establish plugins.
  314. *
  315. * @throws NoConfigException
  316. */
  317. protected static function loadConfigFile($conffile=null)
  318. {
  319. global $_server, $_path, $config;
  320. // From most general to most specific:
  321. // server-wide, then vhost-wide, then for a path,
  322. // finally for a dir (usually only need one of the last two).
  323. if (isset($conffile)) {
  324. $config_files = array($conffile);
  325. } else {
  326. $config_files = array('/etc/statusnet/statusnet.php',
  327. '/etc/statusnet/laconica.php',
  328. '/etc/laconica/laconica.php',
  329. '/etc/statusnet/'.$_server.'.php',
  330. '/etc/laconica/'.$_server.'.php');
  331. if (strlen($_path) > 0) {
  332. $config_files[] = '/etc/statusnet/'.$_server.'_'.$_path.'.php';
  333. $config_files[] = '/etc/laconica/'.$_server.'_'.$_path.'.php';
  334. }
  335. $config_files[] = INSTALLDIR.'/config.php';
  336. }
  337. self::$have_config = false;
  338. foreach ($config_files as $_config_file) {
  339. if (@file_exists($_config_file)) {
  340. // Ignore 0-byte config files
  341. if (filesize($_config_file) > 0) {
  342. common_log(LOG_INFO, "Including config file: " . $_config_file);
  343. include($_config_file);
  344. self::$have_config = true;
  345. }
  346. }
  347. }
  348. if (!self::$have_config) {
  349. throw new NoConfigException("No configuration file found.",
  350. $config_files);
  351. }
  352. // Check for database server; must exist!
  353. if (empty($config['db']['database'])) {
  354. throw new ServerException("No database server for this site.");
  355. }
  356. }
  357. /**
  358. * Are we running from the web with HTTPS?
  359. *
  360. * @return boolean true if we're running with HTTPS; else false
  361. */
  362. static function isHTTPS()
  363. {
  364. // There are some exceptions to this; add them here!
  365. if (empty($_SERVER['HTTPS'])) {
  366. return false;
  367. }
  368. // If it is _not_ "off", it is on, so "true".
  369. return strtolower($_SERVER['HTTPS']) !== 'off';
  370. }
  371. /**
  372. * Can we use HTTPS? Then do! Only return false if it's not configured ("never").
  373. */
  374. static function useHTTPS()
  375. {
  376. return self::isHTTPS() || common_config('site', 'ssl') != 'never';
  377. }
  378. }
  379. class NoConfigException extends Exception
  380. {
  381. public $configFiles;
  382. function __construct($msg, $configFiles) {
  383. parent::__construct($msg);
  384. $this->configFiles = $configFiles;
  385. }
  386. }