]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/gnusocial.php
Merge remote-tracking branch 'upstream/nightly' into nightly
[quix0rs-gnu-social.git] / lib / gnusocial.php
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
21 if (!defined('GNUSOCIAL')) { exit(1); }
22
23 global $config, $_server, $_path;
24
25 /**
26  * Global configuration setup and management.
27  */
28 class GNUsocial
29 {
30     protected static $config_files = array();
31     protected static $have_config;
32     protected static $is_api;
33     protected static $is_ajax;
34     protected static $plugins = array();
35
36     /**
37      * Configure and instantiate a plugin into the current configuration.
38      * Class definitions will be loaded from standard paths if necessary.
39      * Note that initialization events won't be fired until later.
40      *
41      * @param string $name class name & plugin file/subdir name
42      * @param array $attrs key/value pairs of public attributes to set on plugin instance
43      *
44      * @throws ServerException if plugin can't be found
45      */
46     public static function addPlugin($name, array $attrs=array())
47     {
48         $name = ucfirst($name);
49
50         if (isset(self::$plugins[$name])) {
51             // We have already loaded this plugin. Don't try to
52             // do it again with (possibly) different values.
53             // Försten till kvarn får mala.
54             return true;
55         }
56
57         $pluginclass = "{$name}Plugin";
58
59         if (!class_exists($pluginclass)) {
60
61             $files = array("local/plugins/{$pluginclass}.php",
62                            "local/plugins/{$name}/{$pluginclass}.php",
63                            "local/{$pluginclass}.php",
64                            "local/{$name}/{$pluginclass}.php",
65                            "plugins/{$pluginclass}.php",
66                            "plugins/{$name}/{$pluginclass}.php");
67
68             foreach ($files as $file) {
69                 $fullpath = INSTALLDIR.'/'.$file;
70                 if (@file_exists($fullpath)) {
71                     include_once($fullpath);
72                     break;
73                 }
74             }
75             if (!class_exists($pluginclass)) {
76                 throw new ServerException("Plugin $name not found.", 500);
77             }
78         }
79
80         // Doesn't this $inst risk being garbage collected or something?
81         // TODO: put into a static array that makes sure $inst isn't lost.
82         $inst = new $pluginclass();
83         foreach ($attrs as $aname => $avalue) {
84             $inst->$aname = $avalue;
85         }
86
87         // Record activated plugins for later display/config dump
88         self::$plugins[$name] = $attrs;
89
90         return true;
91     }
92
93     public static function delPlugin($name)
94     {
95         // Remove our plugin if it was previously loaded
96         $name = ucfirst($name);
97         if (isset(self::$plugins[$name])) {
98             unset(self::$plugins[$name]);
99         }
100
101         // make sure initPlugins will avoid this
102         common_config_set('plugins', 'disable-'.$name, true);
103
104         return true;
105     }
106
107     /**
108      * Get a list of activated plugins in this process.
109      * @return array of (string $name, array $args) pairs
110      */
111     public static function getActivePlugins()
112     {
113         return self::$plugins;
114     }
115
116     /**
117      * Initialize, or re-initialize, GNU social global configuration
118      * and plugins.
119      *
120      * If switching site configurations during script execution, be
121      * careful when working with leftover objects -- global settings
122      * affect many things and they may not behave as you expected.
123      *
124      * @param $server optional web server hostname for picking config
125      * @param $path optional URL path for picking config
126      * @param $conffile optional configuration file path
127      *
128      * @throws NoConfigException if config file can't be found
129      */
130     public static function init($server=null, $path=null, $conffile=null)
131     {
132         Router::clear();
133
134         self::initDefaults($server, $path);
135         self::loadConfigFile($conffile);
136
137         $sprofile = common_config('site', 'profile');
138         if (!empty($sprofile)) {
139             self::loadSiteProfile($sprofile);
140         }
141         // Load settings from database; note we need autoload for this
142         Config::loadSettings();
143
144         self::fillConfigVoids();
145         self::verifyLoadedConfig();
146
147         self::initPlugins();
148     }
149
150     /**
151      * Get identifier of the currently active site configuration
152      * @return string
153      */
154     public static function currentSite()
155     {
156         return common_config('site', 'nickname');
157     }
158
159     /**
160      * Change site configuration to site specified by nickname,
161      * if set up via Status_network. If not, sites other than
162      * the current will fail horribly.
163      *
164      * May throw exception or trigger a fatal error if the given
165      * site is missing or configured incorrectly.
166      *
167      * @param string $nickname
168      */
169     public static function switchSite($nickname)
170     {
171         if ($nickname == self::currentSite()) {
172             return true;
173         }
174
175         $sn = Status_network::getKV('nickname', $nickname);
176         if (empty($sn)) {
177             return false;
178             throw new Exception("No such site nickname '$nickname'");
179         }
180
181         $server = $sn->getServerName();
182         self::init($server);
183     }
184
185     /**
186      * Pull all local sites from status_network table.
187      *
188      * Behavior undefined if site is not configured via Status_network.
189      *
190      * @return array of nicknames
191      */
192     public static function findAllSites()
193     {
194         $sites = array();
195         $sn = new Status_network();
196         $sn->find();
197         while ($sn->fetch()) {
198             $sites[] = $sn->nickname;
199         }
200         return $sites;
201     }
202
203     /**
204      * Fire initialization events for all instantiated plugins.
205      */
206     protected static function initPlugins()
207     {
208         // User config may have already added some of these plugins, with
209         // maybe configured parameters. The self::addPlugin function will
210         // ignore the new call if it has already been instantiated.
211
212         // Load core plugins
213         foreach (common_config('plugins', 'core') as $name => $params) {
214             call_user_func('self::addPlugin', $name, $params);
215         }
216
217         // Load default plugins
218         foreach (common_config('plugins', 'default') as $name => $params) {
219             $key = 'disable-' . $name;
220             if (common_config('plugins', $key)) {
221                 continue;
222             }
223
224             // TODO: We should be able to avoid this is_null and assume $params
225             // is an array, since that's how it is typed in addPlugin
226             if (is_null($params)) {
227                 self::addPlugin($name);
228             } else if (is_array($params)) {
229                 if (count($params) == 0) {
230                     self::addPlugin($name);
231                 } else {
232                     $keys = array_keys($params);
233                     if (is_string($keys[0])) {
234                         self::addPlugin($name, $params);
235                     } else {
236                         foreach ($params as $paramset) {
237                             self::addPlugin($name, $paramset);
238                         }
239                     }
240                 }
241             }
242         }
243
244         // XXX: if plugins should check the schema at runtime, do that here.
245         if (common_config('db', 'schemacheck') == 'runtime') {
246             Event::handle('CheckSchema');
247         }
248
249         // Give plugins a chance to initialize in a fully-prepared environment
250         Event::handle('InitializePlugin');
251     }
252
253     /**
254      * Quick-check if configuration has been established.
255      * Useful for functions which may get used partway through
256      * initialization to back off from fancier things.
257      *
258      * @return bool
259      */
260     public static function haveConfig()
261     {
262         return self::$have_config;
263     }
264
265     /**
266      * Returns a list of configuration files that have
267      * been loaded for this instance of GNU social.
268      */
269     public static function configFiles()
270     {
271         return self::$config_files;
272     }
273
274     public static function isApi()
275     {
276         return self::$is_api;
277     }
278
279     public static function setApi($mode)
280     {
281         self::$is_api = $mode;
282     }
283
284     public static function isAjax()
285     {
286         return self::$is_ajax;
287     }
288
289     public static function setAjax($mode)
290     {
291         self::$is_ajax = $mode;
292     }
293
294     /**
295      * Build default configuration array
296      * @return array
297      */
298     protected static function defaultConfig()
299     {
300         global $_server, $_path;
301         require(INSTALLDIR.'/lib/default.php');
302         return $default;
303     }
304
305     /**
306      * Establish default configuration based on given or default server and path
307      * Sets global $_server, $_path, and $config
308      */
309     public static function initDefaults($server, $path)
310     {
311         global $_server, $_path, $config, $_PEAR;
312
313         Event::clearHandlers();
314         self::$plugins = array();
315
316         // try to figure out where we are. $server and $path
317         // can be set by including module, else we guess based
318         // on HTTP info.
319
320         if (isset($server)) {
321             $_server = $server;
322         } else {
323             $_server = array_key_exists('SERVER_NAME', $_SERVER) ?
324               strtolower($_SERVER['SERVER_NAME']) :
325             null;
326         }
327
328         if (isset($path)) {
329             $_path = $path;
330         } else {
331             $_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ?
332               self::_sn_to_path($_SERVER['SCRIPT_NAME']) :
333             null;
334         }
335
336         // Set config values initially to default values
337         $default = self::defaultConfig();
338         $config = $default;
339
340         // default configuration, overwritten in config.php
341         // Keep DB_DataObject's db config synced to ours...
342
343         $config['db'] = &$_PEAR->getStaticProperty('DB_DataObject','options');
344
345         $config['db'] = $default['db'];
346
347         if (function_exists('date_default_timezone_set')) {
348             /* Work internally in UTC */
349             date_default_timezone_set('UTC');
350         }
351     }
352
353     public static function loadSiteProfile($name)
354     {
355         global $config;
356         $settings = SiteProfile::getSettings($name);
357         $config = array_replace_recursive($config, $settings);
358     }
359
360     protected static function _sn_to_path($sn)
361     {
362         $past_root = substr($sn, 1);
363         $last_slash = strrpos($past_root, '/');
364         if ($last_slash > 0) {
365             $p = substr($past_root, 0, $last_slash);
366         } else {
367             $p = '';
368         }
369         return $p;
370     }
371
372     /**
373      * Load the default or specified configuration file.
374      * Modifies global $config and may establish plugins.
375      *
376      * @throws NoConfigException
377      */
378     protected static function loadConfigFile($conffile=null)
379     {
380         global $_server, $_path, $config;
381
382         // From most general to most specific:
383         // server-wide, then vhost-wide, then for a path,
384         // finally for a dir (usually only need one of the last two).
385
386         if (isset($conffile)) {
387             $config_files = array($conffile);
388         } else {
389             $config_files = array('/etc/gnusocial/config.php',
390                                   '/etc/gnusocial/config.d/'.$_server.'.php');
391
392             if (strlen($_path) > 0) {
393                 $config_files[] = '/etc/gnusocial/config.d/'.$_server.'_'.$_path.'.php';
394             }
395
396             $config_files[] = INSTALLDIR.'/config.php';
397         }
398
399         self::$have_config = false;
400
401         foreach ($config_files as $_config_file) {
402             if (@file_exists($_config_file)) {
403                 // Ignore 0-byte config files
404                 if (filesize($_config_file) > 0) {
405                     include($_config_file);
406                     self::$config_files[] = $_config_file;
407                     self::$have_config = true;
408                 }
409             }
410         }
411
412         if (!self::$have_config) {
413             throw new NoConfigException("No configuration file found.",
414                                         $config_files);
415         }
416
417         // Check for database server; must exist!
418
419         if (empty($config['db']['database'])) {
420             throw new ServerException("No database server for this site.");
421         }
422     }
423
424     static function fillConfigVoids()
425     {
426         // special cases on empty configuration options
427         if (!common_config('thumbnail', 'dir')) {
428             common_config_set('thumbnail', 'dir', File::path('thumb'));
429         }
430     }
431
432     /**
433      * Verify that the loaded config is good. Not complete, but will
434      * throw exceptions on common configuration problems I hope.
435      *
436      * Might make changes to the filesystem, to created dirs, but will
437      * not make database changes.
438      */
439     static function verifyLoadedConfig()
440     {
441         $mkdirs = [];
442
443         if (common_config('htmlpurifier', 'Cache.DefinitionImpl') === 'Serializer'
444                 && !is_dir(common_config('htmlpurifier', 'Cache.SerializerPath'))) {
445             $mkdirs[common_config('htmlpurifier', 'Cache.SerializerPath')] = 'HTMLPurifier Serializer cache';
446         }
447
448         // go through our configurable storage directories
449         foreach (['attachments', 'thumbnail'] as $dirtype) {
450             $dir = common_config($dirtype, 'dir');
451             if (!empty($dir) && !is_dir($dir)) {
452                 $mkdirs[$dir] = $dirtype;
453             }
454         }
455
456         // try to create those that are not directories
457         foreach ($mkdirs as $dir=>$description) {
458             if (is_file($dir)) {
459                 throw new ConfigException('Expected directory for '._ve($description).' is a file!');
460             }
461             if (!mkdir($dir)) {
462                 throw new ConfigException('Could not create directory for '._ve($description).': '._ve($dir));
463             }
464             if (!chmod($dir, 0775)) {
465                 common_log(LOG_WARNING, 'Could not chmod 0775 on directory for '._ve($description).': '._ve($dir));
466             }
467         }
468
469         if (!is_array(common_config('public', 'autosource'))) {
470             throw new ConfigException('Configuration option public/autosource is not an array.');
471         }
472     }
473
474     /**
475      * Are we running from the web with HTTPS?
476      *
477      * @return boolean true if we're running with HTTPS; else false
478      */
479
480     static function isHTTPS()
481     {
482         if (common_config('site', 'sslproxy')) {
483             return true;
484         }
485
486         // There are some exceptions to this; add them here!
487         if (empty($_SERVER['HTTPS'])) {
488             return false;
489         }
490
491         // If it is _not_ "off", it is on, so "true".
492         return strtolower($_SERVER['HTTPS']) !== 'off';
493     }
494
495     /**
496      * Can we use HTTPS? Then do! Only return false if it's not configured ("never").
497      */
498     static function useHTTPS()
499     {
500         return self::isHTTPS() || common_config('site', 'ssl') != 'never';
501     }
502 }
503
504 class NoConfigException extends Exception
505 {
506     public $configFiles;
507
508     function __construct($msg, $configFiles) {
509         parent::__construct($msg);
510         $this->configFiles = $configFiles;
511     }
512 }