2017-05-08 06:11:38 +00:00
< ? php
2018-01-22 14:54:13 +00:00
/**
* @ file src / App . php
*/
2017-05-08 06:11:38 +00:00
namespace Friendica ;
2018-07-20 02:15:21 +00:00
use Detection\MobileDetect ;
2018-10-22 02:24:47 +00:00
use DOMDocument ;
use DOMXPath ;
2018-07-20 02:15:21 +00:00
use Exception ;
2019-02-10 18:52:21 +00:00
use Friendica\Core\Config\Cache\IConfigCache ;
use Friendica\Core\Config\Configuration ;
2019-04-04 03:35:24 +00:00
use Friendica\Core\Hook ;
2019-04-01 01:50:00 +00:00
use Friendica\Core\Theme ;
2018-07-20 12:19:26 +00:00
use Friendica\Database\DBA ;
2019-03-19 06:44:51 +00:00
use Friendica\Model\Profile ;
2018-10-06 14:27:20 +00:00
use Friendica\Network\HTTPException\InternalServerErrorException ;
2019-04-08 19:12:10 +00:00
use Friendica\Util\BaseURL ;
2019-03-24 11:54:26 +00:00
use Friendica\Util\Config\ConfigFileLoader ;
2019-03-19 06:44:51 +00:00
use Friendica\Util\HTTPSignature ;
2019-02-16 22:11:30 +00:00
use Friendica\Util\Profiler ;
2019-04-01 01:50:00 +00:00
use Friendica\Util\Strings ;
2018-12-30 20:42:56 +00:00
use Psr\Log\LoggerInterface ;
2017-05-11 15:53:04 +00:00
2017-05-08 06:11:38 +00: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 00:13:21 +00:00
class App
{
2017-12-17 16:37:03 +00:00
public $module_class = null ;
2018-06-26 00:38:41 +00:00
public $query_string = '' ;
public $page = [];
2017-05-08 06:11:38 +00:00
public $profile ;
public $profile_uid ;
public $user ;
public $cid ;
public $contact ;
public $contacts ;
public $page_contact ;
public $content ;
2018-01-15 13:05:12 +00:00
public $data = [];
2017-05-08 06:11:38 +00:00
public $error = false ;
2018-06-26 00:38:41 +00:00
public $cmd = '' ;
2017-05-08 06:11:38 +00: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 13:05:12 +00:00
public $theme_info = [];
2017-05-08 06:11:38 +00: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 01:30:51 +00:00
public $stylesheets = [];
2018-09-21 01:01:05 +00:00
public $footerScripts = [];
2018-10-06 14:27:20 +00:00
/**
* @ var App\Mode The Mode of the Application
*/
private $mode ;
2019-04-06 03:16:12 +00:00
/**
* @ var App\Router
*/
private $router ;
2018-10-09 17:58:58 +00:00
/**
2019-04-08 19:12:10 +00:00
* @ var BaseURL
2018-10-09 17:58:58 +00:00
*/
2019-04-08 19:12:10 +00:00
private $baseURL ;
2018-10-09 17:58:58 +00: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 16:57:31 +00:00
/**
* @ var bool check if request was an AJAX ( xmlhttprequest ) request
*/
private $isAjax ;
2019-01-12 01:48:29 +00:00
/**
* @ var MobileDetect
*/
public $mobileDetect ;
2018-12-30 20:42:56 +00:00
/**
2019-02-10 18:52:21 +00:00
* @ var Configuration The config
2019-02-03 21:22:04 +00:00
*/
private $config ;
2019-02-17 20:12:12 +00:00
/**
* @ var LoggerInterface The logger
2018-12-30 20:42:56 +00:00
*/
private $logger ;
2019-02-03 21:22:04 +00:00
/**
2019-02-16 22:11:30 +00:00
* @ var Profiler The profiler of this app
2019-02-03 21:22:04 +00:00
*/
2019-02-16 22:11:30 +00:00
private $profiler ;
2019-02-03 21:22:04 +00:00
/**
* Returns the current config cache of this node
*
2019-02-10 18:52:21 +00:00
* @ return IConfigCache
2019-02-03 21:22:04 +00:00
*/
2019-02-10 18:52:21 +00:00
public function getConfigCache ()
2019-02-03 21:22:04 +00:00
{
2019-02-10 18:52:21 +00:00
return $this -> config -> getCache ();
2019-02-03 21:22:04 +00:00
}
2019-04-13 19:46:06 +00:00
/**
* Returns the current config of this nodde
*
* @ return Configuration
*/
public function getConfig ()
{
return $this -> config ;
}
2019-02-03 21:22:04 +00:00
/**
* The basepath of this app
*
* @ return string
*/
public function getBasePath ()
{
2019-03-24 21:51:30 +00:00
return $this -> config -> get ( 'system' , 'basepath' );
2019-02-03 21:22:04 +00:00
}
2019-02-17 20:12:12 +00:00
/**
* The Logger of this app
*
* @ return LoggerInterface
*/
public function getLogger ()
{
return $this -> logger ;
}
2019-02-16 22:11:30 +00:00
/**
* The profiler of this app
*
* @ return Profiler
*/
public function getProfiler ()
{
return $this -> profiler ;
}
2019-03-14 01:36:49 +00:00
/**
* Returns the Mode of the Application
*
* @ return App\Mode The Application Mode
*/
public function getMode ()
{
return $this -> mode ;
}
2019-04-08 19:12:10 +00:00
/**
* Returns the router of the Application
*
* @ return App\Router
*/
2019-04-06 03:16:12 +00:00
public function getRouter ()
{
return $this -> router ;
}
2018-09-21 13:54:40 +00: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 02:24:47 +00:00
* @ see initHead ()
2018-09-21 13:54:40 +00:00
*
* @ param string $path
*/
2018-09-21 01:30:51 +00:00
public function registerStylesheet ( $path )
{
2019-03-31 10:00:20 +00:00
if ( mb_strpos ( $path , $this -> getBasePath () . DIRECTORY_SEPARATOR ) === 0 ) {
$path = mb_substr ( $path , mb_strlen ( $this -> getBasePath () . DIRECTORY_SEPARATOR ));
2019-03-30 10:13:27 +00:00
}
2018-09-21 01:30:51 +00:00
2019-03-31 06:53:25 +00:00
$this -> stylesheets [] = trim ( $path , '/' );
2018-09-21 01:30:51 +00:00
}
2018-09-21 13:54:40 +00: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 02:24:47 +00:00
* @ see initFooter ()
2018-09-21 13:54:40 +00:00
*
* @ param string $path
*/
2018-09-21 01:01:05 +00:00
public function registerFooterScript ( $path )
{
2019-03-23 14:20:51 +00:00
$url = str_replace ( $this -> getBasePath () . DIRECTORY_SEPARATOR , '' , $path );
2018-09-21 01:01:05 +00:00
2018-09-21 01:30:51 +00:00
$this -> footerScripts [] = trim ( $url , '/' );
2018-09-21 01:01:05 +00:00
}
2017-06-11 19:51:18 +00:00
public $queue ;
2017-05-08 06:11:38 +00:00
/**
* @ brief App constructor .
*
2019-02-10 18:52:21 +00:00
* @ param Configuration $config The Configuration
2019-03-14 01:36:49 +00:00
* @ param App\Mode $mode The mode of this Friendica app
2019-04-06 03:16:12 +00:00
* @ param App\Router $router The router of this Friendica app
2019-04-08 19:12:10 +00:00
* @ param BaseURL $baseURL The full base URL of this Friendica app
2019-02-17 20:12:12 +00:00
* @ param LoggerInterface $logger The current app logger
2019-02-16 22:11:30 +00:00
* @ param Profiler $profiler The profiler of this application
2018-12-30 20:42:56 +00:00
* @ param bool $isBackend Whether it is used for backend or frontend ( Default true = backend )
2018-08-20 20:15:39 +00:00
*
* @ throws Exception if the Basepath is not usable
2017-05-08 06:11:38 +00:00
*/
2019-04-08 19:12:10 +00:00
public function __construct ( Configuration $config , App\Mode $mode , App\Router $router , BaseURL $baseURL , LoggerInterface $logger , Profiler $profiler , $isBackend = true )
2018-01-16 00:13:21 +00:00
{
2019-02-17 23:26:38 +00:00
BaseObject :: setApp ( $this );
2019-02-17 20:12:12 +00:00
$this -> config = $config ;
2019-03-14 01:36:49 +00:00
$this -> mode = $mode ;
2019-04-06 03:16:12 +00:00
$this -> router = $router ;
2019-04-08 19:12:10 +00:00
$this -> baseURL = $baseURL ;
2019-04-06 03:16:12 +00:00
$this -> profiler = $profiler ;
$this -> logger = $logger ;
2017-10-11 18:21:10 +00:00
2018-10-09 17:58:58 +00:00
$this -> checkFriendicaApp ();
2017-10-11 18:21:10 +00:00
2019-02-16 22:11:30 +00:00
$this -> profiler -> reset ();
2017-05-08 06:11:38 +00:00
2018-08-27 04:15:55 +00:00
$this -> reload ();
2017-05-08 06:11:38 +00:00
2018-07-02 11:23:47 +00: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 06:11:38 +00:00
set_include_path (
get_include_path () . PATH_SEPARATOR
2019-03-23 14:20:51 +00:00
. $this -> getBasePath () . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR
. $this -> getBasePath () . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
. $this -> getBasePath ());
2017-05-08 06:11:38 +00:00
2018-10-22 02:24:47 +00:00
if ( ! empty ( $_SERVER [ 'QUERY_STRING' ]) && strpos ( $_SERVER [ 'QUERY_STRING' ], 'pagename=' ) === 0 ) {
2017-05-08 06:11:38 +00:00
$this -> query_string = substr ( $_SERVER [ 'QUERY_STRING' ], 9 );
2018-10-22 02:24:47 +00:00
} elseif ( ! empty ( $_SERVER [ 'QUERY_STRING' ]) && strpos ( $_SERVER [ 'QUERY_STRING' ], 'q=' ) === 0 ) {
2017-05-08 06:11:38 +00:00
$this -> query_string = substr ( $_SERVER [ 'QUERY_STRING' ], 2 );
}
2018-06-26 00:38:41 +00:00
// removing trailing / - maybe a nginx problem
$this -> query_string = ltrim ( $this -> query_string , '/' );
if ( ! empty ( $_GET [ 'pagename' ])) {
2017-05-08 06:11:38 +00:00
$this -> cmd = trim ( $_GET [ 'pagename' ], '/\\' );
2018-06-26 00:38:41 +00:00
} elseif ( ! empty ( $_GET [ 'q' ])) {
2017-05-08 06:11:38 +00: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 13:05:12 +00:00
$this -> argv = [ 'home' ];
2017-05-08 06:11:38 +00:00
$this -> module = 'home' ;
}
2019-04-11 09:27:26 +00:00
$this -> isBackend = $isBackend || $this -> checkBackend ( $this -> module );
2017-05-08 06:11:38 +00:00
// Detect mobile devices
2017-05-11 15:53:04 +00:00
$mobile_detect = new MobileDetect ();
2019-01-12 01:48:29 +00:00
$this -> mobileDetect = $mobile_detect ;
2017-05-08 06:11:38 +00:00
$this -> is_mobile = $mobile_detect -> isMobile ();
$this -> is_tablet = $mobile_detect -> isTablet ();
2018-10-13 17:10:46 +00:00
$this -> isAjax = strtolower ( defaults ( $_SERVER , 'HTTP_X_REQUESTED_WITH' , '' )) == 'xmlhttprequest' ;
2018-10-13 16:57:31 +00:00
2017-05-08 06:11:38 +00:00
// Register template engines
2018-10-31 17:25:38 +00:00
Core\Renderer :: registerTemplateEngine ( 'Friendica\Render\FriendicaSmartyEngine' );
2018-06-26 00:38:41 +00:00
}
2018-08-27 04:15:55 +00:00
/**
* Reloads the whole app instance
*/
public function reload ()
{
2019-03-23 14:20:51 +00:00
$this -> getMode () -> determine ( $this -> getBasePath ());
2018-08-27 04:15:55 +00:00
2018-10-06 14:27:20 +00:00
if ( $this -> getMode () -> has ( App\Mode :: DBAVAILABLE )) {
2019-03-24 11:54:26 +00:00
$loader = new ConfigFileLoader ( $this -> getBasePath (), $this -> getMode ());
2019-02-10 18:52:21 +00:00
$this -> config -> getCache () -> load ( $loader -> loadCoreConfig ( 'addon' ), true );
2019-02-23 00:24:08 +00:00
$this -> profiler -> update (
$this -> config -> get ( 'system' , 'profiler' , false ),
$this -> config -> get ( 'rendertime' , 'callstack' , false ));
Core\Hook :: loadHooks ();
2019-03-24 11:54:26 +00:00
$loader = new ConfigFileLoader ( $this -> getBasePath (), $this -> mode );
2019-02-23 00:24:08 +00:00
Core\Hook :: callAll ( 'load_config' , $loader );
2018-08-27 04:15:55 +00:00
}
$this -> loadDefaultTimezone ();
2018-10-22 04:16:30 +00:00
Core\L10n :: init ();
2018-08-27 04:15:55 +00:00
}
2018-06-28 03:05:38 +00:00
/**
* Loads the default timezone
*
* Include support for legacy $default_timezone
*
* @ global string $default_timezone
*/
2018-06-26 00:38:41 +00:00
private function loadDefaultTimezone ()
{
2019-02-03 21:22:04 +00:00
if ( $this -> config -> get ( 'system' , 'default_timezone' )) {
$this -> timezone = $this -> config -> get ( 'system' , 'default_timezone' );
2018-06-26 00:38:41 +00:00
} else {
global $default_timezone ;
$this -> timezone = ! empty ( $default_timezone ) ? $default_timezone : 'UTC' ;
}
if ( $this -> timezone ) {
date_default_timezone_set ( $this -> timezone );
}
}
/**
2019-04-08 19:12:10 +00:00
* Returns the scheme of the current call
* @ return string
*
* @ deprecated 2019.06 - use BaseURL -> getScheme () instead
2018-06-26 00:38:41 +00:00
*/
2018-10-09 17:58:58 +00:00
public function getScheme ()
2018-01-16 00:13:21 +00:00
{
2019-04-08 19:12:10 +00:00
return $this -> baseURL -> getScheme ();
2017-05-08 06:11:38 +00:00
}
/**
2019-04-08 19:12:10 +00:00
* Retrieves the Friendica instance base URL
2017-05-08 06:11:38 +00:00
*
2019-04-08 19:12:10 +00:00
* @ param bool $ssl Whether to append http or https under BaseURL :: SSL_POLICY_SELFSIGN
2017-05-08 06:11:38 +00:00
*
* @ return string Friendica server base URL
2019-04-08 19:12:10 +00:00
*
* @ deprecated 2019.06 - use BaseURL -> get ( $ssl ) instead
2017-05-08 06:11:38 +00:00
*/
2018-10-09 17:58:58 +00:00
public function getBaseURL ( $ssl = false )
2018-01-16 00:13:21 +00:00
{
2019-04-08 19:12:10 +00:00
return $this -> baseURL -> get ( $ssl );
2017-05-08 06:11:38 +00:00
}
/**
* @ brief Initializes the baseurl components
*
2018-01-16 00:13:21 +00:00
* Clears the baseurl cache to prevent inconsistencies
2017-05-08 06:11:38 +00:00
*
* @ param string $url
2019-04-08 19:12:10 +00:00
*
* @ deprecated 2019.06 - use BaseURL -> saveByURL ( $url ) instead
2017-05-08 06:11:38 +00:00
*/
2018-10-09 17:58:58 +00:00
public function setBaseURL ( $url )
2018-01-16 00:13:21 +00:00
{
2019-04-08 19:12:10 +00:00
$this -> baseURL -> saveByURL ( $url );
2017-05-08 06:11:38 +00:00
}
2019-04-08 19:12:10 +00:00
/**
* Returns the current hostname
*
* @ return string
*
* @ deprecated 2019.06 - use BaseURL -> getHostname () instead
*/
2018-10-09 17:58:58 +00:00
public function getHostName ()
2018-01-16 00:13:21 +00:00
{
2019-04-08 19:12:10 +00:00
return $this -> baseURL -> getHostname ();
2017-05-08 06:11:38 +00:00
}
2019-04-08 19:12:10 +00:00
/**
* Returns the sub - path of the full URL
*
* @ return string
*
* @ deprecated 2019.06 - use BaseURL -> getUrlPath () instead
*/
2018-10-09 23:18:47 +00:00
public function getURLPath ()
2018-01-16 00:13:21 +00:00
{
2019-04-08 19:12:10 +00:00
return $this -> baseURL -> getUrlPath ();
2017-05-08 06:11:38 +00:00
}
2018-09-21 13:54:40 +00:00
/**
* Initializes App -> page [ 'htmlhead' ] .
*
* Includes :
* - Page title
* - Favicons
* - Registered stylesheets ( through App -> registerStylesheet ())
* - Infinite scroll data
* - head . tpl template
*/
2018-09-21 01:30:51 +00:00
public function initHead ()
2018-01-16 00:13:21 +00:00
{
2018-10-22 02:24:47 +00:00
$interval = (( local_user ()) ? Core\PConfig :: get ( local_user (), 'system' , 'update_interval' ) : 40000 );
2017-05-08 06:11:38 +00: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 ;
}
2019-03-04 11:47:40 +00:00
// Default title: current module called
if ( empty ( $this -> page [ 'title' ]) && $this -> module ) {
$this -> page [ 'title' ] = ucfirst ( $this -> module );
2017-05-08 06:11:38 +00:00
}
2019-03-04 11:47:40 +00:00
// Prepend the sitename to the page title
2019-03-04 12:54:51 +00:00
$this -> page [ 'title' ] = $this -> config -> get ( 'config' , 'sitename' , '' ) . ( ! empty ( $this -> page [ 'title' ]) ? ' | ' . $this -> page [ 'title' ] : '' );
2019-03-04 11:47:40 +00:00
2018-10-31 17:25:38 +00:00
if ( ! empty ( Core\Renderer :: $theme [ 'stylesheet' ])) {
$stylesheet = Core\Renderer :: $theme [ 'stylesheet' ];
2017-05-08 06:11:38 +00:00
} else {
2018-09-21 13:54:09 +00:00
$stylesheet = $this -> getCurrentThemeStylesheetPath ();
2017-05-08 06:11:38 +00:00
}
2018-09-21 13:54:09 +00:00
$this -> registerStylesheet ( $stylesheet );
2019-03-23 14:23:23 +00:00
$shortcut_icon = $this -> config -> get ( 'system' , 'shortcut_icon' );
2017-05-08 06:11:38 +00:00
if ( $shortcut_icon == '' ) {
$shortcut_icon = 'images/friendica-32.png' ;
}
2019-03-23 14:23:23 +00:00
$touch_icon = $this -> config -> get ( 'system' , 'touch_icon' );
2017-05-08 06:11:38 +00:00
if ( $touch_icon == '' ) {
$touch_icon = 'images/friendica-128.png' ;
}
2018-12-26 06:06:24 +00:00
Core\Hook :: callAll ( 'head' , $this -> page [ 'htmlhead' ]);
2017-05-08 06:11:38 +00:00
2018-10-31 14:44:06 +00:00
$tpl = Core\Renderer :: getMarkupTemplate ( 'head.tpl' );
2018-09-21 13:54:09 +00: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 14:35:50 +00:00
$this -> page [ 'htmlhead' ] = Core\Renderer :: replaceMacros ( $tpl , [
2018-10-09 17:58:58 +00:00
'$baseurl' => $this -> getBaseURL (),
2018-01-16 00:13:21 +00:00
'$local_user' => local_user (),
'$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION ,
2018-10-22 02:24:47 +00:00
'$delitem' => Core\L10n :: t ( 'Delete this item?' ),
2018-01-16 00:13:21 +00:00
'$update_interval' => $interval ,
'$shortcut_icon' => $shortcut_icon ,
'$touch_icon' => $touch_icon ,
2019-03-23 14:23:23 +00:00
'$block_public' => intval ( $this -> config -> get ( 'system' , 'block_public' )),
2018-09-21 01:30:51 +00:00
'$stylesheets' => $this -> stylesheets ,
2018-01-16 00:13:21 +00:00
]) . $this -> page [ 'htmlhead' ];
}
2018-09-21 13:54:40 +00:00
/**
* Initializes App -> page [ 'footer' ] .
*
* Includes :
* - Javascript homebase
* - Mobile toggle link
* - Registered footer scripts ( through App -> registerFooterScript ())
* - footer . tpl template
*/
2018-09-21 01:01:05 +00:00
public function initFooter ()
2018-01-16 00:13:21 +00:00
{
2018-09-21 01:01:05 +00: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 16:45:49 +00:00
$homebase = 'profile/' . $this -> user [ 'nickname' ];
2018-09-21 01:01:05 +00:00
}
if ( isset ( $homebase )) {
$this -> page [ 'footer' ] .= '<script>var homebase="' . $homebase . '";</script>' . " \n " ;
2017-05-08 06:11:38 +00:00
}
2018-09-21 01:01:05 +00: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 13:55:48 +00:00
$link = 'toggle_mobile?address=' . urlencode ( curPageURL ());
2018-09-21 01:01:05 +00:00
} else {
2018-11-10 13:55:48 +00:00
$link = 'toggle_mobile?off=1&address=' . urlencode ( curPageURL ());
2018-09-21 01:01:05 +00:00
}
2018-10-31 14:44:06 +00:00
$this -> page [ 'footer' ] .= Core\Renderer :: replaceMacros ( Core\Renderer :: getMarkupTemplate ( " toggle_mobile_footer.tpl " ), [
2018-09-21 01:01:05 +00:00
'$toggle_link' => $link ,
'$toggle_text' => Core\L10n :: t ( 'toggle mobile' )
]);
}
2018-12-26 06:06:24 +00:00
Core\Hook :: callAll ( 'footer' , $this -> page [ 'footer' ]);
2018-09-21 01:01:05 +00:00
2018-10-31 14:44:06 +00:00
$tpl = Core\Renderer :: getMarkupTemplate ( 'footer.tpl' );
2018-10-31 14:35:50 +00:00
$this -> page [ 'footer' ] = Core\Renderer :: replaceMacros ( $tpl , [
2018-10-09 17:58:58 +00:00
'$baseurl' => $this -> getBaseURL (),
2018-09-21 01:01:05 +00:00
'$footerScripts' => $this -> footerScripts ,
2018-09-21 01:30:51 +00:00
]) . $this -> page [ 'footer' ];
2017-05-08 06:11:38 +00:00
}
/**
2018-01-16 00:13:21 +00:00
* @ brief Removes the base url from an url . This avoids some mixed content problems .
2017-05-08 06:11:38 +00:00
*
2018-10-09 23:18:47 +00:00
* @ param string $origURL
2017-05-08 06:11:38 +00:00
*
* @ return string The cleaned url
2019-01-06 21:06:53 +00:00
* @ throws InternalServerErrorException
2017-05-08 06:11:38 +00:00
*/
2018-10-09 23:18:47 +00:00
public function removeBaseURL ( $origURL )
2018-01-16 00:13:21 +00:00
{
2017-05-08 06:11:38 +00:00
// Remove the hostname from the url if it is an internal link
2018-11-08 16:28:29 +00:00
$nurl = Util\Strings :: normaliseLink ( $origURL );
$base = Util\Strings :: normaliseLink ( $this -> getBaseURL ());
2017-05-08 06:11:38 +00:00
$url = str_replace ( $base . '/' , '' , $nurl );
// if it is an external link return the orignal value
2018-11-08 16:28:29 +00:00
if ( $url == Util\Strings :: normaliseLink ( $origURL )) {
2018-10-09 23:18:47 +00:00
return $origURL ;
2017-05-08 06:11:38 +00:00
} else {
return $url ;
}
}
2018-10-09 17:58:58 +00:00
/**
* Returns the current UserAgent as a String
*
* @ return string the UserAgent as a String
2019-01-06 21:06:53 +00:00
* @ throws InternalServerErrorException
2018-10-09 17:58:58 +00:00
*/
public function getUserAgent ()
2018-01-16 00:13:21 +00:00
{
2017-05-08 06:11:38 +00:00
return
FRIENDICA_PLATFORM . " ' " .
FRIENDICA_CODENAME . " ' " .
FRIENDICA_VERSION . '-' .
DB_UPDATE_VERSION . '; ' .
2018-10-09 17:58:58 +00: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 06:11:38 +00:00
}
2018-10-09 17:58:58 +00: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 00:13:21 +00:00
{
2018-10-09 17:58:58 +00:00
return $this -> isFriendicaApp ;
2017-05-08 06:11:38 +00: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 .
*
2019-04-11 09:27:26 +00:00
* @ param string $module
* @ return bool
2017-05-08 06:11:38 +00:00
*/
2019-04-11 09:27:26 +00:00
private function checkBackend ( $module ) {
2018-01-16 00:13:21 +00:00
static $backends = [
'_well_known' ,
'api' ,
'dfrn_notify' ,
'fetch' ,
'hcard' ,
'hostxrd' ,
2019-03-26 02:08:01 +00:00
'manifest' ,
2018-01-16 00:13:21 +00:00
'nodeinfo' ,
'noscrape' ,
'p' ,
'poco' ,
'post' ,
'proxy' ,
'pubsub' ,
'pubsubhubbub' ,
'receive' ,
'rsd_xml' ,
'salmon' ,
'statistics_json' ,
'xrd' ,
];
2017-05-08 06:11:38 +00:00
// Check if current module is in backend or backend flag is set
2019-04-11 09:27:26 +00:00
return in_array ( $module , $backends );
2018-10-09 17:58:58 +00: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 06:11:38 +00:00
}
/**
* @ brief Checks if the maximum number of database processes is reached
*
* @ return bool Is the limit reached ?
*/
2018-06-30 18:07:01 +00:00
public function isMaxProcessesReached ()
2018-01-16 00:13:21 +00:00
{
2017-06-06 22:18:42 +00:00
// Deactivated, needs more investigating if this check really makes sense
return false ;
2017-05-08 06:11:38 +00:00
2018-01-16 00:13:21 +00:00
/*
* Commented out to suppress static analyzer issues
*
2017-05-08 06:11:38 +00:00
if ( $this -> is_backend ()) {
$process = 'backend' ;
2019-03-23 14:23:23 +00:00
$max_processes = $this -> config -> get ( 'system' , 'max_processes_backend' );
2017-05-08 06:11:38 +00:00
if ( intval ( $max_processes ) == 0 ) {
$max_processes = 5 ;
}
} else {
$process = 'frontend' ;
2019-03-23 14:23:23 +00:00
$max_processes = $this -> config -> get ( 'system' , 'max_processes_frontend' );
2017-05-08 06:11:38 +00:00
if ( intval ( $max_processes ) == 0 ) {
$max_processes = 20 ;
}
}
2018-07-21 12:40:21 +00:00
$processlist = DBA :: processlist ();
2017-05-08 06:11:38 +00:00
if ( $processlist [ 'list' ] != '' ) {
2018-10-30 15:43:27 +00:00
Core\Logger :: log ( 'Processcheck: Processes: ' . $processlist [ 'amount' ] . ' - Processlist: ' . $processlist [ 'list' ], Core\Logger :: DEBUG );
2017-05-08 06:11:38 +00:00
if ( $processlist [ 'amount' ] > $max_processes ) {
2018-10-30 15:43:27 +00:00
Core\Logger :: log ( 'Processcheck: Maximum number of processes for ' . $process . ' tasks (' . $max_processes . ') reached.' , Core\Logger :: DEBUG );
2017-05-08 06:11:38 +00:00
return true ;
}
}
return false ;
2018-01-16 00:13:21 +00:00
*/
2017-05-08 06:11:38 +00:00
}
/**
* @ brief Checks if the minimal memory is reached
*
* @ return bool Is the memory limit reached ?
2019-01-06 21:06:53 +00:00
* @ throws InternalServerErrorException
2017-05-08 06:11:38 +00:00
*/
2018-10-09 17:58:58 +00:00
public function isMinMemoryReached ()
2018-01-16 00:13:21 +00:00
{
2019-03-23 14:23:23 +00:00
$min_memory = $this -> config -> get ( 'system' , 'min_memory' , 0 );
2017-05-08 06:11:38 +00: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 13:05:12 +00:00
$meminfo = [];
2017-05-08 06:11:38 +00:00
foreach ( $memdata as $line ) {
2018-09-04 17:48:09 +00:00
$data = explode ( ':' , $line );
if ( count ( $data ) != 2 ) {
continue ;
}
list ( $key , $val ) = $data ;
2017-05-08 06:11:38 +00:00
$meminfo [ $key ] = ( int ) trim ( str_replace ( 'kB' , '' , $val ));
$meminfo [ $key ] = ( int ) ( $meminfo [ $key ] / 1024 );
}
2018-11-19 22:21:40 +00:00
if ( ! isset ( $meminfo [ 'MemFree' ])) {
2017-05-08 06:11:38 +00:00
return false ;
}
2018-11-19 22:21:40 +00:00
$free = $meminfo [ 'MemFree' ];
2017-05-08 06:11:38 +00:00
$reached = ( $free < $min_memory );
if ( $reached ) {
2018-10-30 15:43:27 +00:00
Core\Logger :: log ( 'Minimal memory reached: ' . $free . '/' . $meminfo [ 'MemTotal' ] . ' - limit ' . $min_memory , Core\Logger :: DEBUG );
2017-05-08 06:11:38 +00:00
}
return $reached ;
}
/**
* @ brief Checks if the maximum load is reached
*
* @ return bool Is the load reached ?
2019-01-06 21:06:53 +00:00
* @ throws InternalServerErrorException
2017-05-08 06:11:38 +00:00
*/
2018-06-30 18:07:01 +00:00
public function isMaxLoadReached ()
2018-01-16 00:13:21 +00:00
{
2018-10-09 17:58:58 +00:00
if ( $this -> isBackend ()) {
2017-05-08 06:11:38 +00:00
$process = 'backend' ;
2019-03-23 14:23:23 +00:00
$maxsysload = intval ( $this -> config -> get ( 'system' , 'maxloadavg' ));
2017-05-08 06:11:38 +00:00
if ( $maxsysload < 1 ) {
$maxsysload = 50 ;
}
} else {
$process = 'frontend' ;
2019-03-23 14:23:23 +00:00
$maxsysload = intval ( $this -> config -> get ( 'system' , 'maxloadavg_frontend' ));
2017-05-08 06:11:38 +00:00
if ( $maxsysload < 1 ) {
$maxsysload = 50 ;
}
}
2018-10-22 02:24:47 +00:00
$load = Core\System :: currentLoad ();
2017-05-08 06:11:38 +00:00
if ( $load ) {
if ( intval ( $load ) > $maxsysload ) {
2018-10-29 21:20:46 +00:00
Core\Logger :: log ( 'system: load ' . $load . ' for ' . $process . ' tasks (' . $maxsysload . ') too high.' );
2017-05-08 06:11:38 +00:00
return true ;
}
}
return false ;
}
2018-07-23 11:40:52 +00: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 21:06:53 +00:00
* @ throws InternalServerErrorException
2018-07-23 11:40:52 +00:00
*/
public function proc_run ( $command , $args )
2018-01-16 00:13:21 +00:00
{
2017-05-08 06:11:38 +00:00
if ( ! function_exists ( 'proc_open' )) {
return ;
}
2019-02-03 21:22:04 +00:00
$cmdline = $this -> config -> get ( 'config' , 'php_path' , 'php' ) . ' ' . escapeshellarg ( $command );
2017-05-08 06:11:38 +00:00
2018-07-23 11:40:52 +00: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 06:11:38 +00:00
}
2018-10-09 17:58:58 +00:00
if ( $this -> isMinMemoryReached ()) {
2017-05-08 06:11:38 +00:00
return ;
}
2018-07-02 11:47:42 +00:00
if ( strtoupper ( substr ( PHP_OS , 0 , 3 )) === 'WIN' ) {
2019-03-23 14:20:51 +00:00
$resource = proc_open ( 'cmd /c start /b ' . $cmdline , [], $foo , $this -> getBasePath ());
2017-05-08 06:11:38 +00:00
} else {
2019-03-23 14:20:51 +00:00
$resource = proc_open ( $cmdline . ' &' , [], $foo , $this -> getBasePath ());
2017-05-08 06:11:38 +00:00
}
if ( ! is_resource ( $resource )) {
2018-10-30 15:43:27 +00:00
Core\Logger :: log ( 'We got no resource for command ' . $cmdline , Core\Logger :: DEBUG );
2017-05-08 06:11:38 +00:00
return ;
}
proc_close ( $resource );
}
2018-04-07 01:47:16 +00:00
/**
* Generates the site ' s default sender email address
*
* @ return string
2019-01-06 21:06:53 +00:00
* @ throws InternalServerErrorException
2018-04-07 01:47:16 +00:00
*/
public function getSenderEmailAddress ()
{
2019-03-23 14:23:23 +00:00
$sender_email = $this -> config -> get ( 'config' , 'sender_email' );
2018-04-07 01:47:16 +00:00
if ( empty ( $sender_email )) {
2019-04-08 21:12:34 +00:00
$hostname = $this -> baseURL -> getHostname ();
2018-04-07 01:47:16 +00:00
if ( strpos ( $hostname , ':' )) {
$hostname = substr ( $hostname , 0 , strpos ( $hostname , ':' ));
}
2018-04-11 06:17:44 +00:00
$sender_email = 'noreply@' . $hostname ;
2018-04-07 01:47:16 +00:00
}
return $sender_email ;
}
2018-04-28 10:36:40 +00:00
2018-04-28 22:30:13 +00:00
/**
* Returns the current theme name .
*
2018-10-09 17:58:58 +00:00
* @ return string the name of the current theme
2019-01-06 21:06:53 +00:00
* @ throws InternalServerErrorException
2018-04-28 22:30:13 +00:00
*/
public function getCurrentTheme ()
{
2018-10-06 14:27:20 +00:00
if ( $this -> getMode () -> isInstall ()) {
2018-05-20 05:44:20 +00:00
return '' ;
}
2018-12-29 05:18:52 +00:00
if ( ! $this -> currentTheme ) {
$this -> computeCurrentTheme ();
}
2018-04-28 22:30:13 +00:00
2018-10-09 17:58:58 +00:00
return $this -> currentTheme ;
2018-04-28 22:30:13 +00:00
}
2018-12-29 05:18:52 +00:00
public function setCurrentTheme ( $theme )
{
$this -> currentTheme = $theme ;
}
2018-04-28 22:30:13 +00:00
/**
* Computes the current theme name based on the node settings , the user settings and the device type
*
* @ throws Exception
*/
private function computeCurrentTheme ()
{
2019-03-23 14:23:23 +00:00
$system_theme = $this -> config -> get ( 'system' , 'theme' );
2018-04-28 22:30:13 +00:00
if ( ! $system_theme ) {
2018-10-22 02:24:47 +00:00
throw new Exception ( Core\L10n :: t ( 'No system theme config value set.' ));
2018-04-28 22:30:13 +00:00
}
// Sane default
2018-10-09 17:58:58 +00:00
$this -> currentTheme = $system_theme ;
2018-04-28 22:30:13 +00: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 12:19:26 +00:00
$user = DBA :: selectFirst ( 'user' , [ 'theme' ], [ 'uid' => $this -> profile_uid ]);
2018-10-22 02:24:47 +00:00
if ( DBA :: isResult ( $user ) && ! Core\PConfig :: get ( local_user (), 'system' , 'always_my_theme' )) {
2018-04-28 22:30:13 +00:00
$page_theme = $user [ 'theme' ];
}
}
2018-08-05 13:56:21 +00:00
$user_theme = Core\Session :: get ( 'theme' , $system_theme );
2018-07-10 12:27:56 +00:00
2018-04-28 22:30:13 +00:00
// Specific mobile theme override
2018-08-05 13:56:21 +00:00
if (( $this -> is_mobile || $this -> is_tablet ) && Core\Session :: get ( 'show-mobile' , true )) {
2019-03-23 14:23:23 +00:00
$system_mobile_theme = $this -> config -> get ( 'system' , 'mobile-theme' );
2018-08-05 13:56:21 +00:00
$user_mobile_theme = Core\Session :: get ( 'mobile-theme' , $system_mobile_theme );
2018-04-28 22:30:13 +00: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 ;
}
2019-04-01 01:50:00 +00:00
$theme_name = Strings :: sanitizeFilePathItem ( $theme_name );
2018-04-28 22:30:13 +00:00
if ( $theme_name
2019-04-01 01:50:00 +00:00
&& in_array ( $theme_name , Theme :: getAllowedList ())
2018-04-28 22:30:13 +00:00
&& ( file_exists ( 'view/theme/' . $theme_name . '/style.css' )
|| file_exists ( 'view/theme/' . $theme_name . '/style.php' ))
) {
2018-10-09 17:58:58 +00:00
$this -> currentTheme = $theme_name ;
2018-04-28 22:30:13 +00: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 21:06:53 +00:00
* @ throws InternalServerErrorException
2018-04-28 22:30:13 +00:00
*/
public function getCurrentThemeStylesheetPath ()
{
return Core\Theme :: getStylesheetPath ( $this -> getCurrentTheme ());
}
2018-10-13 16:57:31 +00: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 16:19:55 +00:00
/**
* Sets the base url for use in cmdline programs which don ' t have
* $_SERVER variables
*/
public function checkURL ()
{
2019-03-23 14:23:23 +00:00
$url = $this -> config -> get ( 'system' , 'url' );
2018-10-20 16:19:55 +00: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
2019-04-08 21:12:34 +00: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 -> baseURL -> getHostname ()))) {
2019-03-23 14:23:23 +00:00
$this -> config -> set ( 'system' , 'url' , $this -> getBaseURL ());
2018-10-22 02:24:47 +00:00
}
}
/**
* 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 04:07:56 +00:00
// Force SSL redirection
2019-04-08 21:12:34 +00:00
if ( $this -> baseURL -> checkRedirectHttps ()) {
2018-10-22 04:07:56 +00:00
header ( 'HTTP/1.1 302 Moved Temporarily' );
header ( 'Location: ' . $this -> getBaseURL () . '/' . $this -> query_string );
2018-10-22 02:24:47 +00:00
exit ();
}
Core\Session :: init ();
2018-12-26 06:06:24 +00:00
Core\Hook :: callAll ( 'init_1' );
2018-10-22 02:24:47 +00:00
}
// Exclude the backend processes from the session management
if ( ! $this -> isBackend ()) {
$stamp1 = microtime ( true );
session_start ();
2019-02-16 22:17:10 +00:00
$this -> profiler -> saveTimestamp ( $stamp1 , 'parser' , Core\System :: callstack ());
2018-10-22 04:16:30 +00:00
Core\L10n :: setSessionVariable ();
Core\L10n :: setLangFromSession ();
2018-10-22 02:24:47 +00:00
} else {
$_SESSION = [];
Core\Worker :: executeIfIdle ();
}
2019-03-19 06:44:51 +00:00
if ( $this -> getMode () -> isNormal ()) {
$requester = HTTPSignature :: getSigner ( '' , $_SERVER );
if ( ! empty ( $requester )) {
Profile :: addVisitorCookieForHandle ( $requester );
}
}
2018-10-22 04:07:56 +00:00
// ZRL
2018-10-22 02:24:47 +00: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 15:43:27 +00:00
Core\Logger :: log ( " Invalid ZRL parameter " . $_GET [ 'zrl' ], Core\Logger :: DEBUG );
2018-10-22 19:16:59 +00:00
Core\System :: httpExit ( 403 , [ 'title' => '403 Forbidden' ]);
2018-10-22 02:24:47 +00: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-03-30 17:54:22 +00:00
Core\Update :: check ( $this -> getBasePath (), false , $this -> getMode ());
2018-10-22 22:01:17 +00:00
Core\Addon :: loadAddons ();
Core\Hook :: loadHooks ();
2018-10-22 02:24:47 +00:00
}
2018-10-22 18:44:55 +00:00
$this -> page = [
'aside' => '' ,
'bottom' => '' ,
'content' => '' ,
'footer' => '' ,
'htmlhead' => '' ,
'nav' => '' ,
'page_title' => '' ,
'right_aside' => '' ,
'template' => '' ,
'title' => ''
];
2018-10-22 02:24:47 +00:00
if ( strlen ( $this -> module )) {
// Compatibility with the Android Diaspora client
if ( $this -> module == 'stream' ) {
2018-10-22 20:15:50 +00:00
$this -> internalRedirect ( 'network?f=&order=post' );
2018-10-22 02:24:47 +00:00
}
if ( $this -> module == 'conversations' ) {
2018-10-22 20:15:50 +00:00
$this -> internalRedirect ( 'message' );
2018-10-22 02:24:47 +00:00
}
if ( $this -> module == 'commented' ) {
2018-10-22 20:15:50 +00:00
$this -> internalRedirect ( 'network?f=&order=comment' );
2018-10-22 02:24:47 +00:00
}
if ( $this -> module == 'liked' ) {
2018-10-22 20:15:50 +00:00
$this -> internalRedirect ( 'network?f=&order=comment' );
2018-10-22 02:24:47 +00:00
}
if ( $this -> module == 'activity' ) {
2018-10-22 20:15:50 +00:00
$this -> internalRedirect ( 'network/?f=&conv=1' );
2018-10-22 02:24:47 +00:00
}
if (( $this -> module == 'status_messages' ) && ( $this -> cmd == 'status_messages/new' )) {
2018-10-22 20:15:50 +00:00
$this -> internalRedirect ( 'bookmarklet' );
2018-10-22 02:24:47 +00:00
}
if (( $this -> module == 'user' ) && ( $this -> cmd == 'user/edit' )) {
2018-10-22 20:15:50 +00:00
$this -> internalRedirect ( 'settings' );
2018-10-22 02:24:47 +00:00
}
if (( $this -> module == 'tag_followings' ) && ( $this -> cmd == 'tag_followings/manage' )) {
2018-10-22 20:15:50 +00:00
$this -> internalRedirect ( 'search' );
2018-10-22 02:24:47 +00:00
}
// Compatibility with the Firefox App
if (( $this -> module == " users " ) && ( $this -> cmd == " users/sign_in " )) {
$this -> module = " login " ;
}
2019-04-06 03:16:12 +00:00
/*
* ROUTING
*
* From the request URL , routing consists of obtaining the name of a BaseModule - extending class of which the
* post () and / or content () static methods can be respectively called to produce a data change or an output .
*/
// First we try explicit routes defined in App\Router
$this -> router -> collectRoutes ();
2019-04-09 05:17:02 +00:00
$data = $this -> router -> getRouteCollector ();
Hook :: callAll ( 'route_collection' , $data );
2019-04-04 03:35:24 +00:00
2019-04-06 03:16:12 +00:00
$this -> module_class = $this -> router -> getModuleClass ( $this -> cmd );
2019-04-04 03:35:24 +00:00
2019-04-06 03:16:12 +00:00
// Then we try addon-provided modules that we wrap in the LegacyModule class
2019-04-04 03:35:24 +00:00
if ( ! $this -> module_class && Core\Addon :: isEnabled ( $this -> module ) && file_exists ( " addon/ { $this -> module } / { $this -> module } .php " )) {
2018-10-22 02:24:47 +00:00
//Check if module is an app and if public access to apps is allowed or not
2019-04-06 03:16:12 +00:00
$privateapps = $this -> config -> get ( 'config' , 'private_addons' , false );
2018-10-23 15:27:53 +00:00
if (( ! local_user ()) && Core\Hook :: isAddonApp ( $this -> module ) && $privateapps ) {
2018-10-22 02:24:47 +00: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' ;
}
}
}
2019-04-06 03:16:12 +00:00
// Then we try name-matching a Friendica\Module class
2019-04-04 03:35:24 +00:00
if ( ! $this -> module_class && class_exists ( 'Friendica\\Module\\' . ucfirst ( $this -> module ))) {
2018-10-22 02:24:47 +00:00
$this -> module_class = 'Friendica\\Module\\' . ucfirst ( $this -> module );
}
2019-04-06 03:16:12 +00:00
/* Finally , we look for a 'standard' program module in the 'mod' directory
2018-10-22 02:24:47 +00:00
* We emulate a Module class through the LegacyModule class
*/
2019-04-04 03:35:24 +00:00
if ( ! $this -> module_class && file_exists ( " mod/ { $this -> module } .php " )) {
2018-10-22 02:24:47 +00:00
LegacyModule :: setModuleFile ( " mod/ { $this -> module } .php " );
$this -> module_class = 'Friendica\\LegacyModule' ;
}
/* 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 .
*/
2019-04-04 03:35:24 +00:00
if ( ! $this -> module_class ) {
2018-10-22 02:24:47 +00:00
// 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 21:20:46 +00:00
Core\Logger :: log ( 'index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER [ 'REQUEST_URI' ]);
2018-10-22 20:15:50 +00:00
$this -> internalRedirect ( $_SERVER [ 'REQUEST_URI' ]);
2018-10-22 02:24:47 +00:00
}
2018-10-30 15:43:27 +00: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 02:24:47 +00:00
header ( $_SERVER [ " SERVER_PROTOCOL " ] . ' 404 ' . Core\L10n :: t ( 'Not Found' ));
2018-10-31 14:44:06 +00:00
$tpl = Core\Renderer :: getMarkupTemplate ( " 404.tpl " );
2018-10-31 14:35:50 +00:00
$this -> page [ 'content' ] = Core\Renderer :: replaceMacros ( $tpl , [
2018-10-22 02:24:47 +00:00
'$message' => Core\L10n :: t ( 'Page not found.' )
]);
}
}
2018-11-21 14:13:24 +00:00
$content = '' ;
2018-10-22 02:24:47 +00:00
2018-11-21 14:13:24 +00:00
// Initialize module that can set the current theme in the init() method, either directly or via App->profile_uid
2019-04-04 03:35:24 +00:00
if ( $this -> module_class ) {
2018-11-21 14:13:24 +00:00
$this -> page [ 'page_title' ] = $this -> module ;
$placeholder = '' ;
2019-01-20 03:03:49 +00:00
2018-12-26 06:06:24 +00:00
Core\Hook :: callAll ( $this -> module . '_mod_init' , $placeholder );
2019-01-20 02:52:43 +00:00
2018-11-21 14:13:24 +00:00
call_user_func ([ $this -> module_class , 'init' ]);
2019-01-20 02:52:43 +00:00
2018-11-21 14:13:24 +00: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 21:34:39 +00:00
}
2018-11-21 14:13:24 +00:00
}
2018-10-22 02:24:47 +00:00
2018-11-21 14:13:24 +00: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 02:24:47 +00:00
2018-11-21 14:13:24 +00:00
if ( function_exists ( str_replace ( '-' , '_' , $this -> getCurrentTheme ()) . '_init' )) {
$func = str_replace ( '-' , '_' , $this -> getCurrentTheme ()) . '_init' ;
$func ( $this );
}
2018-11-20 21:34:39 +00:00
2019-04-04 03:35:24 +00:00
if ( $this -> module_class ) {
2018-11-21 14:13:24 +00:00
if ( ! $this -> error && $_SERVER [ 'REQUEST_METHOD' ] === 'POST' ) {
2018-12-26 06:06:24 +00:00
Core\Hook :: callAll ( $this -> module . '_mod_post' , $_POST );
2018-11-21 14:13:24 +00:00
call_user_func ([ $this -> module_class , 'post' ]);
}
2018-11-20 21:34:39 +00:00
2018-11-21 14:13:24 +00:00
if ( ! $this -> error ) {
2018-12-26 06:06:24 +00:00
Core\Hook :: callAll ( $this -> module . '_mod_afterpost' , $placeholder );
2018-11-21 14:13:24 +00:00
call_user_func ([ $this -> module_class , 'afterpost' ]);
2018-10-22 02:24:47 +00:00
}
2018-11-21 14:13:24 +00:00
if ( ! $this -> error ) {
$arr = [ 'content' => $content ];
2018-12-26 06:06:24 +00:00
Core\Hook :: callAll ( $this -> module . '_mod_content' , $arr );
2018-11-21 14:13:24 +00:00
$content = $arr [ 'content' ];
$arr = [ 'content' => call_user_func ([ $this -> module_class , 'content' ])];
2018-12-26 06:06:24 +00:00
Core\Hook :: callAll ( $this -> module . '_mod_aftercontent' , $arr );
2018-11-21 14:13:24 +00:00
$content .= $arr [ 'content' ];
2018-10-22 02:24:47 +00:00
}
2018-11-21 14:13:24 +00:00
}
2018-10-22 02:24:47 +00:00
2018-11-21 14:13:24 +00:00
// initialise content region
if ( $this -> getMode () -> isNormal ()) {
2018-12-26 06:06:24 +00:00
Core\Hook :: callAll ( 'page_content_top' , $this -> page [ 'content' ]);
2018-12-29 05:18:52 +00:00
}
2018-11-21 14:13:24 +00:00
$this -> page [ 'content' ] .= $content ;
2018-10-22 02:24:47 +00: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.' ));
}
2019-04-13 14:32:15 +00:00
if ( ! $this -> isAjax ()) {
Core\Hook :: callAll ( 'page_end' , $this -> page [ 'content' ]);
}
2018-10-22 02:24:47 +00:00
// Add the navigation (menu) template
if ( $this -> module != 'install' && $this -> module != 'maintenance' ) {
2018-10-31 14:44:06 +00:00
$this -> page [ 'htmlhead' ] .= Core\Renderer :: replaceMacros ( Core\Renderer :: getMarkupTemplate ( 'nav_head.tpl' ), []);
2018-10-22 18:44:55 +00:00
$this -> page [ 'nav' ] = Content\Nav :: build ( $this );
2018-10-22 02:24:47 +00: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 17:51:48 +00:00
if ( $_GET [ " mode " ] == " raw " ) {
header ( " Content-type: text/html; charset=utf-8 " );
2018-10-22 02:24:47 +00:00
2019-01-07 17:51:48 +00:00
echo substr ( $target -> saveHTML (), 6 , - 8 );
2018-10-22 02:24:47 +00:00
2019-01-07 17:51:48 +00:00
exit ();
}
2018-10-22 02:24:47 +00:00
}
$page = $this -> page ;
$profile = $this -> profile ;
header ( " X-Friendica-Version: " . FRIENDICA_VERSION );
header ( " Content-type: text/html; charset=utf-8 " );
2019-04-08 21:12:34 +00:00
if ( $this -> config -> get ( 'system' , 'hsts' ) && ( $this -> baseURL -> getSSLPolicy () == BaseUrl :: SSL_POLICY_FULL )) {
2018-10-22 02:24:47 +00:00
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 12:17:55 +00:00
/* We use $_GET [ " mode " ] for special page templates . So we will check if we have
2018-10-22 02:24:47 +00: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 12:13:31 +00:00
// Used as is in view/php/default.php
$lang = Core\L10n :: getCurrentLang ();
2018-10-22 02:24:47 +00: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 16:19:55 +00:00
}
2018-10-13 18:02:04 +00:00
/**
2018-10-19 21:42:33 +00:00
* Redirects to another module relative to the current Friendica base .
2018-10-19 18:11:27 +00:00
* If you want to redirect to a external URL , use System :: externalRedirectTo ()
2018-10-13 18:02:04 +00: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 18:11:27 +00:00
*
* @ throws InternalServerErrorException In Case the given URL is not relative to the Friendica node
2018-10-13 18:02:04 +00:00
*/
2018-10-19 18:11:27 +00:00
public function internalRedirect ( $toUrl = '' , $ssl = false )
2018-10-13 18:02:04 +00:00
{
2018-11-30 11:27:17 +00:00
if ( ! empty ( parse_url ( $toUrl , PHP_URL_SCHEME ))) {
2018-10-23 10:17:41 +00:00
throw new InternalServerErrorException ( " ' $toUrl is not a relative path, please use System::externalRedirectTo " );
2018-10-13 18:02:04 +00:00
}
2018-10-19 18:11:27 +00:00
$redirectTo = $this -> getBaseURL ( $ssl ) . '/' . ltrim ( $toUrl , '/' );
2018-10-22 22:07:00 +00:00
Core\System :: externalRedirect ( $redirectTo );
2018-10-13 18:02:04 +00:00
}
2018-10-24 18:16:14 +00:00
/**
2018-10-24 18:52:38 +00:00
* Automatically redirects to relative or absolute URL
2018-10-24 18:16:14 +00: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 21:06:53 +00:00
* @ throws InternalServerErrorException
2018-10-24 18:16:14 +00:00
*/
public function redirect ( $toUrl )
{
2018-11-30 11:27:17 +00:00
if ( ! empty ( parse_url ( $toUrl , PHP_URL_SCHEME ))) {
2018-10-24 18:24:22 +00:00
Core\System :: externalRedirect ( $toUrl );
2018-10-24 18:16:14 +00:00
} else {
$this -> internalRedirect ( $toUrl );
}
}
2017-05-08 06:11:38 +00:00
}