2019-08-15 14:52:42 -04:00
< ? php
2020-02-08 11:16:42 -05:00
/**
2022-01-02 02:27:47 -05:00
* @ copyright Copyright ( C ) 2010 - 2022 , the Friendica project
2020-02-08 11:16:42 -05: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 14:52:42 -04:00
namespace Friendica\App ;
use ArrayAccess ;
use DOMDocument ;
use DOMXPath ;
use Friendica\App ;
use Friendica\Content\Nav ;
2021-10-26 15:44:29 -04:00
use Friendica\Core\Config\Capability\IManageConfigValues ;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues ;
2019-08-15 14:52:42 -04:00
use Friendica\Core\Hook ;
2020-01-18 14:59:39 -05:00
use Friendica\Core\L10n ;
2019-08-15 14:52:42 -04:00
use Friendica\Core\Renderer ;
use Friendica\Core\Theme ;
use Friendica\Network\HTTPException ;
2020-01-31 19:41:20 -05:00
use Friendica\Util\Network ;
2019-11-28 12:27:16 -05:00
use Friendica\Util\Strings ;
2020-12-09 17:10:27 -05:00
use Friendica\Util\Profiler ;
2021-11-21 17:37:17 -05:00
use Psr\Http\Message\ResponseInterface ;
2019-08-15 14:52:42 -04: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 11:33:48 -05:00
private $stylesheets = [];
2019-08-15 14:52:42 -04:00
/**
* @ var array Contains all scripts , which are added to the footer at last
*/
2019-11-29 11:33:48 -05:00
private $footerScripts = [];
2019-08-15 14:52:42 -04:00
/**
* @ var array The page content , which are showed directly
*/
2019-11-29 11:33:48 -05:00
private $page = [
'aside' => '' ,
'bottom' => '' ,
'content' => '' ,
'footer' => '' ,
'htmlhead' => '' ,
'nav' => '' ,
'page_title' => '' ,
'right_aside' => '' ,
'template' => '' ,
'title' => '' ,
];
2019-08-15 14:52:42 -04: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-15 18:56:17 -04:00
* @ param string $media
2019-08-15 14:52:42 -04:00
* @ see Page :: initHead ()
*/
2020-08-15 18:56:17 -04:00
public function registerStylesheet ( $path , string $media = 'screen' )
2019-08-15 14:52:42 -04:00
{
2020-01-31 19:41:20 -05:00
$path = Network :: appendQueryParam ( $path , [ 'v' => FRIENDICA_VERSION ]);
2019-08-15 14:52:42 -04:00
if ( mb_strpos ( $path , $this -> basePath . DIRECTORY_SEPARATOR ) === 0 ) {
$path = mb_substr ( $path , mb_strlen ( $this -> basePath . DIRECTORY_SEPARATOR ));
}
2020-08-15 18:56:17 -04:00
$this -> stylesheets [ trim ( $path , '/' )] = $media ;
2019-08-15 14:52:42 -04:00
}
/**
* Initializes Page -> page [ 'htmlhead' ] .
*
* Includes :
* - Page title
* - Favicons
* - Registered stylesheets ( through App -> registerStylesheet ())
* - Infinite scroll data
* - head . tpl template
*
2021-10-26 16:09:11 -04:00
* @ param App $app The Friendica App instance
2021-11-19 16:47:49 -05:00
* @ param Arguments $args The Friendica App Arguments
2021-10-26 16:09:11 -04:00
* @ 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 14:52:42 -04:00
*
* @ throws HTTPException\InternalServerErrorException
*/
2021-11-19 16:47:49 -05:00
private function initHead ( App $app , Arguments $args , L10n $l10n , IManageConfigValues $config , IManagePersonalConfigValues $pConfig )
2019-08-15 14:52:42 -04: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
2021-11-19 16:47:49 -05:00
if ( empty ( $this -> page [ 'title' ]) && $args -> getModuleName ()) {
$this -> page [ 'title' ] = ucfirst ( $args -> getModuleName ());
2019-08-15 14:52:42 -04:00
}
// 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 14:25:10 -04:00
$touch_icon = 'images/friendica-192.png' ;
2019-08-15 14:52:42 -04: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 12:27:16 -05:00
'$local_user' => local_user (),
'$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION ,
'$delitem' => $l10n -> t ( 'Delete this item?' ),
2021-01-20 18:44:02 -05: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 12:27:16 -05:00
'$update_interval' => $interval ,
'$shortcut_icon' => $shortcut_icon ,
'$touch_icon' => $touch_icon ,
'$block_public' => intval ( $config -> get ( 'system' , 'block_public' )),
2020-08-15 18:56:17 -04:00
'$stylesheets' => $this -> stylesheets ,
2019-11-28 12:27:16 -05:00
]) . $this -> page [ 'htmlhead' ];
2019-08-15 14:52:42 -04:00
}
2021-11-04 16:29:59 -04: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 " ;
}
2021-11-20 09:38:03 -05:00
2021-11-04 16:29:59 -04:00
$pageURL .= " :// " ;
2021-11-20 09:38:03 -05:00
2021-11-04 16:29:59 -04:00
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 14:52:42 -04: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 03:46:38 -04:00
* @ param Mode $mode The Friendica runtime mode
2019-08-15 14:52:42 -04:00
* @ param L10n $l10n The l10n instance
*
* @ throws HTTPException\InternalServerErrorException
*/
2019-08-16 03:46:38 -04:00
private function initFooter ( App $app , Mode $mode , L10n $l10n )
2019-08-15 14:52:42 -04:00
{
// If you're just visiting, let javascript take you home
if ( ! empty ( $_SESSION [ 'visitor_home' ])) {
$homebase = $_SESSION [ 'visitor_home' ];
2021-08-09 15:48:39 -04:00
} elseif ( ! empty ( $app -> getLoggedInUserNickname ())) {
$homebase = 'profile/' . $app -> getLoggedInUserNickname ();
2019-08-15 14:52:42 -04: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 03:46:38 -04:00
if ( $mode -> isMobile () || $mode -> isTablet ()) {
2019-08-15 14:52:42 -04:00
if ( isset ( $_SESSION [ 'show-mobile' ]) && ! $_SESSION [ 'show-mobile' ]) {
2021-11-04 16:29:59 -04:00
$link = 'toggle_mobile?address=' . urlencode ( $this -> curPageURL ());
2019-08-15 14:52:42 -04:00
} else {
2021-11-04 16:29:59 -04:00
$link = 'toggle_mobile?off=1&address=' . urlencode ( $this -> curPageURL ());
2019-08-15 14:52:42 -04: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 12:27:16 -05:00
'$footerScripts' => array_unique ( $this -> footerScripts ),
]) . $this -> page [ 'footer' ];
2019-08-15 14:52:42 -04:00
}
/**
* Initializes Page -> page [ 'content' ] .
*
* Includes :
* - module content
* - hooks for content
*
2021-11-21 17:37:17 -05:00
* @ param ResponseInterface $response The Module response class
2021-11-21 14:06:36 -05:00
* @ param Mode $mode The Friendica execution mode
2019-08-15 14:52:42 -04:00
*
* @ throws HTTPException\InternalServerErrorException
*/
2021-11-21 17:37:17 -05:00
private function initContent ( ResponseInterface $response , Mode $mode )
2019-08-15 14:52:42 -04:00
{
// initialise content region
if ( $mode -> isNormal ()) {
Hook :: callAll ( 'page_content_top' , $this -> page [ 'content' ]);
}
2021-11-21 17:37:17 -05:00
$this -> page [ 'content' ] .= ( string ) $response -> getBody ();
2019-08-15 14:52:42 -04: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 .
*
* @ param string $path
*
* @ see Page :: initFooter ()
*
*/
public function registerFooterScript ( $path )
{
2020-01-31 19:41:20 -05:00
$path = Network :: appendQueryParam ( $path , [ 'v' => FRIENDICA_VERSION ]);
2019-08-15 14:52:42 -04:00
$url = str_replace ( $this -> basePath . DIRECTORY_SEPARATOR , '' , $path );
$this -> footerScripts [] = trim ( $url , '/' );
}
2021-11-21 15:52:36 -05:00
/**
* Directly exit with the current response ( include setting all headers )
*
2021-11-21 17:37:17 -05:00
* @ param ResponseInterface $response
2021-11-21 15:52:36 -05:00
*/
2021-11-21 17:37:17 -05:00
public function exit ( ResponseInterface $response )
2021-11-21 15:52:36 -05:00
{
2022-01-02 15:28:28 -05:00
header ( sprintf ( " HTTP/%s %i %s " ,
$response -> getProtocolVersion (),
$response -> getStatusCode (),
$response -> getReasonPhrase ())
);
2021-11-21 15:52:36 -05:00
foreach ( $response -> getHeaders () as $key => $header ) {
2021-11-21 17:37:17 -05:00
if ( is_array ( $header )) {
$header_str = implode ( ',' , $header );
2021-11-21 18:13:02 -05:00
} else {
$header_str = $header ;
2021-11-21 17:37:17 -05:00
}
2021-11-21 18:13:02 -05:00
2021-11-21 15:52:36 -05:00
if ( empty ( $key )) {
2021-11-21 17:37:17 -05:00
header ( $header_str );
2021-11-21 15:52:36 -05:00
} else {
2021-11-21 17:37:17 -05:00
header ( " $key : $header_str " );
2021-11-21 15:52:36 -05:00
}
}
2021-11-21 17:37:17 -05:00
echo $response -> getBody ();
2021-11-21 15:52:36 -05:00
}
2019-08-15 14:52:42 -04:00
/**
* Executes the creation of the current page and prints it to the screen
*
2021-11-21 14:06:36 -05:00
* @ param App $app The Friendica App
* @ param BaseURL $baseURL The Friendica Base URL
* @ param Arguments $args The Friendica App arguments
* @ param Mode $mode The current node mode
2021-11-21 17:37:17 -05:00
* @ param ResponseInterface $response The Response of the module class , including type , content & headers
2021-11-21 14:06:36 -05:00
* @ 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 14:52:42 -04:00
*
2021-11-20 09:38:03 -05:00
* @ throws HTTPException\InternalServerErrorException | HTTPException\ServiceUnavailableException
2019-08-15 14:52:42 -04:00
*/
2021-11-21 17:37:17 -05:00
public function run ( App $app , BaseURL $baseURL , Arguments $args , Mode $mode , ResponseInterface $response , L10n $l10n , Profiler $profiler , IManageConfigValues $config , IManagePersonalConfigValues $pconfig )
2019-08-15 14:52:42 -04:00
{
2021-11-19 16:47:49 -05:00
$moduleName = $args -> getModuleName ();
2019-08-15 14:52:42 -04:00
/* Create the page content .
* Calls all hooks which are including content operations
*
* Sets the $Page -> page [ 'content' ] variable
*/
2020-12-09 17:10:27 -05:00
$timestamp = microtime ( true );
2021-11-21 14:06:36 -05:00
$this -> initContent ( $response , $mode );
2019-08-15 14:52:42 -04:00
2019-10-06 11:18:51 -04: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 14:52:42 -04: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 .
*/
2021-11-19 16:47:49 -05:00
$this -> initHead ( $app , $args , $l10n , $config , $pconfig );
2019-08-15 14:52:42 -04:00
/* Build the page ending -- this is stuff that goes right before
* the closing </ body > tag
*/
2019-08-16 03:46:38 -04:00
$this -> initFooter ( $app , $mode , $l10n );
2019-08-15 14:52:42 -04:00
2021-12-09 08:04:51 -05:00
$profiler -> set ( microtime ( true ) - $timestamp , 'aftermath' );
2019-08-16 03:46:38 -04:00
if ( ! $mode -> isAjax ()) {
2019-08-15 14:52:42 -04: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 );
}
2021-11-21 15:52:36 -05:00
foreach ( $response -> getHeaders () as $key => $header ) {
2021-11-21 18:13:02 -05:00
if ( is_array ( $header )) {
$header_str = implode ( ',' , $header );
} else {
$header_str = $header ;
}
2021-11-21 15:52:36 -05:00
if ( empty ( $key )) {
2021-11-21 18:13:02 -05:00
header ( $header_str );
2021-11-21 14:06:36 -05:00
} else {
2021-11-21 18:13:02 -05:00
header ( " $key : $header_str " );
2021-11-21 14:06:36 -05:00
}
}
2019-08-15 14:52:42 -04: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 );
}
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 12:27:16 -05:00
if ( isset ( $_GET [ 'mode' ])) {
$template = Theme :: getPathForFile ( 'php/' . Strings :: sanitizeFilePathItem ( $_GET [ 'mode' ]) . '.php' );
2019-08-15 14:52:42 -04:00
}
// If there is no page template use the default page template
if ( empty ( $template )) {
2019-11-28 12:27:16 -05:00
$template = Theme :: getPathForFile ( 'php/default.php' );
2019-08-15 14:52:42 -04:00
}
// Theme templates expect $a as an App instance
2019-08-15 14:58:57 -04:00
$a = $app ;
2019-08-15 14:52:42 -04:00
// Used as is in view/php/default.php
$lang = $l10n -> getCurrentLang ();
require_once $template ;
}
}