Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there)
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.

App.php 39KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508
  1. <?php
  2. /**
  3. * @file src/App.php
  4. */
  5. namespace Friendica;
  6. use Detection\MobileDetect;
  7. use Exception;
  8. use Friendica\Core\Config;
  9. use Friendica\Core\L10n;
  10. use Friendica\Core\PConfig;
  11. use Friendica\Core\System;
  12. use Friendica\Database\DBA;
  13. use Friendica\Network\HTTPException\InternalServerErrorException;
  14. require_once 'boot.php';
  15. require_once 'include/dba.php';
  16. require_once 'include/text.php';
  17. /**
  18. *
  19. * class: App
  20. *
  21. * @brief Our main application structure for the life of this page.
  22. *
  23. * Primarily deals with the URL that got us here
  24. * and tries to make some sense of it, and
  25. * stores our page contents and config storage
  26. * and anything else that might need to be passed around
  27. * before we spit the page out.
  28. *
  29. */
  30. class App
  31. {
  32. public $module_loaded = false;
  33. public $module_class = null;
  34. public $query_string = '';
  35. public $config = [];
  36. public $page = [];
  37. public $pager = [];
  38. public $page_offset;
  39. public $profile;
  40. public $profile_uid;
  41. public $user;
  42. public $cid;
  43. public $contact;
  44. public $contacts;
  45. public $page_contact;
  46. public $content;
  47. public $data = [];
  48. public $error = false;
  49. public $cmd = '';
  50. public $argv;
  51. public $argc;
  52. public $module;
  53. public $strings;
  54. public $basepath;
  55. public $urlpath;
  56. public $hooks = [];
  57. public $timezone;
  58. public $interactive = true;
  59. public $addons;
  60. public $addons_admin = [];
  61. public $apps = [];
  62. public $identities;
  63. public $is_mobile = false;
  64. public $is_tablet = false;
  65. public $is_friendica_app;
  66. public $performance = [];
  67. public $callstack = [];
  68. public $theme_info = [];
  69. public $backend = true;
  70. public $nav_sel;
  71. public $category;
  72. // Allow themes to control internal parameters
  73. // by changing App values in theme.php
  74. public $sourcename = '';
  75. public $videowidth = 425;
  76. public $videoheight = 350;
  77. public $force_max_items = 0;
  78. public $theme_events_in_profile = true;
  79. public $stylesheets = [];
  80. public $footerScripts = [];
  81. /**
  82. * @var App\Mode The Mode of the Application
  83. */
  84. private $mode;
  85. /**
  86. * Register a stylesheet file path to be included in the <head> tag of every page.
  87. * Inclusion is done in App->initHead().
  88. * The path can be absolute or relative to the Friendica installation base folder.
  89. *
  90. * @see App->initHead()
  91. *
  92. * @param string $path
  93. */
  94. public function registerStylesheet($path)
  95. {
  96. $url = str_replace($this->get_basepath() . DIRECTORY_SEPARATOR, '', $path);
  97. $this->stylesheets[] = trim($url, '/');
  98. }
  99. /**
  100. * Register a javascript file path to be included in the <footer> tag of every page.
  101. * Inclusion is done in App->initFooter().
  102. * The path can be absolute or relative to the Friendica installation base folder.
  103. *
  104. * @see App->initFooter()
  105. *
  106. * @param string $path
  107. */
  108. public function registerFooterScript($path)
  109. {
  110. $url = str_replace($this->get_basepath() . DIRECTORY_SEPARATOR, '', $path);
  111. $this->footerScripts[] = trim($url, '/');
  112. }
  113. /**
  114. * @brief An array for all theme-controllable parameters
  115. *
  116. * Mostly unimplemented yet. Only options 'template_engine' and
  117. * beyond are used.
  118. */
  119. public $theme = [
  120. 'sourcename' => '',
  121. 'videowidth' => 425,
  122. 'videoheight' => 350,
  123. 'force_max_items' => 0,
  124. 'stylesheet' => '',
  125. 'template_engine' => 'smarty3',
  126. ];
  127. /**
  128. * @brief An array of registered template engines ('name'=>'class name')
  129. */
  130. public $template_engines = [];
  131. /**
  132. * @brief An array of instanced template engines ('name'=>'instance')
  133. */
  134. public $template_engine_instance = [];
  135. public $process_id;
  136. public $queue;
  137. private $ldelim = [
  138. 'internal' => '',
  139. 'smarty3' => '{{'
  140. ];
  141. private $rdelim = [
  142. 'internal' => '',
  143. 'smarty3' => '}}'
  144. ];
  145. private $scheme;
  146. private $hostname;
  147. private $curl_code;
  148. private $curl_content_type;
  149. private $curl_headers;
  150. /**
  151. * @brief App constructor.
  152. *
  153. * @param string $basepath Path to the app base folder
  154. *
  155. * @throws Exception if the Basepath is not usable
  156. */
  157. public function __construct($basepath)
  158. {
  159. if (!static::directory_usable($basepath, false)) {
  160. throw new Exception('Basepath ' . $basepath . ' isn\'t usable.');
  161. }
  162. BaseObject::setApp($this);
  163. $this->basepath = rtrim($basepath, DIRECTORY_SEPARATOR);
  164. $this->performance['start'] = microtime(true);
  165. $this->performance['database'] = 0;
  166. $this->performance['database_write'] = 0;
  167. $this->performance['cache'] = 0;
  168. $this->performance['cache_write'] = 0;
  169. $this->performance['network'] = 0;
  170. $this->performance['file'] = 0;
  171. $this->performance['rendering'] = 0;
  172. $this->performance['parser'] = 0;
  173. $this->performance['marktime'] = 0;
  174. $this->performance['markstart'] = microtime(true);
  175. $this->callstack['database'] = [];
  176. $this->callstack['database_write'] = [];
  177. $this->callstack['cache'] = [];
  178. $this->callstack['cache_write'] = [];
  179. $this->callstack['network'] = [];
  180. $this->callstack['file'] = [];
  181. $this->callstack['rendering'] = [];
  182. $this->callstack['parser'] = [];
  183. $this->mode = new App\Mode($basepath);
  184. $this->reload();
  185. set_time_limit(0);
  186. // This has to be quite large to deal with embedded private photos
  187. ini_set('pcre.backtrack_limit', 500000);
  188. $this->scheme = 'http';
  189. if ((x($_SERVER, 'HTTPS') && $_SERVER['HTTPS']) ||
  190. (x($_SERVER, 'HTTP_FORWARDED') && preg_match('/proto=https/', $_SERVER['HTTP_FORWARDED'])) ||
  191. (x($_SERVER, 'HTTP_X_FORWARDED_PROTO') && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ||
  192. (x($_SERVER, 'HTTP_X_FORWARDED_SSL') && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') ||
  193. (x($_SERVER, 'FRONT_END_HTTPS') && $_SERVER['FRONT_END_HTTPS'] == 'on') ||
  194. (x($_SERVER, 'SERVER_PORT') && (intval($_SERVER['SERVER_PORT']) == 443)) // XXX: reasonable assumption, but isn't this hardcoding too much?
  195. ) {
  196. $this->scheme = 'https';
  197. }
  198. if (x($_SERVER, 'SERVER_NAME')) {
  199. $this->hostname = $_SERVER['SERVER_NAME'];
  200. if (x($_SERVER, 'SERVER_PORT') && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
  201. $this->hostname .= ':' . $_SERVER['SERVER_PORT'];
  202. }
  203. }
  204. set_include_path(
  205. get_include_path() . PATH_SEPARATOR
  206. . $this->basepath . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR
  207. . $this->basepath . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
  208. . $this->basepath);
  209. if ((x($_SERVER, 'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'], 0, 9) === 'pagename=') {
  210. $this->query_string = substr($_SERVER['QUERY_STRING'], 9);
  211. } elseif ((x($_SERVER, 'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'], 0, 2) === 'q=') {
  212. $this->query_string = substr($_SERVER['QUERY_STRING'], 2);
  213. }
  214. // removing trailing / - maybe a nginx problem
  215. $this->query_string = ltrim($this->query_string, '/');
  216. if (!empty($_GET['pagename'])) {
  217. $this->cmd = trim($_GET['pagename'], '/\\');
  218. } elseif (!empty($_GET['q'])) {
  219. $this->cmd = trim($_GET['q'], '/\\');
  220. }
  221. // fix query_string
  222. $this->query_string = str_replace($this->cmd . '&', $this->cmd . '?', $this->query_string);
  223. // unix style "homedir"
  224. if (substr($this->cmd, 0, 1) === '~') {
  225. $this->cmd = 'profile/' . substr($this->cmd, 1);
  226. }
  227. // Diaspora style profile url
  228. if (substr($this->cmd, 0, 2) === 'u/') {
  229. $this->cmd = 'profile/' . substr($this->cmd, 2);
  230. }
  231. /*
  232. * Break the URL path into C style argc/argv style arguments for our
  233. * modules. Given "http://example.com/module/arg1/arg2", $this->argc
  234. * will be 3 (integer) and $this->argv will contain:
  235. * [0] => 'module'
  236. * [1] => 'arg1'
  237. * [2] => 'arg2'
  238. *
  239. *
  240. * There will always be one argument. If provided a naked domain
  241. * URL, $this->argv[0] is set to "home".
  242. */
  243. $this->argv = explode('/', $this->cmd);
  244. $this->argc = count($this->argv);
  245. if ((array_key_exists('0', $this->argv)) && strlen($this->argv[0])) {
  246. $this->module = str_replace('.', '_', $this->argv[0]);
  247. $this->module = str_replace('-', '_', $this->module);
  248. } else {
  249. $this->argc = 1;
  250. $this->argv = ['home'];
  251. $this->module = 'home';
  252. }
  253. // See if there is any page number information, and initialise pagination
  254. $this->pager['page'] = ((x($_GET, 'page') && intval($_GET['page']) > 0) ? intval($_GET['page']) : 1);
  255. $this->pager['itemspage'] = 50;
  256. $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
  257. if ($this->pager['start'] < 0) {
  258. $this->pager['start'] = 0;
  259. }
  260. $this->pager['total'] = 0;
  261. // Detect mobile devices
  262. $mobile_detect = new MobileDetect();
  263. $this->is_mobile = $mobile_detect->isMobile();
  264. $this->is_tablet = $mobile_detect->isTablet();
  265. // Friendica-Client
  266. $this->is_friendica_app = isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT'] == 'Apache-HttpClient/UNAVAILABLE (java 1.4)';
  267. // Register template engines
  268. $this->register_template_engine('Friendica\Render\FriendicaSmartyEngine');
  269. }
  270. /**
  271. * Returns the Mode of the Application
  272. *
  273. * @return App\Mode The Application Mode
  274. *
  275. * @throws InternalServerErrorException when the mode isn't created
  276. */
  277. public function getMode()
  278. {
  279. if (empty($this->mode)) {
  280. throw new InternalServerErrorException('Mode of the Application is not defined');
  281. }
  282. return $this->mode;
  283. }
  284. /**
  285. * Reloads the whole app instance
  286. */
  287. public function reload()
  288. {
  289. // The order of the following calls is important to ensure proper initialization
  290. $this->loadConfigFiles();
  291. $this->loadDatabase();
  292. $this->getMode()->determine($this->basepath);
  293. $this->determineUrlPath();
  294. Config::load();
  295. if ($this->getMode()->has(App\Mode::DBAVAILABLE)) {
  296. Core\Addon::loadHooks();
  297. $this->loadAddonConfig();
  298. }
  299. $this->loadDefaultTimezone();
  300. $this->page = [
  301. 'aside' => '',
  302. 'bottom' => '',
  303. 'content' => '',
  304. 'footer' => '',
  305. 'htmlhead' => '',
  306. 'nav' => '',
  307. 'page_title' => '',
  308. 'right_aside' => '',
  309. 'template' => '',
  310. 'title' => ''
  311. ];
  312. $this->process_id = System::processID('log');
  313. }
  314. /**
  315. * Load the configuration files
  316. *
  317. * First loads the default value for all the configuration keys, then the legacy configuration files, then the
  318. * expected local.ini.php
  319. */
  320. private function loadConfigFiles()
  321. {
  322. $this->loadConfigFile($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'config.ini.php');
  323. $this->loadConfigFile($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'settings.ini.php');
  324. // Legacy .htconfig.php support
  325. if (file_exists($this->basepath . DIRECTORY_SEPARATOR . '.htpreconfig.php')) {
  326. $a = $this;
  327. include $this->basepath . DIRECTORY_SEPARATOR . '.htpreconfig.php';
  328. }
  329. // Legacy .htconfig.php support
  330. if (file_exists($this->basepath . DIRECTORY_SEPARATOR . '.htconfig.php')) {
  331. $a = $this;
  332. include $this->basepath . DIRECTORY_SEPARATOR . '.htconfig.php';
  333. $this->setConfigValue('database', 'hostname', $db_host);
  334. $this->setConfigValue('database', 'username', $db_user);
  335. $this->setConfigValue('database', 'password', $db_pass);
  336. $this->setConfigValue('database', 'database', $db_data);
  337. if (isset($a->config['system']['db_charset'])) {
  338. $this->setConfigValue('database', 'charset', $a->config['system']['db_charset']);
  339. }
  340. unset($db_host, $db_user, $db_pass, $db_data);
  341. if (isset($default_timezone)) {
  342. $this->setConfigValue('system', 'default_timezone', $default_timezone);
  343. unset($default_timezone);
  344. }
  345. if (isset($pidfile)) {
  346. $this->setConfigValue('system', 'pidfile', $pidfile);
  347. unset($pidfile);
  348. }
  349. if (isset($lang)) {
  350. $this->setConfigValue('system', 'language', $lang);
  351. unset($lang);
  352. }
  353. }
  354. if (file_exists($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php')) {
  355. $this->loadConfigFile($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.ini.php', true);
  356. }
  357. }
  358. /**
  359. * Tries to load the specified configuration file into the App->config array.
  360. * Doesn't overwrite previously set values by default to prevent default config files to supersede DB Config.
  361. *
  362. * The config format is INI and the template for configuration files is the following:
  363. *
  364. * <?php return <<<INI
  365. *
  366. * [section]
  367. * key = value
  368. *
  369. * INI;
  370. * // Keep this line
  371. *
  372. * @param type $filepath
  373. * @param bool $overwrite Force value overwrite if the config key already exists
  374. * @throws Exception
  375. */
  376. public function loadConfigFile($filepath, $overwrite = false)
  377. {
  378. if (!file_exists($filepath)) {
  379. throw new Exception('Error parsing non-existent config file ' . $filepath);
  380. }
  381. $contents = include($filepath);
  382. $config = parse_ini_string($contents, true, INI_SCANNER_TYPED);
  383. if ($config === false) {
  384. throw new Exception('Error parsing config file ' . $filepath);
  385. }
  386. foreach ($config as $category => $values) {
  387. foreach ($values as $key => $value) {
  388. if ($overwrite) {
  389. $this->setConfigValue($category, $key, $value);
  390. } else {
  391. $this->setDefaultConfigValue($category, $key, $value);
  392. }
  393. }
  394. }
  395. }
  396. /**
  397. * Loads addons configuration files
  398. *
  399. * First loads all activated addons default configuration throught the load_config hook, then load the local.ini.php
  400. * again to overwrite potential local addon configuration.
  401. */
  402. private function loadAddonConfig()
  403. {
  404. // Loads addons default config
  405. Core\Addon::callHooks('load_config');
  406. // Load the local addon config file to overwritten default addon config values
  407. if (file_exists($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'addon.ini.php')) {
  408. $this->loadConfigFile($this->basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'addon.ini.php', true);
  409. }
  410. }
  411. /**
  412. * Loads the default timezone
  413. *
  414. * Include support for legacy $default_timezone
  415. *
  416. * @global string $default_timezone
  417. */
  418. private function loadDefaultTimezone()
  419. {
  420. if ($this->getConfigValue('system', 'default_timezone')) {
  421. $this->timezone = $this->getConfigValue('system', 'default_timezone');
  422. } else {
  423. global $default_timezone;
  424. $this->timezone = !empty($default_timezone) ? $default_timezone : 'UTC';
  425. }
  426. if ($this->timezone) {
  427. date_default_timezone_set($this->timezone);
  428. }
  429. }
  430. /**
  431. * Figure out if we are running at the top of a domain or in a sub-directory and adjust accordingly
  432. */
  433. private function determineUrlPath()
  434. {
  435. $this->urlpath = $this->getConfigValue('system', 'urlpath');
  436. /* SCRIPT_URL gives /path/to/friendica/module/parameter
  437. * QUERY_STRING gives pagename=module/parameter
  438. *
  439. * To get /path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING
  440. */
  441. if (!empty($_SERVER['SCRIPT_URL'])) {
  442. // Module
  443. if (!empty($_SERVER['QUERY_STRING'])) {
  444. $path = trim(dirname($_SERVER['SCRIPT_URL'], substr_count(trim($_SERVER['QUERY_STRING'], '/'), '/') + 1), '/');
  445. } else {
  446. // Root page
  447. $path = trim($_SERVER['SCRIPT_URL'], '/');
  448. }
  449. if ($path && $path != $this->urlpath) {
  450. $this->urlpath = $path;
  451. }
  452. }
  453. }
  454. public function loadDatabase()
  455. {
  456. if (DBA::connected()) {
  457. return;
  458. }
  459. $db_host = $this->getConfigValue('database', 'hostname');
  460. $db_user = $this->getConfigValue('database', 'username');
  461. $db_pass = $this->getConfigValue('database', 'password');
  462. $db_data = $this->getConfigValue('database', 'database');
  463. $charset = $this->getConfigValue('database', 'charset');
  464. // Use environment variables for mysql if they are set beforehand
  465. if (!empty(getenv('MYSQL_HOST'))
  466. && (!empty(getenv('MYSQL_USERNAME')) || !empty(getenv('MYSQL_USER')))
  467. && getenv('MYSQL_PASSWORD') !== false
  468. && !empty(getenv('MYSQL_DATABASE')))
  469. {
  470. $db_host = getenv('MYSQL_HOST');
  471. if (!empty(getenv('MYSQL_PORT'))) {
  472. $db_host .= ':' . getenv('MYSQL_PORT');
  473. }
  474. if (!empty(getenv('MYSQL_USERNAME'))) {
  475. $db_user = getenv('MYSQL_USERNAME');
  476. } else {
  477. $db_user = getenv('MYSQL_USER');
  478. }
  479. $db_pass = (string) getenv('MYSQL_PASSWORD');
  480. $db_data = getenv('MYSQL_DATABASE');
  481. }
  482. $stamp1 = microtime(true);
  483. DBA::connect($db_host, $db_user, $db_pass, $db_data, $charset);
  484. unset($db_host, $db_user, $db_pass, $db_data, $charset);
  485. $this->save_timestamp($stamp1, 'network');
  486. }
  487. /**
  488. * @brief Returns the base filesystem path of the App
  489. *
  490. * It first checks for the internal variable, then for DOCUMENT_ROOT and
  491. * finally for PWD
  492. *
  493. * @return string
  494. */
  495. public function get_basepath()
  496. {
  497. $basepath = $this->basepath;
  498. if (!$basepath) {
  499. $basepath = Config::get('system', 'basepath');
  500. }
  501. if (!$basepath && x($_SERVER, 'DOCUMENT_ROOT')) {
  502. $basepath = $_SERVER['DOCUMENT_ROOT'];
  503. }
  504. if (!$basepath && x($_SERVER, 'PWD')) {
  505. $basepath = $_SERVER['PWD'];
  506. }
  507. return self::realpath($basepath);
  508. }
  509. /**
  510. * @brief Returns a normalized file path
  511. *
  512. * This is a wrapper for the "realpath" function.
  513. * That function cannot detect the real path when some folders aren't readable.
  514. * Since this could happen with some hosters we need to handle this.
  515. *
  516. * @param string $path The path that is about to be normalized
  517. * @return string normalized path - when possible
  518. */
  519. public static function realpath($path)
  520. {
  521. $normalized = realpath($path);
  522. if (!is_bool($normalized)) {
  523. return $normalized;
  524. } else {
  525. return $path;
  526. }
  527. }
  528. public function get_scheme()
  529. {
  530. return $this->scheme;
  531. }
  532. /**
  533. * @brief Retrieves the Friendica instance base URL
  534. *
  535. * This function assembles the base URL from multiple parts:
  536. * - Protocol is determined either by the request or a combination of
  537. * system.ssl_policy and the $ssl parameter.
  538. * - Host name is determined either by system.hostname or inferred from request
  539. * - Path is inferred from SCRIPT_NAME
  540. *
  541. * Note: $ssl parameter value doesn't directly correlate with the resulting protocol
  542. *
  543. * @param bool $ssl Whether to append http or https under SSL_POLICY_SELFSIGN
  544. * @return string Friendica server base URL
  545. */
  546. public function get_baseurl($ssl = false)
  547. {
  548. $scheme = $this->scheme;
  549. if (Config::get('system', 'ssl_policy') == SSL_POLICY_FULL) {
  550. $scheme = 'https';
  551. }
  552. // Basically, we have $ssl = true on any links which can only be seen by a logged in user
  553. // (and also the login link). Anything seen by an outsider will have it turned off.
  554. if (Config::get('system', 'ssl_policy') == SSL_POLICY_SELFSIGN) {
  555. if ($ssl) {
  556. $scheme = 'https';
  557. } else {
  558. $scheme = 'http';
  559. }
  560. }
  561. if (Config::get('config', 'hostname') != '') {
  562. $this->hostname = Config::get('config', 'hostname');
  563. }
  564. return $scheme . '://' . $this->hostname . (!empty($this->urlpath) ? '/' . $this->urlpath : '' );
  565. }
  566. /**
  567. * @brief Initializes the baseurl components
  568. *
  569. * Clears the baseurl cache to prevent inconsistencies
  570. *
  571. * @param string $url
  572. */
  573. public function set_baseurl($url)
  574. {
  575. $parsed = @parse_url($url);
  576. $hostname = '';
  577. if (x($parsed)) {
  578. if (!empty($parsed['scheme'])) {
  579. $this->scheme = $parsed['scheme'];
  580. }
  581. if (!empty($parsed['host'])) {
  582. $hostname = $parsed['host'];
  583. }
  584. if (x($parsed, 'port')) {
  585. $hostname .= ':' . $parsed['port'];
  586. }
  587. if (x($parsed, 'path')) {
  588. $this->urlpath = trim($parsed['path'], '\\/');
  589. }
  590. if (file_exists($this->basepath . DIRECTORY_SEPARATOR . '.htpreconfig.php')) {
  591. include $this->basepath . DIRECTORY_SEPARATOR . '.htpreconfig.php';
  592. }
  593. if (Config::get('config', 'hostname') != '') {
  594. $this->hostname = Config::get('config', 'hostname');
  595. }
  596. if (!isset($this->hostname) || ($this->hostname == '')) {
  597. $this->hostname = $hostname;
  598. }
  599. }
  600. }
  601. public function get_hostname()
  602. {
  603. if (Config::get('config', 'hostname') != '') {
  604. $this->hostname = Config::get('config', 'hostname');
  605. }
  606. return $this->hostname;
  607. }
  608. public function get_path()
  609. {
  610. return $this->urlpath;
  611. }
  612. public function set_pager_total($n)
  613. {
  614. $this->pager['total'] = intval($n);
  615. }
  616. public function set_pager_itemspage($n)
  617. {
  618. $this->pager['itemspage'] = ((intval($n) > 0) ? intval($n) : 0);
  619. $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
  620. }
  621. public function set_pager_page($n)
  622. {
  623. $this->pager['page'] = $n;
  624. $this->pager['start'] = ($this->pager['page'] * $this->pager['itemspage']) - $this->pager['itemspage'];
  625. }
  626. /**
  627. * Initializes App->page['htmlhead'].
  628. *
  629. * Includes:
  630. * - Page title
  631. * - Favicons
  632. * - Registered stylesheets (through App->registerStylesheet())
  633. * - Infinite scroll data
  634. * - head.tpl template
  635. */
  636. public function initHead()
  637. {
  638. $interval = ((local_user()) ? PConfig::get(local_user(), 'system', 'update_interval') : 40000);
  639. // If the update is 'deactivated' set it to the highest integer number (~24 days)
  640. if ($interval < 0) {
  641. $interval = 2147483647;
  642. }
  643. if ($interval < 10000) {
  644. $interval = 40000;
  645. }
  646. // compose the page title from the sitename and the
  647. // current module called
  648. if (!$this->module == '') {
  649. $this->page['title'] = $this->config['sitename'] . ' (' . $this->module . ')';
  650. } else {
  651. $this->page['title'] = $this->config['sitename'];
  652. }
  653. if (!empty($this->theme['stylesheet'])) {
  654. $stylesheet = $this->theme['stylesheet'];
  655. } else {
  656. $stylesheet = $this->getCurrentThemeStylesheetPath();
  657. }
  658. $this->registerStylesheet($stylesheet);
  659. $shortcut_icon = Config::get('system', 'shortcut_icon');
  660. if ($shortcut_icon == '') {
  661. $shortcut_icon = 'images/friendica-32.png';
  662. }
  663. $touch_icon = Config::get('system', 'touch_icon');
  664. if ($touch_icon == '') {
  665. $touch_icon = 'images/friendica-128.png';
  666. }
  667. // get data wich is needed for infinite scroll on the network page
  668. $infinite_scroll = infinite_scroll_data($this->module);
  669. Core\Addon::callHooks('head', $this->page['htmlhead']);
  670. $tpl = get_markup_template('head.tpl');
  671. /* put the head template at the beginning of page['htmlhead']
  672. * since the code added by the modules frequently depends on it
  673. * being first
  674. */
  675. $this->page['htmlhead'] = replace_macros($tpl, [
  676. '$baseurl' => $this->get_baseurl(),
  677. '$local_user' => local_user(),
  678. '$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION,
  679. '$delitem' => L10n::t('Delete this item?'),
  680. '$showmore' => L10n::t('show more'),
  681. '$showfewer' => L10n::t('show fewer'),
  682. '$update_interval' => $interval,
  683. '$shortcut_icon' => $shortcut_icon,
  684. '$touch_icon' => $touch_icon,
  685. '$infinite_scroll' => $infinite_scroll,
  686. '$block_public' => intval(Config::get('system', 'block_public')),
  687. '$stylesheets' => $this->stylesheets,
  688. ]) . $this->page['htmlhead'];
  689. }
  690. /**
  691. * Initializes App->page['footer'].
  692. *
  693. * Includes:
  694. * - Javascript homebase
  695. * - Mobile toggle link
  696. * - Registered footer scripts (through App->registerFooterScript())
  697. * - footer.tpl template
  698. */
  699. public function initFooter()
  700. {
  701. // If you're just visiting, let javascript take you home
  702. if (!empty($_SESSION['visitor_home'])) {
  703. $homebase = $_SESSION['visitor_home'];
  704. } elseif (local_user()) {
  705. $homebase = 'profile/' . $this->user['nickname'];
  706. }
  707. if (isset($homebase)) {
  708. $this->page['footer'] .= '<script>var homebase="' . $homebase . '";</script>' . "\n";
  709. }
  710. /*
  711. * Add a "toggle mobile" link if we're using a mobile device
  712. */
  713. if ($this->is_mobile || $this->is_tablet) {
  714. if (isset($_SESSION['show-mobile']) && !$_SESSION['show-mobile']) {
  715. $link = 'toggle_mobile?address=' . curPageURL();
  716. } else {
  717. $link = 'toggle_mobile?off=1&address=' . curPageURL();
  718. }
  719. $this->page['footer'] .= replace_macros(get_markup_template("toggle_mobile_footer.tpl"), [
  720. '$toggle_link' => $link,
  721. '$toggle_text' => Core\L10n::t('toggle mobile')
  722. ]);
  723. }
  724. Core\Addon::callHooks('footer', $this->page['footer']);
  725. $tpl = get_markup_template('footer.tpl');
  726. $this->page['footer'] = replace_macros($tpl, [
  727. '$baseurl' => $this->get_baseurl(),
  728. '$footerScripts' => $this->footerScripts,
  729. ]) . $this->page['footer'];
  730. }
  731. public function set_curl_code($code)
  732. {
  733. $this->curl_code = $code;
  734. }
  735. public function get_curl_code()
  736. {
  737. return $this->curl_code;
  738. }
  739. public function set_curl_content_type($content_type)
  740. {
  741. $this->curl_content_type = $content_type;
  742. }
  743. public function get_curl_content_type()
  744. {
  745. return $this->curl_content_type;
  746. }
  747. public function set_curl_headers($headers)
  748. {
  749. $this->curl_headers = $headers;
  750. }
  751. public function get_curl_headers()
  752. {
  753. return $this->curl_headers;
  754. }
  755. /**
  756. * @brief Removes the base url from an url. This avoids some mixed content problems.
  757. *
  758. * @param string $orig_url
  759. *
  760. * @return string The cleaned url
  761. */
  762. public function remove_baseurl($orig_url)
  763. {
  764. // Remove the hostname from the url if it is an internal link
  765. $nurl = normalise_link($orig_url);
  766. $base = normalise_link($this->get_baseurl());
  767. $url = str_replace($base . '/', '', $nurl);
  768. // if it is an external link return the orignal value
  769. if ($url == normalise_link($orig_url)) {
  770. return $orig_url;
  771. } else {
  772. return $url;
  773. }
  774. }
  775. /**
  776. * @brief Register template engine class
  777. *
  778. * @param string $class
  779. */
  780. private function register_template_engine($class)
  781. {
  782. $v = get_class_vars($class);
  783. if (x($v, 'name')) {
  784. $name = $v['name'];
  785. $this->template_engines[$name] = $class;
  786. } else {
  787. echo "template engine <tt>$class</tt> cannot be registered without a name.\n";
  788. die();
  789. }
  790. }
  791. /**
  792. * @brief Return template engine instance.
  793. *
  794. * If $name is not defined, return engine defined by theme,
  795. * or default
  796. *
  797. * @return object Template Engine instance
  798. */
  799. public function template_engine()
  800. {
  801. $template_engine = 'smarty3';
  802. if (x($this->theme, 'template_engine')) {
  803. $template_engine = $this->theme['template_engine'];
  804. }
  805. if (isset($this->template_engines[$template_engine])) {
  806. if (isset($this->template_engine_instance[$template_engine])) {
  807. return $this->template_engine_instance[$template_engine];
  808. } else {
  809. $class = $this->template_engines[$template_engine];
  810. $obj = new $class;
  811. $this->template_engine_instance[$template_engine] = $obj;
  812. return $obj;
  813. }
  814. }
  815. echo "template engine <tt>$template_engine</tt> is not registered!\n";
  816. killme();
  817. }
  818. /**
  819. * @brief Returns the active template engine.
  820. *
  821. * @return string
  822. */
  823. public function get_template_engine()
  824. {
  825. return $this->theme['template_engine'];
  826. }
  827. public function set_template_engine($engine = 'smarty3')
  828. {
  829. $this->theme['template_engine'] = $engine;
  830. }
  831. public function get_template_ldelim($engine = 'smarty3')
  832. {
  833. return $this->ldelim[$engine];
  834. }
  835. public function get_template_rdelim($engine = 'smarty3')
  836. {
  837. return $this->rdelim[$engine];
  838. }
  839. public function save_timestamp($stamp, $value)
  840. {
  841. if (!isset($this->config['system']['profiler']) || !$this->config['system']['profiler']) {
  842. return;
  843. }
  844. $duration = (float) (microtime(true) - $stamp);
  845. if (!isset($this->performance[$value])) {
  846. // Prevent ugly E_NOTICE
  847. $this->performance[$value] = 0;
  848. }
  849. $this->performance[$value] += (float) $duration;
  850. $this->performance['marktime'] += (float) $duration;
  851. $callstack = System::callstack();
  852. if (!isset($this->callstack[$value][$callstack])) {
  853. // Prevent ugly E_NOTICE
  854. $this->callstack[$value][$callstack] = 0;
  855. }
  856. $this->callstack[$value][$callstack] += (float) $duration;
  857. }
  858. public function get_useragent()
  859. {
  860. return
  861. FRIENDICA_PLATFORM . " '" .
  862. FRIENDICA_CODENAME . "' " .
  863. FRIENDICA_VERSION . '-' .
  864. DB_UPDATE_VERSION . '; ' .
  865. $this->get_baseurl();
  866. }
  867. public function is_friendica_app()
  868. {
  869. return $this->is_friendica_app;
  870. }
  871. /**
  872. * @brief Checks if the site is called via a backend process
  873. *
  874. * This isn't a perfect solution. But we need this check very early.
  875. * So we cannot wait until the modules are loaded.
  876. *
  877. * @return bool Is it a known backend?
  878. */
  879. public function is_backend()
  880. {
  881. static $backends = [
  882. '_well_known',
  883. 'api',
  884. 'dfrn_notify',
  885. 'fetch',
  886. 'hcard',
  887. 'hostxrd',
  888. 'nodeinfo',
  889. 'noscrape',
  890. 'p',
  891. 'poco',
  892. 'post',
  893. 'proxy',
  894. 'pubsub',
  895. 'pubsubhubbub',
  896. 'receive',
  897. 'rsd_xml',
  898. 'salmon',
  899. 'statistics_json',
  900. 'xrd',
  901. ];
  902. // Check if current module is in backend or backend flag is set
  903. return (in_array($this->module, $backends) || $this->backend);
  904. }
  905. /**
  906. * @brief Checks if the maximum number of database processes is reached
  907. *
  908. * @return bool Is the limit reached?
  909. */
  910. public function isMaxProcessesReached()
  911. {
  912. // Deactivated, needs more investigating if this check really makes sense
  913. return false;
  914. /*
  915. * Commented out to suppress static analyzer issues
  916. *
  917. if ($this->is_backend()) {
  918. $process = 'backend';
  919. $max_processes = Config::get('system', 'max_processes_backend');
  920. if (intval($max_processes) == 0) {
  921. $max_processes = 5;
  922. }
  923. } else {
  924. $process = 'frontend';
  925. $max_processes = Config::get('system', 'max_processes_frontend');
  926. if (intval($max_processes) == 0) {
  927. $max_processes = 20;
  928. }
  929. }
  930. $processlist = DBA::processlist();
  931. if ($processlist['list'] != '') {
  932. logger('Processcheck: Processes: ' . $processlist['amount'] . ' - Processlist: ' . $processlist['list'], LOGGER_DEBUG);
  933. if ($processlist['amount'] > $max_processes) {
  934. logger('Processcheck: Maximum number of processes for ' . $process . ' tasks (' . $max_processes . ') reached.', LOGGER_DEBUG);
  935. return true;
  936. }
  937. }
  938. return false;
  939. */
  940. }
  941. /**
  942. * @brief Checks if the minimal memory is reached
  943. *
  944. * @return bool Is the memory limit reached?
  945. */
  946. public function min_memory_reached()
  947. {
  948. $min_memory = Config::get('system', 'min_memory', 0);
  949. if ($min_memory == 0) {
  950. return false;
  951. }
  952. if (!is_readable('/proc/meminfo')) {
  953. return false;
  954. }
  955. $memdata = explode("\n", file_get_contents('/proc/meminfo'));
  956. $meminfo = [];
  957. foreach ($memdata as $line) {
  958. $data = explode(':', $line);
  959. if (count($data) != 2) {
  960. continue;
  961. }
  962. list($key, $val) = $data;
  963. $meminfo[$key] = (int) trim(str_replace('kB', '', $val));
  964. $meminfo[$key] = (int) ($meminfo[$key] / 1024);
  965. }
  966. if (!isset($meminfo['MemAvailable']) || !isset($meminfo['MemFree'])) {
  967. return false;
  968. }
  969. $free = $meminfo['MemAvailable'] + $meminfo['MemFree'];
  970. $reached = ($free < $min_memory);
  971. if ($reached) {
  972. logger('Minimal memory reached: ' . $free . '/' . $meminfo['MemTotal'] . ' - limit ' . $min_memory, LOGGER_DEBUG);
  973. }
  974. return $reached;
  975. }
  976. /**
  977. * @brief Checks if the maximum load is reached
  978. *
  979. * @return bool Is the load reached?
  980. */
  981. public function isMaxLoadReached()
  982. {
  983. if ($this->is_backend()) {
  984. $process = 'backend';
  985. $maxsysload = intval(Config::get('system', 'maxloadavg'));
  986. if ($maxsysload < 1) {
  987. $maxsysload = 50;
  988. }
  989. } else {
  990. $process = 'frontend';
  991. $maxsysload = intval(Config::get('system', 'maxloadavg_frontend'));
  992. if ($maxsysload < 1) {
  993. $maxsysload = 50;
  994. }
  995. }
  996. $load = current_load();
  997. if ($load) {
  998. if (intval($load) > $maxsysload) {
  999. logger('system: load ' . $load . ' for ' . $process . ' tasks (' . $maxsysload . ') too high.');
  1000. return true;
  1001. }
  1002. }
  1003. return false;
  1004. }
  1005. /**
  1006. * Executes a child process with 'proc_open'
  1007. *
  1008. * @param string $command The command to execute
  1009. * @param array $args Arguments to pass to the command ( [ 'key' => value, 'key2' => value2, ... ]
  1010. */
  1011. public function proc_run($command, $args)
  1012. {
  1013. if (!function_exists('proc_open')) {
  1014. return;
  1015. }
  1016. $cmdline = $this->getConfigValue('config', 'php_path', 'php') . ' ' . escapeshellarg($command);
  1017. foreach ($args as $key => $value) {
  1018. if (!is_null($value) && is_bool($value) && !$value) {
  1019. continue;
  1020. }
  1021. $cmdline .= ' --' . $key;
  1022. if (!is_null($value) && !is_bool($value)) {
  1023. $cmdline .= ' ' . $value;
  1024. }
  1025. }
  1026. if ($this->min_memory_reached()) {
  1027. return;
  1028. }
  1029. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
  1030. $resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->get_basepath());
  1031. } else {
  1032. $resource = proc_open($cmdline . ' &', [], $foo, $this->get_basepath());
  1033. }
  1034. if (!is_resource($resource)) {
  1035. logger('We got no resource for command ' . $cmdline, LOGGER_DEBUG);
  1036. return;
  1037. }
  1038. proc_close($resource);
  1039. }
  1040. /**
  1041. * @brief Returns the system user that is executing the script
  1042. *
  1043. * This mostly returns something like "www-data".
  1044. *
  1045. * @return string system username
  1046. */
  1047. private static function systemuser()
  1048. {
  1049. if (!function_exists('posix_getpwuid') || !function_exists('posix_geteuid')) {
  1050. return '';
  1051. }
  1052. $processUser = posix_getpwuid(posix_geteuid());
  1053. return $processUser['name'];
  1054. }
  1055. /**
  1056. * @brief Checks if a given directory is usable for the system
  1057. *
  1058. * @return boolean the directory is usable
  1059. */
  1060. public static function directory_usable($directory, $check_writable = true)
  1061. {
  1062. if ($directory == '') {
  1063. logger('Directory is empty. This shouldn\'t happen.', LOGGER_DEBUG);
  1064. return false;
  1065. }
  1066. if (!file_exists($directory)) {
  1067. logger('Path "' . $directory . '" does not exist for user ' . self::systemuser(), LOGGER_DEBUG);
  1068. return false;
  1069. }
  1070. if (is_file($directory)) {
  1071. logger('Path "' . $directory . '" is a file for user ' . self::systemuser(), LOGGER_DEBUG);
  1072. return false;
  1073. }
  1074. if (!is_dir($directory)) {
  1075. logger('Path "' . $directory . '" is not a directory for user ' . self::systemuser(), LOGGER_DEBUG);
  1076. return false;
  1077. }
  1078. if ($check_writable && !is_writable($directory)) {
  1079. logger('Path "' . $directory . '" is not writable for user ' . self::systemuser(), LOGGER_DEBUG);
  1080. return false;
  1081. }
  1082. return true;
  1083. }
  1084. /**
  1085. * @param string $cat Config category
  1086. * @param string $k Config key
  1087. * @param mixed $default Default value if it isn't set
  1088. */
  1089. public function getConfigValue($cat, $k, $default = null)
  1090. {
  1091. $return = $default;
  1092. if ($cat === 'config') {
  1093. if (isset($this->config[$k])) {
  1094. $return = $this->config[$k];
  1095. }
  1096. } else {
  1097. if (isset($this->config[$cat][$k])) {
  1098. $return = $this->config[$cat][$k];
  1099. }
  1100. }
  1101. return $return;
  1102. }
  1103. /**
  1104. * Sets a default value in the config cache. Ignores already existing keys.
  1105. *
  1106. * @param string $cat Config category
  1107. * @param string $k Config key
  1108. * @param mixed $v Default value to set
  1109. */
  1110. private function setDefaultConfigValue($cat, $k, $v)
  1111. {
  1112. if (!isset($this->config[$cat][$k])) {
  1113. $this->setConfigValue($cat, $k, $v);
  1114. }
  1115. }
  1116. /**
  1117. * Sets a value in the config cache. Accepts raw output from the config table
  1118. *
  1119. * @param string $cat Config category
  1120. * @param string $k Config key
  1121. * @param mixed $v Value to set
  1122. */
  1123. public function setConfigValue($cat, $k, $v)
  1124. {
  1125. // Only arrays are serialized in database, so we have to unserialize sparingly
  1126. $value = is_string($v) && preg_match("|^a:[0-9]+:{.*}$|s", $v) ? unserialize($v) : $v;
  1127. if ($cat === 'config') {
  1128. $this->config[$k] = $value;
  1129. } else {
  1130. if (!isset($this->config[$cat])) {
  1131. $this->config[$cat] = [];
  1132. }
  1133. $this->config[$cat][$k] = $value;
  1134. }
  1135. }
  1136. /**
  1137. * Deletes a value from the config cache
  1138. *
  1139. * @param string $cat Config category
  1140. * @param string $k Config key
  1141. */
  1142. public function deleteConfigValue($cat, $k)
  1143. {
  1144. if ($cat === 'config') {
  1145. if (isset($this->config[$k])) {
  1146. unset($this->config[$k]);
  1147. }
  1148. } else {
  1149. if (isset($this->config[$cat][$k])) {
  1150. unset($this->config[$cat][$k]);
  1151. }
  1152. }
  1153. }
  1154. /**
  1155. * Retrieves a value from the user config cache
  1156. *
  1157. * @param int $uid User Id
  1158. * @param string $cat Config category
  1159. * @param string $k Config key
  1160. * @param mixed $default Default value if key isn't set
  1161. */
  1162. public function getPConfigValue($uid, $cat, $k, $default = null)
  1163. {
  1164. $return = $default;
  1165. if (isset($this->config[$uid][$cat][$k])) {
  1166. $return = $this->config[$uid][$cat][$k];
  1167. }
  1168. return $return;
  1169. }
  1170. /**
  1171. * Sets a value in the user config cache
  1172. *
  1173. * Accepts raw output from the pconfig table
  1174. *
  1175. * @param int $uid User Id
  1176. * @param string $cat Config category
  1177. * @param string $k Config key
  1178. * @param mixed $v Value to set
  1179. */
  1180. public function setPConfigValue($uid, $cat, $k, $v)
  1181. {
  1182. // Only arrays are serialized in database, so we have to unserialize sparingly
  1183. $value = is_string($v) && preg_match("|^a:[0-9]+:{.*}$|s", $v) ? unserialize($v) : $v;
  1184. if (!isset($this->config[$uid]) || !is_array($this->config[$uid])) {
  1185. $this->config[$uid] = [];
  1186. }
  1187. if (!isset($this->config[$uid][$cat]) || !is_array($this->config[$uid][$cat])) {
  1188. $this->config[$uid][$cat] = [];
  1189. }
  1190. $this->config[$uid][$cat][$k] = $value;
  1191. }
  1192. /**
  1193. * Deletes a value from the user config cache
  1194. *
  1195. * @param int $uid User Id
  1196. * @param string $cat Config category
  1197. * @param string $k Config key
  1198. */
  1199. public function deletePConfigValue($uid, $cat, $k)
  1200. {
  1201. if (isset($this->config[$uid][$cat][$k])) {
  1202. unset($this->config[$uid][$cat][$k]);
  1203. }
  1204. }
  1205. /**
  1206. * Generates the site's default sender email address
  1207. *
  1208. * @return string
  1209. */
  1210. public function getSenderEmailAddress()
  1211. {
  1212. $sender_email = Config::get('config', 'sender_email');
  1213. if (empty($sender_email)) {
  1214. $hostname = $this->get_hostname();
  1215. if (strpos($hostname, ':')) {
  1216. $hostname = substr($hostname, 0, strpos($hostname, ':'));
  1217. }
  1218. $sender_email = 'noreply@' . $hostname;
  1219. }
  1220. return $sender_email;
  1221. }
  1222. /**
  1223. * Returns the current theme name.
  1224. *
  1225. * @return string
  1226. */
  1227. public function getCurrentTheme()
  1228. {
  1229. if ($this->getMode()->isInstall()) {
  1230. return '';
  1231. }
  1232. //// @TODO Compute the current theme only once (this behavior has
  1233. /// already been implemented, but it didn't work well -
  1234. /// https://github.com/friendica/friendica/issues/5092)
  1235. $this->computeCurrentTheme();
  1236. return $this->current_theme;
  1237. }
  1238. /**
  1239. * Computes the current theme name based on the node settings, the user settings and the device type
  1240. *
  1241. * @throws Exception
  1242. */
  1243. private function computeCurrentTheme()
  1244. {
  1245. $system_theme = Config::get('system', 'theme');
  1246. if (!$system_theme) {
  1247. throw new Exception(L10n::t('No system theme config value set.'));
  1248. }
  1249. // Sane default
  1250. $this->current_theme = $system_theme;
  1251. $allowed_themes = explode(',', Config::get('system', 'allowed_themes', $system_theme));
  1252. $page_theme = null;
  1253. // Find the theme that belongs to the user whose stuff we are looking at
  1254. if ($this->profile_uid && ($this->profile_uid != local_user())) {
  1255. // Allow folks to override user themes and always use their own on their own site.
  1256. // This works only if the user is on the same server
  1257. $user = DBA::selectFirst('user', ['theme'], ['uid' => $this->profile_uid]);
  1258. if (DBA::isResult($user) && !PConfig::get(local_user(), 'system', 'always_my_theme')) {
  1259. $page_theme = $user['theme'];
  1260. }
  1261. }
  1262. $user_theme = Core\Session::get('theme', $system_theme);
  1263. // Specific mobile theme override
  1264. if (($this->is_mobile || $this->is_tablet) && Core\Session::get('show-mobile', true)) {
  1265. $system_mobile_theme = Config::get('system', 'mobile-theme');
  1266. $user_mobile_theme = Core\Session::get('mobile-theme', $system_mobile_theme);
  1267. // --- means same mobile theme as desktop
  1268. if (!empty($user_mobile_theme) && $user_mobile_theme !== '---') {
  1269. $user_theme = $user_mobile_theme;
  1270. }
  1271. }
  1272. if ($page_theme) {
  1273. $theme_name = $page_theme;
  1274. } else {
  1275. $theme_name = $user_theme;
  1276. }
  1277. if ($theme_name
  1278. && in_array($theme_name, $allowed_themes)
  1279. && (file_exists('view/theme/' . $theme_name . '/style.css')
  1280. || file_exists('view/theme/' . $theme_name . '/style.php'))
  1281. ) {
  1282. $this->current_theme = $theme_name;
  1283. }
  1284. }
  1285. /**
  1286. * @brief Return full URL to theme which is currently in effect.
  1287. *
  1288. * Provide a sane default if nothing is chosen or the specified theme does not exist.
  1289. *
  1290. * @return string
  1291. */
  1292. public function getCurrentThemeStylesheetPath()
  1293. {
  1294. return Core\Theme::getStylesheetPath($this->getCurrentTheme());
  1295. }
  1296. }