2017-05-08 08:11:38 +02:00
< ? php
2018-01-22 15:54:13 +01:00
/**
* @ file src / App . php
*/
2017-05-08 08:11:38 +02:00
namespace Friendica ;
2018-07-20 04:15:21 +02:00
use Detection\MobileDetect ;
2018-10-22 04:24:47 +02:00
use DOMDocument ;
use DOMXPath ;
2018-07-20 04:15:21 +02:00
use Exception ;
2019-02-10 19:52:21 +01:00
use Friendica\Core\Config\Cache\ConfigCacheLoader ;
use Friendica\Core\Config\Cache\IConfigCache ;
use Friendica\Core\Config\Configuration ;
2018-07-20 14:19:26 +02:00
use Friendica\Database\DBA ;
2018-10-06 16:27:20 +02:00
use Friendica\Network\HTTPException\InternalServerErrorException ;
2019-02-16 23:11:30 +01:00
use Friendica\Util\Profiler ;
2018-12-30 21:42:56 +01:00
use Psr\Log\LoggerInterface ;
2017-05-11 17:53:04 +02:00
2017-05-08 08:11:38 +02:00
/**
*
* class : App
*
* @ brief Our main application structure for the life of this page .
*
* Primarily deals with the URL that got us here
* and tries to make some sense of it , and
* stores our page contents and config storage
* and anything else that might need to be passed around
* before we spit the page out .
*
*/
2018-01-16 01:13:21 +01:00
class App
{
2017-05-08 08:11:38 +02:00
public $module_loaded = false ;
2017-12-17 17:37:03 +01:00
public $module_class = null ;
2018-06-26 02:38:41 +02:00
public $query_string = '' ;
public $page = [];
2017-05-08 08:11:38 +02:00
public $profile ;
public $profile_uid ;
public $user ;
public $cid ;
public $contact ;
public $contacts ;
public $page_contact ;
public $content ;
2018-01-15 14:05:12 +01:00
public $data = [];
2017-05-08 08:11:38 +02:00
public $error = false ;
2018-06-26 02:38:41 +02:00
public $cmd = '' ;
2017-05-08 08:11:38 +02:00
public $argv ;
public $argc ;
public $module ;
public $timezone ;
public $interactive = true ;
public $identities ;
public $is_mobile = false ;
public $is_tablet = false ;
2018-01-15 14:05:12 +01:00
public $theme_info = [];
2017-05-08 08:11:38 +02:00
public $category ;
// Allow themes to control internal parameters
// by changing App values in theme.php
public $sourcename = '' ;
public $videowidth = 425 ;
public $videoheight = 350 ;
public $force_max_items = 0 ;
public $theme_events_in_profile = true ;
2018-09-21 03:30:51 +02:00
public $stylesheets = [];
2018-09-21 03:01:05 +02:00
public $footerScripts = [];
2018-10-06 16:27:20 +02:00
/**
* @ var App\Mode The Mode of the Application
*/
private $mode ;
2018-10-09 19:58:58 +02:00
/**
2018-10-10 09:16:06 +02:00
* @ var string The App base path
2018-10-09 19:58:58 +02:00
*/
2018-10-10 08:54:18 +02:00
private $basePath ;
2018-10-09 19:58:58 +02:00
/**
* @ var string The App URL path
*/
2018-10-10 08:54:18 +02:00
private $urlPath ;
2018-10-09 19:58:58 +02:00
/**
* @ var bool true , if the call is from the Friendica APP , otherwise false
*/
private $isFriendicaApp ;
/**
* @ var bool true , if the call is from an backend node ( f . e . worker )
*/
private $isBackend ;
/**
* @ var string The name of the current theme
*/
private $currentTheme ;
2018-10-13 18:57:31 +02:00
/**
* @ var bool check if request was an AJAX ( xmlhttprequest ) request
*/
private $isAjax ;
2019-01-12 02:48:29 +01:00
/**
* @ var MobileDetect
*/
public $mobileDetect ;
2018-12-30 21:42:56 +01:00
/**
2019-02-10 19:52:21 +01:00
* @ var Configuration The config
2019-02-03 22:22:04 +01:00
*/
private $config ;
2019-02-17 21:12:12 +01:00
/**
* @ var LoggerInterface The logger
2018-12-30 21:42:56 +01:00
*/
private $logger ;
2019-02-03 22:22:04 +01:00
/**
2019-02-16 23:11:30 +01:00
* @ var Profiler The profiler of this app
2019-02-03 22:22:04 +01:00
*/
2019-02-16 23:11:30 +01:00
private $profiler ;
2019-02-03 22:22:04 +01:00
/**
* Returns the current config cache of this node
*
2019-02-10 19:52:21 +01:00
* @ return IConfigCache
2019-02-03 22:22:04 +01:00
*/
2019-02-10 19:52:21 +01:00
public function getConfigCache ()
2019-02-03 22:22:04 +01:00
{
2019-02-10 19:52:21 +01:00
return $this -> config -> getCache ();
2019-02-03 22:22:04 +01:00
}
/**
* The basepath of this app
*
* @ return string
*/
public function getBasePath ()
{
return $this -> basePath ;
}
2019-02-17 21:12:12 +01:00
/**
* The Logger of this app
*
* @ return LoggerInterface
*/
public function getLogger ()
{
return $this -> logger ;
}
2019-02-16 23:11:30 +01:00
/**
* The profiler of this app
*
* @ return Profiler
*/
public function getProfiler ()
{
return $this -> profiler ;
}
2018-09-21 15:54:40 +02:00
/**
* Register a stylesheet file path to be included in the < head > tag of every page .
* Inclusion is done in App -> initHead () .
* The path can be absolute or relative to the Friendica installation base folder .
*
2018-10-22 04:24:47 +02:00
* @ see initHead ()
2018-09-21 15:54:40 +02:00
*
* @ param string $path
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2018-09-21 15:54:40 +02:00
*/
2018-09-21 03:30:51 +02:00
public function registerStylesheet ( $path )
{
2019-02-03 22:22:04 +01:00
$url = str_replace ( $this -> basePath . DIRECTORY_SEPARATOR , '' , $path );
2018-09-21 03:30:51 +02:00
$this -> stylesheets [] = trim ( $url , '/' );
}
2018-09-21 15:54:40 +02:00
/**
* Register a javascript file path to be included in the < footer > tag of every page .
* Inclusion is done in App -> initFooter () .
* The path can be absolute or relative to the Friendica installation base folder .
*
2018-10-22 04:24:47 +02:00
* @ see initFooter ()
2018-09-21 15:54:40 +02:00
*
* @ param string $path
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2018-09-21 15:54:40 +02:00
*/
2018-09-21 03:01:05 +02:00
public function registerFooterScript ( $path )
{
2019-02-03 22:22:04 +01:00
$url = str_replace ( $this -> basePath . DIRECTORY_SEPARATOR , '' , $path );
2018-09-21 03:01:05 +02:00
2018-09-21 03:30:51 +02:00
$this -> footerScripts [] = trim ( $url , '/' );
2018-09-21 03:01:05 +02:00
}
2017-05-08 08:11:38 +02:00
public $process_id ;
2017-06-11 21:51:18 +02:00
public $queue ;
2017-05-08 08:11:38 +02:00
private $scheme ;
private $hostname ;
/**
* @ brief App constructor .
*
2019-02-10 19:52:21 +01:00
* @ param Configuration $config The Configuration
2019-02-17 21:12:12 +01:00
* @ param LoggerInterface $logger The current app logger
2019-02-16 23:11:30 +01:00
* @ param Profiler $profiler The profiler of this application
2018-12-30 21:42:56 +01:00
* @ param bool $isBackend Whether it is used for backend or frontend ( Default true = backend )
2018-08-20 22:15:39 +02:00
*
* @ throws Exception if the Basepath is not usable
2017-05-08 08:11:38 +02:00
*/
2019-02-10 19:52:21 +01:00
public function __construct ( Configuration $config , LoggerInterface $logger , Profiler $profiler , $isBackend = true )
2018-01-16 01:13:21 +01:00
{
2019-02-18 00:26:38 +01:00
BaseObject :: setApp ( $this );
2019-02-03 22:22:04 +01:00
$this -> logger = $logger ;
2019-02-17 21:12:12 +01:00
$this -> config = $config ;
2019-02-16 23:11:30 +01:00
$this -> profiler = $profiler ;
2019-02-03 22:22:04 +01:00
$this -> basePath = $this -> config -> get ( 'system' , 'basepath' );
2018-12-30 21:42:56 +01:00
2019-02-05 22:30:18 +01:00
if ( ! Core\System :: isDirectoryUsable ( $this -> basePath , false )) {
2019-02-18 00:26:38 +01:00
throw new Exception ( 'Basepath \'' . $this -> basePath . '\' isn\'t usable.' );
2017-10-11 20:21:10 +02:00
}
2019-02-03 22:22:04 +01:00
$this -> basePath = rtrim ( $this -> basePath , DIRECTORY_SEPARATOR );
2017-10-11 20:21:10 +02:00
2018-10-22 04:24:47 +02:00
$this -> checkBackend ( $isBackend );
2018-10-09 19:58:58 +02:00
$this -> checkFriendicaApp ();
2017-10-11 20:21:10 +02:00
2019-02-16 23:11:30 +01:00
$this -> profiler -> reset ();
2017-05-08 08:11:38 +02:00
2019-02-03 22:22:04 +01:00
$this -> mode = new App\Mode ( $this -> basePath );
2018-10-06 16:27:20 +02:00
2018-08-27 06:15:55 +02:00
$this -> reload ();
2017-05-08 08:11:38 +02:00
2018-07-02 13:23:47 +02:00
set_time_limit ( 0 );
// This has to be quite large to deal with embedded private photos
ini_set ( 'pcre.backtrack_limit' , 500000 );
2017-05-08 08:11:38 +02:00
$this -> scheme = 'http' ;
2018-10-22 04:24:47 +02:00
if ( ! empty ( $_SERVER [ 'HTTPS' ]) ||
! empty ( $_SERVER [ 'HTTP_FORWARDED' ]) && preg_match ( '/proto=https/' , $_SERVER [ 'HTTP_FORWARDED' ]) ||
! empty ( $_SERVER [ 'HTTP_X_FORWARDED_PROTO' ]) && $_SERVER [ 'HTTP_X_FORWARDED_PROTO' ] == 'https' ||
! empty ( $_SERVER [ 'HTTP_X_FORWARDED_SSL' ]) && $_SERVER [ 'HTTP_X_FORWARDED_SSL' ] == 'on' ||
! empty ( $_SERVER [ 'FRONT_END_HTTPS' ]) && $_SERVER [ 'FRONT_END_HTTPS' ] == 'on' ||
! empty ( $_SERVER [ 'SERVER_PORT' ]) && ( intval ( $_SERVER [ 'SERVER_PORT' ]) == 443 ) // XXX: reasonable assumption, but isn't this hardcoding too much?
2017-05-08 08:11:38 +02:00
) {
$this -> scheme = 'https' ;
}
2018-10-22 04:24:47 +02:00
if ( ! empty ( $_SERVER [ 'SERVER_NAME' ])) {
2017-05-08 08:11:38 +02:00
$this -> hostname = $_SERVER [ 'SERVER_NAME' ];
2018-10-22 04:24:47 +02:00
if ( ! empty ( $_SERVER [ 'SERVER_PORT' ]) && $_SERVER [ 'SERVER_PORT' ] != 80 && $_SERVER [ 'SERVER_PORT' ] != 443 ) {
2017-05-08 08:11:38 +02:00
$this -> hostname .= ':' . $_SERVER [ 'SERVER_PORT' ];
}
}
set_include_path (
get_include_path () . PATH_SEPARATOR
2019-02-03 22:22:04 +01:00
. $this -> basePath . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR
. $this -> basePath . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
. $this -> basePath );
2017-05-08 08:11:38 +02:00
2018-10-22 04:24:47 +02:00
if ( ! empty ( $_SERVER [ 'QUERY_STRING' ]) && strpos ( $_SERVER [ 'QUERY_STRING' ], 'pagename=' ) === 0 ) {
2017-05-08 08:11:38 +02:00
$this -> query_string = substr ( $_SERVER [ 'QUERY_STRING' ], 9 );
2018-10-22 04:24:47 +02:00
} elseif ( ! empty ( $_SERVER [ 'QUERY_STRING' ]) && strpos ( $_SERVER [ 'QUERY_STRING' ], 'q=' ) === 0 ) {
2017-05-08 08:11:38 +02:00
$this -> query_string = substr ( $_SERVER [ 'QUERY_STRING' ], 2 );
}
2018-06-26 02:38:41 +02:00
// removing trailing / - maybe a nginx problem
$this -> query_string = ltrim ( $this -> query_string , '/' );
if ( ! empty ( $_GET [ 'pagename' ])) {
2017-05-08 08:11:38 +02:00
$this -> cmd = trim ( $_GET [ 'pagename' ], '/\\' );
2018-06-26 02:38:41 +02:00
} elseif ( ! empty ( $_GET [ 'q' ])) {
2017-05-08 08:11:38 +02:00
$this -> cmd = trim ( $_GET [ 'q' ], '/\\' );
}
// fix query_string
$this -> query_string = str_replace ( $this -> cmd . '&' , $this -> cmd . '?' , $this -> query_string );
// unix style "homedir"
if ( substr ( $this -> cmd , 0 , 1 ) === '~' ) {
$this -> cmd = 'profile/' . substr ( $this -> cmd , 1 );
}
// Diaspora style profile url
if ( substr ( $this -> cmd , 0 , 2 ) === 'u/' ) {
$this -> cmd = 'profile/' . substr ( $this -> cmd , 2 );
}
/*
* Break the URL path into C style argc / argv style arguments for our
* modules . Given " http://example.com/module/arg1/arg2 " , $this -> argc
* will be 3 ( integer ) and $this -> argv will contain :
* [ 0 ] => 'module'
* [ 1 ] => 'arg1'
* [ 2 ] => 'arg2'
*
*
* There will always be one argument . If provided a naked domain
* URL , $this -> argv [ 0 ] is set to " home " .
*/
$this -> argv = explode ( '/' , $this -> cmd );
$this -> argc = count ( $this -> argv );
if (( array_key_exists ( '0' , $this -> argv )) && strlen ( $this -> argv [ 0 ])) {
$this -> module = str_replace ( '.' , '_' , $this -> argv [ 0 ]);
$this -> module = str_replace ( '-' , '_' , $this -> module );
} else {
$this -> argc = 1 ;
2018-01-15 14:05:12 +01:00
$this -> argv = [ 'home' ];
2017-05-08 08:11:38 +02:00
$this -> module = 'home' ;
}
// Detect mobile devices
2017-05-11 17:53:04 +02:00
$mobile_detect = new MobileDetect ();
2019-01-12 02:48:29 +01:00
$this -> mobileDetect = $mobile_detect ;
2017-05-08 08:11:38 +02:00
$this -> is_mobile = $mobile_detect -> isMobile ();
$this -> is_tablet = $mobile_detect -> isTablet ();
2018-10-13 19:10:46 +02:00
$this -> isAjax = strtolower ( defaults ( $_SERVER , 'HTTP_X_REQUESTED_WITH' , '' )) == 'xmlhttprequest' ;
2018-10-13 18:57:31 +02:00
2017-05-08 08:11:38 +02:00
// Register template engines
2018-10-31 18:25:38 +01:00
Core\Renderer :: registerTemplateEngine ( 'Friendica\Render\FriendicaSmartyEngine' );
2018-06-26 02:38:41 +02:00
}
2018-10-06 16:27:20 +02:00
/**
* Returns the Mode of the Application
*
* @ return App\Mode The Application Mode
*
* @ throws InternalServerErrorException when the mode isn ' t created
*/
public function getMode ()
{
if ( empty ( $this -> mode )) {
throw new InternalServerErrorException ( 'Mode of the Application is not defined' );
}
return $this -> mode ;
}
2018-08-27 06:15:55 +02:00
/**
* Reloads the whole app instance
*/
public function reload ()
{
2018-10-09 19:58:58 +02:00
$this -> determineURLPath ();
2018-08-27 06:15:55 +02:00
2019-02-03 22:22:04 +01:00
$this -> getMode () -> determine ( $this -> basePath );
2018-08-27 06:15:55 +02:00
2018-10-06 16:27:20 +02:00
if ( $this -> getMode () -> has ( App\Mode :: DBAVAILABLE )) {
2018-10-21 07:15:02 +02:00
Core\Hook :: loadHooks ();
2019-02-03 22:22:04 +01:00
$loader = new ConfigCacheLoader ( $this -> basePath );
Core\Hook :: callAll ( 'load_config' , $loader );
2019-02-10 19:52:21 +01:00
$this -> config -> getCache () -> load ( $loader -> loadCoreConfig ( 'addon' ), true );
2018-08-27 06:15:55 +02:00
}
$this -> loadDefaultTimezone ();
2018-10-22 06:16:30 +02:00
Core\L10n :: init ();
2018-10-22 04:24:47 +02:00
$this -> process_id = Core\System :: processID ( 'log' );
2018-08-27 06:15:55 +02:00
}
2018-06-28 05:05:38 +02:00
/**
* Loads the default timezone
*
* Include support for legacy $default_timezone
*
* @ global string $default_timezone
*/
2018-06-26 02:38:41 +02:00
private function loadDefaultTimezone ()
{
2019-02-03 22:22:04 +01:00
if ( $this -> config -> get ( 'system' , 'default_timezone' )) {
$this -> timezone = $this -> config -> get ( 'system' , 'default_timezone' );
2018-06-26 02:38:41 +02:00
} else {
global $default_timezone ;
$this -> timezone = ! empty ( $default_timezone ) ? $default_timezone : 'UTC' ;
}
if ( $this -> timezone ) {
date_default_timezone_set ( $this -> timezone );
}
}
/**
2018-06-28 05:05:38 +02:00
* Figure out if we are running at the top of a domain or in a sub - directory and adjust accordingly
2018-06-26 02:38:41 +02:00
*/
2018-10-09 19:58:58 +02:00
private function determineURLPath ()
2018-06-26 02:38:41 +02:00
{
2018-10-11 14:54:45 +02:00
/* Relative script path to the web server root
* Not all of those $_SERVER properties can be present , so we do by inverse priority order
*/
$relative_script_path = '' ;
$relative_script_path = defaults ( $_SERVER , 'REDIRECT_URL' , $relative_script_path );
$relative_script_path = defaults ( $_SERVER , 'REDIRECT_URI' , $relative_script_path );
$relative_script_path = defaults ( $_SERVER , 'REDIRECT_SCRIPT_URL' , $relative_script_path );
$relative_script_path = defaults ( $_SERVER , 'SCRIPT_URL' , $relative_script_path );
2018-10-30 12:58:15 +01:00
$relative_script_path = defaults ( $_SERVER , 'REQUEST_URI' , $relative_script_path );
2018-10-11 14:54:45 +02:00
2019-02-03 22:22:04 +01:00
$this -> urlPath = $this -> config -> get ( 'system' , 'urlpath' );
2018-06-26 02:38:41 +02:00
2018-10-11 14:54:45 +02:00
/* $relative_script_path gives / relative / path / to / friendica / module / parameter
2018-06-26 02:38:41 +02:00
* QUERY_STRING gives pagename = module / parameter
*
2018-10-11 14:54:45 +02:00
* To get / relative / path / to / friendica we perform dirname () for as many levels as there are slashes in the QUERY_STRING
2018-04-27 21:07:04 +02:00
*/
2018-10-11 14:54:45 +02:00
if ( ! empty ( $relative_script_path )) {
2018-07-12 04:23:57 +02:00
// Module
if ( ! empty ( $_SERVER [ 'QUERY_STRING' ])) {
2018-12-15 08:46:08 +01:00
$path = trim ( rdirname ( $relative_script_path , substr_count ( trim ( $_SERVER [ 'QUERY_STRING' ], '/' ), '/' ) + 1 ), '/' );
2018-07-12 04:23:57 +02:00
} else {
// Root page
2018-10-11 14:54:45 +02:00
$path = trim ( $relative_script_path , '/' );
2018-07-12 04:23:57 +02:00
}
2018-06-26 02:38:41 +02:00
2018-10-10 08:54:18 +02:00
if ( $path && $path != $this -> urlPath ) {
$this -> urlPath = $path ;
2018-06-26 02:38:41 +02:00
}
}
}
2018-10-09 19:58:58 +02:00
public function getScheme ()
2018-01-16 01:13:21 +01:00
{
2017-05-08 08:11:38 +02:00
return $this -> scheme ;
}
/**
* @ brief Retrieves the Friendica instance base URL
*
* This function assembles the base URL from multiple parts :
* - Protocol is determined either by the request or a combination of
* system . ssl_policy and the $ssl parameter .
* - Host name is determined either by system . hostname or inferred from request
* - Path is inferred from SCRIPT_NAME
*
* Note : $ssl parameter value doesn ' t directly correlate with the resulting protocol
*
* @ param bool $ssl Whether to append http or https under SSL_POLICY_SELFSIGN
* @ return string Friendica server base URL
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2017-05-08 08:11:38 +02:00
*/
2018-10-09 19:58:58 +02:00
public function getBaseURL ( $ssl = false )
2018-01-16 01:13:21 +01:00
{
2017-05-08 08:11:38 +02:00
$scheme = $this -> scheme ;
2018-10-22 04:24:47 +02:00
if ( Core\Config :: get ( 'system' , 'ssl_policy' ) == SSL_POLICY_FULL ) {
2017-05-08 08:11:38 +02:00
$scheme = 'https' ;
}
// Basically, we have $ssl = true on any links which can only be seen by a logged in user
// (and also the login link). Anything seen by an outsider will have it turned off.
2018-10-22 04:24:47 +02:00
if ( Core\Config :: get ( 'system' , 'ssl_policy' ) == SSL_POLICY_SELFSIGN ) {
2017-05-08 08:11:38 +02:00
if ( $ssl ) {
$scheme = 'https' ;
} else {
$scheme = 'http' ;
}
}
2018-10-22 04:24:47 +02:00
if ( Core\Config :: get ( 'config' , 'hostname' ) != '' ) {
$this -> hostname = Core\Config :: get ( 'config' , 'hostname' );
2017-05-08 08:11:38 +02:00
}
2019-01-30 01:00:40 +01:00
return $scheme . '://' . $this -> hostname . ( ! empty ( $this -> getURLPath ()) ? '/' . $this -> getURLPath () : '' );
2017-05-08 08:11:38 +02:00
}
/**
* @ brief Initializes the baseurl components
*
2018-01-16 01:13:21 +01:00
* Clears the baseurl cache to prevent inconsistencies
2017-05-08 08:11:38 +02:00
*
* @ param string $url
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2017-05-08 08:11:38 +02:00
*/
2018-10-09 19:58:58 +02:00
public function setBaseURL ( $url )
2018-01-16 01:13:21 +01:00
{
2017-05-08 08:11:38 +02:00
$parsed = @ parse_url ( $url );
2018-07-08 07:29:06 +02:00
$hostname = '' ;
2017-05-08 08:11:38 +02:00
2018-10-22 04:24:47 +02:00
if ( ! empty ( $parsed )) {
2018-07-08 07:29:06 +02:00
if ( ! empty ( $parsed [ 'scheme' ])) {
$this -> scheme = $parsed [ 'scheme' ];
}
if ( ! empty ( $parsed [ 'host' ])) {
$hostname = $parsed [ 'host' ];
}
2017-05-08 08:11:38 +02:00
2018-10-22 04:24:47 +02:00
if ( ! empty ( $parsed [ 'port' ])) {
2017-05-08 08:11:38 +02:00
$hostname .= ':' . $parsed [ 'port' ];
}
2018-10-22 04:24:47 +02:00
if ( ! empty ( $parsed [ 'path' ])) {
2018-10-10 08:54:18 +02:00
$this -> urlPath = trim ( $parsed [ 'path' ], '\\/' );
2017-05-08 08:11:38 +02:00
}
2019-02-03 22:22:04 +01:00
if ( file_exists ( $this -> basePath . '/.htpreconfig.php' )) {
include $this -> basePath . '/.htpreconfig.php' ;
2017-05-08 08:11:38 +02:00
}
2018-10-22 04:24:47 +02:00
if ( Core\Config :: get ( 'config' , 'hostname' ) != '' ) {
$this -> hostname = Core\Config :: get ( 'config' , 'hostname' );
2017-05-08 08:11:38 +02:00
}
2018-07-08 07:29:06 +02:00
if ( ! isset ( $this -> hostname ) || ( $this -> hostname == '' )) {
2017-05-08 08:11:38 +02:00
$this -> hostname = $hostname ;
}
}
}
2018-10-09 19:58:58 +02:00
public function getHostName ()
2018-01-16 01:13:21 +01:00
{
2018-10-22 04:24:47 +02:00
if ( Core\Config :: get ( 'config' , 'hostname' ) != '' ) {
$this -> hostname = Core\Config :: get ( 'config' , 'hostname' );
2017-05-08 08:11:38 +02:00
}
return $this -> hostname ;
}
2018-10-10 01:18:47 +02:00
public function getURLPath ()
2018-01-16 01:13:21 +01:00
{
2018-10-10 08:54:18 +02:00
return $this -> urlPath ;
2017-05-08 08:11:38 +02:00
}
2018-09-21 15:54:40 +02:00
/**
* Initializes App -> page [ 'htmlhead' ] .
*
* Includes :
* - Page title
* - Favicons
* - Registered stylesheets ( through App -> registerStylesheet ())
* - Infinite scroll data
* - head . tpl template
*/
2018-09-21 03:30:51 +02:00
public function initHead ()
2018-01-16 01:13:21 +01:00
{
2018-10-22 04:24:47 +02:00
$interval = (( local_user ()) ? Core\PConfig :: get ( local_user (), 'system' , 'update_interval' ) : 40000 );
2017-05-08 08:11:38 +02:00
// If the update is 'deactivated' set it to the highest integer number (~24 days)
if ( $interval < 0 ) {
$interval = 2147483647 ;
}
if ( $interval < 10000 ) {
$interval = 40000 ;
}
// compose the page title from the sitename and the
// current module called
if ( ! $this -> module == '' ) {
2019-02-03 22:22:04 +01:00
$this -> page [ 'title' ] = $this -> config -> get ( 'config' , 'sitename' ) . ' (' . $this -> module . ')' ;
2017-05-08 08:11:38 +02:00
} else {
2019-02-03 22:22:04 +01:00
$this -> page [ 'title' ] = $this -> config -> get ( 'config' , 'sitename' );
2017-05-08 08:11:38 +02:00
}
2018-10-31 18:25:38 +01:00
if ( ! empty ( Core\Renderer :: $theme [ 'stylesheet' ])) {
$stylesheet = Core\Renderer :: $theme [ 'stylesheet' ];
2017-05-08 08:11:38 +02:00
} else {
2018-09-21 15:54:09 +02:00
$stylesheet = $this -> getCurrentThemeStylesheetPath ();
2017-05-08 08:11:38 +02:00
}
2018-09-21 15:54:09 +02:00
$this -> registerStylesheet ( $stylesheet );
2018-10-22 04:24:47 +02:00
$shortcut_icon = Core\Config :: get ( 'system' , 'shortcut_icon' );
2017-05-08 08:11:38 +02:00
if ( $shortcut_icon == '' ) {
$shortcut_icon = 'images/friendica-32.png' ;
}
2018-10-22 04:24:47 +02:00
$touch_icon = Core\Config :: get ( 'system' , 'touch_icon' );
2017-05-08 08:11:38 +02:00
if ( $touch_icon == '' ) {
$touch_icon = 'images/friendica-128.png' ;
}
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( 'head' , $this -> page [ 'htmlhead' ]);
2017-05-08 08:11:38 +02:00
2018-10-31 15:44:06 +01:00
$tpl = Core\Renderer :: getMarkupTemplate ( 'head.tpl' );
2018-09-21 15:54:09 +02:00
/* put the head template at the beginning of page [ 'htmlhead' ]
* since the code added by the modules frequently depends on it
* being first
*/
2018-10-31 15:35:50 +01:00
$this -> page [ 'htmlhead' ] = Core\Renderer :: replaceMacros ( $tpl , [
2018-10-09 19:58:58 +02:00
'$baseurl' => $this -> getBaseURL (),
2018-01-16 01:13:21 +01:00
'$local_user' => local_user (),
'$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION ,
2018-10-22 04:24:47 +02:00
'$delitem' => Core\L10n :: t ( 'Delete this item?' ),
2018-01-16 01:13:21 +01:00
'$update_interval' => $interval ,
'$shortcut_icon' => $shortcut_icon ,
'$touch_icon' => $touch_icon ,
2018-10-22 04:24:47 +02:00
'$block_public' => intval ( Core\Config :: get ( 'system' , 'block_public' )),
2018-09-21 03:30:51 +02:00
'$stylesheets' => $this -> stylesheets ,
2018-01-16 01:13:21 +01:00
]) . $this -> page [ 'htmlhead' ];
}
2018-09-21 15:54:40 +02:00
/**
* Initializes App -> page [ 'footer' ] .
*
* Includes :
* - Javascript homebase
* - Mobile toggle link
* - Registered footer scripts ( through App -> registerFooterScript ())
* - footer . tpl template
*/
2018-09-21 03:01:05 +02:00
public function initFooter ()
2018-01-16 01:13:21 +01:00
{
2018-09-21 03:01:05 +02:00
// If you're just visiting, let javascript take you home
if ( ! empty ( $_SESSION [ 'visitor_home' ])) {
$homebase = $_SESSION [ 'visitor_home' ];
} elseif ( local_user ()) {
2018-09-22 18:45:49 +02:00
$homebase = 'profile/' . $this -> user [ 'nickname' ];
2018-09-21 03:01:05 +02:00
}
if ( isset ( $homebase )) {
$this -> page [ 'footer' ] .= '<script>var homebase="' . $homebase . '";</script>' . " \n " ;
2017-05-08 08:11:38 +02:00
}
2018-09-21 03:01:05 +02:00
/*
* Add a " toggle mobile " link if we ' re using a mobile device
*/
if ( $this -> is_mobile || $this -> is_tablet ) {
if ( isset ( $_SESSION [ 'show-mobile' ]) && ! $_SESSION [ 'show-mobile' ]) {
2018-11-10 14:55:48 +01:00
$link = 'toggle_mobile?address=' . urlencode ( curPageURL ());
2018-09-21 03:01:05 +02:00
} else {
2018-11-10 14:55:48 +01:00
$link = 'toggle_mobile?off=1&address=' . urlencode ( curPageURL ());
2018-09-21 03:01:05 +02:00
}
2018-10-31 15:44:06 +01:00
$this -> page [ 'footer' ] .= Core\Renderer :: replaceMacros ( Core\Renderer :: getMarkupTemplate ( " toggle_mobile_footer.tpl " ), [
2018-09-21 03:01:05 +02:00
'$toggle_link' => $link ,
'$toggle_text' => Core\L10n :: t ( 'toggle mobile' )
]);
}
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( 'footer' , $this -> page [ 'footer' ]);
2018-09-21 03:01:05 +02:00
2018-10-31 15:44:06 +01:00
$tpl = Core\Renderer :: getMarkupTemplate ( 'footer.tpl' );
2018-10-31 15:35:50 +01:00
$this -> page [ 'footer' ] = Core\Renderer :: replaceMacros ( $tpl , [
2018-10-09 19:58:58 +02:00
'$baseurl' => $this -> getBaseURL (),
2018-09-21 03:01:05 +02:00
'$footerScripts' => $this -> footerScripts ,
2018-09-21 03:30:51 +02:00
]) . $this -> page [ 'footer' ];
2017-05-08 08:11:38 +02:00
}
/**
2018-01-16 01:13:21 +01:00
* @ brief Removes the base url from an url . This avoids some mixed content problems .
2017-05-08 08:11:38 +02:00
*
2018-10-10 01:18:47 +02:00
* @ param string $origURL
2017-05-08 08:11:38 +02:00
*
* @ return string The cleaned url
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2017-05-08 08:11:38 +02:00
*/
2018-10-10 01:18:47 +02:00
public function removeBaseURL ( $origURL )
2018-01-16 01:13:21 +01:00
{
2017-05-08 08:11:38 +02:00
// Remove the hostname from the url if it is an internal link
2018-11-08 17:28:29 +01:00
$nurl = Util\Strings :: normaliseLink ( $origURL );
$base = Util\Strings :: normaliseLink ( $this -> getBaseURL ());
2017-05-08 08:11:38 +02:00
$url = str_replace ( $base . '/' , '' , $nurl );
// if it is an external link return the orignal value
2018-11-08 17:28:29 +01:00
if ( $url == Util\Strings :: normaliseLink ( $origURL )) {
2018-10-10 01:18:47 +02:00
return $origURL ;
2017-05-08 08:11:38 +02:00
} else {
return $url ;
}
}
2018-10-09 19:58:58 +02:00
/**
* Returns the current UserAgent as a String
*
* @ return string the UserAgent as a String
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2018-10-09 19:58:58 +02:00
*/
public function getUserAgent ()
2018-01-16 01:13:21 +01:00
{
2017-05-08 08:11:38 +02:00
return
FRIENDICA_PLATFORM . " ' " .
FRIENDICA_CODENAME . " ' " .
FRIENDICA_VERSION . '-' .
DB_UPDATE_VERSION . '; ' .
2018-10-09 19:58:58 +02:00
$this -> getBaseURL ();
}
/**
* Checks , if the call is from the Friendica App
*
* Reason :
* The friendica client has problems with the GUID in the notify . this is some workaround
*/
private function checkFriendicaApp ()
{
// Friendica-Client
$this -> isFriendicaApp = isset ( $_SERVER [ 'HTTP_USER_AGENT' ]) && $_SERVER [ 'HTTP_USER_AGENT' ] == 'Apache-HttpClient/UNAVAILABLE (java 1.4)' ;
2017-05-08 08:11:38 +02:00
}
2018-10-09 19:58:58 +02:00
/**
* Is the call via the Friendica app ? ( not a " normale " call )
*
* @ return bool true if it ' s from the Friendica app
*/
public function isFriendicaApp ()
2018-01-16 01:13:21 +01:00
{
2018-10-09 19:58:58 +02:00
return $this -> isFriendicaApp ;
2017-05-08 08:11:38 +02:00
}
/**
* @ brief Checks if the site is called via a backend process
*
* This isn ' t a perfect solution . But we need this check very early .
* So we cannot wait until the modules are loaded .
*
2018-10-10 08:54:18 +02:00
* @ param string $backend true , if the backend flag was set during App initialization
*
2017-05-08 08:11:38 +02:00
*/
2018-10-09 19:58:58 +02:00
private function checkBackend ( $backend ) {
2018-01-16 01:13:21 +01:00
static $backends = [
'_well_known' ,
'api' ,
'dfrn_notify' ,
'fetch' ,
'hcard' ,
'hostxrd' ,
'nodeinfo' ,
'noscrape' ,
'p' ,
'poco' ,
'post' ,
'proxy' ,
'pubsub' ,
'pubsubhubbub' ,
'receive' ,
'rsd_xml' ,
'salmon' ,
'statistics_json' ,
'xrd' ,
];
2017-05-08 08:11:38 +02:00
// Check if current module is in backend or backend flag is set
2018-10-10 01:16:51 +02:00
$this -> isBackend = ( in_array ( $this -> module , $backends ) || $backend || $this -> isBackend );
2018-10-09 19:58:58 +02:00
}
/**
* Returns true , if the call is from a backend node ( f . e . from a worker )
*
* @ return bool Is it a known backend ?
*/
public function isBackend ()
{
return $this -> isBackend ;
2017-05-08 08:11:38 +02:00
}
/**
* @ brief Checks if the maximum number of database processes is reached
*
* @ return bool Is the limit reached ?
*/
2018-06-30 20:07:01 +02:00
public function isMaxProcessesReached ()
2018-01-16 01:13:21 +01:00
{
2017-06-07 00:18:42 +02:00
// Deactivated, needs more investigating if this check really makes sense
return false ;
2017-05-08 08:11:38 +02:00
2018-01-16 01:13:21 +01:00
/*
* Commented out to suppress static analyzer issues
*
2017-05-08 08:11:38 +02:00
if ( $this -> is_backend ()) {
$process = 'backend' ;
2018-10-22 04:24:47 +02:00
$max_processes = Core\Config :: get ( 'system' , 'max_processes_backend' );
2017-05-08 08:11:38 +02:00
if ( intval ( $max_processes ) == 0 ) {
$max_processes = 5 ;
}
} else {
$process = 'frontend' ;
2018-10-22 04:24:47 +02:00
$max_processes = Core\Config :: get ( 'system' , 'max_processes_frontend' );
2017-05-08 08:11:38 +02:00
if ( intval ( $max_processes ) == 0 ) {
$max_processes = 20 ;
}
}
2018-07-21 14:40:21 +02:00
$processlist = DBA :: processlist ();
2017-05-08 08:11:38 +02:00
if ( $processlist [ 'list' ] != '' ) {
2018-10-30 16:43:27 +01:00
Core\Logger :: log ( 'Processcheck: Processes: ' . $processlist [ 'amount' ] . ' - Processlist: ' . $processlist [ 'list' ], Core\Logger :: DEBUG );
2017-05-08 08:11:38 +02:00
if ( $processlist [ 'amount' ] > $max_processes ) {
2018-10-30 16:43:27 +01:00
Core\Logger :: log ( 'Processcheck: Maximum number of processes for ' . $process . ' tasks (' . $max_processes . ') reached.' , Core\Logger :: DEBUG );
2017-05-08 08:11:38 +02:00
return true ;
}
}
return false ;
2018-01-16 01:13:21 +01:00
*/
2017-05-08 08:11:38 +02:00
}
/**
* @ brief Checks if the minimal memory is reached
*
* @ return bool Is the memory limit reached ?
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2017-05-08 08:11:38 +02:00
*/
2018-10-09 19:58:58 +02:00
public function isMinMemoryReached ()
2018-01-16 01:13:21 +01:00
{
2018-10-22 04:24:47 +02:00
$min_memory = Core\Config :: get ( 'system' , 'min_memory' , 0 );
2017-05-08 08:11:38 +02:00
if ( $min_memory == 0 ) {
return false ;
}
if ( ! is_readable ( '/proc/meminfo' )) {
return false ;
}
$memdata = explode ( " \n " , file_get_contents ( '/proc/meminfo' ));
2018-01-15 14:05:12 +01:00
$meminfo = [];
2017-05-08 08:11:38 +02:00
foreach ( $memdata as $line ) {
2018-09-04 19:48:09 +02:00
$data = explode ( ':' , $line );
if ( count ( $data ) != 2 ) {
continue ;
}
list ( $key , $val ) = $data ;
2017-05-08 08:11:38 +02:00
$meminfo [ $key ] = ( int ) trim ( str_replace ( 'kB' , '' , $val ));
$meminfo [ $key ] = ( int ) ( $meminfo [ $key ] / 1024 );
}
2018-11-19 23:21:40 +01:00
if ( ! isset ( $meminfo [ 'MemFree' ])) {
2017-05-08 08:11:38 +02:00
return false ;
}
2018-11-19 23:21:40 +01:00
$free = $meminfo [ 'MemFree' ];
2017-05-08 08:11:38 +02:00
$reached = ( $free < $min_memory );
if ( $reached ) {
2018-10-30 16:43:27 +01:00
Core\Logger :: log ( 'Minimal memory reached: ' . $free . '/' . $meminfo [ 'MemTotal' ] . ' - limit ' . $min_memory , Core\Logger :: DEBUG );
2017-05-08 08:11:38 +02:00
}
return $reached ;
}
/**
* @ brief Checks if the maximum load is reached
*
* @ return bool Is the load reached ?
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2017-05-08 08:11:38 +02:00
*/
2018-06-30 20:07:01 +02:00
public function isMaxLoadReached ()
2018-01-16 01:13:21 +01:00
{
2018-10-09 19:58:58 +02:00
if ( $this -> isBackend ()) {
2017-05-08 08:11:38 +02:00
$process = 'backend' ;
2018-10-22 04:24:47 +02:00
$maxsysload = intval ( Core\Config :: get ( 'system' , 'maxloadavg' ));
2017-05-08 08:11:38 +02:00
if ( $maxsysload < 1 ) {
$maxsysload = 50 ;
}
} else {
$process = 'frontend' ;
2018-10-22 04:24:47 +02:00
$maxsysload = intval ( Core\Config :: get ( 'system' , 'maxloadavg_frontend' ));
2017-05-08 08:11:38 +02:00
if ( $maxsysload < 1 ) {
$maxsysload = 50 ;
}
}
2018-10-22 04:24:47 +02:00
$load = Core\System :: currentLoad ();
2017-05-08 08:11:38 +02:00
if ( $load ) {
if ( intval ( $load ) > $maxsysload ) {
2018-10-29 22:20:46 +01:00
Core\Logger :: log ( 'system: load ' . $load . ' for ' . $process . ' tasks (' . $maxsysload . ') too high.' );
2017-05-08 08:11:38 +02:00
return true ;
}
}
return false ;
}
2018-07-23 13:40:52 +02:00
/**
* Executes a child process with 'proc_open'
*
* @ param string $command The command to execute
* @ param array $args Arguments to pass to the command ( [ 'key' => value , 'key2' => value2 , ... ]
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2018-07-23 13:40:52 +02:00
*/
public function proc_run ( $command , $args )
2018-01-16 01:13:21 +01:00
{
2017-05-08 08:11:38 +02:00
if ( ! function_exists ( 'proc_open' )) {
return ;
}
2019-02-03 22:22:04 +01:00
$cmdline = $this -> config -> get ( 'config' , 'php_path' , 'php' ) . ' ' . escapeshellarg ( $command );
2017-05-08 08:11:38 +02:00
2018-07-23 13:40:52 +02:00
foreach ( $args as $key => $value ) {
if ( ! is_null ( $value ) && is_bool ( $value ) && ! $value ) {
continue ;
}
$cmdline .= ' --' . $key ;
if ( ! is_null ( $value ) && ! is_bool ( $value )) {
$cmdline .= ' ' . $value ;
}
2017-05-08 08:11:38 +02:00
}
2018-10-09 19:58:58 +02:00
if ( $this -> isMinMemoryReached ()) {
2017-05-08 08:11:38 +02:00
return ;
}
2018-07-02 13:47:42 +02:00
if ( strtoupper ( substr ( PHP_OS , 0 , 3 )) === 'WIN' ) {
2019-02-03 22:22:04 +01:00
$resource = proc_open ( 'cmd /c start /b ' . $cmdline , [], $foo , $this -> basePath );
2017-05-08 08:11:38 +02:00
} else {
2019-02-03 22:22:04 +01:00
$resource = proc_open ( $cmdline . ' &' , [], $foo , $this -> basePath );
2017-05-08 08:11:38 +02:00
}
if ( ! is_resource ( $resource )) {
2018-10-30 16:43:27 +01:00
Core\Logger :: log ( 'We got no resource for command ' . $cmdline , Core\Logger :: DEBUG );
2017-05-08 08:11:38 +02:00
return ;
}
proc_close ( $resource );
}
2018-04-07 03:47:16 +02:00
/**
* Generates the site ' s default sender email address
*
* @ return string
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2018-04-07 03:47:16 +02:00
*/
public function getSenderEmailAddress ()
{
2018-10-22 04:24:47 +02:00
$sender_email = Core\Config :: get ( 'config' , 'sender_email' );
2018-04-07 03:47:16 +02:00
if ( empty ( $sender_email )) {
2018-10-09 19:58:58 +02:00
$hostname = $this -> getHostName ();
2018-04-07 03:47:16 +02:00
if ( strpos ( $hostname , ':' )) {
$hostname = substr ( $hostname , 0 , strpos ( $hostname , ':' ));
}
2018-04-11 08:17:44 +02:00
$sender_email = 'noreply@' . $hostname ;
2018-04-07 03:47:16 +02:00
}
return $sender_email ;
}
2018-04-28 12:36:40 +02:00
2018-04-29 00:30:13 +02:00
/**
* Returns the current theme name .
*
2018-10-09 19:58:58 +02:00
* @ return string the name of the current theme
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2018-04-29 00:30:13 +02:00
*/
public function getCurrentTheme ()
{
2018-10-06 16:27:20 +02:00
if ( $this -> getMode () -> isInstall ()) {
2018-05-20 07:44:20 +02:00
return '' ;
}
2018-12-29 06:18:52 +01:00
if ( ! $this -> currentTheme ) {
$this -> computeCurrentTheme ();
}
2018-04-29 00:30:13 +02:00
2018-10-09 19:58:58 +02:00
return $this -> currentTheme ;
2018-04-29 00:30:13 +02:00
}
2018-12-29 06:18:52 +01:00
public function setCurrentTheme ( $theme )
{
$this -> currentTheme = $theme ;
}
2018-04-29 00:30:13 +02:00
/**
* Computes the current theme name based on the node settings , the user settings and the device type
*
* @ throws Exception
*/
private function computeCurrentTheme ()
{
2018-10-22 04:24:47 +02:00
$system_theme = Core\Config :: get ( 'system' , 'theme' );
2018-04-29 00:30:13 +02:00
if ( ! $system_theme ) {
2018-10-22 04:24:47 +02:00
throw new Exception ( Core\L10n :: t ( 'No system theme config value set.' ));
2018-04-29 00:30:13 +02:00
}
// Sane default
2018-10-09 19:58:58 +02:00
$this -> currentTheme = $system_theme ;
2018-04-29 00:30:13 +02:00
2018-10-22 04:24:47 +02:00
$allowed_themes = explode ( ',' , Core\Config :: get ( 'system' , 'allowed_themes' , $system_theme ));
2018-04-29 00:30:13 +02:00
$page_theme = null ;
// Find the theme that belongs to the user whose stuff we are looking at
if ( $this -> profile_uid && ( $this -> profile_uid != local_user ())) {
// Allow folks to override user themes and always use their own on their own site.
// This works only if the user is on the same server
2018-07-20 14:19:26 +02:00
$user = DBA :: selectFirst ( 'user' , [ 'theme' ], [ 'uid' => $this -> profile_uid ]);
2018-10-22 04:24:47 +02:00
if ( DBA :: isResult ( $user ) && ! Core\PConfig :: get ( local_user (), 'system' , 'always_my_theme' )) {
2018-04-29 00:30:13 +02:00
$page_theme = $user [ 'theme' ];
}
}
2018-08-05 15:56:21 +02:00
$user_theme = Core\Session :: get ( 'theme' , $system_theme );
2018-07-10 14:27:56 +02:00
2018-04-29 00:30:13 +02:00
// Specific mobile theme override
2018-08-05 15:56:21 +02:00
if (( $this -> is_mobile || $this -> is_tablet ) && Core\Session :: get ( 'show-mobile' , true )) {
2018-10-22 04:24:47 +02:00
$system_mobile_theme = Core\Config :: get ( 'system' , 'mobile-theme' );
2018-08-05 15:56:21 +02:00
$user_mobile_theme = Core\Session :: get ( 'mobile-theme' , $system_mobile_theme );
2018-04-29 00:30:13 +02:00
// --- means same mobile theme as desktop
if ( ! empty ( $user_mobile_theme ) && $user_mobile_theme !== '---' ) {
$user_theme = $user_mobile_theme ;
}
}
if ( $page_theme ) {
$theme_name = $page_theme ;
} else {
$theme_name = $user_theme ;
}
if ( $theme_name
&& in_array ( $theme_name , $allowed_themes )
&& ( file_exists ( 'view/theme/' . $theme_name . '/style.css' )
|| file_exists ( 'view/theme/' . $theme_name . '/style.php' ))
) {
2018-10-09 19:58:58 +02:00
$this -> currentTheme = $theme_name ;
2018-04-29 00:30:13 +02:00
}
}
/**
* @ brief Return full URL to theme which is currently in effect .
*
* Provide a sane default if nothing is chosen or the specified theme does not exist .
*
* @ return string
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2018-04-29 00:30:13 +02:00
*/
public function getCurrentThemeStylesheetPath ()
{
return Core\Theme :: getStylesheetPath ( $this -> getCurrentTheme ());
}
2018-10-13 18:57:31 +02:00
/**
* Check if request was an AJAX ( xmlhttprequest ) request .
*
* @ return boolean true if it was an AJAX request
*/
public function isAjax ()
{
return $this -> isAjax ;
}
/**
* Returns the value of a argv key
* TODO there are a lot of $a -> argv usages in combination with defaults () which can be replaced with this method
*
* @ param int $position the position of the argument
* @ param mixed $default the default value if not found
*
* @ return mixed returns the value of the argument
*/
public function getArgumentValue ( $position , $default = '' )
{
if ( array_key_exists ( $position , $this -> argv )) {
return $this -> argv [ $position ];
}
return $default ;
}
2018-10-20 18:19:55 +02:00
/**
* Sets the base url for use in cmdline programs which don ' t have
* $_SERVER variables
*/
public function checkURL ()
{
2018-10-22 04:24:47 +02:00
$url = Core\Config :: get ( 'system' , 'url' );
2018-10-20 18:19:55 +02:00
// if the url isn't set or the stored url is radically different
// than the currently visited url, store the current value accordingly.
// "Radically different" ignores common variations such as http vs https
// and www.example.com vs example.com.
// We will only change the url to an ip address if there is no existing setting
2018-11-08 16:46:50 +01:00
if ( empty ( $url ) || ( ! Util\Strings :: compareLink ( $url , $this -> getBaseURL ())) && ( ! preg_match ( " /^( \ d { 1,3}) \ .( \ d { 1,3}) \ .( \ d { 1,3}) \ .( \ d { 1,3}) $ / " , $this -> getHostName ()))) {
2018-10-22 04:24:47 +02:00
Core\Config :: set ( 'system' , 'url' , $this -> getBaseURL ());
}
}
/**
* Frontend App script
*
* The App object behaves like a container and a dispatcher at the same time , including a representation of the
* request and a representation of the response .
*
* This probably should change to limit the size of this monster method .
*/
public function runFrontend ()
{
// Missing DB connection: ERROR
if ( $this -> getMode () -> has ( App\Mode :: LOCALCONFIGPRESENT ) && ! $this -> getMode () -> has ( App\Mode :: DBAVAILABLE )) {
Core\System :: httpExit ( 500 , [ 'title' => 'Error 500 - Internal Server Error' , 'description' => 'Apologies but the website is unavailable at the moment.' ]);
}
// Max Load Average reached: ERROR
if ( $this -> isMaxProcessesReached () || $this -> isMaxLoadReached ()) {
header ( 'Retry-After: 120' );
header ( 'Refresh: 120; url=' . $this -> getBaseURL () . " / " . $this -> query_string );
Core\System :: httpExit ( 503 , [ 'title' => 'Error 503 - Service Temporarily Unavailable' , 'description' => 'Core\System is currently overloaded. Please try again later.' ]);
}
if ( strstr ( $this -> query_string , '.well-known/host-meta' ) && ( $this -> query_string != '.well-known/host-meta' )) {
Core\System :: httpExit ( 404 );
}
if ( ! $this -> getMode () -> isInstall ()) {
2018-10-22 06:07:56 +02:00
// Force SSL redirection
2018-10-22 04:24:47 +02:00
if ( Core\Config :: get ( 'system' , 'force_ssl' ) && ( $this -> getScheme () == " http " )
2018-10-22 06:07:56 +02:00
&& intval ( Core\Config :: get ( 'system' , 'ssl_policy' )) == SSL_POLICY_FULL
&& strpos ( $this -> getBaseURL (), 'https://' ) === 0
&& $_SERVER [ 'REQUEST_METHOD' ] == 'GET' ) {
header ( 'HTTP/1.1 302 Moved Temporarily' );
header ( 'Location: ' . $this -> getBaseURL () . '/' . $this -> query_string );
2018-10-22 04:24:47 +02:00
exit ();
}
Core\Session :: init ();
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( 'init_1' );
2018-10-22 04:24:47 +02:00
}
// Exclude the backend processes from the session management
if ( ! $this -> isBackend ()) {
$stamp1 = microtime ( true );
session_start ();
2019-02-16 23:17:10 +01:00
$this -> profiler -> saveTimestamp ( $stamp1 , 'parser' , Core\System :: callstack ());
2018-10-22 06:16:30 +02:00
Core\L10n :: setSessionVariable ();
Core\L10n :: setLangFromSession ();
2018-10-22 04:24:47 +02:00
} else {
$_SESSION = [];
Core\Worker :: executeIfIdle ();
}
2018-10-22 06:07:56 +02:00
// ZRL
2018-10-22 04:24:47 +02:00
if ( ! empty ( $_GET [ 'zrl' ]) && $this -> getMode () -> isNormal ()) {
$this -> query_string = Model\Profile :: stripZrls ( $this -> query_string );
if ( ! local_user ()) {
// Only continue when the given profile link seems valid
// Valid profile links contain a path with "/profile/" and no query parameters
if (( parse_url ( $_GET [ 'zrl' ], PHP_URL_QUERY ) == " " ) &&
strstr ( parse_url ( $_GET [ 'zrl' ], PHP_URL_PATH ), " /profile/ " )) {
if ( defaults ( $_SESSION , " visitor_home " , " " ) != $_GET [ " zrl " ]) {
$_SESSION [ 'my_url' ] = $_GET [ 'zrl' ];
$_SESSION [ 'authenticated' ] = 0 ;
}
Model\Profile :: zrlInit ( $this );
} else {
// Someone came with an invalid parameter, maybe as a DDoS attempt
// We simply stop processing here
2018-10-30 16:43:27 +01:00
Core\Logger :: log ( " Invalid ZRL parameter " . $_GET [ 'zrl' ], Core\Logger :: DEBUG );
2018-10-22 21:16:59 +02:00
Core\System :: httpExit ( 403 , [ 'title' => '403 Forbidden' ]);
2018-10-22 04:24:47 +02:00
}
}
}
if ( ! empty ( $_GET [ 'owt' ]) && $this -> getMode () -> isNormal ()) {
$token = $_GET [ 'owt' ];
$this -> query_string = Model\Profile :: stripQueryParam ( $this -> query_string , 'owt' );
Model\Profile :: openWebAuthInit ( $token );
}
Module\Login :: sessionAuth ();
if ( empty ( $_SESSION [ 'authenticated' ])) {
header ( 'X-Account-Management-Status: none' );
}
$_SESSION [ 'sysmsg' ] = defaults ( $_SESSION , 'sysmsg' , []);
$_SESSION [ 'sysmsg_info' ] = defaults ( $_SESSION , 'sysmsg_info' , []);
$_SESSION [ 'last_updated' ] = defaults ( $_SESSION , 'last_updated' , []);
/*
* check_config () is responsible for running update scripts . These automatically
* update the DB schema whenever we push a new one out . It also checks to see if
* any addons have been added or removed and reacts accordingly .
*/
// in install mode, any url loads install module
// but we need "view" module for stylesheet
if ( $this -> getMode () -> isInstall () && $this -> module != 'view' ) {
$this -> module = 'install' ;
} elseif ( ! $this -> getMode () -> has ( App\Mode :: MAINTENANCEDISABLED ) && $this -> module != 'view' ) {
$this -> module = 'maintenance' ;
} else {
$this -> checkURL ();
2019-02-03 22:46:50 +01:00
Core\Update :: check ( $this -> basePath , false );
2018-10-23 00:01:17 +02:00
Core\Addon :: loadAddons ();
Core\Hook :: loadHooks ();
2018-10-22 04:24:47 +02:00
}
2018-10-22 20:44:55 +02:00
$this -> page = [
'aside' => '' ,
'bottom' => '' ,
'content' => '' ,
'footer' => '' ,
'htmlhead' => '' ,
'nav' => '' ,
'page_title' => '' ,
'right_aside' => '' ,
'template' => '' ,
'title' => ''
];
2018-10-22 04:24:47 +02:00
if ( strlen ( $this -> module )) {
// Compatibility with the Android Diaspora client
if ( $this -> module == 'stream' ) {
2018-10-22 22:15:50 +02:00
$this -> internalRedirect ( 'network?f=&order=post' );
2018-10-22 04:24:47 +02:00
}
if ( $this -> module == 'conversations' ) {
2018-10-22 22:15:50 +02:00
$this -> internalRedirect ( 'message' );
2018-10-22 04:24:47 +02:00
}
if ( $this -> module == 'commented' ) {
2018-10-22 22:15:50 +02:00
$this -> internalRedirect ( 'network?f=&order=comment' );
2018-10-22 04:24:47 +02:00
}
if ( $this -> module == 'liked' ) {
2018-10-22 22:15:50 +02:00
$this -> internalRedirect ( 'network?f=&order=comment' );
2018-10-22 04:24:47 +02:00
}
if ( $this -> module == 'activity' ) {
2018-10-22 22:15:50 +02:00
$this -> internalRedirect ( 'network/?f=&conv=1' );
2018-10-22 04:24:47 +02:00
}
if (( $this -> module == 'status_messages' ) && ( $this -> cmd == 'status_messages/new' )) {
2018-10-22 22:15:50 +02:00
$this -> internalRedirect ( 'bookmarklet' );
2018-10-22 04:24:47 +02:00
}
if (( $this -> module == 'user' ) && ( $this -> cmd == 'user/edit' )) {
2018-10-22 22:15:50 +02:00
$this -> internalRedirect ( 'settings' );
2018-10-22 04:24:47 +02:00
}
if (( $this -> module == 'tag_followings' ) && ( $this -> cmd == 'tag_followings/manage' )) {
2018-10-22 22:15:50 +02:00
$this -> internalRedirect ( 'search' );
2018-10-22 04:24:47 +02:00
}
// Compatibility with the Firefox App
if (( $this -> module == " users " ) && ( $this -> cmd == " users/sign_in " )) {
$this -> module = " login " ;
}
2018-10-22 06:07:56 +02:00
$privateapps = Core\Config :: get ( 'config' , 'private_addons' , false );
2018-10-23 03:09:53 +02:00
if ( Core\Addon :: isEnabled ( $this -> module ) && file_exists ( " addon/ { $this -> module } / { $this -> module } .php " )) {
2018-10-22 04:24:47 +02:00
//Check if module is an app and if public access to apps is allowed or not
2018-10-23 17:27:53 +02:00
if (( ! local_user ()) && Core\Hook :: isAddonApp ( $this -> module ) && $privateapps ) {
2018-10-22 04:24:47 +02:00
info ( Core\L10n :: t ( " You must be logged in to use addons. " ));
} else {
include_once " addon/ { $this -> module } / { $this -> module } .php " ;
if ( function_exists ( $this -> module . '_module' )) {
LegacyModule :: setModuleFile ( " addon/ { $this -> module } / { $this -> module } .php " );
$this -> module_class = 'Friendica\\LegacyModule' ;
$this -> module_loaded = true ;
}
}
}
// Controller class routing
if ( ! $this -> module_loaded && class_exists ( 'Friendica\\Module\\' . ucfirst ( $this -> module ))) {
$this -> module_class = 'Friendica\\Module\\' . ucfirst ( $this -> module );
$this -> module_loaded = true ;
}
/* If not , next look for a 'standard' program module in the 'mod' directory
* We emulate a Module class through the LegacyModule class
*/
if ( ! $this -> module_loaded && file_exists ( " mod/ { $this -> module } .php " )) {
LegacyModule :: setModuleFile ( " mod/ { $this -> module } .php " );
$this -> module_class = 'Friendica\\LegacyModule' ;
$this -> module_loaded = true ;
}
/* The URL provided does not resolve to a valid module .
*
* On Dreamhost sites , quite often things go wrong for no apparent reason and they send us to '/internal_error.html' .
* We don ' t like doing this , but as it occasionally accounts for 10 - 20 % or more of all site traffic -
* we are going to trap this and redirect back to the requested page . As long as you don ' t have a critical error on your page
* this will often succeed and eventually do the right thing .
*
* Otherwise we are going to emit a 404 not found .
*/
if ( ! $this -> module_loaded ) {
// Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
if ( ! empty ( $_SERVER [ 'QUERY_STRING' ]) && preg_match ( '/{[0-9]}/' , $_SERVER [ 'QUERY_STRING' ]) !== 0 ) {
exit ();
}
if ( ! empty ( $_SERVER [ 'QUERY_STRING' ]) && ( $_SERVER [ 'QUERY_STRING' ] === 'q=internal_error.html' ) && isset ( $dreamhost_error_hack )) {
2018-10-29 22:20:46 +01:00
Core\Logger :: log ( 'index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER [ 'REQUEST_URI' ]);
2018-10-22 22:15:50 +02:00
$this -> internalRedirect ( $_SERVER [ 'REQUEST_URI' ]);
2018-10-22 04:24:47 +02:00
}
2018-10-30 16:43:27 +01:00
Core\Logger :: log ( 'index.php: page not found: ' . $_SERVER [ 'REQUEST_URI' ] . ' ADDRESS: ' . $_SERVER [ 'REMOTE_ADDR' ] . ' QUERY: ' . $_SERVER [ 'QUERY_STRING' ], Core\Logger :: DEBUG );
2018-10-22 04:24:47 +02:00
header ( $_SERVER [ " SERVER_PROTOCOL " ] . ' 404 ' . Core\L10n :: t ( 'Not Found' ));
2018-10-31 15:44:06 +01:00
$tpl = Core\Renderer :: getMarkupTemplate ( " 404.tpl " );
2018-10-31 15:35:50 +01:00
$this -> page [ 'content' ] = Core\Renderer :: replaceMacros ( $tpl , [
2018-10-22 04:24:47 +02:00
'$message' => Core\L10n :: t ( 'Page not found.' )
]);
}
}
2018-11-21 15:13:24 +01:00
$content = '' ;
2018-10-22 04:24:47 +02:00
2018-11-21 15:13:24 +01:00
// Initialize module that can set the current theme in the init() method, either directly or via App->profile_uid
if ( $this -> module_loaded ) {
$this -> page [ 'page_title' ] = $this -> module ;
$placeholder = '' ;
2019-01-20 04:03:49 +01:00
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( $this -> module . '_mod_init' , $placeholder );
2019-01-20 03:52:43 +01:00
2018-11-21 15:13:24 +01:00
call_user_func ([ $this -> module_class , 'init' ]);
2019-01-20 03:52:43 +01:00
2018-11-21 15:13:24 +01:00
// "rawContent" is especially meant for technical endpoints.
// This endpoint doesn't need any theme initialization or other comparable stuff.
if ( ! $this -> error ) {
call_user_func ([ $this -> module_class , 'rawContent' ]);
2018-11-20 22:34:39 +01:00
}
2018-11-21 15:13:24 +01:00
}
2018-10-22 04:24:47 +02:00
2018-11-21 15:13:24 +01:00
// Load current theme info after module has been initialized as theme could have been set in module
$theme_info_file = 'view/theme/' . $this -> getCurrentTheme () . '/theme.php' ;
if ( file_exists ( $theme_info_file )) {
require_once $theme_info_file ;
}
2018-10-22 04:24:47 +02:00
2018-11-21 15:13:24 +01:00
if ( function_exists ( str_replace ( '-' , '_' , $this -> getCurrentTheme ()) . '_init' )) {
$func = str_replace ( '-' , '_' , $this -> getCurrentTheme ()) . '_init' ;
$func ( $this );
}
2018-11-20 22:34:39 +01:00
2018-11-21 15:13:24 +01:00
if ( $this -> module_loaded ) {
if ( ! $this -> error && $_SERVER [ 'REQUEST_METHOD' ] === 'POST' ) {
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( $this -> module . '_mod_post' , $_POST );
2018-11-21 15:13:24 +01:00
call_user_func ([ $this -> module_class , 'post' ]);
}
2018-11-20 22:34:39 +01:00
2018-11-21 15:13:24 +01:00
if ( ! $this -> error ) {
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( $this -> module . '_mod_afterpost' , $placeholder );
2018-11-21 15:13:24 +01:00
call_user_func ([ $this -> module_class , 'afterpost' ]);
2018-10-22 04:24:47 +02:00
}
2018-11-21 15:13:24 +01:00
if ( ! $this -> error ) {
$arr = [ 'content' => $content ];
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( $this -> module . '_mod_content' , $arr );
2018-11-21 15:13:24 +01:00
$content = $arr [ 'content' ];
$arr = [ 'content' => call_user_func ([ $this -> module_class , 'content' ])];
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( $this -> module . '_mod_aftercontent' , $arr );
2018-11-21 15:13:24 +01:00
$content .= $arr [ 'content' ];
2018-10-22 04:24:47 +02:00
}
2018-11-21 15:13:24 +01:00
}
2018-10-22 04:24:47 +02:00
2018-11-21 15:13:24 +01:00
// initialise content region
if ( $this -> getMode () -> isNormal ()) {
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( 'page_content_top' , $this -> page [ 'content' ]);
2018-12-29 06:18:52 +01:00
}
2018-11-21 15:13:24 +01:00
$this -> page [ 'content' ] .= $content ;
2018-10-22 04:24:47 +02:00
/* Create the page head after setting the language
* and getting any auth credentials .
*
* Moved initHead () and initFooter () to after
* all the module functions have executed so that all
* theme choices made by the modules can take effect .
*/
$this -> initHead ();
/* Build the page ending -- this is stuff that goes right before
* the closing </ body > tag
*/
$this -> initFooter ();
/* now that we ' ve been through the module content , see if the page reported
* a permission problem and if so , a 403 response would seem to be in order .
*/
if ( stristr ( implode ( " " , $_SESSION [ 'sysmsg' ]), Core\L10n :: t ( 'Permission denied' ))) {
header ( $_SERVER [ " SERVER_PROTOCOL " ] . ' 403 ' . Core\L10n :: t ( 'Permission denied.' ));
}
// Report anything which needs to be communicated in the notification area (before the main body)
2018-12-26 07:06:24 +01:00
Core\Hook :: callAll ( 'page_end' , $this -> page [ 'content' ]);
2018-10-22 04:24:47 +02:00
// Add the navigation (menu) template
if ( $this -> module != 'install' && $this -> module != 'maintenance' ) {
2018-10-31 15:44:06 +01:00
$this -> page [ 'htmlhead' ] .= Core\Renderer :: replaceMacros ( Core\Renderer :: getMarkupTemplate ( 'nav_head.tpl' ), []);
2018-10-22 20:44:55 +02:00
$this -> page [ 'nav' ] = Content\Nav :: build ( $this );
2018-10-22 04:24:47 +02:00
}
// Build the page - now that we have all the components
if ( isset ( $_GET [ " mode " ]) && (( $_GET [ " mode " ] == " raw " ) || ( $_GET [ " mode " ] == " minimal " ))) {
$doc = new DOMDocument ();
$target = new DOMDocument ();
$target -> loadXML ( " <root></root> " );
$content = mb_convert_encoding ( $this -> page [ " content " ], 'HTML-ENTITIES' , " UTF-8 " );
/// @TODO one day, kill those error-surpressing @ stuff, or PHP should ban it
@ $doc -> loadHTML ( $content );
$xpath = new DOMXPath ( $doc );
$list = $xpath -> query ( " //*[contains(@id,'tread-wrapper-')] " ); /* */
foreach ( $list as $item ) {
$item = $target -> importNode ( $item , true );
// And then append it to the target
$target -> documentElement -> appendChild ( $item );
}
2019-01-07 18:51:48 +01:00
if ( $_GET [ " mode " ] == " raw " ) {
header ( " Content-type: text/html; charset=utf-8 " );
2018-10-22 04:24:47 +02:00
2019-01-07 18:51:48 +01:00
echo substr ( $target -> saveHTML (), 6 , - 8 );
2018-10-22 04:24:47 +02:00
2019-01-07 18:51:48 +01:00
exit ();
}
2018-10-22 04:24:47 +02:00
}
$page = $this -> page ;
$profile = $this -> profile ;
header ( " X-Friendica-Version: " . FRIENDICA_VERSION );
header ( " Content-type: text/html; charset=utf-8 " );
if ( Core\Config :: get ( 'system' , 'hsts' ) && ( Core\Config :: get ( 'system' , 'ssl_policy' ) == SSL_POLICY_FULL )) {
header ( " Strict-Transport-Security: max-age=31536000 " );
}
// Some security stuff
header ( 'X-Content-Type-Options: nosniff' );
header ( 'X-XSS-Protection: 1; mode=block' );
header ( 'X-Permitted-Cross-Domain-Policies: none' );
header ( 'X-Frame-Options: sameorigin' );
// Things like embedded OSM maps don't work, when this is enabled
// header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' https: data:; media-src 'self' https:; child-src 'self' https:; object-src 'none'");
2018-10-22 14:17:55 +02:00
/* We use $_GET [ " mode " ] for special page templates . So we will check if we have
2018-10-22 04:24:47 +02:00
* to load another page template than the default one .
* The page templates are located in / view / php / or in the theme directory .
*/
if ( isset ( $_GET [ " mode " ])) {
$template = Core\Theme :: getPathForFile ( $_GET [ " mode " ] . '.php' );
}
// If there is no page template use the default page template
if ( empty ( $template )) {
$template = Core\Theme :: getPathForFile ( " default.php " );
}
// Theme templates expect $a as an App instance
$a = $this ;
2018-10-26 14:13:31 +02:00
// Used as is in view/php/default.php
$lang = Core\L10n :: getCurrentLang ();
2018-10-22 04:24:47 +02:00
/// @TODO Looks unsafe (remote-inclusion), is maybe not but Core\Theme::getPathForFile() uses file_exists() but does not escape anything
require_once $template ;
2018-10-20 18:19:55 +02:00
}
2018-10-13 20:02:04 +02:00
/**
2018-10-19 23:42:33 +02:00
* Redirects to another module relative to the current Friendica base .
2018-10-19 20:11:27 +02:00
* If you want to redirect to a external URL , use System :: externalRedirectTo ()
2018-10-13 20:02:04 +02:00
*
* @ param string $toUrl The destination URL ( Default is empty , which is the default page of the Friendica node )
* @ param bool $ssl if true , base URL will try to get called with https :// ( works just for relative paths )
2018-10-19 20:11:27 +02:00
*
* @ throws InternalServerErrorException In Case the given URL is not relative to the Friendica node
2018-10-13 20:02:04 +02:00
*/
2018-10-19 20:11:27 +02:00
public function internalRedirect ( $toUrl = '' , $ssl = false )
2018-10-13 20:02:04 +02:00
{
2018-11-30 12:27:17 +01:00
if ( ! empty ( parse_url ( $toUrl , PHP_URL_SCHEME ))) {
2018-10-23 12:17:41 +02:00
throw new InternalServerErrorException ( " ' $toUrl is not a relative path, please use System::externalRedirectTo " );
2018-10-13 20:02:04 +02:00
}
2018-10-19 20:11:27 +02:00
$redirectTo = $this -> getBaseURL ( $ssl ) . '/' . ltrim ( $toUrl , '/' );
2018-10-23 00:07:00 +02:00
Core\System :: externalRedirect ( $redirectTo );
2018-10-13 20:02:04 +02:00
}
2018-10-24 20:16:14 +02:00
/**
2018-10-24 20:52:38 +02:00
* Automatically redirects to relative or absolute URL
2018-10-24 20:16:14 +02:00
* Should only be used if it isn ' t clear if the URL is either internal or external
*
* @ param string $toUrl The target URL
2019-01-06 22:06:53 +01:00
* @ throws InternalServerErrorException
2018-10-24 20:16:14 +02:00
*/
public function redirect ( $toUrl )
{
2018-11-30 12:27:17 +01:00
if ( ! empty ( parse_url ( $toUrl , PHP_URL_SCHEME ))) {
2018-10-24 20:24:22 +02:00
Core\System :: externalRedirect ( $toUrl );
2018-10-24 20:16:14 +02:00
} else {
$this -> internalRedirect ( $toUrl );
}
}
2017-05-08 08:11:38 +02:00
}