]> git.mxchange.org Git - friendica.git/blob - src/App.php
Merge pull request #3423 from Hypolite/improvement/move-app-to-src-2
[friendica.git] / src / App.php
1 <?php\r
2 \r
3 namespace Friendica;\r
4 \r
5 use Friendica\Core\Config;\r
6 use Friendica\Core\PConfig;\r
7 \r
8 /**\r
9  *\r
10  * class: App\r
11  *\r
12  * @brief Our main application structure for the life of this page.\r
13  *\r
14  * Primarily deals with the URL that got us here\r
15  * and tries to make some sense of it, and\r
16  * stores our page contents and config storage\r
17  * and anything else that might need to be passed around\r
18  * before we spit the page out.\r
19  *\r
20  */\r
21 class App {\r
22 \r
23         public $module_loaded = false;\r
24         public $query_string;\r
25         public $config;\r
26         public $page;\r
27         public $profile;\r
28         public $profile_uid;\r
29         public $user;\r
30         public $cid;\r
31         public $contact;\r
32         public $contacts;\r
33         public $page_contact;\r
34         public $content;\r
35         public $data = array();\r
36         public $error = false;\r
37         public $cmd;\r
38         public $argv;\r
39         public $argc;\r
40         public $module;\r
41         public $pager;\r
42         public $strings;\r
43         public $basepath;\r
44         public $path;\r
45         public $hooks;\r
46         public $timezone;\r
47         public $interactive = true;\r
48         public $plugins;\r
49         public $apps = array();\r
50         public $identities;\r
51         public $is_mobile = false;\r
52         public $is_tablet = false;\r
53         public $is_friendica_app;\r
54         public $performance = array();\r
55         public $callstack = array();\r
56         public $theme_info = array();\r
57         public $backend = true;\r
58         public $nav_sel;\r
59         public $category;\r
60         // Allow themes to control internal parameters\r
61         // by changing App values in theme.php\r
62 \r
63         public $sourcename = '';\r
64         public $videowidth = 425;\r
65         public $videoheight = 350;\r
66         public $force_max_items = 0;\r
67         public $theme_thread_allow = true;\r
68         public $theme_events_in_profile = true;\r
69 \r
70         /**\r
71          * @brief An array for all theme-controllable parameters\r
72          *\r
73          * Mostly unimplemented yet. Only options 'template_engine' and\r
74          * beyond are used.\r
75          */\r
76         public $theme = array(\r
77                 'sourcename' => '',\r
78                 'videowidth' => 425,\r
79                 'videoheight' => 350,\r
80                 'force_max_items' => 0,\r
81                 'thread_allow' => true,\r
82                 'stylesheet' => '',\r
83                 'template_engine' => 'smarty3',\r
84         );\r
85 \r
86         /**\r
87          * @brief An array of registered template engines ('name'=>'class name')\r
88          */\r
89         public $template_engines = array();\r
90 \r
91         /**\r
92          * @brief An array of instanced template engines ('name'=>'instance')\r
93          */\r
94         public $template_engine_instance = array();\r
95         public $process_id;\r
96         private $ldelim = array(\r
97                 'internal' => '',\r
98                 'smarty3' => '{{'\r
99         );\r
100         private $rdelim = array(\r
101                 'internal' => '',\r
102                 'smarty3' => '}}'\r
103         );\r
104         private $scheme;\r
105         private $hostname;\r
106         private $db;\r
107         private $curl_code;\r
108         private $curl_content_type;\r
109         private $curl_headers;\r
110         private $cached_profile_image;\r
111         private $cached_profile_picdate;\r
112         private static $a;\r
113 \r
114         /**\r
115          * @brief App constructor.\r
116          *\r
117          * @param string $basepath Path to the app base folder\r
118          */\r
119         function __construct($basepath) {\r
120 \r
121                 global $default_timezone;\r
122 \r
123                 $hostname = '';\r
124 \r
125                 if (file_exists('.htpreconfig.php')) {\r
126                         include '.htpreconfig.php';\r
127                 }\r
128 \r
129                 $this->timezone = ((x($default_timezone)) ? $default_timezone : 'UTC');\r
130 \r
131                 date_default_timezone_set($this->timezone);\r
132 \r
133                 $this->performance['start'] = microtime(true);\r
134                 $this->performance['database'] = 0;\r
135                 $this->performance['database_write'] = 0;\r
136                 $this->performance['network'] = 0;\r
137                 $this->performance['file'] = 0;\r
138                 $this->performance['rendering'] = 0;\r
139                 $this->performance['parser'] = 0;\r
140                 $this->performance['marktime'] = 0;\r
141                 $this->performance['markstart'] = microtime(true);\r
142 \r
143                 $this->callstack['database'] = array();\r
144                 $this->callstack['database_write'] = array();\r
145                 $this->callstack['network'] = array();\r
146                 $this->callstack['file'] = array();\r
147                 $this->callstack['rendering'] = array();\r
148                 $this->callstack['parser'] = array();\r
149 \r
150                 $this->config = array();\r
151                 $this->page = array();\r
152                 $this->pager = array();\r
153 \r
154                 $this->query_string = '';\r
155 \r
156                 $this->process_id = uniqid('log', true);\r
157 \r
158                 startup();\r
159 \r
160                 $this->scheme = 'http';\r
161 \r
162                 if ((x($_SERVER, 'HTTPS') && $_SERVER['HTTPS']) ||\r
163                         (x($_SERVER, 'HTTP_FORWARDED') && preg_match('/proto=https/', $_SERVER['HTTP_FORWARDED'])) ||\r
164                         (x($_SERVER, 'HTTP_X_FORWARDED_PROTO') && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ||\r
165                         (x($_SERVER, 'HTTP_X_FORWARDED_SSL') && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') ||\r
166                         (x($_SERVER, 'FRONT_END_HTTPS') && $_SERVER['FRONT_END_HTTPS'] == 'on') ||\r
167                         (x($_SERVER, 'SERVER_PORT') && (intval($_SERVER['SERVER_PORT']) == 443)) // XXX: reasonable assumption, but isn't this hardcoding too much?\r
168                 ) {\r
169                         $this->scheme = 'https';\r
170                 }\r
171 \r
172                 if (x($_SERVER, 'SERVER_NAME')) {\r
173                         $this->hostname = $_SERVER['SERVER_NAME'];\r
174 \r
175                         if (x($_SERVER, 'SERVER_PORT') && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {\r
176                                 $this->hostname .= ':' . $_SERVER['SERVER_PORT'];\r
177                         }\r
178                         /*\r
179                          * Figure out if we are running at the top of a domain\r
180                          * or in a sub-directory and adjust accordingly\r
181                          */\r
182 \r
183                         /// @TODO This kind of escaping breaks syntax-highlightning on CoolEdit (Midnight Commander)\r
184                         $path = trim(dirname($_SERVER['SCRIPT_NAME']), '/\\');\r
185                         if (isset($path) && strlen($path) && ($path != $this->path)) {\r
186                                 $this->path = $path;\r
187                         }\r
188                 }\r
189 \r
190                 if ($hostname != '') {\r
191                         $this->hostname = $hostname;\r
192                 }\r
193 \r
194                 if (! static::directory_usable($basepath)) {\r
195                         throw new Exception('Basepath ' . $basepath . ' isn\'t usable.');\r
196                 }\r
197 \r
198                 $this->basepath = rtrim($basepath, DIRECTORY_SEPARATOR);\r
199 \r
200                 set_include_path(\r
201                         get_include_path() . PATH_SEPARATOR\r
202                         . $this->basepath . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR\r
203                         . $this->basepath . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR\r
204                         . $this->basepath . DIRECTORY_SEPARATOR . 'library/langdet' . PATH_SEPARATOR\r
205                         . $this->basepath);\r
206 \r
207 \r
208                 if (is_array($_SERVER['argv']) && $_SERVER['argc'] > 1 && substr(end($_SERVER['argv']), 0, 4) == 'http') {\r
209                         $this->set_baseurl(array_pop($_SERVER['argv']));\r
210                         $_SERVER['argc'] --;\r
211                 }\r
212 \r
213                 if ((x($_SERVER, 'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'], 0, 9) === 'pagename=') {\r
214                         $this->query_string = substr($_SERVER['QUERY_STRING'], 9);\r
215 \r
216                         // removing trailing / - maybe a nginx problem\r
217                         $this->query_string = ltrim($this->query_string, '/');\r
218                 } elseif ((x($_SERVER, 'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'], 0, 2) === 'q=') {\r
219                         $this->query_string = substr($_SERVER['QUERY_STRING'], 2);\r
220 \r
221                         // removing trailing / - maybe a nginx problem\r
222                         $this->query_string = ltrim($this->query_string, '/');\r
223                 }\r
224 \r
225                 if (x($_GET, 'pagename')) {\r
226                         $this->cmd = trim($_GET['pagename'], '/\\');\r
227                 } elseif (x($_GET, 'q')) {\r
228                         $this->cmd = trim($_GET['q'], '/\\');\r
229                 }\r
230 \r
231                 // fix query_string\r
232                 $this->query_string = str_replace($this->cmd . '&', $this->cmd . '?', $this->query_string);\r
233 \r
234                 // unix style "homedir"\r
235                 if (substr($this->cmd, 0, 1) === '~') {\r
236                         $this->cmd = 'profile/' . substr($this->cmd, 1);\r
237                 }\r
238 \r
239                 // Diaspora style profile url\r
240                 if (substr($this->cmd, 0, 2) === 'u/') {\r
241                         $this->cmd = 'profile/' . substr($this->cmd, 2);\r
242                 }\r
243 \r
244                 /*\r
245                  * Break the URL path into C style argc/argv style arguments for our\r
246                  * modules. Given "http://example.com/module/arg1/arg2", $this->argc\r
247                  * will be 3 (integer) and $this->argv will contain:\r
248                  *   [0] => 'module'\r
249                  *   [1] => 'arg1'\r
250                  *   [2] => 'arg2'\r
251                  *\r
252                  *\r
253                  * There will always be one argument. If provided a naked domain\r
254                  * URL, $this->argv[0] is set to "home".\r
255                  */\r
256 \r
257                 $this->argv = explode('/', $this->cmd);\r
258                 $this->argc = count($this->argv);\r
259                 if ((array_key_exists('0', $this->argv)) && strlen($this->argv[0])) {\r
260                         $this->module = str_replace('.', '_', $this->argv[0]);\r
261                         $this->module = str_replace('-', '_', $this->module);\r
262                 } else {\r
263                         $this->argc = 1;\r
264                         $this->argv = array('home');\r
265                         $this->module = 'home';\r
266                 }\r
267 \r
268                 // See if there is any page number information, and initialise pagination\r
269                 $this->pager['page'] = ((x($_GET, 'page') && intval($_GET['page']) > 0) ? intval($_GET['page']) : 1);\r
270                 $this->pager['itemspage'] = 50;\r
271                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];\r
272 \r
273                 if ($this->pager['start'] < 0) {\r
274                         $this->pager['start'] = 0;\r
275                 }\r
276                 $this->pager['total'] = 0;\r
277 \r
278                 // Detect mobile devices\r
279                 $mobile_detect = new \Mobile_Detect();\r
280                 $this->is_mobile = $mobile_detect->isMobile();\r
281                 $this->is_tablet = $mobile_detect->isTablet();\r
282 \r
283                 // Friendica-Client\r
284                 $this->is_friendica_app = ($_SERVER['HTTP_USER_AGENT'] == 'Apache-HttpClient/UNAVAILABLE (java 1.4)');\r
285 \r
286                 // Register template engines\r
287                 $dc = get_declared_classes();\r
288                 foreach ($dc as $k) {\r
289                         if (in_array('ITemplateEngine', class_implements($k))) {\r
290                                 $this->register_template_engine($k);\r
291                         }\r
292                 }\r
293 \r
294                 self::$a = $this;\r
295         }\r
296 \r
297         /**\r
298          * @brief Returns the base filesystem path of the App\r
299          *\r
300          * It first checks for the internal variable, then for DOCUMENT_ROOT and\r
301          * finally for PWD\r
302          *\r
303          * @return string\r
304          */\r
305         public static function get_basepath() {\r
306                 if (isset($this)) {\r
307                         $basepath = $this->basepath;\r
308                 }\r
309 \r
310                 if (! $basepath) {\r
311                         $basepath = Config::get('system', 'basepath');\r
312                 }\r
313 \r
314                 if (! $basepath && x($_SERVER, 'DOCUMENT_ROOT')) {\r
315                         $basepath = $_SERVER['DOCUMENT_ROOT'];\r
316                 }\r
317 \r
318                 if (! $basepath && x($_SERVER, 'PWD')) {\r
319                         $basepath = $_SERVER['PWD'];\r
320                 }\r
321 \r
322                 return $basepath;\r
323         }\r
324 \r
325         function get_scheme() {\r
326                 return $this->scheme;\r
327         }\r
328 \r
329         /**\r
330          * @brief Retrieves the Friendica instance base URL\r
331          *\r
332          * This function assembles the base URL from multiple parts:\r
333          * - Protocol is determined either by the request or a combination of\r
334          * system.ssl_policy and the $ssl parameter.\r
335          * - Host name is determined either by system.hostname or inferred from request\r
336          * - Path is inferred from SCRIPT_NAME\r
337          *\r
338          * Note: $ssl parameter value doesn't directly correlate with the resulting protocol\r
339          *\r
340          * @param bool $ssl Whether to append http or https under SSL_POLICY_SELFSIGN\r
341          * @return string Friendica server base URL\r
342          */\r
343         function get_baseurl($ssl = false) {\r
344                 // Is the function called statically?\r
345                 if (!(isset($this) && get_class($this) == __CLASS__)) {\r
346                         return self::$a->get_baseurl($ssl);\r
347                 }\r
348 \r
349                 $scheme = $this->scheme;\r
350 \r
351                 if (Config::get('system', 'ssl_policy') == SSL_POLICY_FULL) {\r
352                         $scheme = 'https';\r
353                 }\r
354 \r
355                 //      Basically, we have $ssl = true on any links which can only be seen by a logged in user\r
356                 //      (and also the login link). Anything seen by an outsider will have it turned off.\r
357 \r
358                 if (Config::get('system', 'ssl_policy') == SSL_POLICY_SELFSIGN) {\r
359                         if ($ssl) {\r
360                                 $scheme = 'https';\r
361                         } else {\r
362                                 $scheme = 'http';\r
363                         }\r
364                 }\r
365 \r
366                 if (Config::get('config', 'hostname') != '') {\r
367                         $this->hostname = Config::get('config', 'hostname');\r
368                 }\r
369 \r
370                 return $scheme . '://' . $this->hostname . ((isset($this->path) && strlen($this->path)) ? '/' . $this->path : '' );\r
371         }\r
372 \r
373         /**\r
374          * @brief Initializes the baseurl components\r
375          *\r
376          * Clears the baseurl cache to prevent inconstistencies\r
377          *\r
378          * @param string $url\r
379          */\r
380         function set_baseurl($url) {\r
381                 $parsed = @parse_url($url);\r
382 \r
383                 if ($parsed) {\r
384                         $this->scheme = $parsed['scheme'];\r
385 \r
386                         $hostname = $parsed['host'];\r
387                         if (x($parsed, 'port')) {\r
388                                 $hostname .= ':' . $parsed['port'];\r
389                         }\r
390                         if (x($parsed, 'path')) {\r
391                                 $this->path = trim($parsed['path'], '\\/');\r
392                         }\r
393 \r
394                         if (file_exists('.htpreconfig.php')) {\r
395                                 include '.htpreconfig.php';\r
396                         }\r
397 \r
398                         if (Config::get('config', 'hostname') != '') {\r
399                                 $this->hostname = Config::get('config', 'hostname');\r
400                         }\r
401 \r
402                         if (!isset($this->hostname) OR ( $this->hostname == '')) {\r
403                                 $this->hostname = $hostname;\r
404                         }\r
405                 }\r
406         }\r
407 \r
408         function get_hostname() {\r
409                 if (Config::get('config', 'hostname') != '') {\r
410                         $this->hostname = Config::get('config', 'hostname');\r
411                 }\r
412 \r
413                 return $this->hostname;\r
414         }\r
415 \r
416         function set_hostname($h) {\r
417                 $this->hostname = $h;\r
418         }\r
419 \r
420         function set_path($p) {\r
421                 $this->path = trim(trim($p), '/');\r
422         }\r
423 \r
424         function get_path() {\r
425                 return $this->path;\r
426         }\r
427 \r
428         function set_pager_total($n) {\r
429                 $this->pager['total'] = intval($n);\r
430         }\r
431 \r
432         function set_pager_itemspage($n) {\r
433                 $this->pager['itemspage'] = ((intval($n) > 0) ? intval($n) : 0);\r
434                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];\r
435         }\r
436 \r
437         function set_pager_page($n) {\r
438                 $this->pager['page'] = $n;\r
439                 $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];\r
440         }\r
441 \r
442         function init_pagehead() {\r
443                 $interval = ((local_user()) ? PConfig::get(local_user(), 'system', 'update_interval') : 40000);\r
444 \r
445                 // If the update is 'deactivated' set it to the highest integer number (~24 days)\r
446                 if ($interval < 0) {\r
447                         $interval = 2147483647;\r
448                 }\r
449 \r
450                 if ($interval < 10000) {\r
451                         $interval = 40000;\r
452                 }\r
453 \r
454                 // compose the page title from the sitename and the\r
455                 // current module called\r
456                 if (!$this->module == '') {\r
457                         $this->page['title'] = $this->config['sitename'] . ' (' . $this->module . ')';\r
458                 } else {\r
459                         $this->page['title'] = $this->config['sitename'];\r
460                 }\r
461 \r
462                 /* put the head template at the beginning of page['htmlhead']\r
463                  * since the code added by the modules frequently depends on it\r
464                  * being first\r
465                  */\r
466                 if (!isset($this->page['htmlhead'])) {\r
467                         $this->page['htmlhead'] = '';\r
468                 }\r
469 \r
470                 // If we're using Smarty, then doing replace_macros() will replace\r
471                 // any unrecognized variables with a blank string. Since we delay\r
472                 // replacing $stylesheet until later, we need to replace it now\r
473                 // with another variable name\r
474                 if ($this->theme['template_engine'] === 'smarty3') {\r
475                         $stylesheet = $this->get_template_ldelim('smarty3') . '$stylesheet' . $this->get_template_rdelim('smarty3');\r
476                 } else {\r
477                         $stylesheet = '$stylesheet';\r
478                 }\r
479 \r
480                 $shortcut_icon = Config::get('system', 'shortcut_icon');\r
481                 if ($shortcut_icon == '') {\r
482                         $shortcut_icon = 'images/friendica-32.png';\r
483                 }\r
484 \r
485                 $touch_icon = Config::get('system', 'touch_icon');\r
486                 if ($touch_icon == '') {\r
487                         $touch_icon = 'images/friendica-128.png';\r
488                 }\r
489 \r
490                 // get data wich is needed for infinite scroll on the network page\r
491                 $invinite_scroll = infinite_scroll_data($this->module);\r
492 \r
493                 $tpl = get_markup_template('head.tpl');\r
494                 $this->page['htmlhead'] = replace_macros($tpl, array(\r
495                                 '$baseurl' => $this->get_baseurl(), // FIXME for z_path!!!!\r
496                                 '$local_user' => local_user(),\r
497                                 '$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION,\r
498                                 '$delitem' => t('Delete this item?'),\r
499                                 '$showmore' => t('show more'),\r
500                                 '$showfewer' => t('show fewer'),\r
501                                 '$update_interval' => $interval,\r
502                                 '$shortcut_icon' => $shortcut_icon,\r
503                                 '$touch_icon' => $touch_icon,\r
504                                 '$stylesheet' => $stylesheet,\r
505                                 '$infinite_scroll' => $invinite_scroll,\r
506                         )) . $this->page['htmlhead'];\r
507         }\r
508 \r
509         function init_page_end() {\r
510                 if (!isset($this->page['end'])) {\r
511                         $this->page['end'] = '';\r
512                 }\r
513                 $tpl = get_markup_template('end.tpl');\r
514                 $this->page['end'] = replace_macros($tpl, array(\r
515                                 '$baseurl' => $this->get_baseurl() // FIXME for z_path!!!!\r
516                         )) . $this->page['end'];\r
517         }\r
518 \r
519         function set_curl_code($code) {\r
520                 $this->curl_code = $code;\r
521         }\r
522 \r
523         function get_curl_code() {\r
524                 return $this->curl_code;\r
525         }\r
526 \r
527         function set_curl_content_type($content_type) {\r
528                 $this->curl_content_type = $content_type;\r
529         }\r
530 \r
531         function get_curl_content_type() {\r
532                 return $this->curl_content_type;\r
533         }\r
534 \r
535         function set_curl_headers($headers) {\r
536                 $this->curl_headers = $headers;\r
537         }\r
538 \r
539         function get_curl_headers() {\r
540                 return $this->curl_headers;\r
541         }\r
542 \r
543         function get_cached_avatar_image($avatar_image) {\r
544                 return $avatar_image;\r
545         }\r
546 \r
547         /**\r
548          * @brief Removes the baseurl from an url. This avoids some mixed content problems.\r
549          *\r
550          * @param string $orig_url\r
551          *\r
552          * @return string The cleaned url\r
553          */\r
554         function remove_baseurl($orig_url) {\r
555 \r
556                 // Is the function called statically?\r
557                 if (!(isset($this) && get_class($this) == __CLASS__)) {\r
558                         return self::$a->remove_baseurl($orig_url);\r
559                 }\r
560 \r
561                 // Remove the hostname from the url if it is an internal link\r
562                 $nurl = normalise_link($orig_url);\r
563                 $base = normalise_link($this->get_baseurl());\r
564                 $url = str_replace($base . '/', '', $nurl);\r
565 \r
566                 // if it is an external link return the orignal value\r
567                 if ($url == normalise_link($orig_url)) {\r
568                         return $orig_url;\r
569                 } else {\r
570                         return $url;\r
571                 }\r
572         }\r
573 \r
574         /**\r
575          * @brief Register template engine class\r
576          *\r
577          * If $name is '', is used class static property $class::$name\r
578          *\r
579          * @param string $class\r
580          * @param string $name\r
581          */\r
582         function register_template_engine($class, $name = '') {\r
583                 /// @TODO Really === and not just == ?\r
584                 if ($name === '') {\r
585                         $v = get_class_vars($class);\r
586                         if (x($v, 'name'))\r
587                                 $name = $v['name'];\r
588                 }\r
589                 if ($name === '') {\r
590                         echo "template engine <tt>$class</tt> cannot be registered without a name.\n";\r
591                         killme();\r
592                 }\r
593                 $this->template_engines[$name] = $class;\r
594         }\r
595 \r
596         /**\r
597          * @brief Return template engine instance.\r
598          *\r
599          * If $name is not defined, return engine defined by theme,\r
600          * or default\r
601          *\r
602          * @param strin $name Template engine name\r
603          * @return object Template Engine instance\r
604          */\r
605         function template_engine($name = '') {\r
606                 /// @TODO really type-check included?\r
607                 if ($name !== '') {\r
608                         $template_engine = $name;\r
609                 } else {\r
610                         $template_engine = 'smarty3';\r
611                         if (x($this->theme, 'template_engine')) {\r
612                                 $template_engine = $this->theme['template_engine'];\r
613                         }\r
614                 }\r
615 \r
616                 if (isset($this->template_engines[$template_engine])) {\r
617                         if (isset($this->template_engine_instance[$template_engine])) {\r
618                                 return $this->template_engine_instance[$template_engine];\r
619                         } else {\r
620                                 $class = $this->template_engines[$template_engine];\r
621                                 $obj = new $class;\r
622                                 $this->template_engine_instance[$template_engine] = $obj;\r
623                                 return $obj;\r
624                         }\r
625                 }\r
626 \r
627                 echo "template engine <tt>$template_engine</tt> is not registered!\n";\r
628                 killme();\r
629         }\r
630 \r
631         /**\r
632          * @brief Returns the active template engine.\r
633          *\r
634          * @return string\r
635          */\r
636         function get_template_engine() {\r
637                 return $this->theme['template_engine'];\r
638         }\r
639 \r
640         function set_template_engine($engine = 'smarty3') {\r
641                 $this->theme['template_engine'] = $engine;\r
642         }\r
643 \r
644         function get_template_ldelim($engine = 'smarty3') {\r
645                 return $this->ldelim[$engine];\r
646         }\r
647 \r
648         function get_template_rdelim($engine = 'smarty3') {\r
649                 return $this->rdelim[$engine];\r
650         }\r
651 \r
652         function save_timestamp($stamp, $value) {\r
653                 if (!isset($this->config['system']['profiler']) || !$this->config['system']['profiler']) {\r
654                         return;\r
655                 }\r
656 \r
657                 $duration = (float) (microtime(true) - $stamp);\r
658 \r
659                 if (!isset($this->performance[$value])) {\r
660                         // Prevent ugly E_NOTICE\r
661                         $this->performance[$value] = 0;\r
662                 }\r
663 \r
664                 $this->performance[$value] += (float) $duration;\r
665                 $this->performance['marktime'] += (float) $duration;\r
666 \r
667                 $callstack = $this->callstack();\r
668 \r
669                 if (!isset($this->callstack[$value][$callstack])) {\r
670                         // Prevent ugly E_NOTICE\r
671                         $this->callstack[$value][$callstack] = 0;\r
672                 }\r
673 \r
674                 $this->callstack[$value][$callstack] += (float) $duration;\r
675         }\r
676 \r
677         /**\r
678          * @brief Log active processes into the "process" table\r
679          */\r
680         function start_process() {\r
681                 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);\r
682 \r
683                 $command = basename($trace[0]['file']);\r
684 \r
685                 $this->remove_inactive_processes();\r
686 \r
687                 q('START TRANSACTION');\r
688 \r
689                 $r = q('SELECT `pid` FROM `process` WHERE `pid` = %d', intval(getmypid()));\r
690                 if (!\dbm::is_result($r)) {\r
691                         q("INSERT INTO `process` (`pid`,`command`,`created`) VALUES (%d, '%s', '%s')", intval(getmypid()), dbesc($command), dbesc(datetime_convert()));\r
692                 }\r
693                 q('COMMIT');\r
694         }\r
695 \r
696         /**\r
697          * @brief Remove inactive processes\r
698          */\r
699         function remove_inactive_processes() {\r
700                 q('START TRANSACTION');\r
701 \r
702                 $r = q('SELECT `pid` FROM `process`');\r
703                 if (\dbm::is_result($r)) {\r
704                         foreach ($r AS $process) {\r
705                                 if (!posix_kill($process['pid'], 0)) {\r
706                                         q('DELETE FROM `process` WHERE `pid` = %d', intval($process['pid']));\r
707                                 }\r
708                         }\r
709                 }\r
710                 q('COMMIT');\r
711         }\r
712 \r
713         /**\r
714          * @brief Remove the active process from the "process" table\r
715          */\r
716         function end_process() {\r
717                 q('DELETE FROM `process` WHERE `pid` = %d', intval(getmypid()));\r
718         }\r
719 \r
720         /**\r
721          * @brief Returns a string with a callstack. Can be used for logging.\r
722          *\r
723          * @return string\r
724          */\r
725         function callstack() {\r
726                 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6);\r
727 \r
728                 // We remove the first two items from the list since they contain data that we don't need.\r
729                 array_shift($trace);\r
730                 array_shift($trace);\r
731 \r
732                 $callstack = array();\r
733                 foreach ($trace AS $func) {\r
734                         $callstack[] = $func['function'];\r
735                 }\r
736 \r
737                 return implode(', ', $callstack);\r
738         }\r
739 \r
740         function get_useragent() {\r
741                 return\r
742                         FRIENDICA_PLATFORM . " '" .\r
743                         FRIENDICA_CODENAME . "' " .\r
744                         FRIENDICA_VERSION . '-' .\r
745                         DB_UPDATE_VERSION . '; ' .\r
746                         $this->get_baseurl();\r
747         }\r
748 \r
749         function is_friendica_app() {\r
750                 return $this->is_friendica_app;\r
751         }\r
752 \r
753         /**\r
754          * @brief Checks if the site is called via a backend process\r
755          *\r
756          * This isn't a perfect solution. But we need this check very early.\r
757          * So we cannot wait until the modules are loaded.\r
758          *\r
759          * @return bool Is it a known backend?\r
760          */\r
761         function is_backend() {\r
762                 static $backends = array();\r
763                 $backends[] = '_well_known';\r
764                 $backends[] = 'api';\r
765                 $backends[] = 'dfrn_notify';\r
766                 $backends[] = 'fetch';\r
767                 $backends[] = 'hcard';\r
768                 $backends[] = 'hostxrd';\r
769                 $backends[] = 'nodeinfo';\r
770                 $backends[] = 'noscrape';\r
771                 $backends[] = 'p';\r
772                 $backends[] = 'poco';\r
773                 $backends[] = 'post';\r
774                 $backends[] = 'proxy';\r
775                 $backends[] = 'pubsub';\r
776                 $backends[] = 'pubsubhubbub';\r
777                 $backends[] = 'receive';\r
778                 $backends[] = 'rsd_xml';\r
779                 $backends[] = 'salmon';\r
780                 $backends[] = 'statistics_json';\r
781                 $backends[] = 'xrd';\r
782 \r
783                 // Check if current module is in backend or backend flag is set\r
784                 return (in_array($this->module, $backends) || $this->backend);\r
785         }\r
786 \r
787         /**\r
788          * @brief Checks if the maximum number of database processes is reached\r
789          *\r
790          * @return bool Is the limit reached?\r
791          */\r
792         function max_processes_reached() {\r
793 \r
794                 if ($this->is_backend()) {\r
795                         $process = 'backend';\r
796                         $max_processes = Config::get('system', 'max_processes_backend');\r
797                         if (intval($max_processes) == 0) {\r
798                                 $max_processes = 5;\r
799                         }\r
800                 } else {\r
801                         $process = 'frontend';\r
802                         $max_processes = Config::get('system', 'max_processes_frontend');\r
803                         if (intval($max_processes) == 0) {\r
804                                 $max_processes = 20;\r
805                         }\r
806                 }\r
807 \r
808                 $processlist = \dbm::processlist();\r
809                 if ($processlist['list'] != '') {\r
810                         logger('Processcheck: Processes: ' . $processlist['amount'] . ' - Processlist: ' . $processlist['list'], LOGGER_DEBUG);\r
811 \r
812                         if ($processlist['amount'] > $max_processes) {\r
813                                 logger('Processcheck: Maximum number of processes for ' . $process . ' tasks (' . $max_processes . ') reached.', LOGGER_DEBUG);\r
814                                 return true;\r
815                         }\r
816                 }\r
817                 return false;\r
818         }\r
819 \r
820         /**\r
821          * @brief Checks if the minimal memory is reached\r
822          *\r
823          * @return bool Is the memory limit reached?\r
824          */\r
825         public function min_memory_reached() {\r
826                 $min_memory = Config::get('system', 'min_memory', 0);\r
827                 if ($min_memory == 0) {\r
828                         return false;\r
829                 }\r
830 \r
831                 if (!is_readable('/proc/meminfo')) {\r
832                         return false;\r
833                 }\r
834 \r
835                 $memdata = explode("\n", file_get_contents('/proc/meminfo'));\r
836 \r
837                 $meminfo = array();\r
838                 foreach ($memdata as $line) {\r
839                         list($key, $val) = explode(':', $line);\r
840                         $meminfo[$key] = (int) trim(str_replace('kB', '', $val));\r
841                         $meminfo[$key] = (int) ($meminfo[$key] / 1024);\r
842                 }\r
843 \r
844                 if (!isset($meminfo['MemAvailable']) OR ! isset($meminfo['MemFree'])) {\r
845                         return false;\r
846                 }\r
847 \r
848                 $free = $meminfo['MemAvailable'] + $meminfo['MemFree'];\r
849 \r
850                 $reached = ($free < $min_memory);\r
851 \r
852                 if ($reached) {\r
853                         logger('Minimal memory reached: ' . $free . '/' . $meminfo['MemTotal'] . ' - limit ' . $min_memory, LOGGER_DEBUG);\r
854                 }\r
855 \r
856                 return $reached;\r
857         }\r
858 \r
859         /**\r
860          * @brief Checks if the maximum load is reached\r
861          *\r
862          * @return bool Is the load reached?\r
863          */\r
864         function maxload_reached() {\r
865 \r
866                 if ($this->is_backend()) {\r
867                         $process = 'backend';\r
868                         $maxsysload = intval(Config::get('system', 'maxloadavg'));\r
869                         if ($maxsysload < 1) {\r
870                                 $maxsysload = 50;\r
871                         }\r
872                 } else {\r
873                         $process = 'frontend';\r
874                         $maxsysload = intval(Config::get('system', 'maxloadavg_frontend'));\r
875                         if ($maxsysload < 1) {\r
876                                 $maxsysload = 50;\r
877                         }\r
878                 }\r
879 \r
880                 $load = current_load();\r
881                 if ($load) {\r
882                         if (intval($load) > $maxsysload) {\r
883                                 logger('system: load ' . $load . ' for ' . $process . ' tasks (' . $maxsysload . ') too high.');\r
884                                 return true;\r
885                         }\r
886                 }\r
887                 return false;\r
888         }\r
889 \r
890         function proc_run($args) {\r
891 \r
892                 if (!function_exists('proc_open')) {\r
893                         return;\r
894                 }\r
895 \r
896                 // If the last worker fork was less than 10 seconds before then don't fork another one.\r
897                 // This should prevent the forking of masses of workers.\r
898                 $cachekey = 'app:proc_run:started';\r
899                 $result = \Cache::get($cachekey);\r
900 \r
901                 if (!is_null($result) AND ( time() - $result) < 10) {\r
902                         return;\r
903                 }\r
904 \r
905                 // Set the timestamp of the last proc_run\r
906                 \Cache::set($cachekey, time(), CACHE_MINUTE);\r
907 \r
908                 array_unshift($args, ((x($this->config, 'php_path')) && (strlen($this->config['php_path'])) ? $this->config['php_path'] : 'php'));\r
909 \r
910                 // add baseurl to args. cli scripts can't construct it\r
911                 $args[] = $this->get_baseurl();\r
912 \r
913                 for ($x = 0; $x < count($args); $x ++) {\r
914                         $args[$x] = escapeshellarg($args[$x]);\r
915                 }\r
916 \r
917                 $cmdline = implode($args, ' ');\r
918 \r
919                 if ($this->min_memory_reached()) {\r
920                         return;\r
921                 }\r
922 \r
923                 if (Config::get('system', 'proc_windows')) {\r
924                         $resource = proc_open('cmd /c start /b ' . $cmdline, array(), $foo, $this->get_basepath());\r
925                 } else {\r
926                         $resource = proc_open($cmdline . ' &', array(), $foo, $this->get_basepath());\r
927                 }\r
928                 if (!is_resource($resource)) {\r
929                         logger('We got no resource for command ' . $cmdline, LOGGER_DEBUG);\r
930                         return;\r
931                 }\r
932                 proc_close($resource);\r
933         }\r
934 \r
935         /**\r
936          * @brief Returns the system user that is executing the script\r
937          *\r
938          * This mostly returns something like "www-data".\r
939          *\r
940          * @return string system username\r
941          */\r
942         static function systemuser() {\r
943                 if (!function_exists('posix_getpwuid') OR ! function_exists('posix_geteuid')) {\r
944                         return '';\r
945                 }\r
946 \r
947                 $processUser = posix_getpwuid(posix_geteuid());\r
948                 return $processUser['name'];\r
949         }\r
950 \r
951         /**\r
952          * @brief Checks if a given directory is usable for the system\r
953          *\r
954          * @return boolean the directory is usable\r
955          */\r
956         static function directory_usable($directory) {\r
957                 if ($directory == '') {\r
958                         logger('Directory is empty. This shouldn\'t happen.', LOGGER_DEBUG);\r
959                         return false;\r
960                 }\r
961 \r
962                 if (!file_exists($directory)) {\r
963                         logger('Path "' . $directory . '" does not exist for user ' . self::systemuser(), LOGGER_DEBUG);\r
964                         return false;\r
965                 }\r
966                 if (is_file($directory)) {\r
967                         logger('Path "' . $directory . '" is a file for user ' . self::systemuser(), LOGGER_DEBUG);\r
968                         return false;\r
969                 }\r
970                 if (!is_dir($directory)) {\r
971                         logger('Path "' . $directory . '" is not a directory for user ' . self::systemuser(), LOGGER_DEBUG);\r
972                         return false;\r
973                 }\r
974                 if (!is_writable($directory)) {\r
975                         logger('Path "' . $directory . '" is not writable for user ' . self::systemuser(), LOGGER_DEBUG);\r
976                         return false;\r
977                 }\r
978                 return true;\r
979         }\r
980 }\r