2019-08-15 20:52:42 +02:00
< ? php
2020-02-08 17:16:42 +01:00
/**
2021-03-29 08:40:20 +02:00
* @ copyright Copyright ( C ) 2010 - 2021 , the Friendica project
2020-02-08 17:16:42 +01:00
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*
*/
2019-08-15 20:52:42 +02:00
namespace Friendica\App ;
use ArrayAccess ;
use DOMDocument ;
use DOMXPath ;
use Friendica\App ;
use Friendica\Content\Nav ;
2021-10-26 21:44:29 +02:00
use Friendica\Core\Config\Capability\IManageConfigValues ;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues ;
2019-08-15 20:52:42 +02:00
use Friendica\Core\Hook ;
2020-01-18 20:59:39 +01:00
use Friendica\Core\L10n ;
2019-08-15 20:52:42 +02:00
use Friendica\Core\Renderer ;
use Friendica\Core\Theme ;
use Friendica\Module\Special\HTTPException as ModuleHTTPException ;
use Friendica\Network\HTTPException ;
2020-02-01 01:41:20 +01:00
use Friendica\Util\Network ;
2019-11-28 18:27:16 +01:00
use Friendica\Util\Strings ;
2020-12-09 23:10:27 +01:00
use Friendica\Util\Profiler ;
2019-08-15 20:52:42 +02:00
/**
* Contains the page specific environment variables for the current Page
* - Contains all stylesheets
* - Contains all footer - scripts
* - Contains all page specific content ( header , footer , content , ... )
*
* The run () method is the single point where the page will get printed to the screen
*/
class Page implements ArrayAccess
{
/**
* @ var array Contains all stylesheets , which should get loaded during page
*/
2019-11-29 17:33:48 +01:00
private $stylesheets = [];
2019-08-15 20:52:42 +02:00
/**
* @ var array Contains all scripts , which are added to the footer at last
*/
2019-11-29 17:33:48 +01:00
private $footerScripts = [];
2019-08-15 20:52:42 +02:00
/**
* @ var array The page content , which are showed directly
*/
2019-11-29 17:33:48 +01:00
private $page = [
'aside' => '' ,
'bottom' => '' ,
'content' => '' ,
'footer' => '' ,
'htmlhead' => '' ,
'nav' => '' ,
'page_title' => '' ,
'right_aside' => '' ,
'template' => '' ,
'title' => '' ,
];
2019-08-15 20:52:42 +02:00
/**
* @ var string The basepath of the page
*/
private $basePath ;
/**
* @ param string $basepath The Page basepath
*/
public function __construct ( string $basepath )
{
$this -> basePath = $basepath ;
}
/**
* Whether a offset exists
*
* @ link https :// php . net / manual / en / arrayaccess . offsetexists . php
*
* @ param mixed $offset < p >
* An offset to check for .
* </ p >
*
* @ return boolean true on success or false on failure .
* </ p >
* < p >
* The return value will be casted to boolean if non - boolean was returned .
* @ since 5.0 . 0
*/
public function offsetExists ( $offset )
{
return isset ( $this -> page [ $offset ]);
}
/**
* Offset to retrieve
*
* @ link https :// php . net / manual / en / arrayaccess . offsetget . php
*
* @ param mixed $offset < p >
* The offset to retrieve .
* </ p >
*
* @ return mixed Can return all value types .
* @ since 5.0 . 0
*/
public function offsetGet ( $offset )
{
return $this -> page [ $offset ] ? ? null ;
}
/**
* Offset to set
*
* @ link https :// php . net / manual / en / arrayaccess . offsetset . php
*
* @ param mixed $offset < p >
* The offset to assign the value to .
* </ p >
* @ param mixed $value < p >
* The value to set .
* </ p >
*
* @ return void
* @ since 5.0 . 0
*/
public function offsetSet ( $offset , $value )
{
$this -> page [ $offset ] = $value ;
}
/**
* Offset to unset
*
* @ link https :// php . net / manual / en / arrayaccess . offsetunset . php
*
* @ param mixed $offset < p >
* The offset to unset .
* </ p >
*
* @ return void
* @ since 5.0 . 0
*/
public function offsetUnset ( $offset )
{
if ( isset ( $this -> page [ $offset ])) {
unset ( $this -> page [ $offset ]);
}
}
/**
* 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 .
*
* @ param string $path
2020-08-16 00:56:17 +02:00
* @ param string $media
2019-08-15 20:52:42 +02:00
* @ see Page :: initHead ()
*/
2020-08-16 00:56:17 +02:00
public function registerStylesheet ( $path , string $media = 'screen' )
2019-08-15 20:52:42 +02:00
{
2020-02-01 01:41:20 +01:00
$path = Network :: appendQueryParam ( $path , [ 'v' => FRIENDICA_VERSION ]);
2019-08-15 20:52:42 +02:00
if ( mb_strpos ( $path , $this -> basePath . DIRECTORY_SEPARATOR ) === 0 ) {
$path = mb_substr ( $path , mb_strlen ( $this -> basePath . DIRECTORY_SEPARATOR ));
}
2020-08-16 00:56:17 +02:00
$this -> stylesheets [ trim ( $path , '/' )] = $media ;
2019-08-15 20:52:42 +02:00
}
/**
* Initializes Page -> page [ 'htmlhead' ] .
*
* Includes :
* - Page title
* - Favicons
* - Registered stylesheets ( through App -> registerStylesheet ())
* - Infinite scroll data
* - head . tpl template
*
2021-10-26 22:09:11 +02:00
* @ param App $app The Friendica App instance
* @ param Module $module The loaded Friendica module
* @ param L10n $l10n The l10n language instance
* @ param IManageConfigValues $config The Friendica configuration
* @ param IManagePersonalConfigValues $pConfig The Friendica personal configuration ( for user )
2019-08-15 20:52:42 +02:00
*
* @ throws HTTPException\InternalServerErrorException
*/
2021-10-26 21:44:29 +02:00
private function initHead ( App $app , Module $module , L10n $l10n , IManageConfigValues $config , IManagePersonalConfigValues $pConfig )
2019-08-15 20:52:42 +02:00
{
$interval = (( local_user ()) ? $pConfig -> get ( local_user (), 'system' , 'update_interval' ) : 40000 );
// If the update is 'deactivated' set it to the highest integer number (~24 days)
if ( $interval < 0 ) {
$interval = 2147483647 ;
}
if ( $interval < 10000 ) {
$interval = 40000 ;
}
// Default title: current module called
if ( empty ( $this -> page [ 'title' ]) && $module -> getName ()) {
$this -> page [ 'title' ] = ucfirst ( $module -> getName ());
}
// Prepend the sitename to the page title
$this -> page [ 'title' ] = $config -> get ( 'config' , 'sitename' , '' ) . ( ! empty ( $this -> page [ 'title' ]) ? ' | ' . $this -> page [ 'title' ] : '' );
if ( ! empty ( Renderer :: $theme [ 'stylesheet' ])) {
$stylesheet = Renderer :: $theme [ 'stylesheet' ];
} else {
$stylesheet = $app -> getCurrentThemeStylesheetPath ();
}
$this -> registerStylesheet ( $stylesheet );
$shortcut_icon = $config -> get ( 'system' , 'shortcut_icon' );
if ( $shortcut_icon == '' ) {
$shortcut_icon = 'images/friendica-32.png' ;
}
$touch_icon = $config -> get ( 'system' , 'touch_icon' );
if ( $touch_icon == '' ) {
2020-10-03 20:25:10 +02:00
$touch_icon = 'images/friendica-192.png' ;
2019-08-15 20:52:42 +02:00
}
Hook :: callAll ( 'head' , $this -> page [ 'htmlhead' ]);
$tpl = Renderer :: getMarkupTemplate ( 'head.tpl' );
/* put the head template at the beginning of page [ 'htmlhead' ]
* since the code added by the modules frequently depends on it
* being first
*/
$this -> page [ 'htmlhead' ] = Renderer :: replaceMacros ( $tpl , [
2019-11-28 18:27:16 +01:00
'$local_user' => local_user (),
'$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION ,
'$delitem' => $l10n -> t ( 'Delete this item?' ),
2021-01-21 00:44:02 +01:00
'$blockAuthor' => $l10n -> t ( 'Block this author? They won\'t be able to follow you nor see your public posts, and you won\'t be able to see their posts and their notifications.' ),
2019-11-28 18:27:16 +01:00
'$update_interval' => $interval ,
'$shortcut_icon' => $shortcut_icon ,
'$touch_icon' => $touch_icon ,
'$block_public' => intval ( $config -> get ( 'system' , 'block_public' )),
2020-08-16 00:56:17 +02:00
'$stylesheets' => $this -> stylesheets ,
2019-11-28 18:27:16 +01:00
]) . $this -> page [ 'htmlhead' ];
2019-08-15 20:52:42 +02:00
}
2021-11-04 21:29:59 +01:00
/**
* Returns the complete URL of the current page , e . g .: http ( s ) :// something . com / network
*
* Taken from http :// webcheatsheet . com / php / get_current_page_url . php
*/
private function curPageURL ()
{
$pageURL = 'http' ;
if ( ! empty ( $_SERVER [ " HTTPS " ]) && ( $_SERVER [ " HTTPS " ] == " on " )) {
$pageURL .= " s " ;
}
$pageURL .= " :// " ;
if ( $_SERVER [ " SERVER_PORT " ] != " 80 " && $_SERVER [ " SERVER_PORT " ] != " 443 " ) {
$pageURL .= $_SERVER [ " SERVER_NAME " ] . " : " . $_SERVER [ " SERVER_PORT " ] . $_SERVER [ " REQUEST_URI " ];
} else {
$pageURL .= $_SERVER [ " SERVER_NAME " ] . $_SERVER [ " REQUEST_URI " ];
}
return $pageURL ;
}
2019-08-15 20:52:42 +02:00
/**
* Initializes Page -> page [ 'footer' ] .
*
* Includes :
* - Javascript homebase
* - Mobile toggle link
* - Registered footer scripts ( through App -> registerFooterScript ())
* - footer . tpl template
*
* @ param App $app The Friendica App instance
2019-08-16 09:46:38 +02:00
* @ param Mode $mode The Friendica runtime mode
2019-08-15 20:52:42 +02:00
* @ param L10n $l10n The l10n instance
*
* @ throws HTTPException\InternalServerErrorException
*/
2019-08-16 09:46:38 +02:00
private function initFooter ( App $app , Mode $mode , L10n $l10n )
2019-08-15 20:52:42 +02:00
{
// If you're just visiting, let javascript take you home
if ( ! empty ( $_SESSION [ 'visitor_home' ])) {
$homebase = $_SESSION [ 'visitor_home' ];
2021-08-09 21:48:39 +02:00
} elseif ( ! empty ( $app -> getLoggedInUserNickname ())) {
$homebase = 'profile/' . $app -> getLoggedInUserNickname ();
2019-08-15 20:52:42 +02:00
}
if ( isset ( $homebase )) {
$this -> page [ 'footer' ] .= '<script>var homebase="' . $homebase . '";</script>' . " \n " ;
}
/*
* Add a " toggle mobile " link if we ' re using a mobile device
*/
2019-08-16 09:46:38 +02:00
if ( $mode -> isMobile () || $mode -> isTablet ()) {
2019-08-15 20:52:42 +02:00
if ( isset ( $_SESSION [ 'show-mobile' ]) && ! $_SESSION [ 'show-mobile' ]) {
2021-11-04 21:29:59 +01:00
$link = 'toggle_mobile?address=' . urlencode ( $this -> curPageURL ());
2019-08-15 20:52:42 +02:00
} else {
2021-11-04 21:29:59 +01:00
$link = 'toggle_mobile?off=1&address=' . urlencode ( $this -> curPageURL ());
2019-08-15 20:52:42 +02:00
}
$this -> page [ 'footer' ] .= Renderer :: replaceMacros ( Renderer :: getMarkupTemplate ( " toggle_mobile_footer.tpl " ), [
'$toggle_link' => $link ,
'$toggle_text' => $l10n -> t ( 'toggle mobile' )
]);
}
Hook :: callAll ( 'footer' , $this -> page [ 'footer' ]);
$tpl = Renderer :: getMarkupTemplate ( 'footer.tpl' );
$this -> page [ 'footer' ] = Renderer :: replaceMacros ( $tpl , [
2019-11-28 18:27:16 +01:00
'$footerScripts' => array_unique ( $this -> footerScripts ),
]) . $this -> page [ 'footer' ];
2019-08-15 20:52:42 +02:00
}
/**
* Initializes Page -> page [ 'content' ] .
*
* Includes :
* - module content
* - hooks for content
*
* @ param Module $module The module
* @ param Mode $mode The Friendica execution mode
*
* @ throws HTTPException\InternalServerErrorException
*/
private function initContent ( Module $module , Mode $mode )
{
$content = '' ;
try {
2021-11-14 20:28:36 +01:00
$moduleClass = $module -> getClass ();
2019-08-15 20:52:42 +02:00
$arr = [ 'content' => $content ];
2021-11-16 21:12:12 +01:00
Hook :: callAll ( $moduleClass -> getClassName () . '_mod_content' , $arr );
2019-08-15 20:52:42 +02:00
$content = $arr [ 'content' ];
2021-11-14 23:13:47 +01:00
$arr = [ 'content' => $moduleClass -> content ()];
Hook :: callAll ( $moduleClass -> getClassName () . '_mod_aftercontent' , $arr );
2019-08-15 20:52:42 +02:00
$content .= $arr [ 'content' ];
} catch ( HTTPException $e ) {
2021-11-14 23:13:47 +01:00
$content = ( new ModuleHTTPException ()) -> content ( $e );
2019-08-15 20:52:42 +02:00
}
// initialise content region
if ( $mode -> isNormal ()) {
Hook :: callAll ( 'page_content_top' , $this -> page [ 'content' ]);
}
$this -> page [ 'content' ] .= $content ;
}
/**
* 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 .
*
* @ param string $path
*
* @ see Page :: initFooter ()
*
*/
public function registerFooterScript ( $path )
{
2020-02-01 01:41:20 +01:00
$path = Network :: appendQueryParam ( $path , [ 'v' => FRIENDICA_VERSION ]);
2019-08-15 20:52:42 +02:00
$url = str_replace ( $this -> basePath . DIRECTORY_SEPARATOR , '' , $path );
$this -> footerScripts [] = trim ( $url , '/' );
}
/**
* Executes the creation of the current page and prints it to the screen
*
2021-10-26 21:44:29 +02:00
* @ param App $app The Friendica App
* @ param BaseURL $baseURL The Friendica Base URL
* @ param Mode $mode The current node mode
* @ param Module $module The loaded Friendica module
* @ param L10n $l10n The l10n language class
* @ param IManageConfigValues $config The Configuration of this node
* @ param IManagePersonalConfigValues $pconfig The personal / user configuration
2019-08-15 20:52:42 +02:00
*
* @ throws HTTPException\InternalServerErrorException
*/
2021-10-26 21:44:29 +02:00
public function run ( App $app , BaseURL $baseURL , Mode $mode , Module $module , L10n $l10n , Profiler $profiler , IManageConfigValues $config , IManagePersonalConfigValues $pconfig )
2019-08-15 20:52:42 +02:00
{
$moduleName = $module -> getName ();
/* Create the page content .
* Calls all hooks which are including content operations
*
* Sets the $Page -> page [ 'content' ] variable
*/
2020-12-09 23:10:27 +01:00
$timestamp = microtime ( true );
2019-08-15 20:52:42 +02:00
$this -> initContent ( $module , $mode );
2020-12-09 23:10:27 +01:00
$profiler -> set ( microtime ( true ) - $timestamp , 'content' );
2019-08-15 20:52:42 +02:00
2019-10-06 17:18:51 +02:00
// Load current theme info after module has been initialized as theme could have been set in module
$currentTheme = $app -> getCurrentTheme ();
$theme_info_file = 'view/theme/' . $currentTheme . '/theme.php' ;
if ( file_exists ( $theme_info_file )) {
require_once $theme_info_file ;
}
if ( function_exists ( str_replace ( '-' , '_' , $currentTheme ) . '_init' )) {
$func = str_replace ( '-' , '_' , $currentTheme ) . '_init' ;
$func ( $app );
}
2019-08-15 20:52:42 +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 ( $app , $module , $l10n , $config , $pconfig );
/* Build the page ending -- this is stuff that goes right before
* the closing </ body > tag
*/
2019-08-16 09:46:38 +02:00
$this -> initFooter ( $app , $mode , $l10n );
2019-08-15 20:52:42 +02:00
2019-08-16 09:46:38 +02:00
if ( ! $mode -> isAjax ()) {
2019-08-15 20:52:42 +02:00
Hook :: callAll ( 'page_end' , $this -> page [ 'content' ]);
}
// Add the navigation (menu) template
if ( $moduleName != 'install' && $moduleName != 'maintenance' ) {
$this -> page [ 'htmlhead' ] .= Renderer :: replaceMacros ( Renderer :: getMarkupTemplate ( 'nav_head.tpl' ), []);
$this -> page [ 'nav' ] = Nav :: build ( $app );
}
// 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 );
}
if ( $_GET [ " mode " ] == " raw " ) {
header ( " Content-type: text/html; charset=utf-8 " );
echo substr ( $target -> saveHTML (), 6 , - 8 );
exit ();
}
}
$page = $this -> page ;
header ( " X-Friendica-Version: " . FRIENDICA_VERSION );
header ( " Content-type: text/html; charset=utf-8 " );
if ( $config -> get ( 'system' , 'hsts' ) && ( $baseURL -> getSSLPolicy () == BaseURL :: 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'");
/* We use $_GET [ " mode " ] for special page templates . So we will check if we have
* to load another page template than the default one .
* The page templates are located in / view / php / or in the theme directory .
*/
2019-11-28 18:27:16 +01:00
if ( isset ( $_GET [ 'mode' ])) {
$template = Theme :: getPathForFile ( 'php/' . Strings :: sanitizeFilePathItem ( $_GET [ 'mode' ]) . '.php' );
2019-08-15 20:52:42 +02:00
}
// If there is no page template use the default page template
if ( empty ( $template )) {
2019-11-28 18:27:16 +01:00
$template = Theme :: getPathForFile ( 'php/default.php' );
2019-08-15 20:52:42 +02:00
}
// Theme templates expect $a as an App instance
2019-08-15 20:58:57 +02:00
$a = $app ;
2019-08-15 20:52:42 +02:00
// Used as is in view/php/default.php
$lang = $l10n -> getCurrentLang ();
require_once $template ;
}
}