mirror of
https://git.sekbaer.de/Friendica/friendica.git
synced 2025-06-12 01:54:26 +02:00
Add Arguments & Modules class
This commit is contained in:
parent
f068d00645
commit
0af9747c6c
12 changed files with 1083 additions and 358 deletions
194
src/App/Arguments.php
Normal file
194
src/App/Arguments.php
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\App;
|
||||
|
||||
/**
|
||||
* Determine all arguments of the current call, including
|
||||
* - The whole querystring (except the pagename/q parameter)
|
||||
* - The command
|
||||
* - The arguments (C-Style based)
|
||||
* - The count of arguments
|
||||
*/
|
||||
class Arguments
|
||||
{
|
||||
/**
|
||||
* @var string The complete query string
|
||||
*/
|
||||
private $queryString;
|
||||
/**
|
||||
* @var string The current Friendica command
|
||||
*/
|
||||
private $command;
|
||||
/**
|
||||
* @var array The arguments of the current execution
|
||||
*/
|
||||
private $argv;
|
||||
/**
|
||||
* @var int The count of arguments
|
||||
*/
|
||||
private $argc;
|
||||
|
||||
public function __construct(string $queryString = '', string $command = '', array $argv = [Module::DEFAULT], int $argc = 1)
|
||||
{
|
||||
$this->queryString = $queryString;
|
||||
$this->command = $command;
|
||||
$this->argv = $argv;
|
||||
$this->argc = $argc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The whole query string of this call
|
||||
*/
|
||||
public function getQueryString()
|
||||
{
|
||||
return $this->queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The whole command of this call
|
||||
*/
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array All arguments of this call
|
||||
*/
|
||||
public function getArgv()
|
||||
{
|
||||
return $this->argv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int The count of arguments of this call
|
||||
*/
|
||||
public function getArgc()
|
||||
{
|
||||
return $this->argc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 get(int $position, $default = '')
|
||||
{
|
||||
return $this->has($position) ? $this->argv[$position] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $position
|
||||
*
|
||||
* @return bool if the argument position exists
|
||||
*/
|
||||
public function has(int $position)
|
||||
{
|
||||
return array_key_exists($position, $this->argv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the arguments of the current call
|
||||
*
|
||||
* @param array $server The $_SERVER variable
|
||||
* @param array $get The $_GET variable
|
||||
*
|
||||
* @return Arguments The determined arguments
|
||||
*/
|
||||
public function determine(array $server, array $get)
|
||||
{
|
||||
$queryString = '';
|
||||
|
||||
if (!empty($server['QUERY_STRING']) && strpos($server['QUERY_STRING'], 'pagename=') === 0) {
|
||||
$queryString = substr($server['QUERY_STRING'], 9);
|
||||
} elseif (!empty($server['QUERY_STRING']) && strpos($server['QUERY_STRING'], 'q=') === 0) {
|
||||
$queryString = substr($server['QUERY_STRING'], 2);
|
||||
}
|
||||
|
||||
// eventually strip ZRL
|
||||
$queryString = $this->stripZRLs($queryString);
|
||||
|
||||
// eventually strip OWT
|
||||
$queryString = $this->stripQueryParam($queryString, 'owt');
|
||||
|
||||
// removing trailing / - maybe a nginx problem
|
||||
$queryString = ltrim($queryString, '/');
|
||||
|
||||
if (!empty($get['pagename'])) {
|
||||
$command = trim($get['pagename'], '/\\');
|
||||
} elseif (!empty($get['q'])) {
|
||||
$command = trim($get['q'], '/\\');
|
||||
} else {
|
||||
$command = Module::DEFAULT;
|
||||
}
|
||||
|
||||
|
||||
// fix query_string
|
||||
if (!empty($command)) {
|
||||
$queryString = str_replace(
|
||||
$command . '&',
|
||||
$command . '?',
|
||||
$queryString
|
||||
);
|
||||
}
|
||||
|
||||
// unix style "homedir"
|
||||
if (substr($command, 0, 1) === '~') {
|
||||
$command = 'profile/' . substr($command, 1);
|
||||
}
|
||||
|
||||
// Diaspora style profile url
|
||||
if (substr($command, 0, 2) === 'u/') {
|
||||
$command = 'profile/' . substr($command, 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".
|
||||
*/
|
||||
|
||||
$argv = explode('/', $command);
|
||||
$argc = count($argv);
|
||||
|
||||
|
||||
return new Arguments($queryString, $command, $argv, $argc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip zrl parameter from a string.
|
||||
*
|
||||
* @param string $queryString The input string.
|
||||
*
|
||||
* @return string The zrl.
|
||||
*/
|
||||
public function stripZRLs(string $queryString)
|
||||
{
|
||||
return preg_replace('/[?&]zrl=(.*?)(&|$)/ism', '$2', $queryString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip query parameter from a string.
|
||||
*
|
||||
* @param string $queryString The input string.
|
||||
* @param string $param
|
||||
*
|
||||
* @return string The query parameter.
|
||||
*/
|
||||
public function stripQueryParam(string $queryString, string $param)
|
||||
{
|
||||
return preg_replace('/[?&]' . $param . '=(.*?)(&|$)/ism', '$2', $queryString);
|
||||
}
|
||||
}
|
|
@ -24,27 +24,9 @@ class Mode
|
|||
*/
|
||||
private $mode;
|
||||
|
||||
/**
|
||||
* @var string the basepath of the application
|
||||
*/
|
||||
private $basepath;
|
||||
|
||||
/**
|
||||
* @var Database
|
||||
*/
|
||||
private $database;
|
||||
|
||||
/**
|
||||
* @var ConfigCache
|
||||
*/
|
||||
private $configCache;
|
||||
|
||||
public function __construct(BasePath $basepath, Database $database, ConfigCache $configCache)
|
||||
public function __construct(int $mode = 0)
|
||||
{
|
||||
$this->basepath = $basepath->getPath();
|
||||
$this->database = $database;
|
||||
$this->configCache = $configCache;
|
||||
$this->mode = 0;
|
||||
$this->mode = $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,50 +36,46 @@ class Mode
|
|||
* - App::MODE_MAINTENANCE: The maintenance mode has been set
|
||||
* - App::MODE_NORMAL : Normal run with all features enabled
|
||||
*
|
||||
* @param string $basePath the Basepath of the Application
|
||||
*
|
||||
* @return Mode returns itself
|
||||
* @return Mode returns the determined mode
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function determine($basePath = null)
|
||||
public function determine(BasePath $basepath, Database $database, ConfigCache $configCache)
|
||||
{
|
||||
if (!empty($basePath)) {
|
||||
$this->basepath = $basePath;
|
||||
$mode = 0;
|
||||
|
||||
$basepathName = $basepath->getPath();
|
||||
|
||||
if (!file_exists($basepathName . '/config/local.config.php')
|
||||
&& !file_exists($basepathName . '/config/local.ini.php')
|
||||
&& !file_exists($basepathName . '/.htconfig.php')) {
|
||||
return new Mode($mode);
|
||||
}
|
||||
|
||||
$this->mode = 0;
|
||||
$mode |= Mode::LOCALCONFIGPRESENT;
|
||||
|
||||
if (!file_exists($this->basepath . '/config/local.config.php')
|
||||
&& !file_exists($this->basepath . '/config/local.ini.php')
|
||||
&& !file_exists($this->basepath . '/.htconfig.php')) {
|
||||
return $this;
|
||||
if (!$database->connected()) {
|
||||
return new Mode($mode);
|
||||
}
|
||||
|
||||
$this->mode |= Mode::LOCALCONFIGPRESENT;
|
||||
$mode |= Mode::DBAVAILABLE;
|
||||
|
||||
if (!$this->database->connected()) {
|
||||
return $this;
|
||||
if ($database->fetchFirst("SHOW TABLES LIKE 'config'") === false) {
|
||||
return new Mode($mode);
|
||||
}
|
||||
|
||||
$this->mode |= Mode::DBAVAILABLE;
|
||||
$mode |= Mode::DBCONFIGAVAILABLE;
|
||||
|
||||
if ($this->database->fetchFirst("SHOW TABLES LIKE 'config'") === false) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->mode |= Mode::DBCONFIGAVAILABLE;
|
||||
|
||||
if (!empty($this->configCache->get('system', 'maintenance')) ||
|
||||
if (!empty($configCache->get('system', 'maintenance')) ||
|
||||
// Don't use Config or Configuration here because we're possibly BEFORE initializing the Configuration,
|
||||
// so this could lead to a dependency circle
|
||||
!empty($this->database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) {
|
||||
return $this;
|
||||
!empty($database->selectFirst('config', ['v'], ['cat' => 'system', 'k' => 'maintenance'])['v'])) {
|
||||
return new Mode($mode);
|
||||
}
|
||||
|
||||
$this->mode |= Mode::MAINTENANCEDISABLED;
|
||||
$mode |= Mode::MAINTENANCEDISABLED;
|
||||
|
||||
return $this;
|
||||
return new Mode($mode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
279
src/App/Module.php
Normal file
279
src/App/Module.php
Normal file
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\App;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Core;
|
||||
use Friendica\LegacyModule;
|
||||
use Friendica\Module\Home;
|
||||
use Friendica\Module\PageNotFound;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Holds the common context of the current, loaded module
|
||||
*/
|
||||
class Module
|
||||
{
|
||||
const DEFAULT = 'home';
|
||||
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;
|
||||
|
||||
/**
|
||||
* @var BaseObject The module class
|
||||
*/
|
||||
private $module_class;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isBackend()
|
||||
{
|
||||
return $this->isBackend;
|
||||
}
|
||||
|
||||
public function __construct(string $module = self::DEFAULT, string $moduleClass = self::DEFAULT_CLASS, bool $isBackend = false, bool $printNotAllowedAddon = false)
|
||||
{
|
||||
$this->module = $module;
|
||||
$this->module_class = $moduleClass;
|
||||
$this->isBackend = $isBackend;
|
||||
$this->printNotAllowedAddon = $printNotAllowedAddon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the current module based on the App arguments and the server variable
|
||||
*
|
||||
* @param Arguments $args The Friendica arguments
|
||||
* @param array $server The $_SERVER variable
|
||||
*
|
||||
* @return Module The module with the determined module
|
||||
*/
|
||||
public function determineModule(Arguments $args, array $server)
|
||||
{
|
||||
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";
|
||||
}
|
||||
|
||||
$isBackend = $this->checkBackend($module, $server);
|
||||
|
||||
return new Module($module, $this->module_class, $isBackend, $this->printNotAllowedAddon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the class of the current module
|
||||
*
|
||||
* @param Arguments $args The Friendica execution arguments
|
||||
* @param Router $router The Friendica routing instance
|
||||
* @param Core\Config\Configuration $config The Friendica Configuration
|
||||
*
|
||||
* @return Module The determined module of this call
|
||||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function determineClass(Arguments $args, Router $router, Core\Config\Configuration $config)
|
||||
{
|
||||
$printNotAllowedAddon = false;
|
||||
|
||||
/**
|
||||
* 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
|
||||
$router->collectRoutes();
|
||||
|
||||
$data = $router->getRouteCollector();
|
||||
Core\Hook::callAll('route_collection', $data);
|
||||
|
||||
$module_class = $router->getModuleClass($args->getCommand());
|
||||
|
||||
// Then we try addon-provided modules that we wrap in the LegacyModule class
|
||||
if (!$module_class && 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
$module_class = !isset($module_class) ? PageNotFound::class : $module_class;
|
||||
|
||||
return new Module($this->module, $module_class, $this->isBackend, $printNotAllowedAddon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the determined module class and calls all hooks applied to
|
||||
*
|
||||
* @param Core\L10n\L10n $l10n The L10n instance
|
||||
* @param App $app The whole Friendica app (for method arguments)
|
||||
* @param LoggerInterface $logger The Friendica logger
|
||||
* @param string $currentTheme The chosen theme
|
||||
* @param array $server The $_SERVER variable
|
||||
* @param array $post The $_POST variables
|
||||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function run(Core\L10n\L10n $l10n, App $app, LoggerInterface $logger, string $currentTheme, array $server, array $post)
|
||||
{
|
||||
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']]);
|
||||
$app->internalRedirect($server['REQUEST_URI']);
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
call_user_func([$this->module_class, 'init']);
|
||||
|
||||
// "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']);
|
||||
|
||||
// Load current theme info after module has been initialized as theme could have been set in module
|
||||
$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);
|
||||
}
|
||||
|
||||
if ($server['REQUEST_METHOD'] === 'POST') {
|
||||
Core\Hook::callAll($this->module . '_mod_post', $post);
|
||||
call_user_func([$this->module_class, 'post']);
|
||||
}
|
||||
|
||||
Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
|
||||
call_user_func([$this->module_class, 'afterpost']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* @param string $module The determined module
|
||||
* @param array $server The $_SERVER variable
|
||||
*
|
||||
* @return bool True, if the current module is called at backend
|
||||
*/
|
||||
private function checkBackend($module, array $server)
|
||||
{
|
||||
// Check if current module is in backend or backend flag is set
|
||||
return basename(($server['PHP_SELF'] ?? ''), '.php') !== 'index' &&
|
||||
in_array($module, Module::BACKEND_MODULES);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue