2019-08-12 18:13:58 +02:00
< ? php
2020-02-08 17:16:42 +01:00
/**
* @ copyright Copyright ( C ) 2020 , Friendica
*
* @ 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-12 18:13:58 +02:00
namespace Friendica\App ;
use Friendica\App ;
2019-12-15 23:28:01 +01:00
use Friendica\BaseModule ;
2019-08-12 18:13:58 +02:00
use Friendica\Core ;
use Friendica\LegacyModule ;
use Friendica\Module\Home ;
2019-10-11 17:55:02 +02:00
use Friendica\Module\HTTPException\MethodNotAllowed ;
use Friendica\Module\HTTPException\PageNotFound ;
use Friendica\Network\HTTPException\MethodNotAllowedException ;
use Friendica\Network\HTTPException\NotFoundException ;
2019-08-12 18:13:58 +02:00
use Psr\Log\LoggerInterface ;
/**
* Holds the common context of the current , loaded module
*/
class Module
{
2019-08-12 18:20:22 +02:00
const DEFAULT = 'home' ;
2019-08-12 18:13:58 +02:00
const DEFAULT_CLASS = Home :: class ;
/**
* A list of modules , which are backend methods
*
* @ var array
*/
const BACKEND_MODULES = [
'_well_known' ,
'api' ,
'dfrn_notify' ,
'feed' ,
'fetch' ,
'followers' ,
'following' ,
'hcard' ,
'hostxrd' ,
'inbox' ,
'manifest' ,
'nodeinfo' ,
'noscrape' ,
'objects' ,
'outbox' ,
'poco' ,
'post' ,
'proxy' ,
'pubsub' ,
'pubsubhubbub' ,
'receive' ,
'rsd_xml' ,
'salmon' ,
'statistics_json' ,
'xrd' ,
];
/**
* @ var string The module name
*/
private $module ;
/**
2019-12-15 23:28:01 +01:00
* @ var BaseModule The module class
2019-08-12 18:13:58 +02:00
*/
private $module_class ;
2019-11-05 06:03:05 +01:00
/**
* @ var array The module parameters
*/
private $module_parameters ;
2019-08-12 18:13:58 +02:00
/**
* @ var bool true , if the module is a backend module
*/
private $isBackend ;
/**
* @ var bool true , if the loaded addon is private , so we have to print out not allowed
*/
private $printNotAllowedAddon ;
/**
* @ return string
*/
public function getName ()
{
return $this -> module ;
}
/**
* @ return string The base class name
*/
public function getClassName ()
{
return $this -> module_class ;
}
2019-11-07 04:34:38 +01:00
/**
* @ return array The module parameters extracted from the route
*/
public function getParameters ()
{
return $this -> module_parameters ;
}
2019-08-12 18:13:58 +02:00
/**
2019-08-12 18:20:22 +02:00
* @ return bool True , if the current module is a backend module
* @ see Module :: BACKEND_MODULES for a list
2019-08-12 18:13:58 +02:00
*/
public function isBackend ()
{
return $this -> isBackend ;
}
2019-11-05 06:03:05 +01:00
public function __construct ( string $module = self :: DEFAULT , string $moduleClass = self :: DEFAULT_CLASS , array $moduleParameters = [], bool $isBackend = false , bool $printNotAllowedAddon = false )
2019-08-12 18:13:58 +02:00
{
2019-08-12 18:20:22 +02:00
$this -> module = $module ;
$this -> module_class = $moduleClass ;
2019-11-05 06:03:05 +01:00
$this -> module_parameters = $moduleParameters ;
2019-08-12 18:20:22 +02:00
$this -> isBackend = $isBackend ;
2019-08-12 18:13:58 +02:00
$this -> printNotAllowedAddon = $printNotAllowedAddon ;
}
/**
* Determines the current module based on the App arguments and the server variable
*
* @ param Arguments $args The Friendica arguments
*
* @ return Module The module with the determined module
*/
2019-08-12 21:51:51 +02:00
public function determineModule ( Arguments $args )
2019-08-12 18:13:58 +02:00
{
if ( $args -> getArgc () > 0 ) {
$module = str_replace ( '.' , '_' , $args -> get ( 0 ));
$module = str_replace ( '-' , '_' , $module );
} else {
$module = self :: DEFAULT ;
}
// Compatibility with the Firefox App
if (( $module == " users " ) && ( $args -> getCommand () == " users/sign_in " )) {
$module = " login " ;
}
2019-08-12 18:20:22 +02:00
$isBackend = in_array ( $module , Module :: BACKEND_MODULES );;
2019-08-12 18:13:58 +02:00
2019-11-05 06:03:05 +01:00
return new Module ( $module , $this -> module_class , [], $isBackend , $this -> printNotAllowedAddon );
2019-08-12 18:13:58 +02:00
}
/**
* Determine the class of the current module
*
2020-01-19 21:29:36 +01:00
* @ param Arguments $args The Friendica execution arguments
* @ param Router $router The Friendica routing instance
* @ param Core\Config\IConfig $config The Friendica Configuration
2019-08-12 18:13:58 +02:00
*
* @ return Module The determined module of this call
*
2019-10-06 17:18:51 +02:00
* @ throws \Exception
2019-08-12 18:13:58 +02:00
*/
2020-01-19 21:29:36 +01:00
public function determineClass ( Arguments $args , Router $router , Core\Config\IConfig $config )
2019-08-12 18:13:58 +02:00
{
$printNotAllowedAddon = false ;
2019-10-11 17:55:02 +02:00
$module_class = null ;
2019-11-05 06:03:05 +01:00
$module_parameters = [];
2019-08-12 18:13:58 +02: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 .
**/
2019-10-11 17:55:02 +02:00
try {
$module_class = $router -> getModuleClass ( $args -> getCommand ());
2019-11-05 06:03:05 +01:00
$module_parameters = $router -> getModuleParameters ();
2019-10-11 17:55:02 +02:00
} catch ( MethodNotAllowedException $e ) {
$module_class = MethodNotAllowed :: class ;
} catch ( NotFoundException $e ) {
// Then we try addon-provided modules that we wrap in the LegacyModule class
if ( Core\Addon :: isEnabled ( $this -> module ) && file_exists ( " addon/ { $this -> module } / { $this -> module } .php " )) {
//Check if module is an app and if public access to apps is allowed or not
$privateapps = $config -> get ( 'config' , 'private_addons' , false );
if (( ! local_user ()) && Core\Hook :: isAddonApp ( $this -> module ) && $privateapps ) {
$printNotAllowedAddon = true ;
} else {
include_once " addon/ { $this -> module } / { $this -> module } .php " ;
if ( function_exists ( $this -> module . '_module' )) {
LegacyModule :: setModuleFile ( " addon/ { $this -> module } / { $this -> module } .php " );
$module_class = LegacyModule :: class ;
}
2019-08-12 18:13:58 +02:00
}
}
2019-10-11 17:55:02 +02:00
/* Finally , we look for a 'standard' program module in the 'mod' directory
* We emulate a Module class through the LegacyModule class
*/
if ( ! $module_class && file_exists ( " mod/ { $this -> module } .php " )) {
LegacyModule :: setModuleFile ( " mod/ { $this -> module } .php " );
$module_class = LegacyModule :: class ;
}
2019-08-12 18:13:58 +02:00
2019-10-11 17:55:02 +02:00
$module_class = $module_class ? : PageNotFound :: class ;
}
2019-08-12 18:13:58 +02:00
2019-11-05 06:03:05 +01:00
return new Module ( $this -> module , $module_class , $module_parameters , $this -> isBackend , $printNotAllowedAddon );
2019-08-12 18:13:58 +02:00
}
/**
* Run the determined module class and calls all hooks applied to
*
2020-01-18 20:59:39 +01:00
* @ param \Friendica\Core\L10n $l10n The L10n instance
* @ param App\BaseURL $baseUrl The Friendica Base URL
* @ param LoggerInterface $logger The Friendica logger
* @ param array $server The $_SERVER variable
* @ param array $post The $_POST variables
2019-08-12 18:13:58 +02:00
*
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
2020-01-18 20:59:39 +01:00
public function run ( Core\L10n $l10n , App\BaseURL $baseUrl , LoggerInterface $logger , array $server , array $post )
2019-08-12 18:13:58 +02:00
{
if ( $this -> printNotAllowedAddon ) {
info ( $l10n -> t ( " You must be logged in to use addons. " ));
}
/* The URL provided does not resolve to a valid module .
*
* On Dreamhost sites , quite often things go wrong for no apparent reason and they send us to '/internal_error.html' .
* We don ' t like doing this , but as it occasionally accounts for 10 - 20 % or more of all site traffic -
* we are going to trap this and redirect back to the requested page . As long as you don ' t have a critical error on your page
* this will often succeed and eventually do the right thing .
*
* Otherwise we are going to emit a 404 not found .
*/
if ( $this -> module_class === PageNotFound :: class ) {
$queryString = $server [ 'QUERY_STRING' ];
// Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
if ( ! empty ( $queryString ) && preg_match ( '/{[0-9]}/' , $queryString ) !== 0 ) {
exit ();
}
if ( ! empty ( $queryString ) && ( $queryString === 'q=internal_error.html' ) && isset ( $dreamhost_error_hack )) {
$logger -> info ( 'index.php: dreamhost_error_hack invoked.' , [ 'Original URI' => $server [ 'REQUEST_URI' ]]);
2019-12-16 00:28:31 +01:00
$baseUrl -> redirect ( $server [ 'REQUEST_URI' ]);
2019-08-12 18:13:58 +02:00
}
$logger -> debug ( 'index.php: page not found.' , [ 'request_uri' => $server [ 'REQUEST_URI' ], 'address' => $server [ 'REMOTE_ADDR' ], 'query' => $server [ 'QUERY_STRING' ]]);
}
$placeholder = '' ;
Core\Hook :: callAll ( $this -> module . '_mod_init' , $placeholder );
2019-11-05 06:03:05 +01:00
call_user_func ([ $this -> module_class , 'init' ], $this -> module_parameters );
2019-08-12 18:13:58 +02:00
if ( $server [ 'REQUEST_METHOD' ] === 'POST' ) {
Core\Hook :: callAll ( $this -> module . '_mod_post' , $post );
2019-11-05 06:03:05 +01:00
call_user_func ([ $this -> module_class , 'post' ], $this -> module_parameters );
2019-08-12 18:13:58 +02:00
}
Core\Hook :: callAll ( $this -> module . '_mod_afterpost' , $placeholder );
2019-11-05 06:03:05 +01:00
call_user_func ([ $this -> module_class , 'afterpost' ], $this -> module_parameters );
2019-12-11 09:31:28 +01:00
// "rawContent" is especially meant for technical endpoints.
// This endpoint doesn't need any theme initialization or other comparable stuff.
call_user_func ([ $this -> module_class , 'rawContent' ], $this -> module_parameters );
2019-08-12 18:13:58 +02:00
}
}