Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

981 lines
27KB

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